Debugger: added support for the "Continue Until" debug action.

Sat, 19 Dec 2020 14:41:43 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 19 Dec 2020 14:41:43 +0100
changeset 7897
9acc015ea443
parent 7896
75ce42b1df23
child 7898
1bcd6d4df182

Debugger: added support for the "Continue Until" debug action.

docs/changelog file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/DebugBase.py file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/DebugClientBase.py file | annotate | diff | comparison | revisions
eric6/Debugger/DebugServer.py file | annotate | diff | comparison | revisions
eric6/Debugger/DebugUI.py file | annotate | diff | comparison | revisions
eric6/Debugger/DebuggerInterfaceNone.py file | annotate | diff | comparison | revisions
eric6/Debugger/DebuggerInterfacePython.py file | annotate | diff | comparison | revisions
eric6/icons/breeze-dark/continueUntil.svg file | annotate | diff | comparison | revisions
eric6/icons/breeze-light/continueUntil.svg file | annotate | diff | comparison | revisions
eric6/icons/oxygen/continueUntil.png file | annotate | diff | comparison | revisions
--- a/docs/changelog	Fri Dec 18 17:01:10 2020 +0100
+++ b/docs/changelog	Sat Dec 19 14:41:43 2020 +0100
@@ -3,15 +3,18 @@
 Version 21.1:
 - bug fixes
 - Debugger
-  - added support for debugging multiprocess scripts using these methods
-    -- QProcess.start(), QProcess.startDetached()
-    -- subprocess.Popen()
-    -- multiprocessing.Process()
-    -- os.spawnl...(), os.spawnv...(), os.posix_spawn...(), os.fork(),
+  -- added support for debugging multiprocess scripts using these methods
+     --- QProcess.start(), QProcess.startDetached()
+     --- subprocess.Popen()
+     --- multiprocessing.Process()
+     --- os.spawnl...(), os.spawnv...(), os.posix_spawn...(), os.fork(),
        os.fork_exec()
-    -- _winapi.CreateProcess(), _subprocess.CreateProcess()
-  - added support for debugging code strings (-c, --code) or script modules
-    (-m, --module)
+     --- _winapi.CreateProcess(), _subprocess.CreateProcess()
+  -- added support for debugging code strings (-c, --code) or script modules
+     (-m, --module)
+  -- added support for the "Continue Until" action execution code until the
+     current cursor line (if it is greater than the current line) or until
+     returning from the current frame
 - Editor
   -- added code to enclose the current selection by entering " or ' characters
 
--- a/eric6/DebugClients/Python/DebugBase.py	Fri Dec 18 17:01:10 2020 +0100
+++ b/eric6/DebugClients/Python/DebugBase.py	Sat Dec 19 14:41:43 2020 +0100
@@ -23,6 +23,10 @@
 
 gRecursionLimit = 64
 
+GENERATOR_AND_COROUTINE_FLAGS = (
+    inspect.CO_GENERATOR | inspect.CO_COROUTINE | inspect.CO_ASYNC_GENERATOR
+)
+
 
 def printerr(s):
     """
@@ -309,8 +313,11 @@
         
         if event == 'line':
             if self.stop_here(frame) or self.break_here(frame):
-                if (self.stop_everywhere and frame.f_back and
-                        frame.f_back.f_code.co_name == "prepareJsonCommand"):
+                if (
+                    self.stop_everywhere and
+                    frame.f_back and
+                    frame.f_back.f_code.co_name == "prepareJsonCommand"
+                ):
                     # Just stepped into print statement, so skip these frames
                     self._set_stopinfo(None, frame.f_back)
                 else:
@@ -318,19 +325,33 @@
             return self.trace_dispatch
         
         if event == 'call':
-            if (self.stop_here(frame) or
-                    self.__checkBreakInFrame(frame) or
-                    Watch.watches != []):
+            if (
+                self.stop_here(frame) or
+                self.__checkBreakInFrame(frame) or
+                Watch.watches != []
+            ):
+                return self.trace_dispatch
+            elif (
+                self.stopframe and
+                frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS
+            ):
                 return self.trace_dispatch
             else:
                 # No need to trace this function
                 return None
         
         if event == 'return':
-            if frame == self.returnframe:
-                # Only true if we didn't stopped in this frame, because it's
+            if self.stop_here(frame) or frame == self.returnframe:
+                # Ignore return events in generator except when stepping.
+                if (
+                    self.stopframe and
+                    frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS
+                ):
+                    return self.trace_dispatch
+                # Only true if we didn't stop in this frame, because it's
                 # belonging to the eric debugger.
-                self._set_stopinfo(None, frame.f_back)
+                if self.stopframe is frame and self.stoplineno != -1:
+                    self._set_stopinfo(None, frame.f_back)
             return None
         
         if event == 'exception':
@@ -339,16 +360,23 @@
                 # skip the internal StopIteration exception (with no traceback)
                 # triggered by a subiterator run with the 'yield from'
                 # statement.
-                if not (frame.f_code.co_flags & inspect.CO_GENERATOR and
-                        arg[0] is StopIteration and arg[2] is None):
+                if not (
+                    frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS and
+                    arg[0] is StopIteration and
+                    arg[2] is None
+                ):
                     self.user_exception(arg)
             # Stop at the StopIteration or GeneratorExit exception when the
             # user has set stopframe in a generator by issuing a return
             # command, or a next/until command at the last statement in the
             # generator before the exception.
-            elif (self.stopframe and frame is not self.stopframe and
-                    self.stopframe.f_code.co_flags & inspect.CO_GENERATOR and
-                    arg[0] in (StopIteration, GeneratorExit)):
+            elif (
+                self.stopframe and
+                frame is not self.stopframe and
+                (self.stopframe.f_code.co_flags &
+                 GENERATOR_AND_COROUTINE_FLAGS) and
+                arg[0] in (StopIteration, GeneratorExit)
+            ):
                 self.user_exception(arg)
             return None
 
@@ -362,6 +390,7 @@
         print('DebugBase.trace_dispatch:'       # __IGNORE_WARNING_M801__
               ' unknown debugging event: ',
               repr(event))
+        
         return self.trace_dispatch
 
     def set_trace(self, frame=None):
@@ -383,7 +412,7 @@
         frame.f_trace = self.trace_dispatch
         while frame.f_back is not None:
             # stop at eric's debugger frame or a threading bootstrap
-            if (frame.f_back.f_code == stopOnHandleCommand):
+            if frame.f_back.f_code == stopOnHandleCommand:
                 frame.f_trace = self.trace_dispatch
                 break
             
@@ -471,7 +500,7 @@
             sys.settrace(None)
         return exitcode
 
-    def _set_stopinfo(self, stopframe, returnframe):
+    def _set_stopinfo(self, stopframe, returnframe, stoplineno=0):
         """
         Protected method to update the frame pointers.
         
@@ -479,9 +508,17 @@
         @type frame object
         @param returnframe the frame object where to stop on a function return
         @type frame object
+        @param stoplineno line number to stop at. If stoplineno is greater than
+            or equal to 0, then stop at line greater than or equal to the
+            stopline. If stoplineno is -1, then don't stop at all.
+        @type int
         """
         self.stopframe = stopframe
         self.returnframe = returnframe
+        # stoplineno >= 0 means: stop at line >= the stoplineno
+        # stoplineno -1 means: don't stop at all
+        self.stoplineno = stoplineno
+        
         if returnframe is not None:
             # Ensure to be able to stop on the return frame
             returnframe.f_trace = self.trace_dispatch
@@ -496,13 +533,30 @@
         """
         # Here we only set a new stop frame if it is a normal continue.
         if not special:
-            self._set_stopinfo(None, None)
+            self._set_stopinfo(None, None, -1)
         
         # Disable tracing if not started in debug mode
         if not self._dbgClient.debugging:
             sys.settrace(None)
             sys.setprofile(None)
 
+    def set_until(self, frame=None, lineno=None):
+        """
+        Public method to stop when the line with the lineno greater than the
+        current one is reached or when returning from current frame.
+        
+        @param frame reference to the frame object
+        @type frame object
+        @param lineno line number to continue to
+        @type int
+        """
+        # the name "until" is borrowed from gdb
+        if frame is None:
+            frame = self.currentFrame
+        if lineno is None:
+            lineno = frame.f_lineno + 1
+        self._set_stopinfo(frame, frame, lineno)
+
     def set_step(self):
         """
         Public method to stop after one line of code.
@@ -725,8 +779,9 @@
             fname = self._dbgClient.absPath(self.fix_frame_filename(fr))
             # Always show at least one stack frame, even if it's from eric.
             if stack and os.path.basename(fname).startswith(
-                    ("DebugBase.py", "DebugClientBase.py",
-                     "ThreadExtension.py", "threading.py")):
+                ("DebugBase.py", "DebugClientBase.py",
+                 "ThreadExtension.py", "threading.py")
+            ):
                 break
             
             fline = tb_lineno or fr.f_lineno
@@ -1031,7 +1086,7 @@
             message = str(excval)
         
         return exitcode, message
-
+    
     def stop_here(self, frame):
         """
         Public method reimplemented to filter out debugger files.
@@ -1047,9 +1102,11 @@
         if self.__skipFrame(frame):
             return False
         
-        return (self.stop_everywhere or
-                frame is self.stopframe or
-                frame is self.returnframe)
+        if frame is self.stopframe:
+            if self.stoplineno == -1:
+                return False
+            return frame.f_lineno >= self.stoplineno
+        return self.stop_everywhere or frame is self.returnframe
 
     def tracePythonLibs(self, enable):
         """
--- a/eric6/DebugClients/Python/DebugClientBase.py	Fri Dec 18 17:01:10 2020 +0100
+++ b/eric6/DebugClients/Python/DebugClientBase.py	Sat Dec 19 14:41:43 2020 +0100
@@ -684,6 +684,11 @@
             self.currentThreadExec.go(params["special"])
             self.eventExit = True
         
+        elif method == "RequestContinueUntil":
+            newLine = params["newLine"]
+            self.currentThreadExec.set_until(lineno=newLine)
+            self.eventExit = True
+        
         elif method == "RawInput":
             # If we are handling raw mode input then break out of the current
             # event loop.
--- a/eric6/Debugger/DebugServer.py	Fri Dec 18 17:01:10 2020 +0100
+++ b/eric6/Debugger/DebugServer.py	Sat Dec 19 14:41:43 2020 +0100
@@ -1236,6 +1236,18 @@
         """
         self.debuggerInterface.remoteContinue(debuggerId, special)
     
+    def remoteContinueUntil(self, debuggerId, line):
+        """
+        Public method to continue the debugged program to the given line
+        or until returning from the current frame.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
+        @param line the new line, where execution should be continued to
+        @type int
+        """
+        self.debuggerInterface.remoteContinueUntil(debuggerId, line)
+    
     def remoteMoveIP(self, debuggerId, line):
         """
         Public method to move the instruction pointer to a different line.
--- a/eric6/Debugger/DebugUI.py	Fri Dec 18 17:01:10 2020 +0100
+++ b/eric6/Debugger/DebugUI.py	Sat Dec 19 14:41:43 2020 +0100
@@ -115,7 +115,8 @@
         self.lastAction = -1
         self.debugActions = [
             self.__continue, self.__step, self.__stepOver, self.__stepOut,
-            self.__stepQuit, self.__runToCursor, self.__moveInstructionPointer
+            self.__stepQuit, self.__runToCursor, self.__runUntil,
+            self.__moveInstructionPointer
         ]
         self.__localsVarFilterList, self.__globalsVarFilterList = (
             Preferences.getVarFilters())
@@ -377,6 +378,23 @@
         self.actions.append(act)
         
         act = E5Action(
+            self.tr('Continue Until'),
+            UI.PixmapCache.getIcon("continueUntil"),
+            self.tr('Continue &Until'), Qt.SHIFT + Qt.Key_F6, 0,
+            self.debugActGrp, 'dbg_continue_until')
+        act.setStatusTip(self.tr(
+            """Continue running the program from the current line to the"""
+            """ current cursor position or until leaving the current frame"""))
+        act.setWhatsThis(self.tr(
+            """<b>Continue Until</b>"""
+            """<p>Continue running the program from the current line to the"""
+            """ cursor position greater than the current line or until"""
+            """ leaving the current frame.</p>"""
+        ))
+        act.triggered.connect(self.__runUntil)
+        self.actions.append(act)
+        
+        act = E5Action(
             self.tr('Move Instruction Pointer to Cursor'),
             UI.PixmapCache.getIcon("moveInstructionPointer"),
             self.tr('&Jump To Cursor'), Qt.Key_F12, 0,
@@ -2425,6 +2443,22 @@
             aw.getFileName(), line, 1, None, 1)
         self.debugServer.remoteContinue(debuggerId)
 
+    def __runUntil(self, debuggerId=""):
+        """
+        Private method to handle the Run Until action.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
+        """
+        if not debuggerId:
+            debuggerId = self.getSelectedDebuggerId()
+        
+        self.lastAction = 0
+        aw = self.viewmanager.activeWindow()
+        line = aw.getCursorPosition()[0] + 1
+        self.__enterRemote()
+        self.debugServer.remoteContinueUntil(debuggerId, line)
+
     def __moveInstructionPointer(self, debuggerId=""):
         """
         Private method to move the instruction pointer to a different line.
--- a/eric6/Debugger/DebuggerInterfaceNone.py	Fri Dec 18 17:01:10 2020 +0100
+++ b/eric6/Debugger/DebuggerInterfaceNone.py	Sat Dec 19 14:41:43 2020 +0100
@@ -266,6 +266,18 @@
         """
         return
 
+    def remoteContinueUntil(self, debuggerId, line):
+        """
+        Public method to continue the debugged program to the given line
+        or until returning from the current frame.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
+        @param line the new line, where execution should be continued to
+        @type int
+        """
+        return
+
     def remoteMoveIP(self, debuggerId, line):
         """
         Public method to move the instruction pointer to a different line.
--- a/eric6/Debugger/DebuggerInterfacePython.py	Fri Dec 18 17:01:10 2020 +0100
+++ b/eric6/Debugger/DebuggerInterfacePython.py	Sat Dec 19 14:41:43 2020 +0100
@@ -850,6 +850,21 @@
             "special": special,
         }, debuggerId)
     
+    def remoteContinueUntil(self, debuggerId, line):
+        """
+        Public method to continue the debugged program to the given line
+        or until returning from the current frame.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
+        @param line the new line, where execution should be continued to
+        @type int
+        """
+        self.__isStepCommand = False
+        self.__sendJsonCommand("RequestContinueUntil", {
+            "newLine": line,
+        }, debuggerId)
+    
     def remoteMoveIP(self, debuggerId, line):
         """
         Public method to move the instruction pointer to a different line.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/icons/breeze-dark/continueUntil.svg	Sat Dec 19 14:41:43 2020 +0100
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg id="a" version="1.1" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"><defs id="b"><style id="c" type="text/css">.ColorScheme-Text {
+        color:#eff0f1;
+      }
+      .ColorScheme-Highlight {
+        color:#3daee9;
+      }</style></defs><path id="d" d="m15.75 8 5.25-3.5-5.25-3.5z" fill="#f60" fill-rule="evenodd" stroke-width="1.75"/><path id="e" class="ColorScheme-Text" d="m1 1v20h20v-12.5h-1.25v11.25h-17.5v-13.75h11.25v-5z" color="#eff0f1" fill="currentColor"/><path id="f" class="ColorScheme-Text" d="m11.25 7v0.75h1.5v10.5h-1.5v0.75h3.75v-0.75h-1.5v-10.5h1.5v-0.75h-3.75m-6.75 3.75v1.5h-1.5v1.5h1.5v1.5l3-2.25-3-2.25" color="#eff0f1" fill="currentColor" stroke-width=".6"/></svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/icons/breeze-light/continueUntil.svg	Sat Dec 19 14:41:43 2020 +0100
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg id="a" version="1.1" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"><defs id="b"><style id="c" type="text/css">.ColorScheme-Text {
+        color:#eff0f1;
+      }
+      .ColorScheme-Highlight {
+        color:#3daee9;
+      }</style></defs><path id="d" d="m15.75 8 5.25-3.5-5.25-3.5z" fill="#f60" fill-rule="evenodd" stroke-width="1.75"/><path id="e" class="ColorScheme-Text" d="m1 1v20h20v-12.5h-1.25v11.25h-17.5v-13.75h11.25v-5z" color="#eff0f1" fill="#232629"/><path id="f" class="ColorScheme-Text" d="m11.25 7v0.75h1.5v10.5h-1.5v0.75h3.75v-0.75h-1.5v-10.5h1.5v-0.75h-3.75m-6.75 3.75v1.5h-1.5v1.5h1.5v1.5l3-2.25-3-2.25" color="#eff0f1" fill="#232629" stroke-width=".6"/></svg>
Binary file eric6/icons/oxygen/continueUntil.png has changed

eric ide

mercurial