37 super(CodeAssistServer, self).__init__( |
38 super(CodeAssistServer, self).__init__( |
38 "CodeAssistServer", multiplex=True, parent=parent) |
39 "CodeAssistServer", multiplex=True, parent=parent) |
39 |
40 |
40 self.__plugin = plugin |
41 self.__plugin = plugin |
41 self.__ui = parent |
42 self.__ui = parent |
|
43 self.__vm = e5App().getObject("ViewManager") |
42 |
44 |
43 self.__editorLanguageMapping = {} |
45 self.__editorLanguageMapping = {} |
|
46 self.__clientConfigs = {} |
|
47 self.__editors = {} |
|
48 |
|
49 self.__asyncCompletions = False |
44 |
50 |
45 # attributes to store the resuls of the client side |
51 # attributes to store the resuls of the client side |
46 self.__completions = None |
52 self.__completions = None |
47 self.__calltips = None |
53 self.__calltips = None |
48 |
54 |
49 self.__methodMapping = { |
55 self.__methodMapping = { |
|
56 "Config": self.__setConfig, |
50 "CompletionsResult": self.__processCompletionsResult, |
57 "CompletionsResult": self.__processCompletionsResult, |
51 "CallTipsResult": self.__processCallTipsResult, |
58 "CallTipsResult": self.__processCallTipsResult, |
52 |
59 |
53 "ClientException": self.__processClientException, |
60 "ClientException": self.__processClientException, |
54 } |
61 } |
75 self.__editorLanguageMapping.update({ |
82 self.__editorLanguageMapping.update({ |
76 "Python3": "Python3", |
83 "Python3": "Python3", |
77 "Pygments|Python 3": "Python3", |
84 "Pygments|Python 3": "Python3", |
78 }) |
85 }) |
79 |
86 |
|
87 def __getConfigs(self): |
|
88 """ |
|
89 Private method to get the configurations of all connected clients. |
|
90 """ |
|
91 for idString in self.connectionNames(): |
|
92 self.sendJson("getConfig", {}, idString=idString) |
|
93 |
|
94 def __setConfig(self, params): |
|
95 """ |
|
96 Private method to set the rope client configuration data. |
|
97 |
|
98 @param params dictionary containing the configuration data |
|
99 @type dict |
|
100 """ |
|
101 idString = params["Id"] |
|
102 ropeFolder = params["RopeFolderName"] |
|
103 |
|
104 self.__clientConfigs[idString] = ropeFolder |
|
105 |
|
106 def __ropeConfigFile(self, idString): |
|
107 """ |
|
108 Private method to get the name of the rope configuration file. |
|
109 |
|
110 @param idString id for which to get the configuration file |
|
111 @type str |
|
112 @return name of the rope configuration file |
|
113 @rtype str |
|
114 """ |
|
115 configfile = None |
|
116 if idString in self.__clientConfigs: |
|
117 ropedir = self.__clientConfigs[idString] |
|
118 if ropedir: |
|
119 configfile = os.path.join(ropedir, "config.py") |
|
120 if not os.path.exists(configfile): |
|
121 configfile = None |
|
122 return configfile |
|
123 |
|
124 def __configChanged(self, idString): |
|
125 """ |
|
126 Private slot called, when the rope config file has changed. |
|
127 |
|
128 @param idString id for which to get the configuration file |
|
129 @type str |
|
130 """ |
|
131 self.sendJson("configChanged", {}, idString=idString) |
|
132 |
|
133 def editConfig(self, idString): |
|
134 """ |
|
135 Public slot to open the rope configuration file in an editor. |
|
136 |
|
137 @param idString id for which to get the configuration file |
|
138 @type str |
|
139 """ |
|
140 configfile = self.__ropeConfigFile(idString) |
|
141 if configfile: |
|
142 if os.path.exists(configfile): |
|
143 from QScintilla.MiniEditor import MiniEditor |
|
144 editor = MiniEditor(configfile) |
|
145 editor.show() |
|
146 editor.editorSaved.connect( |
|
147 lambda: self.__configChanged(idString)) |
|
148 self.__editors[idString] = editor |
|
149 return |
|
150 else: |
|
151 E5MessageBox.critical( |
|
152 self.__ui, |
|
153 self.tr("Configure Rope"), |
|
154 self.tr("""The Rope configuration file '{0}' does""" |
|
155 """ not exist.""").format(configfile)) |
|
156 |
80 def isSupportedLanguage(self, language): |
157 def isSupportedLanguage(self, language): |
81 """ |
158 """ |
82 Public method to check, if the given language is supported. |
159 Public method to check, if the given language is supported. |
83 |
160 |
84 @param language editor programming language to check |
161 @param language editor programming language to check |
86 @return flag indicating the support status |
163 @return flag indicating the support status |
87 @rtype bool |
164 @rtype bool |
88 """ |
165 """ |
89 return language in self.__editorLanguageMapping |
166 return language in self.__editorLanguageMapping |
90 |
167 |
91 def getCompletions(self, editor): |
168 def getCompletions(self, editor, context): |
92 """ |
169 """ |
93 Public method to calculate the possible completions. |
170 Public method to calculate the possible completions. |
94 |
171 |
|
172 Note: This is the synchronous variant for eric6 before 17.11. |
|
173 |
95 @param editor reference to the editor object, that called this method |
174 @param editor reference to the editor object, that called this method |
96 @type QScintilla.Editor |
175 @type QScintilla.Editor |
97 @return list of proposals |
176 @param context flag indicating to autocomplete a context |
|
177 @type bool |
|
178 @return list of possible completions |
98 @rtype list of str |
179 @rtype list of str |
99 """ |
180 """ |
100 # reset the completions buffer |
181 # reset the completions buffer |
101 self.__completions = None |
182 self.__completions = None |
102 |
183 |
103 language = editor.getLanguage() |
184 language = editor.getLanguage() |
104 if language not in self.__editorLanguageMapping: |
185 if language not in self.__editorLanguageMapping: |
105 return [] |
186 return [] |
|
187 |
|
188 self.requestCompletions(editor, context, "") |
|
189 |
|
190 # emulate the synchronous behaviour |
|
191 timer = QTimer() |
|
192 timer.setSingleShot(True) |
|
193 timer.start(5000) # 5s timeout |
|
194 while self.__completions is None and timer.isActive(): |
|
195 QCoreApplication.processEvents() |
|
196 |
|
197 return [] if self.__completions is None else self.__completions |
|
198 |
|
199 def requestCompletions(self, editor, context, acText): |
|
200 """ |
|
201 Public method to request a list of possible completions. |
|
202 |
|
203 Note: This is part of the asynchronous variant for eric6 17.11 and |
|
204 later. |
|
205 |
|
206 @param editor reference to the editor object, that called this method |
|
207 @type QScintilla.Editor |
|
208 @param context flag indicating to autocomplete a context |
|
209 @type bool |
|
210 @param acText text to be completed |
|
211 @type str |
|
212 """ |
|
213 language = editor.getLanguage() |
|
214 if language not in self.__editorLanguageMapping: |
|
215 return |
106 idString = self.__editorLanguageMapping[language] |
216 idString = self.__editorLanguageMapping[language] |
107 |
217 |
108 filename = editor.getFileName() |
218 filename = editor.getFileName() |
109 line, index = editor.getCursorPosition() |
219 line, index = editor.getCursorPosition() |
110 source = editor.text() |
220 source = editor.text() |
115 self.sendJson("getCompletions", { |
225 self.sendJson("getCompletions", { |
116 "FileName": filename, |
226 "FileName": filename, |
117 "Source": source, |
227 "Source": source, |
118 "Offset": offset, |
228 "Offset": offset, |
119 "MaxFixes": maxfixes, |
229 "MaxFixes": maxfixes, |
|
230 "CompletionText": acText, |
120 }, idString=idString) |
231 }, idString=idString) |
121 |
|
122 # emulate the synchronous behaviour |
|
123 timer = QTimer() |
|
124 timer.setSingleShot(True) |
|
125 timer.start(5000) # 5s timeout |
|
126 while self.__completions is None and timer.isActive(): |
|
127 QCoreApplication.processEvents() |
|
128 |
|
129 return [] if self.__completions is None else self.__completions |
|
130 |
232 |
131 def __processCompletionsResult(self, result): |
233 def __processCompletionsResult(self, result): |
132 """ |
234 """ |
133 Private method to process the completions sent by the client. |
235 Private method to process the completions sent by the client. |
134 |
236 |
135 @param result dictionary containing the result sent by the client |
237 @param result dictionary containing the result sent by the client |
136 @type dict |
238 @type dict |
137 """ |
239 """ |
138 if "Error" in result: |
240 if self.__asyncCompletions: |
139 self.__completions = [] |
241 # asynchronous variant for eric6 17.11 and later |
|
242 if "Error" not in result: |
|
243 editor = self.__vm.getOpenEditor(result["FileName"]) |
|
244 if editor is not None: |
|
245 editor.completionsListReady(result["Completions"], |
|
246 result["CompletionText"]) |
140 else: |
247 else: |
141 self.__completions = result["Completions"] |
248 # synchronous variant for eric6 before 17.11 |
142 |
249 if "Error" in result: |
143 def getCallTips(self, pos, editor): |
250 self.__completions = [] |
|
251 else: |
|
252 self.__completions = result["Completions"] |
|
253 |
|
254 def getCallTips(self, editor, pos, commas): |
144 """ |
255 """ |
145 Public method to calculate calltips. |
256 Public method to calculate calltips. |
146 |
257 |
|
258 @param editor reference to the editor object, that called this method |
|
259 @type QScintilla.Editor |
147 @param pos position in the text for the calltip |
260 @param pos position in the text for the calltip |
148 @type int |
261 @type int |
149 @param editor reference to the editor object, that called this method |
262 @param commas minimum number of commas contained in the calltip |
150 @type QScintilla.Editor |
263 @type int |
151 @return list of possible calltips |
264 @return list of possible calltips |
152 @rtype list of str |
265 @rtype list of str |
153 """ |
266 """ |
154 # reset the calltips buffer |
267 # reset the calltips buffer |
155 self.__calltips = None |
268 self.__calltips = None |
201 @param filename file name of the changed source |
314 @param filename file name of the changed source |
202 @type str |
315 @type str |
203 @param oldSource source code before the change |
316 @param oldSource source code before the change |
204 @type str |
317 @type str |
205 """ |
318 """ |
206 editor = e5App().getObject("ViewManager").getOpenEditor(filename) |
319 editor = self.__vm.getOpenEditor(filename) |
207 if editor is not None: |
320 if editor is not None: |
208 language = editor.getLanguage() |
321 language = editor.getLanguage() |
209 if language in self.__editorLanguageMapping: |
322 if language in self.__editorLanguageMapping: |
210 idString = self.__editorLanguageMapping[language] |
323 idString = self.__editorLanguageMapping[language] |
211 |
324 |
272 @rtype bool |
385 @rtype bool |
273 """ |
386 """ |
274 ok = False |
387 ok = False |
275 |
388 |
276 if interpreter: |
389 if interpreter: |
|
390 configDir = os.path.join(Globals.getConfigDir(), "rope", idString) |
|
391 if not os.path.exists(configDir): |
|
392 os.makedirs(configDir) |
|
393 |
277 client = os.path.join(os.path.dirname(__file__), |
394 client = os.path.join(os.path.dirname(__file__), |
278 "CodeAssistClient.py") |
395 "CodeAssistClient.py") |
279 ok = self.startClient(interpreter, client, |
396 ok = self.startClient(interpreter, client, [configDir], |
280 [Globals.getConfigDir()], |
|
281 idString=idString) |
397 idString=idString) |
282 if not ok: |
398 if not ok: |
283 self.__ui.appendToStderr(self.tr( |
399 self.__ui.appendToStderr(self.tr( |
284 "'{0}' is not supported because the configured interpreter" |
400 "'{0}' is not supported because the configured interpreter" |
285 " could not be started.\n" |
401 " could not be started.\n" |
336 Public method to deactivate the code assist server. |
455 Public method to deactivate the code assist server. |
337 """ |
456 """ |
338 """ |
457 """ |
339 Public method to shut down the code assist server. |
458 Public method to shut down the code assist server. |
340 """ |
459 """ |
|
460 for idString in self.connectionNames(): |
|
461 self.sendJson("closeProject", {}, flush=True, idString=idString) |
|
462 |
341 self.stopAllClients() |
463 self.stopAllClients() |
|
464 |
|
465 ####################################################################### |
|
466 ## Methods below handle setting/unsetting the hook methods |
|
467 ####################################################################### |
|
468 |
|
469 def connectEditor(self, editor): |
|
470 """ |
|
471 Public method to connect an editor. |
|
472 |
|
473 @param editor reference to the editor |
|
474 @type QScintilla.Editor |
|
475 """ |
|
476 if self.isSupportedLanguage(editor.getLanguage()): |
|
477 if self.__plugin.getPreferences("CodeAssistEnabled") and \ |
|
478 editor.getCompletionListHook("rope") is None: |
|
479 self.__setAutoCompletionHook(editor) |
|
480 if self.__plugin.getPreferences("CodeAssistCalltipsEnabled") and \ |
|
481 editor.getCallTipHook("rope") is None: |
|
482 self.__setCalltipsHook(editor) |
|
483 else: |
|
484 self.disconnectEditor(editor) |
|
485 |
|
486 def disconnectEditor(self, editor): |
|
487 """ |
|
488 Public method to disconnect an editor. |
|
489 |
|
490 @param editor reference to the editor |
|
491 @type QScintilla.Editor |
|
492 """ |
|
493 if editor.getCompletionListHook("rope"): |
|
494 self.__unsetAutoCompletionHook(editor) |
|
495 if editor.getCallTipHook("rope"): |
|
496 self.__unsetCalltipsHook(editor) |
|
497 |
|
498 def __setAutoCompletionHook(self, editor): |
|
499 """ |
|
500 Private method to set the auto-completion hook. |
|
501 |
|
502 @param editor reference to the editor |
|
503 @type QScintilla.Editor |
|
504 """ |
|
505 try: |
|
506 editor.addCompletionListHook("rope", self.requestCompletions, |
|
507 async=True) |
|
508 self.__asyncCompletions = True |
|
509 except TypeError: |
|
510 # backward compatibility for eric6 before 17.11 |
|
511 editor.addCompletionListHook("rope", self.getCompletions) |
|
512 self.__asyncCompletions = False |
|
513 |
|
514 def __unsetAutoCompletionHook(self, editor): |
|
515 """ |
|
516 Private method to unset the auto-completion hook. |
|
517 |
|
518 @param editor reference to the editor |
|
519 @type QScintilla.Editor |
|
520 """ |
|
521 editor.removeCompletionListHook("rope") |
|
522 |
|
523 def __setCalltipsHook(self, editor): |
|
524 """ |
|
525 Private method to set the calltip hook. |
|
526 |
|
527 @param editor reference to the editor |
|
528 @type QScintilla.Editor |
|
529 """ |
|
530 editor.addCallTipHook("rope", self.getCallTips) |
|
531 |
|
532 def __unsetCalltipsHook(self, editor): |
|
533 """ |
|
534 Private method to unset the calltip hook. |
|
535 |
|
536 @param editor reference to the editor |
|
537 @type QScintilla.Editor |
|
538 """ |
|
539 editor.removeCallTipHook("rope") |
|
540 |
|
541 # TODO: add method to edit the codeassist python2 and 3 config files |