# System Imports import code, string, sys, traceback import gtk True = 1 False = 0 # Twisted Imports from twisted.spread.ui import gtkutil from twisted.python import util rcfile = util.sibpath(__file__, 'gtkrc') gtk.rc_parse(rcfile) def isCursorOnFirstLine(entry): firstnewline = string.find(entry.get_chars(0,-1), '\n') if entry.get_point() <= firstnewline or firstnewline == -1: return 1 def isCursorOnLastLine(entry): if entry.get_point() >= string.rfind(string.rstrip(entry.get_chars(0,-1)), '\n'): return 1 class InputText(gtk.GtkText): linemode = 0 blockcount = 0 def __init__(self, toplevel=None): gtk.GtkText.__init__(self) self['name'] = 'Input' self.set_editable(gtk.TRUE) self.connect("key_press_event", self.processKey) #self.set_word_wrap(gtk.TRUE) self.history = [] self.histpos = 0 if toplevel: self.toplevel = toplevel def historyUp(self): if self.histpos > 0: self.histpos = self.histpos - 1 self.delete_text(0, -1) self.insert_defaults(self.history[self.histpos]) self.set_position(0) def historyDown(self): if self.histpos < len(self.history) - 1: self.histpos = self.histpos + 1 self.delete_text(0, -1) self.insert_defaults(self.history[self.histpos]) elif self.histpos == len(self.history) - 1: self.histpos = self.histpos + 1 self.delete_text(0, -1) def processKey(self, entry, event): # TODO: make key bindings easier to customize. stopSignal = False # ASSUMPTION: Assume Meta == mod4 isMeta = event.state & gtk.GDK.MOD4_MASK if event.keyval == gtk.GDK.Return: isShift = event.state & gtk.GDK.SHIFT_MASK if isShift: self.linemode = True self.insert_defaults('\n') else: stopSignal = True text = self.get_chars(0,-1) if not text: return try: if text[0] == '/': # It's a local-command, don't evaluate it as # Python. c = True else: # This will tell us it's a complete expression. c = code.compile_command(text) except SyntaxError, e: # Ding! self.set_positionLineOffset(e.lineno, e.offset) print "offset", e.offset errmsg = {'traceback': [], 'exception': [str(e) + '\n']} self.toplevel.output.console([('exception', errmsg)]) except OverflowError, e: e = traceback.format_exception_only(OverflowError, e) errmsg = {'traceback': [], 'exception': e} self.toplevel.output.console([('exception', errmsg)]) else: if c is None: self.linemode = True stopSignal = False else: self.sendMessage(entry) self.clear() elif ((event.keyval == gtk.GDK.Up and isCursorOnFirstLine(self)) or (isMeta and event.string == 'p')): self.historyUp() stopSignal = True elif ((event.keyval == gtk.GDK.Down and isCursorOnLastLine(self)) or (isMeta and event.string == 'n')): self.historyDown() stopSignal = True if stopSignal: self.emit_stop_by_name("key_press_event") return True def clear(self): self.delete_text(0, -1) self.linemode = False def set_positionLineOffset(self, line, offset): text = self.get_chars(0, -1) pos = 0 for l in xrange(line - 1): pos = string.index(text, '\n', pos) + 1 pos = pos + offset - 1 self.set_position(pos) def sendMessage(self, unused_data=None): text = self.get_chars(0,-1) if self.linemode: self.blockcount = self.blockcount + 1 fmt = ">>> # begin %s\n%%s\n#end %s\n" % ( self.blockcount, self.blockcount) else: fmt = ">>> %s\n" self.history.append(text) self.histpos = len(self.history) self.toplevel.output.console([['command',fmt % text]]) self.toplevel.codeInput(text) def readHistoryFile(self, filename=None): if filename is None: filename = self.historyfile f = open(filename, 'r', 1) self.history.extend(f.readlines()) f.close() self.histpos = len(self.history) def writeHistoryFile(self, filename=None): if filename is None: filename = self.historyfile f = open(filename, 'a', 1) f.writelines(self.history) f.close() class Interaction(gtk.GtkWindow): titleText = "Abstract Python Console" def __init__(self): gtk.GtkWindow.__init__(self, gtk.WINDOW_TOPLEVEL) self.set_title(self.titleText) self.set_default_size(300, 300) self.set_name("Manhole") vbox = gtk.GtkVBox() pane = gtk.GtkVPaned() self.output = OutputConsole(toplevel=self) pane.pack1(gtkutil.scrollify(self.output), gtk.TRUE, gtk.FALSE) self.input = InputText(toplevel=self) pane.pack2(gtkutil.scrollify(self.input), gtk.FALSE, gtk.TRUE) vbox.pack_start(pane, 1,1,0) self.add(vbox) self.input.grab_focus() def codeInput(self, text): raise NotImplementedError("Bleh.") class LocalInteraction(Interaction): titleText = "Local Python Console" def __init__(self): Interaction.__init__(self) self.globalNS = {} self.localNS = {} self.filename = "" def codeInput(self, text): from twisted.manhole.service import runInConsole val = runInConsole(text, self.output.console, self.globalNS, self.localNS, self.filename) if val is not None: self.localNS["_"] = val self.output.console([("result", repr(val) + "\n")]) class OutputConsole(gtk.GtkText): maxBufSz = 10000 def __init__(self, toplevel=None): gtk.GtkText.__init__(self) self['name'] = "Console" gtkutil.defocusify(self) #self.set_word_wrap(gtk.TRUE) if toplevel: self.toplevel = toplevel def console(self, message): self.set_point(self.get_length()) self.freeze() previous_kind = None style = self.get_style() style_cache = {} try: for element in message: if element[0] == 'exception': s = traceback.format_list(element[1]['traceback']) s.extend(element[1]['exception']) s = string.join(s, '') else: s = element[1] if element[0] != previous_kind: style = style_cache.get(element[0], None) if style is None: gtk.rc_parse_string( 'widget \"Manhole.*.Console\" ' 'style \"Console_%s\"\n' % (element[0])) self.set_rc_style() style_cache[element[0]] = style = self.get_style() # XXX: You'd think we'd use style.bg instead of 'None' # here, but that doesn't seem to match the color of # the backdrop. self.insert(style.font, style.fg[gtk.STATE_NORMAL], None, s) previous_kind = element[0] l = self.get_length() diff = self.maxBufSz - l if diff < 0: diff = - diff self.delete_text(0,diff) finally: self.thaw() a = self.get_vadjustment() a.set_value(a.upper - a.page_size)