Home | History | Annotate | Download | only in idlelib
      1 import os
      2 import bdb
      3 import types
      4 from Tkinter import *
      5 from idlelib.WindowList import ListedToplevel
      6 from idlelib.ScrolledList import ScrolledList
      7 from idlelib import macosxSupport
      8 
      9 
     10 class Idb(bdb.Bdb):
     11 
     12     def __init__(self, gui):
     13         self.gui = gui
     14         bdb.Bdb.__init__(self)
     15 
     16     def user_line(self, frame):
     17         if self.in_rpc_code(frame):
     18             self.set_step()
     19             return
     20         message = self.__frame2message(frame)
     21         self.gui.interaction(message, frame)
     22 
     23     def user_exception(self, frame, info):
     24         if self.in_rpc_code(frame):
     25             self.set_step()
     26             return
     27         message = self.__frame2message(frame)
     28         self.gui.interaction(message, frame, info)
     29 
     30     def in_rpc_code(self, frame):
     31         if frame.f_code.co_filename.count('rpc.py'):
     32             return True
     33         else:
     34             prev_frame = frame.f_back
     35             if prev_frame.f_code.co_filename.count('Debugger.py'):
     36                 # (that test will catch both Debugger.py and RemoteDebugger.py)
     37                 return False
     38             return self.in_rpc_code(prev_frame)
     39 
     40     def __frame2message(self, frame):
     41         code = frame.f_code
     42         filename = code.co_filename
     43         lineno = frame.f_lineno
     44         basename = os.path.basename(filename)
     45         message = "%s:%s" % (basename, lineno)
     46         if code.co_name != "?":
     47             message = "%s: %s()" % (message, code.co_name)
     48         return message
     49 
     50 
     51 class Debugger:
     52 
     53     vstack = vsource = vlocals = vglobals = None
     54 
     55     def __init__(self, pyshell, idb=None):
     56         if idb is None:
     57             idb = Idb(self)
     58         self.pyshell = pyshell
     59         self.idb = idb
     60         self.frame = None
     61         self.make_gui()
     62         self.interacting = 0
     63 
     64     def run(self, *args):
     65         try:
     66             self.interacting = 1
     67             return self.idb.run(*args)
     68         finally:
     69             self.interacting = 0
     70 
     71     def close(self, event=None):
     72         if self.interacting:
     73             self.top.bell()
     74             return
     75         if self.stackviewer:
     76             self.stackviewer.close(); self.stackviewer = None
     77         # Clean up pyshell if user clicked debugger control close widget.
     78         # (Causes a harmless extra cycle through close_debugger() if user
     79         # toggled debugger from pyshell Debug menu)
     80         self.pyshell.close_debugger()
     81         # Now close the debugger control window....
     82         self.top.destroy()
     83 
     84     def make_gui(self):
     85         pyshell = self.pyshell
     86         self.flist = pyshell.flist
     87         self.root = root = pyshell.root
     88         self.top = top = ListedToplevel(root)
     89         self.top.wm_title("Debug Control")
     90         self.top.wm_iconname("Debug")
     91         top.wm_protocol("WM_DELETE_WINDOW", self.close)
     92         self.top.bind("<Escape>", self.close)
     93         #
     94         self.bframe = bframe = Frame(top)
     95         self.bframe.pack(anchor="w")
     96         self.buttons = bl = []
     97         #
     98         self.bcont = b = Button(bframe, text="Go", command=self.cont)
     99         bl.append(b)
    100         self.bstep = b = Button(bframe, text="Step", command=self.step)
    101         bl.append(b)
    102         self.bnext = b = Button(bframe, text="Over", command=self.next)
    103         bl.append(b)
    104         self.bret = b = Button(bframe, text="Out", command=self.ret)
    105         bl.append(b)
    106         self.bret = b = Button(bframe, text="Quit", command=self.quit)
    107         bl.append(b)
    108         #
    109         for b in bl:
    110             b.configure(state="disabled")
    111             b.pack(side="left")
    112         #
    113         self.cframe = cframe = Frame(bframe)
    114         self.cframe.pack(side="left")
    115         #
    116         if not self.vstack:
    117             self.__class__.vstack = BooleanVar(top)
    118             self.vstack.set(1)
    119         self.bstack = Checkbutton(cframe,
    120             text="Stack", command=self.show_stack, variable=self.vstack)
    121         self.bstack.grid(row=0, column=0)
    122         if not self.vsource:
    123             self.__class__.vsource = BooleanVar(top)
    124         self.bsource = Checkbutton(cframe,
    125             text="Source", command=self.show_source, variable=self.vsource)
    126         self.bsource.grid(row=0, column=1)
    127         if not self.vlocals:
    128             self.__class__.vlocals = BooleanVar(top)
    129             self.vlocals.set(1)
    130         self.blocals = Checkbutton(cframe,
    131             text="Locals", command=self.show_locals, variable=self.vlocals)
    132         self.blocals.grid(row=1, column=0)
    133         if not self.vglobals:
    134             self.__class__.vglobals = BooleanVar(top)
    135         self.bglobals = Checkbutton(cframe,
    136             text="Globals", command=self.show_globals, variable=self.vglobals)
    137         self.bglobals.grid(row=1, column=1)
    138         #
    139         self.status = Label(top, anchor="w")
    140         self.status.pack(anchor="w")
    141         self.error = Label(top, anchor="w")
    142         self.error.pack(anchor="w", fill="x")
    143         self.errorbg = self.error.cget("background")
    144         #
    145         self.fstack = Frame(top, height=1)
    146         self.fstack.pack(expand=1, fill="both")
    147         self.flocals = Frame(top)
    148         self.flocals.pack(expand=1, fill="both")
    149         self.fglobals = Frame(top, height=1)
    150         self.fglobals.pack(expand=1, fill="both")
    151         #
    152         if self.vstack.get():
    153             self.show_stack()
    154         if self.vlocals.get():
    155             self.show_locals()
    156         if self.vglobals.get():
    157             self.show_globals()
    158 
    159     def interaction(self, message, frame, info=None):
    160         self.frame = frame
    161         self.status.configure(text=message)
    162         #
    163         if info:
    164             type, value, tb = info
    165             try:
    166                 m1 = type.__name__
    167             except AttributeError:
    168                 m1 = "%s" % str(type)
    169             if value is not None:
    170                 try:
    171                     m1 = "%s: %s" % (m1, str(value))
    172                 except:
    173                     pass
    174             bg = "yellow"
    175         else:
    176             m1 = ""
    177             tb = None
    178             bg = self.errorbg
    179         self.error.configure(text=m1, background=bg)
    180         #
    181         sv = self.stackviewer
    182         if sv:
    183             stack, i = self.idb.get_stack(self.frame, tb)
    184             sv.load_stack(stack, i)
    185         #
    186         self.show_variables(1)
    187         #
    188         if self.vsource.get():
    189             self.sync_source_line()
    190         #
    191         for b in self.buttons:
    192             b.configure(state="normal")
    193         #
    194         self.top.wakeup()
    195         self.root.mainloop()
    196         #
    197         for b in self.buttons:
    198             b.configure(state="disabled")
    199         self.status.configure(text="")
    200         self.error.configure(text="", background=self.errorbg)
    201         self.frame = None
    202 
    203     def sync_source_line(self):
    204         frame = self.frame
    205         if not frame:
    206             return
    207         filename, lineno = self.__frame2fileline(frame)
    208         if filename[:1] + filename[-1:] != "<>" and os.path.exists(filename):
    209             self.flist.gotofileline(filename, lineno)
    210 
    211     def __frame2fileline(self, frame):
    212         code = frame.f_code
    213         filename = code.co_filename
    214         lineno = frame.f_lineno
    215         return filename, lineno
    216 
    217     def cont(self):
    218         self.idb.set_continue()
    219         self.root.quit()
    220 
    221     def step(self):
    222         self.idb.set_step()
    223         self.root.quit()
    224 
    225     def next(self):
    226         self.idb.set_next(self.frame)
    227         self.root.quit()
    228 
    229     def ret(self):
    230         self.idb.set_return(self.frame)
    231         self.root.quit()
    232 
    233     def quit(self):
    234         self.idb.set_quit()
    235         self.root.quit()
    236 
    237     stackviewer = None
    238 
    239     def show_stack(self):
    240         if not self.stackviewer and self.vstack.get():
    241             self.stackviewer = sv = StackViewer(self.fstack, self.flist, self)
    242             if self.frame:
    243                 stack, i = self.idb.get_stack(self.frame, None)
    244                 sv.load_stack(stack, i)
    245         else:
    246             sv = self.stackviewer
    247             if sv and not self.vstack.get():
    248                 self.stackviewer = None
    249                 sv.close()
    250             self.fstack['height'] = 1
    251 
    252     def show_source(self):
    253         if self.vsource.get():
    254             self.sync_source_line()
    255 
    256     def show_frame(self, (frame, lineno)):
    257         self.frame = frame
    258         self.show_variables()
    259 
    260     localsviewer = None
    261     globalsviewer = None
    262 
    263     def show_locals(self):
    264         lv = self.localsviewer
    265         if self.vlocals.get():
    266             if not lv:
    267                 self.localsviewer = NamespaceViewer(self.flocals, "Locals")
    268         else:
    269             if lv:
    270                 self.localsviewer = None
    271                 lv.close()
    272                 self.flocals['height'] = 1
    273         self.show_variables()
    274 
    275     def show_globals(self):
    276         gv = self.globalsviewer
    277         if self.vglobals.get():
    278             if not gv:
    279                 self.globalsviewer = NamespaceViewer(self.fglobals, "Globals")
    280         else:
    281             if gv:
    282                 self.globalsviewer = None
    283                 gv.close()
    284                 self.fglobals['height'] = 1
    285         self.show_variables()
    286 
    287     def show_variables(self, force=0):
    288         lv = self.localsviewer
    289         gv = self.globalsviewer
    290         frame = self.frame
    291         if not frame:
    292             ldict = gdict = None
    293         else:
    294             ldict = frame.f_locals
    295             gdict = frame.f_globals
    296             if lv and gv and ldict is gdict:
    297                 ldict = None
    298         if lv:
    299             lv.load_dict(ldict, force, self.pyshell.interp.rpcclt)
    300         if gv:
    301             gv.load_dict(gdict, force, self.pyshell.interp.rpcclt)
    302 
    303     def set_breakpoint_here(self, filename, lineno):
    304         self.idb.set_break(filename, lineno)
    305 
    306     def clear_breakpoint_here(self, filename, lineno):
    307         self.idb.clear_break(filename, lineno)
    308 
    309     def clear_file_breaks(self, filename):
    310         self.idb.clear_all_file_breaks(filename)
    311 
    312     def load_breakpoints(self):
    313         "Load PyShellEditorWindow breakpoints into subprocess debugger"
    314         pyshell_edit_windows = self.pyshell.flist.inversedict.keys()
    315         for editwin in pyshell_edit_windows:
    316             filename = editwin.io.filename
    317             try:
    318                 for lineno in editwin.breakpoints:
    319                     self.set_breakpoint_here(filename, lineno)
    320             except AttributeError:
    321                 continue
    322 
    323 class StackViewer(ScrolledList):
    324 
    325     def __init__(self, master, flist, gui):
    326         if macosxSupport.runningAsOSXApp():
    327             # At least on with the stock AquaTk version on OSX 10.4 you'll
    328             # get an shaking GUI that eventually kills IDLE if the width
    329             # argument is specified.
    330             ScrolledList.__init__(self, master)
    331         else:
    332             ScrolledList.__init__(self, master, width=80)
    333         self.flist = flist
    334         self.gui = gui
    335         self.stack = []
    336 
    337     def load_stack(self, stack, index=None):
    338         self.stack = stack
    339         self.clear()
    340         for i in range(len(stack)):
    341             frame, lineno = stack[i]
    342             try:
    343                 modname = frame.f_globals["__name__"]
    344             except:
    345                 modname = "?"
    346             code = frame.f_code
    347             filename = code.co_filename
    348             funcname = code.co_name
    349             import linecache
    350             sourceline = linecache.getline(filename, lineno)
    351             import string
    352             sourceline = string.strip(sourceline)
    353             if funcname in ("?", "", None):
    354                 item = "%s, line %d: %s" % (modname, lineno, sourceline)
    355             else:
    356                 item = "%s.%s(), line %d: %s" % (modname, funcname,
    357                                                  lineno, sourceline)
    358             if i == index:
    359                 item = "> " + item
    360             self.append(item)
    361         if index is not None:
    362             self.select(index)
    363 
    364     def popup_event(self, event):
    365         "override base method"
    366         if self.stack:
    367             return ScrolledList.popup_event(self, event)
    368 
    369     def fill_menu(self):
    370         "override base method"
    371         menu = self.menu
    372         menu.add_command(label="Go to source line",
    373                          command=self.goto_source_line)
    374         menu.add_command(label="Show stack frame",
    375                          command=self.show_stack_frame)
    376 
    377     def on_select(self, index):
    378         "override base method"
    379         if 0 <= index < len(self.stack):
    380             self.gui.show_frame(self.stack[index])
    381 
    382     def on_double(self, index):
    383         "override base method"
    384         self.show_source(index)
    385 
    386     def goto_source_line(self):
    387         index = self.listbox.index("active")
    388         self.show_source(index)
    389 
    390     def show_stack_frame(self):
    391         index = self.listbox.index("active")
    392         if 0 <= index < len(self.stack):
    393             self.gui.show_frame(self.stack[index])
    394 
    395     def show_source(self, index):
    396         if not (0 <= index < len(self.stack)):
    397             return
    398         frame, lineno = self.stack[index]
    399         code = frame.f_code
    400         filename = code.co_filename
    401         if os.path.isfile(filename):
    402             edit = self.flist.open(filename)
    403             if edit:
    404                 edit.gotoline(lineno)
    405 
    406 
    407 class NamespaceViewer:
    408 
    409     def __init__(self, master, title, dict=None):
    410         width = 0
    411         height = 40
    412         if dict:
    413             height = 20*len(dict) # XXX 20 == observed height of Entry widget
    414         self.master = master
    415         self.title = title
    416         import repr
    417         self.repr = repr.Repr()
    418         self.repr.maxstring = 60
    419         self.repr.maxother = 60
    420         self.frame = frame = Frame(master)
    421         self.frame.pack(expand=1, fill="both")
    422         self.label = Label(frame, text=title, borderwidth=2, relief="groove")
    423         self.label.pack(fill="x")
    424         self.vbar = vbar = Scrollbar(frame, name="vbar")
    425         vbar.pack(side="right", fill="y")
    426         self.canvas = canvas = Canvas(frame,
    427                                       height=min(300, max(40, height)),
    428                                       scrollregion=(0, 0, width, height))
    429         canvas.pack(side="left", fill="both", expand=1)
    430         vbar["command"] = canvas.yview
    431         canvas["yscrollcommand"] = vbar.set
    432         self.subframe = subframe = Frame(canvas)
    433         self.sfid = canvas.create_window(0, 0, window=subframe, anchor="nw")
    434         self.load_dict(dict)
    435 
    436     dict = -1
    437 
    438     def load_dict(self, dict, force=0, rpc_client=None):
    439         if dict is self.dict and not force:
    440             return
    441         subframe = self.subframe
    442         frame = self.frame
    443         for c in subframe.children.values():
    444             c.destroy()
    445         self.dict = None
    446         if not dict:
    447             l = Label(subframe, text="None")
    448             l.grid(row=0, column=0)
    449         else:
    450             names = dict.keys()
    451             names.sort()
    452             row = 0
    453             for name in names:
    454                 value = dict[name]
    455                 svalue = self.repr.repr(value) # repr(value)
    456                 # Strip extra quotes caused by calling repr on the (already)
    457                 # repr'd value sent across the RPC interface:
    458                 if rpc_client:
    459                     svalue = svalue[1:-1]
    460                 l = Label(subframe, text=name)
    461                 l.grid(row=row, column=0, sticky="nw")
    462                 l = Entry(subframe, width=0, borderwidth=0)
    463                 l.insert(0, svalue)
    464                 l.grid(row=row, column=1, sticky="nw")
    465                 row = row+1
    466         self.dict = dict
    467         # XXX Could we use a <Configure> callback for the following?
    468         subframe.update_idletasks() # Alas!
    469         width = subframe.winfo_reqwidth()
    470         height = subframe.winfo_reqheight()
    471         canvas = self.canvas
    472         self.canvas["scrollregion"] = (0, 0, width, height)
    473         if height > 300:
    474             canvas["height"] = 300
    475             frame.pack(expand=1)
    476         else:
    477             canvas["height"] = height
    478             frame.pack(expand=0)
    479 
    480     def close(self):
    481         self.frame.destroy()
    482