Sat, 19 Dec 2020 14:41:43 +0100
Debugger: added support for the "Continue Until" debug action.
--- 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>