RefactoringRope/CodeAssistServer.py

Wed, 27 Sep 2017 18:42:35 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 27 Sep 2017 18:42:35 +0200
branch
server_client_variant
changeset 199
ae2ad82725b0
parent 198
898d8b4187de
child 200
1584892147ef
permissions
-rw-r--r--

Implemented the distributed code assist calltips method.

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

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

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

from __future__ import unicode_literals

import os
import sys

from PyQt5.QtCore import pyqtSlot, QCoreApplication, QTimer

# TODO: eliminate this
sys.path.insert(0, os.path.dirname(__file__))

from JsonServer import JsonServer

import Globals
import Preferences


class CodeAssistServer(JsonServer):
    """
    Class implementing the autocompletion interface to rope.
    """
    def __init__(self, plugin, parent=None):
        """
        Constructor
        
        @param plugin reference to the plugin object
        @type RefactoringRopePlugin
        @param parent parent
        @type QObject
        """
        super(CodeAssistServer, self).__init__(
            "CodeAssistServer", multiplex=True, parent=parent)
        
        self.__plugin = plugin
        self.__ui = parent
        
        self.__editorLanguageMapping = {}
        
        # attributes to store the resuls of the client side
        self.__completions = None
        self.__calltips = None
        
        self.__methodMapping = {
            "CompletionsResult": self.__processCompletionsResult,
            "CallTipsResult": self.__processCallTipsResult,
        }
        
        # Python 2
        interpreter = Preferences.getDebugger("PythonInterpreter")
        self.__startCodeAssistClient(interpreter, "Python2")
        
        # Python 3
        interpreter = Preferences.getDebugger("Python3Interpreter")
        self.__startCodeAssistClient(interpreter, "Python3")
    
    def __updateEditorLanguageMapping(self):
        """
        Private method to update the editor language to connection mapping.
        """
        self.__editorLanguageMapping = {}
        for name in self.connectionNames():
            if name == "Python2":
                self.__editorLanguageMapping.update({
                    "Python": "Python2",
                    "Python2": "Python2",
                    "Pygments|Python": "Python2",
                })
            elif name == "Python3":
                self.__editorLanguageMapping.update({
                    "Python3": "Python3",
                    "Pygments|Python 3": "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 getCompletions(self, editor):
        """
        Public method to calculate the possible completions.
        
        @param editor reference to the editor object, that called this method
        @type QScintilla.Editor
        @return list of proposals
        @rtype list of str
        """
        # reset the completions buffer
        self.__completions = None
        
        language = editor.getLanguage()
        if language not in self.__editorLanguageMapping:
            return []
        idString = self.__editorLanguageMapping[language]
        
        filename = editor.getFileName()
        line, index = editor.getCursorPosition()
        source = editor.text()
        offset = len("".join(source.splitlines(True)[:line])) + index
        maxfixes = self.__plugin.getPreferences("MaxFixes")
        
        self.sendJson("getCompletions", {
            "FileName": filename,
            "Source": source,
            "Offset": offset,
            "MaxFixes": maxfixes,
        }, idString=idString)
        
        # emulate the synchronous behaviour
        timer = QTimer()
        timer.setSingleShot(True)
        timer.start(5000)           # 5s timeout
        while self.__completions is None and timer.isActive():
            QCoreApplication.processEvents()
        
        return [] if self.__completions is None else self.__completions
    
    def __processCompletionsResult(self, result):
        """
        Private method to process the completions sent by the client.
        
        @param result dictionary containg the result sent by the client
        @type dict
        """
        if "Error" in result:
            self.__completions = []
        else:
            self.__completions = result["Completions"]
    
    def getCallTips(self, pos, editor):
        """
        Public method to calculate calltips.
        
        @param pos position in the text for the calltip
        @type int
        @param editor reference to the editor object, that called this method
        @type QScintilla.Editor
        @return list of possible calltips
        @rtype list of str
        """
        # reset the calltips buffer
        self.__calltips = None
        
        language = editor.getLanguage()
        if language not in self.__editorLanguageMapping:
            return []
        idString = self.__editorLanguageMapping[language]
        
        filename = editor.getFileName()
        source = editor.text()
        line, index = editor.lineIndexFromPosition(pos)
        offset = len("".join(source.splitlines(True)[:line])) + index
        maxfixes = self.__plugin.getPreferences("CalltipsMaxFixes")
        
        self.sendJson("getCallTips", {
            "FileName": filename,
            "Source": source,
            "Offset": offset,
            "MaxFixes": maxfixes,
        }, 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()
        
        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 containg the result sent by the client
        @type dict
        """
        if "Error" in result:
            self.__calltips = []
        else:
            self.__calltips = result["CallTips"]
    
    # TODO: port this to the distributed variant
    def reportChanged(self, filename, oldSource):
        """
        Public slot to report some changed sources.
        
        @param filename file name of the changed source (string)
        @param oldSource source code before the change (string)
        """
        print("code assist: reportChanged")
##        try:
##            rope.base.libutils.report_change(
##                self.__project, filename, oldSource)
##        except RuntimeError:
##            # this could come from trying to do PyQt4/PyQt5 mixed stuff
##            # simply ignore it
##            pass
    
    #######################################################################
    ## Methods below handle the network connection
    #######################################################################
    
    def handleCall(self, method, params):
        """
        Public method to handle a method call from the client.
        
        Note: This is an empty implementation that must be overridden in
        derived classes.
        
        @param method requested method name
        @type str
        @param params dictionary with method specific parameters
        @type dict
        """
        self.__methodMapping[method](params)
    
    def __startCodeAssistClient(self, interpreter, idString):
        """
        Private method to start the code assist client with the given
        interpreter.
        
        @param interpreter interpreter to be used for the code assist client
        @type str
        @param idString id of the client to be started
        @type str
        @return flag indicating a successful client start
        @rtype bool
        """
        if interpreter:
            client = os.path.join(os.path.dirname(__file__),
                                  "CodeAssistClient.py")
            ok = self.startClient(interpreter, client,
                                  [Globals.getConfigDir()],
                                  idString=idString)
            if not ok:
                self.__ui.appendToStderr(self.tr(
                    "'{0}' is not supported because the configured interpreter"
                    " could not be started."
                ).format(idString))
        else:
            self.__ui.appendToStderr(self.tr(
                "'{0}' is not supported because no suitable interpreter is"
                " configured."
            ).format(idString))
    
    @pyqtSlot()
    def handleNewConnection(self):
        """
        Public slot for new incoming connections from a client.
        """
        super(CodeAssistServer, self).handleNewConnection()
        self.__updateEditorLanguageMapping()
    
    def activate(self):
        """
        Public method to activate the code assist server.
        """
        pass
    
    def deactivate(self):
        """
        Public method to deactivate the code assist server.
        """
        """
        Public method to shut down the code assist server.
        """
        self.stopAllClients()

eric ide

mercurial