Tue, 24 Aug 2021 18:04:32 +0200
Editor: added code to move a breakpoint to a line actually creating some byte code (Python only).
--- a/docs/changelog Tue Aug 24 17:20:58 2021 +0200 +++ b/docs/changelog Tue Aug 24 18:04:32 2021 +0200 @@ -10,6 +10,8 @@ - Editor -- added the capability to suppress syntax highlighting by associating the file type 'Text' + -- added code to move a breakpoint to a line actually creating some byte code + (Python only) - Plugin Uninstall Dialog -- added capability to uninstall several plugins with one invocation of the dialog
--- a/eric7/Preferences/ConfigurationPages/DebuggerGeneralPage.py Tue Aug 24 17:20:58 2021 +0200 +++ b/eric7/Preferences/ConfigurationPages/DebuggerGeneralPage.py Tue Aug 24 18:04:32 2021 +0200 @@ -142,6 +142,8 @@ Preferences.getDebugger("MultiProcessEnabled")) self.debugThreeStateBreakPoint.setChecked( Preferences.getDebugger("ThreeStateBreakPoints")) + self.intelligentBreakPointCheckBox.setChecked( + Preferences.getDebugger("IntelligentBreakpoints")) self.recentFilesSpinBox.setValue( Preferences.getDebugger("RecentNumber")) self.exceptionBreakCheckBox.setChecked( @@ -237,6 +239,9 @@ "ThreeStateBreakPoints", self.debugThreeStateBreakPoint.isChecked()) Preferences.setDebugger( + "IntelligentBreakpoints", + self.intelligentBreakPointCheckBox.isChecked()) + Preferences.setDebugger( "RecentNumber", self.recentFilesSpinBox.value()) Preferences.setDebugger(
--- a/eric7/Preferences/ConfigurationPages/DebuggerGeneralPage.ui Tue Aug 24 17:20:58 2021 +0200 +++ b/eric7/Preferences/ConfigurationPages/DebuggerGeneralPage.ui Tue Aug 24 18:04:32 2021 +0200 @@ -505,8 +505,8 @@ <property name="title"> <string>Breakpoints</string> </property> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="0" column="0"> <widget class="QCheckBox" name="debugThreeStateBreakPoint"> <property name="toolTip"> <string>Select to change the breakpoint toggle order from Off->On->Off to Off->On (permanent)->On (temporary)->Off</string> @@ -516,7 +516,17 @@ </property> </widget> </item> - <item> + <item row="0" column="1"> + <widget class="QCheckBox" name="intelligentBreakPointCheckBox"> + <property name="toolTip"> + <string>Select to move a breakpoint to a line generating executable code</string> + </property> + <property name="text"> + <string>Intelligent breakpoint</string> + </property> + </widget> + </item> + <item row="1" column="0" colspan="2"> <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> <widget class="QLabel" name="label_4"> @@ -814,6 +824,7 @@ <tabstop>automaticResetCheckBox</tabstop> <tabstop>multiprocessCheckBox</tabstop> <tabstop>debugThreeStateBreakPoint</tabstop> + <tabstop>intelligentBreakPointCheckBox</tabstop> <tabstop>recentFilesSpinBox</tabstop> <tabstop>exceptionBreakCheckBox</tabstop> <tabstop>exceptionShellCheckBox</tabstop>
--- a/eric7/Preferences/__init__.py Tue Aug 24 17:20:58 2021 +0200 +++ b/eric7/Preferences/__init__.py Tue Aug 24 18:04:32 2021 +0200 @@ -76,6 +76,7 @@ # max. number of file names to be remembered for the add breakpoint # dialog "BreakAlways": False, + "IntelligentBreakpoints": True, "ShowExceptionInShell": True, "Python3VirtualEnv": "", "RubyInterpreter": "", @@ -1722,19 +1723,23 @@ Module function to retrieve the debugger settings. @param key the key of the value to get + @type str @param prefClass preferences class used as the storage area + @type Prefs @return the requested debugger setting + @rtype Any """ - if key in ["RemoteDbgEnabled", "PassiveDbgEnabled", + if key in ("RemoteDbgEnabled", "PassiveDbgEnabled", "AutomaticReset", "DebugEnvironmentReplace", "PythonRedirect", "PythonNoEncoding", "Python3Redirect", "Python3NoEncoding", "RubyRedirect", "ConsoleDbgEnabled", "PathTranslation", "Autosave", "ThreeStateBreakPoints", - "BreakAlways", "AutoViewSourceCode", - "ShowExceptionInShell", "MultiProcessEnabled", - ]: + "BreakAlways", "IntelligentBreakpoints", + "AutoViewSourceCode", "ShowExceptionInShell", + "MultiProcessEnabled", + ): return toBool(prefClass.settings.value( "Debugger/" + key, prefClass.debuggerDefaults[key])) elif key in ["PassiveDbgPort", "MaxVariableSize", "RecentNumber"]: @@ -1782,8 +1787,11 @@ Module function to store the debugger settings. @param key the key of the setting to be set + @type str @param value the value to be set + @type Any @param prefClass preferences class used as the storage area + @type Prefs """ prefClass.settings.setValue("Debugger/" + key, value)
--- a/eric7/QScintilla/Editor.py Tue Aug 24 17:20:58 2021 +0200 +++ b/eric7/QScintilla/Editor.py Tue Aug 24 18:04:32 2021 +0200 @@ -7,6 +7,7 @@ Module implementing the editor component of the eric IDE. """ +import bisect import collections import contextlib import difflib @@ -44,6 +45,8 @@ import UI.PixmapCache +from UI import PythonDisViewer + EditorAutoCompletionListID = 1 TemplateCompletionListID = 2 ReferencesListID = 3 @@ -2415,6 +2418,23 @@ @param temporary flag indicating a temporary breakpoint (boolean) """ if self.fileName and self.isPyFile(): + linestarts = PythonDisViewer.linestarts(self.text()) + if line not in linestarts: + if Preferences.getDebugger("IntelligentBreakpoints"): + # change line to the next one starting an instruction block + index = bisect.bisect(linestarts, line) + with contextlib.suppress(IndexError): + line = linestarts[index] + self.__toggleBreakpoint(line, temporary=temporary) + else: + EricMessageBox.warning( + self, + self.tr("Add Breakpoint"), + self.tr("No Python byte code will be created for the" + " selected line. No break point will be set!") + ) + return + self.breakpointModel.addBreakPoint( self.fileName, line, ('', temporary, True, 0)) self.breakpointToggled.emit(self)
--- a/eric7/UI/PythonDisViewer.py Tue Aug 24 17:20:58 2021 +0200 +++ b/eric7/UI/PythonDisViewer.py Tue Aug 24 18:04:32 2021 +0200 @@ -477,7 +477,7 @@ with EricOverrideCursor(): try: - codeObject = self.__tryCompile(source, filename) + codeObject = tryCompile(source, filename) except Exception as exc: codeObject = None self.__createErrorItem(str(exc)) @@ -652,24 +652,6 @@ expand=True) self.__editor.setHighlight(startLine - 1, 0, endLine, -1) - def __tryCompile(self, source, name): - """ - Private method to attempt to compile the given source, first as an - expression and then as a statement if the first approach fails. - - @param source source code string to be compiled - @type str - @param name name of the file containing the source - @type str - @return compiled code - @rtype code object - """ - try: - c = compile(source, name, 'eval') - except SyntaxError: - c = compile(source, name, 'exec') - return c - def __disassembleObject(self, co, parentItem, parentName="", lasti=-1): """ Private method to disassemble the given code object recursively. @@ -888,3 +870,51 @@ """ ericApp().getObject("UserInterface").showPreferences( "pythonPage") + + +def tryCompile(source, name): + """ + Function to attempt to compile the given source, first as an + expression and then as a statement if the first approach fails. + + @param source source code string to be compiled + @type str + @param name name of the file containing the source + @type str + @return compiled code + @rtype code object + """ + try: + c = compile(source, name, 'eval') + except SyntaxError: + c = compile(source, name, 'exec') + return c + + +def linestarts(co, filename="", getall=True): + """ + Function to get the line starts for the given code object + + @param co reference to the compiled code object or the source code + @type code object or str + @param filename name of the source file (optional) + @type str + @param getall flag indicating to get all line starts recursively + @type bool + @return list of lines starting some byte code instruction block + @rtype list of int + """ + if isinstance(co, str): + # try to compile the given source code first + try: + fn = filename if filename else "<dis>" + co = tryCompile(co, fn) + except SyntaxError: + return [] + + starts = [inst[1] for inst in dis.findlinestarts(co)] + if getall: + for x in co.co_consts: + if hasattr(x, 'co_code'): + starts.extend(linestarts(x)) + return sorted(starts)