src/eric7/QScintilla/Editor.py

branch
eric7-maintenance
changeset 9549
67295777d9fe
parent 9442
906485dcd210
parent 9535
8b5402794fb6
child 9654
7328efba128b
--- a/src/eric7/QScintilla/Editor.py	Mon Oct 31 14:07:57 2022 +0100
+++ b/src/eric7/QScintilla/Editor.py	Wed Nov 30 09:19:51 2022 +0100
@@ -17,45 +17,47 @@
 
 import editorconfig
 
+from PyQt6.Qsci import QsciMacro, QsciScintilla, QsciStyledText
 from PyQt6.QtCore import (
-    pyqtSignal,
-    pyqtSlot,
-    Qt,
+    QCryptographicHash,
+    QDateTime,
     QDir,
-    QTimer,
+    QEvent,
     QModelIndex,
-    QCryptographicHash,
-    QEvent,
-    QDateTime,
     QPoint,
     QSize,
+    Qt,
+    QTimer,
+    pyqtSignal,
+    pyqtSlot,
 )
-from PyQt6.QtGui import QPalette, QFont, QPixmap, QPainter, QActionGroup
-from PyQt6.QtWidgets import QLineEdit, QDialog, QInputDialog, QApplication, QMenu
-from PyQt6.QtPrintSupport import QPrinter, QPrintDialog, QAbstractPrintDialog
-from PyQt6.Qsci import QsciScintilla, QsciMacro, QsciStyledText
-
-from eric7.EricWidgets.EricApplication import ericApp
-from eric7.EricWidgets import EricFileDialog, EricMessageBox
-from eric7.EricGui.EricOverrideCursor import EricOverrideCursor
-
-from eric7.EricUtilities.EricCache import EricCache
-
-from .QsciScintillaCompat import QsciScintillaCompat
-from .EditorMarkerMap import EditorMarkerMap
-from .SpellChecker import SpellChecker
-
-from eric7.Globals import recentNameBreakpointConditions
+from PyQt6.QtGui import QActionGroup, QFont, QPainter, QPalette, QPixmap
+from PyQt6.QtPrintSupport import (
+    QAbstractPrintDialog,
+    QPrintDialog,
+    QPrinter,
+    QPrintPreviewDialog,
+)
+from PyQt6.QtWidgets import QApplication, QDialog, QInputDialog, QLineEdit, QMenu
 
 from eric7 import Preferences, Utilities
-from eric7.Utilities import MouseUtilities
-
-from eric7.EricGui import EricPixmapCache
-
-from eric7.UI import PythonDisViewer
-
 from eric7.CodeFormatting.BlackFormattingAction import BlackFormattingAction
 from eric7.CodeFormatting.BlackUtilities import aboutBlack
+from eric7.CodeFormatting.IsortFormattingAction import IsortFormattingAction
+from eric7.CodeFormatting.IsortUtilities import aboutIsort
+from eric7.EricGui import EricPixmapCache
+from eric7.EricGui.EricOverrideCursor import EricOverrideCursor
+from eric7.EricUtilities.EricCache import EricCache
+from eric7.EricWidgets import EricFileDialog, EricMessageBox
+from eric7.EricWidgets.EricApplication import ericApp
+from eric7.Globals import recentNameBreakpointConditions
+from eric7.UI import PythonDisViewer
+from eric7.Utilities import MouseUtilities
+
+from . import Exporters, Lexers, TypingCompleters
+from .EditorMarkerMap import EditorMarkerMap
+from .QsciScintillaCompat import QsciScintillaCompat
+from .SpellChecker import SpellChecker
 
 EditorAutoCompletionListID = 1
 TemplateCompletionListID = 2
@@ -603,7 +605,7 @@
         if (
             self.fileName
             and self.project.isOpen()
-            and self.project.isProjectSource(self.fileName)
+            and self.project.isProjectCategory(self.fileName, "SOURCES")
         ):
             self.project.projectPropertiesChanged.connect(
                 self.__projectPropertiesChanged
@@ -744,8 +746,6 @@
 
         if not bindName and self.filetype:
             # check filetype
-            from . import Lexers
-
             supportedLanguages = Lexers.getSupportedLanguages()
             if self.filetype in supportedLanguages:
                 bindName = supportedLanguages[self.filetype][1]
@@ -1046,6 +1046,10 @@
         """
         menu = QMenu(self.tr("Code Formatting"))
 
+        #######################################################################
+        ## Black related entries
+        #######################################################################
+
         act = menu.addAction(self.tr("Black"), aboutBlack)
         font = act.font()
         font.setBold(True)
@@ -1062,6 +1066,25 @@
             self.tr("Formatting Diff"),
             lambda: self.__performFormatWithBlack(BlackFormattingAction.Diff),
         )
+        menu.addSeparator()
+
+        #######################################################################
+        ## isort related entries
+        #######################################################################
+
+        act = menu.addAction(self.tr("isort"), aboutIsort)
+        font = act.font()
+        font.setBold(True)
+        act.setFont(font)
+        menu.addAction(
+            self.tr("Sort Imports"),
+            lambda: self.__performImportSortingWithIsort(IsortFormattingAction.Sort),
+        )
+        menu.addAction(
+            self.tr("Imports Sorting Diff"),
+            lambda: self.__performImportSortingWithIsort(IsortFormattingAction.Diff),
+        )
+        menu.addSeparator()
 
         menu.aboutToShow.connect(self.__showContextMenuFormatting)
 
@@ -1150,8 +1173,6 @@
         self.languagesActGrp.addAction(self.noLanguageAct)
         menu.addSeparator()
 
-        from . import Lexers
-
         self.supportedLanguages = {}
         supportedLanguages = Lexers.getSupportedLanguages()
         languages = sorted(supportedLanguages.keys())
@@ -1468,8 +1489,6 @@
         @param exporterFormat format the file should be exported into (string)
         """
         if exporterFormat:
-            from . import Exporters
-
             exporter = Exporters.getExporter(exporterFormat, self)
             if exporter:
                 exporter.exportSource()
@@ -1508,7 +1527,7 @@
 
         @return name of the selected pygments lexer (string)
         """
-        from pygments.lexers import get_all_lexers
+        from pygments.lexers import get_all_lexers  # __IGNORE_WARNING_I102__
 
         lexerList = sorted(lex[0] for lex in get_all_lexers())
         try:
@@ -1869,8 +1888,6 @@
             self.filetype = language.split("|")[-1]
             language = ""
 
-        from . import Lexers
-
         self.lexer_ = Lexers.getLexer(language, self, pyname=pyname)
         if self.lexer_ is None:
             self.setLexer()
@@ -2010,8 +2027,6 @@
             elif self.isRubyFile():
                 apiLanguage = "Ruby"
 
-        from . import TypingCompleters
-
         self.completer = TypingCompleters.getCompleter(apiLanguage, self)
 
     def getCompleter(self):
@@ -2600,6 +2615,8 @@
 
         @param line linenumber of the breakpoint to edit
         """
+        from eric7.Debugger.EditBreakpointDialog import EditBreakpointDialog
+
         if line is not None:
             self.line = line - 1
         if self.line < 0:
@@ -2620,8 +2637,6 @@
                     else []
                 )
 
-                from eric7.Debugger.EditBreakpointDialog import EditBreakpointDialog
-
                 dlg = EditBreakpointDialog(
                     (self.fileName, ln),
                     (cond, temp, enabled, ignorecount),
@@ -2864,7 +2879,6 @@
         """
         Public slot to show a print preview of the text.
         """
-        from PyQt6.QtPrintSupport import QPrintPreviewDialog
         from .Printer import Printer
 
         printer = Printer(mode=QPrinter.PrinterMode.HighResolution)
@@ -3419,8 +3433,6 @@
         if not path:
             path = Preferences.getMultiProject("Workspace") or Utilities.getHomeDir()
 
-        from . import Lexers
-
         if self.fileName:
             filterPattern = "(*{0})".format(os.path.splitext(self.fileName)[1])
             for fileFilter in Lexers.getSaveFileFiltersList(True):
@@ -5704,7 +5716,9 @@
         coEnable = False
 
         # first check if the file belongs to a project
-        if self.project.isOpen() and self.project.isProjectSource(self.fileName):
+        if self.project.isOpen() and self.project.isProjectCategory(
+            self.fileName, "SOURCES"
+        ):
             fn = self.project.getMainScript(True)
             if fn is not None:
                 prEnable = self.project.isPy3Project() and bool(
@@ -5744,7 +5758,9 @@
         Private slot handling the aboutToShow signal of the diagrams context
         menu.
         """
-        if self.project.isOpen() and self.project.isProjectSource(self.fileName):
+        if self.project.isOpen() and self.project.isProjectCategory(
+            self.fileName, "SOURCES"
+        ):
             self.applicationDiagramMenuAct.setEnabled(True)
         else:
             self.applicationDiagramMenuAct.setEnabled(False)
@@ -6103,11 +6119,11 @@
         """
         Private method to handle the code metrics context menu action.
         """
+        from eric7.DataViews.CodeMetricsDialog import CodeMetricsDialog
+
         if not self.checkDirty():
             return
 
-        from eric7.DataViews.CodeMetricsDialog import CodeMetricsDialog
-
         self.codemetrics = CodeMetricsDialog()
         self.codemetrics.show()
         self.codemetrics.start(self.fileName)
@@ -6120,7 +6136,7 @@
         @return file name of the coverage file
         @rtype str
         """
-        files = set()
+        files = []
 
         if bool(self.__coverageFile):
             # return the path of a previously used coverage file
@@ -6128,15 +6144,21 @@
 
         # first check if the file belongs to a project and there is
         # a project coverage file
-        if self.project.isOpen() and self.project.isProjectSource(self.fileName):
+        if self.project.isOpen() and self.project.isProjectCategory(
+            self.fileName, "SOURCES"
+        ):
             pfn = self.project.getMainScript(True)
             if pfn is not None:
-                files |= set(Utilities.getCoverageFileNames(pfn))
+                files.extend(
+                    [f for f in Utilities.getCoverageFileNames(pfn) if f not in files]
+                )
 
         # now check, if there are coverage files belonging to ourselves
         fn = self.getFileName()
         if fn is not None:
-            files |= set(Utilities.getCoverageFileNames(fn))
+            files.extend(
+                [f for f in Utilities.getCoverageFileNames(fn) if f not in files]
+            )
 
         files = list(files)
         if files:
@@ -6162,11 +6184,11 @@
         """
         Private method to handle the code coverage context menu action.
         """
+        from eric7.DataViews.PyCoverageDialog import PyCoverageDialog
+
         fn = self.__getCodeCoverageFile()
         self.__coverageFile = fn
         if fn:
-            from eric7.DataViews.PyCoverageDialog import PyCoverageDialog
-
             self.codecoverage = PyCoverageDialog()
             self.codecoverage.show()
             self.codecoverage.start(fn, self.fileName)
@@ -6190,14 +6212,16 @@
             (defaults to None)
         @type str (optional)
         """
+        from eric7.DebugClients.Python.coverage import (  # __IGNORE_WARNING_I102__
+            Coverage,
+        )
+
         self.__codeCoverageHideAnnotations()
 
         fn = coverageFile if bool(coverageFile) else self.__getCodeCoverageFile()
         self.__coverageFile = fn
 
         if fn:
-            from coverage import Coverage
-
             cover = Coverage(data_file=fn)
             cover.load()
             missing = cover.analysis2(self.fileName)[3]
@@ -6297,19 +6321,27 @@
         """
         Private method to handle the show profile data context menu action.
         """
-        files = set()
+        from eric7.DataViews.PyProfileDialog import PyProfileDialog
+
+        files = []
 
         # first check if the file belongs to a project and there is
         # a project profile file
-        if self.project.isOpen() and self.project.isProjectSource(self.fileName):
+        if self.project.isOpen() and self.project.isProjectCategory(
+            self.fileName, "SOURCES"
+        ):
             fn = self.project.getMainScript(True)
             if fn is not None:
-                files |= set(Utilities.getProfileFileNames(fn))
+                files.extend(
+                    [f for f in Utilities.getProfileFileNames(fn) if f not in files]
+                )
 
         # now check, if there are profile files belonging to ourselves
         fn = self.getFileName()
         if fn is not None:
-            files |= set(Utilities.getProfileFileNames(fn))
+            files.extend(
+                [f for f in Utilities.getProfileFileNames(fn) if f not in files]
+            )
 
         files = list(files)
         if files:
@@ -6329,8 +6361,6 @@
         else:
             return
 
-        from eric7.DataViews.PyProfileDialog import PyProfileDialog
-
         self.profiledata = PyProfileDialog()
         self.profiledata.show()
         self.profiledata.start(fn, self.fileName)
@@ -7893,7 +7923,7 @@
         """
         Public slot to handle the opening of a project.
         """
-        if self.fileName and self.project.isProjectSource(self.fileName):
+        if self.fileName and self.project.isProjectCategory(self.fileName, "SOURCES"):
             self.project.projectPropertiesChanged.connect(
                 self.__projectPropertiesChanged
             )
@@ -7966,7 +7996,7 @@
         if (
             self.fileName
             and self.project.isOpen()
-            and self.project.isProjectSource(self.fileName)
+            and self.project.isProjectCategory(self.fileName, "SOURCES")
         ):
             pwl, pel = self.project.getProjectDictionaries()
             self.__setSpellingLanguage(
@@ -8038,10 +8068,10 @@
         """
         Public slot to perform an interactive spell check of the document.
         """
+        from .SpellCheckingDialog import SpellCheckingDialog
+
         if self.spell:
             cline, cindex = self.getCursorPosition()
-            from .SpellCheckingDialog import SpellCheckingDialog
-
             dlg = SpellCheckingDialog(self.spell, 0, self.length(), self)
             dlg.exec()
             self.setCursorPosition(cline, cindex)
@@ -8479,11 +8509,11 @@
         """
         Public slot to sort the lines spanned by a rectangular selection.
         """
+        from .SortOptionsDialog import SortOptionsDialog
+
         if not self.selectionIsRectangle():
             return
 
-        from .SortOptionsDialog import SortOptionsDialog
-
         dlg = SortOptionsDialog()
         if dlg.exec() == QDialog.DialogCode.Accepted:
             ascending, alnum, caseSensitive = dlg.getData()
@@ -8900,9 +8930,9 @@
         @return reference to the docstring generator
         @rtype BaseDocstringGenerator
         """
+        from . import DocstringGenerator
+
         if self.__docstringGenerator is None:
-            from . import DocstringGenerator
-
             self.__docstringGenerator = DocstringGenerator.getDocstringGenerator(self)
 
         return self.__docstringGenerator
@@ -8963,7 +8993,7 @@
 
         generator = self.getDocstringGenerator()
         if generator.hasFunctionDefinition(cursorPosition):
-            from .DocstringGenerator.BaseDocstringGenerator import (
+            from .DocstringGenerator.BaseDocstringGenerator import (  # __IGNORE_WARNING__
                 DocstringMenuForEnterOnly,
             )
 
@@ -9061,7 +9091,7 @@
             self.__cancelMouseHoverHelp()
 
     #######################################################################
-    ## Methods implementing the Black code formatting interface
+    ## Methods implementing the code formatting interface
     #######################################################################
 
     def __performFormatWithBlack(self, action):
@@ -9089,7 +9119,7 @@
             withProject = (
                 self.fileName
                 and self.project.isOpen()
-                and self.project.isProjectSource(self.fileName)
+                and self.project.isProjectCategory(self.fileName, "SOURCES")
             )
             dlg = BlackConfigurationDialog(withProject=withProject)
             if dlg.exec() == QDialog.DialogCode.Accepted:
@@ -9099,3 +9129,42 @@
                     config, [self.fileName], project=self.project, action=action
                 )
                 formattingDialog.exec()
+
+    def __performImportSortingWithIsort(self, action):
+        """
+        Private method to sort the import statements using the 'isort' tool.
+
+        Following actions are supported.
+        <ul>
+        <li>IsortFormattingAction.Sort - the import statement sorting is performed</li>
+        <li>IsortFormattingAction.Check - a check is performed, if import statement
+            resorting is necessary</li>
+        <li>IsortFormattingAction.Diff - a unified diff of potential import statement
+            changes is generated</li>
+        </ul>
+
+        @param action sorting operation to be performed
+        @type IsortFormattingAction
+        """
+        from eric7.CodeFormatting.IsortConfigurationDialog import (
+            IsortConfigurationDialog,
+        )
+        from eric7.CodeFormatting.IsortFormattingDialog import IsortFormattingDialog
+
+        if not self.isModified() or self.saveFile():
+            withProject = (
+                self.fileName
+                and self.project.isOpen()
+                and self.project.isProjectCategory(self.fileName, "SOURCES")
+            )
+            dlg = IsortConfigurationDialog(withProject=withProject)
+            if dlg.exec() == QDialog.DialogCode.Accepted:
+                config = dlg.getConfiguration()
+
+                formattingDialog = IsortFormattingDialog(
+                    config,
+                    [self.fileName],
+                    project=self.project if withProject else None,
+                    action=action,
+                )
+                formattingDialog.exec()

eric ide

mercurial