import sys import bdb import types import os import W import WASTEconst import PyBrowser import Qd import Evt import Lists import MacOS _filenames = {} SIMPLE_TYPES = ( types.NoneType, types.IntType, types.LongType, types.FloatType, types.ComplexType, types.StringType ) class Debugger(bdb.Bdb): def __init__(self, title = 'Debugger'): bdb.Bdb.__init__(self) self.closed = 1 self.title = title self.breaksviewer = None self.reset() self.tracing = 0 self.tracingmonitortime = Evt.TickCount() self.editors = {} prefs = W.getapplication().getprefs() if prefs.debugger: for file, breaks in prefs.debugger.breaks.items(): for b in breaks: self.set_break(file, b) self.bounds, self.horpanes, self.verpanes = prefs.debugger.windowsettings self.tracemagic = prefs.debugger.tracemagic else: self.breaks = {} self.horpanes = (0.4, 0.6) self.verpanes = (0.3, 0.35, 0.35) self.bounds = (600, 400) self.tracemagic = 0 self.laststacksel = None def reset(self): self.currentframe = None self.file = None self.laststack = None self.reason = 'Not running' self.continuewithoutdebugger = 0 bdb.Bdb.reset(self) self.forget() def start(self, bottomframe = None, running = 0): W.getapplication().DebuggerQuit = bdb.BdbQuit import Menu Menu.HiliteMenu(0) if self.closed: self.setupwidgets(self.title) self.closed = 0 if not self.w.parent.debugger_quitting: self.w.select() raise W.AlertError, 'There is another debugger session busy.' self.reset() self.botframe = bottomframe if running: self.set_continue() self.reason = 'RunningŠ' self.setstate('running') else: self.set_step() self.reason = 'stopped' self.setstate('stopped') sys.settrace(self.trace_dispatch) def stop(self): self.set_quit() if self.w.parent: self.exit_mainloop() self.resetwidgets() def set_continue_without_debugger(self): sys.settrace(None) self.set_quit() self.clear_tracefuncs() self.continuewithoutdebugger = 1 if hasattr(self, "w") and self.w.parent: self.exit_mainloop() self.resetwidgets() def clear_tracefuncs(self): try: raise 'spam' except: pass frame = sys.exc_traceback.tb_frame while frame is not None: del frame.f_trace frame = frame.f_back def postmortem(self, exc_type, exc_value, traceback): if self.closed: self.setupwidgets(self.title) self.closed = 0 if not self.w.parent.debugger_quitting: raise W.AlertError, 'There is another debugger session busy.' self.reset() if traceback: self.botframe = traceback.tb_frame while traceback.tb_next <> None: traceback = traceback.tb_next frame = traceback.tb_frame else: self.botframe = None frame = None self.w.panes.bottom.buttons.killbutton.enable(1) self.reason = '(dead) ' + self.formatexception(exc_type, exc_value) self.w.select() self.setup(frame, traceback) self.setstate('dead') self.showstack(self.curindex) self.showframe(self.curindex) def setupwidgets(self, title): self.w = w = W.Window(self.bounds, title, minsize = (500, 300)) w.panes = W.HorizontalPanes((8, 4, -8, -8), self.horpanes) w.panes.browserpanes = browserpanes = W.VerticalPanes(None, self.verpanes) browserpanes.stacklist = W.Group(None) browserpanes.stacklist.title = W.TextBox((4, 0, 0, 12), 'Stack') browserpanes.stacklist.stack = W.List((0, 16, 0, 0), callback = self.do_stack, flags = Lists.lOnlyOne) browserpanes.locals = W.Group(None) browserpanes.locals.title = W.TextBox((4, 0, 0, 12), 'Local variables') browserpanes.locals.browser = PyBrowser.BrowserWidget((0, 16, 0, 0)) browserpanes.globals = W.Group(None) browserpanes.globals.title = W.TextBox((4, 0, 0, 12), 'Global variables') browserpanes.globals.browser = PyBrowser.BrowserWidget((0, 16, 0, 0)) w.panes.bottom = bottom = W.Group(None) bottom.src = src = W.Group((0, 52, 0, 0)) source = SourceViewer((1, 1, -15, -15), readonly = 1, debugger = self) src.optionsmenu = W.PopupMenu((-16, 0, 16, 16), []) src.optionsmenu.bind('', self.makeoptionsmenu) src._barx = W.Scrollbar((0, -16, -15, 16), source.hscroll, max = 32767) src._bary = W.Scrollbar((-16, 15, 16, -15), source.vscroll, max = 32767) src.source = source src.frame = W.Frame((0, 0, -15, -15)) bottom.tracingmonitor = TracingMonitor((0, 23, 6, 6)) bottom.state = W.TextBox((12, 20, 0, 16), self.reason) bottom.srctitle = W.TextBox((12, 36, 0, 14)) bottom.buttons = buttons = W.Group((12, 0, 0, 16)) buttons.runbutton = W.Button((0, 0, 50, 16), "Run", self.do_run) buttons.stopbutton = W.Button((58, 0, 50, 16), "Stop", self.do_stop) buttons.killbutton = W.Button((116, 0, 50, 16), "Kill", self.do_kill) buttons.line = W.VerticalLine((173, 0, 0, 0)) buttons.stepbutton = W.Button((181, 0, 50, 16), "Step", self.do_step) buttons.stepinbutton = W.Button((239, 0, 50, 16), "Step in", self.do_stepin) buttons.stepoutbutton = W.Button((297, 0, 50, 16), "Step out", self.do_stepout) w.bind('cmdr', buttons.runbutton.push) w.bind('cmd.', buttons.stopbutton.push) w.bind('cmdk', buttons.killbutton.push) w.bind('cmds', buttons.stepbutton.push) w.bind('cmdt', buttons.stepinbutton.push) w.bind('cmdu', buttons.stepoutbutton.push) w.bind('', self.close) w.open() w.xxx___select(w.panes.bottom.src.source) def makeoptionsmenu(self): options = [('Clear breakpoints', self.w.panes.bottom.src.source.clearbreakpoints), ('Clear all breakpoints', self.clear_all_breaks), ('Edit breakpointsŠ', self.edit_breaks), '-', (self.tracemagic and 'Disable __magic__ tracing' or 'Enable __magic__ tracing', self.togglemagic)] self.w.panes.bottom.src.optionsmenu.set(options) def edit_breaks(self): if self.breaksviewer: self.breaksviewer.select() else: self.breaksviewer = BreakpointsViewer(self) def togglemagic(self): self.tracemagic = not self.tracemagic def setstate(self, state): self.w.panes.bottom.tracingmonitor.reset() self.w.panes.bottom.state.set(self.reason) buttons = self.w.panes.bottom.buttons if state == 'stopped': buttons.runbutton.enable(1) buttons.stopbutton.enable(0) buttons.killbutton.enable(1) buttons.stepbutton.enable(1) buttons.stepinbutton.enable(1) buttons.stepoutbutton.enable(1) elif state == 'running': buttons.runbutton.enable(0) buttons.stopbutton.enable(1) buttons.killbutton.enable(1) buttons.stepbutton.enable(0) buttons.stepinbutton.enable(0) buttons.stepoutbutton.enable(0) elif state == 'idle': buttons.runbutton.enable(0) buttons.stopbutton.enable(0) buttons.killbutton.enable(0) buttons.stepbutton.enable(0) buttons.stepinbutton.enable(0) buttons.stepoutbutton.enable(0) elif state == 'dead': buttons.runbutton.enable(0) buttons.stopbutton.enable(0) buttons.killbutton.enable(1) buttons.stepbutton.enable(0) buttons.stepinbutton.enable(0) buttons.stepoutbutton.enable(0) else: print 'unknown state:', state def resetwidgets(self): self.reason = '' self.w.panes.bottom.srctitle.set('') self.w.panes.bottom.src.source.set('') self.w.panes.browserpanes.stacklist.stack.set([]) self.w.panes.browserpanes.locals.browser.set({}) self.w.panes.browserpanes.globals.browser.set({}) self.setstate('idle') # W callbacks def close(self): self.set_quit() self.exit_mainloop() self.closed = 1 self.unregister_editor(self.w.panes.bottom.src.source, self.w.panes.bottom.src.source.file) self.horpanes = self.w.panes.getpanesizes() self.verpanes = self.w.panes.browserpanes.getpanesizes() self.bounds = self.w.getbounds() prefs = W.getapplication().getprefs() prefs.debugger.breaks = self.breaks prefs.debugger.windowsettings = self.bounds, self.horpanes, self.verpanes prefs.debugger.tracemagic = self.tracemagic prefs.save() # stack list callback def do_stack(self, isdbl): sel = self.w.panes.browserpanes.stacklist.stack.getselection() if isdbl: if sel: frame, lineno = self.stack[sel[0] + 1] filename = frame.f_code.co_filename editor = self.w._parentwindow.parent.openscript(filename, lineno) if self.breaks.has_key(filename): editor.showbreakpoints(1) else: if sel and sel <> self.laststacksel: self.showframe(sel[0] + 1) self.laststacksel = sel def geteditor(self, filename): if filename[:1] == '<' and filename[-1:] == '>': editor = W.getapplication().getscript(filename[1:-1]) else: editor = W.getapplication().getscript(filename) return editor # button callbacks def do_run(self): self.running() self.set_continue() self.exit_mainloop() def do_stop(self): self.set_step() def do_kill(self): self.set_quit() self.exit_mainloop() self.resetwidgets() def do_step(self): self.running() self.set_next(self.curframe) self.exit_mainloop() def do_stepin(self): self.running() self.set_step() self.exit_mainloop() def do_stepout(self): self.running() self.set_return(self.curframe) self.exit_mainloop() def running(self): W.SetCursor('watch') self.reason = 'RunningŠ' self.setstate('running') #self.w.panes.bottom.src.source.set('') #self.w.panes.browserpanes.stacklist.stack.set([]) #self.w.panes.browserpanes.locals.browser.set({}) #self.w.panes.browserpanes.globals.browser.set({}) def exit_mainloop(self): self.w.parent.debugger_quitting = 1 # def showframe(self, stackindex): (frame, lineno) = self.stack[stackindex] W.SetCursor('watch') filename = frame.f_code.co_filename if filename <> self.file: editor = self.geteditor(filename) if editor: self.w.panes.bottom.src.source.set(editor.get(), filename) else: try: f = open(filename, 'rb') data = f.read() f.close() except IOError: if filename[-3:] == '.py': import imp modname = os.path.basename(filename)[:-3] try: f, filename, (suff, mode, dummy) = imp.find_module(modname) except ImportError: self.w.panes.bottom.src.source.set('canıt find file') else: if f: f.close() if f and suff == '.py': f = open(filename, 'rb') data = f.read() f.close() self.w.panes.bottom.src.source.set(data, filename) else: self.w.panes.bottom.src.source.set('canıt find file') else: self.w.panes.bottom.src.source.set('canıt find file') else: self.w.panes.bottom.src.source.set(data, filename) self.file = filename self.w.panes.bottom.srctitle.set('Source: ' + filename + ((lineno > 0) and (' (line %d)' % lineno) or ' ')) self.goto_line(lineno) self.lineno = lineno self.showvars((frame, lineno)) def showvars(self, (frame, lineno)): if frame.f_locals is not frame.f_globals: locals = frame.f_locals else: locals = {'Same as Globals':''} filteredlocals = {} for key, value in locals.items(): # empty key is magic for Python 1.4; '.' is magic for 1.5... if not key or key[0] <> '.': filteredlocals[key] = value self.w.panes.browserpanes.locals.browser.set(filteredlocals) self.w.panes.browserpanes.globals.browser.set(frame.f_globals) def showstack(self, stackindex): stack = [] for frame, lineno in self.stack[1:]: filename = frame.f_code.co_filename try: filename = _filenames[filename] except KeyError: if filename[:1] + filename[-1:] <> '<>': filename = os.path.basename(filename) _filenames[frame.f_code.co_filename] = filename funcname = frame.f_code.co_name if funcname == '?': funcname = '' stack.append(filename + ': ' + funcname) if stack <> self.laststack: self.w.panes.browserpanes.stacklist.stack.set(stack) self.laststack = stack sel = [stackindex - 1] self.w.panes.browserpanes.stacklist.stack.setselection(sel) self.laststacksel = sel def goto_line(self, lineno): if lineno > 0: self.w.panes.bottom.src.source.selectline(lineno - 1) else: self.w.panes.bottom.src.source.setselection(0, 0) # bdb entry points # def user_call(self, frame, argument_list): # self.reason = 'Calling' # self.interaction(frame, None) def user_line(self, frame): # This function is called when we stop or break at this line self.reason = 'Stopped' self.interaction(frame, None) def user_return(self, frame, return_value): # This function is called when a return trap is set here fname = frame.f_code.co_name if fname <> '?': self.reason = 'Returning from %s()' % frame.f_code.co_name frame.f_locals['__return__'] = return_value elif frame.f_back is self.botframe: self.reason = 'Done' else: self.reason = 'Returning' self.interaction(frame, None, 1) def user_exception(self, frame, (exc_type, exc_value, exc_traceback)): # This function is called when we stop or break at this line self.reason = self.formatexception(exc_type, exc_value) self.interaction(frame, exc_traceback) def formatexception(self, exc_type, exc_value): if exc_type == SyntaxError: try: value, (filename, lineno, charno, line) = exc_value except: pass else: return str(exc_type) + ': ' + str(value) if type(exc_type) == types.ClassType: nice = exc_type.__name__ else: nice = str(exc_type) value = str(exc_value) if exc_value and value: nice = nice + ": " + value return nice def forget(self): self.stack = [] self.curindex = 0 self.curframe = None def setup(self, f, t, isreturning = 0): self.forget() self.stack, self.curindex = self.get_stack(f, t) self.curframe = self.stack[self.curindex - isreturning][0] def interaction(self, frame, traceback, isreturning = 0): saveport = Qd.GetPort() self.w.select() try: self.setup(frame, traceback, isreturning) self.setstate('stopped') stackindex = self.curindex if isreturning: if frame.f_back is not self.botframe: stackindex = stackindex - 1 self.showstack(stackindex) self.showframe(stackindex) self.w.parent.debugger_mainloop() self.forget() finally: Qd.SetPort(saveport) # bdb customization def trace_dispatch(self, frame, event, arg, TickCount = Evt.TickCount): if TickCount() - self.tracingmonitortime > 15: self.tracingmonitortime = TickCount() self.w.panes.bottom.tracingmonitor.toggle() try: try: MacOS.EnableAppswitch(0) if self.quitting: # returning None is not enough, a former BdbQuit exception # might have been eaten by the print statement raise bdb.BdbQuit if event == 'line': return self.dispatch_line(frame) if event == 'call': return self.dispatch_call(frame, arg) if event == 'return': return self.dispatch_return(frame, arg) if event == 'exception': return self.dispatch_exception(frame, arg) print 'bdb.Bdb.dispatch: unknown debugging event:', `event` return self.trace_dispatch finally: MacOS.EnableAppswitch(-1) except KeyboardInterrupt: self.set_step() return self.trace_dispatch except bdb.BdbQuit: if self.continuewithoutdebugger: self.clear_tracefuncs() return else: raise bdb.BdbQuit except: print 'XXX Exception during debugger interaction.', \ self.formatexception(sys.exc_type, sys.exc_value) import traceback traceback.print_exc() return self.trace_dispatch def dispatch_call(self, frame, arg): if not self.tracemagic and \ frame.f_code.co_name[:2] == '__' == frame.f_code.co_name[-2:] and \ frame.f_code.co_name <> '__init__': return if self.botframe is None: # First call of dispatch since reset() self.botframe = frame.f_back # xxx !!! added f_back return self.trace_dispatch if not (self.stop_here(frame) or self.break_anywhere(frame)): # No need to trace this function return # None self.user_call(frame, arg) if self.quitting: raise bdb.BdbQuit return self.trace_dispatch def set_continue(self): # Don't stop except at breakpoints or when finished self.stopframe = self.botframe self.returnframe = None self.quitting = 0 # unlike in bdb/pdb, there's a chance that breakpoints change # *while* a program (this program ;-) is running. It's actually quite likely. # So we don't delete frame.f_trace until the bottom frame if there are no breakpoints. def set_break(self, filename, lineno): if not self.breaks.has_key(filename): self.breaks[filename] = [] list = self.breaks[filename] if lineno in list: return 'There is already a breakpoint there!' list.append(lineno) list.sort() # I want to keep them neatly sorted; easier for drawing if hasattr(bdb, "Breakpoint"): # 1.5.2b1 specific bp = bdb.Breakpoint(filename, lineno, 0, None) self.update_breaks(filename) def clear_break(self, filename, lineno): bdb.Bdb.clear_break(self, filename, lineno) self.update_breaks(filename) def clear_all_file_breaks(self, filename): bdb.Bdb.clear_all_file_breaks(self, filename) self.update_breaks(filename) def clear_all_breaks(self): bdb.Bdb.clear_all_breaks(self) for editors in self.editors.values(): for editor in editors: editor.drawbreakpoints() # special def toggle_break(self, filename, lineno): if self.get_break(filename, lineno): self.clear_break(filename, lineno) else: self.set_break(filename, lineno) def clear_breaks_above(self, filename, above): if not self.breaks.has_key(filename): return 'There are no breakpoints in that file!' for lineno in self.breaks[filename][:]: if lineno > above: self.breaks[filename].remove(lineno) if not self.breaks[filename]: del self.breaks[filename] # editor stuff def update_breaks(self, filename): if self.breaksviewer: self.breaksviewer.update() if self.editors.has_key(filename): for editor in self.editors[filename]: if editor._debugger: # XXX editor.drawbreakpoints() else: print 'xxx dead editor!' def update_allbreaks(self): if self.breaksviewer: self.breaksviewer.update() for filename in self.breaks.keys(): if self.editors.has_key(filename): for editor in self.editors[filename]: if editor._debugger: # XXX editor.drawbreakpoints() else: print 'xxx dead editor!' def register_editor(self, editor, filename): if not filename: return if not self.editors.has_key(filename): self.editors[filename] = [editor] elif editor not in self.editors[filename]: self.editors[filename].append(editor) def unregister_editor(self, editor, filename): if not filename: return try: self.editors[filename].remove(editor) if not self.editors[filename]: del self.editors[filename] # if this was an untitled window, clear the breaks. if filename[:1] == '<' and filename[-1:] == '>' and \ self.breaks.has_key(filename): self.clear_all_file_breaks(filename) except (KeyError, ValueError): pass class SourceViewer(W.PyEditor): def __init__(self, *args, **kwargs): apply(W.PyEditor.__init__, (self,) + args, kwargs) self.bind('', self.clickintercept) def clickintercept(self, point, modifiers): if self._parentwindow._currentwidget <> self and not self.pt_in_breaks(point): self._parentwindow.xxx___select(self) return 1 def _getviewrect(self): l, t, r, b = self._bounds if self._debugger: return (l + 12, t + 2, r - 1, b - 2) else: return (l + 5, t + 2, r - 1, b - 2) def select(self, onoff, isclick = 0): if W.SelectableWidget.select(self, onoff): return self.SetPort() #if onoff: # self.ted.WEActivate() #else: # self.ted.WEDeactivate() self.drawselframe(onoff) def drawselframe(self, onoff): pass class BreakpointsViewer: def __init__(self, debugger): self.debugger = debugger import Lists self.w = W.Window((300, 250), 'Breakpoints', minsize = (200, 200)) self.w.panes = W.HorizontalPanes((8, 8, -8, -32), (0.3, 0.7)) self.w.panes.files = W.List(None, callback = self.filehit) #, flags = Lists.lOnlyOne) self.w.panes.gr = W.Group(None) self.w.panes.gr.breaks = W.List((0, 0, -130, 0), callback = self.linehit) #, flags = Lists.lOnlyOne) self.w.panes.gr.openbutton = W.Button((-80, 4, 0, 16), 'ViewŠ', self.openbuttonhit) self.w.panes.gr.deletebutton = W.Button((-80, 28, 0, 16), 'Delete', self.deletebuttonhit) self.w.bind('', self.close) self.w.bind('backspace', self.w.panes.gr.deletebutton.push) self.setup() self.w.open() self.w.panes.gr.openbutton.enable(0) self.w.panes.gr.deletebutton.enable(0) self.curfile = None def deletebuttonhit(self): if self.w._currentwidget == self.w.panes.files: self.del_filename() else: self.del_number() self.checkbuttons() def del_number(self): if self.curfile is None: return sel = self.w.panes.gr.breaks.getselectedobjects() for lineno in sel: self.debugger.clear_break(self.curfile, lineno) def del_filename(self): sel = self.w.panes.files.getselectedobjects() for filename in sel: self.debugger.clear_all_file_breaks(filename) self.debugger.update_allbreaks() def setup(self): files = self.debugger.breaks.keys() files.sort() self.w.panes.files.set(files) def close(self): self.debugger.breaksviewer = None self.debugger = None def update(self): sel = self.w.panes.files.getselectedobjects() self.setup() self.w.panes.files.setselectedobjects(sel) sel = self.w.panes.files.getselection() if len(sel) == 0 and self.curfile: self.w.panes.files.setselectedobjects([self.curfile]) self.filehit(0) def select(self): self.w.select() def selectfile(self, file): self.w.panes.files.setselectedobjects([file]) self.filehit(0) def openbuttonhit(self): self.filehit(1) def filehit(self, isdbl): sel = self.w.panes.files.getselectedobjects() if isdbl: for filename in sel: lineno = None if filename == self.curfile: linesel = self.w.panes.gr.breaks.getselectedobjects() if linesel: lineno = linesel[-1] elif self.w.panes.gr.breaks: lineno = self.w.panes.gr.breaks[0] editor = self.w._parentwindow.parent.openscript(filename, lineno) editor.showbreakpoints(1) return if len(sel) == 1: file = sel[0] filebreaks = self.debugger.breaks[file][:] if self.curfile == file: linesel = self.w.panes.gr.breaks.getselectedobjects() self.w.panes.gr.breaks.set(filebreaks) if self.curfile == file: self.w.panes.gr.breaks.setselectedobjects(linesel) self.curfile = file else: if len(sel) <> 0: self.curfile = None self.w.panes.gr.breaks.set([]) self.checkbuttons() def linehit(self, isdbl): if isdbl: files = self.w.panes.files.getselectedobjects() if len(files) <> 1: return filename = files[0] linenos = self.w.panes.gr.breaks.getselectedobjects() if not linenos: return lineno = linenos[-1] editor = self.w._parentwindow.parent.openscript(filename, lineno) editor.showbreakpoints(1) self.checkbuttons() def checkbuttons(self): if self.w.panes.files.getselection(): self.w.panes.gr.openbutton.enable(1) self.w._parentwindow.setdefaultbutton(self.w.panes.gr.openbutton) if self.w._currentwidget == self.w.panes.files: if self.w.panes.files.getselection(): self.w.panes.gr.deletebutton.enable(1) else: self.w.panes.gr.deletebutton.enable(0) else: if self.w.panes.gr.breaks.getselection(): self.w.panes.gr.deletebutton.enable(1) else: self.w.panes.gr.deletebutton.enable(0) else: self.w.panes.gr.openbutton.enable(0) self.w.panes.gr.deletebutton.enable(0) class TracingMonitor(W.Widget): def __init__(self, *args, **kwargs): apply(W.Widget.__init__, (self,) + args, kwargs) self.state = 0 def toggle(self): if hasattr(self, "_parentwindow") and self._parentwindow is not None: self.state = self.state % 2 + 1 port = Qd.GetPort() self.SetPort() self.draw() Qd.SetPort(port) def reset(self): if self._parentwindow: self.state = 0 port = Qd.GetPort() self.SetPort() self.draw() Qd.SetPort(port) def draw(self, visRgn = None): if self.state == 2: Qd.PaintOval(self._bounds) else: Qd.EraseOval(self._bounds) # convenience funcs def postmortem(exc_type, exc_value, tb): d = getdebugger() d.postmortem(exc_type, exc_value, tb) def start(bottomframe = None): d = getdebugger() d.start(bottomframe) def startfromhere(): d = getdebugger() try: raise 'spam' except: frame = sys.exc_traceback.tb_frame.f_back d.start(frame) def startfrombottom(): d = getdebugger() d.start(_getbottomframe(), 1) def stop(): d = getdebugger() d.stop() def cont(): sys.settrace(None) d = getdebugger() d.set_continue_without_debugger() def _getbottomframe(): try: raise 'spam' except: pass frame = sys.exc_traceback.tb_frame while 1: if frame.f_code.co_name == 'mainloop' or frame.f_back is None: break frame = frame.f_back return frame _debugger = None def getdebugger(): if not __debug__: raise W.AlertError, "Canıt debug in ³Optimize bytecode² mode.\r(see ³Default startup options² in EditPythonPreferences)" global _debugger if _debugger is None: _debugger = Debugger() return _debugger