|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2015 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the autocompletion interface to jedi. |
|
8 """ |
|
9 |
|
10 import contextlib |
|
11 import os |
|
12 import uuid |
|
13 |
|
14 from PyQt6.QtCore import pyqtSlot, QCoreApplication, QTimer, QThread |
|
15 from PyQt6.QtWidgets import QInputDialog, QLineEdit, QDialog |
|
16 |
|
17 from EricWidgets.EricApplication import ericApp |
|
18 from EricWidgets import EricMessageBox |
|
19 |
|
20 from EricNetwork.EricJsonServer import EricJsonServer |
|
21 |
|
22 from QScintilla.Editor import Editor |
|
23 |
|
24 import Preferences |
|
25 import Globals |
|
26 |
|
27 from .RefactoringPreviewDialog import RefactoringPreviewDialog |
|
28 |
|
29 |
|
30 class JediServer(EricJsonServer): |
|
31 """ |
|
32 Class implementing the interface to the jedi library. |
|
33 """ |
|
34 |
|
35 IdProject = "Project" |
|
36 |
|
37 PictureIDs = { |
|
38 "class": "?{0}".format(Editor.ClassID), |
|
39 "_class": "?{0}".format(Editor.ClassProtectedID), |
|
40 "__class": "?{0}".format(Editor.ClassPrivateID), |
|
41 "instance": "?{0}".format(Editor.ClassID), |
|
42 "_instance": "?{0}".format(Editor.ClassProtectedID), |
|
43 "__instance": "?{0}".format(Editor.ClassPrivateID), |
|
44 "function": "?{0}".format(Editor.MethodID), |
|
45 "_function": "?{0}".format(Editor.MethodProtectedID), |
|
46 "__function": "?{0}".format(Editor.MethodPrivateID), |
|
47 "module": "?{0}".format(Editor.ModuleID), |
|
48 "_module": "?{0}".format(Editor.ModuleID), |
|
49 "__module": "?{0}".format(Editor.ModuleID), |
|
50 "param": "?{0}".format(Editor.AttributeID), |
|
51 "_param": "?{0}".format(Editor.AttributeProtectedID), |
|
52 "__param": "?{0}".format(Editor.AttributePrivateID), |
|
53 "statement": "?{0}".format(Editor.AttributeID), |
|
54 "_statement": "?{0}".format(Editor.AttributeProtectedID), |
|
55 "__statement": "?{0}".format(Editor.AttributePrivateID), |
|
56 "import": "", |
|
57 "None": "", |
|
58 } |
|
59 |
|
60 def __init__(self, viewManager, project, ui): |
|
61 """ |
|
62 Constructor |
|
63 |
|
64 @param viewManager reference to the viewmanager object |
|
65 @type ViewManager |
|
66 @param project reference to the project object |
|
67 @type Project |
|
68 @param ui reference to the user interface |
|
69 @type UserInterface |
|
70 """ |
|
71 super().__init__("JediServer", multiplex=True, parent=ui) |
|
72 |
|
73 self.__ui = ui |
|
74 self.__vm = viewManager |
|
75 self.__ericProject = project |
|
76 |
|
77 self.__editorLanguageMapping = {} |
|
78 |
|
79 self.__documentationViewer = None |
|
80 |
|
81 # attributes to store the resuls of the client side |
|
82 self.__completions = None |
|
83 self.__calltips = None |
|
84 |
|
85 self.__methodMapping = { |
|
86 "CompletionsResult": self.__processCompletionsResult, |
|
87 "CallTipsResult": self.__processCallTipsResult, |
|
88 "DocumentationResult": self.__processDocumentationResult, |
|
89 "HoverHelpResult": self.__processHoverHelpResult, |
|
90 "GotoDefinitionResult": self.__processGotoDefinitionResult, |
|
91 "GotoReferencesResult": self.__processGotoReferencesResult, |
|
92 "RefactoringDiff": self.__showRefactoringDiff, |
|
93 "RefactoringApplyResult": self.__checkRefactoringResult, |
|
94 "ClientException": self.__processClientException, |
|
95 } |
|
96 |
|
97 # temporary store for editor references indexed by Uuid |
|
98 self.__editors = {} |
|
99 |
|
100 # Python 3 |
|
101 self.__ensureActive("Python3") |
|
102 |
|
103 def __updateEditorLanguageMapping(self): |
|
104 """ |
|
105 Private method to update the editor language to connection mapping. |
|
106 """ |
|
107 self.__editorLanguageMapping = {} |
|
108 for name in self.connectionNames(): |
|
109 if name == "Python3": |
|
110 self.__editorLanguageMapping.update( |
|
111 { |
|
112 "Python3": "Python3", |
|
113 "MicroPython": "Python3", |
|
114 "Pygments|Python": "Python3", |
|
115 "Pygments|Python 2.x": "Python3", |
|
116 "Cython": "Python3", |
|
117 } |
|
118 ) |
|
119 |
|
120 def isSupportedLanguage(self, language): |
|
121 """ |
|
122 Public method to check, if the given language is supported. |
|
123 |
|
124 @param language editor programming language to check |
|
125 @type str |
|
126 @return flag indicating the support status |
|
127 @rtype bool |
|
128 """ |
|
129 return language in self.__editorLanguageMapping |
|
130 |
|
131 def __idString(self, editor): |
|
132 """ |
|
133 Private method to determine the ID string for the back-end. |
|
134 |
|
135 @param editor reference to the editor to determine the ID string for |
|
136 @type Editor |
|
137 @return ID string |
|
138 @rtype str |
|
139 """ |
|
140 idString = "" |
|
141 |
|
142 language = editor.getLanguage() |
|
143 if ( |
|
144 self.__ericProject.isOpen() |
|
145 and self.__ericProject.getProjectLanguage() == language |
|
146 ): |
|
147 filename = editor.getFileName() |
|
148 if self.__ericProject.isProjectSource(filename): |
|
149 idString = JediServer.IdProject |
|
150 |
|
151 if not idString and language in self.__editorLanguageMapping: |
|
152 idString = self.__editorLanguageMapping[language] |
|
153 |
|
154 return idString |
|
155 |
|
156 def __prepareData(self, editor): |
|
157 """ |
|
158 Private method to gather data about current cursor position. |
|
159 |
|
160 @param editor reference to the editor object, that called this method |
|
161 @type Editor |
|
162 @return tuple of filename, line, index, source |
|
163 @rtype tuple (str, int, int, str) |
|
164 """ |
|
165 filename = editor.getFileName() |
|
166 line, index = editor.getCursorPosition() |
|
167 line += 1 # jedi line numbers are 1 based |
|
168 source = editor.text() |
|
169 return filename, line, index, source |
|
170 |
|
171 def requestCompletions(self, editor, context, acText): |
|
172 """ |
|
173 Public method to request a list of possible completions. |
|
174 |
|
175 @param editor reference to the editor object, that called this method |
|
176 @type Editor |
|
177 @param context flag indicating to autocomplete a context |
|
178 @type bool |
|
179 @param acText text to be completed |
|
180 @type str |
|
181 """ |
|
182 if not Preferences.getJedi("JediCompletionsEnabled"): |
|
183 return |
|
184 |
|
185 idString = self.__idString(editor) |
|
186 if not idString: |
|
187 return |
|
188 |
|
189 filename, line, index, source = self.__prepareData(editor) |
|
190 fuzzy = Preferences.getJedi("JediFuzzyCompletionsEnabled") |
|
191 |
|
192 self.__ensureActive(idString) |
|
193 |
|
194 self.sendJson( |
|
195 "getCompletions", |
|
196 { |
|
197 "FileName": filename, |
|
198 "Source": source, |
|
199 "Line": line, |
|
200 "Index": index, |
|
201 "Fuzzy": fuzzy, |
|
202 "CompletionText": acText, |
|
203 }, |
|
204 idString=idString, |
|
205 ) |
|
206 |
|
207 def __processCompletionsResult(self, result): |
|
208 """ |
|
209 Private method to process the completions sent by the client. |
|
210 |
|
211 @param result dictionary containing the result sent by the client |
|
212 @type dict |
|
213 """ |
|
214 names = [] |
|
215 for completion in result["Completions"]: |
|
216 name = completion["Name"] |
|
217 context = completion["FullName"] |
|
218 if context: |
|
219 if context.endswith(".{0}".format(name)): |
|
220 context = context.rsplit(".", 1)[0] |
|
221 name = "{0} ({1})".format(name, context) |
|
222 |
|
223 name += JediServer.PictureIDs.get(completion["CompletionType"], "") |
|
224 names.append(name) |
|
225 |
|
226 if "Error" not in result: |
|
227 editor = self.__vm.getOpenEditor(result["FileName"]) |
|
228 if editor is not None: |
|
229 editor.completionsListReady(names, result["CompletionText"]) |
|
230 |
|
231 def getCallTips(self, editor, pos, commas): |
|
232 """ |
|
233 Public method to calculate calltips. |
|
234 |
|
235 @param editor reference to the editor object, that called this method |
|
236 @type Editor |
|
237 @param pos position in the text for the calltip |
|
238 @type int |
|
239 @param commas minimum number of commas contained in the calltip |
|
240 @type int |
|
241 @return list of possible calltips |
|
242 @rtype list of str |
|
243 """ |
|
244 if not Preferences.getJedi("JediCalltipsEnabled"): |
|
245 return [] |
|
246 |
|
247 # reset the calltips buffer |
|
248 self.__calltips = None |
|
249 |
|
250 idString = self.__idString(editor) |
|
251 if not idString: |
|
252 return [] |
|
253 |
|
254 filename, line, index, source = self.__prepareData(editor) |
|
255 |
|
256 self.__ensureActive(idString) |
|
257 self.sendJson( |
|
258 "getCallTips", |
|
259 { |
|
260 "FileName": filename, |
|
261 "Source": source, |
|
262 "Line": line, |
|
263 "Index": index, |
|
264 }, |
|
265 idString=idString, |
|
266 ) |
|
267 |
|
268 # emulate the synchronous behaviour |
|
269 timer = QTimer() |
|
270 timer.setSingleShot(True) |
|
271 timer.start(5000) # 5s timeout |
|
272 while self.__calltips is None and timer.isActive(): |
|
273 QCoreApplication.processEvents() |
|
274 QThread.msleep(100) |
|
275 |
|
276 return [] if self.__calltips is None else self.__calltips |
|
277 |
|
278 def __processCallTipsResult(self, result): |
|
279 """ |
|
280 Private method to process the calltips sent by the client. |
|
281 |
|
282 @param result dictionary containing the result sent by the client |
|
283 @type dict |
|
284 """ |
|
285 if "Error" in result: |
|
286 self.__calltips = [] |
|
287 else: |
|
288 self.__calltips = result["CallTips"] |
|
289 |
|
290 def requestCodeDocumentation(self, editor): |
|
291 """ |
|
292 Public method to request source code documentation for the given |
|
293 editor. |
|
294 |
|
295 @param editor reference to the editor to get source code documentation |
|
296 for |
|
297 @type Editor |
|
298 """ |
|
299 if self.__documentationViewer is None: |
|
300 return |
|
301 |
|
302 idString = self.__idString(editor) |
|
303 |
|
304 if not idString: |
|
305 language = editor.getLanguage() |
|
306 warning = self.tr("Language <b>{0}</b> is not supported.").format(language) |
|
307 self.__documentationViewer.documentationReady(warning, isWarning=True) |
|
308 return |
|
309 |
|
310 filename, line, index, source = self.__prepareData(editor) |
|
311 sourceLines = source.splitlines() |
|
312 # Correct index if cursor is standing after an opening bracket |
|
313 if line > 0 and index > 0 and sourceLines[line - 1][index - 1] == "(": |
|
314 index -= 1 |
|
315 |
|
316 self.__ensureActive(idString) |
|
317 self.sendJson( |
|
318 "getDocumentation", |
|
319 { |
|
320 "FileName": filename, |
|
321 "Source": source, |
|
322 "Line": line, |
|
323 "Index": index, |
|
324 }, |
|
325 idString=idString, |
|
326 ) |
|
327 |
|
328 def __processDocumentationResult(self, result): |
|
329 """ |
|
330 Private method to process the documentation sent by the client. |
|
331 |
|
332 @param result dictionary containing the result sent by the client |
|
333 @type dict with keys 'name', 'module', 'argspec', 'docstring' |
|
334 """ |
|
335 if self.__documentationViewer is None: |
|
336 return |
|
337 |
|
338 docu = None |
|
339 |
|
340 if "Error" not in result: |
|
341 docu = result["DocumentationDict"] |
|
342 docu["note"] = self.tr("Present in <i>{0}</i> module").format( |
|
343 docu["module"] |
|
344 ) |
|
345 |
|
346 if docu is None: |
|
347 msg = self.tr("No documentation available.") |
|
348 self.__documentationViewer.documentationReady(msg, isDocWarning=True) |
|
349 else: |
|
350 self.__documentationViewer.documentationReady(docu) |
|
351 |
|
352 def gotoDefinition(self, editor): |
|
353 """ |
|
354 Public slot to find the definition for the word at the cursor position |
|
355 and go to it. |
|
356 |
|
357 Note: This is executed upon a mouse click sequence. |
|
358 |
|
359 @param editor reference to the calling editor |
|
360 @type Editor |
|
361 """ |
|
362 if not Preferences.getJedi("MouseClickEnabled"): |
|
363 return |
|
364 |
|
365 idString = self.__idString(editor) |
|
366 if not idString: |
|
367 return |
|
368 |
|
369 filename, line, index, source = self.__prepareData(editor) |
|
370 |
|
371 self.__ensureActive(idString) |
|
372 |
|
373 euuid = str(uuid.uuid4()) |
|
374 self.__editors[euuid] = editor |
|
375 |
|
376 self.sendJson( |
|
377 "gotoDefinition", |
|
378 { |
|
379 "FileName": filename, |
|
380 "Source": source, |
|
381 "Line": line, |
|
382 "Index": index, |
|
383 "Uuid": euuid, |
|
384 }, |
|
385 idString=idString, |
|
386 ) |
|
387 |
|
388 def __processGotoDefinitionResult(self, result): |
|
389 """ |
|
390 Private method callback for the goto definition result. |
|
391 |
|
392 @param result dictionary containing the result data |
|
393 @type dict |
|
394 """ |
|
395 euuid = result["Uuid"] |
|
396 if "Error" not in result: |
|
397 # ignore errors silently |
|
398 location = result["GotoDefinitionDict"] |
|
399 if location: |
|
400 self.__vm.openSourceFile( |
|
401 location["ModulePath"], location["Line"], addNext=True |
|
402 ) |
|
403 else: |
|
404 ericApp().getObject("UserInterface").statusBar().showMessage( |
|
405 self.tr("Jedi: No definition found"), 5000 |
|
406 ) |
|
407 |
|
408 with contextlib.suppress(KeyError): |
|
409 del self.__editors[euuid] |
|
410 |
|
411 def __processGotoReferencesResult(self, result): |
|
412 """ |
|
413 Private method callback for the goto references result. |
|
414 |
|
415 @param result dictionary containing the result data |
|
416 @type dict |
|
417 """ |
|
418 euuid = result["Uuid"] |
|
419 with contextlib.suppress(ImportError): |
|
420 from QScintilla.Editor import ReferenceItem |
|
421 |
|
422 if "Error" not in result: |
|
423 # ignore errors silently |
|
424 references = result["GotoReferencesList"] |
|
425 if references: |
|
426 try: |
|
427 editor = self.__editors[euuid] |
|
428 except KeyError: |
|
429 editor = None |
|
430 if editor is not None: |
|
431 referenceItemsList = [ |
|
432 ReferenceItem( |
|
433 modulePath=ref["ModulePath"], |
|
434 codeLine=ref["Code"], |
|
435 line=ref["Line"], |
|
436 column=ref["Column"], |
|
437 ) |
|
438 for ref in references |
|
439 ] |
|
440 editor.gotoReferenceHandler(referenceItemsList) |
|
441 |
|
442 with contextlib.suppress(KeyError): |
|
443 del self.__editors[euuid] |
|
444 |
|
445 def hoverHelp(self, editor, line, index): |
|
446 """ |
|
447 Public method to initiate the display of mouse hover help. |
|
448 |
|
449 @param editor reference to the calling editor |
|
450 @type Editor |
|
451 @param line line number (zero based) |
|
452 @type int |
|
453 @param index index within the line (zero based) |
|
454 @type int |
|
455 """ |
|
456 idString = self.__idString(editor) |
|
457 if not idString: |
|
458 return |
|
459 |
|
460 filename = editor.getFileName() |
|
461 line += 1 # jedi line numbers are 1 based |
|
462 source = editor.text() |
|
463 |
|
464 self.__ensureActive(idString) |
|
465 |
|
466 euuid = str(uuid.uuid4()) |
|
467 self.__editors[euuid] = editor |
|
468 |
|
469 self.sendJson( |
|
470 "hoverHelp", |
|
471 { |
|
472 "FileName": filename, |
|
473 "Source": source, |
|
474 "Line": line, |
|
475 "Index": index, |
|
476 "Uuid": euuid, |
|
477 }, |
|
478 idString=idString, |
|
479 ) |
|
480 |
|
481 def __processHoverHelpResult(self, result): |
|
482 """ |
|
483 Private method callback for the goto definition result. |
|
484 |
|
485 @param result dictionary containing the result data |
|
486 @type dict |
|
487 """ |
|
488 euuid = result["Uuid"] |
|
489 if "Error" not in result: |
|
490 # ignore errors silently |
|
491 helpText = result["HoverHelp"] |
|
492 if helpText: |
|
493 with contextlib.suppress(KeyError): |
|
494 self.__editors[euuid].showMouseHoverHelpData( |
|
495 result["Line"] - 1, result["Index"], helpText |
|
496 ) |
|
497 else: |
|
498 ericApp().getObject("UserInterface").statusBar().showMessage( |
|
499 self.tr("Jedi: No mouse hover help found"), 5000 |
|
500 ) |
|
501 |
|
502 with contextlib.suppress(KeyError): |
|
503 del self.__editors[euuid] |
|
504 |
|
505 ####################################################################### |
|
506 ## Refactoring methods below |
|
507 ####################################################################### |
|
508 |
|
509 @pyqtSlot() |
|
510 def refactoringRenameVariable(self): |
|
511 """ |
|
512 Public slot to rename the selected variable. |
|
513 """ |
|
514 editor = self.__vm.activeWindow() |
|
515 if editor: |
|
516 idString = self.__idString(editor) |
|
517 if not idString: |
|
518 return |
|
519 |
|
520 newName, ok = QInputDialog.getText( |
|
521 None, |
|
522 self.tr("Rename Variable"), |
|
523 self.tr("Enter the new name for the variable:"), |
|
524 QLineEdit.EchoMode.Normal, |
|
525 editor.selectedText(), |
|
526 ) |
|
527 |
|
528 if ok and newName and self.__vm.checkAllDirty(): |
|
529 filename = editor.getFileName() |
|
530 line, index = editor.getCursorPosition() |
|
531 source = editor.text() |
|
532 |
|
533 self.__ensureActive(idString) |
|
534 |
|
535 euuid = str(uuid.uuid4()) |
|
536 self.__editors[euuid] = editor |
|
537 |
|
538 self.sendJson( |
|
539 "renameVariable", |
|
540 { |
|
541 "FileName": filename, |
|
542 "Source": source, |
|
543 "Line": line + 1, |
|
544 "Index": index, |
|
545 "Uuid": euuid, |
|
546 "NewName": newName, |
|
547 }, |
|
548 idString=idString, |
|
549 ) |
|
550 |
|
551 @pyqtSlot() |
|
552 def refactoringExtractNewVariable(self): |
|
553 """ |
|
554 Public slot to extract a statement to a new variable. |
|
555 """ |
|
556 editor = self.__vm.activeWindow() |
|
557 if editor: |
|
558 idString = self.__idString(editor) |
|
559 if not idString: |
|
560 return |
|
561 |
|
562 newName, ok = QInputDialog.getText( |
|
563 None, |
|
564 self.tr("Extract Variable"), |
|
565 self.tr("Enter the name for the new variable:"), |
|
566 QLineEdit.EchoMode.Normal, |
|
567 ) |
|
568 |
|
569 if ok and newName and editor.checkDirty(): |
|
570 filename = editor.getFileName() |
|
571 sLine, sIndex, eLine, eIndex = editor.getSelection() |
|
572 source = editor.text() |
|
573 |
|
574 self.__ensureActive(idString) |
|
575 |
|
576 euuid = str(uuid.uuid4()) |
|
577 self.__editors[euuid] = editor |
|
578 |
|
579 self.sendJson( |
|
580 "extractVariable", |
|
581 { |
|
582 "FileName": filename, |
|
583 "Source": source, |
|
584 "Line": sLine + 1, |
|
585 "Index": sIndex, |
|
586 "EndLine": eLine + 1, |
|
587 "EndIndex": eIndex, |
|
588 "Uuid": euuid, |
|
589 "NewName": newName, |
|
590 }, |
|
591 idString=idString, |
|
592 ) |
|
593 |
|
594 @pyqtSlot() |
|
595 def refactoringInlineVariable(self): |
|
596 """ |
|
597 Public slot to inline the selected variable. |
|
598 |
|
599 Note: This is the opposite to Extract New Variable. |
|
600 """ |
|
601 editor = self.__vm.activeWindow() |
|
602 if editor: |
|
603 idString = self.__idString(editor) |
|
604 if not idString: |
|
605 return |
|
606 |
|
607 if editor.checkDirty(): |
|
608 filename = editor.getFileName() |
|
609 line, index = editor.getCursorPosition() |
|
610 source = editor.text() |
|
611 |
|
612 self.__ensureActive(idString) |
|
613 |
|
614 euuid = str(uuid.uuid4()) |
|
615 self.__editors[euuid] = editor |
|
616 |
|
617 self.sendJson( |
|
618 "inlineVariable", |
|
619 { |
|
620 "FileName": filename, |
|
621 "Source": source, |
|
622 "Line": line + 1, |
|
623 "Index": index, |
|
624 "Uuid": euuid, |
|
625 }, |
|
626 idString=idString, |
|
627 ) |
|
628 |
|
629 @pyqtSlot() |
|
630 def refactoringExtractFunction(self): |
|
631 """ |
|
632 Public slot to extract an expression to a function. |
|
633 """ |
|
634 editor = self.__vm.activeWindow() |
|
635 if editor: |
|
636 idString = self.__idString(editor) |
|
637 if not idString: |
|
638 return |
|
639 |
|
640 newName, ok = QInputDialog.getText( |
|
641 None, |
|
642 self.tr("Extract Function"), |
|
643 self.tr("Enter the name for the function:"), |
|
644 QLineEdit.EchoMode.Normal, |
|
645 ) |
|
646 |
|
647 if ok and newName and editor.checkDirty(): |
|
648 filename = editor.getFileName() |
|
649 sLine, sIndex, eLine, eIndex = editor.getSelection() |
|
650 source = editor.text() |
|
651 |
|
652 self.__ensureActive(idString) |
|
653 |
|
654 euuid = str(uuid.uuid4()) |
|
655 self.__editors[euuid] = editor |
|
656 |
|
657 self.sendJson( |
|
658 "extractFunction", |
|
659 { |
|
660 "FileName": filename, |
|
661 "Source": source, |
|
662 "Line": sLine + 1, |
|
663 "Index": sIndex, |
|
664 "EndLine": eLine + 1, |
|
665 "EndIndex": eIndex, |
|
666 "Uuid": euuid, |
|
667 "NewName": newName, |
|
668 }, |
|
669 idString=idString, |
|
670 ) |
|
671 |
|
672 def __showRefactoringDiff(self, result): |
|
673 """ |
|
674 Private method to show the diff of a refactoring. |
|
675 |
|
676 @param result dictionary containing the result data |
|
677 @type dict |
|
678 """ |
|
679 if "Error" not in result: |
|
680 euuid = result["Uuid"] |
|
681 diff = result["Diff"] |
|
682 dlg = RefactoringPreviewDialog(self.tr("Rename Variable"), diff) |
|
683 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
684 self.__applyRefactoring(euuid) |
|
685 else: |
|
686 self.__cancelRefactoring(euuid) |
|
687 else: |
|
688 EricMessageBox.critical( |
|
689 None, |
|
690 self.tr("Refactoring"), |
|
691 self.tr( |
|
692 "<p>The refactoring could not be performed.</p>" |
|
693 "<p>Reason: {0}</p>" |
|
694 ).format(result["ErrorString"]), |
|
695 ) |
|
696 |
|
697 def __applyRefactoring(self, uid): |
|
698 """ |
|
699 Private method to apply a given refactoring. |
|
700 |
|
701 @param uid UID of the calculated refactoring |
|
702 @type str |
|
703 """ |
|
704 with contextlib.suppress(KeyError): |
|
705 editor = self.__editors[uid] |
|
706 idString = self.__idString(editor) |
|
707 |
|
708 self.sendJson( |
|
709 "applyRefactoring", |
|
710 { |
|
711 "Uuid": uid, |
|
712 }, |
|
713 idString=idString, |
|
714 ) |
|
715 |
|
716 del self.__editors[uid] |
|
717 |
|
718 def __cancelRefactoring(self, uid): |
|
719 """ |
|
720 Private method to cancel a given refactoring. |
|
721 |
|
722 @param uid UID of the calculated refactoring |
|
723 @type str |
|
724 """ |
|
725 with contextlib.suppress(KeyError): |
|
726 editor = self.__editors[uid] |
|
727 idString = self.__idString(editor) |
|
728 |
|
729 self.sendJson( |
|
730 "cancelRefactoring", |
|
731 { |
|
732 "Uuid": uid, |
|
733 }, |
|
734 idString=idString, |
|
735 ) |
|
736 |
|
737 del self.__editors[uid] |
|
738 |
|
739 def __checkRefactoringResult(self, result): |
|
740 """ |
|
741 Private method to check the refactoring result for errors. |
|
742 |
|
743 @param result dictionary containing the result data |
|
744 @type dict |
|
745 """ |
|
746 if "Error" in result: |
|
747 EricMessageBox.critical( |
|
748 None, |
|
749 self.tr("Apply Refactoring"), |
|
750 self.tr( |
|
751 "<p>The refactoring could not be applied.</p>" "<p>Reason: {0}</p>" |
|
752 ).format(result["ErrorString"]), |
|
753 ) |
|
754 |
|
755 ####################################################################### |
|
756 ## Methods below handle the network connection |
|
757 ####################################################################### |
|
758 |
|
759 def handleCall(self, method, params): |
|
760 """ |
|
761 Public method to handle a method call from the client. |
|
762 |
|
763 @param method requested method name |
|
764 @type str |
|
765 @param params dictionary with method specific parameters |
|
766 @type dict |
|
767 """ |
|
768 self.__methodMapping[method](params) |
|
769 |
|
770 def __processClientException(self, params): |
|
771 """ |
|
772 Private method to handle exceptions of the refactoring client. |
|
773 |
|
774 @param params dictionary containing the exception data |
|
775 @type dict |
|
776 """ |
|
777 if params["ExceptionType"] == "ProtocolError": |
|
778 self.__ui.appendToStderr( |
|
779 self.tr( |
|
780 "The data received from the Jedi server could not be" |
|
781 " decoded. Please report this issue with the received" |
|
782 " data to the eric bugs email address.\n" |
|
783 "Error: {0}\n" |
|
784 "Data:\n{1}\n" |
|
785 ).format(params["ExceptionValue"], params["ProtocolData"]) |
|
786 ) |
|
787 else: |
|
788 self.__ui.appendToStderr( |
|
789 self.tr( |
|
790 "An exception happened in the Jedi client. Please" |
|
791 " report it to the eric bugs email address.\n" |
|
792 "Exception: {0}\n" |
|
793 "Value: {1}\n" |
|
794 "Traceback: {2}\n" |
|
795 ).format( |
|
796 params["ExceptionType"], |
|
797 params["ExceptionValue"], |
|
798 params["Traceback"], |
|
799 ) |
|
800 ) |
|
801 |
|
802 def __startJediClient(self, interpreter, idString, clientEnv): |
|
803 """ |
|
804 Private method to start the Jedi client with the given interpreter. |
|
805 |
|
806 @param interpreter interpreter to be used for the Jedi client |
|
807 @type str |
|
808 @param idString id of the client to be started |
|
809 @type str |
|
810 @param clientEnv dictionary with environment variables to run the |
|
811 interpreter with |
|
812 @type dict |
|
813 @return flag indicating a successful start of the client |
|
814 @rtype bool |
|
815 """ |
|
816 ok = False |
|
817 |
|
818 if interpreter: |
|
819 client = os.path.join(os.path.dirname(__file__), "JediClient.py") |
|
820 ok, exitCode = self.startClient( |
|
821 interpreter, |
|
822 client, |
|
823 [Globals.getPythonLibraryDirectory()], |
|
824 idString=idString, |
|
825 environment=clientEnv, |
|
826 ) |
|
827 if not ok: |
|
828 if exitCode == 42: |
|
829 self.__ui.appendToStderr( |
|
830 "JediServer: " |
|
831 + self.tr("The jedi and/or parso library is not installed.\n") |
|
832 ) |
|
833 else: |
|
834 self.__ui.appendToStderr( |
|
835 "JediServer: " |
|
836 + self.tr( |
|
837 "'{0}' is not supported because the configured" |
|
838 " interpreter could not be started.\n" |
|
839 ).format(idString) |
|
840 ) |
|
841 else: |
|
842 self.__ui.appendToStderr( |
|
843 "JediServer: " |
|
844 + self.tr( |
|
845 "'{0}' is not supported because no suitable interpreter is" |
|
846 " configured.\n" |
|
847 ).format(idString) |
|
848 ) |
|
849 |
|
850 return ok |
|
851 |
|
852 def __ensureActive(self, idString): |
|
853 """ |
|
854 Private method to ensure, that the requested client is active. |
|
855 |
|
856 A non-active client will be started. |
|
857 |
|
858 @param idString id of the client to be checked |
|
859 @type str |
|
860 @return flag indicating an active client |
|
861 @rtype bool |
|
862 """ |
|
863 ok = idString in self.connectionNames() |
|
864 if not ok: |
|
865 # client is not running |
|
866 if idString == JediServer.IdProject: |
|
867 interpreter, clientEnv = self.__interpreterForProject() |
|
868 else: |
|
869 interpreter = "" |
|
870 venvName = "" |
|
871 clientEnv = os.environ.copy() |
|
872 if "PATH" in clientEnv: |
|
873 clientEnv["PATH"] = self.__ui.getOriginalPathString() |
|
874 # new code using virtual environments |
|
875 venvManager = ericApp().getObject("VirtualEnvManager") |
|
876 if idString == "Python3": |
|
877 venvName = Preferences.getDebugger("Python3VirtualEnv") |
|
878 if not venvName: |
|
879 venvName, _ = venvManager.getDefaultEnvironment() |
|
880 if venvName: |
|
881 interpreter = venvManager.getVirtualenvInterpreter(venvName) |
|
882 execPath = venvManager.getVirtualenvExecPath(venvName) |
|
883 |
|
884 # build a suitable environment |
|
885 if execPath: |
|
886 if "PATH" in clientEnv: |
|
887 clientEnv["PATH"] = os.pathsep.join( |
|
888 [execPath, clientEnv["PATH"]] |
|
889 ) |
|
890 else: |
|
891 clientEnv["PATH"] = execPath |
|
892 if interpreter: |
|
893 ok = self.__startJediClient(interpreter, idString, clientEnv) |
|
894 else: |
|
895 ok = False |
|
896 return ok |
|
897 |
|
898 def __interpreterForProject(self): |
|
899 """ |
|
900 Private method to determine the interpreter for the current project and |
|
901 the environment to run it. |
|
902 |
|
903 @return tuple containing the interpreter of the current project and the |
|
904 environment variables |
|
905 @rtype tuple of (str, dict) |
|
906 """ |
|
907 projectLanguage = self.__ericProject.getProjectLanguage() |
|
908 interpreter = "" |
|
909 clientEnv = os.environ.copy() |
|
910 if "PATH" in clientEnv: |
|
911 clientEnv["PATH"] = self.__ui.getOriginalPathString() |
|
912 |
|
913 if projectLanguage in ("Python3", "MicroPython", "Cython"): |
|
914 interpreter = self.__ericProject.getProjectInterpreter(resolveGlobal=False) |
|
915 if interpreter: |
|
916 execPath = self.__ericProject.getProjectExecPath() |
|
917 |
|
918 # build a suitable environment |
|
919 if execPath: |
|
920 if "PATH" in clientEnv: |
|
921 clientEnv["PATH"] = os.pathsep.join( |
|
922 [execPath, clientEnv["PATH"]] |
|
923 ) |
|
924 else: |
|
925 clientEnv["PATH"] = execPath |
|
926 |
|
927 return interpreter, clientEnv |
|
928 |
|
929 @pyqtSlot() |
|
930 def handleNewConnection(self): |
|
931 """ |
|
932 Public slot for new incoming connections from a client. |
|
933 """ |
|
934 super().handleNewConnection() |
|
935 |
|
936 self.__updateEditorLanguageMapping() |
|
937 |
|
938 def activate(self): |
|
939 """ |
|
940 Public method to activate the Jedi server. |
|
941 """ |
|
942 self.__documentationViewer = self.__ui.documentationViewer() |
|
943 if self.__documentationViewer is not None: |
|
944 self.__documentationViewer.registerProvider( |
|
945 "jedi", |
|
946 self.tr("Jedi"), |
|
947 self.requestCodeDocumentation, |
|
948 self.isSupportedLanguage, |
|
949 ) |
|
950 |
|
951 self.__ericProject.projectOpened.connect(self.__projectOpened) |
|
952 self.__ericProject.projectClosed.connect(self.__projectClosed) |
|
953 |
|
954 def deactivate(self): |
|
955 """ |
|
956 Public method to deactivate the code assist server. |
|
957 """ |
|
958 """ |
|
959 Public method to shut down the code assist server. |
|
960 """ |
|
961 if self.__documentationViewer is not None: |
|
962 self.__documentationViewer.unregisterProvider("jedi") |
|
963 |
|
964 with contextlib.suppress(TypeError): |
|
965 self.__ericProject.projectOpened.disconnect(self.__projectOpened) |
|
966 self.__ericProject.projectClosed.disconnect(self.__projectClosed) |
|
967 |
|
968 self.stopAllClients() |
|
969 |
|
970 @pyqtSlot() |
|
971 def __projectOpened(self): |
|
972 """ |
|
973 Private slot to handle the projectOpened signal. |
|
974 """ |
|
975 self.__ensureActive(JediServer.IdProject) |
|
976 self.sendJson( |
|
977 "openProject", |
|
978 { |
|
979 "ProjectPath": self.__ericProject.getProjectPath(), |
|
980 }, |
|
981 idString=JediServer.IdProject, |
|
982 ) |
|
983 |
|
984 @pyqtSlot() |
|
985 def __projectClosed(self): |
|
986 """ |
|
987 Private slot to handle the projectClosed signal. |
|
988 """ |
|
989 self.__ensureActive(JediServer.IdProject) |
|
990 self.sendJson("closeProject", {}, idString=JediServer.IdProject) |
|
991 |
|
992 self.stopClient(idString=JediServer.IdProject) |