--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PluginPyright.py Tue Nov 07 15:17:48 2023 +0100 @@ -0,0 +1,351 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the pyright plug-in. +""" + +import contextlib +import os + +from PyQt6.QtCore import QObject, QTranslator, pyqtSlot + +from eric7 import Preferences +from eric7.EricGui.EricAction import EricAction +from eric7.EricWidgets.EricApplication import ericApp +from eric7.Project.ProjectBrowserModel import ProjectBrowserFileItem + +# Start-Of-Header +__header__ = { + "name": "Python Typing Checker Plug-in", + "author": "Detlev Offenbach <detlev@die-offenbachs.de>", + "autoactivate": True, + "deactivateable": True, + "version": "10.0.0", + "className": "PyrightPlugin", + "packageName": "PyrightChecker", + "shortDescription": "Plug-in to check Python sources for typing issues.", + "longDescription": ("""Plug-in to check Python sources for typing issues."""), + "needsRestart": False, + "pyqtApi": 2, +} +# End-Of-Header + +error = "" # noqa: U200 + + +def prepareUninstall(): + """ + Module function to prepare for an uninstallation. + """ + Preferences.Prefs.settings.remove(PyrightPlugin.PreferencesKey) + + +class PyrightPlugin(QObject): + """ + Class documentation goes here. + """ + + PreferencesKey = "Pyright" + + def __init__(self, ui): + """ + Constructor + + @param ui reference to the user interface object + @type UI.UserInterface + """ + super().__init__(ui) + self.__ui = ui + self.__initialize() + + def __initialize(self): + """ + Private slot to (re)initialize the plug-in. + """ + self.__projectAct = None + self.__projectPyrightCheckerDialog = None + + self.__projectBrowserAct = None + self.__projectBrowserMenu = None + self.__projectBrowserPyrightCheckerDialog = None + + self.__editors = [] + self.__editorAct = None + self.__editorPyrightCheckerDialog = None + + def activate(self): + """ + Public method to activate this plug-in. + + @return tuple of None and activation status + @rtype bool + """ + global error + error = "" # clear previous error + + menu = ericApp().getObject("Project").getMenu("Checks") + if menu: + self.__projectAct = EricAction( + self.tr("Static Type Check"), + self.tr("Static &Typing..."), + 0, + 0, + self, + "project_check_pyright", + ) + self.__projectAct.setStatusTip(self.tr("Check for typing issues")) + self.__projectAct.setWhatsThis( + self.tr( + """<b>Static Type Check...</b>""" + """<p>This checks a Python project for static typing issues.</p>""" + ) + ) + self.__projectAct.triggered.connect(self.__projectPyrightCheck) + ericApp().getObject("Project").addEricActions([self.__projectAct]) + menu.addAction(self.__projectAct) + + self.__editorAct = EricAction( + self.tr("Static Type Check"), self.tr("Static &Typing..."), 0, 0, self, "" + ) + self.__editorAct.setWhatsThis( + self.tr( + """<b>Static Type Check...</b>""" + """<p>This checks a Python file for static typing issues.</p>""" + ) + ) + self.__editorAct.triggered.connect(self.__editorPyrightCheck) + + ericApp().getObject("Project").showMenu.connect(self.__projectShowMenu) + ericApp().getObject("ProjectBrowser").getProjectBrowser( + "sources" + ).showMenu.connect(self.__projectBrowserShowMenu) + ericApp().getObject("ViewManager").editorOpenedEd.connect(self.__editorOpened) + ericApp().getObject("ViewManager").editorClosedEd.connect(self.__editorClosed) + + for editor in ericApp().getObject("ViewManager").getOpenEditors(): + self.__editorOpened(editor) + + return None, True + + def deactivate(self): + """ + Public method to deactivate this plug-in. + """ + ericApp().getObject("Project").showMenu.disconnect(self.__projectShowMenu) + ericApp().getObject("ProjectBrowser").getProjectBrowser( + "sources" + ).showMenu.disconnect(self.__projectBrowserShowMenu) + + menu = ericApp().getObject("Project").getMenu("Checks") + if menu is not None and self.__projectAct is not None: + menu.removeAction(self.__projectAct) + ericApp().getObject("Project").removeEricActions([self.__projectAct]) + + if self.__projectBrowserMenu and self.__projectBrowserAct: + self.__projectBrowserMenu.removeAction(self.__projectBrowserAct) + + for editor in self.__editors: + editor.showMenu.disconnect(self.__editorShowMenu) + menu = editor.getMenu("Checks") + if menu is not None: + menu.removeAction(self.__editorAct) + + self.__initialize() + + def __loadTranslator(self): + """ + Private method to load the translation file. + """ + if self.__ui is not None: + loc = self.__ui.getLocale() + if loc and loc != "C": + locale_dir = os.path.join( + os.path.dirname(__file__), "PyrightChecker", "i18n" + ) + translation = "pyright_{0}".format(loc) + translator = QTranslator(None) + loaded = translator.load(translation, locale_dir) + if loaded: + self.__translator = translator + ericApp().installTranslator(self.__translator) + else: + print( + "Warning: translation file '{0}' could not be" + " loaded.".format(translation) + ) + print("Using default.") + + def __projectShowMenu( + self, + menuName, + menu, # noqa: U100 + ): + """ + Private slot called, when the the project menu or a submenu is + about to be shown. + + @param menuName name of the menu to be shown + @type str + @param menu reference to the menu + @type QMenu + """ + if menuName == "Check" and self.__projectAct is not None: + self.__projectAct.setEnabled( + ericApp().getObject("Project").getProjectLanguage() == "Python3" + ) + + def __projectBrowserShowMenu(self, menuName, menu): + """ + Private slot called, when the the project browser menu or a submenu is + about to be shown. + + @param menuName name of the menu to be shown + @type str + @param menu reference to the menu + @type QMenu + """ + if ( + menuName == "Checks" + and ericApp().getObject("Project").getProjectLanguage() == "Python3" + ): + self.__projectBrowserMenu = menu + if self.__projectBrowserAct is None: + self.__projectBrowserAct = EricAction( + self.tr("Static Type Check"), + self.tr("Static &Typing..."), + 0, + 0, + self, + "", + ) + self.__projectBrowserAct.setWhatsThis( + self.tr( + """<b>Static Type Check...</b>""" + """<p>This checks Python files for static typing issues.</p>""" + ) + ) + self.__projectBrowserAct.triggered.connect( + self.__projectBrowserPyrightCheck + ) + if self.__projectBrowserAct not in menu.actions(): + menu.addAction(self.__projectBrowserAct) + + @pyqtSlot() + def __projectPyrightCheck(self): + """ + Private slot used to check the project for static typing issues. + """ + from PyrightChecker.PyrightCheckerDialog import PyrightCheckerDialog + + project = ericApp().getObject("Project") + project.saveAllScripts() + + if self.__projectPyrightCheckerDialog is None: + self.__projectPyrightCheckerDialog = PyrightCheckerDialog(self) + self.__projectPyrightCheckerDialog.show() + self.__projectPyrightCheckerDialog.prepare(project) + + @pyqtSlot() + def __projectBrowserPyrightCheck(self): + """ + Private method to handle the static typing check context menu action of + the project sources browser. + """ + from PyrightChecker.PyrightCheckerDialog import PyrightCheckerDialog + + browser = ericApp().getObject("ProjectBrowser").getProjectBrowser("sources") + if browser.getSelectedItemsCount([ProjectBrowserFileItem]) > 1: + fn = [] + for itm in browser.getSelectedItems([ProjectBrowserFileItem]): + fn.append(itm.fileName()) + else: + itm = browser.model().item(browser.currentIndex()) + try: + fn = [itm.fileName()] + except AttributeError: + fn = [itm.dirName()] + + self.__projectBrowserPyrightCheckerDialog = PyrightCheckerDialog(self) + self.__projectBrowserPyrightCheckerDialog.show() + self.__projectBrowserPyrightCheckerDialog.start(fn, save=True) + + def __editorOpened(self, editor): + """ + Private slot called, when a new editor was opened. + + @param editor reference to the new editor + @type QScintilla.Editor + """ + menu = editor.getMenu("Checks") + if menu is not None: + menu.addAction(self.__editorAct) + editor.showMenu.connect(self.__editorShowMenu) + self.__editors.append(editor) + + def __editorClosed(self, editor): + """ + Private slot called, when an editor was closed. + + @param editor reference to the editor + @type QScintilla.Editor + """ + with contextlib.suppress(ValueError): + self.__editors.remove(editor) + + def __editorShowMenu(self, menuName, menu, editor): + """ + Private slot called, when the the editor context menu or a submenu is + about to be shown. + + @param menuName name of the menu to be shown + @type str + @param menu reference to the menu + @type QMenu + @param editor reference to the editor + @type QScintilla.Editor + """ + if menuName == "Checks": + if self.__editorAct not in menu.actions(): + menu.addAction(self.__editorAct) + self.__editorAct.setEnabled(editor.isPyFile()) + + def __editorPyrightCheck(self): + """ + Private slot to handle the code style check context menu action + of the editors. + """ + from PyrightChecker.PyrightCheckerDialog import PyrightCheckerDialog + + editor = ericApp().getObject("ViewManager").activeWindow() + if ( + editor is not None + and editor.checkDirty() + and editor.getFileName() is not None + ): + self.__editorPyrightCheckerDialog = PyrightCheckerDialog(self) + self.__editorPyrightCheckerDialog.show() + self.__editorPyrightCheckerDialog.start([editor.getFileName()], save=True) + + +def installDependencies(pipInstall): + """ + Function to install dependencies of this plug-in. + + @param pipInstall function to be called with a list of package names. + @type function + """ + try: + import pyright # __IGNORE_WARNING__ + except ImportError: + pipInstall(["pyright"]) + try: + import tomlkit # __IGNORE_WARNING__ + except ImportError: + pipInstall(["tomlkit"]) + + +# +# eflag: noqa = M801