src/eric7/QScintilla/Editor.py

branch
eric7-maintenance
changeset 10349
df7edc29cbfb
parent 10272
7ae72d1df070
parent 10345
175e6f023126
child 10460
3b34efa2857c
--- a/src/eric7/QScintilla/Editor.py	Tue Oct 31 09:23:05 2023 +0100
+++ b/src/eric7/QScintilla/Editor.py	Wed Nov 29 14:23:36 2023 +0100
@@ -31,7 +31,7 @@
     pyqtSignal,
     pyqtSlot,
 )
-from PyQt6.QtGui import QActionGroup, QFont, QPainter, QPalette, QPixmap
+from PyQt6.QtGui import QAction, QActionGroup, QFont, QPainter, QPalette, QPixmap
 from PyQt6.QtPrintSupport import (
     QAbstractPrintDialog,
     QPrintDialog,
@@ -149,6 +149,8 @@
     WarningCode = 1
     WarningPython = 2
     WarningStyle = 3
+    WarningInfo = 4
+    WarningError = 5
 
     # Autocompletion icon definitions
     ClassID = 1
@@ -251,7 +253,7 @@
         self.syntaxerrors = {}
         # key:   marker handle
         # value: list of (error message, error index)
-        self.warnings = {}
+        self._warnings = {}
         # key:   marker handle
         # value: list of (warning message, warning type)
         self.notcoveredMarkers = []  # just a list of marker handles
@@ -315,6 +317,16 @@
         self.__markedText = ""
         self.__searchIndicatorLines = []
 
+        # set the autosave attributes
+        self.__autosaveInterval = Preferences.getEditor("AutosaveIntervalSeconds")
+        self.__autosaveManuallyDisabled = False
+
+        # initialize the autosave timer
+        self.__autosaveTimer = QTimer(self)
+        self.__autosaveTimer.setObjectName("AutosaveTimer")
+        self.__autosaveTimer.setSingleShot(True)
+        self.__autosaveTimer.timeout.connect(self.__autosave)
+
         # initialize some spellchecking stuff
         self.spell = None
         self.lastLine = 0
@@ -548,10 +560,6 @@
             if bnd.width() < req.width() or bnd.height() < req.height():
                 self.resize(bnd)
 
-        # set the autosave flag
-        self.autosaveEnabled = Preferences.getEditor("AutosaveInterval") > 0
-        self.autosaveManuallyDisabled = False
-
         # code coverage related attributes
         self.__coverageFile = ""
 
@@ -571,7 +579,7 @@
             self.__encodingChanged(editor.encoding, propagate=False)
             self.__spellLanguageChanged(editor.getSpellingLanguage(), propagate=False)
             # link the warnings to the original editor
-            self.warnings = editor.warnings
+            self._warnings = editor._warnings
 
         self.setAcceptDrops(True)
 
@@ -957,7 +965,7 @@
             self.tr("Autosave enabled"), self.__autosaveEnable
         )
         self.menuActs["AutosaveEnable"].setCheckable(True)
-        self.menuActs["AutosaveEnable"].setChecked(self.autosaveEnabled)
+        self.menuActs["AutosaveEnable"].setChecked(self.__autosaveInterval > 0)
         self.menuActs["TypingAidsEnabled"] = self.menu.addAction(
             self.tr("Typing aids enabled"), self.__toggleTypingAids
         )
@@ -1539,6 +1547,7 @@
                 self.tr("""No export format given. Aborting..."""),
             )
 
+    @pyqtSlot()
     def __showContextMenuLanguages(self):
         """
         Private slot handling the aboutToShow signal of the languages context
@@ -1605,7 +1614,7 @@
 
     def __languageChanged(self, language, propagate=True):
         """
-        Private slot handling a change of a connected editor's language.
+        Private method handling a change of a connected editor's language.
 
         @param language language to be set (string)
         @param propagate flag indicating to propagate the change (boolean)
@@ -1710,12 +1719,14 @@
         else:
             self.supportedLanguages[self.apiLanguage][2].setChecked(True)
 
+    @pyqtSlot()
     def projectLexerAssociationsChanged(self):
         """
         Public slot to handle changes of the project lexer associations.
         """
         self.setLanguage(self.fileName)
 
+    @pyqtSlot()
     def __showContextMenuEncodings(self):
         """
         Private slot handling the aboutToShow signal of the encodings context
@@ -1740,12 +1751,15 @@
         with contextlib.suppress(AttributeError, KeyError):
             (self.supportedEncodings[self.__normalizedEncoding()].setChecked(True))
 
+    @pyqtSlot(str)
     def __encodingChanged(self, encoding, propagate=True):
         """
         Private slot to handle a change of the encoding.
 
-        @param encoding changed encoding (string)
-        @param propagate flag indicating to propagate the change (boolean)
+        @param encoding changed encoding
+        @type str
+        @param propagate flag indicating to propagate the change
+        @type bool
         """
         self.encoding = encoding
         self.__checkEncoding()
@@ -1770,6 +1784,7 @@
             .replace("-selected", "")
         )
 
+    @pyqtSlot()
     def __showContextMenuEol(self):
         """
         Private slot handling the aboutToShow signal of the eol context menu.
@@ -1793,6 +1808,7 @@
         with contextlib.suppress(AttributeError, TypeError):
             self.supportedEols[self.getLineSeparator()].setChecked(True)
 
+    @pyqtSlot()
     def __eolChanged(self):
         """
         Private slot to handle a change of the eol mode.
@@ -1805,6 +1821,7 @@
             self.eolChanged.emit(eol)
             self.inEolChanged = False
 
+    @pyqtSlot()
     def __showContextMenuSpellCheck(self):
         """
         Private slot handling the aboutToShow signal of the spell check
@@ -1822,6 +1839,7 @@
 
         self.showMenu.emit("SpellCheck", self.spellCheckMenu, self)
 
+    @pyqtSlot()
     def __showContextMenuSpellLanguages(self):
         """
         Private slot handling the aboutToShow signal of the spell check
@@ -1840,6 +1858,7 @@
         self.__setSpellingLanguage(language)
         self.spellLanguageChanged.emit(language)
 
+    @pyqtSlot()
     def __checkSpellLanguage(self):
         """
         Private slot to check the selected spell check language action.
@@ -1848,6 +1867,7 @@
         with contextlib.suppress(AttributeError, KeyError):
             self.supportedSpellLanguages[language].setChecked(True)
 
+    @pyqtSlot(str)
     def __spellLanguageChanged(self, language, propagate=True):
         """
         Private slot to handle a change of the spell check language.
@@ -1867,11 +1887,13 @@
 
     def __bindLexer(self, filename, pyname=""):
         """
-        Private slot to set the correct lexer depending on language.
+        Private method to set the correct lexer depending on language.
 
         @param filename filename used to determine the associated lexer
-            language (string)
-        @param pyname name of the pygments lexer to use (string)
+            language
+        @type str
+        @param pyname name of the pygments lexer to use
+        @type str
         """
         if self.lexer_ is not None and (
             self.lexer_.lexer() == "container" or self.lexer_.lexer() is None
@@ -1983,11 +2005,13 @@
         self.lexer_.setDefaultColor(self.lexer_.color(0))
         self.lexer_.setDefaultPaper(self.lexer_.paper(0))
 
+    @pyqtSlot(int)
     def __styleNeeded(self, position):
         """
         Private slot to handle the need for more styling.
 
-        @param position end position, that needs styling (integer)
+        @param position end position, that needs styling
+        @type int
         """
         self.lexer_.styleText(self.getEndStyled(), position)
 
@@ -2033,10 +2057,11 @@
 
     def __bindCompleter(self, filename):
         """
-        Private slot to set the correct typing completer depending on language.
+        Private method to set the correct typing completer depending on language.
 
         @param filename filename used to determine the associated typing
-            completer language (string)
+            completer language
+        @type str
         """
         if self.completer is not None:
             self.completer.setEnabled(False)
@@ -2061,6 +2086,7 @@
         """
         return self.completer
 
+    @pyqtSlot(bool)
     def __modificationChanged(self, m):
         """
         Private slot to handle the modificationChanged signal.
@@ -2069,6 +2095,7 @@
         m and self.
 
         @param m modification status
+        @type bool
         """
         if not m and bool(self.fileName) and pathlib.Path(self.fileName).exists():
             self.lastModified = pathlib.Path(self.fileName).stat().st_mtime
@@ -2076,6 +2103,10 @@
         self.undoAvailable.emit(self.isUndoAvailable())
         self.redoAvailable.emit(self.isRedoAvailable())
 
+        if not m:
+            self.__autosaveTimer.stop()
+
+    @pyqtSlot(int, int)
     def __cursorPositionChanged(self, line, index):
         """
         Private slot to handle the cursorPositionChanged signal.
@@ -2084,12 +2115,13 @@
         line and pos.
 
         @param line line number of the cursor
+        @type int
         @param index position in line of the cursor
+        @type int
         """
         self.cursorChanged.emit(self.fileName, line + 1, index)
 
         if Preferences.getEditor("MarkOccurrencesEnabled"):
-            self.__markOccurrencesTimer.stop()
             self.__markOccurrencesTimer.start()
 
         if self.lastLine != line:
@@ -2112,6 +2144,7 @@
         self.lastLine = line
         self.lastIndex = index
 
+    @pyqtSlot()
     def __modificationReadOnly(self):
         """
         Private slot to handle the modificationAttempted signal.
@@ -2382,40 +2415,52 @@
         @param annotationLinesAdded number of added/deleted annotation lines
             (integer)
         """
-        if (
-            mtype & (self.SC_MOD_INSERTTEXT | self.SC_MOD_DELETETEXT)
-            and linesAdded != 0
-            and self.breaks
-        ):
-            bps = []  # list of breakpoints
-            for handle, (ln, cond, temp, enabled, ignorecount) in self.breaks.items():
-                line = self.markerLine(handle) + 1
-                if ln != line:
-                    bps.append((ln, line))
-                    self.breaks[handle] = (line, cond, temp, enabled, ignorecount)
-            self.inLinesChanged = True
-            for ln, line in sorted(bps, reverse=linesAdded > 0):
-                index1 = self.breakpointModel.getBreakPointIndex(self.fileName, ln)
-                index2 = self.breakpointModel.index(index1.row(), 1)
-                self.breakpointModel.setData(index2, line)
-            self.inLinesChanged = False
+        if mtype & (self.SC_MOD_INSERTTEXT | self.SC_MOD_DELETETEXT):
+            # 1. set/reset the autosave timer
+            if self.__autosaveInterval > 0:
+                self.__autosaveTimer.start(self.__autosaveInterval * 1000)
+
+            # 2. move breakpoints if a line was inserted or deleted
+            if linesAdded != 0 and self.breaks:
+                bps = []  # list of breakpoints
+                for handle, (
+                    ln,
+                    cond,
+                    temp,
+                    enabled,
+                    ignorecount,
+                ) in self.breaks.items():
+                    line = self.markerLine(handle) + 1
+                    if ln != line:
+                        bps.append((ln, line))
+                        self.breaks[handle] = (line, cond, temp, enabled, ignorecount)
+                self.inLinesChanged = True
+                for ln, line in sorted(bps, reverse=linesAdded > 0):
+                    index1 = self.breakpointModel.getBreakPointIndex(self.fileName, ln)
+                    index2 = self.breakpointModel.index(index1.row(), 1)
+                    self.breakpointModel.setData(index2, line)
+                self.inLinesChanged = False
 
     def __restoreBreakpoints(self):
         """
         Private method to restore the breakpoints.
         """
-        for handle in list(self.breaks.keys()):
+        for handle in self.breaks:
             self.markerDeleteHandle(handle)
         self.__addBreakPoints(QModelIndex(), 0, self.breakpointModel.rowCount() - 1)
         self.__markerMap.update()
 
+    @pyqtSlot(QModelIndex, int, int)
     def __deleteBreakPoints(self, parentIndex, start, end):
         """
         Private slot to delete breakpoints.
 
-        @param parentIndex index of parent item (QModelIndex)
-        @param start start row (integer)
-        @param end end row (integer)
+        @param parentIndex index of parent item
+        @type QModelIndex
+        @param start start row
+        @type int
+        @param end end row
+        @type int
         """
         for row in range(start, end + 1):
             index = self.breakpointModel.index(row, 0, parentIndex)
@@ -2423,35 +2468,43 @@
             if fn == self.fileName:
                 self.clearBreakpoint(lineno)
 
+    @pyqtSlot(QModelIndex, QModelIndex)
     def __changeBreakPoints(self, startIndex, endIndex):
         """
         Private slot to set changed breakpoints.
 
         @param startIndex start index of the breakpoints being changed
-            (QModelIndex)
+        @type QModelIndex
         @param endIndex end index of the breakpoints being changed
-            (QModelIndex)
+        @type QModelIndex
         """
         if not self.inLinesChanged:
             self.__addBreakPoints(QModelIndex(), startIndex.row(), endIndex.row())
 
+    @pyqtSlot(QModelIndex, QModelIndex)
     def __breakPointDataAboutToBeChanged(self, startIndex, endIndex):
         """
         Private slot to handle the dataAboutToBeChanged signal of the
         breakpoint model.
 
-        @param startIndex start index of the rows to be changed (QModelIndex)
-        @param endIndex end index of the rows to be changed (QModelIndex)
+        @param startIndex start index of the rows to be changed
+        @type QModelIndex
+        @param endIndex end index of the rows to be changed
+        @type QModelIndex
         """
         self.__deleteBreakPoints(QModelIndex(), startIndex.row(), endIndex.row())
 
+    @pyqtSlot(QModelIndex, int, int)
     def __addBreakPoints(self, parentIndex, start, end):
         """
         Private slot to add breakpoints.
 
-        @param parentIndex index of parent item (QModelIndex)
-        @param start start row (integer)
-        @param end end row (integer)
+        @param parentIndex index of parent item
+        @type QModelIndex
+        @param start start row
+        @type int
+        @param end end row
+        @type int
         """
         for row in range(start, end + 1):
             index = self.breakpointModel.index(row, 0, parentIndex)
@@ -2482,14 +2535,10 @@
 
         for handle in self.breaks:
             if self.markerLine(handle) == line - 1:
-                break
-        else:
-            # not found, simply ignore it
-            return
-
-        del self.breaks[handle]
-        self.markerDeleteHandle(handle)
-        self.__markerMap.update()
+                del self.breaks[handle]
+                self.markerDeleteHandle(handle)
+                self.__markerMap.update()
+                return
 
     def newBreakpointWithProperties(self, line, properties):
         """
@@ -2615,6 +2664,7 @@
         """
         return len(self.breaks) > 0
 
+    @pyqtSlot()
     def __menuToggleTemporaryBreakpoint(self):
         """
         Private slot to handle the 'Toggle temporary breakpoint' context menu
@@ -2626,6 +2676,7 @@
         self.__toggleBreakpoint(self.line, 1)
         self.line = -1
 
+    @pyqtSlot()
     def menuToggleBreakpoint(self):
         """
         Public slot to handle the 'Toggle breakpoint' context menu action.
@@ -2636,6 +2687,7 @@
         self.__toggleBreakpoint(self.line)
         self.line = -1
 
+    @pyqtSlot()
     def __menuToggleBreakpointEnabled(self):
         """
         Private slot to handle the 'Enable/Disable breakpoint' context menu
@@ -2647,11 +2699,13 @@
         self.__toggleBreakpointEnabled(self.line)
         self.line = -1
 
+    @pyqtSlot()
     def menuEditBreakpoint(self, line=None):
         """
         Public slot to handle the 'Edit breakpoint' context menu action.
 
-        @param line linenumber of the breakpoint to edit
+        @param line line number of the breakpoint to edit
+        @type int
         """
         from eric7.Debugger.EditBreakpointDialog import EditBreakpointDialog
 
@@ -2702,6 +2756,7 @@
 
         self.line = -1
 
+    @pyqtSlot()
     def menuNextBreakpoint(self):
         """
         Public slot to handle the 'Next breakpoint' context menu action.
@@ -2719,6 +2774,7 @@
             self.setCursorPosition(bpline, 0)
             self.ensureLineVisible(bpline)
 
+    @pyqtSlot()
     def menuPreviousBreakpoint(self):
         """
         Public slot to handle the 'Previous breakpoint' context menu action.
@@ -2736,6 +2792,7 @@
             self.setCursorPosition(bpline, 0)
             self.ensureLineVisible(bpline)
 
+    @pyqtSlot()
     def __menuClearBreakpoints(self):
         """
         Private slot to handle the 'Clear all breakpoints' context menu action.
@@ -2744,9 +2801,10 @@
 
     def __clearBreakpoints(self, fileName):
         """
-        Private slot to clear all breakpoints.
-
-        @param fileName name of the file (string)
+        Private method to clear all breakpoints.
+
+        @param fileName name of the file
+        @type str
         """
         idxList = []
         for ln, _, _, _, _ in self.breaks.values():
@@ -2816,6 +2874,7 @@
         """
         return len(self.bookmarks) > 0
 
+    @pyqtSlot()
     def menuToggleBookmark(self):
         """
         Public slot to handle the 'Toggle bookmark' context menu action.
@@ -2826,6 +2885,7 @@
         self.toggleBookmark(self.line)
         self.line = -1
 
+    @pyqtSlot()
     def nextBookmark(self):
         """
         Public slot to handle the 'Next bookmark' context menu action.
@@ -2843,6 +2903,7 @@
             self.setCursorPosition(bmline, 0)
             self.ensureLineVisible(bmline)
 
+    @pyqtSlot()
     def previousBookmark(self):
         """
         Public slot to handle the 'Previous bookmark' context menu action.
@@ -2860,6 +2921,7 @@
             self.setCursorPosition(bmline, 0)
             self.ensureLineVisible(bmline)
 
+    @pyqtSlot()
     def clearBookmarks(self):
         """
         Public slot to handle the 'Clear all bookmarks' context menu action.
@@ -2874,6 +2936,7 @@
     ## Printing methods below
     ###########################################################################
 
+    @pyqtSlot()
     def printFile(self):
         """
         Public slot to print the text.
@@ -2913,6 +2976,7 @@
             sb.showMessage(self.tr("Printing aborted"), 2000)
             QApplication.processEvents()
 
+    @pyqtSlot()
     def printPreviewFile(self):
         """
         Public slot to show a print preview of the text.
@@ -2966,6 +3030,7 @@
         """
         return self.__hasTaskMarkers
 
+    @pyqtSlot()
     def nextTask(self):
         """
         Public slot to handle the 'Next task' context menu action.
@@ -2983,6 +3048,7 @@
             self.setCursorPosition(taskline, 0)
             self.ensureLineVisible(taskline)
 
+    @pyqtSlot()
     def previousTask(self):
         """
         Public slot to handle the 'Previous task' context menu action.
@@ -3000,6 +3066,7 @@
             self.setCursorPosition(taskline, 0)
             self.ensureLineVisible(taskline)
 
+    @pyqtSlot()
     def extractTasks(self):
         """
         Public slot to extract all tasks.
@@ -3061,6 +3128,7 @@
         painter.end()
         return pixmap
 
+    @pyqtSlot()
     def __initOnlineChangeTrace(self):
         """
         Private slot to initialize the online change trace.
@@ -3078,6 +3146,7 @@
         )
         self.textChanged.connect(self.__resetOnlineChangeTraceTimer)
 
+    @pyqtSlot()
     def __reinitOnlineChangeTrace(self):
         """
         Private slot to re-initialize the online change trace.
@@ -3094,6 +3163,7 @@
             self.__onlineChangeTraceTimer.stop()
             self.__onlineChangeTraceTimer.start()
 
+    @pyqtSlot()
     def __onlineChangeTraceTimerTimeout(self):
         """
         Private slot to mark added and changed lines.
@@ -3126,6 +3196,7 @@
             self.changeMarkersUpdated.emit(self)
             self.__markerMap.update()
 
+    @pyqtSlot()
     def resetOnlineChangeTraceInfo(self):
         """
         Public slot to reset the online change trace info.
@@ -3148,6 +3219,7 @@
             self.changeMarkersUpdated.emit(self)
             self.__markerMap.update()
 
+    @pyqtSlot()
     def __deleteAllChangeMarkers(self):
         """
         Private slot to delete all change markers.
@@ -3188,6 +3260,7 @@
         """
         return self.__hasChangeMarkers
 
+    @pyqtSlot()
     def nextChange(self):
         """
         Public slot to handle the 'Next change' context menu action.
@@ -3205,6 +3278,7 @@
             self.setCursorPosition(changeline, 0)
             self.ensureLineVisible(changeline)
 
+    @pyqtSlot()
     def previousChange(self):
         """
         Public slot to handle the 'Previous change' context menu action.
@@ -3304,7 +3378,7 @@
 
     def readFile(self, fn, createIt=False, encoding=""):
         """
-        Public slot to read the text from a file.
+        Public method to read the text from a file.
 
         @param fn filename to read from (string)
         @param createIt flag indicating the creation of a new file, if the
@@ -3360,6 +3434,7 @@
             self.setModified(modified)
             self.lastModified = pathlib.Path(fn).stat().st_mtime
 
+    @pyqtSlot()
     def __convertTabs(self):
         """
         Private slot to convert tabulators to spaces.
@@ -3393,11 +3468,14 @@
 
     def writeFile(self, fn, backup=True):
         """
-        Public slot to write the text to a file.
-
-        @param fn filename to write to (string)
-        @param backup flag indicating to save a backup (boolean)
-        @return flag indicating success (boolean)
+        Public method to write the text to a file.
+
+        @param fn filename to write to
+        @type str
+        @param backup flag indicating to save a backup
+        @type bool
+        @return flag indicating success
+        @rtype bool
         """
         config = self.__loadEditorConfigObject(fn)
 
@@ -3578,6 +3656,8 @@
 
         self.__loadEditorConfig(fn)
         self.editorAboutToBeSaved.emit(self.fileName)
+        if self.__autosaveTimer.isActive():
+            self.__autosaveTimer.stop()
         if self.writeFile(fn):
             if saveas:
                 self.__clearBreakpoints(self.fileName)
@@ -3619,11 +3699,12 @@
 
     def saveFileAs(self, path=None):
         """
-        Public slot to save a file with a new name.
-
-        @param path directory to save the file in (string)
-        @return tuple of two values (boolean, string) giving a success
-            indicator and the name of the saved file
+        Public method to save a file with a new name.
+
+        @param path directory to save the file in
+        @type str
+        @return tuple containing a success indicator and the name of the saved file
+        @rtype tuple of (bool, str)
         """
         return self.saveFile(True, path)
 
@@ -3669,7 +3750,7 @@
 
     def handleRenamed(self, fn):
         """
-        Public slot to handle the editorRenamed signal.
+        Public method to handle the editorRenamed signal.
 
         @param fn filename to be set for the editor (string).
         """
@@ -3687,11 +3768,13 @@
         self.vm.setEditorName(self, self.fileName)
         self.__updateReadOnly(True)
 
+    @pyqtSlot(str)
     def fileRenamed(self, fn):
         """
         Public slot to handle the editorRenamed signal.
 
-        @param fn filename to be set for the editor (string).
+        @param fn filename to be set for the editor
+        @type str.
         """
         self.handleRenamed(fn)
         if not self.inFileRenamed:
@@ -3705,7 +3788,7 @@
 
     def ensureVisible(self, line, expand=False):
         """
-        Public slot to ensure, that the specified line is visible.
+        Public method to ensure, that the specified line is visible.
 
         @param line line number to make visible
         @type int
@@ -3722,7 +3805,7 @@
 
     def ensureVisibleTop(self, line, expand=False):
         """
-        Public slot to ensure, that the specified line is visible at the top
+        Public method to ensure, that the specified line is visible at the top
         of the editor.
 
         @param line line number to make visible
@@ -3744,9 +3827,12 @@
         """
         Private slot to handle the marginClicked signal.
 
-        @param margin id of the clicked margin (integer)
-        @param line line number of the click (integer)
-        @param modifiers keyboard modifiers (Qt.KeyboardModifiers)
+        @param margin id of the clicked margin
+        @type int
+        @param line line number of the click
+        @type int
+        @param modifiers keyboard modifiers
+        @type Qt.KeyboardModifiers
         """
         if margin == self.__bmMargin:
             self.toggleBookmark(line + 1)
@@ -3758,6 +3844,7 @@
             elif self.markersAtLine(line) & (1 << self.warning):
                 self.__showWarning(line)
 
+    @pyqtSlot()
     def handleMonospacedEnable(self):
         """
         Public slot to handle the Use Monospaced Font context menu entry.
@@ -3774,16 +3861,22 @@
             self.setMonospaced(False)
             self.__setMarginsDisplay()
 
-    def getWordBoundaries(self, line, index, useWordChars=True):
+    def getWordBoundaries(self, line, index, useWordChars=True, forCompletion=False):
         """
         Public method to get the word boundaries at a position.
 
-        @param line number of line to look at (int)
-        @param index position to look at (int)
+        @param line number of line to look at
+        @type int
+        @param index position to look at
+        @type int
         @param useWordChars flag indicating to use the wordCharacters
-            method (boolean)
+            method (defaults to True)
+        @type bool (optional)
+        @param forCompletion flag indicating a modification for completions (defaults
+            to False)
+        @type bool (optional)
         @return tuple with start and end indexes of the word at the position
-            (integer, integer)
+        @rtype tuple of (int, int)
         """
         wc = self.wordCharacters()
         if wc is None or not useWordChars:
@@ -3791,6 +3884,8 @@
         else:
             wc = re.sub(r"\w", "", wc)
             pattern = r"\b[\w{0}]+\b".format(re.escape(wc))
+        if forCompletion:
+            pattern += "=?"
         rx = (
             re.compile(pattern)
             if self.caseSensitive()
@@ -3805,19 +3900,29 @@
 
         return (index, index)
 
-    def getWord(self, line, index, direction=0, useWordChars=True):
+    def getWord(self, line, index, direction=0, useWordChars=True, forCompletion=False):
         """
         Public method to get the word at a position.
 
-        @param line number of line to look at (int)
-        @param index position to look at (int)
+        @param line number of line to look at
+        @type int
+        @param index position to look at
+        @type int
         @param direction direction to look in (0 = whole word, 1 = left,
             2 = right)
+        @type int
         @param useWordChars flag indicating to use the wordCharacters
-            method (boolean)
-        @return the word at that position (string)
-        """
-        start, end = self.getWordBoundaries(line, index, useWordChars)
+            method (defaults to True)
+        @type bool (optional)
+        @param forCompletion flag indicating a modification for completions (defaults
+            to False)
+        @type bool (optional)
+        @return the word at that position
+        @rtype str
+        """
+        start, end = self.getWordBoundaries(
+            line, index, useWordChars=useWordChars, forCompletion=forCompletion
+        )
         if direction == 1:
             end = index
         elif direction == 2:
@@ -4080,6 +4185,7 @@
         else:
             return line.strip().startswith(commentStr)
 
+    @pyqtSlot()
     def toggleCommentBlock(self):
         """
         Public slot to toggle the comment of a block.
@@ -4127,6 +4233,7 @@
             self.uncommentLineOrSelection()
             self.setCursorPosition(line, index - len(commentStr))
 
+    @pyqtSlot()
     def commentLine(self):
         """
         Public slot to comment the current line.
@@ -4144,6 +4251,7 @@
             self.insertAt(self.lexer_.commentStr(), line, pos)
         self.endUndoAction()
 
+    @pyqtSlot()
     def uncommentLine(self):
         """
         Public slot to uncomment the current line.
@@ -4170,6 +4278,7 @@
         self.removeSelectedText()
         self.endUndoAction()
 
+    @pyqtSlot()
     def commentSelection(self):
         """
         Public slot to comment the current selection.
@@ -4200,6 +4309,7 @@
         self.setSelection(lineFrom, 0, endLine + 1, 0)
         self.endUndoAction()
 
+    @pyqtSlot()
     def uncommentSelection(self):
         """
         Public slot to uncomment the current selection.
@@ -4248,6 +4358,7 @@
         self.setSelection(lineFrom, indexFrom, lineTo, indexTo)
         self.endUndoAction()
 
+    @pyqtSlot()
     def commentLineOrSelection(self):
         """
         Public slot to comment the current line or current selection.
@@ -4257,6 +4368,7 @@
         else:
             self.commentLine()
 
+    @pyqtSlot()
     def uncommentLineOrSelection(self):
         """
         Public slot to uncomment the current line or current selection.
@@ -4266,6 +4378,7 @@
         else:
             self.uncommentLine()
 
+    @pyqtSlot()
     def streamCommentLine(self):
         """
         Public slot to stream comment the current line.
@@ -4281,6 +4394,7 @@
         self.insertAt(commentStr["start"], line, 0)
         self.endUndoAction()
 
+    @pyqtSlot()
     def streamCommentSelection(self):
         """
         Public slot to comment the current selection.
@@ -4314,6 +4428,7 @@
         self.setSelection(lineFrom, indexFrom, lineTo, indexTo)
         self.endUndoAction()
 
+    @pyqtSlot()
     def streamCommentLineOrSelection(self):
         """
         Public slot to stream comment the current line or current selection.
@@ -4323,6 +4438,7 @@
         else:
             self.streamCommentLine()
 
+    @pyqtSlot()
     def boxCommentLine(self):
         """
         Public slot to box comment the current line.
@@ -4342,6 +4458,7 @@
         self.insertAt(commentStr["start"], line, 0)
         self.endUndoAction()
 
+    @pyqtSlot()
     def boxCommentSelection(self):
         """
         Public slot to box comment the current selection.
@@ -4374,6 +4491,7 @@
         self.setSelection(lineFrom, 0, endLine + 3, 0)
         self.endUndoAction()
 
+    @pyqtSlot()
     def boxCommentLineOrSelection(self):
         """
         Public slot to box comment the current line or current selection.
@@ -4451,6 +4569,7 @@
                 indexEnd = 0
             self.setSelection(lineFrom, indexStart, lineTo, indexEnd)
 
+    @pyqtSlot()
     def indentLineOrSelection(self):
         """
         Public slot to indent the current line or current selection.
@@ -4460,6 +4579,7 @@
         else:
             self.__indentLine(True)
 
+    @pyqtSlot()
     def unindentLineOrSelection(self):
         """
         Public slot to unindent the current line or current selection.
@@ -4469,6 +4589,7 @@
         else:
             self.__indentLine(False)
 
+    @pyqtSlot()
     def smartIndentLineOrSelection(self):
         """
         Public slot to indent current line smartly.
@@ -4486,7 +4607,7 @@
 
     def gotoLine(self, line, pos=1, firstVisible=False, expand=False):
         """
-        Public slot to jump to the beginning of a line.
+        Public method to jump to the beginning of a line.
 
         @param line line number to go to
         @type int
@@ -4504,6 +4625,7 @@
         else:
             self.ensureVisible(line, expand)
 
+    @pyqtSlot()
     def __textChanged(self):
         """
         Private slot to handle a change of the editor text.
@@ -4514,6 +4636,7 @@
         """
         QTimer.singleShot(0, self.__saveLastEditPosition)
 
+    @pyqtSlot()
     def __saveLastEditPosition(self):
         """
         Private slot to record the last edit position.
@@ -4591,6 +4714,7 @@
     ## Setup methods below
     ###########################################################################
 
+    @pyqtSlot()
     def readSettings(self):
         """
         Public slot to read the settings into our lexer.
@@ -4646,7 +4770,12 @@
         self.__setCallTips()
 
         # set the autosave flags
-        self.autosaveEnabled = Preferences.getEditor("AutosaveInterval") > 0
+        self.__autosaveInterval = Preferences.getEditor("AutosaveIntervalSeconds")
+        if self.__autosaveInterval == 0:
+            self.__autosaveTimer.stop()
+        else:
+            if self.isModified():
+                self.__autosaveTimer.start(self.__autosaveInterval * 1000)
 
         if Preferences.getEditor("MiniContextMenu") != self.miniMenu:
             # regenerate context menu
@@ -4658,7 +4787,7 @@
             )
             self.menuActs["MonospacedFont"].setChecked(self.useMonospaced)
             self.menuActs["AutosaveEnable"].setChecked(
-                self.autosaveEnabled and not self.autosaveManuallyDisabled
+                self.__autosaveInterval > 0 and not self.__autosaveManuallyDisabled
             )
 
         # regenerate the margins context menu(s)
@@ -4802,6 +4931,7 @@
                 QsciScintilla.FoldStyle.NoFoldStyle.value, self.__foldMargin
             )
 
+    @pyqtSlot()
     def __resizeLinenoMargin(self):
         """
         Private slot to resize the line numbers margin.
@@ -5130,6 +5260,7 @@
             else:
                 self.setAutoCompletionSource(QsciScintilla.AutoCompletionSource.AcsAll)
 
+    @pyqtSlot()
     def __toggleAutoCompletionEnable(self):
         """
         Private slot to handle the Enable Autocompletion context menu entry.
@@ -5143,11 +5274,13 @@
     ## Support for autocompletion hook methods
     #################################################################
 
+    @pyqtSlot(int)
     def __charAdded(self, charNumber):
         """
         Private slot called to handle the user entering a character.
 
-        @param charNumber value of the character entered (integer)
+        @param charNumber value of the character entered
+        @type int
         """
         char = chr(charNumber)
         # update code documentation viewer
@@ -5199,6 +5332,7 @@
         wseps = self.lexer_.autoCompletionWordSeparators()
         return any(wsep.endswith(ch) for wsep in wseps)
 
+    @pyqtSlot()
     def __autocompletionCancelled(self):
         """
         Private slot to handle the cancellation of an auto-completion list.
@@ -5447,12 +5581,15 @@
         """
         self.__acCache.clear()
 
+    @pyqtSlot(int, str)
     def __completionListSelected(self, listId, txt):
         """
         Private slot to handle the selection from the completion list.
 
-        @param listId the ID of the user list (should be 1 or 2) (integer)
-        @param txt the selected text (string)
+        @param listId the ID of the user list (should be 1 or 2)
+        @type int
+        @param txt the selected text
+        @type str
         """
         # custom completions via plug-ins
         if listId == EditorAutoCompletionListID:
@@ -5467,7 +5604,7 @@
                 line, col = self.getCursorPosition()
             else:
                 line, col = self.getCursorPosition()
-                wLeft = self.getWordLeft(line, col)
+                wLeft = self.getWord(line, col, 1, forCompletion=True)  # word left
                 if not txt.startswith(wLeft):
                     self.selectCurrentWord()
                     self.removeSelectedText()
@@ -5711,6 +5848,7 @@
     ## Methods needed by the code documentation viewer
     #################################################################
 
+    @pyqtSlot()
     def __showCodeInfo(self):
         """
         Private slot to handle the context menu action to show code info.
@@ -5764,6 +5902,7 @@
             elif self.__marginNumber(pos.x()) == self.__foldMargin:
                 self.foldMarginMenu.popup(self.mapToGlobal(pos))
 
+    @pyqtSlot()
     def __aboutToShowContextMenu(self):
         """
         Private slot handling the aboutToShow signal of the context menu.
@@ -5828,6 +5967,7 @@
 
         self.showMenu.emit("Main", self.menu, self)
 
+    @pyqtSlot()
     def __showContextMenuAutocompletion(self):
         """
         Private slot called before the autocompletion menu is shown.
@@ -5839,6 +5979,7 @@
 
         self.showMenu.emit("Autocompletion", self.autocompletionMenu, self)
 
+    @pyqtSlot()
     def __showContextMenuShow(self):
         """
         Private slot called before the show menu is shown.
@@ -5884,6 +6025,7 @@
 
         self.showMenu.emit("Show", self.menuShow, self)
 
+    @pyqtSlot()
     def __showContextMenuGraphics(self):
         """
         Private slot handling the aboutToShow signal of the diagrams context
@@ -5898,6 +6040,7 @@
 
         self.showMenu.emit("Graphics", self.graphicsMenu, self)
 
+    @pyqtSlot(QMenu)
     def __showContextMenuMargin(self, menu):
         """
         Private slot handling the aboutToShow signal of the margins context
@@ -5956,7 +6099,7 @@
 
         if menu is self.indicMarginMenu:
             hasSyntaxErrors = bool(self.syntaxerrors)
-            hasWarnings = bool(self.warnings)
+            hasWarnings = bool(self._warnings)
             hasNotCoveredMarkers = bool(self.notcoveredMarkers)
 
             self.marginMenuActs["GotoSyntaxError"].setEnabled(hasSyntaxErrors)
@@ -5994,6 +6137,7 @@
 
         self.showMenu.emit("Margin", menu, self)
 
+    @pyqtSlot()
     def __showContextMenuChecks(self):
         """
         Private slot handling the aboutToShow signal of the checks context
@@ -6001,6 +6145,7 @@
         """
         self.showMenu.emit("Checks", self.checksMenu, self)
 
+    @pyqtSlot()
     def __showContextMenuTools(self):
         """
         Private slot handling the aboutToShow signal of the tools context
@@ -6008,6 +6153,7 @@
         """
         self.showMenu.emit("Tools", self.toolsMenu, self)
 
+    @pyqtSlot()
     def __showContextMenuFormatting(self):
         """
         Private slot handling the aboutToShow signal of the code formatting context
@@ -6027,6 +6173,7 @@
         self.__convertTabs()
         self.__checkEncoding()
 
+    @pyqtSlot()
     def __contextSave(self):
         """
         Private slot handling the save context menu entry.
@@ -6035,6 +6182,7 @@
         if ok:
             self.vm.setEditorName(self, self.fileName)
 
+    @pyqtSlot()
     def __contextSaveAs(self):
         """
         Private slot handling the save as context menu entry.
@@ -6043,24 +6191,28 @@
         if ok:
             self.vm.setEditorName(self, self.fileName)
 
+    @pyqtSlot()
     def __contextSaveCopy(self):
         """
         Private slot handling the save copy context menu entry.
         """
         self.saveFileCopy()
 
+    @pyqtSlot()
     def __contextClose(self):
         """
         Private slot handling the close context menu entry.
         """
         self.vm.closeEditor(self)
 
+    @pyqtSlot()
     def __newView(self):
         """
         Private slot to create a new view to an open document.
         """
         self.vm.newEditorView(self.fileName, self, self.filetype)
 
+    @pyqtSlot()
     def __newViewNewSplit(self):
         """
         Private slot to create a new view to an open document.
@@ -6068,18 +6220,21 @@
         self.vm.addSplit()
         self.vm.newEditorView(self.fileName, self, self.filetype)
 
+    @pyqtSlot()
     def __selectAll(self):
         """
         Private slot handling the select all context menu action.
         """
         self.selectAll(True)
 
+    @pyqtSlot()
     def __deselectAll(self):
         """
         Private slot handling the deselect all context menu action.
         """
         self.selectAll(False)
 
+    @pyqtSlot()
     def joinLines(self):
         """
         Public slot to join the current line with the next one.
@@ -6121,6 +6276,7 @@
         self.insertAt(" ", curLine, startIndex)
         self.endUndoAction()
 
+    @pyqtSlot()
     def shortenEmptyLines(self):
         """
         Public slot to compress lines consisting solely of whitespace
@@ -6135,26 +6291,38 @@
             ok = self.findNextTarget()
         self.endUndoAction()
 
+    @pyqtSlot()
     def __autosaveEnable(self):
         """
         Private slot handling the autosave enable context menu action.
         """
         if self.menuActs["AutosaveEnable"].isChecked():
-            self.autosaveManuallyDisabled = False
-        else:
-            self.autosaveManuallyDisabled = True
-
-    def shouldAutosave(self):
-        """
-        Public slot to check the autosave flags.
+            self.__autosaveManuallyDisabled = False
+        else:
+            self.__autosaveManuallyDisabled = True
+
+    def __shouldAutosave(self):
+        """
+        Private method to check the autosave flags.
 
         @return flag indicating this editor should be saved (boolean)
         """
         return (
             bool(self.fileName)
-            and not self.autosaveManuallyDisabled
+            and not self.__autosaveManuallyDisabled
             and not self.isReadOnly()
-        )
+            and self.isModified()
+        )
+
+    def __autosave(self):
+        """
+        Private slot to save the contents of the editor automatically.
+
+        It is only saved by the autosave timer after an initial save (i.e. it already
+        has a name).
+        """
+        if self.__shouldAutosave():
+            self.saveFile()
 
     def checkSyntax(self):
         """
@@ -6197,6 +6365,7 @@
                 self.text(),
             )
 
+    @pyqtSlot(str, str)
     def __processSyntaxCheckError(self, fn, msg):
         """
         Private slot to report an error message of a syntax check.
@@ -6216,15 +6385,18 @@
 
         self.updateVerticalScrollBar()
 
+    @pyqtSlot(str, dict)
     def __processSyntaxCheckResult(self, fn, problems):
         """
         Private slot to report the resulting messages of a syntax check.
 
-        @param fn filename of the checked file (str)
+        @param fn filename of the checked file
+        @type str
         @param problems dictionary with the keys 'error' and 'warnings' which
             hold a list containing details about the error/ warnings
             (file name, line number, column, codestring (only at syntax
-            errors), the message) (dict)
+            errors), the message)
+        @type dict
         """
         # Check if it's the requested file, otherwise ignore signal
         if fn != self.fileName and (bool(self.fileName) or fn != "(Unnamed)"):
@@ -6238,16 +6410,15 @@
             _fn, lineno, col, code, msg = error
             self.toggleSyntaxError(lineno, col, True, msg)
 
-        warnings = problems.get("py_warnings", [])
-        for _fn, lineno, col, _code, msg in warnings:
+        for _fn, lineno, col, _code, msg in problems.get("py_warnings", []):
             self.toggleWarning(lineno, col, True, msg, warningType=Editor.WarningPython)
 
-        warnings = problems.get("warnings", [])
-        for _fn, lineno, col, _code, msg in warnings:
+        for _fn, lineno, col, _code, msg in problems.get("warnings", []):
             self.toggleWarning(lineno, col, True, msg, warningType=Editor.WarningCode)
 
         self.updateVerticalScrollBar()
 
+    @pyqtSlot()
     def __initOnlineSyntaxCheck(self):
         """
         Private slot to initialize the online syntax check.
@@ -6434,6 +6605,7 @@
         """
         return len(self.notcoveredMarkers) > 0
 
+    @pyqtSlot()
     def nextUncovered(self):
         """
         Public slot to handle the 'Next uncovered' context menu action.
@@ -6451,6 +6623,7 @@
             self.setCursorPosition(ucline, 0)
             self.ensureLineVisible(ucline)
 
+    @pyqtSlot()
     def previousUncovered(self):
         """
         Public slot to handle the 'Previous uncovered' context menu action.
@@ -6536,22 +6709,31 @@
     ## Syntax error handling methods below
     ###########################################################################
 
-    def toggleSyntaxError(self, line, index, error, msg="", show=False):
+    def toggleSyntaxError(self, line, index, setError, msg="", show=False):
         """
         Public method to toggle a syntax error indicator.
 
-        @param line line number of the syntax error (integer)
-        @param index index number of the syntax error (integer)
-        @param error flag indicating if the error marker should be
-            set or deleted (boolean)
-        @param msg error message (string)
+        @param line line number of the syntax error
+        @type int
+        @param index index number of the syntax error
+        @type int
+        @param setError flag indicating if the error marker should be
+            set or deleted
+        @type bool
+        @param msg error message
+        @type str
         @param show flag indicating to set the cursor to the error position
-            (boolean)
+        @type bool
         """
         if line == 0:
             line = 1
             # hack to show a syntax error marker, if line is reported to be 0
-        if error:
+
+        line = min(line, self.lines())
+        # Limit the line number to the ones we really have to ensure proper display
+        # of the error annotation.
+
+        if setError:
             # set a new syntax error marker
             markers = self.markersAtLine(line - 1)
             index += self.indentation(line - 1)
@@ -6560,7 +6742,7 @@
                 self.syntaxerrors[handle] = [(msg, index)]
                 self.syntaxerrorToggled.emit(self)
             else:
-                for handle in list(self.syntaxerrors.keys()):
+                for handle in self.syntaxerrors:
                     if (
                         self.markerLine(handle) == line - 1
                         and (msg, index) not in self.syntaxerrors[handle]
@@ -6579,20 +6761,6 @@
         self.__setAnnotation(line - 1)
         self.__markerMap.update()
 
-    def getSyntaxErrors(self):
-        """
-        Public method to retrieve the syntax error markers.
-
-        @return sorted list of all lines containing a syntax error
-            (list of integer)
-        """
-        selist = []
-        for handle in list(self.syntaxerrors.keys()):
-            selist.append(self.markerLine(handle) + 1)
-
-        selist.sort()
-        return selist
-
     def getSyntaxErrorLines(self):
         """
         Public method to get the lines containing a syntax error.
@@ -6617,6 +6785,7 @@
         """
         return len(self.syntaxerrors) > 0
 
+    @pyqtSlot()
     def gotoSyntaxError(self):
         """
         Public slot to handle the 'Goto syntax error' context menu action.
@@ -6630,6 +6799,7 @@
             self.setCursorPosition(seline, index)
         self.ensureLineVisible(seline)
 
+    @pyqtSlot()
     def clearSyntaxError(self):
         """
         Public slot to handle the 'Clear all syntax error' context menu action.
@@ -6641,17 +6811,19 @@
         self.syntaxerrors.clear()
         self.syntaxerrorToggled.emit(self)
 
+    @pyqtSlot()
     def __showSyntaxError(self, line=-1):
         """
         Private slot to handle the 'Show syntax error message'
         context menu action.
 
-        @param line line number to show the syntax error for (integer)
+        @param line line number to show the syntax error for
+        @type int
         """
         if line == -1:
             line = self.line
 
-        for handle in list(self.syntaxerrors.keys()):
+        for handle in self.syntaxerrors:
             if self.markerLine(handle) == line:
                 errors = [e[0] for e in self.syntaxerrors[handle]]
                 EricMessageBox.critical(
@@ -6693,7 +6865,7 @@
     ###########################################################################
 
     def toggleWarning(
-        self, line, col, warning, msg="", warningType=WarningCode  # noqa: U100
+        self, line, col, setWarning, msg="", warningType=WarningCode  # noqa: U100
     ):
         """
         Public method to toggle a warning indicator.
@@ -6701,54 +6873,52 @@
         Note: This method is used to set pyflakes and code style warnings.
 
         @param line line number of the warning
+        @type int
         @param col column of the warning
-        @param warning flag indicating if the warning marker should be
-            set or deleted (boolean)
-        @param msg warning message (string)
-        @param warningType type of warning message (integer)
+        @type int
+        @param setWarning flag indicating if the warning marker should be
+            set or deleted
+        @type bool
+        @param msg warning message
+        @type str
+        @param warningType type of warning message
+        @type int
         """
         if line == 0:
             line = 1
             # hack to show a warning marker, if line is reported to be 0
-        if warning:
+
+        line = min(line, self.lines())
+        # Limit the line number to the ones we really have to ensure proper display
+        # of the warning annotation.
+
+        if setWarning:
             # set/amend a new warning marker
             warn = (msg, warningType)
             markers = self.markersAtLine(line - 1)
             if not (markers & (1 << self.warning)):
                 handle = self.markerAdd(line - 1, self.warning)
-                self.warnings[handle] = [warn]
+                self._warnings[handle] = [warn]
                 self.syntaxerrorToggled.emit(self)
+                # signal is also used for warnings
             else:
-                for handle in list(self.warnings.keys()):
+                for handle in self._warnings:
                     if (
                         self.markerLine(handle) == line - 1
-                        and warn not in self.warnings[handle]
+                        and warn not in self._warnings[handle]
                     ):
-                        self.warnings[handle].append(warn)
-        else:
-            for handle in list(self.warnings.keys()):
+                        self._warnings[handle].append(warn)
+        else:
+            for handle in list(self._warnings.keys()):
                 if self.markerLine(handle) == line - 1:
-                    del self.warnings[handle]
+                    del self._warnings[handle]
                     self.markerDeleteHandle(handle)
                     self.syntaxerrorToggled.emit(self)
+                    # signal is also used for warnings
 
         self.__setAnnotation(line - 1)
         self.__markerMap.update()
 
-    def getWarnings(self):
-        """
-        Public method to retrieve the warning markers.
-
-        @return sorted list of all lines containing a warning
-            (list of integer)
-        """
-        fwlist = []
-        for handle in list(self.warnings.keys()):
-            fwlist.append(self.markerLine(handle) + 1)
-
-        fwlist.sort()
-        return fwlist
-
     def getWarningLines(self):
         """
         Public method to get the lines containing a warning.
@@ -6771,8 +6941,9 @@
 
         @return flag indicating the presence of warnings (boolean)
         """
-        return len(self.warnings) > 0
-
+        return len(self._warnings) > 0
+
+    @pyqtSlot()
     def nextWarning(self):
         """
         Public slot to handle the 'Next warning' context menu action.
@@ -6790,6 +6961,7 @@
             self.setCursorPosition(fwline, 0)
             self.ensureLineVisible(fwline)
 
+    @pyqtSlot()
     def previousWarning(self):
         """
         Public slot to handle the 'Previous warning' context menu action.
@@ -6807,6 +6979,7 @@
             self.setCursorPosition(fwline, 0)
             self.ensureLineVisible(fwline)
 
+    @pyqtSlot()
     def clearFlakesWarnings(self):
         """
         Public slot to clear all pyflakes warnings.
@@ -6814,12 +6987,34 @@
         self.__clearTypedWarning(Editor.WarningCode)
         self.__clearTypedWarning(Editor.WarningPython)
 
+    @pyqtSlot()
     def clearStyleWarnings(self):
         """
         Public slot to clear all style warnings.
         """
         self.__clearTypedWarning(Editor.WarningStyle)
 
+    @pyqtSlot()
+    def clearInfoWarnings(self):
+        """
+        Public slot to clear all info warnings.
+        """
+        self.__clearTypedWarning(Editor.WarningInfo)
+
+    @pyqtSlot()
+    def clearErrorWarnings(self):
+        """
+        Public slot to clear all error warnings.
+        """
+        self.__clearTypedWarning(Editor.WarningError)
+
+    @pyqtSlot()
+    def clearCodeWarnings(self):
+        """
+        Public slot to clear all code warnings.
+        """
+        self.__clearTypedWarning(Editor.WarningCode)
+
     def __clearTypedWarning(self, warningKind):
         """
         Private method to clear warnings of a specific kind.
@@ -6827,51 +7022,54 @@
         @param warningKind kind of warning to clear (Editor.WarningCode,
             Editor.WarningPython, Editor.WarningStyle)
         """
-        for handle in list(self.warnings.keys()):
-            warnings = []
-            for msg, warningType in self.warnings[handle]:
+        for handle in list(self._warnings.keys()):
+            issues = []
+            for msg, warningType in self._warnings[handle]:
                 if warningType == warningKind:
                     continue
 
-                warnings.append((msg, warningType))
-
-            if warnings:
-                self.warnings[handle] = warnings
+                issues.append((msg, warningType))
+
+            if issues:
+                self._warnings[handle] = issues
                 self.__setAnnotation(self.markerLine(handle))
             else:
-                del self.warnings[handle]
+                del self._warnings[handle]
                 self.__setAnnotation(self.markerLine(handle))
                 self.markerDeleteHandle(handle)
         self.syntaxerrorToggled.emit(self)
         self.__markerMap.update()
 
+    @pyqtSlot()
     def clearWarnings(self):
         """
         Public slot to clear all warnings.
         """
-        for handle in self.warnings:
-            self.warnings[handle] = []
+        for handle in self._warnings:
+            self._warnings[handle] = []
             self.__setAnnotation(self.markerLine(handle))
             self.markerDeleteHandle(handle)
-        self.warnings.clear()
+        self._warnings.clear()
         self.syntaxerrorToggled.emit(self)
         self.__markerMap.update()
 
+    @pyqtSlot()
     def __showWarning(self, line=-1):
         """
         Private slot to handle the 'Show warning' context menu action.
 
-        @param line line number to show the warning for (integer)
+        @param line line number to show the warning for
+        @type int
         """
         if line == -1:
             line = self.line
 
-        for handle in list(self.warnings.keys()):
+        for handle in self._warnings:
             if self.markerLine(handle) == line:
                 EricMessageBox.warning(
                     self,
                     self.tr("Warning"),
-                    "\n".join([w[0] for w in self.warnings[handle]]),
+                    "\n".join([w[0] for w in self._warnings[handle]]),
                 )
                 break
         else:
@@ -6883,6 +7081,7 @@
     ## Annotation handling methods below
     ###########################################################################
 
+    @pyqtSlot()
     def __setAnnotationStyles(self):
         """
         Private slot to define the style used by inline annotations.
@@ -6924,6 +7123,18 @@
                 Preferences.getEditorColour("AnnotationsStyleBackground"),
             )
 
+            self.annotationInfoStyle = self.annotationStyleStyle + 1
+            self.SendScintilla(
+                QsciScintilla.SCI_STYLESETFORE,
+                self.annotationInfoStyle,
+                Preferences.getEditorColour("AnnotationsInfoForeground"),
+            )
+            self.SendScintilla(
+                QsciScintilla.SCI_STYLESETBACK,
+                self.annotationInfoStyle,
+                Preferences.getEditorColour("AnnotationsInfoBackground"),
+            )
+
     def __setAnnotation(self, line):
         """
         Private method to set the annotations for the given line.
@@ -6934,12 +7145,17 @@
             warningAnnotations = []
             errorAnnotations = []
             styleAnnotations = []
+            infoAnnotations = []
 
             # step 1: do warnings
-            for handle in self.warnings:
+            for handle in self._warnings:
                 if self.markerLine(handle) == line:
-                    for msg, warningType in self.warnings[handle]:
-                        if warningType == Editor.WarningStyle:
+                    for msg, warningType in self._warnings[handle]:
+                        if warningType == Editor.WarningInfo:
+                            infoAnnotations.append(self.tr("Info: {0}").format(msg))
+                        elif warningType == Editor.WarningError:
+                            errorAnnotations.append(self.tr("Error: {0}").format(msg))
+                        elif warningType == Editor.WarningStyle:
                             styleAnnotations.append(self.tr("Style: {0}").format(msg))
                         elif warningType == Editor.WarningPython:
                             warningAnnotations.append(msg)
@@ -6954,7 +7170,16 @@
                     for msg, _ in self.syntaxerrors[handle]:
                         errorAnnotations.append(self.tr("Error: {0}").format(msg))
 
+            # step 3: assemble the annotation
             annotations = []
+            if infoAnnotations:
+                annotationInfoTxt = "\n".join(infoAnnotations)
+                if styleAnnotations or warningAnnotations or errorAnnotations:
+                    annotationInfoTxt += "\n"
+                annotations.append(
+                    QsciStyledText(annotationInfoTxt, self.annotationInfoStyle)
+                )
+
             if styleAnnotations:
                 annotationStyleTxt = "\n".join(styleAnnotations)
                 if warningAnnotations or errorAnnotations:
@@ -6988,7 +7213,10 @@
         """
         if hasattr(QsciScintilla, "annotate"):
             self.clearAnnotations()
-            for handle in list(self.warnings.keys()) + list(self.syntaxerrors.keys()):
+            for handle in self._warnings:
+                line = self.markerLine(handle)
+                self.__setAnnotation(line)
+            for handle in self.syntaxerrors:
                 line = self.markerLine(handle)
                 self.__setAnnotation(line)
 
@@ -6996,6 +7224,7 @@
     ## Fold handling methods
     #################################################################
 
+    @pyqtSlot()
     def toggleCurrentFold(self):
         """
         Public slot to toggle the fold containing the current line.
@@ -7005,7 +7234,7 @@
 
     def expandFoldWithChildren(self, line=-1):
         """
-        Public slot to expand the current fold including its children.
+        Public method to expand the current fold including its children.
 
         @param line number of line to be expanded
         @type int
@@ -7019,7 +7248,7 @@
 
     def collapseFoldWithChildren(self, line=-1):
         """
-        Public slot to collapse the current fold including its children.
+        Public method to collapse the current fold including its children.
 
         @param line number of line to be expanded
         @type int
@@ -7031,12 +7260,14 @@
             QsciScintilla.SCI_FOLDCHILDREN, line, QsciScintilla.SC_FOLDACTION_CONTRACT
         )
 
+    @pyqtSlot()
     def __contextMenuExpandFoldWithChildren(self):
         """
         Private slot to handle the context menu expand with children action.
         """
         self.expandFoldWithChildren(self.line)
 
+    @pyqtSlot()
     def __contextMenuCollapseFoldWithChildren(self):
         """
         Private slot to handle the context menu collapse with children action.
@@ -7055,7 +7286,7 @@
             pressed ok or canceled the operation. (string, boolean)
         """
         qs = []
-        for s in list(self.macros.keys()):
+        for s in self.macros:
             qs.append(s)
         qs.sort()
         return QInputDialog.getItem(
@@ -7391,6 +7622,9 @@
         @param event the event object
         @type QFocusEvent
         """
+        if Preferences.getEditor("AutosaveOnFocusLost") and self.__shouldAutosave():
+            self.saveFile()
+
         self.vm.editorActGrp.setEnabled(False)
         self.setCaretWidth(0)
 
@@ -7585,6 +7819,7 @@
             self.isLocalFile() and not os.access(self.fileName, os.W_OK)
         ) or self.isReadOnly()
 
+    @pyqtSlot()
     def refresh(self):
         """
         Public slot to refresh the editor contents.
@@ -7603,7 +7838,7 @@
         self.clearWarnings()
 
         # clear breakpoint markers
-        for handle in list(self.breaks.keys()):
+        for handle in self.breaks:
             self.markerDeleteHandle(handle)
         self.breaks.clear()
 
@@ -7759,6 +7994,7 @@
 
         return menu
 
+    @pyqtSlot()
     def __showContextMenuResources(self):
         """
         Private slot handling the aboutToShow signal of the resources context
@@ -7936,6 +8172,7 @@
         )
         self.applicationDiagram.show()
 
+    @pyqtSlot()
     def __loadDiagram(self):
         """
         Private slot to load a diagram from file.
@@ -7954,6 +8191,7 @@
     ## Typing aids related methods below
     #######################################################################
 
+    @pyqtSlot()
     def __toggleTypingAids(self):
         """
         Private slot to toggle the typing aids.
@@ -8051,6 +8289,7 @@
     ## Project related methods
     #######################################################################
 
+    @pyqtSlot()
     def __projectPropertiesChanged(self):
         """
         Private slot to handle changes of the project properties.
@@ -8080,6 +8319,7 @@
 
         self.project.projectPropertiesChanged.connect(self.__projectPropertiesChanged)
 
+    @pyqtSlot()
     def projectOpened(self):
         """
         Public slot to handle the opening of a project.
@@ -8090,6 +8330,7 @@
             )
             self.setSpellingForProject()
 
+    @pyqtSlot()
     def projectClosed(self):
         """
         Public slot to handle the closing of a project.
@@ -8117,7 +8358,7 @@
 
     def __setSpellingLanguage(self, language, pwl="", pel=""):
         """
-        Private slot to set the spell checking language.
+        Private method to set the spell checking language.
 
         @param language spell checking language to be set (string)
         @param pwl name of the personal/project word list (string)
@@ -8217,7 +8458,8 @@
         """
         Private slot called to handle the user entering a character.
 
-        @param charNumber value of the character entered (integer)
+        @param charNumber value of the character entered
+        @type int
         """
         if self.spell:
             if not chr(charNumber).isalnum():
@@ -8225,6 +8467,7 @@
             elif self.hasIndicator(self.spellingIndicator, self.currentPosition()):
                 self.spell.checkWord(self.currentPosition())
 
+    @pyqtSlot()
     def checkSpelling(self):
         """
         Public slot to perform an interactive spell check of the document.
@@ -8239,6 +8482,7 @@
             if Preferences.getEditor("AutoSpellCheckingEnabled"):
                 self.spell.checkDocumentIncrementally()
 
+    @pyqtSlot()
     def __checkSpellingSelection(self):
         """
         Private slot to spell check the current selection.
@@ -8251,6 +8495,7 @@
         dlg = SpellCheckingDialog(self.spell, startPos, endPos, self)
         dlg.exec()
 
+    @pyqtSlot()
     def __checkSpellingWord(self):
         """
         Private slot to check the word below the spelling context menu.
@@ -8264,6 +8509,7 @@
         dlg = SpellCheckingDialog(self.spell, wordStartPos, wordEndPos, self)
         dlg.exec()
 
+    @pyqtSlot()
     def __showContextMenuSpelling(self):
         """
         Private slot to set up the spelling menu before it is shown.
@@ -8289,12 +8535,14 @@
 
         self.showMenu.emit("Spelling", self.spellingMenu, self)
 
+    @pyqtSlot(QAction)
     def __contextMenuSpellingTriggered(self, action):
         """
         Private slot to handle the selection of a suggestion of the spelling
         context menu.
 
-        @param action reference to the action that was selected (QAction)
+        @param action reference to the action that was selected
+        @type QAction
         """
         if action in self.spellingSuggActs:
             replacement = action.text()
@@ -8306,6 +8554,7 @@
             self.insert(replacement)
             self.endUndoAction()
 
+    @pyqtSlot()
     def __addToSpellingDictionary(self):
         """
         Private slot to add the word below the spelling context menu to the
@@ -8320,6 +8569,7 @@
         if Preferences.getEditor("AutoSpellCheckingEnabled"):
             self.spell.checkDocumentIncrementally()
 
+    @pyqtSlot()
     def __removeFromSpellingDictionary(self):
         """
         Private slot to remove the word below the context menu to the
@@ -8368,7 +8618,7 @@
 
     def shareConnected(self, connected):
         """
-        Public slot to handle a change of the connected state.
+        Public method to handle a change of the connected state.
 
         @param connected flag indicating the connected state (boolean)
         """
@@ -8382,7 +8632,7 @@
 
     def shareEditor(self, share):
         """
-        Public slot to set the shared status of the editor.
+        Public method to set the shared status of the editor.
 
         @param share flag indicating the share status (boolean)
         """
@@ -8390,6 +8640,7 @@
         if not share:
             self.shareConnected(False)
 
+    @pyqtSlot()
     def startSharedEdit(self):
         """
         Public slot to start a shared edit session for the editor.
@@ -8405,6 +8656,7 @@
         )
         self.__send(Editor.StartEditToken, hashStr)
 
+    @pyqtSlot()
     def sendSharedEdit(self):
         """
         Public slot to end a shared edit session for the editor and
@@ -8417,7 +8669,7 @@
 
     def cancelSharedEdit(self, send=True):
         """
-        Public slot to cancel a shared edit session for the editor.
+        Public method to cancel a shared edit session for the editor.
 
         @param send flag indicating to send the CancelEdit command (boolean)
         """
@@ -8447,11 +8699,13 @@
 
             self.vm.send(self.fileName, msg)
 
+    @pyqtSlot(str)
     def receive(self, command):
         """
         Public slot to handle received editor commands.
 
-        @param command command string (string)
+        @param command command string
+        @type str
         """
         if self.__isShared:
             if self.__isSyncing and not command.startswith(
@@ -8479,11 +8733,13 @@
         elif token == Editor.SyncToken:
             self.__processSyncCommand(argsString)
 
+    @pyqtSlot(str)
     def __processStartEditCommand(self, argsString):
         """
         Private slot to process a remote StartEdit command.
 
-        @param argsString string containing the command parameters (string)
+        @param argsString string containing the command parameters
+        @type str
         """
         if not self.__inSharedEdit and not self.__inRemoteSharedEdit:
             self.__inRemoteSharedEdit = True
@@ -8528,11 +8784,13 @@
 
         return "\n".join(commands) + "\n"
 
+    @pyqtSlot(str)
     def __processEndEditCommand(self, argsString):
         """
         Private slot to process a remote EndEdit command.
 
-        @param argsString string containing the command parameters (string)
+        @param argsString string containing the command parameters
+        @type str
         """
         commands = argsString.splitlines()
         sep = self.getLineSeparator()
@@ -8567,11 +8825,13 @@
 
         self.setCursorPosition(*cur)
 
+    @pyqtSlot(str)
     def __processRequestSyncCommand(self, argsString):
         """
         Private slot to process a remote RequestSync command.
 
-        @param argsString string containing the command parameters (string)
+        @param argsString string containing the command parameters
+        @type str
         """
         if self.__inSharedEdit:
             hashStr = str(
@@ -8585,11 +8845,13 @@
             if hashStr == argsString:
                 self.__send(Editor.SyncToken, self.__savedText)
 
+    @pyqtSlot(str)
     def __processSyncCommand(self, argsString):
         """
         Private slot to process a remote Sync command.
 
-        @param argsString string containing the command parameters (string)
+        @param argsString string containing the command parameters
+        @type str
         """
         if self.__isSyncing:
             cur = self.getCursorPosition()
@@ -8614,12 +8876,14 @@
     ## Special search related methods
     #######################################################################
 
+    @pyqtSlot()
     def searchCurrentWordForward(self):
         """
         Public slot to search the current word forward.
         """
         self.__searchCurrentWord(forward=True)
 
+    @pyqtSlot()
     def searchCurrentWordBackward(self):
         """
         Public slot to search the current word backward.
@@ -8666,6 +8930,7 @@
     ## Sort related methods
     #######################################################################
 
+    @pyqtSlot()
     def sortLines(self):
         """
         Public slot to sort the lines spanned by a rectangular selection.
@@ -8870,12 +9135,9 @@
         @param name name of the plug-in
         @type str
         """
-        keys = []
-        for key in self.__mouseClickHandlers:
+        for key in list(self.__mouseClickHandlers.keys()):
             if self.__mouseClickHandlers[key][0] == name:
-                keys.append(key)
-        for key in keys:
-            del self.__mouseClickHandlers[key]
+                del self.__mouseClickHandlers[key]
 
     def gotoReferenceHandler(self, referencesList):
         """
@@ -8924,6 +9186,7 @@
     ## Methods implementing a Shell interface
     #######################################################################
 
+    @pyqtSlot()
     def __executeSelection(self):
         """
         Private slot to execute the selected text in the shell window.
@@ -9136,7 +9399,7 @@
 
     def __popupDocstringMenu(self, lastLineText, lastCursorPosition):
         """
-        Private slot to pop up a menu asking the user, if a docstring should be
+        Private method to pop up a menu asking the user, if a docstring should be
         inserted.
 
         @param lastLineText line contents when the delay timer was started
@@ -9145,6 +9408,8 @@
             was started (line and index)
         @type tuple of (int, int)
         """
+        from .DocstringGenerator.BaseDocstringGenerator import DocstringMenuForEnterOnly
+
         cursorPosition = self.getCursorPosition()
         if lastCursorPosition != cursorPosition:
             return
@@ -9154,10 +9419,6 @@
 
         generator = self.getDocstringGenerator()
         if generator.hasFunctionDefinition(cursorPosition):
-            from .DocstringGenerator.BaseDocstringGenerator import (  # __IGNORE_WARNING__
-                DocstringMenuForEnterOnly,
-            )
-
             docstringMenu = DocstringMenuForEnterOnly(self)
             act = docstringMenu.addAction(
                 EricPixmapCache.getIcon("fileText"),
@@ -9199,6 +9460,7 @@
             else:
                 self.__cancelMouseHoverHelp()
 
+    @pyqtSlot()
     def __cancelMouseHoverHelp(self):
         """
         Private slot cancelling the display of mouse hover help.

eric ide

mercurial