test_whitespace_eater_unicode(): Make this test Python 2.1 compatible.
[python/dscho.git] / Lib / idlelib / Debugger.py
blob5f851821768fbc59690cfb5593a7cb7648d0fb25
1 import os
2 import bdb
3 import types
4 from Tkinter import *
5 from WindowList import ListedToplevel
6 from ScrolledList import ScrolledList
9 class Idb(bdb.Bdb):
11 def __init__(self, gui):
12 self.gui = gui
13 bdb.Bdb.__init__(self)
15 def user_line(self, frame):
17 co_filename = frame.f_code.co_filename
18 ## co_name = frame.f_code.co_name
20 ## print>>sys.__stderr__, "*function: ", frame.f_code.co_name
21 ## print>>sys.__stderr__, "*file: ", frame.f_code.co_filename
22 ## 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)
26 ## 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
40 # workaround for the problem above
41 exclude = ('rpc.py', 'threading.py', '<string>')
42 for rpcfile in exclude:
43 if co_filename.count(rpcfile):
44 self.set_step()
45 return
46 message = self.__frame2message(frame)
47 self.gui.interaction(message, frame)
49 def user_exception(self, frame, info):
50 message = self.__frame2message(frame)
51 self.gui.interaction(message, frame, info)
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
64 class Debugger:
66 vstack = vsource = vlocals = vglobals = None
68 def __init__(self, pyshell, idb=None):
69 if idb is None:
70 idb = Idb(self)
71 self.pyshell = pyshell
72 self.idb = idb
73 self.frame = None
74 self.make_gui()
75 self.interacting = 0
77 def run(self, *args):
78 try:
79 self.interacting = 1
80 return self.idb.run(*args)
81 finally:
82 self.interacting = 0
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
90 # 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)
93 self.pyshell.close_debugger()
94 # Now close the debugger control window....
95 self.top.destroy()
97 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)
107 self.bframe = bframe = Frame(top)
108 self.bframe.pack(anchor="w")
109 self.buttons = bl = []
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)
122 for b in bl:
123 b.configure(state="disabled")
124 b.pack(side="left")
126 self.cframe = cframe = Frame(bframe)
127 self.cframe.pack(side="left")
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)
137 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)
148 self.bglobals = Checkbutton(cframe,
149 text="Globals", command=self.show_globals, variable=self.vglobals)
150 self.bglobals.grid(row=1, column=1)
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")
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")
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()
173 def interaction(self, message, frame, info=None):
174 self.frame = frame
175 self.status.configure(text=message)
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)
195 sv = self.stackviewer
196 if sv:
197 stack, i = self.idb.get_stack(self.frame, tb)
198 sv.load_stack(stack, i)
200 self.show_variables(1)
202 if self.vsource.get():
203 self.sync_source_line()
205 for b in self.buttons:
206 b.configure(state="normal")
208 self.top.tkraise()
209 self.root.mainloop()
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
217 def sync_source_line(self):
218 frame = self.frame
219 if not frame:
220 return
221 filename, lineno = self.__frame2fileline(frame)
222 if filename[:1] + filename[-1:] != "<>" and os.path.exists(filename):
223 self.flist.gotofileline(filename, lineno)
225 def __frame2fileline(self, frame):
226 code = frame.f_code
227 filename = code.co_filename
228 lineno = frame.f_lineno
229 return filename, lineno
231 def cont(self):
232 self.idb.set_continue()
233 self.root.quit()
235 def step(self):
236 self.idb.set_step()
237 self.root.quit()
239 def next(self):
240 self.idb.set_next(self.frame)
241 self.root.quit()
243 def ret(self):
244 self.idb.set_return(self.frame)
245 self.root.quit()
247 def quit(self):
248 self.idb.set_quit()
249 self.root.quit()
251 stackviewer = None
253 def show_stack(self):
254 if not self.stackviewer and self.vstack.get():
255 self.stackviewer = sv = StackViewer(self.fstack, self.flist, self)
256 if self.frame:
257 stack, i = self.idb.get_stack(self.frame, None)
258 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
266 def show_source(self):
267 if self.vsource.get():
268 self.sync_source_line()
270 def show_frame(self, (frame, lineno)):
271 self.frame = frame
272 self.show_variables()
274 localsviewer = None
275 globalsviewer = None
277 def show_locals(self):
278 lv = self.localsviewer
279 if self.vlocals.get():
280 if not lv:
281 self.localsviewer = NamespaceViewer(self.flocals, "Locals")
282 else:
283 if lv:
284 self.localsviewer = None
285 lv.close()
286 self.flocals['height'] = 1
287 self.show_variables()
289 def show_globals(self):
290 gv = self.globalsviewer
291 if self.vglobals.get():
292 if not gv:
293 self.globalsviewer = NamespaceViewer(self.fglobals, "Globals")
294 else:
295 if gv:
296 self.globalsviewer = None
297 gv.close()
298 self.fglobals['height'] = 1
299 self.show_variables()
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:
313 lv.load_dict(ldict, force, self.pyshell.interp.rpcclt)
314 if gv:
315 gv.load_dict(gdict, force, self.pyshell.interp.rpcclt)
317 def set_breakpoint_here(self, filename, lineno):
318 self.idb.set_break(filename, lineno)
320 def clear_breakpoint_here(self, filename, lineno):
321 self.idb.clear_break(filename, lineno)
323 def clear_file_breaks(self, filename):
324 self.idb.clear_all_file_breaks(filename)
326 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:
332 for lineno in editwin.breakpoints:
333 self.set_breakpoint_here(filename, lineno)
334 except AttributeError:
335 continue
337 class StackViewer(ScrolledList):
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 = []
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)
372 def popup_event(self, event):
373 "override base method"
374 if self.stack:
375 return ScrolledList.popup_event(self, event)
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)
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])
390 def on_double(self, index):
391 "override base method"
392 self.show_source(index)
394 def goto_source_line(self):
395 index = self.listbox.index("active")
396 self.show_source(index)
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])
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)
415 class NamespaceViewer:
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)
444 dict = -1
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")
470 l = Entry(subframe, width=0, borderwidth=0)
471 l.insert(0, svalue)
472 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)
488 def close(self):
489 self.frame.destroy()