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