Debugger.py 18 KB

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