| """This is a substantially improved version of the older Interpreter.py demo |
| It creates a simple GUI JPython console window with simple history |
| as well as the ability to interupt running code (with the ESC key). |
| |
| Like Interpreter.py, this is still just a demo, and needs substantial |
| work before serious use. |
| |
| Thanks to Geza Groma (groma@everx.szbk.u-szeged.hu) for several valuable |
| ideas for this tool -- his JPConsole is a more refined implementation |
| of similar ideas. |
| """ |
| |
| from Styles import Styles |
| from Keymap import Keymap |
| |
| from pawt import swing, colors |
| from java.awt.event.KeyEvent import VK_UP, VK_DOWN |
| from java.awt.event import ActionEvent |
| from java.lang import Thread, System |
| from code import compile_command |
| import string, sys, re |
| |
| class OutputBuffer: |
| def __init__(self, console, stylename): |
| self.console = console |
| self.stylename = stylename |
| |
| def flush(self): |
| pass |
| |
| def write(self, text): |
| self.console.write(text, self.stylename) |
| |
| class Console: |
| def __init__(self, styles=None, keymap=None): |
| if styles is None: |
| styles = Styles() |
| basic = styles.add('normal', tabsize=3, fontSize=12, fontFamily="Courier") |
| styles.add('error', parent=basic, foreground=colors.red) |
| styles.add('output', parent=basic, foreground=colors.blue) |
| styles.add('input', parent=basic, foreground=colors.black) |
| styles.add('prompt', parent=basic, foreground=colors.purple) |
| self.styles = styles |
| |
| # This is a hack to get at an inner class |
| # This will not be required in JPython-1.1 |
| ForegroundAction = getattr(swing.text, 'StyledEditorKit$ForegroundAction') |
| self.inputAction = ForegroundAction("start input", colors.black) |
| |
| if keymap is None: |
| keymap = Keymap() |
| keymap.bind('enter', self.enter) |
| keymap.bind('tab', self.tab) |
| keymap.bind('escape', self.escape) |
| keymap.bind('up', self.uphistory) |
| keymap.bind('down', self.downhistory) |
| |
| self.keymap = keymap |
| |
| self.document = swing.text.DefaultStyledDocument(self.styles) |
| self.document.setLogicalStyle(0, self.styles.get('normal')) |
| |
| self.textpane = swing.JTextPane(self.document) |
| self.textpane.keymap = self.keymap |
| |
| self.history = [] |
| self.oldHistoryLength = 0 |
| self.historyPosition = 0 |
| |
| self.command = [] |
| self.locals = {} |
| |
| def write(self, text, stylename='normal'): |
| style = self.styles.get(stylename) |
| self.document.insertString(self.document.length, text, style) |
| |
| def beep(self): |
| self.textpane.toolkit.beep() |
| |
| def startUserInput(self, prompt=None): |
| if prompt is not None: |
| self.write(prompt, 'prompt') |
| self.startInput = self.document.createPosition(self.document.length-1) |
| #self.document.setCharacterAttributes(self.document.length-1, 1, self.styles.get('input'), 1) |
| self.textpane.caretPosition = self.document.length |
| ae = ActionEvent(self.textpane, ActionEvent.ACTION_PERFORMED, 'start input') |
| self.inputAction.actionPerformed(ae) |
| |
| def getinput(self): |
| offset = self.startInput.offset |
| line = self.document.getText(offset+1, self.document.length-offset) |
| return string.rstrip(line) |
| |
| def replaceinput(self, text): |
| offset = self.startInput.offset + 1 |
| self.document.remove(offset, self.document.length-offset) |
| self.write(text, 'input') |
| |
| def enter(self): |
| line = self.getinput() |
| self.write('\n', 'input') |
| |
| self.history.append(line) |
| self.handleLine(line) |
| |
| def gethistory(self, direction): |
| historyLength = len(self.history) |
| if self.oldHistoryLength < historyLength: |
| # new line was entered after last call |
| self.oldHistoryLength = historyLength |
| if self.history[self.historyPosition] != self.history[-1]: |
| self.historyPosition = historyLength |
| |
| pos = self.historyPosition + direction |
| |
| if 0 <= pos < historyLength: |
| self.historyPosition = pos |
| self.replaceinput(self.history[pos]) |
| else: |
| self.beep() |
| |
| def uphistory(self): |
| self.gethistory(-1) |
| |
| def downhistory(self): |
| self.gethistory(1) |
| |
| def tab(self): |
| self.write('\t', 'input') |
| |
| def escape(self): |
| if (not hasattr(self, 'pythonThread') or self.pythonThread is None or not self.pythonThread.alive): |
| self.beep() |
| return |
| |
| self.pythonThread.stopPython() |
| |
| def capturePythonOutput(self, stdoutStyle='output', stderrStyle='error'): |
| import sys |
| sys.stdout = OutputBuffer(self, stdoutStyle) |
| sys.stderr = OutputBuffer(self, stderrStyle) |
| |
| def handleLine(self, text): |
| self.command.append(text) |
| |
| try: |
| code = compile_command(string.join(self.command, '\n')) |
| except SyntaxError: |
| traceback.print_exc(0) |
| self.command = [] |
| self.startUserInput(str(sys.ps1)+'\t') |
| return |
| |
| if code is None: |
| self.startUserInput(str(sys.ps2)+'\t') |
| return |
| |
| self.command = [] |
| |
| pt = PythonThread(code, self) |
| self.pythonThread = pt |
| pt.start() |
| |
| def newInput(self): |
| self.startUserInput(str(sys.ps1)+'\t') |
| |
| import traceback |
| |
| class PythonThread(Thread): |
| def __init__(self, code, console): |
| self.code = code |
| self.console = console |
| self.locals = console.locals |
| |
| def run(self): |
| try: |
| exec self.code in self.locals |
| |
| #Include these lines to actually exit on a sys.exit() call |
| #except SystemExit, value: |
| # raise SystemExit, value |
| |
| except: |
| exc_type, exc_value, exc_traceback = sys.exc_info() |
| l = len(traceback.extract_tb(sys.exc_traceback)) |
| try: |
| 1/0 |
| except: |
| m = len(traceback.extract_tb(sys.exc_traceback)) |
| traceback.print_exception(exc_type, exc_value, exc_traceback, l-m) |
| |
| self.console.newInput() |
| |
| def stopPython(self): |
| #Should spend 2 seconds trying to kill thread in nice Python style first... |
| self.stop() |
| |
| header = """\ |
| JPython %(version)s on %(platform)s |
| %(copyright)s |
| """ % {'version':sys.version, 'platform':sys.platform, 'copyright':sys.copyright} |
| |
| if __name__ == '__main__': |
| c = Console() |
| pane = swing.JScrollPane(c.textpane) |
| swing.test(pane, size=(500,400), name='JPython Console') |
| c.write(header, 'output') |
| c.capturePythonOutput() |
| c.textpane.requestFocus() |
| c.newInput() |