#! /usr/bin/env python import os import sys import string import getopt import re import linecache from code import InteractiveInterpreter from Tkinter import * import tkMessageBox from EditorWindow import fixwordbreaks from FileList import FileList, MultiEditorWindow, MultiIOBinding from ColorDelegator import ColorDelegator class PyShellEditorWindow(MultiEditorWindow): def fixedwindowsmenu(self, wmenu): wmenu.add_command(label="Python Shell", command=self.flist.open_shell) wmenu.add_separator() class PyShellFileList(FileList): EditorWindow = PyShellEditorWindow pyshell = None def open_shell(self): if self.pyshell: self.pyshell.wakeup() else: self.pyshell = PyShell(self) self.pyshell.begin() return self.pyshell class ModifiedIOBinding(MultiIOBinding): def defaultfilename(self, mode="open"): if self.filename: return MultiIOBinding.defaultfilename(self, mode) else: try: pwd = os.getcwd() except os.error: pwd = "" return pwd, "" def open(self, event): # Override base class method -- don't allow reusing this window filename = self.askopenfile() if filename: self.flist.open(filename) return "break" def maybesave(self): # Override base class method -- don't ask any questions if self.text.get_saved(): return "yes" else: return "no" class ModifiedColorDelegator(ColorDelegator): def recolorize_main(self): self.tag_remove("TODO", "1.0", "iomark") self.tag_add("SYNC", "1.0", "iomark") ColorDelegator.recolorize_main(self) class ModifiedInterpreter(InteractiveInterpreter): def __init__(self, tkconsole): self.tkconsole = tkconsole InteractiveInterpreter.__init__(self) gid = 0 def runsource(self, source): # Extend base class to stuff the source in the line cache filename = "" % self.gid self.gid = self.gid + 1 lines = string.split(source, "\n") linecache.cache[filename] = len(source)+1, 0, lines, filename self.more = 0 return InteractiveInterpreter.runsource(self, source, filename) def showsyntaxerror(self, filename=None): # Extend base class to color the offending position # (instead of printing it and pointing at it with a caret) text = self.tkconsole.text stuff = self.unpackerror() if not stuff: self.tkconsole.resetoutput() InteractiveInterpreter.showsyntaxerror(self, filename) return msg, lineno, offset, line = stuff if lineno == 1: pos = "iomark + %d chars" % (offset-1) else: pos = "iomark linestart + %d lines + %d chars" % (lineno-1, offset-1) text.tag_add("ERROR", pos) text.see(pos) char = text.get(pos) if char in string.letters + string.digits + "_": text.tag_add("ERROR", pos + " wordstart", pos) self.tkconsole.resetoutput() self.write("SyntaxError: %s\n" % str(msg)) def unpackerror(self): type, value, tb = sys.exc_info() ok = type == SyntaxError if ok: try: msg, (dummy_filename, lineno, offset, line) = value except: ok = 0 if ok: return msg, lineno, offset, line else: return None def showtraceback(self): # Extend base class method to reset output properly text = self.tkconsole.text self.tkconsole.resetoutput() self.checklinecache() InteractiveInterpreter.showtraceback(self) def checklinecache(self): c = linecache.cache for key in c.keys(): if key[:1] + key[-1:] != "<>": del c[key] debugger = None def setdebugger(self, debugger): self.debugger = debugger def getdebugger(self): return self.debugger def runcode(self, code): # Override base class method debugger = self.debugger try: self.tkconsole.beginexecuting() try: if debugger: debugger.run(code, self.locals) else: exec code in self.locals except SystemExit: if tkMessageBox.askyesno( "Exit?", "Do you want to exit altogether?", default="yes", master=self.tkconsole.text): raise else: self.showtraceback() except: self.showtraceback() finally: self.tkconsole.endexecuting() def write(self, s): # Override base class write self.tkconsole.console.write(s) class PyShell(PyShellEditorWindow): # Override classes ColorDelegator = ModifiedColorDelegator IOBinding = ModifiedIOBinding # Override menu bar specs menu_specs = PyShellEditorWindow.menu_specs[:] menu_specs.insert(len(menu_specs)-1, ("debug", "Debug")) # New class from History import History def __init__(self, flist=None): self.interp = ModifiedInterpreter(self) if flist is None: root = Tk() fixwordbreaks(root) root.withdraw() flist = PyShellFileList(root) PyShellEditorWindow.__init__(self, flist, None, None) self.config_colors() import __builtin__ __builtin__.quit = __builtin__.exit = "To exit, type Ctrl-D." self.auto.config(prefertabs=1) text = self.text text.bind("<>", self.enter_callback) text.bind("<>", self.linefeed_callback) text.bind("<>", self.cancel_callback) text.bind("<>", self.home_callback) text.bind("<>", self.eof_callback) text.bind("<>", self.goto_traceback_line) text.bind("<>", self.open_stack_viewer) text.bind("<>", self.toggle_debugger) sys.stdout = PseudoFile(self, "stdout") sys.stderr = PseudoFile(self, "stderr") sys.stdin = self self.console = PseudoFile(self, "console") self.history = self.History(self.text) tagdefs = { ##"stdin": {"background": "yellow"}, "stdout": {"foreground": "blue"}, "stderr": {"foreground": "#007700"}, "console": {"foreground": "red"}, "ERROR": {"background": "#FF7777"}, None: {"foreground": "purple"}, # default } def config_colors(self): for tag, cnf in self.tagdefs.items(): if cnf: if not tag: apply(self.text.configure, (), cnf) else: apply(self.text.tag_configure, (tag,), cnf) reading = 0 executing = 0 canceled = 0 endoffile = 0 def toggle_debugger(self, event=None): if self.executing: tkMessageBox.showerror("Don't debug now", "You can only toggle the debugger when idle", master=self.text) return "break" db = self.interp.getdebugger() if db: self.close_debugger() else: self.open_debugger() def close_debugger(self): db = self.interp.getdebugger() if db: self.interp.setdebugger(None) db.close() self.resetoutput() self.console.write("[DEBUG OFF]\n") sys.ps1 = ">>> " self.showprompt() def open_debugger(self): import Debugger self.interp.setdebugger(Debugger.Debugger(self)) sys.ps1 = "[DEBUG ON]>>> " self.showprompt() self.top.tkraise() self.text.focus_set() def beginexecuting(self): # Helper for ModifiedInterpreter self.resetoutput() self.executing = 1 self._cancel_check = self.cancel_check ##sys.settrace(self._cancel_check) def endexecuting(self): # Helper for ModifiedInterpreter sys.settrace(None) self.executing = 0 self.canceled = 0 def close(self): # Extend base class method if self.executing: # XXX Need to ask a question here if not tkMessageBox.askokcancel( "Cancel?", "The program is still running; do you want to cancel it?", default="ok", master=self.text): return "cancel" self.canceled = 1 if self.reading: self.top.quit() return "cancel" reply = PyShellEditorWindow.close(self) if reply != "cancel": self.flist.pyshell = None # Restore std streams sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ sys.stdin = sys.__stdin__ # Break cycles self.interp = None self.console = None return reply def ispythonsource(self, filename): # Override this so EditorWindow never removes the colorizer return 1 def saved_change_hook(self): # Override this to get the title right title = "Python Shell" if self.io.filename: title = title + ": " + self.io.filename if not self.undo.get_saved(): title = title + " *" self.top.wm_title(title) def begin(self): self.resetoutput() self.write("Python %s on %s\n%s\n" % (sys.version, sys.platform, sys.copyright)) try: sys.ps1 except AttributeError: sys.ps1 = ">>> " self.showprompt() import Tkinter Tkinter._default_root = None def interact(self): self.begin() self.top.mainloop() def readline(self): save = self.reading try: self.reading = 1 self.top.mainloop() finally: self.reading = save line = self.text.get("iomark", "end-1c") self.resetoutput() if self.canceled: self.canceled = 0 raise KeyboardInterrupt if self.endoffile: self.endoffile = 0 return "" return line def cancel_callback(self, event): try: if self.text.compare("sel.first", "!=", "sel.last"): return # Active selection -- always use default binding except: pass if not (self.executing or self.reading): self.resetoutput() self.write("KeyboardInterrupt\n") self.showprompt() return "break" self.endoffile = 0 self.canceled = 1 if self.reading: self.top.quit() return "break" def eof_callback(self, event): if self.executing and not self.reading: return # Let the default binding (delete next char) take over if not (self.text.compare("iomark", "==", "insert") and self.text.compare("insert", "==", "end-1c")): return # Let the default binding (delete next char) take over if not self.executing: ## if not tkMessageBox.askokcancel( ## "Exit?", ## "Are you sure you want to exit?", ## default="ok", master=self.text): ## return "break" self.resetoutput() self.close() else: self.canceled = 0 self.endoffile = 1 self.top.quit() return "break" def home_callback(self, event): if event.state != 0 and event.keysym == "Home": return # ; fall back to class binding if self.text.compare("iomark", "<=", "insert") and \ self.text.compare("insert linestart", "<=", "iomark"): self.text.mark_set("insert", "iomark") self.text.tag_remove("sel", "1.0", "end") self.text.see("insert") return "break" def linefeed_callback(self, event): # Insert a linefeed without entering anything (still autoindented) if self.reading: self.text.insert("insert", "\n") self.text.see("insert") else: self.auto.autoindent(event) return "break" def enter_callback(self, event): if self.executing and not self.reading: return # Let the default binding (insert '\n') take over # If some text is selected, recall the selection # (but only if this before the I/O mark) try: sel = self.text.get("sel.first", "sel.last") if sel: if self.text.compare("self.last", "<=", "iomark"): self.recall(sel) return "break" except: pass # If we're strictly before the line containing iomark, recall # the current line, less a leading prompt, less leading or # trailing whitespace if self.text.compare("insert", "<", "iomark linestart"): # Check if there's a relevant stdin range -- if so, use it prev = self.text.tag_prevrange("stdin", "insert") if prev and self.text.compare("insert", "<", prev[1]): self.recall(self.text.get(prev[0], prev[1])) return "break" next = self.text.tag_nextrange("stdin", "insert") if next and self.text.compare("insert lineend", ">=", next[0]): self.recall(self.text.get(next[0], next[1])) return "break" # No stdin mark -- just get the current line self.recall(self.text.get("insert linestart", "insert lineend")) return "break" # If we're in the current input before its last line, # insert a newline right at the insert point if self.text.compare("insert", "<", "end-1c linestart"): self.auto.autoindent(event) return "break" # We're in the last line; append a newline and submit it self.text.mark_set("insert", "end-1c") if self.reading: self.text.insert("insert", "\n") self.text.see("insert") else: self.auto.autoindent(event) self.text.tag_add("stdin", "iomark", "end-1c") self.text.update_idletasks() if self.reading: self.top.quit() # Break out of recursive mainloop() in raw_input() else: self.runit() return "break" def recall(self, s): if self.history: self.history.recall(s) def runit(self): line = self.text.get("iomark", "end-1c") # Strip off last newline and surrounding whitespace. # (To allow you to hit return twice to end a statement.) i = len(line) while i > 0 and line[i-1] in " \t": i = i-1 if i > 0 and line[i-1] == "\n": i = i-1 while i > 0 and line[i-1] in " \t": i = i-1 line = line[:i] more = self.interp.runsource(line) if not more: self.showprompt() def cancel_check(self, frame, what, args, dooneevent=tkinter.dooneevent, dontwait=tkinter.DONT_WAIT): # Hack -- use the debugger hooks to be able to handle events # and interrupt execution at any time. # This slows execution down quite a bit, so you may want to # disable this (by not calling settrace() in runcode() above) # for full-bore (uninterruptable) speed. # XXX This should become a user option. if self.canceled: return dooneevent(dontwait) if self.canceled: self.canceled = 0 raise KeyboardInterrupt return self._cancel_check file_line_pats = [ r'File "([^"]*)", line (\d+)', r'([^\s]+)\((\d+)\)', r'([^\s]+):\s*(\d+):', ] file_line_progs = None def goto_traceback_line(self, event=None): if self.file_line_progs is None: l = [] for pat in self.file_line_pats: l.append(re.compile(pat)) self.file_line_progs = l # x, y = self.event.x, self.event.y # self.text.mark_set("insert", "@%d,%d" % (x, y)) line = self.text.get("insert linestart", "insert lineend") for prog in self.file_line_progs: m = prog.search(line) if m: break else: tkMessageBox.showerror("No traceback line", "The line you point at doesn't look " "like an error message.", master=self.text) return filename, lineno = m.group(1, 2) try: f = open(filename, "r") f.close() except IOError, msg: self.text.bell() return edit = self.flist.open(filename) try: lineno = int(lineno) except ValueError, msg: self.text.bell() return edit.gotoline(lineno) def open_stack_viewer(self, event=None): try: sys.last_traceback except: tkMessageBox.showerror("No stack trace", "There is no stack trace yet.\n" "(sys.last_traceback is not defined)", master=self.text) return from StackViewer import StackBrowser sv = StackBrowser(self.root, self.flist) def showprompt(self): self.resetoutput() try: s = str(sys.ps1) except: s = "" self.console.write(s) self.text.mark_set("insert", "end-1c") def resetoutput(self): source = self.text.get("iomark", "end-1c") if self.history: self.history.history_store(source) if self.text.get("end-2c") != "\n": self.text.insert("end-1c", "\n") self.text.mark_set("iomark", "end-1c") sys.stdout.softspace = 0 def write(self, s): # Overrides base class write self.console.write(s) class PseudoFile: def __init__(self, interp, tags): self.interp = interp self.text = interp.text self.tags = tags def write(self, s): self.text.mark_gravity("iomark", "right") self.text.insert("iomark", str(s), self.tags) self.text.mark_gravity("iomark", "left") self.text.see("iomark") self.text.update() if self.interp.canceled: self.interp.canceled = 0 raise KeyboardInterrupt def writelines(self, l): map(self.write, l) def main(): debug = 0 try: opts, args = getopt.getopt(sys.argv[1:], "d") except getopt.error, msg: sys.stderr.write("Error: %s\n" % str(msg)) sys.exit(2) for o, a in opts: if o == "-d": debug = 1 global flist, root root = Tk() fixwordbreaks(root) root.withdraw() flist = PyShellFileList(root) if args: for filename in sys.argv[1:]: flist.open(filename) t = PyShell(flist) flist.pyshell = t t.begin() if debug: t.open_debugger() root.mainloop() if __name__ == "__main__": main()