Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
debugger.py603 linesDownload Raw Back to idlelib
1"""Debug user code with a GUI interface to a subclass of bdb.Bdb.2 3The Idb instance 'idb' and Debugger instance 'gui' need references to each4other or to an rpc proxy for each other.5 6If IDLE is started with '-n', so that user code and idb both run in the7IDLE process, Debugger is called without an idb.  Debugger.__init__8calls Idb with its incomplete self.  Idb.__init__ stores gui and gui9then stores idb.10 11If IDLE is started normally, so that user code executes in a separate12process, debugger_r.start_remote_debugger is called, executing in the13IDLE process.  It calls 'start the debugger' in the remote process,14which calls Idb with a gui proxy.  Then Debugger is called in the IDLE15for more.16"""17 18import bdb19import os20 21from tkinter import *22from tkinter.ttk import Frame, Scrollbar23 24from idlelib import macosx25from idlelib.scrolledlist import ScrolledList26from idlelib.window import ListedToplevel27 28 29class Idb(bdb.Bdb):30    "Supply user_line and user_exception functions for Bdb."31 32    def __init__(self, gui):33        self.gui = gui  # An instance of Debugger or proxy thereof.34        super().__init__()35 36    def user_line(self, 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):42            self.set_step()43            return44        message = _frame2message(frame)45        try:46            self.gui.interaction(message, frame)47        except TclError:  # When closing debugger window with [x] in 3.x48            pass49 50    def user_exception(self, frame, exc_info):51        """Handle an the occurrence of an exception."""52        if _in_rpc_code(frame):53            self.set_step()54            return55        message = _frame2message(frame)56        self.gui.interaction(message, frame, exc_info)57 58def _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_back64        if prev_frame is None:65            return False66        prev_name = prev_frame.f_code.co_filename67        if 'idlelib' in prev_name and 'debugger' in prev_name:68            # catch both idlelib/debugger.py and idlelib/debugger_r.py69            # on both Posix and Windows70            return False71        return _in_rpc_code(prev_frame)72 73def _frame2message(frame):74    """Return a message string for frame."""75    code = frame.f_code76    filename = code.co_filename77    lineno = frame.f_lineno78    basename = os.path.basename(filename)79    message = f"{basename}:{lineno}"80    if code.co_name != "?":81        message = f"{message}: {code.co_name}()"82    return message83 84 85class Debugger:86    """The debugger interface.87 88    This class handles the drawing of the debugger window and89    the interactions with the underlying debugger session.90    """91    vstack = None92    vsource = None93    vlocals = None94    vglobals = None95    stackviewer = None96    localsviewer = None97    globalsviewer = None98 99    def __init__(self, pyshell, idb=None):100        """Instantiate and draw a debugger window.101 102        :param pyshell: An instance of the PyShell Window103        :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        """108        if idb is None:109            idb = Idb(self)110        self.pyshell = pyshell111        self.idb = idb  # If passed, a proxy of remote instance.112        self.frame = None113        self.make_gui()114        self.interacting = False115        self.nesting_level = 0116 117    def run(self, *args):118        """Run the debugger."""119        # Deal with the scenario where we've already got a program running120        # in the debugger and we want to start another. If that is the case,121        # our second 'run' was invoked from an event dispatched not from122        # the main event loop, but from the nested event loop in 'interaction'123        # below. So our stack looks something like this:124        #       outer main event loop125        #         run()126        #           <running program with traces>127        #             callback to debugger's interaction()128        #               nested event loop129        #                 run() for second command130        #131        # This kind of nesting of event loops causes all kinds of problems132        # (see e.g. issue #24455) especially when dealing with running as a133        # subprocess, where there's all kinds of extra stuff happening in134        # there - insert a traceback.print_stack() to check it out.135        #136        # By this point, we've already called restart_subprocess() in137        # ScriptBinding. However, we also need to unwind the stack back to138        # that outer event loop.  To accomplish this, we:139        #   - return immediately from the nested run()140        #   - abort_loop ensures the nested event loop will terminate141        #   - the debugger's interaction routine completes normally142        #   - the restart_subprocess() will have taken care of stopping143        #     the running program, which will also let the outer run complete144        #145        # That leaves us back at the outer main event loop, at which point our146        # after event can fire, and we'll come back to this routine with a147        # clean stack.148        if self.nesting_level > 0:149            self.abort_loop()150            self.root.after(100, lambda: self.run(*args))151            return152        try:153            self.interacting = True154            return self.idb.run(*args)155        finally:156            self.interacting = False157 158    def close(self, event=None):159        """Close the debugger and window."""160        try:161            self.quit()162        except Exception:163            pass164        if self.interacting:165            self.top.bell()166            return167        if self.stackviewer:168            self.stackviewer.close(); self.stackviewer = None169        # Clean up pyshell if user clicked debugger control close widget.170        # (Causes a harmless extra cycle through close_debugger() if user171        # toggled debugger from pyshell Debug menu)172        self.pyshell.close_debugger()173        # Now close the debugger control window....174        self.top.destroy()175 176    def make_gui(self):177        """Draw the debugger gui on the screen."""178        pyshell = self.pyshell179        self.flist = pyshell.flist180        self.root = root = pyshell.root181        self.top = top = ListedToplevel(root)182        self.top.wm_title("Debug Control")183        self.top.wm_iconname("Debug")184        top.wm_protocol("WM_DELETE_WINDOW", self.close)185        self.top.bind("<Escape>", self.close)186 187        self.bframe = bframe = Frame(top)188        self.bframe.pack(anchor="w")189        self.buttons = bl = []190 191        self.bcont = b = Button(bframe, text="Go", command=self.cont)192        bl.append(b)193        self.bstep = b = Button(bframe, text="Step", command=self.step)194        bl.append(b)195        self.bnext = b = Button(bframe, text="Over", command=self.next)196        bl.append(b)197        self.bret = b = Button(bframe, text="Out", command=self.ret)198        bl.append(b)199        self.bret = b = Button(bframe, text="Quit", command=self.quit)200        bl.append(b)201 202        for b in bl:203            b.configure(state="disabled")204            b.pack(side="left")205 206        self.cframe = cframe = Frame(bframe)207        self.cframe.pack(side="left")208 209        if not self.vstack:210            self.__class__.vstack = BooleanVar(top)211            self.vstack.set(1)212        self.bstack = Checkbutton(cframe,213            text="Stack", command=self.show_stack, variable=self.vstack)214        self.bstack.grid(row=0, column=0)215        if not self.vsource:216            self.__class__.vsource = BooleanVar(top)217        self.bsource = Checkbutton(cframe,218            text="Source", command=self.show_source, variable=self.vsource)219        self.bsource.grid(row=0, column=1)220        if not self.vlocals:221            self.__class__.vlocals = BooleanVar(top)222            self.vlocals.set(1)223        self.blocals = Checkbutton(cframe,224            text="Locals", command=self.show_locals, variable=self.vlocals)225        self.blocals.grid(row=1, column=0)226        if not self.vglobals:227            self.__class__.vglobals = BooleanVar(top)228        self.bglobals = Checkbutton(cframe,229            text="Globals", command=self.show_globals, variable=self.vglobals)230        self.bglobals.grid(row=1, column=1)231 232        self.status = Label(top, anchor="w")233        self.status.pack(anchor="w")234        self.error = Label(top, anchor="w")235        self.error.pack(anchor="w", fill="x")236        self.errorbg = self.error.cget("background")237 238        self.fstack = Frame(top, height=1)239        self.fstack.pack(expand=1, fill="both")240        self.flocals = Frame(top)241        self.flocals.pack(expand=1, fill="both")242        self.fglobals = Frame(top, height=1)243        self.fglobals.pack(expand=1, fill="both")244 245        if self.vstack.get():246            self.show_stack()247        if self.vlocals.get():248            self.show_locals()249        if self.vglobals.get():250            self.show_globals()251 252    def interaction(self, message, frame, info=None):253        self.frame = frame254        self.status.configure(text=message)255 256        if info:257            type, value, tb = info258            try:259                m1 = type.__name__260            except AttributeError:261                m1 = "%s" % str(type)262            if value is not None:263                try:264                   # TODO redo entire section, tries not needed.265                    m1 = f"{m1}: {value}"266                except:267                    pass268            bg = "yellow"269        else:270            m1 = ""271            tb = None272            bg = self.errorbg273        self.error.configure(text=m1, background=bg)274 275        sv = self.stackviewer276        if sv:277            stack, i = self.idb.get_stack(self.frame, tb)278            sv.load_stack(stack, i)279 280        self.show_variables(1)281 282        if self.vsource.get():283            self.sync_source_line()284 285        for b in self.buttons:286            b.configure(state="normal")287 288        self.top.wakeup()289        # Nested main loop: Tkinter's main loop is not reentrant, so use290        # Tcl's vwait facility, which reenters the event loop until an291        # event handler sets the variable we're waiting on.292        self.nesting_level += 1293        self.root.tk.call('vwait', '::idledebugwait')294        self.nesting_level -= 1295 296        for b in self.buttons:297            b.configure(state="disabled")298        self.status.configure(text="")299        self.error.configure(text="", background=self.errorbg)300        self.frame = None301 302    def sync_source_line(self):303        frame = self.frame304        if not frame:305            return306        filename, lineno = self.__frame2fileline(frame)307        if filename[:1] + filename[-1:] != "<>" and os.path.exists(filename):308            self.flist.gotofileline(filename, lineno)309 310    def __frame2fileline(self, frame):311        code = frame.f_code312        filename = code.co_filename313        lineno = frame.f_lineno314        return filename, lineno315 316    def cont(self):317        self.idb.set_continue()318        self.abort_loop()319 320    def step(self):321        self.idb.set_step()322        self.abort_loop()323 324    def next(self):325        self.idb.set_next(self.frame)326        self.abort_loop()327 328    def ret(self):329        self.idb.set_return(self.frame)330        self.abort_loop()331 332    def quit(self):333        self.idb.set_quit()334        self.abort_loop()335 336    def abort_loop(self):337        self.root.tk.call('set', '::idledebugwait', '1')338 339    def show_stack(self):340        if not self.stackviewer and self.vstack.get():341            self.stackviewer = sv = StackViewer(self.fstack, self.flist, self)342            if self.frame:343                stack, i = self.idb.get_stack(self.frame, None)344                sv.load_stack(stack, i)345        else:346            sv = self.stackviewer347            if sv and not self.vstack.get():348                self.stackviewer = None349                sv.close()350            self.fstack['height'] = 1351 352    def show_source(self):353        if self.vsource.get():354            self.sync_source_line()355 356    def show_frame(self, stackitem):357        self.frame = stackitem[0]  # lineno is stackitem[1]358        self.show_variables()359 360    def show_locals(self):361        lv = self.localsviewer362        if self.vlocals.get():363            if not lv:364                self.localsviewer = NamespaceViewer(self.flocals, "Locals")365        else:366            if lv:367                self.localsviewer = None368                lv.close()369                self.flocals['height'] = 1370        self.show_variables()371 372    def show_globals(self):373        gv = self.globalsviewer374        if self.vglobals.get():375            if not gv:376                self.globalsviewer = NamespaceViewer(self.fglobals, "Globals")377        else:378            if gv:379                self.globalsviewer = None380                gv.close()381                self.fglobals['height'] = 1382        self.show_variables()383 384    def show_variables(self, force=0):385        lv = self.localsviewer386        gv = self.globalsviewer387        frame = self.frame388        if not frame:389            ldict = gdict = None390        else:391            ldict = frame.f_locals392            gdict = frame.f_globals393            if lv and gv and ldict is gdict:394                ldict = None395        if lv:396            lv.load_dict(ldict, force, self.pyshell.interp.rpcclt)397        if gv:398            gv.load_dict(gdict, force, self.pyshell.interp.rpcclt)399 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.setbreakpoint404        """405        self.idb.set_break(filename, lineno)406 407    def clear_breakpoint(self, filename, lineno):408        self.idb.clear_break(filename, lineno)409 410    def clear_file_breaks(self, filename):411        self.idb.clear_all_file_breaks(filename)412 413    def load_breakpoints(self):414        """Load PyShellEditorWindow breakpoints into subprocess debugger."""415        for editwin in self.pyshell.flist.inversedict:416            filename = editwin.io.filename417            try:418                for lineno in editwin.breakpoints:419                    self.set_breakpoint(filename, lineno)420            except AttributeError:421                continue422 423 424class StackViewer(ScrolledList):425    "Code stack viewer for debugger GUI."426 427    def __init__(self, master, flist, gui):428        if macosx.isAquaTk():429            # At least on with the stock AquaTk version on OSX 10.4 you'll430            # get a shaking GUI that eventually kills IDLE if the width431            # argument is specified.432            ScrolledList.__init__(self, master)433        else:434            ScrolledList.__init__(self, master, width=80)435        self.flist = flist436        self.gui = gui437        self.stack = []438 439    def load_stack(self, stack, index=None):440        self.stack = stack441        self.clear()442        for i in range(len(stack)):443            frame, lineno = stack[i]444            try:445                modname = frame.f_globals["__name__"]446            except:447                modname = "?"448            code = frame.f_code449            filename = code.co_filename450            funcname = code.co_name451            import linecache452            sourceline = linecache.getline(filename, lineno)453            sourceline = sourceline.strip()454            if funcname in ("?", "", None):455                item = "%s, line %d: %s" % (modname, lineno, sourceline)456            else:457                item = "%s.%s(), line %d: %s" % (modname, funcname,458                                                 lineno, sourceline)459            if i == index:460                item = "> " + item461            self.append(item)462        if index is not None:463            self.select(index)464 465    def popup_event(self, event):466        "Override base method."467        if self.stack:468            return ScrolledList.popup_event(self, event)469 470    def fill_menu(self):471        "Override base method."472        menu = self.menu473        menu.add_command(label="Go to source line",474                         command=self.goto_source_line)475        menu.add_command(label="Show stack frame",476                         command=self.show_stack_frame)477 478    def on_select(self, index):479        "Override base method."480        if 0 <= index < len(self.stack):481            self.gui.show_frame(self.stack[index])482 483    def on_double(self, index):484        "Override base method."485        self.show_source(index)486 487    def goto_source_line(self):488        index = self.listbox.index("active")489        self.show_source(index)490 491    def show_stack_frame(self):492        index = self.listbox.index("active")493        if 0 <= index < len(self.stack):494            self.gui.show_frame(self.stack[index])495 496    def show_source(self, index):497        if not (0 <= index < len(self.stack)):498            return499        frame, lineno = self.stack[index]500        code = frame.f_code501        filename = code.co_filename502        if os.path.isfile(filename):503            edit = self.flist.open(filename)504            if edit:505                edit.gotoline(lineno)506 507 508class NamespaceViewer:509    "Global/local namespace viewer for debugger GUI."510 511    def __init__(self, master, title, odict=None):  # XXX odict never passed.512        width = 0513        height = 40514        if odict:515            height = 20*len(odict) # XXX 20 == observed height of Entry widget516        self.master = master517        self.title = title518        import reprlib519        self.repr = reprlib.Repr()520        self.repr.maxstring = 60521        self.repr.maxother = 60522        self.frame = frame = Frame(master)523        self.frame.pack(expand=1, fill="both")524        self.label = Label(frame, text=title, borderwidth=2, relief="groove")525        self.label.pack(fill="x")526        self.vbar = vbar = Scrollbar(frame, name="vbar")527        vbar.pack(side="right", fill="y")528        self.canvas = canvas = Canvas(frame,529                                      height=min(300, max(40, height)),530                                      scrollregion=(0, 0, width, height))531        canvas.pack(side="left", fill="both", expand=1)532        vbar["command"] = canvas.yview533        canvas["yscrollcommand"] = vbar.set534        self.subframe = subframe = Frame(canvas)535        self.sfid = canvas.create_window(0, 0, window=subframe, anchor="nw")536        self.load_dict(odict)537 538    prev_odict = -1  # Needed for initial comparison below.539 540    def load_dict(self, odict, force=0, rpc_client=None):541        if odict is self.prev_odict and not force:542            return543        subframe = self.subframe544        frame = self.frame545        for c in list(subframe.children.values()):546            c.destroy()547        self.prev_odict = None548        if not odict:549            l = Label(subframe, text="None")550            l.grid(row=0, column=0)551        else:552            #names = sorted(dict)553            #554            # Because of (temporary) limitations on the dict_keys type (not yet555            # public or pickleable), have the subprocess to send a list of556            # keys, not a dict_keys object.  sorted() will take a dict_keys557            # (no subprocess) or a list.558            #559            # There is also an obscure bug in sorted(dict) where the560            # interpreter gets into a loop requesting non-existing dict[0],561            # dict[1], dict[2], etc from the debugger_r.DictProxy.562            # TODO recheck above; see debugger_r 159ff, debugobj 60.563            keys_list = odict.keys()564            names = sorted(keys_list)565 566            row = 0567            for name in names:568                value = odict[name]569                svalue = self.repr.repr(value) # repr(value)570                # Strip extra quotes caused by calling repr on the (already)571                # repr'd value sent across the RPC interface:572                if rpc_client:573                    svalue = svalue[1:-1]574                l = Label(subframe, text=name)575                l.grid(row=row, column=0, sticky="nw")576                l = Entry(subframe, width=0, borderwidth=0)577                l.insert(0, svalue)578                l.grid(row=row, column=1, sticky="nw")579                row = row+1580        self.prev_odict = odict581        # XXX Could we use a <Configure> callback for the following?582        subframe.update_idletasks() # Alas!583        width = subframe.winfo_reqwidth()584        height = subframe.winfo_reqheight()585        canvas = self.canvas586        self.canvas["scrollregion"] = (0, 0, width, height)587        if height > 300:588            canvas["height"] = 300589            frame.pack(expand=1)590        else:591            canvas["height"] = height592            frame.pack(expand=0)593 594    def close(self):595        self.frame.destroy()596 597 598if __name__ == "__main__":599    from unittest import main600    main('idlelib.idle_test.test_debugger', verbosity=2, exit=False)601 602# TODO: htest?603 
codekingpro/portable-devtools · Team Ai