eric7/JediInterface/JediClient.py

branch
eric7
changeset 8593
1d66b6af60ed
child 8606
dd9bf9841c50
equal deleted inserted replaced
8592:ab8580937d4b 8593:1d66b6af60ed
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2017 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the Jedi client of eric7.
8 """
9
10 import sys
11
12 SuppressedException = Exception
13
14 modulePath = sys.argv[-1] # it is always the last parameter
15 sys.path.insert(1, modulePath)
16
17 try:
18 import jedi
19 except ImportError:
20 sys.exit(42)
21
22 from eric7.EricNetwork.EricJsonClient import EricJsonClient
23
24
25 class JediClient(EricJsonClient):
26 """
27 Class implementing the Jedi client of eric7.
28 """
29 def __init__(self, host, port, idString):
30 """
31 Constructor
32
33 @param host ip address the background service is listening
34 @type str
35 @param port port of the background service
36 @type int
37 @param idString assigned client id to be sent back to the server in
38 order to identify the connection
39 @type str
40 """
41 super().__init__(host, port, idString)
42
43 # TODO: add additional methods for these topics
44 # - rename variable
45 # - extract function
46 # - extract variable
47 # - inline variable
48 self.__methodMapping = {
49 "openProject": self.__openProject,
50 "closeProject": self.__closeProject,
51
52 "getCompletions": self.__getCompletions,
53 "getCallTips": self.__getCallTips,
54 "getDocumentation": self.__getDocumentation,
55 "hoverHelp": self.__getHoverHelp,
56 "gotoDefinition": self.__getAssignment,
57 "gotoReferences": self.__getReferences,
58 }
59
60 self.__id = idString
61
62 self.__project = None
63
64 def handleCall(self, method, params):
65 """
66 Public method to handle a method call from the server.
67
68 @param method requested method name
69 @type str
70 @param params dictionary with method specific parameters
71 @type dict
72 """
73 self.__methodMapping[method](params)
74
75 def __handleError(self, err):
76 """
77 Private method to process an error.
78
79 @param err exception object
80 @type Exception or Warning
81 @return dictionary containing the error information
82 @rtype dict
83 """
84 error = str(type(err)).split()[-1]
85 error = error[1:-2].split('.')[-1]
86 errorDict = {
87 "Error": error,
88 "ErrorString": str(err),
89 }
90
91 return errorDict
92
93 def __openProject(self, params):
94 """
95 Private method to create a jedi project and load its saved data.
96
97 @param params dictionary containing the method parameters
98 @type dict
99 """
100 projectPath = params["ProjectPath"]
101 self.__project = jedi.Project(projectPath)
102
103 def __closeProject(self, params):
104 """
105 Private method to save a jedi project's data.
106
107 @param params dictionary containing the method parameters
108 @type dict
109 """
110 if self.__project is not None:
111 self.__project.save()
112
113 def __completionType(self, completion):
114 """
115 Private method to assemble the completion type depending on the
116 visibility indicated by the completion name.
117
118 @param completion reference to the completion object
119 @type jedi.api.classes.Completion
120 @return modified completion type
121 @rtype str
122 """
123 if completion.name.startswith('__'):
124 compType = '__' + completion.type
125 elif completion.name.startswith('_'):
126 compType = '_' + completion.type
127 else:
128 compType = completion.type
129
130 return compType
131
132 def __completionFullName(self, completion):
133 """
134 Private method to extract the full completion name.
135
136 @param completion reference to the completion object
137 @type jedi.api.classes.Completion
138 @return full completion name
139 @rtype str
140 """
141 fullName = completion.full_name
142 fullName = (
143 fullName.replace("__main__", completion.module_name)
144 if fullName else
145 completion.module_name
146 )
147
148 return fullName
149
150 def __getCompletions(self, params):
151 """
152 Private method to calculate possible completions.
153
154 @param params dictionary containing the method parameters
155 @type dict
156 """
157 filename = params["FileName"]
158 source = params["Source"]
159 line = params["Line"]
160 index = params["Index"]
161 fuzzy = params["Fuzzy"]
162
163 errorDict = {}
164 response = []
165
166 script = jedi.Script(source, path=filename, project=self.__project)
167
168 try:
169 completions = script.complete(line, index, fuzzy=fuzzy)
170 response = [
171 {
172 'ModulePath': str(completion.module_path),
173 'Name': completion.name,
174 'FullName': self.__completionFullName(completion),
175 'CompletionType': self.__completionType(completion),
176 } for completion in completions
177 if not (completion.name.startswith("__") and
178 completion.name.endswith("__"))
179 ]
180 except SuppressedException as err:
181 errorDict = self.__handleError(err)
182
183 result = {
184 "Completions": response,
185 "CompletionText": params["CompletionText"],
186 "FileName": filename,
187 }
188 result.update(errorDict)
189
190 self.sendJson("CompletionsResult", result)
191
192 def __getCallTips(self, params):
193 """
194 Private method to calculate possible calltips.
195
196 @param params dictionary containing the method parameters
197 @type dict
198 """
199 filename = params["FileName"]
200 source = params["Source"]
201 line = params["Line"]
202 index = params["Index"]
203
204 errorDict = {}
205 calltips = []
206
207 script = jedi.Script(source, path=filename, project=self.__project)
208
209 try:
210 signatures = script.get_signatures(line, index)
211 for signature in signatures:
212 name = signature.name
213 params = self.__extractParameters(signature)
214 calltips.append("{0}{1}".format(name, params))
215 except SuppressedException as err:
216 errorDict = self.__handleError(err)
217
218 result = {
219 "CallTips": calltips,
220 }
221 result.update(errorDict)
222
223 self.sendJson("CallTipsResult", result)
224
225 def __extractParameters(self, signature):
226 """
227 Private method to extract the call parameter descriptions.
228
229 @param signature a jedi signature object
230 @type object
231 @return a string with comma seperated parameter names and default
232 values
233 @rtype str
234 """
235 try:
236 params = ", ".join([param.description.split('param ', 1)[-1]
237 for param in signature.params])
238 return "({0})".format(params)
239 except AttributeError:
240 # Empty strings as argspec suppress display of "definition"
241 return ' '
242
243 def __getDocumentation(self, params):
244 """
245 Private method to get some source code documentation.
246
247 @param params dictionary containing the method parameters
248 @type dict
249 """
250 filename = params["FileName"]
251 source = params["Source"]
252 line = params["Line"]
253 index = params["Index"]
254
255 errorDict = {}
256 docu = {}
257
258 script = jedi.Script(source, path=filename, project=self.__project)
259
260 try:
261 definitions = script.infer(line, index)
262 definition = definitions[0] # use the first one only
263 docu = {
264 "name": definition.full_name,
265 "module": definition.module_name,
266 "argspec": self.__extractParameters(definition),
267 "docstring": definition.docstring(),
268 }
269 except SuppressedException as err:
270 errorDict = self.__handleError(err)
271
272 result = {
273 "DocumentationDict": docu,
274 }
275 result.update(errorDict)
276
277 self.sendJson("DocumentationResult", result)
278
279 def __getHoverHelp(self, params):
280 """
281 Private method to get some source code documentation.
282
283 @param params dictionary containing the method parameters
284 @type dict
285 """
286 filename = params["FileName"]
287 source = params["Source"]
288 line = params["Line"]
289 index = params["Index"]
290 uid = params["Uuid"]
291
292 script = jedi.Script(source, path=filename, project=self.__project)
293
294 errorDict = {}
295 helpText = ""
296
297 try:
298 helpText = script.help(line, index)[0].docstring()
299 except SuppressedException as err:
300 errorDict = self.__handleError(err)
301
302 result = {
303 "Line": line,
304 "Index": index,
305 "HoverHelp": helpText,
306 "Uuid": uid,
307 }
308 result.update(errorDict)
309
310 self.sendJson("HoverHelpResult", result)
311
312 def __getAssignment(self, params):
313 """
314 Private method to get the place a parameter is defined.
315
316 @param params dictionary containing the method parameters
317 @type dict
318 """
319 filename = params["FileName"]
320 source = params["Source"]
321 line = params["Line"]
322 index = params["Index"]
323 uid = params["Uuid"]
324
325 errorDict = {}
326 gotoDefinition = {}
327
328 script = jedi.Script(source, path=filename, project=self.__project)
329
330 try:
331 assignments = script.goto(
332 line, index, follow_imports=True, follow_builtin_imports=True)
333 for assignment in assignments:
334 if bool(assignment.module_path):
335 # TODO: call __getReferences if
336 # assignment.module_path == filename and
337 # assignment.line == line
338 gotoDefinition = {
339 'ModulePath': str(assignment.module_path),
340 'Line': (0 if assignment.line is None else
341 assignment.line),
342 'Column': assignment.column,
343 }
344 break
345 except SuppressedException as err:
346 errorDict = self.__handleError(err)
347
348 result = {
349 "GotoDefinitionDict": gotoDefinition,
350 "Uuid": uid,
351 }
352 result.update(errorDict)
353
354 self.sendJson("GotoDefinitionResult", result)
355
356 def __getReferences(self, params):
357 """
358 Private method to get the places a parameter is referenced.
359
360 @param params dictionary containing the method parameters
361 @type dict
362 """
363 filename = params["FileName"]
364 source = params["Source"]
365 line = params["Line"]
366 index = params["Index"]
367 uid = params["Uuid"]
368
369 errorDict = {}
370 gotoReferences = []
371
372 script = jedi.Script(source, path=filename, project=self.__project)
373
374 try:
375 references = script.get_references(line, index,
376 include_builtins=False)
377 for reference in references:
378 if bool(reference.module_path):
379 if (
380 reference.line == line and
381 str(reference.module_path) == filename
382 ):
383 continue
384 gotoReferences.append({
385 'ModulePath': str(reference.module_path),
386 'Line': (0 if reference.line is None else
387 reference.line),
388 'Column': reference.column,
389 'Code': reference.get_line_code(),
390 })
391 except SuppressedException as err:
392 errorDict = self.__handleError(err)
393
394 result = {
395 "GotoReferencesList": gotoReferences,
396 "Uuid": uid,
397 }
398 result.update(errorDict)
399
400 self.sendJson("GotoReferencesResult", result)
401
402
403 if __name__ == '__main__':
404 if len(sys.argv) != 5:
405 print('Host, port, id and module path parameters are missing.'
406 ' Abort.')
407 sys.exit(1)
408
409 host, port, idString = sys.argv[1:-1]
410
411 client = JediClient(host, int(port), idString)
412 # Start the main loop
413 client.run()
414
415 sys.exit(0)
416
417 #
418 # eflag: noqa = M801

eric ide

mercurial