mirror of
https://github.com/python/cpython.git
synced 2025-07-14 06:45:17 +00:00

M IOBinding.py M PyShell.py * Could not remove last set of saved breakpoints from a file * Starting with empty edit window, could not load a file * Multiple adjacent breakpoints were saved as one breakpoint * Storing breakpoints whenever a file is closed can get them out of synch with the saved version of a file. Only store them when the file is saved. * Add comment on current limitations on file editing in the presence of breakpoints. * Replace get_current_breaks() with update_breakpoints(), add an update to PyShellEditorWindow.breakpoints, which is the master breakpoint data structure, and which is used to reload the subprocess debugger. * Revert Debugger.Debugger.load_breakpoints() to use editwin.breakpoints. It is easier to debug the debugger if the breakpoint list in IDLE is identical to the list in the subprocess debugger and is transferred when the subprocess debugger is restarted, because this list can be easily queried. * Cleanup some linespacing and comments in IOBinding.py
491 lines
16 KiB
Python
491 lines
16 KiB
Python
import os
|
|
import bdb
|
|
import types
|
|
from Tkinter import *
|
|
from WindowList import ListedToplevel
|
|
from ScrolledList import ScrolledList
|
|
|
|
|
|
class Idb(bdb.Bdb):
|
|
|
|
def __init__(self, gui):
|
|
self.gui = gui
|
|
bdb.Bdb.__init__(self)
|
|
|
|
def user_line(self, frame):
|
|
|
|
co_filename = frame.f_code.co_filename
|
|
co_name = frame.f_code.co_name
|
|
|
|
## print>>sys.__stderr__, "*function: ", frame.f_code.co_name
|
|
## print>>sys.__stderr__, "*file: ", frame.f_code.co_filename
|
|
## print>>sys.__stderr__, "*line number: ", frame.f_code.co_firstlineno
|
|
## print>>sys.__stderr__, "*name: ", co_name
|
|
## print>>sys.__stderr__, "*function: ", frame.f_locals.get(co_name,None)
|
|
|
|
try:
|
|
# XXX 12 Dec 2002 CGT TO DO: Find way to get a reference to the
|
|
# XXX currently running function. If the function has an
|
|
# attribute called "DebuggerStepThrough", prevent the debugger
|
|
# from stepping through Idle code. The following doesn't work
|
|
# in instance methods. Hard coded some workarounds.
|
|
|
|
func = frame.f_locals[co_name]
|
|
if getattr(func, "DebuggerStepThrough", 0):
|
|
print "XXXX DEBUGGER STEPPING THROUGH"
|
|
self.set_step()
|
|
return
|
|
except:
|
|
pass
|
|
|
|
# workaround for the problem above
|
|
if co_filename in (r'.\rpc.py', 'rpc.py','<string>'):
|
|
self.set_step()
|
|
return
|
|
if co_filename.endswith('threading.py'):
|
|
self.set_step()
|
|
return
|
|
message = self.__frame2message(frame)
|
|
self.gui.interaction(message, frame)
|
|
|
|
def user_exception(self, frame, info):
|
|
message = self.__frame2message(frame)
|
|
self.gui.interaction(message, frame, info)
|
|
|
|
def __frame2message(self, frame):
|
|
code = frame.f_code
|
|
filename = code.co_filename
|
|
lineno = frame.f_lineno
|
|
basename = os.path.basename(filename)
|
|
message = "%s:%s" % (basename, lineno)
|
|
if code.co_name != "?":
|
|
message = "%s: %s()" % (message, code.co_name)
|
|
return message
|
|
|
|
|
|
class Debugger:
|
|
|
|
vstack = vsource = vlocals = vglobals = None
|
|
|
|
def __init__(self, pyshell, idb=None):
|
|
if idb is None:
|
|
idb = Idb(self)
|
|
self.pyshell = pyshell
|
|
self.idb = idb
|
|
self.frame = None
|
|
self.make_gui()
|
|
self.interacting = 0
|
|
|
|
def run(self, *args):
|
|
try:
|
|
self.interacting = 1
|
|
return self.idb.run(*args)
|
|
finally:
|
|
self.interacting = 0
|
|
|
|
def close(self, event=None):
|
|
if self.interacting:
|
|
self.top.bell()
|
|
return
|
|
if self.stackviewer:
|
|
self.stackviewer.close(); self.stackviewer = None
|
|
# Clean up pyshell if user clicked debugger control close widget.
|
|
# (Causes a harmless extra cycle through close_debugger() if user
|
|
# toggled debugger from pyshell Debug menu)
|
|
self.pyshell.close_debugger()
|
|
# Now close the debugger control window....
|
|
self.top.destroy()
|
|
|
|
def make_gui(self):
|
|
pyshell = self.pyshell
|
|
self.flist = pyshell.flist
|
|
self.root = root = pyshell.root
|
|
self.top = top =ListedToplevel(root)
|
|
self.top.wm_title("Debug Control")
|
|
self.top.wm_iconname("Debug")
|
|
top.wm_protocol("WM_DELETE_WINDOW", self.close)
|
|
self.top.bind("<Escape>", self.close)
|
|
#
|
|
self.bframe = bframe = Frame(top)
|
|
self.bframe.pack(anchor="w")
|
|
self.buttons = bl = []
|
|
#
|
|
self.bcont = b = Button(bframe, text="Go", command=self.cont)
|
|
bl.append(b)
|
|
self.bstep = b = Button(bframe, text="Step", command=self.step)
|
|
bl.append(b)
|
|
self.bnext = b = Button(bframe, text="Over", command=self.next)
|
|
bl.append(b)
|
|
self.bret = b = Button(bframe, text="Out", command=self.ret)
|
|
bl.append(b)
|
|
self.bret = b = Button(bframe, text="Quit", command=self.quit)
|
|
bl.append(b)
|
|
#
|
|
for b in bl:
|
|
b.configure(state="disabled")
|
|
b.pack(side="left")
|
|
#
|
|
self.cframe = cframe = Frame(bframe)
|
|
self.cframe.pack(side="left")
|
|
#
|
|
if not self.vstack:
|
|
self.__class__.vstack = BooleanVar(top)
|
|
self.vstack.set(1)
|
|
self.bstack = Checkbutton(cframe,
|
|
text="Stack", command=self.show_stack, variable=self.vstack)
|
|
self.bstack.grid(row=0, column=0)
|
|
if not self.vsource:
|
|
self.__class__.vsource = BooleanVar(top)
|
|
self.bsource = Checkbutton(cframe,
|
|
text="Source", command=self.show_source, variable=self.vsource)
|
|
self.bsource.grid(row=0, column=1)
|
|
if not self.vlocals:
|
|
self.__class__.vlocals = BooleanVar(top)
|
|
self.vlocals.set(1)
|
|
self.blocals = Checkbutton(cframe,
|
|
text="Locals", command=self.show_locals, variable=self.vlocals)
|
|
self.blocals.grid(row=1, column=0)
|
|
if not self.vglobals:
|
|
self.__class__.vglobals = BooleanVar(top)
|
|
self.bglobals = Checkbutton(cframe,
|
|
text="Globals", command=self.show_globals, variable=self.vglobals)
|
|
self.bglobals.grid(row=1, column=1)
|
|
#
|
|
self.status = Label(top, anchor="w")
|
|
self.status.pack(anchor="w")
|
|
self.error = Label(top, anchor="w")
|
|
self.error.pack(anchor="w", fill="x")
|
|
self.errorbg = self.error.cget("background")
|
|
#
|
|
self.fstack = Frame(top, height=1)
|
|
self.fstack.pack(expand=1, fill="both")
|
|
self.flocals = Frame(top)
|
|
self.flocals.pack(expand=1, fill="both")
|
|
self.fglobals = Frame(top, height=1)
|
|
self.fglobals.pack(expand=1, fill="both")
|
|
#
|
|
if self.vstack.get():
|
|
self.show_stack()
|
|
if self.vlocals.get():
|
|
self.show_locals()
|
|
if self.vglobals.get():
|
|
self.show_globals()
|
|
|
|
|
|
def interaction(self, message, frame, info=None):
|
|
self.frame = frame
|
|
self.status.configure(text=message)
|
|
#
|
|
if info:
|
|
type, value, tb = info
|
|
try:
|
|
m1 = type.__name__
|
|
except AttributeError:
|
|
m1 = "%s" % str(type)
|
|
if value is not None:
|
|
try:
|
|
m1 = "%s: %s" % (m1, str(value))
|
|
except:
|
|
pass
|
|
bg = "yellow"
|
|
else:
|
|
m1 = ""
|
|
tb = None
|
|
bg = self.errorbg
|
|
self.error.configure(text=m1, background=bg)
|
|
#
|
|
sv = self.stackviewer
|
|
if sv:
|
|
stack, i = self.idb.get_stack(self.frame, tb)
|
|
sv.load_stack(stack, i)
|
|
#
|
|
self.show_variables(1)
|
|
#
|
|
if self.vsource.get():
|
|
self.sync_source_line()
|
|
#
|
|
for b in self.buttons:
|
|
b.configure(state="normal")
|
|
#
|
|
self.top.tkraise()
|
|
self.root.mainloop()
|
|
#
|
|
for b in self.buttons:
|
|
b.configure(state="disabled")
|
|
self.status.configure(text="")
|
|
self.error.configure(text="", background=self.errorbg)
|
|
self.frame = None
|
|
|
|
def sync_source_line(self):
|
|
frame = self.frame
|
|
if not frame:
|
|
return
|
|
filename, lineno = self.__frame2fileline(frame)
|
|
if filename[:1] + filename[-1:] != "<>" and os.path.exists(filename):
|
|
self.flist.gotofileline(filename, lineno)
|
|
|
|
def __frame2fileline(self, frame):
|
|
code = frame.f_code
|
|
filename = code.co_filename
|
|
lineno = frame.f_lineno
|
|
return filename, lineno
|
|
|
|
def cont(self):
|
|
self.idb.set_continue()
|
|
self.root.quit()
|
|
|
|
def step(self):
|
|
self.idb.set_step()
|
|
self.root.quit()
|
|
|
|
def next(self):
|
|
self.idb.set_next(self.frame)
|
|
self.root.quit()
|
|
|
|
def ret(self):
|
|
self.idb.set_return(self.frame)
|
|
self.root.quit()
|
|
|
|
def quit(self):
|
|
self.idb.set_quit()
|
|
self.root.quit()
|
|
|
|
stackviewer = None
|
|
|
|
def show_stack(self):
|
|
if not self.stackviewer and self.vstack.get():
|
|
self.stackviewer = sv = StackViewer(self.fstack, self.flist, self)
|
|
if self.frame:
|
|
stack, i = self.idb.get_stack(self.frame, None)
|
|
sv.load_stack(stack, i)
|
|
else:
|
|
sv = self.stackviewer
|
|
if sv and not self.vstack.get():
|
|
self.stackviewer = None
|
|
sv.close()
|
|
self.fstack['height'] = 1
|
|
|
|
def show_source(self):
|
|
if self.vsource.get():
|
|
self.sync_source_line()
|
|
|
|
def show_frame(self, (frame, lineno)):
|
|
self.frame = frame
|
|
self.show_variables()
|
|
|
|
localsviewer = None
|
|
globalsviewer = None
|
|
|
|
def show_locals(self):
|
|
lv = self.localsviewer
|
|
if self.vlocals.get():
|
|
if not lv:
|
|
self.localsviewer = NamespaceViewer(self.flocals, "Locals")
|
|
else:
|
|
if lv:
|
|
self.localsviewer = None
|
|
lv.close()
|
|
self.flocals['height'] = 1
|
|
self.show_variables()
|
|
|
|
def show_globals(self):
|
|
gv = self.globalsviewer
|
|
if self.vglobals.get():
|
|
if not gv:
|
|
self.globalsviewer = NamespaceViewer(self.fglobals, "Globals")
|
|
else:
|
|
if gv:
|
|
self.globalsviewer = None
|
|
gv.close()
|
|
self.fglobals['height'] = 1
|
|
self.show_variables()
|
|
|
|
def show_variables(self, force=0):
|
|
lv = self.localsviewer
|
|
gv = self.globalsviewer
|
|
frame = self.frame
|
|
if not frame:
|
|
ldict = gdict = None
|
|
else:
|
|
ldict = frame.f_locals
|
|
gdict = frame.f_globals
|
|
if lv and gv and ldict is gdict:
|
|
ldict = None
|
|
if lv:
|
|
lv.load_dict(ldict, force, self.pyshell.interp.rpcclt)
|
|
if gv:
|
|
gv.load_dict(gdict, force, self.pyshell.interp.rpcclt)
|
|
|
|
def set_breakpoint_here(self, filename, lineno):
|
|
self.idb.set_break(filename, lineno)
|
|
|
|
def clear_breakpoint_here(self, filename, lineno):
|
|
self.idb.clear_break(filename, lineno)
|
|
|
|
def clear_file_breaks(self, filename):
|
|
self.idb.clear_all_file_breaks(filename)
|
|
|
|
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):
|
|
|
|
def __init__(self, master, flist, gui):
|
|
ScrolledList.__init__(self, master, width=80)
|
|
self.flist = flist
|
|
self.gui = gui
|
|
self.stack = []
|
|
|
|
def load_stack(self, stack, index=None):
|
|
self.stack = stack
|
|
self.clear()
|
|
for i in range(len(stack)):
|
|
frame, lineno = stack[i]
|
|
try:
|
|
modname = frame.f_globals["__name__"]
|
|
except:
|
|
modname = "?"
|
|
code = frame.f_code
|
|
filename = code.co_filename
|
|
funcname = code.co_name
|
|
import linecache
|
|
sourceline = linecache.getline(filename, lineno)
|
|
import string
|
|
sourceline = string.strip(sourceline)
|
|
if funcname in ("?", "", None):
|
|
item = "%s, line %d: %s" % (modname, lineno, sourceline)
|
|
else:
|
|
item = "%s.%s(), line %d: %s" % (modname, funcname,
|
|
lineno, sourceline)
|
|
if i == index:
|
|
item = "> " + item
|
|
self.append(item)
|
|
if index is not None:
|
|
self.select(index)
|
|
|
|
def popup_event(self, event):
|
|
"override base method"
|
|
if self.stack:
|
|
return ScrolledList.popup_event(self, event)
|
|
|
|
def fill_menu(self):
|
|
"override base method"
|
|
menu = self.menu
|
|
menu.add_command(label="Go to source line",
|
|
command=self.goto_source_line)
|
|
menu.add_command(label="Show stack frame",
|
|
command=self.show_stack_frame)
|
|
|
|
def on_select(self, index):
|
|
"override base method"
|
|
if 0 <= index < len(self.stack):
|
|
self.gui.show_frame(self.stack[index])
|
|
|
|
def on_double(self, index):
|
|
"override base method"
|
|
self.show_source(index)
|
|
|
|
def goto_source_line(self):
|
|
index = self.listbox.index("active")
|
|
self.show_source(index)
|
|
|
|
def show_stack_frame(self):
|
|
index = self.listbox.index("active")
|
|
if 0 <= index < len(self.stack):
|
|
self.gui.show_frame(self.stack[index])
|
|
|
|
def show_source(self, index):
|
|
if not (0 <= index < len(self.stack)):
|
|
return
|
|
frame, lineno = self.stack[index]
|
|
code = frame.f_code
|
|
filename = code.co_filename
|
|
if os.path.isfile(filename):
|
|
edit = self.flist.open(filename)
|
|
if edit:
|
|
edit.gotoline(lineno)
|
|
|
|
|
|
class NamespaceViewer:
|
|
|
|
def __init__(self, master, title, dict=None):
|
|
width = 0
|
|
height = 40
|
|
if dict:
|
|
height = 20*len(dict) # XXX 20 == observed height of Entry widget
|
|
self.master = master
|
|
self.title = title
|
|
import repr
|
|
self.repr = repr.Repr()
|
|
self.repr.maxstring = 60
|
|
self.repr.maxother = 60
|
|
self.frame = frame = Frame(master)
|
|
self.frame.pack(expand=1, fill="both")
|
|
self.label = Label(frame, text=title, borderwidth=2, relief="groove")
|
|
self.label.pack(fill="x")
|
|
self.vbar = vbar = Scrollbar(frame, name="vbar")
|
|
vbar.pack(side="right", fill="y")
|
|
self.canvas = canvas = Canvas(frame,
|
|
height=min(300, max(40, height)),
|
|
scrollregion=(0, 0, width, height))
|
|
canvas.pack(side="left", fill="both", expand=1)
|
|
vbar["command"] = canvas.yview
|
|
canvas["yscrollcommand"] = vbar.set
|
|
self.subframe = subframe = Frame(canvas)
|
|
self.sfid = canvas.create_window(0, 0, window=subframe, anchor="nw")
|
|
self.load_dict(dict)
|
|
|
|
dict = -1
|
|
|
|
def load_dict(self, dict, force=0, rpc_client=None):
|
|
if dict is self.dict and not force:
|
|
return
|
|
subframe = self.subframe
|
|
frame = self.frame
|
|
for c in subframe.children.values():
|
|
c.destroy()
|
|
self.dict = None
|
|
if not dict:
|
|
l = Label(subframe, text="None")
|
|
l.grid(row=0, column=0)
|
|
else:
|
|
names = dict.keys()
|
|
names.sort()
|
|
row = 0
|
|
for name in names:
|
|
value = dict[name]
|
|
svalue = self.repr.repr(value) # repr(value)
|
|
# Strip extra quotes caused by calling repr on the (already)
|
|
# repr'd value sent across the RPC interface:
|
|
if rpc_client:
|
|
svalue = svalue[1:-1]
|
|
l = Label(subframe, text=name)
|
|
l.grid(row=row, column=0, sticky="nw")
|
|
l = Entry(subframe, width=0, borderwidth=0)
|
|
l.insert(0, svalue)
|
|
l.grid(row=row, column=1, sticky="nw")
|
|
row = row+1
|
|
self.dict = dict
|
|
# XXX Could we use a <Configure> callback for the following?
|
|
subframe.update_idletasks() # Alas!
|
|
width = subframe.winfo_reqwidth()
|
|
height = subframe.winfo_reqheight()
|
|
canvas = self.canvas
|
|
self.canvas["scrollregion"] = (0, 0, width, height)
|
|
if height > 300:
|
|
canvas["height"] = 300
|
|
frame.pack(expand=1)
|
|
else:
|
|
canvas["height"] = height
|
|
frame.pack(expand=0)
|
|
|
|
def close(self):
|
|
self.frame.destroy()
|