eric7/JediInterface/JediServer.py

Tue, 08 Feb 2022 16:21:09 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 08 Feb 2022 16:21:09 +0100
branch
eric7
changeset 8943
23f9c7b9e18e
parent 8920
fe16c3cf165d
child 9056
af7c8c7b7c62
child 9067
abad8563ee7d
child 9111
4ac66b6c33a4
permissions
-rw-r--r--

Implemented some performance improvements.

# -*- coding: utf-8 -*-

# Copyright (c) 2015 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the autocompletion interface to jedi.
"""

import contextlib
import os
import uuid

from PyQt6.QtCore import pyqtSlot, QCoreApplication, QTimer, QThread
from PyQt6.QtWidgets import QInputDialog, QLineEdit, QDialog

from EricWidgets.EricApplication import ericApp
from EricWidgets import EricMessageBox

from EricNetwork.EricJsonServer import EricJsonServer

from QScintilla.Editor import Editor

import Preferences
import Globals

from .RefactoringPreviewDialog import RefactoringPreviewDialog


class JediServer(EricJsonServer):
    """
    Class implementing the interface to the jedi library.
    """
    IdProject = "Project"
    
    PictureIDs = {
        "class": "?{0}".format(Editor.ClassID),
        "_class": "?{0}".format(Editor.ClassProtectedID),
        "__class": "?{0}".format(Editor.ClassPrivateID),
        "instance": "?{0}".format(Editor.ClassID),
        "_instance": "?{0}".format(Editor.ClassProtectedID),
        "__instance": "?{0}".format(Editor.ClassPrivateID),
        "function": "?{0}".format(Editor.MethodID),
        "_function": "?{0}".format(Editor.MethodProtectedID),
        "__function": "?{0}".format(Editor.MethodPrivateID),
        "module": "?{0}".format(Editor.ModuleID),
        "_module": "?{0}".format(Editor.ModuleID),
        "__module": "?{0}".format(Editor.ModuleID),
        "param": "?{0}".format(Editor.AttributeID),
        "_param": "?{0}".format(Editor.AttributeProtectedID),
        "__param": "?{0}".format(Editor.AttributePrivateID),
        "statement": "?{0}".format(Editor.AttributeID),
        "_statement": "?{0}".format(Editor.AttributeProtectedID),
        "__statement": "?{0}".format(Editor.AttributePrivateID),
        "import": "",
        "None": "",
    }
    
    def __init__(self, viewManager, project, ui):
        """
        Constructor
        
        @param viewManager reference to the viewmanager object
        @type ViewManager
        @param project reference to the project object
        @type Project
        @param ui reference to the user interface
        @type UserInterface
        """
        super().__init__(
            "JediServer", multiplex=True, parent=ui)
        
        self.__ui = ui
        self.__vm = viewManager
        self.__ericProject = project
        
        self.__editorLanguageMapping = {}
        
        self.__documentationViewer = None
        
        # attributes to store the resuls of the client side
        self.__completions = None
        self.__calltips = None
        
        self.__methodMapping = {
            "CompletionsResult": self.__processCompletionsResult,
            "CallTipsResult": self.__processCallTipsResult,
            "DocumentationResult": self.__processDocumentationResult,
            "HoverHelpResult": self.__processHoverHelpResult,
            "GotoDefinitionResult": self.__processGotoDefinitionResult,
            "GotoReferencesResult": self.__processGotoReferencesResult,
            
            "RefactoringDiff": self.__showRefactoringDiff,
            "RefactoringApplyResult": self.__checkRefactoringResult,
            
            "ClientException": self.__processClientException,
        }
        
        # temporary store for editor references indexed by Uuid
        self.__editors = {}
        
        # Python 3
        self.__ensureActive("Python3")
    
    def __updateEditorLanguageMapping(self):
        """
        Private method to update the editor language to connection mapping.
        """
        self.__editorLanguageMapping = {}
        for name in self.connectionNames():
            if name == "Python3":
                self.__editorLanguageMapping.update({
                    "Python3": "Python3",
                    "MicroPython": "Python3",
                    "Pygments|Python": "Python3",
                    "Pygments|Python 2.x": "Python3",
                    "Cython": "Python3",
                })
    
    def isSupportedLanguage(self, language):
        """
        Public method to check, if the given language is supported.
        
        @param language editor programming language to check
        @type str
        @return flag indicating the support status
        @rtype bool
        """
        return language in self.__editorLanguageMapping
    
    def __idString(self, editor):
        """
        Private method to determine the ID string for the back-end.
        
        @param editor reference to the editor to determine the ID string for
        @type Editor
        @return ID string
        @rtype str
        """
        idString = ""
        
        language = editor.getLanguage()
        if (
            self.__ericProject.isOpen() and
            self.__ericProject.getProjectLanguage() == language
        ):
            filename = editor.getFileName()
            if self.__ericProject.isProjectSource(filename):
                idString = JediServer.IdProject
        
        if not idString and language in self.__editorLanguageMapping:
            idString = self.__editorLanguageMapping[language]
        
        return idString
    
    def __prepareData(self, editor):
        """
        Private method to gather data about current cursor position.
        
        @param editor reference to the editor object, that called this method
        @type Editor
        @return tuple of filename, line, index, source
        @rtype tuple (str, int, int, str)
        """
        filename = editor.getFileName()
        line, index = editor.getCursorPosition()
        line += 1       # jedi line numbers are 1 based
        source = editor.text()
        return filename, line, index, source
    
    def requestCompletions(self, editor, context, acText):
        """
        Public method to request a list of possible completions.
        
        @param editor reference to the editor object, that called this method
        @type Editor
        @param context flag indicating to autocomplete a context
        @type bool
        @param acText text to be completed
        @type str
        """
        if not Preferences.getJedi("JediCompletionsEnabled"):
            return
        
        idString = self.__idString(editor)
        if not idString:
            return
        
        filename, line, index, source = self.__prepareData(editor)
        fuzzy = Preferences.getJedi("JediFuzzyCompletionsEnabled")
        
        self.__ensureActive(idString)
        
        self.sendJson("getCompletions", {
            "FileName": filename,
            "Source": source,
            "Line": line,
            "Index": index,
            "Fuzzy": fuzzy,
            "CompletionText": acText,
        }, idString=idString)
    
    def __processCompletionsResult(self, result):
        """
        Private method to process the completions sent by the client.
        
        @param result dictionary containing the result sent by the client
        @type dict
        """
        names = []
        for completion in result["Completions"]:
            name = completion['Name']
            context = completion['FullName']
            if context:
                if context.endswith(".{0}".format(name)):
                    context = context.rsplit(".", 1)[0]
                name = "{0} ({1})".format(name, context)
            
            name += JediServer.PictureIDs.get(completion['CompletionType'], '')
            names.append(name)
        
        if "Error" not in result:
            editor = self.__vm.getOpenEditor(result["FileName"])
            if editor is not None:
                editor.completionsListReady(names,
                                            result["CompletionText"])
    
    def getCallTips(self, editor, pos, commas):
        """
        Public method to calculate calltips.
        
        @param editor reference to the editor object, that called this method
        @type Editor
        @param pos position in the text for the calltip
        @type int
        @param commas minimum number of commas contained in the calltip
        @type int
        @return list of possible calltips
        @rtype list of str
        """
        if not Preferences.getJedi("JediCalltipsEnabled"):
            return []
        
        # reset the calltips buffer
        self.__calltips = None
        
        idString = self.__idString(editor)
        if not idString:
            return []
        
        filename, line, index, source = self.__prepareData(editor)
        
        self.__ensureActive(idString)
        self.sendJson("getCallTips", {
            "FileName": filename,
            "Source": source,
            "Line": line,
            "Index": index,
        }, idString=idString)
        
        # emulate the synchronous behaviour
        timer = QTimer()
        timer.setSingleShot(True)
        timer.start(5000)           # 5s timeout
        while self.__calltips is None and timer.isActive():
            QCoreApplication.processEvents()
            QThread.msleep(100)
        
        return [] if self.__calltips is None else self.__calltips
    
    def __processCallTipsResult(self, result):
        """
        Private method to process the calltips sent by the client.
        
        @param result dictionary containing the result sent by the client
        @type dict
        """
        if "Error" in result:
            self.__calltips = []
        else:
            self.__calltips = result["CallTips"]
    
    def requestCodeDocumentation(self, editor):
        """
        Public method to request source code documentation for the given
        editor.
        
        @param editor reference to the editor to get source code documentation
            for
        @type Editor
        """
        if self.__documentationViewer is None:
            return
        
        idString = self.__idString(editor)
        
        if not idString:
            language = editor.getLanguage()
            warning = (
                self.tr("Language <b>{0}</b> is not supported.")
                .format(language)
            )
            self.__documentationViewer.documentationReady(
                warning, isWarning=True)
            return
        
        filename, line, index, source = self.__prepareData(editor)
        sourceLines = source.splitlines()
        # Correct index if cursor is standing after an opening bracket
        if line > 0 and index > 0 and sourceLines[line - 1][index - 1] == '(':
            index -= 1
        
        self.__ensureActive(idString)
        self.sendJson("getDocumentation", {
            "FileName": filename,
            "Source": source,
            "Line": line,
            "Index": index,
        }, idString=idString)
    
    def __processDocumentationResult(self, result):
        """
        Private method to process the documentation sent by the client.
        
        @param result dictionary containing the result sent by the client
        @type dict with keys 'name', 'module', 'argspec', 'docstring'
        """
        if self.__documentationViewer is None:
            return
    
        docu = None
        
        if "Error" not in result:
            docu = result["DocumentationDict"]
            docu["note"] = (
                self.tr("Present in <i>{0}</i> module")
                .format(docu["module"]))
        
        if docu is None:
            msg = self.tr("No documentation available.")
            self.__documentationViewer.documentationReady(
                msg, isDocWarning=True)
        else:
            self.__documentationViewer.documentationReady(docu)
    
    def gotoDefinition(self, editor):
        """
        Public slot to find the definition for the word at the cursor position
        and go to it.
        
        Note: This is executed upon a mouse click sequence.
        
        @param editor reference to the calling editor
        @type Editor
        """
        if not Preferences.getJedi("MouseClickEnabled"):
            return
        
        idString = self.__idString(editor)
        if not idString:
            return
        
        filename, line, index, source = self.__prepareData(editor)
        
        self.__ensureActive(idString)
        
        euuid = str(uuid.uuid4())
        self.__editors[euuid] = editor
        
        self.sendJson("gotoDefinition", {
            "FileName": filename,
            "Source": source,
            "Line": line,
            "Index": index,
            "Uuid": euuid,
        }, idString=idString)
    
    def __processGotoDefinitionResult(self, result):
        """
        Private method callback for the goto definition result.
        
        @param result dictionary containing the result data
        @type dict
        """
        euuid = result["Uuid"]
        if "Error" not in result:
            # ignore errors silently
            location = result["GotoDefinitionDict"]
            if location:
                self.__vm.openSourceFile(location["ModulePath"],
                                         location["Line"],
                                         addNext=True)
            else:
                ericApp().getObject("UserInterface").statusBar().showMessage(
                    self.tr('Jedi: No definition found'), 5000)
        
        with contextlib.suppress(KeyError):
            del self.__editors[euuid]
    
    def __processGotoReferencesResult(self, result):
        """
        Private method callback for the goto references result.
        
        @param result dictionary containing the result data
        @type dict
        """
        euuid = result["Uuid"]
        with contextlib.suppress(ImportError):
            from QScintilla.Editor import ReferenceItem
            
            if "Error" not in result:
                # ignore errors silently
                references = result["GotoReferencesList"]
                if references:
                    try:
                        editor = self.__editors[euuid]
                    except KeyError:
                        editor = None
                    if editor is not None:
                        referenceItemsList = [
                            ReferenceItem(
                                modulePath=ref["ModulePath"],
                                codeLine=ref["Code"],
                                line=ref["Line"],
                                column=ref["Column"],
                            ) for ref in references
                        ]
                        editor.gotoReferenceHandler(referenceItemsList)
        
        with contextlib.suppress(KeyError):
            del self.__editors[euuid]
    
    def hoverHelp(self, editor, line, index):
        """
        Public method to initiate the display of mouse hover help.
        
        @param editor reference to the calling editor
        @type Editor
        @param line line number (zero based)
        @type int
        @param index index within the line (zero based)
        @type int
        """
        idString = self.__idString(editor)
        if not idString:
            return
        
        filename = editor.getFileName()
        line += 1       # jedi line numbers are 1 based
        source = editor.text()
        
        self.__ensureActive(idString)
        
        euuid = str(uuid.uuid4())
        self.__editors[euuid] = editor
        
        self.sendJson("hoverHelp", {
            "FileName": filename,
            "Source": source,
            "Line": line,
            "Index": index,
            "Uuid": euuid,
        }, idString=idString)
    
    def __processHoverHelpResult(self, result):
        """
        Private method callback for the goto definition result.
        
        @param result dictionary containing the result data
        @type dict
        """
        euuid = result["Uuid"]
        if "Error" not in result:
            # ignore errors silently
            helpText = result["HoverHelp"]
            if helpText:
                with contextlib.suppress(KeyError):
                    self.__editors[euuid].showMouseHoverHelpData(
                        result["Line"] - 1,
                        result["Index"],
                        helpText
                    )
            else:
                ericApp().getObject("UserInterface").statusBar().showMessage(
                    self.tr('Jedi: No mouse hover help found'), 5000)
        
        with contextlib.suppress(KeyError):
            del self.__editors[euuid]
    
    #######################################################################
    ## Refactoring methods below
    #######################################################################
    
    @pyqtSlot()
    def refactoringRenameVariable(self):
        """
        Public slot to rename the selected variable.
        """
        editor = self.__vm.activeWindow()
        if editor:
            idString = self.__idString(editor)
            if not idString:
                return
            
            newName, ok = QInputDialog.getText(
                None,
                self.tr("Rename Variable"),
                self.tr("Enter the new name for the variable:"),
                QLineEdit.EchoMode.Normal
            )
            
            if ok and newName:
                filename = editor.getFileName()
                line, index = editor.getCursorPosition()
                source = editor.text()
                
                self.__ensureActive(idString)
                
                euuid = str(uuid.uuid4())
                self.__editors[euuid] = editor
                
                self.sendJson("renameVariable", {
                    "FileName": filename,
                    "Source": source,
                    "Line": line + 1,
                    "Index": index,
                    "Uuid": euuid,
                    "NewName": newName,
                }, idString=idString)
    
    @pyqtSlot()
    def refactoringExtractNewVariable(self):
        """
        Public slot to extract a statement to a new variable.
        """
        editor = self.__vm.activeWindow()
        if editor:
            idString = self.__idString(editor)
            if not idString:
                return
            
            newName, ok = QInputDialog.getText(
                None,
                self.tr("Extract Variable"),
                self.tr("Enter the name for the new variable:"),
                QLineEdit.EchoMode.Normal
            )
            
            if ok and newName:
                filename = editor.getFileName()
                sLine, sIndex, eLine, eIndex = editor.getSelection()
                source = editor.text()
                    
                self.__ensureActive(idString)
                
                euuid = str(uuid.uuid4())
                self.__editors[euuid] = editor
                
                self.sendJson("extractVariable", {
                    "FileName": filename,
                    "Source": source,
                    "Line": sLine + 1,
                    "Index": sIndex,
                    "EndLine": eLine + 1,
                    "EndIndex": eIndex,
                    "Uuid": euuid,
                    "NewName": newName,
                }, idString=idString)
    
    @pyqtSlot()
    def refactoringInlineVariable(self):
        """
        Public slot to inline the selected variable.
        
        Note: This is the opposite to Extract New Variable.
        """
        editor = self.__vm.activeWindow()
        if editor:
            idString = self.__idString(editor)
            if not idString:
                return
            
            filename = editor.getFileName()
            line, index = editor.getCursorPosition()
            source = editor.text()
            
            self.__ensureActive(idString)
            
            euuid = str(uuid.uuid4())
            self.__editors[euuid] = editor
            
            self.sendJson("inlineVariable", {
                "FileName": filename,
                "Source": source,
                "Line": line + 1,
                "Index": index,
                "Uuid": euuid,
            }, idString=idString)
    
    @pyqtSlot()
    def refactoringExtractFunction(self):
        """
        Public slot to extract an expression to a function.
        """
        editor = self.__vm.activeWindow()
        if editor:
            idString = self.__idString(editor)
            if not idString:
                return
            
            newName, ok = QInputDialog.getText(
                None,
                self.tr("Extract Function"),
                self.tr("Enter the name for the function:"),
                QLineEdit.EchoMode.Normal
            )
            
            if ok and newName:
                filename = editor.getFileName()
                sLine, sIndex, eLine, eIndex = editor.getSelection()
                source = editor.text()
                    
                self.__ensureActive(idString)
                
                euuid = str(uuid.uuid4())
                self.__editors[euuid] = editor
                
                self.sendJson("extractFunction", {
                    "FileName": filename,
                    "Source": source,
                    "Line": sLine + 1,
                    "Index": sIndex,
                    "EndLine": eLine + 1,
                    "EndIndex": eIndex,
                    "Uuid": euuid,
                    "NewName": newName,
                }, idString=idString)
    
    def __showRefactoringDiff(self, result):
        """
        Private method to show the diff of a refactoring.
        
        @param result dictionary containing the result data
        @type dict
        """
        if "Error" not in result:
            euuid = result["Uuid"]
            diff = result["Diff"]
            dlg = RefactoringPreviewDialog(self.tr("Rename Variable"), diff)
            if dlg.exec() == QDialog.DialogCode.Accepted:
                self.__applyRefactoring(euuid)
            else:
                self.__cancelRefactoring(euuid)
        else:
            EricMessageBox.critical(
                None,
                self.tr("Refactoring"),
                self.tr("<p>The refactoring could not be performed.</p>"
                        "<p>Reason: {0}</p>").format(result["ErrorString"])
            )
    
    def __applyRefactoring(self, uid):
        """
        Private method to apply a given refactoring.
        
        @param uid UID of the calculated refactoring
        @type str
        """
        with contextlib.suppress(KeyError):
            editor = self.__editors[uid]
            idString = self.__idString(editor)
            
            self.sendJson("applyRefactoring", {
                "Uuid": uid,
            }, idString=idString)
            
            del self.__editors[uid]
    
    def __cancelRefactoring(self, uid):
        """
        Private method to cancel a given refactoring.
        
        @param uid UID of the calculated refactoring
        @type str
        """
        with contextlib.suppress(KeyError):
            editor = self.__editors[uid]
            idString = self.__idString(editor)
            
            self.sendJson("cancelRefactoring", {
                "Uuid": uid,
            }, idString=idString)
            
            del self.__editors[uid]
    
    def __checkRefactoringResult(self, result):
        """
        Private method to check the refactoring result for errors.
        
        @param result dictionary containing the result data
        @type dict
        """
        if "Error" in result:
            EricMessageBox.critical(
                None,
                self.tr("Apply Refactoring"),
                self.tr("<p>The refactoring could not be applied.</p>"
                        "<p>Reason: {0}</p>").format(result["ErrorString"])
            )
    
    #######################################################################
    ## Methods below handle the network connection
    #######################################################################
    
    def handleCall(self, method, params):
        """
        Public method to handle a method call from the client.
        
        @param method requested method name
        @type str
        @param params dictionary with method specific parameters
        @type dict
        """
        self.__methodMapping[method](params)
    
    def __processClientException(self, params):
        """
        Private method to handle exceptions of the refactoring client.
        
        @param params dictionary containing the exception data
        @type dict
        """
        if params["ExceptionType"] == "ProtocolError":
            self.__ui.appendToStderr(
                self.tr("The data received from the Jedi server could not be"
                        " decoded. Please report this issue with the received"
                        " data to the eric bugs email address.\n"
                        "Error: {0}\n"
                        "Data:\n{1}\n").format(
                    params["ExceptionValue"],
                    params["ProtocolData"]))
        else:
            self.__ui.appendToStderr(
                self.tr("An exception happened in the Jedi client. Please"
                        " report it to the eric bugs email address.\n"
                        "Exception: {0}\n"
                        "Value: {1}\n"
                        "Traceback: {2}\n").format(
                    params["ExceptionType"],
                    params["ExceptionValue"],
                    params["Traceback"]))
    
    def __startJediClient(self, interpreter, idString, clientEnv):
        """
        Private method to start the Jedi client with the given interpreter.
        
        @param interpreter interpreter to be used for the Jedi client
        @type str
        @param idString id of the client to be started
        @type str
        @param clientEnv dictionary with environment variables to run the
            interpreter with
        @type dict
        @return flag indicating a successful start of the client
        @rtype bool
        """
        ok = False
        
        if interpreter:
            client = os.path.join(os.path.dirname(__file__),
                                  "JediClient.py")
            ok, exitCode = self.startClient(
                interpreter, client,
                [Globals.getPythonLibraryDirectory()],
                idString=idString, environment=clientEnv)
            if not ok:
                if exitCode == 42:
                    self.__ui.appendToStderr("JediServer: " + self.tr(
                        "The jedi and/or parso library is not installed.\n"
                    ))
                else:
                    self.__ui.appendToStderr("JediServer: " + self.tr(
                        "'{0}' is not supported because the configured"
                        " interpreter could not be started.\n"
                    ).format(idString))
        else:
            self.__ui.appendToStderr("JediServer: " + self.tr(
                "'{0}' is not supported because no suitable interpreter is"
                " configured.\n"
            ).format(idString))
        
        return ok
    
    def __ensureActive(self, idString):
        """
        Private method to ensure, that the requested client is active.
        
        A non-active client will be started.
        
        @param idString id of the client to be checked
        @type str
        @return flag indicating an active client
        @rtype bool
        """
        ok = idString in self.connectionNames()
        if not ok:
            # client is not running
            if idString == JediServer.IdProject:
                interpreter, clientEnv = self.__interpreterForProject()
            else:
                interpreter = ""
                venvName = ""
                clientEnv = os.environ.copy()
                if "PATH" in clientEnv:
                    clientEnv["PATH"] = self.__ui.getOriginalPathString()
                # new code using virtual environments
                venvManager = ericApp().getObject("VirtualEnvManager")
                if idString == "Python3":
                    venvName = Preferences.getDebugger("Python3VirtualEnv")
                    if not venvName:
                        venvName, _ = venvManager.getDefaultEnvironment()
                if venvName:
                    interpreter = venvManager.getVirtualenvInterpreter(
                        venvName)
                    execPath = venvManager.getVirtualenvExecPath(venvName)
                    
                    # build a suitable environment
                    if execPath:
                        if "PATH" in clientEnv:
                            clientEnv["PATH"] = os.pathsep.join(
                                [execPath, clientEnv["PATH"]])
                        else:
                            clientEnv["PATH"] = execPath
            if interpreter:
                ok = self.__startJediClient(interpreter, idString, clientEnv)
            else:
                ok = False
        return ok
    
    def __interpreterForProject(self):
        """
        Private method to determine the interpreter for the current project and
        the environment to run it.
        
        @return tuple containing the interpreter of the current project and the
            environment variables
        @rtype tuple of (str, dict)
        """
        projectLanguage = self.__ericProject.getProjectLanguage()
        interpreter = ""
        clientEnv = os.environ.copy()
        if "PATH" in clientEnv:
            clientEnv["PATH"] = self.__ui.getOriginalPathString()
        
        if (projectLanguage.startswith("Python") or
                projectLanguage == "MicroPython"):
            # new code using virtual environments
            venvManager = ericApp().getObject("VirtualEnvManager")
            
            # get virtual environment from project first
            venvName = self.__ericProject.getDebugProperty("VIRTUALENV")
            if not venvName:
                # get it from debugger settings next
                if projectLanguage in ("Python3", "MicroPython", "Cython"):
                    venvName = Preferences.getDebugger("Python3VirtualEnv")
                    if not venvName:
                        venvName, _ = venvManager.getDefaultEnvironment()
                else:
                    venvName = ""
            if venvName:
                interpreter = venvManager.getVirtualenvInterpreter(
                    venvName)
                execPath = venvManager.getVirtualenvExecPath(venvName)
                
                # build a suitable environment
                if execPath:
                    if "PATH" in clientEnv:
                        clientEnv["PATH"] = os.pathsep.join(
                            [execPath, clientEnv["PATH"]])
                    else:
                        clientEnv["PATH"] = execPath
        
        return interpreter, clientEnv
    
    @pyqtSlot()
    def handleNewConnection(self):
        """
        Public slot for new incoming connections from a client.
        """
        super().handleNewConnection()
        
        self.__updateEditorLanguageMapping()
    
    def activate(self):
        """
        Public method to activate the Jedi server.
        """
        self.__documentationViewer = self.__ui.documentationViewer()
        if self.__documentationViewer is not None:
            self.__documentationViewer.registerProvider(
                "jedi", self.tr("Jedi"), self.requestCodeDocumentation,
                self.isSupportedLanguage)
        
        self.__ericProject.projectOpened.connect(self.__projectOpened)
        self.__ericProject.projectClosed.connect(self.__projectClosed)
    
    def deactivate(self):
        """
        Public method to deactivate the code assist server.
        """
        """
        Public method to shut down the code assist server.
        """
        if self.__documentationViewer is not None:
            self.__documentationViewer.unregisterProvider("jedi")
        
        with contextlib.suppress(TypeError):
            self.__ericProject.projectOpened.disconnect(self.__projectOpened)
            self.__ericProject.projectClosed.disconnect(self.__projectClosed)
        
        self.stopAllClients()
    
    @pyqtSlot()
    def __projectOpened(self):
        """
        Private slot to handle the projectOpened signal.
        """
        self.__ensureActive(JediServer.IdProject)
        self.sendJson("openProject", {
            "ProjectPath": self.__ericProject.getProjectPath(),
        }, idString=JediServer.IdProject)
    
    @pyqtSlot()
    def __projectClosed(self):
        """
        Private slot to handle the projectClosed signal.
        """
        self.__ensureActive(JediServer.IdProject)
        self.sendJson("closeProject", {}, idString=JediServer.IdProject)
        
        self.stopClient(idString=JediServer.IdProject)

eric ide

mercurial