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