blob: 5f851821768fbc59690cfb5593a7cb7648d0fb25 [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001import os
2import bdb
Chui Tey5d2af632002-05-26 13:36:41 +00003import types
David Scherer7aced172000-08-15 01:13:23 +00004from Tkinter import *
5from WindowList import ListedToplevel
unknowned813bf2002-07-05 22:05:24 +00006from ScrolledList import ScrolledList
David Scherer7aced172000-08-15 01:13:23 +00007
8
Chui Tey5d2af632002-05-26 13:36:41 +00009class Idb(bdb.Bdb):
10
11 def __init__(self, gui):
12 self.gui = gui
13 bdb.Bdb.__init__(self)
14
15 def user_line(self, frame):
Chui Tey067d7342002-12-12 20:53:19 +000016
Chui Tey5d2af632002-05-26 13:36:41 +000017 co_filename = frame.f_code.co_filename
Kurt B. Kaiser38f11102003-01-01 00:26:41 +000018## co_name = frame.f_code.co_name
Chui Tey067d7342002-12-12 20:53:19 +000019
20 ## print>>sys.__stderr__, "*function: ", frame.f_code.co_name
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000021 ## print>>sys.__stderr__, "*file: ", frame.f_code.co_filename
Chui Tey067d7342002-12-12 20:53:19 +000022 ## print>>sys.__stderr__, "*line number: ", frame.f_code.co_firstlineno
23 ## print>>sys.__stderr__, "*name: ", co_name
24 ## print>>sys.__stderr__, "*function: ", frame.f_locals.get(co_name,None)
25
Kurt B. Kaiser38f11102003-01-01 00:26:41 +000026## try:
27## # XXX 12 Dec 2002 CGT TO DO: Find way to get a reference to the
28## # XXX currently running function. If the function has an
29## # attribute called "DebuggerStepThrough", prevent the debugger
30## # from stepping through Idle code. The following doesn't work
31## # in instance methods. Hard coded some workarounds.
32## func = frame.f_locals[co_name]
33## if getattr(func, "DebuggerStepThrough", 0):
34## print "XXXX DEBUGGER STEPPING THROUGH"
35## self.set_step()
36## return
37## except:
38## pass
Chui Tey067d7342002-12-12 20:53:19 +000039
40 # workaround for the problem above
Kurt B. Kaiser38f11102003-01-01 00:26:41 +000041 exclude = ('rpc.py', 'threading.py', '<string>')
42 for rpcfile in exclude:
43 if co_filename.count(rpcfile):
44 self.set_step()
45 return
Chui Tey5d2af632002-05-26 13:36:41 +000046 message = self.__frame2message(frame)
47 self.gui.interaction(message, frame)
48
49 def user_exception(self, frame, info):
50 message = self.__frame2message(frame)
51 self.gui.interaction(message, frame, info)
52
53 def __frame2message(self, frame):
54 code = frame.f_code
55 filename = code.co_filename
56 lineno = frame.f_lineno
57 basename = os.path.basename(filename)
58 message = "%s:%s" % (basename, lineno)
59 if code.co_name != "?":
60 message = "%s: %s()" % (message, code.co_name)
61 return message
62
63
64class Debugger:
David Scherer7aced172000-08-15 01:13:23 +000065
David Scherer7aced172000-08-15 01:13:23 +000066 vstack = vsource = vlocals = vglobals = None
67
Chui Tey5d2af632002-05-26 13:36:41 +000068 def __init__(self, pyshell, idb=None):
69 if idb is None:
70 idb = Idb(self)
David Scherer7aced172000-08-15 01:13:23 +000071 self.pyshell = pyshell
Chui Tey5d2af632002-05-26 13:36:41 +000072 self.idb = idb
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +000073 self.frame = None
David Scherer7aced172000-08-15 01:13:23 +000074 self.make_gui()
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +000075 self.interacting = 0
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000076
Chui Tey5d2af632002-05-26 13:36:41 +000077 def run(self, *args):
78 try:
79 self.interacting = 1
80 return self.idb.run(*args)
81 finally:
82 self.interacting = 0
David Scherer7aced172000-08-15 01:13:23 +000083
84 def close(self, event=None):
85 if self.interacting:
86 self.top.bell()
87 return
88 if self.stackviewer:
89 self.stackviewer.close(); self.stackviewer = None
Kurt B. Kaiserf8096fb2002-06-25 03:28:38 +000090 # Clean up pyshell if user clicked debugger control close widget.
91 # (Causes a harmless extra cycle through close_debugger() if user
92 # toggled debugger from pyshell Debug menu)
David Scherer7aced172000-08-15 01:13:23 +000093 self.pyshell.close_debugger()
Kurt B. Kaiserf8096fb2002-06-25 03:28:38 +000094 # Now close the debugger control window....
David Scherer7aced172000-08-15 01:13:23 +000095 self.top.destroy()
96
David Scherer7aced172000-08-15 01:13:23 +000097 def make_gui(self):
98 pyshell = self.pyshell
99 self.flist = pyshell.flist
100 self.root = root = pyshell.root
101 self.top = top =ListedToplevel(root)
102 self.top.wm_title("Debug Control")
103 self.top.wm_iconname("Debug")
104 top.wm_protocol("WM_DELETE_WINDOW", self.close)
105 self.top.bind("<Escape>", self.close)
106 #
107 self.bframe = bframe = Frame(top)
108 self.bframe.pack(anchor="w")
109 self.buttons = bl = []
110 #
111 self.bcont = b = Button(bframe, text="Go", command=self.cont)
112 bl.append(b)
113 self.bstep = b = Button(bframe, text="Step", command=self.step)
114 bl.append(b)
115 self.bnext = b = Button(bframe, text="Over", command=self.next)
116 bl.append(b)
117 self.bret = b = Button(bframe, text="Out", command=self.ret)
118 bl.append(b)
119 self.bret = b = Button(bframe, text="Quit", command=self.quit)
120 bl.append(b)
121 #
122 for b in bl:
123 b.configure(state="disabled")
124 b.pack(side="left")
125 #
126 self.cframe = cframe = Frame(bframe)
127 self.cframe.pack(side="left")
128 #
129 if not self.vstack:
130 self.__class__.vstack = BooleanVar(top)
131 self.vstack.set(1)
132 self.bstack = Checkbutton(cframe,
133 text="Stack", command=self.show_stack, variable=self.vstack)
134 self.bstack.grid(row=0, column=0)
135 if not self.vsource:
136 self.__class__.vsource = BooleanVar(top)
David Scherer7aced172000-08-15 01:13:23 +0000137 self.bsource = Checkbutton(cframe,
138 text="Source", command=self.show_source, variable=self.vsource)
139 self.bsource.grid(row=0, column=1)
140 if not self.vlocals:
141 self.__class__.vlocals = BooleanVar(top)
142 self.vlocals.set(1)
143 self.blocals = Checkbutton(cframe,
144 text="Locals", command=self.show_locals, variable=self.vlocals)
145 self.blocals.grid(row=1, column=0)
146 if not self.vglobals:
147 self.__class__.vglobals = BooleanVar(top)
David Scherer7aced172000-08-15 01:13:23 +0000148 self.bglobals = Checkbutton(cframe,
149 text="Globals", command=self.show_globals, variable=self.vglobals)
150 self.bglobals.grid(row=1, column=1)
151 #
152 self.status = Label(top, anchor="w")
153 self.status.pack(anchor="w")
154 self.error = Label(top, anchor="w")
155 self.error.pack(anchor="w", fill="x")
156 self.errorbg = self.error.cget("background")
157 #
158 self.fstack = Frame(top, height=1)
159 self.fstack.pack(expand=1, fill="both")
160 self.flocals = Frame(top)
161 self.flocals.pack(expand=1, fill="both")
162 self.fglobals = Frame(top, height=1)
163 self.fglobals.pack(expand=1, fill="both")
164 #
165 if self.vstack.get():
166 self.show_stack()
167 if self.vlocals.get():
168 self.show_locals()
169 if self.vglobals.get():
170 self.show_globals()
171
David Scherer7aced172000-08-15 01:13:23 +0000172
Chui Tey5d2af632002-05-26 13:36:41 +0000173 def interaction(self, message, frame, info=None):
David Scherer7aced172000-08-15 01:13:23 +0000174 self.frame = frame
David Scherer7aced172000-08-15 01:13:23 +0000175 self.status.configure(text=message)
176 #
177 if info:
178 type, value, tb = info
179 try:
180 m1 = type.__name__
181 except AttributeError:
182 m1 = "%s" % str(type)
183 if value is not None:
184 try:
185 m1 = "%s: %s" % (m1, str(value))
186 except:
187 pass
188 bg = "yellow"
189 else:
190 m1 = ""
191 tb = None
192 bg = self.errorbg
193 self.error.configure(text=m1, background=bg)
194 #
195 sv = self.stackviewer
196 if sv:
Chui Tey5d2af632002-05-26 13:36:41 +0000197 stack, i = self.idb.get_stack(self.frame, tb)
David Scherer7aced172000-08-15 01:13:23 +0000198 sv.load_stack(stack, i)
199 #
200 self.show_variables(1)
201 #
202 if self.vsource.get():
203 self.sync_source_line()
204 #
205 for b in self.buttons:
206 b.configure(state="normal")
207 #
208 self.top.tkraise()
209 self.root.mainloop()
210 #
211 for b in self.buttons:
212 b.configure(state="disabled")
213 self.status.configure(text="")
214 self.error.configure(text="", background=self.errorbg)
215 self.frame = None
216
217 def sync_source_line(self):
218 frame = self.frame
219 if not frame:
220 return
Chui Tey5d2af632002-05-26 13:36:41 +0000221 filename, lineno = self.__frame2fileline(frame)
222 if filename[:1] + filename[-1:] != "<>" and os.path.exists(filename):
223 self.flist.gotofileline(filename, lineno)
224
225 def __frame2fileline(self, frame):
David Scherer7aced172000-08-15 01:13:23 +0000226 code = frame.f_code
Chui Tey5d2af632002-05-26 13:36:41 +0000227 filename = code.co_filename
David Scherer7aced172000-08-15 01:13:23 +0000228 lineno = frame.f_lineno
Chui Tey5d2af632002-05-26 13:36:41 +0000229 return filename, lineno
David Scherer7aced172000-08-15 01:13:23 +0000230
231 def cont(self):
Chui Tey5d2af632002-05-26 13:36:41 +0000232 self.idb.set_continue()
David Scherer7aced172000-08-15 01:13:23 +0000233 self.root.quit()
234
235 def step(self):
Chui Tey5d2af632002-05-26 13:36:41 +0000236 self.idb.set_step()
David Scherer7aced172000-08-15 01:13:23 +0000237 self.root.quit()
238
239 def next(self):
Chui Tey5d2af632002-05-26 13:36:41 +0000240 self.idb.set_next(self.frame)
David Scherer7aced172000-08-15 01:13:23 +0000241 self.root.quit()
242
243 def ret(self):
Chui Tey5d2af632002-05-26 13:36:41 +0000244 self.idb.set_return(self.frame)
David Scherer7aced172000-08-15 01:13:23 +0000245 self.root.quit()
246
247 def quit(self):
Chui Tey5d2af632002-05-26 13:36:41 +0000248 self.idb.set_quit()
David Scherer7aced172000-08-15 01:13:23 +0000249 self.root.quit()
250
251 stackviewer = None
252
253 def show_stack(self):
254 if not self.stackviewer and self.vstack.get():
unknowned813bf2002-07-05 22:05:24 +0000255 self.stackviewer = sv = StackViewer(self.fstack, self.flist, self)
David Scherer7aced172000-08-15 01:13:23 +0000256 if self.frame:
Chui Tey5d2af632002-05-26 13:36:41 +0000257 stack, i = self.idb.get_stack(self.frame, None)
David Scherer7aced172000-08-15 01:13:23 +0000258 sv.load_stack(stack, i)
259 else:
260 sv = self.stackviewer
261 if sv and not self.vstack.get():
262 self.stackviewer = None
263 sv.close()
264 self.fstack['height'] = 1
265
266 def show_source(self):
267 if self.vsource.get():
268 self.sync_source_line()
269
270 def show_frame(self, (frame, lineno)):
271 self.frame = frame
272 self.show_variables()
273
274 localsviewer = None
275 globalsviewer = None
276
277 def show_locals(self):
278 lv = self.localsviewer
279 if self.vlocals.get():
280 if not lv:
unknowned813bf2002-07-05 22:05:24 +0000281 self.localsviewer = NamespaceViewer(self.flocals, "Locals")
David Scherer7aced172000-08-15 01:13:23 +0000282 else:
283 if lv:
284 self.localsviewer = None
285 lv.close()
286 self.flocals['height'] = 1
287 self.show_variables()
288
289 def show_globals(self):
290 gv = self.globalsviewer
291 if self.vglobals.get():
292 if not gv:
unknowned813bf2002-07-05 22:05:24 +0000293 self.globalsviewer = NamespaceViewer(self.fglobals, "Globals")
David Scherer7aced172000-08-15 01:13:23 +0000294 else:
295 if gv:
296 self.globalsviewer = None
297 gv.close()
298 self.fglobals['height'] = 1
299 self.show_variables()
300
301 def show_variables(self, force=0):
302 lv = self.localsviewer
303 gv = self.globalsviewer
304 frame = self.frame
305 if not frame:
306 ldict = gdict = None
307 else:
308 ldict = frame.f_locals
309 gdict = frame.f_globals
310 if lv and gv and ldict is gdict:
311 ldict = None
312 if lv:
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000313 lv.load_dict(ldict, force, self.pyshell.interp.rpcclt)
David Scherer7aced172000-08-15 01:13:23 +0000314 if gv:
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000315 gv.load_dict(gdict, force, self.pyshell.interp.rpcclt)
David Scherer7aced172000-08-15 01:13:23 +0000316
Kurt B. Kaiser45186c42002-10-23 04:48:08 +0000317 def set_breakpoint_here(self, filename, lineno):
Kurt B. Kaiser491427d2002-12-02 04:41:29 +0000318 self.idb.set_break(filename, lineno)
David Scherer7aced172000-08-15 01:13:23 +0000319
Kurt B. Kaiser45186c42002-10-23 04:48:08 +0000320 def clear_breakpoint_here(self, filename, lineno):
Kurt B. Kaiser491427d2002-12-02 04:41:29 +0000321 self.idb.clear_break(filename, lineno)
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000322
Kurt B. Kaiser45186c42002-10-23 04:48:08 +0000323 def clear_file_breaks(self, filename):
Kurt B. Kaiser491427d2002-12-02 04:41:29 +0000324 self.idb.clear_all_file_breaks(filename)
unknowned813bf2002-07-05 22:05:24 +0000325
Kurt B. Kaiser45186c42002-10-23 04:48:08 +0000326 def load_breakpoints(self):
327 "Load PyShellEditorWindow breakpoints into subprocess debugger"
328 pyshell_edit_windows = self.pyshell.flist.inversedict.keys()
329 for editwin in pyshell_edit_windows:
330 filename = editwin.io.filename
331 try:
Kurt B. Kaiserbfed3462002-12-14 04:38:51 +0000332 for lineno in editwin.breakpoints:
Kurt B. Kaiser45186c42002-10-23 04:48:08 +0000333 self.set_breakpoint_here(filename, lineno)
334 except AttributeError:
335 continue
unknowned813bf2002-07-05 22:05:24 +0000336
337class StackViewer(ScrolledList):
338
339 def __init__(self, master, flist, gui):
340 ScrolledList.__init__(self, master, width=80)
341 self.flist = flist
342 self.gui = gui
343 self.stack = []
344
345 def load_stack(self, stack, index=None):
346 self.stack = stack
347 self.clear()
348 for i in range(len(stack)):
349 frame, lineno = stack[i]
350 try:
351 modname = frame.f_globals["__name__"]
352 except:
353 modname = "?"
354 code = frame.f_code
355 filename = code.co_filename
356 funcname = code.co_name
357 import linecache
358 sourceline = linecache.getline(filename, lineno)
359 import string
360 sourceline = string.strip(sourceline)
361 if funcname in ("?", "", None):
362 item = "%s, line %d: %s" % (modname, lineno, sourceline)
363 else:
364 item = "%s.%s(), line %d: %s" % (modname, funcname,
365 lineno, sourceline)
366 if i == index:
367 item = "> " + item
368 self.append(item)
369 if index is not None:
370 self.select(index)
371
372 def popup_event(self, event):
373 "override base method"
374 if self.stack:
375 return ScrolledList.popup_event(self, event)
376
377 def fill_menu(self):
378 "override base method"
379 menu = self.menu
380 menu.add_command(label="Go to source line",
381 command=self.goto_source_line)
382 menu.add_command(label="Show stack frame",
383 command=self.show_stack_frame)
384
385 def on_select(self, index):
386 "override base method"
387 if 0 <= index < len(self.stack):
388 self.gui.show_frame(self.stack[index])
389
390 def on_double(self, index):
391 "override base method"
392 self.show_source(index)
393
394 def goto_source_line(self):
395 index = self.listbox.index("active")
396 self.show_source(index)
397
398 def show_stack_frame(self):
399 index = self.listbox.index("active")
400 if 0 <= index < len(self.stack):
401 self.gui.show_frame(self.stack[index])
402
403 def show_source(self, index):
404 if not (0 <= index < len(self.stack)):
405 return
406 frame, lineno = self.stack[index]
407 code = frame.f_code
408 filename = code.co_filename
409 if os.path.isfile(filename):
410 edit = self.flist.open(filename)
411 if edit:
412 edit.gotoline(lineno)
413
414
415class NamespaceViewer:
416
417 def __init__(self, master, title, dict=None):
418 width = 0
419 height = 40
420 if dict:
421 height = 20*len(dict) # XXX 20 == observed height of Entry widget
422 self.master = master
423 self.title = title
424 import repr
425 self.repr = repr.Repr()
426 self.repr.maxstring = 60
427 self.repr.maxother = 60
428 self.frame = frame = Frame(master)
429 self.frame.pack(expand=1, fill="both")
430 self.label = Label(frame, text=title, borderwidth=2, relief="groove")
431 self.label.pack(fill="x")
432 self.vbar = vbar = Scrollbar(frame, name="vbar")
433 vbar.pack(side="right", fill="y")
434 self.canvas = canvas = Canvas(frame,
435 height=min(300, max(40, height)),
436 scrollregion=(0, 0, width, height))
437 canvas.pack(side="left", fill="both", expand=1)
438 vbar["command"] = canvas.yview
439 canvas["yscrollcommand"] = vbar.set
440 self.subframe = subframe = Frame(canvas)
441 self.sfid = canvas.create_window(0, 0, window=subframe, anchor="nw")
442 self.load_dict(dict)
443
444 dict = -1
445
446 def load_dict(self, dict, force=0, rpc_client=None):
447 if dict is self.dict and not force:
448 return
449 subframe = self.subframe
450 frame = self.frame
451 for c in subframe.children.values():
452 c.destroy()
453 self.dict = None
454 if not dict:
455 l = Label(subframe, text="None")
456 l.grid(row=0, column=0)
457 else:
458 names = dict.keys()
459 names.sort()
460 row = 0
461 for name in names:
462 value = dict[name]
463 svalue = self.repr.repr(value) # repr(value)
464 # Strip extra quotes caused by calling repr on the (already)
465 # repr'd value sent across the RPC interface:
466 if rpc_client:
467 svalue = svalue[1:-1]
468 l = Label(subframe, text=name)
469 l.grid(row=row, column=0, sticky="nw")
unknowned813bf2002-07-05 22:05:24 +0000470 l = Entry(subframe, width=0, borderwidth=0)
471 l.insert(0, svalue)
unknowned813bf2002-07-05 22:05:24 +0000472 l.grid(row=row, column=1, sticky="nw")
473 row = row+1
474 self.dict = dict
475 # XXX Could we use a <Configure> callback for the following?
476 subframe.update_idletasks() # Alas!
477 width = subframe.winfo_reqwidth()
478 height = subframe.winfo_reqheight()
479 canvas = self.canvas
480 self.canvas["scrollregion"] = (0, 0, width, height)
481 if height > 300:
482 canvas["height"] = 300
483 frame.pack(expand=1)
484 else:
485 canvas["height"] = height
486 frame.pack(expand=0)
487
488 def close(self):
489 self.frame.destroy()