--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric7/JediInterface/JediClient.py Sat Sep 11 19:47:02 2021 +0200 @@ -0,0 +1,418 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the Jedi client of eric7. +""" + +import sys + +SuppressedException = Exception + +modulePath = sys.argv[-1] # it is always the last parameter +sys.path.insert(1, modulePath) + +try: + import jedi +except ImportError: + sys.exit(42) + +from eric7.EricNetwork.EricJsonClient import EricJsonClient + + +class JediClient(EricJsonClient): + """ + Class implementing the Jedi client of eric7. + """ + def __init__(self, host, port, idString): + """ + 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 + """ + super().__init__(host, port, idString) + + # TODO: add additional methods for these topics + # - rename variable + # - extract function + # - extract variable + # - inline variable + self.__methodMapping = { + "openProject": self.__openProject, + "closeProject": self.__closeProject, + + "getCompletions": self.__getCompletions, + "getCallTips": self.__getCallTips, + "getDocumentation": self.__getDocumentation, + "hoverHelp": self.__getHoverHelp, + "gotoDefinition": self.__getAssignment, + "gotoReferences": self.__getReferences, + } + + self.__id = idString + + self.__project = None + + 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 __handleError(self, err): + """ + Private method to process an error. + + @param err exception object + @type Exception or Warning + @return dictionary containing the error information + @rtype dict + """ + error = str(type(err)).split()[-1] + error = error[1:-2].split('.')[-1] + errorDict = { + "Error": error, + "ErrorString": str(err), + } + + return errorDict + + def __openProject(self, params): + """ + Private method to create a jedi project and load its saved data. + + @param params dictionary containing the method parameters + @type dict + """ + projectPath = params["ProjectPath"] + self.__project = jedi.Project(projectPath) + + def __closeProject(self, params): + """ + Private method to save a jedi project's data. + + @param params dictionary containing the method parameters + @type dict + """ + if self.__project is not None: + self.__project.save() + + def __completionType(self, completion): + """ + Private method to assemble the completion type depending on the + visibility indicated by the completion name. + + @param completion reference to the completion object + @type jedi.api.classes.Completion + @return modified completion type + @rtype str + """ + if completion.name.startswith('__'): + compType = '__' + completion.type + elif completion.name.startswith('_'): + compType = '_' + completion.type + else: + compType = completion.type + + return compType + + def __completionFullName(self, completion): + """ + Private method to extract the full completion name. + + @param completion reference to the completion object + @type jedi.api.classes.Completion + @return full completion name + @rtype str + """ + fullName = completion.full_name + fullName = ( + fullName.replace("__main__", completion.module_name) + if fullName else + completion.module_name + ) + + return fullName + + 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"] + line = params["Line"] + index = params["Index"] + fuzzy = params["Fuzzy"] + + errorDict = {} + response = [] + + script = jedi.Script(source, path=filename, project=self.__project) + + try: + completions = script.complete(line, index, fuzzy=fuzzy) + response = [ + { + 'ModulePath': str(completion.module_path), + 'Name': completion.name, + 'FullName': self.__completionFullName(completion), + 'CompletionType': self.__completionType(completion), + } for completion in completions + if not (completion.name.startswith("__") and + completion.name.endswith("__")) + ] + except SuppressedException as err: + errorDict = self.__handleError(err) + + result = { + "Completions": response, + "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"] + line = params["Line"] + index = params["Index"] + + errorDict = {} + calltips = [] + + script = jedi.Script(source, path=filename, project=self.__project) + + try: + signatures = script.get_signatures(line, index) + for signature in signatures: + name = signature.name + params = self.__extractParameters(signature) + calltips.append("{0}{1}".format(name, params)) + except SuppressedException as err: + errorDict = self.__handleError(err) + + result = { + "CallTips": calltips, + } + result.update(errorDict) + + self.sendJson("CallTipsResult", result) + + def __extractParameters(self, signature): + """ + Private method to extract the call parameter descriptions. + + @param signature a jedi signature object + @type object + @return a string with comma seperated parameter names and default + values + @rtype str + """ + try: + params = ", ".join([param.description.split('param ', 1)[-1] + for param in signature.params]) + return "({0})".format(params) + except AttributeError: + # Empty strings as argspec suppress display of "definition" + return ' ' + + 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"] + line = params["Line"] + index = params["Index"] + + errorDict = {} + docu = {} + + script = jedi.Script(source, path=filename, project=self.__project) + + try: + definitions = script.infer(line, index) + definition = definitions[0] # use the first one only + docu = { + "name": definition.full_name, + "module": definition.module_name, + "argspec": self.__extractParameters(definition), + "docstring": definition.docstring(), + } + except SuppressedException as err: + errorDict = self.__handleError(err) + + result = { + "DocumentationDict": docu, + } + result.update(errorDict) + + self.sendJson("DocumentationResult", result) + + def __getHoverHelp(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"] + line = params["Line"] + index = params["Index"] + uid = params["Uuid"] + + script = jedi.Script(source, path=filename, project=self.__project) + + errorDict = {} + helpText = "" + + try: + helpText = script.help(line, index)[0].docstring() + except SuppressedException as err: + errorDict = self.__handleError(err) + + result = { + "Line": line, + "Index": index, + "HoverHelp": helpText, + "Uuid": uid, + } + result.update(errorDict) + + self.sendJson("HoverHelpResult", result) + + def __getAssignment(self, params): + """ + Private method to get the place a parameter is defined. + + @param params dictionary containing the method parameters + @type dict + """ + filename = params["FileName"] + source = params["Source"] + line = params["Line"] + index = params["Index"] + uid = params["Uuid"] + + errorDict = {} + gotoDefinition = {} + + script = jedi.Script(source, path=filename, project=self.__project) + + try: + assignments = script.goto( + line, index, follow_imports=True, follow_builtin_imports=True) + for assignment in assignments: + if bool(assignment.module_path): + # TODO: call __getReferences if + # assignment.module_path == filename and + # assignment.line == line + gotoDefinition = { + 'ModulePath': str(assignment.module_path), + 'Line': (0 if assignment.line is None else + assignment.line), + 'Column': assignment.column, + } + break + except SuppressedException as err: + errorDict = self.__handleError(err) + + result = { + "GotoDefinitionDict": gotoDefinition, + "Uuid": uid, + } + result.update(errorDict) + + self.sendJson("GotoDefinitionResult", result) + + def __getReferences(self, params): + """ + Private method to get the places a parameter is referenced. + + @param params dictionary containing the method parameters + @type dict + """ + filename = params["FileName"] + source = params["Source"] + line = params["Line"] + index = params["Index"] + uid = params["Uuid"] + + errorDict = {} + gotoReferences = [] + + script = jedi.Script(source, path=filename, project=self.__project) + + try: + references = script.get_references(line, index, + include_builtins=False) + for reference in references: + if bool(reference.module_path): + if ( + reference.line == line and + str(reference.module_path) == filename + ): + continue + gotoReferences.append({ + 'ModulePath': str(reference.module_path), + 'Line': (0 if reference.line is None else + reference.line), + 'Column': reference.column, + 'Code': reference.get_line_code(), + }) + except SuppressedException as err: + errorDict = self.__handleError(err) + + result = { + "GotoReferencesList": gotoReferences, + "Uuid": uid, + } + result.update(errorDict) + + self.sendJson("GotoReferencesResult", result) + + +if __name__ == '__main__': + if len(sys.argv) != 5: + print('Host, port, id and module path parameters are missing.' + ' Abort.') + sys.exit(1) + + host, port, idString = sys.argv[1:-1] + + client = JediClient(host, int(port), idString) + # Start the main loop + client.run() + + sys.exit(0) + +# +# eflag: noqa = M801