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