From 719632a732ae4a03b8c875bf1c1f6036343f6380 Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Mon, 22 May 2017 16:09:32 +0800 Subject: [PATCH] WIP --- Lib/idlelib/debugger.py | 637 +++++++++++++++++++++++------------------------- Lib/idlelib/pyshell.py | 10 + 2 files changed, 315 insertions(+), 332 deletions(-) diff --git a/Lib/idlelib/debugger.py b/Lib/idlelib/debugger.py index 114d0d128e..1a977db697 100644 --- a/Lib/idlelib/debugger.py +++ b/Lib/idlelib/debugger.py @@ -1,14 +1,22 @@ import bdb import os +import linecache from tkinter import * +from tkinter import ttk +from tkinter.font import Font from tkinter.ttk import Scrollbar from idlelib import macosx +from idlelib import ui from idlelib.scrolledlist import ScrolledList from idlelib.windows import ListedToplevel +def underscore_at_end(s): + return s.replace('_', '~') + + class Idb(bdb.Bdb): def __init__(self, gui): @@ -62,12 +70,15 @@ class Debugger: def __init__(self, pyshell, idb=None): if idb is None: idb = Idb(self) + self._ttk = ui.using_ttk self.pyshell = pyshell self.idb = idb self.frame = None self.make_gui() self.interacting = 0 self.nesting_level = 0 + self.framevars = {} + self.running = False def run(self, *args): # Deal with the scenario where we've already got a program running @@ -109,6 +120,15 @@ class Debugger: finally: self.interacting = 0 + def beginexecuting(self): + self.running = True + + def endexecuting(self): + self.running = False + self.show_status('') + self.enable_buttons(['prefs']) + self.clear_stack() + def close(self, event=None): try: self.quit() @@ -117,8 +137,7 @@ class Debugger: if self.interacting: self.top.bell() return - if self.stackviewer: - self.stackviewer.close(); self.stackviewer = None + self.abort_loop() # 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) @@ -130,80 +149,206 @@ class Debugger: pyshell = self.pyshell self.flist = pyshell.flist self.root = root = pyshell.root + self.tooltip = None + self.var_values = {} + _ttk = self._ttk 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("", 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() + + self.var_open_source_windows = BooleanVar(top, False) + + self.pane = ui.PanedWindow(self.top, orient='horizontal') + self.pane.grid(column=0, row=0, sticky='nwes') + self.top.grid_columnconfigure(0, weight=1) + self.top.grid_rowconfigure(0, weight=1) + self.left = left = ui.padframe(ui.Frame(self.pane), 5) + if _ttk: + self.pane.add(left, weight=1) + else: + self.pane.add(left, stretch='always', sticky='nsew') + controls = ui.Frame(left) + col = 0 + f = ('helvetica', 9) + self.buttondata = {} + self.buttons = ['go', 'step', 'over', 'out', 'stop', 'prefs'] + self.button_names = {'go':'Go', 'step':'Step', 'over':'Over', + 'out':'Out', 'stop':'Stop', 'prefs':'Options'} + self.button_cmds = {'go':self.cont, 'step':self.step, + 'over':self.next, 'out':self.ret, + 'stop':self.quit, 'prefs':self.options} + for key in self.buttons: + normal = ui.image('debug_'+key+'.gif') + disabled = ui.image('debug_'+key+'_disabled.gif') + b = ui.Label(controls, image=normal, text=self.button_names[key], + compound='top', font=f) + b.grid(column=col, row=0, padx=[0,5]) + self.buttondata[key] = (b, normal, disabled) + col += 1 + self.enable_buttons(['prefs']) + self.status = ui.Label(controls, text=' ', font=('helvetica', 13)) + self.status.grid(column=6, row=0, sticky='nw', padx=[25,0]) + controls.grid(column=0, row=0, sticky='new', pady=[0,6]) + controls.grid_columnconfigure(7, weight=1) + + self.current_line_img = ui.image('debug_current.gif') + self.regular_line_img = ui.image('debug_line.gif') + if _ttk: + self.stack = ttk.Treeview(left, columns=('statement', ), + height=5, selectmode='browse') + self.stack.column('#0', width=100) + self.stack.column('#1', width=150) + self.stack.tag_configure('error', foreground='red') + else: + self.stack = Listbox(left, height=5, width=35, selectmode='browse', + exportselection=False, activestyle='none') + self.stack.bind('<>' if _ttk else '<>', + lambda e: self.stack_selection_changed()) + self.stack.bind('', lambda e: self.stack_doubleclick()) + self.stack.bind('<>', self.stack_contextmenu) + + scroll = ui.Scrollbar(left, command=self.stack.yview) + self.stack['yscrollcommand'] = scroll.set + self.stack.grid(column=0, row=2, sticky='nwes') + scroll.grid(column=1, row=2, sticky='ns') + left.grid_columnconfigure(0, weight=1) + left.grid_rowconfigure(2, weight=1) + + right = ui.padframe(ui.Frame(self.pane), 5) + if _ttk: + self.pane.add(right, weight=1) + else: + self.pane.add(right, stretch='always', sticky='nsew') + if _ttk: + self.vars = ttk.Treeview(right, columns=('value',), height=5, + selectmode='none') + self.locals = self.vars.insert('', 'end', text='Locals', + open=True) + self.globals = self.vars.insert('', 'end', text='Globals', + open=False) + self.vars.column('#0', width=100) + self.vars.column('#1', width=150) + else: + self.vars = Listbox(right, height=5, width=35, selectmode='none', + exportselection=False, activestyle='none') + self.vars.bind('', self.mouse_moved_vars) + self.vars.bind('', self.leave_vars) + scroll2 = ui.Scrollbar(right, command=self.vars.yview) + self.vars['yscrollcommand'] = scroll2.set + self.vars.grid(column=0, row=0, sticky='nwes') + scroll2.grid(column=1, row=0, sticky='ns') + right.grid_columnconfigure(0, weight=1) + right.grid_rowconfigure(0, weight=1) + left.bind('', lambda e: self._adjust_layout()) + self.clear_stack() + + + def _adjust_layout(self): + # if too narrow, move message below buttons + if self.left.winfo_width() < 380: + self.status.grid(column=0, row=1, columnspan=8, padx=[5,0]) + else: + self.status.grid(column=6, row=0, columnspan=1, padx=[25,0]) + + def enable_buttons(self, buttons=None): + for key in self.buttons: + if buttons is None or not key in buttons: + self.buttondata[key][0]['image'] = self.buttondata[key][2] + self.buttondata[key][0]['foreground'] = '#aaaaaa' + self.buttondata[key][0]['cursor'] = '' + self.buttondata[key][0].bind('<1>', 'break') + self.buttondata[key][0].bind('<>', 'break') + else: + self.buttondata[key][0]['image'] = self.buttondata[key][1] + self.buttondata[key][0]['foreground'] = '#000000' + self.buttondata[key][0].bind('<1>', self.button_cmds[key]) + self.buttondata[key][0].bind('<>', + self.button_cmds[key]) + self.buttondata[key][0]['cursor'] = ui.clickable_cursor + + def stack_selection_changed(self): + self.show_vars() + + def stack_doubleclick(self): + sel = self.stack.selection() if self._ttk else \ + self.stack.curselection() + if len(sel) == 1: + self.show_source(sel[0]) + + def stack_contextmenu(self, event): + if self._ttk: + item = self.stack.identify('item', event.x, event.y) + else: + item = self.stack.nearest(event.y) + if item is not None and item != -1 and item != '': + menu = Menu(self.top, tearoff=0) + menu.add_command(label='View Source', + command = lambda: self.show_source(item)) + menu.tk_popup(event.x_root, event.y_root) + + def show_source(self, item): + if item in self.framevars: + fname = self.framevars[item][2] + lineno = self.framevars[item][3] + if fname[:1] + fname[-1:] != "<>" and os.path.exists(fname): + self.flist.gotofileline(fname, lineno) + + def show_status(self, msg, error=False): + self.status['text'] = msg + self.status['foreground'] = '#ff0000' if error else '#006600' + self.status['font'] = ('helvetica', 13, 'italic') if error \ + else ('helvetica', 13) + + def clear_stack(self): + if self._ttk: + self.stack.delete(*self.stack.get_children('')) + self.vars.delete(*self.vars.get_children(self.locals)) + self.vars.delete(*self.vars.get_children(self.globals)) + self.vars.detach(self.locals) + self.vars.detach(self.globals) + else: + self.stack.delete(0, 'end') + self.vars.delete(0, 'end') + self.var_values = {} + + def add_stackframe(self, frame, lineno, current=False): + func = frame.f_code.co_name + if func in ("?", "", None): + func = '.' + try: + selfval = frame.f_locals['self'] + if selfval.__class__.__name__ == 'str': + # we've probably got the string representation of the + # object sent from the remote debugger, see if we can + # parse it into something useful + match = re.match('^<(?:.*)\.([^\.]*) object at 0x[0-9a-f]+>$', + selfval) + if match: + func = match.group(1) + '.' + func + else: + func = selfval.__class__.__name__ + '.' + func + except Exception: + pass + stmt = linecache.getline(frame.f_code.co_filename, lineno).strip() + if self._ttk: + image=self.current_line_img if current else self.regular_line_img + item = self.stack.insert('', 'end', text=func, + values=(stmt,), image=image) + else: + self.stack.insert('end', func + ' ' + stmt) + item = self.stack.index('end') - 1 + self.framevars[item] = (frame.f_locals, frame.f_globals, + frame.f_code.co_filename, lineno) + if current: + if not self._ttk: + self.stack.selection_clear(0, 'end') + self.stack.selection_set(item) def interaction(self, message, frame, info=None): self.frame = frame - self.status.configure(text=message) + self.show_status(message) # if info: type, value, tb = info @@ -216,26 +361,21 @@ class Debugger: 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") - # + + if m1 != '': + self.show_status(m1, error=True) + stack, idx = self.idb.get_stack(self.frame, tb) + self.clear_stack() + for i in range(len(stack)): + frame, lineno = stack[i] + self.add_stackframe(frame, lineno, current=(i == idx)) + self.show_vars() + self.sync_source_line() + self.enable_buttons(self.buttons) + self.top.wakeup() # Nested main loop: Tkinter's main loop is not reentrant, so use # Tcl's vwait facility, which reenters the event loop until an @@ -243,20 +383,72 @@ class Debugger: self.nesting_level += 1 self.root.tk.call('vwait', '::idledebugwait') self.nesting_level -= 1 - # - for b in self.buttons: - b.configure(state="disabled") - self.status.configure(text="") - self.error.configure(text="", background=self.errorbg) self.frame = None + def show_vars(self): + _ttk = self._ttk + if _ttk: + self.vars.move(self.locals, '', 0) + self.vars.move(self.globals, '', 1) + self.vars.delete(*self.vars.get_children(self.locals)) + self.vars.delete(*self.vars.get_children(self.globals)) + else: + self.vars.delete(0, 'end') + self.var_values = {} + sel = self.stack.selection() if _ttk else self.stack.curselection() + if len(sel) == 1 and sel[0] in self.framevars: + locals, globals, _, _ = self.framevars[sel[0]] + # note: locals/globals may be from a remotedebugger, in + # which case for reasons we don't need to get into here, + # they aren't iterable + self.add_varheader() + for name in sorted(locals.keys(), key=underscore_at_end): + self.add_var(name, locals[name]) + self.add_varheader(isGlobal=True) + for name in sorted(globals.keys(), key=underscore_at_end): + self.add_var(name, globals[name], isGlobal=True) + + def add_varheader(self, isGlobal=False): + if not self._ttk: + self.vars.insert('end', 'Globals:' if isGlobal else 'Locals:') + + def add_var(self, varname, value, isGlobal=False): + if self._ttk: + item = self.vars.insert(self.globals if isGlobal else self.locals, + 'end', text=varname, values=(value, )) + else: + self.vars.insert('end', ' ' + varname + ': ' + str(value)) + item = self.vars.index('end') - 1 + self.var_values[item] = value + + def mouse_moved_vars(self, ev): + ui.tooltip_schedule(ev, self.var_tooltip) + + def leave_vars(self, ev): + ui.tooltip_clear() + + def var_tooltip(self, ev): + # Callback from tooltip package to return text of tooltip + item = None + if self._ttk: + if self.vars.identify('column', ev.x, ev.y) == '#1': + item = self.vars.identify('item', ev.x, ev.y) + else: + item = self.vars.nearest(ev.y) + if item and item in self.var_values: + return(self.var_values[item], ev.x + self.vars.winfo_rootx() + 10, + ev.y + self.vars.winfo_rooty() + 5) + return 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) + if self.var_open_source_windows.get() or\ + self.flist.already_open(filename): + self.flist.gotofileline(filename, lineno) def __frame2fileline(self, frame): code = frame.f_code @@ -264,94 +456,46 @@ class Debugger: lineno = frame.f_lineno return filename, lineno - def cont(self): + def invoke_program(self): + "Called just before taking the next action in debugger, adjust state" + self.enable_buttons(['stop']) + self.show_status('Running...') + + def cont(self, ev=None): + self.invoke_program() self.idb.set_continue() self.abort_loop() - def step(self): + def step(self, ev=None): + self.invoke_program() self.idb.set_step() self.abort_loop() - def next(self): + def next(self, ev=None): + self.invoke_program() self.idb.set_next(self.frame) self.abort_loop() - def ret(self): + def ret(self, ev=None): + self.invoke_program() self.idb.set_return(self.frame) self.abort_loop() - def quit(self): + def quit(self, ev=None): + self.invoke_program() self.idb.set_quit() self.abort_loop() def abort_loop(self): self.root.tk.call('set', '::idledebugwait', '1') - 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, stackitem): - self.frame = stackitem[0] # lineno is stackitem[1] - 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 options(self, ev=None): + menu = Menu(self.top, tearoff=0) + menu.add_checkbutton(label='Show Source in Open Files Only', + variable=self.var_open_source_windows, onvalue=False) + menu.add_checkbutton(label='Automatically Open Files to Show Source', + variable=self.var_open_source_windows, onvalue=True) + menu.tk_popup(ev.x_root, ev.y_root) def set_breakpoint_here(self, filename, lineno): self.idb.set_break(filename, lineno) @@ -371,174 +515,3 @@ class Debugger: self.set_breakpoint_here(filename, lineno) except AttributeError: continue - -class StackViewer(ScrolledList): - - def __init__(self, master, flist, gui): - if macosx.isAquaTk(): - # At least on with the stock AquaTk version on OSX 10.4 you'll - # get a shaking GUI that eventually kills IDLE if the width - # argument is specified. - ScrolledList.__init__(self, master) - else: - 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) - sourceline = sourceline.strip() - 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 reprlib - self.repr = reprlib.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 list(subframe.children.values()): - c.destroy() - self.dict = None - if not dict: - l = Label(subframe, text="None") - l.grid(row=0, column=0) - else: - #names = sorted(dict) - ### - # Because of (temporary) limitations on the dict_keys type (not yet - # public or pickleable), have the subprocess to send a list of - # keys, not a dict_keys object. sorted() will take a dict_keys - # (no subprocess) or a list. - # - # There is also an obscure bug in sorted(dict) where the - # interpreter gets into a loop requesting non-existing dict[0], - # dict[1], dict[2], etc from the debugger_r.DictProxy. - ### - keys_list = dict.keys() - names = sorted(keys_list) - ### - 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 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() diff --git a/Lib/idlelib/pyshell.py b/Lib/idlelib/pyshell.py index 5b0e5b2676..f95f154ca5 100755 --- a/Lib/idlelib/pyshell.py +++ b/Lib/idlelib/pyshell.py @@ -34,6 +34,7 @@ from idlelib.colorizer import ColorDelegator from idlelib.config import idleConf from idlelib import debugger from idlelib import debugger_r +from idlelib import ui from idlelib.editor import EditorWindow, fixwordbreaks from idlelib.filelist import FileList from idlelib.outwin import OutputWindow @@ -463,6 +464,7 @@ class ModifiedInterpreter(InteractiveInterpreter): debugger_r.close_subprocess_debugger(self.rpcclt) except: pass + debug.endexecuting() # Kill subprocess, spawn a new one, accept connection. self.rpcclt.close() self.terminate_subprocess() @@ -782,6 +784,8 @@ class ModifiedInterpreter(InteractiveInterpreter): if self.tkconsole.canceled: self.tkconsole.canceled = False print("KeyboardInterrupt", file=self.tkconsole.stderr) + if self.interp.debugger: + self.interp.debugger.endexecuting() else: self.showtraceback() finally: @@ -852,6 +856,7 @@ class PyShell(OutputWindow): self.interp = ModifiedInterpreter(self) if flist is None: root = Tk() + ui.init(root) fixwordbreaks(root) root.withdraw() flist = PyShellFileList(root) @@ -967,11 +972,15 @@ class PyShell(OutputWindow): def beginexecuting(self): "Helper for ModifiedInterpreter" + if self.interp.debugger: + self.interp.debugger.beginexecuting() self.resetoutput() self.executing = 1 def endexecuting(self): "Helper for ModifiedInterpreter" + if self.interp.debugger: + self.interp.debugger.endexecuting() self.executing = 0 self.canceled = 0 self.showprompt() @@ -1457,6 +1466,7 @@ def main(): NoDefaultRoot() root = Tk(className="Idle") root.withdraw() + ui.init(root, allow_ttk=True) # set application icon icondir = os.path.join(os.path.dirname(__file__), 'Icons') -- 2.12.0