--- 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): """