Implement Restoring Breakpoints in Subprocess Debugger

M Debugger.py
M EditorWindow.py
M PyShell.py

0. Polish PyShell.linecache_checkcache()
1. Move break clearing code to PyShell.PyShellEditorWindow from
   EditorWindow.
2. Add PyShellEditorWindow.breakpoints attribute to __init__, a list of
   line numbers which are breakpoints for that edit window.
3. Remove the code in Debugger which removes all module breakpoints when
   debugger is closed.  Want to be able to reload into debugger when
   restarted.
4. Moved the code which sets EditorWindow.text breakpoints from Debugger
   to PyShell.PyShellEditorWindow and refactored.
5. Implement reloading subprocess debugger with breakpoints from all open
   PyShellEditorWindows when debugger is opened or subprocess restarted.
6. Eliminate the break_set attribute, use the breakpoint list instead.
This commit is contained in:
Kurt B. Kaiser 2002-10-23 04:48:08 +00:00
parent 88f015dc88
commit 45186c4ce0
3 changed files with 109 additions and 73 deletions

View File

@ -77,11 +77,6 @@ class Debugger:
return return
if self.stackviewer: if self.stackviewer:
self.stackviewer.close(); self.stackviewer = None self.stackviewer.close(); self.stackviewer = None
# Remove all EditWindow BREAK tags when closing debugger:
edit_windows = self.pyshell.flist.inversedict.keys()
for window in edit_windows:
window.text.tag_remove("BREAK", 1.0, END)
window.break_set = False
# Clean up pyshell if user clicked debugger control close widget. # Clean up pyshell if user clicked debugger control close widget.
# (Causes a harmless extra cycle through close_debugger() if user # (Causes a harmless extra cycle through close_debugger() if user
# toggled debugger from pyshell Debug menu) # toggled debugger from pyshell Debug menu)
@ -311,48 +306,34 @@ class Debugger:
if gv: if gv:
gv.load_dict(gdict, force, self.pyshell.interp.rpcclt) gv.load_dict(gdict, force, self.pyshell.interp.rpcclt)
def set_breakpoint_here(self, edit): def set_breakpoint_here(self, filename, lineno):
text = edit.text
filename = edit.io.filename
if not filename:
text.bell()
return
lineno = int(float(text.index("insert")))
msg = self.idb.set_break(filename, lineno) msg = self.idb.set_break(filename, lineno)
if msg: if msg:
text.bell() text.bell()
return return
text.tag_add("BREAK", "insert linestart", "insert lineend +1char")
edit.break_set = True
def clear_breakpoint_here(self, edit): def clear_breakpoint_here(self, filename, lineno):
text = edit.text
filename = edit.io.filename
if not filename:
text.bell()
return
lineno = int(float(text.index("insert")))
msg = self.idb.clear_break(filename, lineno) msg = self.idb.clear_break(filename, lineno)
if msg: if msg:
text.bell() text.bell()
return return
text.tag_remove("BREAK", "insert linestart",\
"insert lineend +1char")
# Don't bother to track break_set status
def clear_file_breaks(self, edit): def clear_file_breaks(self, filename):
text = edit.text
filename = edit.io.filename
if not filename:
text.bell()
return
msg = self.idb.clear_all_file_breaks(filename) msg = self.idb.clear_all_file_breaks(filename)
if msg: if msg:
text.bell() text.bell()
return return
text.tag_remove("BREAK", "1.0", END)
edit.break_set = False
def load_breakpoints(self):
"Load PyShellEditorWindow breakpoints into subprocess debugger"
pyshell_edit_windows = self.pyshell.flist.inversedict.keys()
for editwin in pyshell_edit_windows:
filename = editwin.io.filename
try:
for lineno in editwin.breakpoints:
self.set_breakpoint_here(filename, lineno)
except AttributeError:
continue
class StackViewer(ScrolledList): class StackViewer(ScrolledList):

View File

@ -58,7 +58,6 @@ class EditorWindow:
self.top.instanceDict=flist.inversedict self.top.instanceDict=flist.inversedict
self.recentFilesPath=os.path.join(idleConf.GetUserCfgDir(), self.recentFilesPath=os.path.join(idleConf.GetUserCfgDir(),
'recent-files.lst') 'recent-files.lst')
self.break_set = False
self.vbar = vbar = Scrollbar(top, name='vbar') self.vbar = vbar = Scrollbar(top, name='vbar')
self.text_frame = text_frame = Frame(top) self.text_frame = text_frame = Frame(top)
self.text = text = Text(text_frame, name='text', padx=5, wrap='none', self.text = text = Text(text_frame, name='text', padx=5, wrap='none',
@ -625,9 +624,6 @@ class EditorWindow:
if not self.get_saved(): if not self.get_saved():
title = "*%s*" % title title = "*%s*" % title
icon = "*%s" % icon icon = "*%s" % icon
if self.break_set:
shell = self.flist.pyshell
shell.interp.debugger.clear_file_breaks(self)
self.top.wm_title(title) self.top.wm_title(title)
self.top.wm_iconname(icon) self.top.wm_iconname(icon)
@ -696,9 +692,6 @@ class EditorWindow:
#print self.io.filename #print self.io.filename
if self.io.filename: if self.io.filename:
self.UpdateRecentFilesList(newFile=self.io.filename) self.UpdateRecentFilesList(newFile=self.io.filename)
if self.break_set:
shell = self.flist.pyshell
shell.interp.debugger.clear_file_breaks(self)
WindowList.unregister_callback(self.postwindowsmenu) WindowList.unregister_callback(self.postwindowsmenu)
if self.close_hook: if self.close_hook:
self.close_hook() self.close_hook()

View File

@ -44,11 +44,15 @@ else:
file.write(warnings.formatwarning(message, category, filename, lineno)) file.write(warnings.formatwarning(message, category, filename, lineno))
warnings.showwarning = idle_showwarning warnings.showwarning = idle_showwarning
# We need to patch linecache.checkcache, because we don't want it def linecache_checkcache():
# to throw away our <pyshell#...> entries. """Extend linecache.checkcache to preserve the <pyshell#...> entries
# Rather than repeating its code here, we save those entries,
# then call the original function, and then restore the saved entries. Rather than repeating the linecache code, patch it by saving the pyshell#
def linecache_checkcache(orig_checkcache=linecache.checkcache): entries, call linecache.checkcache(), and then restore the saved
entries.
"""
orig_checkcache=linecache.checkcache
cache = linecache.cache cache = linecache.cache
save = {} save = {}
for filename in cache.keys(): for filename in cache.keys():
@ -56,36 +60,91 @@ def linecache_checkcache(orig_checkcache=linecache.checkcache):
save[filename] = cache[filename] save[filename] = cache[filename]
orig_checkcache() orig_checkcache()
cache.update(save) cache.update(save)
linecache.checkcache = linecache_checkcache linecache.checkcache = linecache_checkcache
class PyShellEditorWindow(EditorWindow): class PyShellEditorWindow(EditorWindow):
"Regular text edit window when a shell is present" "Regular text edit window when a shell is present"
# XXX ought to merge with regular editor window
# XXX KBK 19Oct02 Breakpoints are currently removed if module is
# changed or closed. Future plans include saving breakpoints in a
# project file and possibly preserving breakpoints by changing their
# line numbers as a module is modified.
def __init__(self, *args): def __init__(self, *args):
self.breakpoints = []
apply(EditorWindow.__init__, (self,) + args) apply(EditorWindow.__init__, (self,) + args)
self.text.bind("<<set-breakpoint-here>>", self.set_breakpoint_here) self.text.bind("<<set-breakpoint-here>>", self.set_breakpoint_here)
self.text.bind("<<clear-breakpoint-here>>", self.text.bind("<<clear-breakpoint-here>>", self.clear_breakpoint_here)
self.clear_breakpoint_here)
self.text.bind("<<open-python-shell>>", self.flist.open_shell) self.text.bind("<<open-python-shell>>", self.flist.open_shell)
rmenu_specs = [ rmenu_specs = [("Set Breakpoint", "<<set-breakpoint-here>>"),
("Set Breakpoint", "<<set-breakpoint-here>>"), ("Clear Breakpoint", "<<clear-breakpoint-here>>")]
("Clear Breakpoint", "<<clear-breakpoint-here>>")
]
def set_breakpoint_here(self, event=None): def set_breakpoint_here(self, event=None):
if not self.flist.pyshell or not self.flist.pyshell.interp.debugger: text = self.text
self.text.bell() filename = self.io.filename
if not filename:
text.bell()
return return
self.flist.pyshell.interp.debugger.set_breakpoint_here(self) lineno = int(float(text.index("insert")))
try:
i = self.breakpoints.index(lineno)
except: # only add if missing, i.e. do once
self.breakpoints.append(lineno)
text.tag_add("BREAK", "insert linestart", "insert lineend +1char")
try: # update the subprocess debugger
debug = self.flist.pyshell.interp.debugger
debug.set_breakpoint_here(filename, lineno)
except: # but debugger may not be active right now....
pass
def clear_breakpoint_here(self, event=None): def clear_breakpoint_here(self, event=None):
if not self.flist.pyshell or not self.flist.pyshell.interp.debugger: text = self.text
self.text.bell() filename = self.io.filename
if not filename:
text.bell()
return return
self.flist.pyshell.interp.debugger.clear_breakpoint_here(self) lineno = int(float(text.index("insert")))
try:
self.breakpoints.remove(lineno)
except:
pass
text.tag_remove("BREAK", "insert linestart",\
"insert lineend +1char")
try:
debug = self.flist.pyshell.interp.debugger
debug.clear_breakpoint_here(filename, lineno)
except:
pass
def clear_file_breaks(self):
if self.breakpoints:
text = self.text
filename = self.io.filename
if not filename:
text.bell()
return
self.breakpoints = []
text.tag_remove("BREAK", "1.0", END)
try:
debug = self.flist.pyshell.interp.debugger
debug.clear_file_breaks(filename)
except:
pass
def saved_change_hook(self):
"Extend base method - clear breaks if module is modified"
if not self.get_saved():
self.clear_file_breaks()
EditorWindow.saved_change_hook(self)
def _close(self):
"Extend base method - clear breaks when module is closed"
self.clear_file_breaks()
EditorWindow._close(self)
class PyShellFileList(FileList): class PyShellFileList(FileList):
"Extend base class: file list when a shell is present" "Extend base class: file list when a shell is present"
@ -174,7 +233,8 @@ class ModifiedInterpreter(InteractiveInterpreter):
# Instead, find the executable by looking relative to # Instead, find the executable by looking relative to
# sys.prefix. # sys.prefix.
executable = os.path.join(sys.prefix, 'Resources', executable = os.path.join(sys.prefix, 'Resources',
'Python.app', 'Contents', 'MacOS', 'python') 'Python.app', 'Contents',
'MacOS', 'python')
return executable return executable
else: else:
return sys.executable return sys.executable
@ -207,19 +267,19 @@ class ModifiedInterpreter(InteractiveInterpreter):
def restart_subprocess(self): def restart_subprocess(self):
# close only the subprocess debugger # close only the subprocess debugger
db = self.getdebugger() debug = self.getdebugger()
if db: if debug:
RemoteDebugger.close_subprocess_debugger(self.rpcclt) RemoteDebugger.close_subprocess_debugger(self.rpcclt)
# kill subprocess, spawn a new one, accept connection # kill subprocess, spawn a new one, accept connection
self.rpcclt.close() self.rpcclt.close()
self.spawn_subprocess() self.spawn_subprocess()
self.rpcclt.accept() self.rpcclt.accept()
# restart remote debugger # restart remote debugger
if db: if debug:
gui = RemoteDebugger.restart_subprocess_debugger(self.rpcclt) gui = RemoteDebugger.restart_subprocess_debugger(self.rpcclt)
# reload remote debugger breakpoints # reload remote debugger breakpoints for all PyShellEditWindows
pass # XXX KBK 04Sep02 TBD debug.load_breakpoints()
active_seq = None active_seq = None
def poll_subprocess(self): def poll_subprocess(self):
@ -265,6 +325,14 @@ class ModifiedInterpreter(InteractiveInterpreter):
if clt is not None: if clt is not None:
clt.close() clt.close()
debugger = None
def setdebugger(self, debugger):
self.debugger = debugger
def getdebugger(self):
return self.debugger
def remote_stack_viewer(self): def remote_stack_viewer(self):
import RemoteObjectBrowser import RemoteObjectBrowser
oid = self.rpcclt.remotecall("exec", "stackviewer", ("flist",), {}) oid = self.rpcclt.remotecall("exec", "stackviewer", ("flist",), {})
@ -382,14 +450,6 @@ class ModifiedInterpreter(InteractiveInterpreter):
if key[:1] + key[-1:] != "<>": if key[:1] + key[-1:] != "<>":
del c[key] del c[key]
debugger = None
def setdebugger(self, debugger):
self.debugger = debugger
def getdebugger(self):
return self.debugger
def display_executing_dialog(self): def display_executing_dialog(self):
tkMessageBox.showerror( tkMessageBox.showerror(
"Already executing", "Already executing",
@ -567,6 +627,8 @@ class PyShell(OutputWindow):
def open_remote_debugger(self): def open_remote_debugger(self):
gui = RemoteDebugger.start_remote_debugger(self.interp.rpcclt, self) gui = RemoteDebugger.start_remote_debugger(self.interp.rpcclt, self)
self.interp.setdebugger(gui) self.interp.setdebugger(gui)
# Load all PyShellEditorWindow breakpoints:
gui.load_breakpoints()
sys.ps1 = "[DEBUG ON]\n>>> " sys.ps1 = "[DEBUG ON]\n>>> "
self.showprompt() self.showprompt()
self.set_debugger_indicator() self.set_debugger_indicator()