src/eric7/QScintilla/Editor.py

branch
eric7-maintenance
changeset 10534
783d835d7fe4
parent 10460
3b34efa2857c
parent 10517
aecd5a8c958c
child 10616
4aa36fcd4a30
--- a/src/eric7/QScintilla/Editor.py	Mon Jan 01 11:11:21 2024 +0100
+++ b/src/eric7/QScintilla/Editor.py	Wed Jan 31 09:13:13 2024 +0100
@@ -11,6 +11,7 @@
 import collections
 import contextlib
 import difflib
+import enum
 import os
 import pathlib
 import re
@@ -31,14 +32,29 @@
     pyqtSignal,
     pyqtSlot,
 )
-from PyQt6.QtGui import QAction, QActionGroup, QFont, QPainter, QPalette, QPixmap
+from PyQt6.QtGui import (
+    QAction,
+    QActionGroup,
+    QCursor,
+    QFont,
+    QPainter,
+    QPalette,
+    QPixmap,
+)
 from PyQt6.QtPrintSupport import (
     QAbstractPrintDialog,
     QPrintDialog,
     QPrinter,
     QPrintPreviewDialog,
 )
-from PyQt6.QtWidgets import QApplication, QDialog, QInputDialog, QLineEdit, QMenu
+from PyQt6.QtWidgets import (
+    QApplication,
+    QDialog,
+    QInputDialog,
+    QLineEdit,
+    QMenu,
+    QToolTip,
+)
 
 from eric7 import Globals, Preferences, Utilities
 from eric7.CodeFormatting.BlackFormattingAction import BlackFormattingAction
@@ -69,6 +85,40 @@
 )
 
 
+class EditorIconId(enum.IntEnum):
+    """
+    Class defining the completion icon IDs.
+    """
+
+    Class = 1
+    ClassProtected = 2
+    ClassPrivate = 3
+    Method = 4
+    MethodProtected = 5
+    MethodPrivate = 6
+    Attribute = 7
+    AttributeProtected = 8
+    AttributePrivate = 9
+    Enum = 10
+    Keywords = 11
+    Module = 12
+
+    FromDocument = 99
+    TemplateImage = 100
+
+
+class EditorWarningKind(enum.Enum):
+    """
+    Class defining the kind of warnings supported by the Editor class.
+    """
+
+    Code = 1
+    Python = 2
+    Style = 3
+    Info = 4
+    Error = 5
+
+
 class Editor(QsciScintillaCompat):
     """
     Class implementing the editor component of the eric IDE.
@@ -146,32 +196,6 @@
     settingsRead = pyqtSignal()
     mouseDoubleClick = pyqtSignal(QPoint, Qt.MouseButton)
 
-    # TODO: convert to an enum.Enum
-    WarningCode = 1
-    WarningPython = 2
-    WarningStyle = 3
-    WarningInfo = 4
-    WarningError = 5
-
-    # TODO: convert to an enum.IntEnum (also in Assistant plugin)
-    # Autocompletion icon definitions
-    ClassID = 1
-    ClassProtectedID = 2
-    ClassPrivateID = 3
-    MethodID = 4
-    MethodProtectedID = 5
-    MethodPrivateID = 6
-    AttributeID = 7
-    AttributeProtectedID = 8
-    AttributePrivateID = 9
-    EnumID = 10
-    KeywordsID = 11
-    ModuleID = 12
-
-    FromDocumentID = 99
-
-    TemplateImageID = 100
-
     # Cooperation related definitions
     Separator = "@@@"
 
@@ -481,6 +505,11 @@
         self.gotoLine(1)
 
         # connect the mouse hover signals
+        # mouse hover for the editor margins
+        self.SCN_DWELLSTART.connect(self.__marginHoverStart)
+        self.SCN_DWELLEND.connect(self.__marginHoverEnd)
+
+        # mouse hover help for the editor text
         self.SCN_DWELLSTART.connect(self.__showMouseHoverHelp)
         self.SCN_DWELLEND.connect(self.__cancelMouseHoverHelp)
         self.__mouseHoverHelp = None
@@ -657,49 +686,63 @@
         # finale size of the completion images
         imageSize = QSize(22, 22)
 
-        self.registerImage(self.ClassID, EricPixmapCache.getPixmap("class", imageSize))
         self.registerImage(
-            self.ClassProtectedID,
+            EditorIconId.Class,
+            EricPixmapCache.getPixmap("class", imageSize),
+        )
+        self.registerImage(
+            EditorIconId.ClassProtected,
             EricPixmapCache.getPixmap("class_protected", imageSize),
         )
         self.registerImage(
-            self.ClassPrivateID, EricPixmapCache.getPixmap("class_private", imageSize)
+            EditorIconId.ClassPrivate,
+            EricPixmapCache.getPixmap("class_private", imageSize),
         )
         self.registerImage(
-            self.MethodID, EricPixmapCache.getPixmap("method", imageSize)
+            EditorIconId.Method,
+            EricPixmapCache.getPixmap("method", imageSize),
         )
         self.registerImage(
-            self.MethodProtectedID,
+            EditorIconId.MethodProtected,
             EricPixmapCache.getPixmap("method_protected", imageSize),
         )
         self.registerImage(
-            self.MethodPrivateID, EricPixmapCache.getPixmap("method_private", imageSize)
+            EditorIconId.MethodPrivate,
+            EricPixmapCache.getPixmap("method_private", imageSize),
         )
         self.registerImage(
-            self.AttributeID, EricPixmapCache.getPixmap("attribute", imageSize)
+            EditorIconId.Attribute,
+            EricPixmapCache.getPixmap("attribute", imageSize),
         )
         self.registerImage(
-            self.AttributeProtectedID,
+            EditorIconId.AttributeProtected,
             EricPixmapCache.getPixmap("attribute_protected", imageSize),
         )
         self.registerImage(
-            self.AttributePrivateID,
+            EditorIconId.AttributePrivate,
             EricPixmapCache.getPixmap("attribute_private", imageSize),
         )
-        self.registerImage(self.EnumID, EricPixmapCache.getPixmap("enum", imageSize))
-        self.registerImage(
-            self.KeywordsID, EricPixmapCache.getPixmap("keywords", imageSize)
-        )
         self.registerImage(
-            self.ModuleID, EricPixmapCache.getPixmap("module", imageSize)
+            EditorIconId.Enum,
+            EricPixmapCache.getPixmap("enum", imageSize),
+        )
+        self.registerImage(
+            EditorIconId.Keywords,
+            EricPixmapCache.getPixmap("keywords", imageSize),
+        )
+        self.registerImage(
+            EditorIconId.Module,
+            EricPixmapCache.getPixmap("module", imageSize),
         )
 
         self.registerImage(
-            self.FromDocumentID, EricPixmapCache.getPixmap("editor", imageSize)
+            EditorIconId.FromDocument,
+            EricPixmapCache.getPixmap("editor", imageSize),
         )
 
         self.registerImage(
-            self.TemplateImageID, EricPixmapCache.getPixmap("templateViewer", imageSize)
+            EditorIconId.TemplateImage,
+            EricPixmapCache.getPixmap("templateViewer", imageSize),
         )
 
     def addClone(self, editor):
@@ -2575,7 +2618,7 @@
         if self.inLinesChanged:
             return
 
-        for handle in self.breaks:
+        for handle in list(self.breaks):
             if self.markerLine(handle) == line - 1:
                 del self.breaks[handle]
                 self.markerDeleteHandle(handle)
@@ -2877,7 +2920,7 @@
         @param line line number of the bookmark
         @type int
         """
-        for handle in self.bookmarks:
+        for handle in self.bookmarks[:]:
             if self.markerLine(handle) == line - 1:
                 self.bookmarks.remove(handle)
                 self.markerDeleteHandle(handle)
@@ -3510,21 +3553,28 @@
     @pyqtSlot()
     def __convertTabs(self):
         """
-        Private slot to convert tabulators to spaces.
+        Private slot to automatically convert tabulators to spaces upon loading.
         """
         if (
             (not self.__getEditorConfig("TabForIndentation"))
             and Preferences.getEditor("ConvertTabsOnLoad")
             and not (self.lexer_ and self.lexer_.alwaysKeepTabs())
         ):
-            txt = self.text()
-            txtExpanded = txt.expandtabs(self.__getEditorConfig("TabWidth"))
-            if txtExpanded != txt:
-                self.beginUndoAction()
-                self.setText(txt)
-                self.endUndoAction()
-
-                self.setModified(True)
+            self.expandTabs()
+
+    @pyqtSlot()
+    def expandTabs(self):
+        """
+        Public slot to expand tabulators to spaces.
+        """
+        txt = self.text()
+        txtExpanded = txt.expandtabs(self.__getEditorConfig("TabWidth"))
+        if txtExpanded != txt:
+            self.beginUndoAction()
+            self.setText(txtExpanded)
+            self.endUndoAction()
+
+            self.setModified(True)
 
     def __removeTrailingWhitespace(self):
         """
@@ -3929,6 +3979,48 @@
             elif self.markersAtLine(line) & (1 << self.warning):
                 self.__showWarning(line)
 
+    @pyqtSlot(int, int, int)
+    def __marginHoverStart(self, pos, x, y):
+        """
+        Private slot showing the text of a syntax error or a warning marker.
+
+        @param pos mouse position into the document
+        @type int
+        @param x x-value of mouse screen position
+        @type int
+        @param y y-value of mouse screen position
+        @type int
+        """
+        margin = self.__marginNumber(x)
+        if margin == self.__indicMargin:
+            # determine width of all margins; needed to calculate document line
+            width = 0
+            for margin in range(5):
+                width += self.marginWidth(margin)
+
+            message = ""
+            line = self.lineIndexFromPoint(QPoint(width + 1, y))[0]
+            if self.markersAtLine(line) & (1 << self.syntaxerror):
+                for handle in self.syntaxerrors:
+                    if self.markerLine(handle) == line:
+                        message = "\n".join([e[0] for e in self.syntaxerrors[handle]])
+                        break
+            elif self.markersAtLine(line) & (1 << self.warning):
+                for handle in self._warnings:
+                    if self.markerLine(handle) == line:
+                        message = "\n".join([w[0] for w in self._warnings[handle]])
+                        break
+
+            if message:
+                QToolTip.showText(QCursor.pos(), message)
+
+    @pyqtSlot()
+    def __marginHoverEnd(self):
+        """
+        Private slot cancelling the display of syntax error or a warning marker text.
+        """
+        QToolTip.hideText()
+
     @pyqtSlot()
     def handleMonospacedEnable(self):
         """
@@ -6667,9 +6759,7 @@
         ):
             return
 
-        if Preferences.getEditor("AutoCheckSyntax") and Preferences.getEditor(
-            "OnlineSyntaxCheck"
-        ):
+        if Preferences.getEditor("OnlineSyntaxCheck"):
             self.__onlineSyntaxCheckTimer.stop()
 
         if self.isPy3File():
@@ -6739,10 +6829,14 @@
             self.toggleSyntaxError(lineno, col, True, msg)
 
         for _fn, lineno, col, _code, msg in problems.get("py_warnings", []):
-            self.toggleWarning(lineno, col, True, msg, warningType=Editor.WarningPython)
+            self.toggleWarning(
+                lineno, col, True, msg, warningType=EditorWarningKind.Python
+            )
 
         for _fn, lineno, col, _code, msg in problems.get("warnings", []):
-            self.toggleWarning(lineno, col, True, msg, warningType=Editor.WarningCode)
+            self.toggleWarning(
+                lineno, col, True, msg, warningType=EditorWarningKind.Code
+            )
 
         self.updateVerticalScrollBar()
 
@@ -7197,7 +7291,12 @@
     ###########################################################################
 
     def toggleWarning(
-        self, line, col, setWarning, msg="", warningType=WarningCode  # noqa: U100
+        self,
+        line,
+        col,  # noqa: U100
+        setWarning,
+        msg="",
+        warningType=EditorWarningKind.Code,
     ):
         """
         Public method to toggle a warning indicator.
@@ -7214,7 +7313,7 @@
         @param msg warning message
         @type str
         @param warningType type of warning message
-        @type int
+        @type EditorWarningKind
         """
         if line == 0:
             line = 1
@@ -7318,44 +7417,43 @@
         """
         Public slot to clear all pyflakes warnings.
         """
-        self.__clearTypedWarning(Editor.WarningCode)
-        self.__clearTypedWarning(Editor.WarningPython)
+        self.__clearTypedWarning(EditorWarningKind.Code)
+        self.__clearTypedWarning(EditorWarningKind.Python)
 
     @pyqtSlot()
     def clearStyleWarnings(self):
         """
         Public slot to clear all style warnings.
         """
-        self.__clearTypedWarning(Editor.WarningStyle)
+        self.__clearTypedWarning(EditorWarningKind.Style)
 
     @pyqtSlot()
     def clearInfoWarnings(self):
         """
         Public slot to clear all info warnings.
         """
-        self.__clearTypedWarning(Editor.WarningInfo)
+        self.__clearTypedWarning(EditorWarningKind.Info)
 
     @pyqtSlot()
     def clearErrorWarnings(self):
         """
         Public slot to clear all error warnings.
         """
-        self.__clearTypedWarning(Editor.WarningError)
+        self.__clearTypedWarning(EditorWarningKind.Error)
 
     @pyqtSlot()
     def clearCodeWarnings(self):
         """
         Public slot to clear all code warnings.
         """
-        self.__clearTypedWarning(Editor.WarningCode)
+        self.__clearTypedWarning(EditorWarningKind.Code)
 
     def __clearTypedWarning(self, warningKind):
         """
         Private method to clear warnings of a specific kind.
 
-        @param warningKind kind of warning to clear (Editor.WarningCode,
-            Editor.WarningPython, Editor.WarningStyle)
-        @type int
+        @param warningKind kind of warning to clear
+        @type EditorWarningKind
         """
         for handle in list(self._warnings):
             issues = []
@@ -7487,13 +7585,13 @@
             for handle in self._warnings:
                 if self.markerLine(handle) == line:
                     for msg, warningType in self._warnings[handle]:
-                        if warningType == Editor.WarningInfo:
+                        if warningType == EditorWarningKind.Info:
                             infoAnnotations.append(self.tr("Info: {0}").format(msg))
-                        elif warningType == Editor.WarningError:
+                        elif warningType == EditorWarningKind.Error:
                             errorAnnotations.append(self.tr("Error: {0}").format(msg))
-                        elif warningType == Editor.WarningStyle:
+                        elif warningType == EditorWarningKind.Style:
                             styleAnnotations.append(self.tr("Style: {0}").format(msg))
-                        elif warningType == Editor.WarningPython:
+                        elif warningType == EditorWarningKind.Python:
                             warningAnnotations.append(msg)
                         else:
                             warningAnnotations.append(
@@ -8597,7 +8695,7 @@
                             self.showUserList(
                                 TemplateCompletionListID,
                                 [
-                                    "{0}?{1:d}".format(t, self.TemplateImageID)
+                                    "{0}?{1:d}".format(t, EditorIconId.TemplateImage)
                                     for t in templateNames
                                 ],
                             )

eric ide

mercurial