eric6/DebugClients/Python/DebugBase.py

changeset 7897
9acc015ea443
parent 7894
4370a8b30648
child 7900
72b88fb20261
--- 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):
         """

eric ide

mercurial