RefactoringRope/CodeAssistServer.py

branch
eric7
changeset 389
4f53795beff0
parent 374
958f34e97952
child 390
e6848531c902
equal deleted inserted replaced
388:cb044ec27c24 389:4f53795beff0
10 import contextlib 10 import contextlib
11 import os 11 import os
12 import sys 12 import sys
13 import uuid 13 import uuid
14 14
15 from PyQt6.QtCore import ( 15 from PyQt6.QtCore import pyqtSlot, QCoreApplication, QTimer
16 pyqtSlot, QCoreApplication, QTimer
17 )
18 16
19 from EricWidgets.EricApplication import ericApp 17 from EricWidgets.EricApplication import ericApp
20 from EricWidgets import EricMessageBox 18 from EricWidgets import EricMessageBox
21 19
22 from EricNetwork.EricJsonServer import EricJsonServer 20 from EricNetwork.EricJsonServer import EricJsonServer
29 27
30 class CodeAssistServer(EricJsonServer): 28 class CodeAssistServer(EricJsonServer):
31 """ 29 """
32 Class implementing the autocompletion interface to rope. 30 Class implementing the autocompletion interface to rope.
33 """ 31 """
32
34 IdProject = "Project" 33 IdProject = "Project"
35 34
36 PictureIDs = { 35 PictureIDs = {
37 "class": "?{0}".format(Editor.ClassID), 36 "class": "?{0}".format(Editor.ClassID),
38 "_class": "?{0}".format(Editor.ClassProtectedID), 37 "_class": "?{0}".format(Editor.ClassProtectedID),
39 "__class": "?{0}".format(Editor.ClassPrivateID), 38 "__class": "?{0}".format(Editor.ClassPrivateID),
40 "instance": "?{0}".format(Editor.ClassID), 39 "instance": "?{0}".format(Editor.ClassID),
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 """

eric ide

mercurial