Sun, 05 Nov 2017 13:19:52 +0100
Fixed a few bugs.
# -*- coding: utf-8 -*- # Copyright (c) 2017 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing the code assist client interface to rope. """ from __future__ import unicode_literals import sys import os sys.path.insert(0, os.path.dirname(__file__)) if sys.version_info[0] >= 3: # TODO: unify this ## path = os.path.join(os.path.dirname(__file__), 'rope_py3') path = os.path.join(os.path.dirname(__file__), 'rope_py2') else: path = os.path.join(os.path.dirname(__file__), 'rope_py2') str = unicode # __IGNORE_WARNING__ sys.path.insert(0, path) import rope.base.libutils import rope.contrib.codeassist from JsonClient import JsonClient class CodeAssistClient(JsonClient): """ Class implementing the code assist client interface to rope. """ PictureIDs = { "class": "?{0}".format(1), # Editor.ClassID "_class": "?{0}".format(2), # Editor.ClassProtectedID "__class": "?{0}".format(3), # Editor.ClassPrivateID "instance": "?{0}".format(1), # Editor.ClassID "_instance": "?{0}".format(2), # Editor.ClassProtectedID "__instance": "?{0}".format(3), # Editor.ClassPrivateID "function": "?{0}".format(4), # Editor.MethodID "_function": "?{0}".format(5), # Editor.MethodProtectedID "__function": "?{0}".format(6), # Editor.MethodPrivateID "module": "?{0}".format(7), # Editor.AttributeID "_module": "?{0}".format(8), # Editor.AttributeProtectedID "__module": "?{0}".format(9), # Editor.AttributePrivateID "None": "", } # The various ID values are a copy of the ones found in the Editor # class in order to make this module/script independent from an # installed eric def __init__(self, host, port, idString, projectPath): """ Constructor @param host ip address the background service is listening @type str @param port port of the background service @type int @param idString assigned client id to be sent back to the server in order to identify the connection @type str @param projectPath path to the project @type str """ super(CodeAssistClient, self).__init__(host, port, idString) self.__methodMapping = { "getConfig": self.__getConfig, "configChanged": self.__configChanged, "closeProject": self.__closeProject, "getCompletions": self.__getCompletions, "getCallTips": self.__getCallTips, "getDocumentation": self.__getDocumentation, "reportChanged": self.__reportChanged, } self.__projectpath = projectPath self.__project = rope.base.project.Project(self.__projectpath) self.__project.validate(self.__project.root) self.__id = idString def handleCall(self, method, params): """ Public method to handle a method call from the server. @param method requested method name @type str @param params dictionary with method specific parameters @type dict """ self.__methodMapping[method](params) def __handleRopeError(self, err): """ Private method to process a rope error. @param err rope exception object @type Exception @return dictionary containing the error information @rtype dict """ ropeError = str(type(err)).split()[-1] ropeError = ropeError[1:-2].split('.')[-1] errorDict = { "Error": ropeError, "ErrorString": str(err), } if ropeError == 'ModuleSyntaxError': errorDict["ErrorFile"] = err.filename errorDict["ErrorLine"] = err.lineno return errorDict def __getConfig(self, params): """ Private method to send some configuration data to the server. @param params dictionary containing the method parameters sent by the server @type dict """ result = { "RopeFolderName": self.__project.ropefolder.real_path, "Id": self.__id, } self.sendJson("Config", result) def __configChanged(self, params): """ Private method to handle a change of the configuration file. @param params dictionary containing the method parameters sent by the server @type dict """ self.__project.close() self.__project = rope.base.project.Project(self.__projectpath) self.__project.validate(self.__project.root) def __closeProject(self, params): """ Private slot to validate the project. @param params dictionary containing the method parameters sent by the server @type dict """ self.__project.close() def __getCompletions(self, params): """ Private method to calculate possible completions. @param params dictionary containing the method parameters @type dict """ filename = params["FileName"] source = params["Source"] offset = params["Offset"] maxfixes = params["MaxFixes"] self.__project.prefs.set("python_path", params["SysPath"]) if filename: resource = rope.base.libutils.path_to_resource( self.__project, filename) else: resource = None errorDict = {} completions = [] try: proposals = rope.contrib.codeassist.code_assist( self.__project, source, offset, resource, maxfixes=maxfixes) for proposal in proposals: proposalType = proposal.type if proposal.name.startswith("__"): proposalType = "__" + proposalType elif proposal.name.startswith("_"): proposalType = "_" + proposalType completions.append( proposal.name + self.PictureIDs[proposalType]) except Exception as err: errorDict = self.__handleRopeError(err) result = { "Completions": completions, "CompletionText": params["CompletionText"], "FileName": filename, } result.update(errorDict) self.sendJson("CompletionsResult", result) def __getCallTips(self, params): """ Private method to calculate possible calltips. @param params dictionary containing the method parameters @type dict """ filename = params["FileName"] source = params["Source"] offset = params["Offset"] maxfixes = params["MaxFixes"] self.__project.prefs.set("python_path", params["SysPath"]) if filename: resource = rope.base.libutils.path_to_resource( self.__project, filename) else: resource = None errorDict = {} calltips = [] try: cts = rope.contrib.codeassist.get_calltip( self.__project, source, offset, resource, maxfixes=maxfixes, remove_self=True) if cts is not None: calltips = [cts] except Exception as err: errorDict = self.__handleRopeError(err) result = { "CallTips": calltips, } result.update(errorDict) self.sendJson("CallTipsResult", result) def __getDocumentation(self, params): """ Private method to get some source code documentation. @param params dictionary containing the method parameters @type dict """ filename = params["FileName"] source = params["Source"] offset = params["Offset"] maxfixes = params["MaxFixes"] self.__project.prefs.set("python_path", params["SysPath"]) if filename: resource = rope.base.libutils.path_to_resource( self.__project, filename) else: resource = None errorDict = {} documentation = "" cts = None try: cts = rope.contrib.codeassist.get_calltip( self.__project, source, offset, resource, maxfixes=maxfixes, remove_self=True) except Exception: pass if cts is not None: while '..' in cts: cts = cts.replace('..', '.') if '(.)' in cts: cts = cts.replace('(.)', '(...)') try: documentation = rope.contrib.codeassist.get_doc( self.__project, source, offset, resource, maxfixes=maxfixes) except Exception as err: errorDict = self.__handleRopeError(err) documentationDict = self.__processDocumentation(cts, documentation) result = { "DocumentationDict": documentationDict, } result.update(errorDict) self.sendJson("DocumentationResult", result) def __processDocumentation(self, cts, documentation): """ Private method to process the call-tips and documentation. @param cts call-tips @type str @param documentation extracted source code documentation @type str @return dictionary containing document information @rtype dictionary with keys "name", "argspec", "module" and "docstring" """ objectFullname = "" calltip = "" argspec = "" module = "" if cts: cts = cts.replace(".__init__", "") parenthesisPos = cts.find("(") if parenthesisPos != -1: objectFullname = cts[:parenthesisPos] objectName = objectFullname.split('.')[-1] cts = cts.replace(objectFullname, objectName) calltip = cts else: objectFullname = cts if objectFullname and not objectFullname.startswith("self."): if calltip: argspecStart = calltip.find("(") argspec = calltip[argspecStart:] moduleEnd = objectFullname.rfind('.') module = objectFullname[:moduleEnd] if not documentation and not calltip: return None return dict(name=objectFullname, argspec=argspec, module=module, docstring=documentation) def __reportChanged(self, params): """ Private method to register some changed sources. @param params dictionary containing the method parameters sent by the server @type dict """ filename = params["FileName"] oldSource = params["OldSource"] try: rope.base.libutils.report_change( self.__project, filename, oldSource) except Exception: # simply ignore it pass if __name__ == '__main__': if len(sys.argv) != 5: print('Host, port, id and project path parameters are missing. Abort.') sys.exit(1) host, port, idString, projectPath = sys.argv[1:] # Create a Qt4/5 application object in order to allow the processing of # modules containing Qt stuff. try: from PyQt5.QtCore import QCoreApplication except ImportError: try: from PyQt4.QtCore import QCoreApplication except ImportError: QCoreApplication = None if QCoreApplication is not None: app = QCoreApplication(sys.argv) client = CodeAssistClient(host, int(port), idString, projectPath) # Start the main loop client.run() sys.exit(0) # # eflag: noqa = M801