diff --git a/Lib/bdb.py b/Lib/bdb.py new file mode 100644 index 00000000000..36b0f187a10 --- /dev/null +++ b/Lib/bdb.py @@ -0,0 +1,275 @@ +# A generic Python debugger base class. +# This class takes care of details of the trace facility; +# a derived class should implement user interaction. +# There are two debuggers based upon this: +# 'pdb', a text-oriented debugger not unlike dbx or gdb; +# and 'wdb', a window-oriented debugger. +# And of course... you can roll your own! + +import sys + +BdbQuit = 'bdb.BdbQuit' # Exception to give up completely + + +class Bdb: # Basic Debugger + + def init(self): + self.breaks = {} + return self + + def reset(self): + self.botframe = None + self.stopframe = None + self.returnframe = None + self.quitting = 0 + + def trace_dispatch(self, frame, event, arg): + if self.quitting: + return # None + 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 + + def dispatch_line(self, frame): + if self.stop_here(frame) or self.break_here(frame): + self.user_line(frame) + if self.quitting: raise BdbQuit + return self.trace_dispatch + + def dispatch_call(self, frame, arg): + frame.f_locals['__args__'] = arg + if self.botframe is None: + # First call of dispatch since reset() + self.botframe = frame + 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 BdbQuit + return self.trace_dispatch + + def dispatch_return(self, frame, arg): + if self.stop_here(frame) or frame == self.returnframe: + self.user_return(frame, arg) + if self.quitting: raise BdbQuit + + def dispatch_exception(self, frame, arg): + if self.stop_here(frame): + self.user_exception(frame, arg) + if self.quitting: raise BdbQuit + return self.trace_dispatch + + # Normally derived classes don't override the following + # functions, but they may if they want to redefine the + # definition of stopping and breakpoints. + + def stop_here(self, frame): + if self.stopframe is None: + return 1 + if frame is self.stopframe: + return 1 + while frame is not None and frame is not self.stopframe: + if frame is self.botframe: + return 1 + frame = frame.f_back + return 0 + + def break_here(self, frame): + if not self.breaks.has_key(frame.f_code.co_filename): + return 0 + if not frame.f_lineno in \ + self.breaks[frame.f_code.co_filename]: + return 0 + return 1 + + def break_anywhere(self, frame): + return self.breaks.has_key(frame.f_code.co_filename) + + # Derived classes should override the user_* functions + # to gain control. + + def user_call(self, frame, argument_list): + # This function is called when there is the remote possibility + # that we ever need to stop in this function + pass + + def user_line(self, frame): + # This function is called when we stop or break at this line + pass + + def user_return(self, frame, return_value): + # This function is called when a return trap is set here + pass + + def user_exception(self, frame, (exc_type, exc_value, exc_traceback)): + # This function is called if an exception occurs, + # but only if we are to stop at or just below this level + pass + + # Derived classes and clients can call the following functions + # to affect the stepping state. + + def set_step(self): + # Stop after one line of code + self.stopframe = None + self.returnframe = None + self.quitting = 0 + + def set_next(self, frame): + # Stop on the next line in or below the given frame + self.stopframe = frame + self.returnframe = None + self.quitting = 0 + + def set_return(self, frame): + # Stop when returning from the given frame + self.stopframe = frame.f_back + self.returnframe = frame + self.quitting = 0 + + def set_continue(self): + # Don't stop except at breakpoints or when finished + self.stopframe = self.botframe + self.returnframe = None + self.quitting = 0 + + def set_quit(self): + self.stopframe = self.botframe + self.returnframe = None + self.quitting = 1 + sys.trace = None + del sys.trace + + # Derived classes and clients can call the following functions + # to manipulate breakpoints. These functions return an + # error message is something went wrong, None if all is well. + # Call self.get_*break*() to see the breakpoints. + + def set_break(self, filename, lineno): + import linecache # Import as late as possible + line = linecache.getline(filename, lineno) + if not line: + return 'That line does not exist!' + 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) + + def clear_break(self, filename, lineno): + if not self.breaks.has_key(filename): + return 'There are no breakpoints in that file!' + if lineno not in self.breaks[filename]: + return 'There is no breakpoint there!' + self.breaks[filename].remove(lineno) + if not self.breaks[filename]: + del self.breaks[filename] + + def clear_all_file_breaks(self, filename): + if not self.breaks.has_key(filename): + return 'There are no breakpoints in that file!' + del self.breaks[filename] + + def clear_all_breaks(self, filename, lineno): + if not self.breaks: + return 'There are no breakpoints!' + self.breaks = {} + + def get_break(self, filename, lineno): + return self.breaks.has_key(filename) and \ + lineno in self.breaks[filename] + + def get_file_breaks(self, filename): + if self.breaks.has_key(filename): + return self.breaks[filename] + else: + return [] + + def get_all_breaks(self): + return self.breaks + + # Derived classes and clients can call the following function + # to get a data structure representing a stack trace. + + def get_stack(self, f, t): + stack = [] + if t and t.tb_frame is f: + t = t.tb_next + while f is not None: + stack.append((f, f.f_lineno)) + if f is self.botframe: + break + f = f.f_back + stack.reverse() + i = max(0, len(stack) - 1) + while t is not None: + stack.append((t.tb_frame, t.tb_lineno)) + t = t.tb_next + return stack, i + + # The following two functions can be called by clients to use + # a debugger to debug a statement, given as a string. + + def run(self, cmd): + import __main__ + dict = __main__.__dict__ + self.runctx(cmd, dict, dict) + + def runctx(self, cmd, globals, locals): + self.reset() + sys.trace = self.trace_dispatch + try: + exec(cmd + '\n', globals, locals) + except BdbQuit: + pass + finally: + self.quitting = 1 + sys.trace = None + del sys.trace + # XXX What to do if the command finishes normally? + + +# -------------------- testing -------------------- + +class Tdb(Bdb): + def user_call(self, frame, args): + import codehack + name = codehack.getcodename(frame.f_code) + if not name: name = '???' + print '+++ call', name, args + def user_line(self, frame): + import linecache, string, codehack + name = codehack.getcodename(frame.f_code) + if not name: name = '???' + fn = frame.f_code.co_filename + line = linecache.getline(fn, frame.f_lineno) + print '+++', fn, frame.f_lineno, name, ':', string.strip(line) + def user_return(self, frame, retval): + print '+++ return', retval + def user_exception(self, frame, exc_stuff): + print '+++ exception', exc_stuff + self.set_continue() + +def foo(n): + print 'foo(', n, ')' + x = bar(n*10) + print 'bar returned', x + +def bar(a): + print 'bar(', a, ')' + return a/2 + +def test(): + import linecache + linecache.checkcache() + t = Tdb().init() + t.run('import bdb; bdb.foo(10)') diff --git a/Lib/codehack.py b/Lib/codehack.py new file mode 100644 index 00000000000..8a4f611696d --- /dev/null +++ b/Lib/codehack.py @@ -0,0 +1,54 @@ +# A subroutine for extracting a function name from a code object +# (with cache) + +import sys +from stat import * +import string +import os +import linecache + +# Extract the function or class name from a code object. +# This is a bit of a hack, since a code object doesn't contain +# the name directly. So what do we do: +# - get the filename (which *is* in the code object) +# - look in the code string to find the first SET_LINENO instruction +# (this must be the first instruction) +# - get the line from the file +# - if the line starts with 'class' or 'def' (after possible whitespace), +# extract the following identifier +# +# This breaks apart when the function was read from +# or constructed by exec(), when the file is not accessible, +# and also when the file has been modified or when a line is +# continued with a backslash before the function or class name. +# +# Because this is a pretty expensive hack, a cache is kept. + +SET_LINENO = 127 # The opcode (see "opcode.h" in the Python source) +identchars = string.letters + string.digits + '_' # Identifier characters + +_namecache = {} # The cache + +def getcodename(co): + key = `co` # arbitrary but uniquely identifying string + if _namecache.has_key(key): return _namecache[key] + filename = co.co_filename + code = co.co_code + name = '' + if ord(code[0]) == SET_LINENO: + lineno = ord(code[1]) | ord(code[2]) << 8 + line = linecache.getline(filename, lineno) + words = string.split(line) + if len(words) >= 2 and words[0] in ('def', 'class'): + name = words[1] + for i in range(len(name)): + if name[i] not in identchars: + name = name[:i] + break + _namecache[key] = name + return name + +# Use the above routine to find a function's name. + +def getfuncname(func): + return getcodename(func.func_code) diff --git a/Lib/lib-old/codehack.py b/Lib/lib-old/codehack.py new file mode 100644 index 00000000000..8a4f611696d --- /dev/null +++ b/Lib/lib-old/codehack.py @@ -0,0 +1,54 @@ +# A subroutine for extracting a function name from a code object +# (with cache) + +import sys +from stat import * +import string +import os +import linecache + +# Extract the function or class name from a code object. +# This is a bit of a hack, since a code object doesn't contain +# the name directly. So what do we do: +# - get the filename (which *is* in the code object) +# - look in the code string to find the first SET_LINENO instruction +# (this must be the first instruction) +# - get the line from the file +# - if the line starts with 'class' or 'def' (after possible whitespace), +# extract the following identifier +# +# This breaks apart when the function was read from +# or constructed by exec(), when the file is not accessible, +# and also when the file has been modified or when a line is +# continued with a backslash before the function or class name. +# +# Because this is a pretty expensive hack, a cache is kept. + +SET_LINENO = 127 # The opcode (see "opcode.h" in the Python source) +identchars = string.letters + string.digits + '_' # Identifier characters + +_namecache = {} # The cache + +def getcodename(co): + key = `co` # arbitrary but uniquely identifying string + if _namecache.has_key(key): return _namecache[key] + filename = co.co_filename + code = co.co_code + name = '' + if ord(code[0]) == SET_LINENO: + lineno = ord(code[1]) | ord(code[2]) << 8 + line = linecache.getline(filename, lineno) + words = string.split(line) + if len(words) >= 2 and words[0] in ('def', 'class'): + name = words[1] + for i in range(len(name)): + if name[i] not in identchars: + name = name[:i] + break + _namecache[key] = name + return name + +# Use the above routine to find a function's name. + +def getfuncname(func): + return getcodename(func.func_code) diff --git a/Lib/lib-stdwin/basewin.py b/Lib/lib-stdwin/basewin.py new file mode 100644 index 00000000000..f896fe2a059 --- /dev/null +++ b/Lib/lib-stdwin/basewin.py @@ -0,0 +1,65 @@ +# basewin.py + +import stdwin +import mainloop +from stdwinevents import * + +class BaseWindow: + + def init(self, title): + self.win = stdwin.open(title) + self.win.dispatch = self.dispatch + mainloop.register(self.win) + return self + +# def reopen(self): +# title = self.win.gettitle() +# winpos = self.win.getwinpos() +# winsize = self.win.getwinsize() +# origin = self.win.getorigin() +# docsize = self.win.getdocsize() +# mainloop.unregister(self.win) +# del self.win.dispatch +# self.win.close() +# stdwin.setdefwinpos(winpos) +# stdwin.setdefwinsize(winsize) +# self.win = stdwin.open(title) +# stdwin.setdefwinpos(0, 0) +# stdwin.setdefwinsize(0, 0) +# self.win.setdocsize(docsize) +# self.win.setorigin(origin) +# self.win.dispatch = self.dispatch +# mainloop.register(self.win) + + def popup(self): + if self.win is not stdwin.getactive(): + self.win.setactive() + + def close(self): + mainloop.unregister(self.win) + del self.win.dispatch + self.win.close() + + def dispatch(self, event): + type, win, detail = event + if type == WE_CHAR: + self.char(detail) + elif type == WE_COMMAND: + self.command(detail) + elif type == WE_MOUSE_DOWN: + self.mouse_down(detail) + elif type == WE_MOUSE_MOVE: + self.mouse_move(detail) + elif type == WE_MOUSE_UP: + self.mouse_up(detail) + elif type == WE_DRAW: + self.draw(detail) + elif type == WE_CLOSE: + self.close() + + def no_op(self, detail): + pass + char = command = mouse_down = mouse_move = mouse_up = draw = no_op + + def refreshall(self): + self.win.change((-10, 0), (10000, 30000)) diff --git a/Lib/lib-stdwin/srcwin.py b/Lib/lib-stdwin/srcwin.py new file mode 100644 index 00000000000..3f323bad53a --- /dev/null +++ b/Lib/lib-stdwin/srcwin.py @@ -0,0 +1,103 @@ +# srcwin.py -- a source listing window + +import stdwin +from stdwinevents import * +import basewin + +WIDTH = 40 +MAXHEIGHT = 24 + +class SourceWindow(basewin.BaseWindow): + + def init(self, filename): + self.filename = filename + # + f = open(self.filename, 'r') # raise exception if not found + self.contents = f.read() + f.seek(0) + self.linecount = len(f.readlines()) + f.close() + # + self.lineheight = lh = stdwin.lineheight() + self.leftmargin = stdwin.textwidth('00000000') + self.rightmargin = 30000 # Infinity + self.bottom = lh * self.linecount + # + stdwin.setdefwinpos(0, 0) + width = WIDTH*stdwin.textwidth('0') + height = lh*min(MAXHEIGHT, self.linecount) + stdwin.setdefwinsize(width, height) + self = basewin.BaseWindow.init(self, filename) + # + self.win.setdocsize(0, self.bottom + lh) + self.initeditor() + return self + + def initeditor(self): + r = (self.leftmargin, 0), (self.rightmargin, self.bottom) + self.editor = self.win.textcreate(r) + self.editor.settext(self.contents) + + def closeeditor(self): + self.editor.close() + + def reopen(self): + self.closeeditor() + basewin.BaseWindow.reopen(self) + self.initeditor() + + def close(self): + self.closeeditor() + basewin.BaseWindow.close(self) + + # Override this method to format line numbers differently + def getmark(self, lineno): + return `lineno` + + def dispatch(self, event): + if event[0] == WE_NULL: return # Dummy tested by mainloop + if event[0] == WE_DRAW or not self.editor.event(event): + basewin.BaseWindow.dispatch(self, event) + + def draw(self, detail): + dummy = self.editor.draw(detail) + # Draw line numbers + (left, top), (right, bottom) = detail + topline = top/self.lineheight + botline = bottom/self.lineheight + 1 + botline = min(self.linecount, botline) + d = self.win.begindrawing() + try: + h, v = 0, self.lineheight * topline + for lineno in range(topline+1, botline+1): + d.text((h, v), self.getmark(lineno)) + v = v + self.lineheight + finally: + d.close() + + def changemark(self, lineno): + left = 0 + top = (lineno-1) * self.lineheight + right = self.leftmargin + bottom = lineno * self.lineheight + d = self.win.begindrawing() + try: + d.erase((left, top), (right, bottom)) + d.text((left, top), self.getmark(lineno)) + finally: + d.close() + + def showline(self, lineno): + left = 0 + top = (lineno-1) * self.lineheight + right = self.leftmargin + bottom = lineno * self.lineheight + self.win.show((left, top), (right, bottom)) + + +TESTFILE = 'srcwin.py' + +def test(): + import mainloop + sw = SourceWindow().init(TESTFILE) + mainloop.mainloop() diff --git a/Lib/lib-stdwin/wdb.py b/Lib/lib-stdwin/wdb.py new file mode 100644 index 00000000000..89b03f59765 --- /dev/null +++ b/Lib/lib-stdwin/wdb.py @@ -0,0 +1,282 @@ +# wdb.py -- a window-based Python debugger + +import stdwin +from stdwinevents import * +import sys +import basewin +import bdb +import repr + +WIDTH = 40 +HEIGHT = 8 + +WdbDone = 'wdb.WdbDone' # Exception to continue execution + + +class Wdb(bdb.Bdb, basewin.BaseWindow): # Window debugger + + def init(self): + self.sourcewindows = {} + self.framewindows = {} + self = bdb.Bdb.init(self) + width = WIDTH*stdwin.textwidth('0') + height = HEIGHT*stdwin.lineheight() + stdwin.setdefwinsize(width, height) + self = basewin.BaseWindow.init(self, '--Stack--') + self.closed = 0 + return self + + def reset(self): + if self.closed: raise RuntimeError, 'already closed' + bdb.Bdb.reset(self) + self.forget() + + def forget(self): + self.lineno = None + self.stack = [] + self.curindex = 0 + self.curframe = None + for fn in self.sourcewindows.keys(): + self.sourcewindows[fn].resetlineno() + + def setup(self, f, t): + self.forget() + self.stack, self.curindex = self.get_stack(f, t) + self.curframe = self.stack[self.curindex][0] + # Build a list of current frames + cfl = [] + for f, i in self.stack: cfl.append(f) + # Remove deactivated frame windows + for name in self.framewindows.keys(): + fw = self.framewindows[name] + if fw.frame not in cfl: fw.close() + else: fw.refreshframe() + # Refresh the stack window + self.refreshstack() + + # Override Bdb methods (except user_call, for now) + + def user_line(self, frame): + # This function is called when we stop or break at this line + self.interaction(frame, None) + + def user_return(self, frame, return_value): + # This function is called when a return trap is set here + frame.f_locals['__return__'] = return_value + self.settitle('--Return--') + self.interaction(frame, None) + self.settitle('--Stack--') + + def user_exception(self, frame, (exc_type, exc_value, exc_traceback)): + # This function is called if an exception occurs, + # but only if we are to stop at or just below this level + frame.f_locals['__exception__'] = exc_type, exc_value + self.settitle(exc_type + ': ' + repr.repr(exc_value)) + self.interaction(frame, exc_traceback) + self.settitle('--Stack--') + + # Change the title + + def settitle(self, title): + self.savetitle = self.win.gettitle() + self.win.settitle(title) + + # General interaction function + + def interaction(self, frame, traceback): + import mainloop + self.popup() + self.setup(frame, traceback) + try: + mainloop.mainloop() + except WdbDone: + pass + self.forget() + + # Functions whose name is do_X for some character X + # are callable directly from the keyboard. + + def do_up(self): + if self.curindex == 0: + stdwin.fleep() + else: + self.curindex = self.curindex - 1 + self.curframe = self.stack[self.curindex][0] + self.refreshstack() + do_u = do_up + + def do_down(self): + if self.curindex + 1 == len(self.stack): + stdwin.fleep() + else: + self.curindex = self.curindex + 1 + self.curframe = self.stack[self.curindex][0] + self.refreshstack() + do_d = do_down + + def do_step(self): + self.set_step() + raise WdbDone + do_s = do_step + + def do_next(self): + self.set_next(self.curframe) + raise WdbDone + do_n = do_next + + def do_return(self): + self.set_return(self.curframe) + raise WdbDone + do_r = do_return + + def do_continue(self): + self.set_continue() + raise WdbDone + do_c = do_cont = do_continue + + def do_quit(self): + self.close() + raise WdbDone + do_q = do_quit + + def do_list(self): + fn = self.curframe.f_code.co_filename + if not self.sourcewindows.has_key(fn): + import wdbsrcwin + try: + self.sourcewindows[fn] = \ + wdbsrcwin.DebuggerSourceWindow(). \ + init(self, fn) + except IOError: + stdwin.fleep() + return + w = self.sourcewindows[fn] + lineno = self.stack[self.curindex][1] + w.setlineno(lineno) + w.popup() + do_l = do_list + + def do_frame(self): + name = 'locals' + `self.curframe`[16:-1] + if self.framewindows.has_key(name): + self.framewindows[name].popup() + else: + import wdbframewin + self.framewindows[name] = \ + wdbframewin.FrameWindow().init(self, \ + self.curframe, \ + self.curframe.f_locals, name) + do_f = do_frame + + def do_globalframe(self): + name = 'globals' + `self.curframe`[16:-1] + if self.framewindows.has_key(name): + self.framewindows[name].popup() + else: + import wdbframewin + self.framewindows[name] = \ + wdbframewin.FrameWindow().init(self, \ + self.curframe, \ + self.curframe.f_globals, name) + do_g = do_globalframe + + # Link between the debugger and the window + + def refreshstack(self): + height = stdwin.lineheight() * (1 + len(self.stack)) + self.win.setdocsize((0, height)) + self.refreshall() # XXX be more subtle later + # Also pass the information on to the source windows + filename = self.curframe.f_code.co_filename + lineno = self.curframe.f_lineno + for fn in self.sourcewindows.keys(): + w = self.sourcewindows[fn] + if fn == filename: + w.setlineno(lineno) + else: + w.resetlineno() + + # The remaining methods override BaseWindow methods + + def close(self): + if not self.closed: + basewin.BaseWindow.close(self) + self.closed = 1 + for key in self.sourcewindows.keys(): + self.sourcewindows[key].close() + for key in self.framewindows.keys(): + self.framewindows[key].close() + self.set_quit() + + def char(self, detail): + try: + func = eval('self.do_' + detail) + except (AttributeError, SyntaxError): + stdwin.fleep() + return + func() + + def command(self, detail): + if detail == WC_UP: + self.do_up() + elif detail == WC_DOWN: + self.do_down() + + def mouse_down(self, detail): + (h, v), clicks, button, mask = detail + i = v / stdwin.lineheight() + if 0 <= i < len(self.stack): + if i != self.curindex: + self.curindex = i + self.curframe = self.stack[self.curindex][0] + self.refreshstack() + elif clicks == 2: + self.do_frame() + else: + stdwin.fleep() + + def draw(self, detail): + import linecache, codehack, string + d = self.win.begindrawing() + try: + h, v = 0, 0 + for f, lineno in self.stack: + fn = f.f_code.co_filename + if f is self.curframe: + s = '> ' + else: + s = ' ' + s = s + fn + '(' + `lineno` + ')' + s = s + codehack.getcodename(f.f_code) + if f.f_locals.has_key('__args__'): + args = f.f_locals['__args__'] + if args is not None: + s = s + repr.repr(args) + if f.f_locals.has_key('__return__'): + rv = f.f_locals['__return__'] + s = s + '->' + s = s + repr.repr(rv) + line = linecache.getline(fn, lineno) + if line: s = s + ': ' + string.strip(line) + d.text((h, v), s) + v = v + d.lineheight() + finally: + d.close() + + +def run(statement): + x = Wdb().init() + try: x.run(statement) + finally: x.close() + +def runctx(statement, globals, locals): + x = Wdb().init() + try: x.runctx(statement, globals, locals) + finally: x.close() + +TESTCMD = 'import x; x.main()' + +def test(): + import linecache + linecache.checkcache() + run(TESTCMD) diff --git a/Lib/lib-stdwin/wdbframewin.py b/Lib/lib-stdwin/wdbframewin.py new file mode 100644 index 00000000000..422baa28149 --- /dev/null +++ b/Lib/lib-stdwin/wdbframewin.py @@ -0,0 +1,108 @@ +# wdb.py -- a window-based Python debugger + +import stdwin +from stdwinevents import * +import basewin +import sys + +WIDTH = 40 +MAXHEIGHT = 16 + +class FrameWindow(basewin.BaseWindow): + + def init(self, debugger, frame, dict, name): + self.debugger = debugger + self.frame = frame # Not used except for identity tests + self.dict = dict + self.name = name + nl = max(4, len(self.dict)) + nl = min(nl, MAXHEIGHT) + width = WIDTH*stdwin.textwidth('0') + height = nl*stdwin.lineheight() + stdwin.setdefwinsize(width, height) + self = basewin.BaseWindow.init(self, '--Frame ' + name + '--') + self.initeditor() + self.displaylist = ['>>>', '', '-'*WIDTH] + self.refreshframe() + return self + + def initeditor(self): + r = (stdwin.textwidth('>>> '), 0), (30000, stdwin.lineheight()) + self.editor = self.win.textcreate(r) + + def closeeditor(self): + self.editor.close() + + def dispatch(self, event): + type, win, detail = event + if type == WE_NULL: return # Dummy tested by mainloop + if type in (WE_DRAW, WE_COMMAND) \ + or not self.editor.event(event): + basewin.BaseWindow.dispatch(self, event) + + def close(self): + del self.debugger.framewindows[self.name] + del self.debugger, self.dict + self.closeeditor() + basewin.BaseWindow.close(self) + + def command(self, detail): + if detail == WC_RETURN: + self.re_eval() + else: + dummy = self.editor.event(WE_COMMAND, \ + self.win, detail) + + def re_eval(self): + import string, repr + expr = string.strip(self.editor.gettext()) + if expr == '': + output = '' + else: + globals = self.frame.f_globals + locals = self.frame.f_locals + try: + value = eval(expr, globals, locals) + output = repr.repr(value) + except: + output = sys.exc_type + ': ' + `sys.exc_value` + self.displaylist[1] = output + lh = stdwin.lineheight() + r = (-10, 0), (30000, 2*lh) + self.win.change(r) + self.editor.setfocus(0, len(expr)) + + def draw(self, detail): + (left, top), (right, bottom) = detail + dummy = self.editor.draw(detail) + d = self.win.begindrawing() + try: + lh = d.lineheight() + h, v = 0, 0 + for line in self.displaylist: + if v+lh > top and v < bottom: + d.text((h, v), line) + v = v + lh + finally: + d.close() + + def refreshframe(self): + import repr + del self.displaylist[3:] + self.re_eval() + names = self.dict.keys() + for key, label in ('__args__', 'Args: '), \ + ('__return__', 'Return: '): + if self.dict.has_key(key): + names.remove(key) + value = self.dict[key] + label = label + repr.repr(value) + self.displaylist.append(label) + names.sort() + for name in names: + value = self.dict[name] + line = name + ' = ' + repr.repr(value) + self.displaylist.append(line) + self.win.setdocsize(0, \ + stdwin.lineheight() * len(self.displaylist)) + self.refreshall() # XXX Be more subtle later diff --git a/Lib/lib-stdwin/wdbsrcwin.py b/Lib/lib-stdwin/wdbsrcwin.py new file mode 100644 index 00000000000..c5de9283e09 --- /dev/null +++ b/Lib/lib-stdwin/wdbsrcwin.py @@ -0,0 +1,97 @@ +# wdbsrcwin.py -- source window for wdb + +import stdwin +from stdwinevents import * +import srcwin + + +class DebuggerSourceWindow(srcwin.SourceWindow): + + def init(self, debugger, filename): + self.debugger = debugger + self.curlineno = 0 + self.focus = 0 + return srcwin.SourceWindow.init(self, filename) + + def close(self): + del self.debugger.sourcewindows[self.filename] + del self.debugger + srcwin.SourceWindow.close(self) + + def dispatch(self, event): + type, win, detail = event + if type == WE_CHAR: + self.char(detail) + elif type == WE_COMMAND: + self.command(detail) + elif type == WE_MOUSE_DOWN: + self.mouse_down(detail) + else: + srcwin.SourceWindow.dispatch(self, event) + + def char(self, detail): + self.debugger.char(detail) + + def command(self, detail): + self.debugger.command(detail) + + def mouse_down(self, detail): + (h, v), clicks, button, mask = detail + if h >= self.leftmargin: + srcwin.SourceWindow.dispatch(self, \ + (WE_MOUSE_DOWN, self.win, detail)) + return + lineno = v/self.lineheight + 1 + if 1 <= lineno <= self.linecount: + if self.debugger.get_break(self.filename, lineno): + f = self.debugger.clear_break + else: + f = self.debugger.set_break + err = f(self.filename, lineno) + if err: stdwin.message(err) + else: self.changemark(lineno) + else: + stdwin.fleep() + + def getmark(self, lineno): + s = `lineno` + if lineno == self.focus: + s = '[' + s + ']' + else: + s = ' ' + s + ' ' + if lineno == self.curlineno: + s = s + '->' + else: + s = s + ' ' + br = self.debugger.breaks + if br.has_key(self.filename) and lineno in br[self.filename]: + s = s + 'B' + else: + s = s + ' ' + return s + + def setlineno(self, newlineno): + if newlineno != self.curlineno: + oldlineno = self.curlineno + self.curlineno = newlineno + self.changemark(oldlineno) + self.changemark(newlineno) + if newlineno != 0: + self.showline(newlineno) + + def resetlineno(self): + self.setlineno(0) + + def setfocus(self, newfocus): + if newfocus != self.focus: + oldfocus = self.focus + self.focus = newfocus + self.changemark(oldfocus) + self.changemark(newfocus) + if newfocus != 0: + self.showline(newfocus) + + def resetfocus(self): + self.setfocus(0) + +# XXX Should get rid of focus stuff again diff --git a/Lib/repr.py b/Lib/repr.py new file mode 100644 index 00000000000..68f374c7a2e --- /dev/null +++ b/Lib/repr.py @@ -0,0 +1,83 @@ +# Redo the `...` (representation) but with limits on most sizes. + +import string + +class Repr: + def init(self): + self.maxlevel = 6 + self.maxtuple = 6 + self.maxlist = 6 + self.maxdict = 4 + self.maxstring = 30 + self.maxlong = 40 + self.maxother = 20 + return self + def repr(self, x): + return self.repr1(x, self.maxlevel) + def repr1(self, x, level): + typename = `type(x)`[7:-2] # "" + if ' ' in typename: + parts = string.split(typename) + typename = string.joinfields(parts, '_') + try: + f = eval('self.repr_' + typename) + except AttributeError: + s = `x` + if len(s) > self.maxother: + i = max(0, (self.maxother-3)/2) + j = max(0, self.maxother-3-i) + s = s[:i] + '...' + s[len(s)-j:] + return s + return f(x, level) + def repr_tuple(self, x, level): + n = len(x) + if n == 0: return '()' + if level <= 0: return '(...)' + s = '' + for i in range(min(n, self.maxtuple)): + if s: s = s + ', ' + s = s + self.repr1(x[i], level-1) + if n > self.maxtuple: s = s + ', ...' + elif n == 1: s = s + ',' + return '(' + s + ')' + def repr_list(self, x, level): + n = len(x) + if n == 0: return '[]' + if level <= 0: return '[...]' + s = '' + for i in range(min(n, self.maxlist)): + if s: s = s + ', ' + s = s + self.repr1(x[i], level-1) + if n > self.maxlist: s = s + ', ...' + return '[' + s + ']' + def repr_dictionary(self, x, level): + n = len(x) + if n == 0: return '{}' + if level <= 0: return '{...}' + s = '' + keys = x.keys() + keys.sort() + for i in range(min(n, self.maxdict)): + if s: s = s + ', ' + key = keys[i] + s = s + self.repr1(key, level-1) + s = s + ': ' + self.repr1(x[key], level-1) + if n > self.maxlist: s = s + ', ...' + return '{' + s + '}' + def repr_string(self, x, level): + s = `x[:self.maxstring]` + if len(s) > self.maxstring: + i = max(0, (self.maxstring-3)/2) + j = max(0, self.maxstring-3-i) + s = s[:i] + '...' + s[len(s)-j:] + return s + def repr_long_int(self, x, level): + s = `x` # XXX Hope this isn't too slow... + if len(s) > self.maxlong: + i = max(0, (self.maxlong-3)/2) + j = max(0, self.maxlong-3-i) + s = s[:i] + '...' + s[len(s)-j:] + return s + +aRepr = Repr().init() +repr = aRepr.repr diff --git a/Lib/stdwin/basewin.py b/Lib/stdwin/basewin.py new file mode 100755 index 00000000000..f896fe2a059 --- /dev/null +++ b/Lib/stdwin/basewin.py @@ -0,0 +1,65 @@ +# basewin.py + +import stdwin +import mainloop +from stdwinevents import * + +class BaseWindow: + + def init(self, title): + self.win = stdwin.open(title) + self.win.dispatch = self.dispatch + mainloop.register(self.win) + return self + +# def reopen(self): +# title = self.win.gettitle() +# winpos = self.win.getwinpos() +# winsize = self.win.getwinsize() +# origin = self.win.getorigin() +# docsize = self.win.getdocsize() +# mainloop.unregister(self.win) +# del self.win.dispatch +# self.win.close() +# stdwin.setdefwinpos(winpos) +# stdwin.setdefwinsize(winsize) +# self.win = stdwin.open(title) +# stdwin.setdefwinpos(0, 0) +# stdwin.setdefwinsize(0, 0) +# self.win.setdocsize(docsize) +# self.win.setorigin(origin) +# self.win.dispatch = self.dispatch +# mainloop.register(self.win) + + def popup(self): + if self.win is not stdwin.getactive(): + self.win.setactive() + + def close(self): + mainloop.unregister(self.win) + del self.win.dispatch + self.win.close() + + def dispatch(self, event): + type, win, detail = event + if type == WE_CHAR: + self.char(detail) + elif type == WE_COMMAND: + self.command(detail) + elif type == WE_MOUSE_DOWN: + self.mouse_down(detail) + elif type == WE_MOUSE_MOVE: + self.mouse_move(detail) + elif type == WE_MOUSE_UP: + self.mouse_up(detail) + elif type == WE_DRAW: + self.draw(detail) + elif type == WE_CLOSE: + self.close() + + def no_op(self, detail): + pass + char = command = mouse_down = mouse_move = mouse_up = draw = no_op + + def refreshall(self): + self.win.change((-10, 0), (10000, 30000)) diff --git a/Lib/stdwin/srcwin.py b/Lib/stdwin/srcwin.py new file mode 100755 index 00000000000..3f323bad53a --- /dev/null +++ b/Lib/stdwin/srcwin.py @@ -0,0 +1,103 @@ +# srcwin.py -- a source listing window + +import stdwin +from stdwinevents import * +import basewin + +WIDTH = 40 +MAXHEIGHT = 24 + +class SourceWindow(basewin.BaseWindow): + + def init(self, filename): + self.filename = filename + # + f = open(self.filename, 'r') # raise exception if not found + self.contents = f.read() + f.seek(0) + self.linecount = len(f.readlines()) + f.close() + # + self.lineheight = lh = stdwin.lineheight() + self.leftmargin = stdwin.textwidth('00000000') + self.rightmargin = 30000 # Infinity + self.bottom = lh * self.linecount + # + stdwin.setdefwinpos(0, 0) + width = WIDTH*stdwin.textwidth('0') + height = lh*min(MAXHEIGHT, self.linecount) + stdwin.setdefwinsize(width, height) + self = basewin.BaseWindow.init(self, filename) + # + self.win.setdocsize(0, self.bottom + lh) + self.initeditor() + return self + + def initeditor(self): + r = (self.leftmargin, 0), (self.rightmargin, self.bottom) + self.editor = self.win.textcreate(r) + self.editor.settext(self.contents) + + def closeeditor(self): + self.editor.close() + + def reopen(self): + self.closeeditor() + basewin.BaseWindow.reopen(self) + self.initeditor() + + def close(self): + self.closeeditor() + basewin.BaseWindow.close(self) + + # Override this method to format line numbers differently + def getmark(self, lineno): + return `lineno` + + def dispatch(self, event): + if event[0] == WE_NULL: return # Dummy tested by mainloop + if event[0] == WE_DRAW or not self.editor.event(event): + basewin.BaseWindow.dispatch(self, event) + + def draw(self, detail): + dummy = self.editor.draw(detail) + # Draw line numbers + (left, top), (right, bottom) = detail + topline = top/self.lineheight + botline = bottom/self.lineheight + 1 + botline = min(self.linecount, botline) + d = self.win.begindrawing() + try: + h, v = 0, self.lineheight * topline + for lineno in range(topline+1, botline+1): + d.text((h, v), self.getmark(lineno)) + v = v + self.lineheight + finally: + d.close() + + def changemark(self, lineno): + left = 0 + top = (lineno-1) * self.lineheight + right = self.leftmargin + bottom = lineno * self.lineheight + d = self.win.begindrawing() + try: + d.erase((left, top), (right, bottom)) + d.text((left, top), self.getmark(lineno)) + finally: + d.close() + + def showline(self, lineno): + left = 0 + top = (lineno-1) * self.lineheight + right = self.leftmargin + bottom = lineno * self.lineheight + self.win.show((left, top), (right, bottom)) + + +TESTFILE = 'srcwin.py' + +def test(): + import mainloop + sw = SourceWindow().init(TESTFILE) + mainloop.mainloop() diff --git a/Lib/stdwin/wdb.py b/Lib/stdwin/wdb.py new file mode 100755 index 00000000000..89b03f59765 --- /dev/null +++ b/Lib/stdwin/wdb.py @@ -0,0 +1,282 @@ +# wdb.py -- a window-based Python debugger + +import stdwin +from stdwinevents import * +import sys +import basewin +import bdb +import repr + +WIDTH = 40 +HEIGHT = 8 + +WdbDone = 'wdb.WdbDone' # Exception to continue execution + + +class Wdb(bdb.Bdb, basewin.BaseWindow): # Window debugger + + def init(self): + self.sourcewindows = {} + self.framewindows = {} + self = bdb.Bdb.init(self) + width = WIDTH*stdwin.textwidth('0') + height = HEIGHT*stdwin.lineheight() + stdwin.setdefwinsize(width, height) + self = basewin.BaseWindow.init(self, '--Stack--') + self.closed = 0 + return self + + def reset(self): + if self.closed: raise RuntimeError, 'already closed' + bdb.Bdb.reset(self) + self.forget() + + def forget(self): + self.lineno = None + self.stack = [] + self.curindex = 0 + self.curframe = None + for fn in self.sourcewindows.keys(): + self.sourcewindows[fn].resetlineno() + + def setup(self, f, t): + self.forget() + self.stack, self.curindex = self.get_stack(f, t) + self.curframe = self.stack[self.curindex][0] + # Build a list of current frames + cfl = [] + for f, i in self.stack: cfl.append(f) + # Remove deactivated frame windows + for name in self.framewindows.keys(): + fw = self.framewindows[name] + if fw.frame not in cfl: fw.close() + else: fw.refreshframe() + # Refresh the stack window + self.refreshstack() + + # Override Bdb methods (except user_call, for now) + + def user_line(self, frame): + # This function is called when we stop or break at this line + self.interaction(frame, None) + + def user_return(self, frame, return_value): + # This function is called when a return trap is set here + frame.f_locals['__return__'] = return_value + self.settitle('--Return--') + self.interaction(frame, None) + self.settitle('--Stack--') + + def user_exception(self, frame, (exc_type, exc_value, exc_traceback)): + # This function is called if an exception occurs, + # but only if we are to stop at or just below this level + frame.f_locals['__exception__'] = exc_type, exc_value + self.settitle(exc_type + ': ' + repr.repr(exc_value)) + self.interaction(frame, exc_traceback) + self.settitle('--Stack--') + + # Change the title + + def settitle(self, title): + self.savetitle = self.win.gettitle() + self.win.settitle(title) + + # General interaction function + + def interaction(self, frame, traceback): + import mainloop + self.popup() + self.setup(frame, traceback) + try: + mainloop.mainloop() + except WdbDone: + pass + self.forget() + + # Functions whose name is do_X for some character X + # are callable directly from the keyboard. + + def do_up(self): + if self.curindex == 0: + stdwin.fleep() + else: + self.curindex = self.curindex - 1 + self.curframe = self.stack[self.curindex][0] + self.refreshstack() + do_u = do_up + + def do_down(self): + if self.curindex + 1 == len(self.stack): + stdwin.fleep() + else: + self.curindex = self.curindex + 1 + self.curframe = self.stack[self.curindex][0] + self.refreshstack() + do_d = do_down + + def do_step(self): + self.set_step() + raise WdbDone + do_s = do_step + + def do_next(self): + self.set_next(self.curframe) + raise WdbDone + do_n = do_next + + def do_return(self): + self.set_return(self.curframe) + raise WdbDone + do_r = do_return + + def do_continue(self): + self.set_continue() + raise WdbDone + do_c = do_cont = do_continue + + def do_quit(self): + self.close() + raise WdbDone + do_q = do_quit + + def do_list(self): + fn = self.curframe.f_code.co_filename + if not self.sourcewindows.has_key(fn): + import wdbsrcwin + try: + self.sourcewindows[fn] = \ + wdbsrcwin.DebuggerSourceWindow(). \ + init(self, fn) + except IOError: + stdwin.fleep() + return + w = self.sourcewindows[fn] + lineno = self.stack[self.curindex][1] + w.setlineno(lineno) + w.popup() + do_l = do_list + + def do_frame(self): + name = 'locals' + `self.curframe`[16:-1] + if self.framewindows.has_key(name): + self.framewindows[name].popup() + else: + import wdbframewin + self.framewindows[name] = \ + wdbframewin.FrameWindow().init(self, \ + self.curframe, \ + self.curframe.f_locals, name) + do_f = do_frame + + def do_globalframe(self): + name = 'globals' + `self.curframe`[16:-1] + if self.framewindows.has_key(name): + self.framewindows[name].popup() + else: + import wdbframewin + self.framewindows[name] = \ + wdbframewin.FrameWindow().init(self, \ + self.curframe, \ + self.curframe.f_globals, name) + do_g = do_globalframe + + # Link between the debugger and the window + + def refreshstack(self): + height = stdwin.lineheight() * (1 + len(self.stack)) + self.win.setdocsize((0, height)) + self.refreshall() # XXX be more subtle later + # Also pass the information on to the source windows + filename = self.curframe.f_code.co_filename + lineno = self.curframe.f_lineno + for fn in self.sourcewindows.keys(): + w = self.sourcewindows[fn] + if fn == filename: + w.setlineno(lineno) + else: + w.resetlineno() + + # The remaining methods override BaseWindow methods + + def close(self): + if not self.closed: + basewin.BaseWindow.close(self) + self.closed = 1 + for key in self.sourcewindows.keys(): + self.sourcewindows[key].close() + for key in self.framewindows.keys(): + self.framewindows[key].close() + self.set_quit() + + def char(self, detail): + try: + func = eval('self.do_' + detail) + except (AttributeError, SyntaxError): + stdwin.fleep() + return + func() + + def command(self, detail): + if detail == WC_UP: + self.do_up() + elif detail == WC_DOWN: + self.do_down() + + def mouse_down(self, detail): + (h, v), clicks, button, mask = detail + i = v / stdwin.lineheight() + if 0 <= i < len(self.stack): + if i != self.curindex: + self.curindex = i + self.curframe = self.stack[self.curindex][0] + self.refreshstack() + elif clicks == 2: + self.do_frame() + else: + stdwin.fleep() + + def draw(self, detail): + import linecache, codehack, string + d = self.win.begindrawing() + try: + h, v = 0, 0 + for f, lineno in self.stack: + fn = f.f_code.co_filename + if f is self.curframe: + s = '> ' + else: + s = ' ' + s = s + fn + '(' + `lineno` + ')' + s = s + codehack.getcodename(f.f_code) + if f.f_locals.has_key('__args__'): + args = f.f_locals['__args__'] + if args is not None: + s = s + repr.repr(args) + if f.f_locals.has_key('__return__'): + rv = f.f_locals['__return__'] + s = s + '->' + s = s + repr.repr(rv) + line = linecache.getline(fn, lineno) + if line: s = s + ': ' + string.strip(line) + d.text((h, v), s) + v = v + d.lineheight() + finally: + d.close() + + +def run(statement): + x = Wdb().init() + try: x.run(statement) + finally: x.close() + +def runctx(statement, globals, locals): + x = Wdb().init() + try: x.runctx(statement, globals, locals) + finally: x.close() + +TESTCMD = 'import x; x.main()' + +def test(): + import linecache + linecache.checkcache() + run(TESTCMD) diff --git a/Lib/stdwin/wdbframewin.py b/Lib/stdwin/wdbframewin.py new file mode 100755 index 00000000000..422baa28149 --- /dev/null +++ b/Lib/stdwin/wdbframewin.py @@ -0,0 +1,108 @@ +# wdb.py -- a window-based Python debugger + +import stdwin +from stdwinevents import * +import basewin +import sys + +WIDTH = 40 +MAXHEIGHT = 16 + +class FrameWindow(basewin.BaseWindow): + + def init(self, debugger, frame, dict, name): + self.debugger = debugger + self.frame = frame # Not used except for identity tests + self.dict = dict + self.name = name + nl = max(4, len(self.dict)) + nl = min(nl, MAXHEIGHT) + width = WIDTH*stdwin.textwidth('0') + height = nl*stdwin.lineheight() + stdwin.setdefwinsize(width, height) + self = basewin.BaseWindow.init(self, '--Frame ' + name + '--') + self.initeditor() + self.displaylist = ['>>>', '', '-'*WIDTH] + self.refreshframe() + return self + + def initeditor(self): + r = (stdwin.textwidth('>>> '), 0), (30000, stdwin.lineheight()) + self.editor = self.win.textcreate(r) + + def closeeditor(self): + self.editor.close() + + def dispatch(self, event): + type, win, detail = event + if type == WE_NULL: return # Dummy tested by mainloop + if type in (WE_DRAW, WE_COMMAND) \ + or not self.editor.event(event): + basewin.BaseWindow.dispatch(self, event) + + def close(self): + del self.debugger.framewindows[self.name] + del self.debugger, self.dict + self.closeeditor() + basewin.BaseWindow.close(self) + + def command(self, detail): + if detail == WC_RETURN: + self.re_eval() + else: + dummy = self.editor.event(WE_COMMAND, \ + self.win, detail) + + def re_eval(self): + import string, repr + expr = string.strip(self.editor.gettext()) + if expr == '': + output = '' + else: + globals = self.frame.f_globals + locals = self.frame.f_locals + try: + value = eval(expr, globals, locals) + output = repr.repr(value) + except: + output = sys.exc_type + ': ' + `sys.exc_value` + self.displaylist[1] = output + lh = stdwin.lineheight() + r = (-10, 0), (30000, 2*lh) + self.win.change(r) + self.editor.setfocus(0, len(expr)) + + def draw(self, detail): + (left, top), (right, bottom) = detail + dummy = self.editor.draw(detail) + d = self.win.begindrawing() + try: + lh = d.lineheight() + h, v = 0, 0 + for line in self.displaylist: + if v+lh > top and v < bottom: + d.text((h, v), line) + v = v + lh + finally: + d.close() + + def refreshframe(self): + import repr + del self.displaylist[3:] + self.re_eval() + names = self.dict.keys() + for key, label in ('__args__', 'Args: '), \ + ('__return__', 'Return: '): + if self.dict.has_key(key): + names.remove(key) + value = self.dict[key] + label = label + repr.repr(value) + self.displaylist.append(label) + names.sort() + for name in names: + value = self.dict[name] + line = name + ' = ' + repr.repr(value) + self.displaylist.append(line) + self.win.setdocsize(0, \ + stdwin.lineheight() * len(self.displaylist)) + self.refreshall() # XXX Be more subtle later diff --git a/Lib/stdwin/wdbsrcwin.py b/Lib/stdwin/wdbsrcwin.py new file mode 100755 index 00000000000..c5de9283e09 --- /dev/null +++ b/Lib/stdwin/wdbsrcwin.py @@ -0,0 +1,97 @@ +# wdbsrcwin.py -- source window for wdb + +import stdwin +from stdwinevents import * +import srcwin + + +class DebuggerSourceWindow(srcwin.SourceWindow): + + def init(self, debugger, filename): + self.debugger = debugger + self.curlineno = 0 + self.focus = 0 + return srcwin.SourceWindow.init(self, filename) + + def close(self): + del self.debugger.sourcewindows[self.filename] + del self.debugger + srcwin.SourceWindow.close(self) + + def dispatch(self, event): + type, win, detail = event + if type == WE_CHAR: + self.char(detail) + elif type == WE_COMMAND: + self.command(detail) + elif type == WE_MOUSE_DOWN: + self.mouse_down(detail) + else: + srcwin.SourceWindow.dispatch(self, event) + + def char(self, detail): + self.debugger.char(detail) + + def command(self, detail): + self.debugger.command(detail) + + def mouse_down(self, detail): + (h, v), clicks, button, mask = detail + if h >= self.leftmargin: + srcwin.SourceWindow.dispatch(self, \ + (WE_MOUSE_DOWN, self.win, detail)) + return + lineno = v/self.lineheight + 1 + if 1 <= lineno <= self.linecount: + if self.debugger.get_break(self.filename, lineno): + f = self.debugger.clear_break + else: + f = self.debugger.set_break + err = f(self.filename, lineno) + if err: stdwin.message(err) + else: self.changemark(lineno) + else: + stdwin.fleep() + + def getmark(self, lineno): + s = `lineno` + if lineno == self.focus: + s = '[' + s + ']' + else: + s = ' ' + s + ' ' + if lineno == self.curlineno: + s = s + '->' + else: + s = s + ' ' + br = self.debugger.breaks + if br.has_key(self.filename) and lineno in br[self.filename]: + s = s + 'B' + else: + s = s + ' ' + return s + + def setlineno(self, newlineno): + if newlineno != self.curlineno: + oldlineno = self.curlineno + self.curlineno = newlineno + self.changemark(oldlineno) + self.changemark(newlineno) + if newlineno != 0: + self.showline(newlineno) + + def resetlineno(self): + self.setlineno(0) + + def setfocus(self, newfocus): + if newfocus != self.focus: + oldfocus = self.focus + self.focus = newfocus + self.changemark(oldfocus) + self.changemark(newfocus) + if newfocus != 0: + self.showline(newfocus) + + def resetfocus(self): + self.setfocus(0) + +# XXX Should get rid of focus stuff again