46 "module": "?{0}".format(Editor.ModuleID), |
45 "module": "?{0}".format(Editor.ModuleID), |
47 "_module": "?{0}".format(Editor.ModuleID), |
46 "_module": "?{0}".format(Editor.ModuleID), |
48 "__module": "?{0}".format(Editor.ModuleID), |
47 "__module": "?{0}".format(Editor.ModuleID), |
49 "None": "", |
48 "None": "", |
50 } |
49 } |
51 |
50 |
52 def __init__(self, plugin, parent=None): |
51 def __init__(self, plugin, parent=None): |
53 """ |
52 """ |
54 Constructor |
53 Constructor |
55 |
54 |
56 @param plugin reference to the plugin object |
55 @param plugin reference to the plugin object |
57 @type RefactoringRopePlugin |
56 @type RefactoringRopePlugin |
58 @param parent parent |
57 @param parent parent |
59 @type QObject |
58 @type QObject |
60 """ |
59 """ |
61 super().__init__( |
60 super().__init__("CodeAssistServer", multiplex=True, parent=parent) |
62 "CodeAssistServer", multiplex=True, parent=parent) |
61 |
63 |
|
64 self.__plugin = plugin |
62 self.__plugin = plugin |
65 self.__ui = parent |
63 self.__ui = parent |
66 self.__vm = ericApp().getObject("ViewManager") |
64 self.__vm = ericApp().getObject("ViewManager") |
67 self.__e5project = ericApp().getObject("Project") |
65 self.__e5project = ericApp().getObject("Project") |
68 |
66 |
69 self.__editorLanguageMapping = {} |
67 self.__editorLanguageMapping = {} |
70 self.__clientConfigs = {} |
68 self.__clientConfigs = {} |
71 self.__editors = {} |
69 self.__editors = {} |
72 |
70 |
73 self.__documentationViewer = None |
71 self.__documentationViewer = None |
74 |
72 |
75 # attributes to store the resuls of the client side |
73 # attributes to store the resuls of the client side |
76 self.__completions = None |
74 self.__completions = None |
77 self.__calltips = None |
75 self.__calltips = None |
78 |
76 |
79 self.__methodMapping = { |
77 self.__methodMapping = { |
80 "Config": self.__setConfig, |
78 "Config": self.__setConfig, |
81 "CompletionsResult": self.__processCompletionsResult, |
79 "CompletionsResult": self.__processCompletionsResult, |
82 "CallTipsResult": self.__processCallTipsResult, |
80 "CallTipsResult": self.__processCallTipsResult, |
83 "DocumentationResult": self.__processDocumentationResult, |
81 "DocumentationResult": self.__processDocumentationResult, |
84 "GotoDefinitionResult": self.__gotoDefinitionResult, |
82 "GotoDefinitionResult": self.__gotoDefinitionResult, |
85 "GotoReferencesResult": self.__processGotoReferencesResult, |
83 "GotoReferencesResult": self.__processGotoReferencesResult, |
86 |
|
87 "ClientException": self.__processClientException, |
84 "ClientException": self.__processClientException, |
88 } |
85 } |
89 |
86 |
90 self.__typeMapping = { |
87 self.__typeMapping = { |
91 "staticmethod": self.tr("static method"), |
88 "staticmethod": self.tr("static method"), |
92 "classmethod": self.tr("class method"), |
89 "classmethod": self.tr("class method"), |
93 "method": self.tr("method"), |
90 "method": self.tr("method"), |
94 "function": self.tr("function"), |
91 "function": self.tr("function"), |
96 "module": self.tr("module"), |
93 "module": self.tr("module"), |
97 "package": self.tr("package"), |
94 "package": self.tr("package"), |
98 "object": self.tr("object"), |
95 "object": self.tr("object"), |
99 "<unknown>": self.tr("not known"), |
96 "<unknown>": self.tr("not known"), |
100 } |
97 } |
101 |
98 |
102 # temporary store for editor references indexed by Uuid |
99 # temporary store for editor references indexed by Uuid |
103 self.__editors = {} |
100 self.__editors = {} |
104 |
101 |
105 # Python 3 |
102 # Python 3 |
106 self.__ensureActive("Python3") |
103 self.__ensureActive("Python3") |
107 |
104 |
108 def __updateEditorLanguageMapping(self): |
105 def __updateEditorLanguageMapping(self): |
109 """ |
106 """ |
110 Private method to update the editor language to connection mapping. |
107 Private method to update the editor language to connection mapping. |
111 """ |
108 """ |
112 self.__editorLanguageMapping = {} |
109 self.__editorLanguageMapping = {} |
113 for name in self.connectionNames(): |
110 for name in self.connectionNames(): |
114 if name == "Python3": |
111 if name == "Python3": |
115 self.__editorLanguageMapping.update({ |
112 self.__editorLanguageMapping.update( |
116 "Python3": "Python3", |
113 { |
117 "MicroPython": "Python3", |
114 "Python3": "Python3", |
118 "Pygments|Python": "Python3", |
115 "MicroPython": "Python3", |
119 "Pygments|Python 2.x": "Python3", |
116 "Pygments|Python": "Python3", |
120 "Cython": "Python3", |
117 "Pygments|Python 2.x": "Python3", |
121 }) |
118 "Cython": "Python3", |
122 |
119 } |
|
120 ) |
|
121 |
123 def isSupportedLanguage(self, language): |
122 def isSupportedLanguage(self, language): |
124 """ |
123 """ |
125 Public method to check, if the given language is supported. |
124 Public method to check, if the given language is supported. |
126 |
125 |
127 @param language editor programming language to check |
126 @param language editor programming language to check |
128 @type str |
127 @type str |
129 @return flag indicating the support status |
128 @return flag indicating the support status |
130 @rtype bool |
129 @rtype bool |
131 """ |
130 """ |
132 return language in self.__editorLanguageMapping |
131 return language in self.__editorLanguageMapping |
133 |
132 |
134 def __idString(self, editor): |
133 def __idString(self, editor): |
135 """ |
134 """ |
136 Private method to determine the ID string for the back-end. |
135 Private method to determine the ID string for the back-end. |
137 |
136 |
138 @param editor reference to the editor to determine the ID string for |
137 @param editor reference to the editor to determine the ID string for |
139 @type Editor |
138 @type Editor |
140 @return ID string |
139 @return ID string |
141 @rtype str |
140 @rtype str |
142 """ |
141 """ |
143 idString = "" |
142 idString = "" |
144 |
143 |
145 language = editor.getLanguage() |
144 language = editor.getLanguage() |
146 if ( |
145 if ( |
147 self.__e5project.isOpen() and |
146 self.__e5project.isOpen() |
148 self.__e5project.getProjectLanguage() == language |
147 and self.__e5project.getProjectLanguage() == language |
149 ): |
148 ): |
150 filename = editor.getFileName() |
149 filename = editor.getFileName() |
151 if self.__e5project.isProjectSource(filename): |
150 if self.__e5project.isProjectSource(filename): |
152 idString = CodeAssistServer.IdProject |
151 idString = CodeAssistServer.IdProject |
153 |
152 |
154 if not idString and language in self.__editorLanguageMapping: |
153 if not idString and language in self.__editorLanguageMapping: |
155 idString = self.__editorLanguageMapping[language] |
154 idString = self.__editorLanguageMapping[language] |
156 |
155 |
157 return idString |
156 return idString |
158 |
157 |
159 def __getConfigs(self): |
158 def __getConfigs(self): |
160 """ |
159 """ |
161 Private method to get the configurations of all connected clients. |
160 Private method to get the configurations of all connected clients. |
162 """ |
161 """ |
163 for idString in self.connectionNames(): |
162 for idString in self.connectionNames(): |
164 self.sendJson("getConfig", {}, idString=idString) |
163 self.sendJson("getConfig", {}, idString=idString) |
165 |
164 |
166 def __setConfig(self, params): |
165 def __setConfig(self, params): |
167 """ |
166 """ |
168 Private method to set the rope client configuration data. |
167 Private method to set the rope client configuration data. |
169 |
168 |
170 @param params dictionary containing the configuration data |
169 @param params dictionary containing the configuration data |
171 @type dict |
170 @type dict |
172 """ |
171 """ |
173 idString = params["Id"] |
172 idString = params["Id"] |
174 ropeFolder = params["RopeFolderName"] |
173 ropeFolder = params["RopeFolderName"] |
175 |
174 |
176 self.__clientConfigs[idString] = ropeFolder |
175 self.__clientConfigs[idString] = ropeFolder |
177 |
176 |
178 def __ropeConfigFile(self, idString): |
177 def __ropeConfigFile(self, idString): |
179 """ |
178 """ |
180 Private method to get the name of the rope configuration file. |
179 Private method to get the name of the rope configuration file. |
181 |
180 |
182 @param idString id for which to get the configuration file |
181 @param idString id for which to get the configuration file |
183 @type str |
182 @type str |
184 @return name of the rope configuration file |
183 @return name of the rope configuration file |
185 @rtype str |
184 @rtype str |
186 """ |
185 """ |
190 if ropedir: |
189 if ropedir: |
191 configfile = os.path.join(ropedir, "config.py") |
190 configfile = os.path.join(ropedir, "config.py") |
192 if not os.path.exists(configfile): |
191 if not os.path.exists(configfile): |
193 configfile = None |
192 configfile = None |
194 return configfile |
193 return configfile |
195 |
194 |
196 def __configChanged(self, idString): |
195 def __configChanged(self, idString): |
197 """ |
196 """ |
198 Private slot called, when the rope config file has changed. |
197 Private slot called, when the rope config file has changed. |
199 |
198 |
200 @param idString id for which to get the configuration file |
199 @param idString id for which to get the configuration file |
201 @type str |
200 @type str |
202 """ |
201 """ |
203 self.sendJson("configChanged", {}, idString=idString) |
202 self.sendJson("configChanged", {}, idString=idString) |
204 |
203 |
205 def editConfig(self, idString): |
204 def editConfig(self, idString): |
206 """ |
205 """ |
207 Public slot to open the rope configuration file in an editor. |
206 Public slot to open the rope configuration file in an editor. |
208 |
207 |
209 @param idString id for which to get the configuration file |
208 @param idString id for which to get the configuration file |
210 @type str |
209 @type str |
211 """ |
210 """ |
212 configfile = self.__ropeConfigFile(idString) |
211 configfile = self.__ropeConfigFile(idString) |
213 if configfile: |
212 if configfile: |
214 if os.path.exists(configfile): |
213 if os.path.exists(configfile): |
215 from QScintilla.MiniEditor import MiniEditor |
214 from QScintilla.MiniEditor import MiniEditor |
|
215 |
216 editor = MiniEditor(configfile) |
216 editor = MiniEditor(configfile) |
217 editor.show() |
217 editor.show() |
218 editor.editorSaved.connect( |
218 editor.editorSaved.connect(lambda: self.__configChanged(idString)) |
219 lambda: self.__configChanged(idString)) |
|
220 self.__editors[idString] = editor |
219 self.__editors[idString] = editor |
221 return |
220 return |
222 else: |
221 else: |
223 EricMessageBox.critical( |
222 EricMessageBox.critical( |
224 self.__ui, |
223 self.__ui, |
225 self.tr("Configure Rope"), |
224 self.tr("Configure Rope"), |
226 self.tr("""The Rope configuration file '{0}' does""" |
225 self.tr( |
227 """ not exist.""").format(configfile)) |
226 """The Rope configuration file '{0}' does""" """ not exist.""" |
228 |
227 ).format(configfile), |
|
228 ) |
|
229 |
229 def requestCompletions(self, editor, context, acText): |
230 def requestCompletions(self, editor, context, acText): |
230 """ |
231 """ |
231 Public method to request a list of possible completions. |
232 Public method to request a list of possible completions. |
232 |
233 |
233 @param editor reference to the editor object, that called this method |
234 @param editor reference to the editor object, that called this method |
234 @type Editor |
235 @type Editor |
235 @param context flag indicating to autocomplete a context |
236 @param context flag indicating to autocomplete a context |
236 @type bool |
237 @type bool |
237 @param acText text to be completed |
238 @param acText text to be completed |
238 @type str |
239 @type str |
239 """ |
240 """ |
240 if not self.__plugin.getPreferences("CodeAssistEnabled"): |
241 if not self.__plugin.getPreferences("CodeAssistEnabled"): |
241 return |
242 return |
242 |
243 |
243 idString = self.__idString(editor) |
244 idString = self.__idString(editor) |
244 if not idString: |
245 if not idString: |
245 return |
246 return |
246 |
247 |
247 filename = editor.getFileName() |
248 filename = editor.getFileName() |
248 line, index = editor.getCursorPosition() |
249 line, index = editor.getCursorPosition() |
249 source = editor.text() |
250 source = editor.text() |
250 offset = len("".join(source.splitlines(True)[:line])) + index |
251 offset = len("".join(source.splitlines(True)[:line])) + index |
251 maxfixes = self.__plugin.getPreferences("MaxFixes") |
252 maxfixes = self.__plugin.getPreferences("MaxFixes") |
252 |
253 |
253 self.__ensureActive(idString) |
254 self.__ensureActive(idString) |
254 self.sendJson("getCompletions", { |
255 self.sendJson( |
255 "FileName": filename, |
256 "getCompletions", |
256 "Source": source, |
257 { |
257 "Offset": offset, |
258 "FileName": filename, |
258 "MaxFixes": maxfixes, |
259 "Source": source, |
259 "CompletionText": acText, |
260 "Offset": offset, |
260 "SysPath": sys.path, |
261 "MaxFixes": maxfixes, |
261 }, idString=idString) |
262 "CompletionText": acText, |
262 |
263 "SysPath": sys.path, |
|
264 }, |
|
265 idString=idString, |
|
266 ) |
|
267 |
263 def __processCompletionsResult(self, result): |
268 def __processCompletionsResult(self, result): |
264 """ |
269 """ |
265 Private method to process the completions sent by the client. |
270 Private method to process the completions sent by the client. |
266 |
271 |
267 @param result dictionary containing the result sent by the client |
272 @param result dictionary containing the result sent by the client |
268 @type dict |
273 @type dict |
269 """ |
274 """ |
270 names = [] |
275 names = [] |
271 for completion in result["Completions"]: |
276 for completion in result["Completions"]: |
272 name = completion['Name'] |
277 name = completion["Name"] |
273 |
278 |
274 name += CodeAssistServer.PictureIDs.get( |
279 name += CodeAssistServer.PictureIDs.get(completion["CompletionType"], "") |
275 completion['CompletionType'], '') |
|
276 names.append(name) |
280 names.append(name) |
277 |
281 |
278 if "Error" not in result: |
282 if "Error" not in result: |
279 editor = self.__vm.getOpenEditor(result["FileName"]) |
283 editor = self.__vm.getOpenEditor(result["FileName"]) |
280 if editor is not None: |
284 if editor is not None: |
281 editor.completionsListReady(names, |
285 editor.completionsListReady(names, result["CompletionText"]) |
282 result["CompletionText"]) |
286 |
283 |
|
284 def getCallTips(self, editor, pos, commas): |
287 def getCallTips(self, editor, pos, commas): |
285 """ |
288 """ |
286 Public method to calculate calltips. |
289 Public method to calculate calltips. |
287 |
290 |
288 @param editor reference to the editor object, that called this method |
291 @param editor reference to the editor object, that called this method |
289 @type Editor |
292 @type Editor |
290 @param pos position in the text for the calltip |
293 @param pos position in the text for the calltip |
291 @type int |
294 @type int |
292 @param commas minimum number of commas contained in the calltip |
295 @param commas minimum number of commas contained in the calltip |
294 @return list of possible calltips |
297 @return list of possible calltips |
295 @rtype list of str |
298 @rtype list of str |
296 """ |
299 """ |
297 if not self.__plugin.getPreferences("CodeAssistCalltipsEnabled"): |
300 if not self.__plugin.getPreferences("CodeAssistCalltipsEnabled"): |
298 return [] |
301 return [] |
299 |
302 |
300 # reset the calltips buffer |
303 # reset the calltips buffer |
301 self.__calltips = None |
304 self.__calltips = None |
302 |
305 |
303 idString = self.__idString(editor) |
306 idString = self.__idString(editor) |
304 if not idString: |
307 if not idString: |
305 return [] |
308 return [] |
306 |
309 |
307 filename = editor.getFileName() |
310 filename = editor.getFileName() |
308 source = editor.text() |
311 source = editor.text() |
309 line, index = editor.lineIndexFromPosition(pos) |
312 line, index = editor.lineIndexFromPosition(pos) |
310 offset = len("".join(source.splitlines(True)[:line])) + index |
313 offset = len("".join(source.splitlines(True)[:line])) + index |
311 maxfixes = self.__plugin.getPreferences("CalltipsMaxFixes") |
314 maxfixes = self.__plugin.getPreferences("CalltipsMaxFixes") |
312 |
315 |
313 self.__ensureActive(idString) |
316 self.__ensureActive(idString) |
314 self.sendJson("getCallTips", { |
317 self.sendJson( |
315 "FileName": filename, |
318 "getCallTips", |
316 "Source": source, |
319 { |
317 "Offset": offset, |
320 "FileName": filename, |
318 "MaxFixes": maxfixes, |
321 "Source": source, |
319 "SysPath": sys.path, |
322 "Offset": offset, |
320 }, idString=idString) |
323 "MaxFixes": maxfixes, |
321 |
324 "SysPath": sys.path, |
|
325 }, |
|
326 idString=idString, |
|
327 ) |
|
328 |
322 # emulate the synchronous behaviour |
329 # emulate the synchronous behaviour |
323 timer = QTimer() |
330 timer = QTimer() |
324 timer.setSingleShot(True) |
331 timer.setSingleShot(True) |
325 timer.start(5000) # 5s timeout |
332 timer.start(5000) # 5s timeout |
326 while self.__calltips is None and timer.isActive(): |
333 while self.__calltips is None and timer.isActive(): |
327 QCoreApplication.processEvents() |
334 QCoreApplication.processEvents() |
328 |
335 |
329 return [] if self.__calltips is None else self.__calltips |
336 return [] if self.__calltips is None else self.__calltips |
330 |
337 |
331 def __processCallTipsResult(self, result): |
338 def __processCallTipsResult(self, result): |
332 """ |
339 """ |
333 Private method to process the calltips sent by the client. |
340 Private method to process the calltips sent by the client. |
334 |
341 |
335 @param result dictionary containing the result sent by the client |
342 @param result dictionary containing the result sent by the client |
336 @type dict |
343 @type dict |
337 """ |
344 """ |
338 if "Error" in result: |
345 if "Error" in result: |
339 self.__calltips = [] |
346 self.__calltips = [] |
340 else: |
347 else: |
341 self.__calltips = result["CallTips"] |
348 self.__calltips = result["CallTips"] |
342 |
349 |
343 def reportChanged(self, filename, oldSource): |
350 def reportChanged(self, filename, oldSource): |
344 """ |
351 """ |
345 Public slot to report some changed sources. |
352 Public slot to report some changed sources. |
346 |
353 |
347 @param filename file name of the changed source |
354 @param filename file name of the changed source |
348 @type str |
355 @type str |
349 @param oldSource source code before the change |
356 @param oldSource source code before the change |
350 @type str |
357 @type str |
351 """ |
358 """ |
352 editor = self.__vm.getOpenEditor(filename) |
359 editor = self.__vm.getOpenEditor(filename) |
353 if editor is not None: |
360 if editor is not None: |
354 idString = self.__idString(editor) |
361 idString = self.__idString(editor) |
355 if idString: |
362 if idString: |
356 self.__ensureActive(idString) |
363 self.__ensureActive(idString) |
357 self.sendJson("reportChanged", { |
364 self.sendJson( |
358 "FileName": filename, |
365 "reportChanged", |
359 "OldSource": oldSource, |
366 { |
360 }, idString=idString) |
367 "FileName": filename, |
361 |
368 "OldSource": oldSource, |
|
369 }, |
|
370 idString=idString, |
|
371 ) |
|
372 |
362 def requestCodeDocumentation(self, editor): |
373 def requestCodeDocumentation(self, editor): |
363 """ |
374 """ |
364 Public method to request source code documentation for the given |
375 Public method to request source code documentation for the given |
365 editor. |
376 editor. |
366 |
377 |
367 @param editor reference to the editor to get source code documentation |
378 @param editor reference to the editor to get source code documentation |
368 for |
379 for |
369 @type Editor |
380 @type Editor |
370 """ |
381 """ |
371 if self.__documentationViewer is None: |
382 if self.__documentationViewer is None: |
372 return |
383 return |
373 |
384 |
374 idString = self.__idString(editor) |
385 idString = self.__idString(editor) |
375 |
386 |
376 if not idString: |
387 if not idString: |
377 language = editor.getLanguage() |
388 language = editor.getLanguage() |
378 warning = self.tr( |
389 warning = self.tr("Language <b>{0}</b> is not supported.").format(language) |
379 "Language <b>{0}</b> is not supported." |
390 self.__documentationViewer.documentationReady(warning, isWarning=True) |
380 ).format(language) |
|
381 self.__documentationViewer.documentationReady( |
|
382 warning, isWarning=True) |
|
383 return |
391 return |
384 |
392 |
385 filename = editor.getFileName() |
393 filename = editor.getFileName() |
386 source = editor.text() |
394 source = editor.text() |
387 line, index = editor.getCursorPosition() |
395 line, index = editor.getCursorPosition() |
388 offset = len("".join(source.splitlines(True)[:line])) + index |
396 offset = len("".join(source.splitlines(True)[:line])) + index |
389 maxfixes = self.__plugin.getPreferences("CalltipsMaxFixes") |
397 maxfixes = self.__plugin.getPreferences("CalltipsMaxFixes") |
390 |
398 |
391 offset = editor.positionBefore(offset) |
399 offset = editor.positionBefore(offset) |
392 if editor.charAt(offset) == "(": |
400 if editor.charAt(offset) == "(": |
393 offset = editor.positionBefore(offset) |
401 offset = editor.positionBefore(offset) |
394 |
402 |
395 self.__ensureActive(idString) |
403 self.__ensureActive(idString) |
396 self.sendJson("getDocumentation", { |
404 self.sendJson( |
397 "FileName": filename, |
405 "getDocumentation", |
398 "Source": source, |
406 { |
399 "Offset": offset, |
407 "FileName": filename, |
400 "MaxFixes": maxfixes, |
408 "Source": source, |
401 "SysPath": sys.path, |
409 "Offset": offset, |
402 }, idString=idString) |
410 "MaxFixes": maxfixes, |
403 |
411 "SysPath": sys.path, |
|
412 }, |
|
413 idString=idString, |
|
414 ) |
|
415 |
404 def __processDocumentationResult(self, result): |
416 def __processDocumentationResult(self, result): |
405 """ |
417 """ |
406 Private method to process the documentation sent by the client. |
418 Private method to process the documentation sent by the client. |
407 |
419 |
408 @param result dictionary containing the result sent by the client |
420 @param result dictionary containing the result sent by the client |
409 @type dict with keys 'name', 'argspec', 'note', 'docstring', 'typ' |
421 @type dict with keys 'name', 'argspec', 'note', 'docstring', 'typ' |
410 """ |
422 """ |
411 if self.__documentationViewer is None: |
423 if self.__documentationViewer is None: |
412 return |
424 return |
413 |
425 |
414 docu = None |
426 docu = None |
415 |
427 |
416 if "Error" not in result: |
428 if "Error" not in result: |
417 documentationDict = result["DocumentationDict"] |
429 documentationDict = result["DocumentationDict"] |
418 if documentationDict: |
430 if documentationDict: |
419 if "module" in documentationDict: |
431 if "module" in documentationDict: |
420 if documentationDict["module"]: |
432 if documentationDict["module"]: |
421 documentationDict["note"] = self.tr( |
433 documentationDict["note"] = self.tr( |
422 "Present in <i>{0}</i> module" |
434 "Present in <i>{0}</i> module" |
423 ).format(documentationDict["module"]) |
435 ).format(documentationDict["module"]) |
424 del documentationDict["module"] |
436 del documentationDict["module"] |
425 |
437 |
426 if "typ" in documentationDict: |
438 if "typ" in documentationDict: |
427 if documentationDict["typ"] not in self.__typeMapping: |
439 if documentationDict["typ"] not in self.__typeMapping: |
428 del documentationDict["typ"] |
440 del documentationDict["typ"] |
429 else: |
441 else: |
430 documentationDict["typ"] = self.__typeMapping[ |
442 documentationDict["typ"] = self.__typeMapping[ |
431 documentationDict["typ"]] |
443 documentationDict["typ"] |
432 |
444 ] |
|
445 |
433 if "note" not in documentationDict: |
446 if "note" not in documentationDict: |
434 documentationDict["note"] = "" |
447 documentationDict["note"] = "" |
435 |
448 |
436 docu = documentationDict |
449 docu = documentationDict |
437 |
450 |
438 if docu is None: |
451 if docu is None: |
439 msg = self.tr("No documentation available.") |
452 msg = self.tr("No documentation available.") |
440 self.__documentationViewer.documentationReady( |
453 self.__documentationViewer.documentationReady(msg, isDocWarning=True) |
441 msg, isDocWarning=True) |
|
442 else: |
454 else: |
443 self.__documentationViewer.documentationReady(docu) |
455 self.__documentationViewer.documentationReady(docu) |
444 |
456 |
445 def gotoDefinition(self, editor): |
457 def gotoDefinition(self, editor): |
446 """ |
458 """ |
447 Public slot to find the definition for the word at the cursor position |
459 Public slot to find the definition for the word at the cursor position |
448 and go to it. |
460 and go to it. |
449 |
461 |
450 Note: This is executed upon a mouse click sequence. |
462 Note: This is executed upon a mouse click sequence. |
451 |
463 |
452 @param editor reference to the calling editor |
464 @param editor reference to the calling editor |
453 @type Editor |
465 @type Editor |
454 """ |
466 """ |
455 if not self.__plugin.getPreferences("MouseClickEnabled"): |
467 if not self.__plugin.getPreferences("MouseClickEnabled"): |
456 return |
468 return |
457 |
469 |
458 idString = self.__idString(editor) |
470 idString = self.__idString(editor) |
459 if not idString: |
471 if not idString: |
460 return |
472 return |
461 |
473 |
462 filename = editor.getFileName() |
474 filename = editor.getFileName() |
463 source = editor.text() |
475 source = editor.text() |
464 line, index = editor.getCursorPosition() |
476 line, index = editor.getCursorPosition() |
465 offset = len("".join(source.splitlines(True)[:line])) + index |
477 offset = len("".join(source.splitlines(True)[:line])) + index |
466 |
478 |
467 self.__ensureActive(idString) |
479 self.__ensureActive(idString) |
468 |
480 |
469 euuid = str(uuid.uuid4()) |
481 euuid = str(uuid.uuid4()) |
470 self.__editors[euuid] = editor |
482 self.__editors[euuid] = editor |
471 |
483 |
472 self.sendJson("gotoDefinition", { |
484 self.sendJson( |
473 "FileName": filename, |
485 "gotoDefinition", |
474 "Offset": offset, |
486 { |
475 "Source": source, |
487 "FileName": filename, |
476 "SysPath": sys.path, |
488 "Offset": offset, |
477 "Uuid": euuid, |
489 "Source": source, |
478 }, idString=idString) |
490 "SysPath": sys.path, |
479 |
491 "Uuid": euuid, |
|
492 }, |
|
493 idString=idString, |
|
494 ) |
|
495 |
480 def __gotoDefinitionResult(self, result): |
496 def __gotoDefinitionResult(self, result): |
481 """ |
497 """ |
482 Private method to handle the "Goto Definition" result sent by |
498 Private method to handle the "Goto Definition" result sent by |
483 the client. |
499 the client. |
484 |
500 |
485 @param result dictionary containing the result data |
501 @param result dictionary containing the result data |
486 @type dict |
502 @type dict |
487 """ |
503 """ |
488 if "Error" not in result: |
504 if "Error" not in result: |
489 # ignore errors silently |
505 # ignore errors silently |
492 if location: |
508 if location: |
493 try: |
509 try: |
494 editor = self.__editors[euuid] |
510 editor = self.__editors[euuid] |
495 except KeyError: |
511 except KeyError: |
496 editor = None |
512 editor = None |
497 |
513 |
498 if ( |
514 if ( |
499 editor is not None and |
515 editor is not None |
500 editor.getFileName() == location["ModulePath"] and |
516 and editor.getFileName() == location["ModulePath"] |
501 editor.getCursorPosition()[0] + 1 == location["Line"] |
517 and editor.getCursorPosition()[0] + 1 == location["Line"] |
502 ): |
518 ): |
503 # this was a click onto the definition line |
519 # this was a click onto the definition line |
504 # -> get references |
520 # -> get references |
505 idString = self.__idString(editor) |
521 idString = self.__idString(editor) |
506 filename = editor.getFileName() |
522 filename = editor.getFileName() |
507 source = editor.text() |
523 source = editor.text() |
508 line, index = editor.getCursorPosition() |
524 line, index = editor.getCursorPosition() |
509 offset = ( |
525 offset = len("".join(source.splitlines(True)[:line])) + index |
510 len("".join(source.splitlines(True)[:line])) + |
526 self.sendJson( |
511 index |
527 "gotoReferences", |
|
528 { |
|
529 "FileName": filename, |
|
530 "Offset": offset, |
|
531 "Line": line + 1, |
|
532 "SysPath": sys.path, |
|
533 "Uuid": euuid, |
|
534 }, |
|
535 idString=idString, |
512 ) |
536 ) |
513 self.sendJson("gotoReferences", { |
|
514 "FileName": filename, |
|
515 "Offset": offset, |
|
516 "Line": line + 1, |
|
517 "SysPath": sys.path, |
|
518 "Uuid": euuid, |
|
519 }, idString=idString) |
|
520 return |
537 return |
521 |
538 |
522 self.__vm.openSourceFile(location["ModulePath"], |
539 self.__vm.openSourceFile( |
523 location["Line"], |
540 location["ModulePath"], location["Line"], addNext=True |
524 addNext=True) |
541 ) |
525 else: |
542 else: |
526 ericApp().getObject("UserInterface").statusBar().showMessage( |
543 ericApp().getObject("UserInterface").statusBar().showMessage( |
527 self.tr('Code Assist: No definition found'), 5000) |
544 self.tr("Code Assist: No definition found"), 5000 |
528 |
545 ) |
|
546 |
529 with contextlib.suppress(KeyError): |
547 with contextlib.suppress(KeyError): |
530 del self.__editors[euuid] |
548 del self.__editors[euuid] |
531 |
549 |
532 def __processGotoReferencesResult(self, result): |
550 def __processGotoReferencesResult(self, result): |
533 """ |
551 """ |
534 Private method callback for the goto references result. |
552 Private method callback for the goto references result. |
535 |
553 |
536 @param result dictionary containing the result data |
554 @param result dictionary containing the result data |
537 @type dict |
555 @type dict |
538 """ |
556 """ |
539 with contextlib.suppress(ImportError): |
557 with contextlib.suppress(ImportError): |
540 from QScintilla.Editor import ReferenceItem |
558 from QScintilla.Editor import ReferenceItem |
541 |
559 |
542 if "Error" not in result: |
560 if "Error" not in result: |
543 # ignore errors silently |
561 # ignore errors silently |
544 references = result["GotoReferencesList"] |
562 references = result["GotoReferencesList"] |
545 euuid = result["Uuid"] |
563 euuid = result["Uuid"] |
546 if references: |
564 if references: |
553 ReferenceItem( |
571 ReferenceItem( |
554 modulePath=ref["ModulePath"], |
572 modulePath=ref["ModulePath"], |
555 codeLine=ref["Code"], |
573 codeLine=ref["Code"], |
556 line=ref["Line"], |
574 line=ref["Line"], |
557 column=0, |
575 column=0, |
558 ) for ref in references |
576 ) |
|
577 for ref in references |
559 ] |
578 ] |
560 editor.gotoReferenceHandler(referenceItemsList) |
579 editor.gotoReferenceHandler(referenceItemsList) |
561 |
580 |
562 with contextlib.suppress(KeyError): |
581 with contextlib.suppress(KeyError): |
563 del self.__editors[euuid] |
582 del self.__editors[euuid] |
564 |
583 |
565 ####################################################################### |
584 ####################################################################### |
566 ## Methods below handle the network connection |
585 ## Methods below handle the network connection |
567 ####################################################################### |
586 ####################################################################### |
568 |
587 |
569 def handleCall(self, method, params): |
588 def handleCall(self, method, params): |
570 """ |
589 """ |
571 Public method to handle a method call from the client. |
590 Public method to handle a method call from the client. |
572 |
591 |
573 @param method requested method name |
592 @param method requested method name |
574 @type str |
593 @type str |
575 @param params dictionary with method specific parameters |
594 @param params dictionary with method specific parameters |
576 @type dict |
595 @type dict |
577 """ |
596 """ |
578 self.__methodMapping[method](params) |
597 self.__methodMapping[method](params) |
579 |
598 |
580 def __processClientException(self, params): |
599 def __processClientException(self, params): |
581 """ |
600 """ |
582 Private method to handle exceptions of the refactoring client. |
601 Private method to handle exceptions of the refactoring client. |
583 |
602 |
584 @param params dictionary containing the exception data |
603 @param params dictionary containing the exception data |
585 @type dict |
604 @type dict |
586 """ |
605 """ |
587 if params["ExceptionType"] == "ProtocolError": |
606 if params["ExceptionType"] == "ProtocolError": |
588 self.__ui.appendToStderr( |
607 self.__ui.appendToStderr( |
589 self.tr("The data received from the code assist" |
608 self.tr( |
590 " server could not be decoded. Please report" |
609 "The data received from the code assist" |
591 " this issue with the received data to the" |
610 " server could not be decoded. Please report" |
592 " eric bugs email address.\n" |
611 " this issue with the received data to the" |
593 "Error: {0}\n" |
612 " eric bugs email address.\n" |
594 "Data:\n{1}\n").format( |
613 "Error: {0}\n" |
595 params["ExceptionValue"], |
614 "Data:\n{1}\n" |
596 params["ProtocolData"])) |
615 ).format(params["ExceptionValue"], params["ProtocolData"]) |
|
616 ) |
597 else: |
617 else: |
598 self.__ui.appendToStderr( |
618 self.__ui.appendToStderr( |
599 self.tr("An exception happened in the code assist" |
619 self.tr( |
600 " client. Please report it to the eric bugs" |
620 "An exception happened in the code assist" |
601 " email address.\n" |
621 " client. Please report it to the eric bugs" |
602 "Exception: {0}\n" |
622 " email address.\n" |
603 "Value: {1}\n" |
623 "Exception: {0}\n" |
604 "Traceback: {2}\n").format( |
624 "Value: {1}\n" |
|
625 "Traceback: {2}\n" |
|
626 ).format( |
605 params["ExceptionType"], |
627 params["ExceptionType"], |
606 params["ExceptionValue"], |
628 params["ExceptionValue"], |
607 params["Traceback"])) |
629 params["Traceback"], |
608 |
630 ) |
|
631 ) |
|
632 |
609 def __startCodeAssistClient(self, interpreter, idString, clientEnv): |
633 def __startCodeAssistClient(self, interpreter, idString, clientEnv): |
610 """ |
634 """ |
611 Private method to start the code assist client with the given |
635 Private method to start the code assist client with the given |
612 interpreter. |
636 interpreter. |
613 |
637 |
614 @param interpreter interpreter to be used for the code assist client |
638 @param interpreter interpreter to be used for the code assist client |
615 @type str |
639 @type str |
616 @param idString id of the client to be started |
640 @param idString id of the client to be started |
617 @type str |
641 @type str |
618 @param clientEnv dictionary with environment variables to run the |
642 @param clientEnv dictionary with environment variables to run the |
620 @type dict |
644 @type dict |
621 @return flag indicating a successful start of the client |
645 @return flag indicating a successful start of the client |
622 @rtype bool |
646 @rtype bool |
623 """ |
647 """ |
624 ok = False |
648 ok = False |
625 |
649 |
626 if interpreter: |
650 if interpreter: |
627 if idString == CodeAssistServer.IdProject: |
651 if idString == CodeAssistServer.IdProject: |
628 configDir = self.__e5project.getProjectPath() |
652 configDir = self.__e5project.getProjectPath() |
629 else: |
653 else: |
630 configDir = os.path.join(Globals.getConfigDir(), "rope", |
654 configDir = os.path.join(Globals.getConfigDir(), "rope", idString) |
631 idString) |
|
632 if not os.path.exists(configDir): |
655 if not os.path.exists(configDir): |
633 os.makedirs(configDir) |
656 os.makedirs(configDir) |
634 |
657 |
635 client = os.path.join(os.path.dirname(__file__), |
658 client = os.path.join(os.path.dirname(__file__), "CodeAssistClient.py") |
636 "CodeAssistClient.py") |
|
637 ok, exitCode = self.startClient( |
659 ok, exitCode = self.startClient( |
638 interpreter, client, |
660 interpreter, |
|
661 client, |
639 clientArgs=[configDir, Globals.getPythonLibraryDirectory()], |
662 clientArgs=[configDir, Globals.getPythonLibraryDirectory()], |
640 idString=idString, environment=clientEnv) |
663 idString=idString, |
|
664 environment=clientEnv, |
|
665 ) |
641 if not ok: |
666 if not ok: |
642 if exitCode == 42: |
667 if exitCode == 42: |
643 self.__ui.appendToStderr("CodeAssistServer: " + self.tr( |
668 self.__ui.appendToStderr( |
644 "The rope refactoring library is not installed.\n" |
669 "CodeAssistServer: " |
645 )) |
670 + self.tr("The rope refactoring library is not installed.\n") |
|
671 ) |
646 else: |
672 else: |
647 self.__ui.appendToStderr("CodeAssistServer: " + self.tr( |
673 self.__ui.appendToStderr( |
648 "'{0}' is not supported because the configured" |
674 "CodeAssistServer: " |
649 " interpreter could not be started.\n" |
675 + self.tr( |
650 ).format(idString)) |
676 "'{0}' is not supported because the configured" |
|
677 " interpreter could not be started.\n" |
|
678 ).format(idString) |
|
679 ) |
651 else: |
680 else: |
652 self.__ui.appendToStderr("CodeAssistServer: " + self.tr( |
681 self.__ui.appendToStderr( |
653 "'{0}' is not supported because no suitable interpreter is" |
682 "CodeAssistServer: " |
654 " configured.\n" |
683 + self.tr( |
655 ).format(idString)) |
684 "'{0}' is not supported because no suitable interpreter is" |
656 |
685 " configured.\n" |
|
686 ).format(idString) |
|
687 ) |
|
688 |
657 return ok |
689 return ok |
658 |
690 |
659 def __ensureActive(self, idString): |
691 def __ensureActive(self, idString): |
660 """ |
692 """ |
661 Private method to ensure, that the requested client is active. |
693 Private method to ensure, that the requested client is active. |
662 |
694 |
663 A non-active client will be started. |
695 A non-active client will be started. |
664 |
696 |
665 @param idString id of the client to be checked |
697 @param idString id of the client to be checked |
666 @type str |
698 @type str |
667 @return flag indicating an active client |
699 @return flag indicating an active client |
668 @rtype bool |
700 @rtype bool |
669 """ |
701 """ |
682 if idString == "Python3": |
714 if idString == "Python3": |
683 venvName = Preferences.getDebugger("Python3VirtualEnv") |
715 venvName = Preferences.getDebugger("Python3VirtualEnv") |
684 if not venvName: |
716 if not venvName: |
685 venvName, _ = venvManager.getDefaultEnvironment() |
717 venvName, _ = venvManager.getDefaultEnvironment() |
686 if venvName: |
718 if venvName: |
687 interpreter = venvManager.getVirtualenvInterpreter( |
719 interpreter = venvManager.getVirtualenvInterpreter(venvName) |
688 venvName) |
720 |
689 |
|
690 execPath = venvManager.getVirtualenvExecPath(venvName) |
721 execPath = venvManager.getVirtualenvExecPath(venvName) |
691 |
722 |
692 # build a suitable environment |
723 # build a suitable environment |
693 if execPath: |
724 if execPath: |
694 if "PATH" in clientEnv: |
725 if "PATH" in clientEnv: |
695 clientEnv["PATH"] = os.pathsep.join( |
726 clientEnv["PATH"] = os.pathsep.join( |
696 [execPath, clientEnv["PATH"]]) |
727 [execPath, clientEnv["PATH"]] |
|
728 ) |
697 else: |
729 else: |
698 clientEnv["PATH"] = execPath |
730 clientEnv["PATH"] = execPath |
699 if interpreter: |
731 if interpreter: |
700 ok = self.__startCodeAssistClient( |
732 ok = self.__startCodeAssistClient(interpreter, idString, clientEnv) |
701 interpreter, idString, clientEnv) |
|
702 else: |
733 else: |
703 ok = False |
734 ok = False |
704 return ok |
735 return ok |
705 |
736 |
706 def __interpreterForProject(self): |
737 def __interpreterForProject(self): |
707 """ |
738 """ |
708 Private method to determine the interpreter for the current project and |
739 Private method to determine the interpreter for the current project and |
709 the environment to run it. |
740 the environment to run it. |
710 |
741 |
711 @return tuple containing the interpreter of the current project and the |
742 @return tuple containing the interpreter of the current project and the |
712 environment variables |
743 environment variables |
713 @rtype tuple of (str, dict) |
744 @rtype tuple of (str, dict) |
714 """ |
745 """ |
715 projectLanguage = self.__e5project.getProjectLanguage() |
746 projectLanguage = self.__e5project.getProjectLanguage() |
716 interpreter = "" |
747 interpreter = "" |
717 clientEnv = os.environ.copy() |
748 clientEnv = os.environ.copy() |
718 if "PATH" in clientEnv: |
749 if "PATH" in clientEnv: |
719 clientEnv["PATH"] = self.__ui.getOriginalPathString() |
750 clientEnv["PATH"] = self.__ui.getOriginalPathString() |
720 |
751 |
721 if projectLanguage.startswith("Python"): |
752 if projectLanguage.startswith("Python"): |
722 venvManager = ericApp().getObject("VirtualEnvManager") |
753 venvManager = ericApp().getObject("VirtualEnvManager") |
723 |
754 |
724 # get virtual environment from project first |
755 # get virtual environment from project first |
725 venvName = self.__e5project.getDebugProperty("VIRTUALENV") |
756 venvName = self.__e5project.getDebugProperty("VIRTUALENV") |
726 if not venvName: |
757 if not venvName: |
727 # get it from debugger settings next |
758 # get it from debugger settings next |
728 if projectLanguage == "Python3": |
759 if projectLanguage == "Python3": |
730 if not venvName: |
761 if not venvName: |
731 venvName, _ = venvManager.getDefaultEnvironment() |
762 venvName, _ = venvManager.getDefaultEnvironment() |
732 else: |
763 else: |
733 venvName = "" |
764 venvName = "" |
734 if venvName: |
765 if venvName: |
735 interpreter = venvManager.getVirtualenvInterpreter( |
766 interpreter = venvManager.getVirtualenvInterpreter(venvName) |
736 venvName |
|
737 ) |
|
738 execPath = venvManager.getVirtualenvExecPath(venvName) |
767 execPath = venvManager.getVirtualenvExecPath(venvName) |
739 |
768 |
740 # build a suitable environment |
769 # build a suitable environment |
741 if execPath: |
770 if execPath: |
742 if "PATH" in clientEnv: |
771 if "PATH" in clientEnv: |
743 clientEnv["PATH"] = os.pathsep.join( |
772 clientEnv["PATH"] = os.pathsep.join( |
744 [execPath, clientEnv["PATH"]]) |
773 [execPath, clientEnv["PATH"]] |
|
774 ) |
745 else: |
775 else: |
746 clientEnv["PATH"] = execPath |
776 clientEnv["PATH"] = execPath |
747 |
777 |
748 return interpreter, clientEnv |
778 return interpreter, clientEnv |
749 |
779 |
750 @pyqtSlot() |
780 @pyqtSlot() |
751 def handleNewConnection(self): |
781 def handleNewConnection(self): |
752 """ |
782 """ |
753 Public slot for new incoming connections from a client. |
783 Public slot for new incoming connections from a client. |
754 """ |
784 """ |
755 super().handleNewConnection() |
785 super().handleNewConnection() |
756 |
786 |
757 self.__updateEditorLanguageMapping() |
787 self.__updateEditorLanguageMapping() |
758 |
788 |
759 self.__getConfigs() |
789 self.__getConfigs() |
760 |
790 |
761 def activate(self): |
791 def activate(self): |
762 """ |
792 """ |
763 Public method to activate the code assist server. |
793 Public method to activate the code assist server. |
764 """ |
794 """ |
765 self.__documentationViewer = self.__ui.documentationViewer() |
795 self.__documentationViewer = self.__ui.documentationViewer() |
766 if self.__documentationViewer is not None: |
796 if self.__documentationViewer is not None: |
767 self.__documentationViewer.registerProvider( |
797 self.__documentationViewer.registerProvider( |
768 "rope", self.tr("Rope"), self.requestCodeDocumentation, |
798 "rope", |
769 self.isSupportedLanguage) |
799 self.tr("Rope"), |
770 |
800 self.requestCodeDocumentation, |
|
801 self.isSupportedLanguage, |
|
802 ) |
|
803 |
771 self.__e5project.projectClosed.connect(self.__projectClosed) |
804 self.__e5project.projectClosed.connect(self.__projectClosed) |
772 |
805 |
773 def deactivate(self): |
806 def deactivate(self): |
774 """ |
807 """ |
775 Public method to deactivate the code assist server. |
808 Public method to deactivate the code assist server. |
776 """ |
809 """ |
777 """ |
810 """ |
778 Public method to shut down the code assist server. |
811 Public method to shut down the code assist server. |
779 """ |
812 """ |
780 if self.__documentationViewer is not None: |
813 if self.__documentationViewer is not None: |
781 self.__documentationViewer.unregisterProvider("rope") |
814 self.__documentationViewer.unregisterProvider("rope") |
782 |
815 |
783 for idString in self.connectionNames(): |
816 for idString in self.connectionNames(): |
784 self.sendJson("closeProject", {}, flush=True, idString=idString) |
817 self.sendJson("closeProject", {}, flush=True, idString=idString) |
785 |
818 |
786 with contextlib.suppress(TypeError): |
819 with contextlib.suppress(TypeError): |
787 self.__e5project.projectClosed.disconnect(self.__projectClosed) |
820 self.__e5project.projectClosed.disconnect(self.__projectClosed) |
788 |
821 |
789 self.stopAllClients() |
822 self.stopAllClients() |
790 |
823 |
791 @pyqtSlot() |
824 @pyqtSlot() |
792 def __projectClosed(self): |
825 def __projectClosed(self): |
793 """ |
826 """ |
794 Private slot to handle the projectClosed signal. |
827 Private slot to handle the projectClosed signal. |
795 """ |
828 """ |