--- 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 ], )