--- 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()