1+ """Debug user code with a GUI interface to a subclass of bdb.Bdb.
2+
3+ The Idb idb and Debugger gui instances each need a reference to each
4+ other or to an rpc proxy for each other.
5+
6+ If IDLE is started with '-n', so that user code and idb both run in the
7+ IDLE process, Debugger is called without an idb. Debugger.__init__
8+ calls Idb with its incomplete self. Idb.__init__ stores gui and gui
9+ then stores idb.
10+
11+ If IDLE is started normally, so that user code executes in a separate
12+ process, debugger_r.start_remote_debugger is called, executing in the
13+ IDLE process. It calls 'start the debugger' in the remote process,
14+ which calls Idb with a gui proxy. Then Debugger is called in the IDLE
15+ for more.
16+ """
17+
118import bdb
219import os
320
1027
1128
1229class Idb (bdb .Bdb ):
30+ "Supply user_line and user_exception functions for Bdb."
1331
1432 def __init__ (self , gui ):
15- self .gui = gui # An instance of Debugger or proxy of remote .
16- bdb . Bdb . __init__ (self )
33+ self .gui = gui # An instance of Debugger or proxy thereof .
34+ super (). __init__ ()
1735
1836 def user_line (self , frame ):
19- if self .in_rpc_code (frame ):
37+ """Handle a user stopping or breaking at a line.
38+
39+ Convert frame to a string and send it to gui.
40+ """
41+ if _in_rpc_code (frame ):
2042 self .set_step ()
2143 return
22- message = self . __frame2message (frame )
44+ message = _frame2message (frame )
2345 try :
2446 self .gui .interaction (message , frame )
2547 except TclError : # When closing debugger window with [x] in 3.x
2648 pass
2749
28- def user_exception (self , frame , info ):
29- if self .in_rpc_code (frame ):
50+ def user_exception (self , frame , exc_info ):
51+ """Handle an the occurrence of an exception."""
52+ if _in_rpc_code (frame ):
3053 self .set_step ()
3154 return
32- message = self .__frame2message (frame )
33- self .gui .interaction (message , frame , info )
34-
35- def in_rpc_code (self , frame ):
36- if frame .f_code .co_filename .count ('rpc.py' ):
37- return True
38- else :
39- prev_frame = frame .f_back
40- prev_name = prev_frame .f_code .co_filename
41- if 'idlelib' in prev_name and 'debugger' in prev_name :
42- # catch both idlelib/debugger.py and idlelib/debugger_r.py
43- # on both Posix and Windows
44- return False
45- return self .in_rpc_code (prev_frame )
46-
47- def __frame2message (self , frame ):
48- code = frame .f_code
49- filename = code .co_filename
50- lineno = frame .f_lineno
51- basename = os .path .basename (filename )
52- message = f"{ basename } :{ lineno } "
53- if code .co_name != "?" :
54- message = f"{ message } : { code .co_name } ()"
55- return message
55+ message = _frame2message (frame )
56+ self .gui .interaction (message , frame , exc_info )
57+
58+ def _in_rpc_code (frame ):
59+ "Determine if debugger is within RPC code."
60+ if frame .f_code .co_filename .count ('rpc.py' ):
61+ return True # Skip this frame.
62+ else :
63+ prev_frame = frame .f_back
64+ if prev_frame is None :
65+ return False
66+ prev_name = prev_frame .f_code .co_filename
67+ if 'idlelib' in prev_name and 'debugger' in prev_name :
68+ # catch both idlelib/debugger.py and idlelib/debugger_r.py
69+ # on both Posix and Windows
70+ return False
71+ return _in_rpc_code (prev_frame )
72+
73+ def _frame2message (frame ):
74+ """Return a message string for frame."""
75+ code = frame .f_code
76+ filename = code .co_filename
77+ lineno = frame .f_lineno
78+ basename = os .path .basename (filename )
79+ message = f"{ basename } :{ lineno } "
80+ if code .co_name != "?" :
81+ message = f"{ message } : { code .co_name } ()"
82+ return message
5683
5784
5885class Debugger :
59-
60- vstack = vsource = vlocals = vglobals = None
86+ """The debugger interface.
87+
88+ This class handles the drawing of the debugger window and
89+ the interactions with the underlying debugger session.
90+ """
91+ vstack = None
92+ vsource = None
93+ vlocals = None
94+ vglobals = None
95+ stackviewer = None
96+ localsviewer = None
97+ globalsviewer = None
6198
6299 def __init__ (self , pyshell , idb = None ):
100+ """Instantiate and draw a debugger window.
101+
102+ :param pyshell: An instance of the PyShell Window
103+ :type pyshell: :class:`idlelib.pyshell.PyShell`
104+
105+ :param idb: An instance of the IDLE debugger (optional)
106+ :type idb: :class:`idlelib.debugger.Idb`
107+ """
63108 if idb is None :
64109 idb = Idb (self )
65110 self .pyshell = pyshell
66111 self .idb = idb # If passed, a proxy of remote instance.
67112 self .frame = None
68113 self .make_gui ()
69- self .interacting = 0
114+ self .interacting = False
70115 self .nesting_level = 0
71116
72117 def run (self , * args ):
118+ """Run the debugger."""
73119 # Deal with the scenario where we've already got a program running
74120 # in the debugger and we want to start another. If that is the case,
75121 # our second 'run' was invoked from an event dispatched not from
@@ -104,12 +150,13 @@ def run(self, *args):
104150 self .root .after (100 , lambda : self .run (* args ))
105151 return
106152 try :
107- self .interacting = 1
153+ self .interacting = True
108154 return self .idb .run (* args )
109155 finally :
110- self .interacting = 0
156+ self .interacting = False
111157
112158 def close (self , event = None ):
159+ """Close the debugger and window."""
113160 try :
114161 self .quit ()
115162 except Exception :
@@ -127,6 +174,7 @@ def close(self, event=None):
127174 self .top .destroy ()
128175
129176 def make_gui (self ):
177+ """Draw the debugger gui on the screen."""
130178 pyshell = self .pyshell
131179 self .flist = pyshell .flist
132180 self .root = root = pyshell .root
@@ -135,11 +183,11 @@ def make_gui(self):
135183 self .top .wm_iconname ("Debug" )
136184 top .wm_protocol ("WM_DELETE_WINDOW" , self .close )
137185 self .top .bind ("<Escape>" , self .close )
138- #
186+
139187 self .bframe = bframe = Frame (top )
140188 self .bframe .pack (anchor = "w" )
141189 self .buttons = bl = []
142- #
190+
143191 self .bcont = b = Button (bframe , text = "Go" , command = self .cont )
144192 bl .append (b )
145193 self .bstep = b = Button (bframe , text = "Step" , command = self .step )
@@ -150,14 +198,14 @@ def make_gui(self):
150198 bl .append (b )
151199 self .bret = b = Button (bframe , text = "Quit" , command = self .quit )
152200 bl .append (b )
153- #
201+
154202 for b in bl :
155203 b .configure (state = "disabled" )
156204 b .pack (side = "left" )
157- #
205+
158206 self .cframe = cframe = Frame (bframe )
159207 self .cframe .pack (side = "left" )
160- #
208+
161209 if not self .vstack :
162210 self .__class__ .vstack = BooleanVar (top )
163211 self .vstack .set (1 )
@@ -180,20 +228,20 @@ def make_gui(self):
180228 self .bglobals = Checkbutton (cframe ,
181229 text = "Globals" , command = self .show_globals , variable = self .vglobals )
182230 self .bglobals .grid (row = 1 , column = 1 )
183- #
231+
184232 self .status = Label (top , anchor = "w" )
185233 self .status .pack (anchor = "w" )
186234 self .error = Label (top , anchor = "w" )
187235 self .error .pack (anchor = "w" , fill = "x" )
188236 self .errorbg = self .error .cget ("background" )
189- #
237+
190238 self .fstack = Frame (top , height = 1 )
191239 self .fstack .pack (expand = 1 , fill = "both" )
192240 self .flocals = Frame (top )
193241 self .flocals .pack (expand = 1 , fill = "both" )
194242 self .fglobals = Frame (top , height = 1 )
195243 self .fglobals .pack (expand = 1 , fill = "both" )
196- #
244+
197245 if self .vstack .get ():
198246 self .show_stack ()
199247 if self .vlocals .get ():
@@ -204,7 +252,7 @@ def make_gui(self):
204252 def interaction (self , message , frame , info = None ):
205253 self .frame = frame
206254 self .status .configure (text = message )
207- #
255+
208256 if info :
209257 type , value , tb = info
210258 try :
@@ -223,28 +271,28 @@ def interaction(self, message, frame, info=None):
223271 tb = None
224272 bg = self .errorbg
225273 self .error .configure (text = m1 , background = bg )
226- #
274+
227275 sv = self .stackviewer
228276 if sv :
229277 stack , i = self .idb .get_stack (self .frame , tb )
230278 sv .load_stack (stack , i )
231- #
279+
232280 self .show_variables (1 )
233- #
281+
234282 if self .vsource .get ():
235283 self .sync_source_line ()
236- #
284+
237285 for b in self .buttons :
238286 b .configure (state = "normal" )
239- #
287+
240288 self .top .wakeup ()
241289 # Nested main loop: Tkinter's main loop is not reentrant, so use
242290 # Tcl's vwait facility, which reenters the event loop until an
243- # event handler sets the variable we're waiting on
291+ # event handler sets the variable we're waiting on.
244292 self .nesting_level += 1
245293 self .root .tk .call ('vwait' , '::idledebugwait' )
246294 self .nesting_level -= 1
247- #
295+
248296 for b in self .buttons :
249297 b .configure (state = "disabled" )
250298 self .status .configure (text = "" )
@@ -288,8 +336,6 @@ def quit(self):
288336 def abort_loop (self ):
289337 self .root .tk .call ('set' , '::idledebugwait' , '1' )
290338
291- stackviewer = None
292-
293339 def show_stack (self ):
294340 if not self .stackviewer and self .vstack .get ():
295341 self .stackviewer = sv = StackViewer (self .fstack , self .flist , self )
@@ -311,9 +357,6 @@ def show_frame(self, stackitem):
311357 self .frame = stackitem [0 ] # lineno is stackitem[1]
312358 self .show_variables ()
313359
314- localsviewer = None
315- globalsviewer = None
316-
317360 def show_locals (self ):
318361 lv = self .localsviewer
319362 if self .vlocals .get ():
@@ -354,26 +397,32 @@ def show_variables(self, force=0):
354397 if gv :
355398 gv .load_dict (gdict , force , self .pyshell .interp .rpcclt )
356399
357- def set_breakpoint_here (self , filename , lineno ):
400+ def set_breakpoint (self , filename , lineno ):
401+ """Set a filename-lineno breakpoint in the debugger.
402+
403+ Called from self.load_breakpoints and EW.setbreakpoint
404+ """
358405 self .idb .set_break (filename , lineno )
359406
360- def clear_breakpoint_here (self , filename , lineno ):
407+ def clear_breakpoint (self , filename , lineno ):
361408 self .idb .clear_break (filename , lineno )
362409
363410 def clear_file_breaks (self , filename ):
364411 self .idb .clear_all_file_breaks (filename )
365412
366413 def load_breakpoints (self ):
367- "Load PyShellEditorWindow breakpoints into subprocess debugger"
414+ """ Load PyShellEditorWindow breakpoints into subprocess debugger."" "
368415 for editwin in self .pyshell .flist .inversedict :
369416 filename = editwin .io .filename
370417 try :
371418 for lineno in editwin .breakpoints :
372- self .set_breakpoint_here (filename , lineno )
419+ self .set_breakpoint (filename , lineno )
373420 except AttributeError :
374421 continue
375422
423+
376424class StackViewer (ScrolledList ):
425+ "Code stack viewer for debugger GUI."
377426
378427 def __init__ (self , master , flist , gui ):
379428 if macosx .isAquaTk ():
@@ -414,25 +463,25 @@ def load_stack(self, stack, index=None):
414463 self .select (index )
415464
416465 def popup_event (self , event ):
417- "override base method"
466+ "Override base method. "
418467 if self .stack :
419468 return ScrolledList .popup_event (self , event )
420469
421470 def fill_menu (self ):
422- "override base method"
471+ "Override base method. "
423472 menu = self .menu
424473 menu .add_command (label = "Go to source line" ,
425474 command = self .goto_source_line )
426475 menu .add_command (label = "Show stack frame" ,
427476 command = self .show_stack_frame )
428477
429478 def on_select (self , index ):
430- "override base method"
479+ "Override base method. "
431480 if 0 <= index < len (self .stack ):
432481 self .gui .show_frame (self .stack [index ])
433482
434483 def on_double (self , index ):
435- "override base method"
484+ "Override base method. "
436485 self .show_source (index )
437486
438487 def goto_source_line (self ):
@@ -457,6 +506,7 @@ def show_source(self, index):
457506
458507
459508class NamespaceViewer :
509+ "Global/local namespace viewer for debugger GUI."
460510
461511 def __init__ (self , master , title , dict = None ):
462512 width = 0
@@ -544,6 +594,7 @@ def load_dict(self, dict, force=0, rpc_client=None):
544594 def close (self ):
545595 self .frame .destroy ()
546596
597+
547598if __name__ == "__main__" :
548599 from unittest import main
549600 main ('idlelib.idle_test.test_debugger' , verbosity = 2 , exit = False )
0 commit comments