AssistantEric/Assistant.py

branch
eric7
changeset 190
3104a5a3ea13
parent 189
c0d638327085
child 191
6798a98189da
equal deleted inserted replaced
189:c0d638327085 190:3104a5a3ea13
24 24
25 class Assistant(QObject): 25 class Assistant(QObject):
26 """ 26 """
27 Class implementing the autocompletion and calltips system. 27 Class implementing the autocompletion and calltips system.
28 """ 28 """
29
29 def __init__(self, plugin, parent=None): 30 def __init__(self, plugin, parent=None):
30 """ 31 """
31 Constructor 32 Constructor
32 33
33 @param plugin reference to the plugin object 34 @param plugin reference to the plugin object
34 @type AssistantEricPlugin 35 @type AssistantEricPlugin
35 @param parent parent 36 @param parent parent
36 @type QObject 37 @type QObject
37 """ 38 """
38 QObject.__init__(self, parent) 39 QObject.__init__(self, parent)
39 40
40 self.__plugin = plugin 41 self.__plugin = plugin
41 self.__ui = parent 42 self.__ui = parent
42 self.__project = ericApp().getObject("Project") 43 self.__project = ericApp().getObject("Project")
43 self.__viewmanager = ericApp().getObject("ViewManager") 44 self.__viewmanager = ericApp().getObject("ViewManager")
44 self.__pluginManager = ericApp().getObject("PluginManager") 45 self.__pluginManager = ericApp().getObject("PluginManager")
45 46
46 self.__apisManager = APIsManager(self.__ui, self) 47 self.__apisManager = APIsManager(self.__ui, self)
47 48
48 self.__editors = [] 49 self.__editors = []
49 self.__lastContext = None 50 self.__lastContext = None
50 self.__lastFullContext = None 51 self.__lastFullContext = None
51 52
52 from QScintilla.Editor import Editor 53 from QScintilla.Editor import Editor
54
53 self.__fromDocumentID = Editor.FromDocumentID 55 self.__fromDocumentID = Editor.FromDocumentID
54 56
55 def activate(self): 57 def activate(self):
56 """ 58 """
57 Public method to perform actions upon activation. 59 Public method to perform actions upon activation.
58 """ 60 """
59 self.__pluginManager.shutdown.connect(self.__shutdown) 61 self.__pluginManager.shutdown.connect(self.__shutdown)
60 62
61 self.__ui.preferencesChanged.connect(self.__preferencesChanged) 63 self.__ui.preferencesChanged.connect(self.__preferencesChanged)
62 64
63 self.__viewmanager.editorOpenedEd.connect(self.__editorOpened) 65 self.__viewmanager.editorOpenedEd.connect(self.__editorOpened)
64 self.__viewmanager.editorClosedEd.connect(self.__editorClosed) 66 self.__viewmanager.editorClosedEd.connect(self.__editorClosed)
65 67
66 # preload the project APIs object 68 # preload the project APIs object
67 self.__apisManager.getAPIs(ApisNameProject) 69 self.__apisManager.getAPIs(ApisNameProject)
68 70
69 for editor in self.__viewmanager.getOpenEditors(): 71 for editor in self.__viewmanager.getOpenEditors():
70 self.__editorOpened(editor) 72 self.__editorOpened(editor)
71 73
72 def deactivate(self): 74 def deactivate(self):
73 """ 75 """
74 Public method to perform actions upon deactivation. 76 Public method to perform actions upon deactivation.
75 """ 77 """
76 self.__pluginManager.shutdown.disconnect(self.__shutdown) 78 self.__pluginManager.shutdown.disconnect(self.__shutdown)
77 79
78 self.__ui.preferencesChanged.disconnect(self.__preferencesChanged) 80 self.__ui.preferencesChanged.disconnect(self.__preferencesChanged)
79 81
80 self.__viewmanager.editorOpenedEd.disconnect(self.__editorOpened) 82 self.__viewmanager.editorOpenedEd.disconnect(self.__editorOpened)
81 self.__viewmanager.editorClosedEd.disconnect(self.__editorClosed) 83 self.__viewmanager.editorClosedEd.disconnect(self.__editorClosed)
82 84
83 self.__shutdown() 85 self.__shutdown()
84 86
85 def __shutdown(self): 87 def __shutdown(self):
86 """ 88 """
87 Private slot to handle the shutdown signal. 89 Private slot to handle the shutdown signal.
88 """ 90 """
89 for editor in self.__editors[:]: 91 for editor in self.__editors[:]:
90 self.__editorClosed(editor) 92 self.__editorClosed(editor)
91 93
92 self.__apisManager.deactivate() 94 self.__apisManager.deactivate()
93 95
94 def setEnabled(self, key, enabled): 96 def setEnabled(self, key, enabled):
95 """ 97 """
96 Public method to enable or disable a feature. 98 Public method to enable or disable a feature.
97 99
98 @param key feature to set 100 @param key feature to set
99 @type str 101 @type str
100 @param enabled flag indicating the status 102 @param enabled flag indicating the status
101 @type bool 103 @type bool
102 """ 104 """
103 for editor in self.__editors[:]: 105 for editor in self.__editors[:]:
104 self.__editorClosed(editor) 106 self.__editorClosed(editor)
105 for editor in self.__viewmanager.getOpenEditors(): 107 for editor in self.__viewmanager.getOpenEditors():
106 self.__editorOpened(editor) 108 self.__editorOpened(editor)
107 109
108 def __editorOpened(self, editor): 110 def __editorOpened(self, editor):
109 """ 111 """
110 Private slot called, when a new editor was opened. 112 Private slot called, when a new editor was opened.
111 113
112 @param editor reference to the new editor 114 @param editor reference to the new editor
113 @type Editor 115 @type Editor
114 """ 116 """
115 if self.__plugin.getPreferences("AutoCompletionEnabled"): 117 if self.__plugin.getPreferences("AutoCompletionEnabled"):
116 self.__setAutoCompletionHook(editor) 118 self.__setAutoCompletionHook(editor)
117 if self.__plugin.getPreferences("CalltipsEnabled"): 119 if self.__plugin.getPreferences("CalltipsEnabled"):
118 self.__setCalltipsHook(editor) 120 self.__setCalltipsHook(editor)
119 editor.editorSaved.connect( 121 editor.editorSaved.connect(
120 self.__apisManager.getAPIs(ApisNameProject).editorSaved) 122 self.__apisManager.getAPIs(ApisNameProject).editorSaved
123 )
121 self.__editors.append(editor) 124 self.__editors.append(editor)
122 125
123 # preload the api to give the manager a chance to prepare the database 126 # preload the api to give the manager a chance to prepare the database
124 language = editor.getApiLanguage() 127 language = editor.getApiLanguage()
125 if language: 128 if language:
126 projectType = self.__getProjectType(editor) 129 projectType = self.__getProjectType(editor)
127 self.__apisManager.getAPIs(language, projectType=projectType) 130 self.__apisManager.getAPIs(language, projectType=projectType)
128 131
129 def __editorClosed(self, editor): 132 def __editorClosed(self, editor):
130 """ 133 """
131 Private slot called, when an editor was closed. 134 Private slot called, when an editor was closed.
132 135
133 @param editor reference to the editor 136 @param editor reference to the editor
134 @type Editor 137 @type Editor
135 """ 138 """
136 if editor in self.__editors: 139 if editor in self.__editors:
137 editor.editorSaved.disconnect( 140 editor.editorSaved.disconnect(
138 self.__apisManager.getAPIs(ApisNameProject).editorSaved) 141 self.__apisManager.getAPIs(ApisNameProject).editorSaved
142 )
139 self.__editors.remove(editor) 143 self.__editors.remove(editor)
140 if editor.getCompletionListHook("Assistant"): 144 if editor.getCompletionListHook("Assistant"):
141 self.__unsetAutoCompletionHook(editor) 145 self.__unsetAutoCompletionHook(editor)
142 if editor.getCallTipHook("Assistant"): 146 if editor.getCallTipHook("Assistant"):
143 self.__unsetCalltipsHook(editor) 147 self.__unsetCalltipsHook(editor)
144 148
145 def __preferencesChanged(self): 149 def __preferencesChanged(self):
146 """ 150 """
147 Private method to handle a change of the global configuration. 151 Private method to handle a change of the global configuration.
148 """ 152 """
149 self.__apisManager.reloadAPIs() 153 self.__apisManager.reloadAPIs()
150 154
151 def __getProjectType(self, editor): 155 def __getProjectType(self, editor):
152 """ 156 """
153 Private method to determine the project type to be used. 157 Private method to determine the project type to be used.
154 158
155 @param editor reference to the editor to check 159 @param editor reference to the editor to check
156 @type Editor 160 @type Editor
157 @return project type 161 @return project type
158 @rtype str 162 @rtype str
159 """ 163 """
160 filename = editor.getFileName() 164 filename = editor.getFileName()
161 projectType = ( 165 projectType = (
162 self.__project.getProjectType() 166 self.__project.getProjectType()
163 if (self.__project.isOpen() and 167 if (
164 filename and 168 self.__project.isOpen()
165 self.__project.isProjectFile(filename)) else 169 and filename
166 "" 170 and self.__project.isProjectFile(filename)
171 )
172 else ""
167 ) 173 )
168 174
169 return projectType 175 return projectType
170 176
171 ################################# 177 #################################
172 ## auto-completion methods below 178 ## auto-completion methods below
173 ################################# 179 #################################
174 180
175 def __recordSelectedContext(self, userListId, txt): 181 def __recordSelectedContext(self, userListId, txt):
176 """ 182 """
177 Private slot to handle the selection from the completion list to 183 Private slot to handle the selection from the completion list to
178 record the selected completion context. 184 record the selected completion context.
179 185
180 @param userListId the ID of the user list (should be 1) 186 @param userListId the ID of the user list (should be 1)
181 @type int 187 @type int
182 @param txt the selected text 188 @param txt the selected text
183 @type str 189 @type str
184 """ 190 """
185 from QScintilla.Editor import EditorAutoCompletionListID 191 from QScintilla.Editor import EditorAutoCompletionListID
186 192
187 if userListId == EditorAutoCompletionListID: 193 if userListId == EditorAutoCompletionListID:
188 lst = txt.split() 194 lst = txt.split()
189 if len(lst) > 1: 195 if len(lst) > 1:
190 self.__lastFullContext = lst[1][1:].split(")")[0] 196 self.__lastFullContext = lst[1][1:].split(")")[0]
191 else: 197 else:
192 self.__lastFullContext = None 198 self.__lastFullContext = None
193 199
194 def __setAutoCompletionHook(self, editor): 200 def __setAutoCompletionHook(self, editor):
195 """ 201 """
196 Private method to set the autocompletion hook. 202 Private method to set the autocompletion hook.
197 203
198 @param editor reference to the editor 204 @param editor reference to the editor
199 @type Editor 205 @type Editor
200 """ 206 """
201 editor.userListActivated.connect(self.__recordSelectedContext) 207 editor.userListActivated.connect(self.__recordSelectedContext)
202 editor.addCompletionListHook("Assistant", self.getCompletionsList) 208 editor.addCompletionListHook("Assistant", self.getCompletionsList)
203 209
204 def __unsetAutoCompletionHook(self, editor): 210 def __unsetAutoCompletionHook(self, editor):
205 """ 211 """
206 Private method to unset the autocompletion hook. 212 Private method to unset the autocompletion hook.
207 213
208 @param editor reference to the editor 214 @param editor reference to the editor
209 @type Editor 215 @type Editor
210 """ 216 """
211 editor.userListActivated.disconnect(self.__recordSelectedContext) 217 editor.userListActivated.disconnect(self.__recordSelectedContext)
212 editor.removeCompletionListHook("Assistant") 218 editor.removeCompletionListHook("Assistant")
213 219
214 def getCompletionsList(self, editor, context): 220 def getCompletionsList(self, editor, context):
215 """ 221 """
216 Public method to get a list of possible completions. 222 Public method to get a list of possible completions.
217 223
218 @param editor reference to the editor object, that called this method 224 @param editor reference to the editor object, that called this method
219 @type Editor 225 @type Editor
220 @param context flag indicating to autocomplete a context 226 @param context flag indicating to autocomplete a context
221 @type bool 227 @type bool
222 @return list of possible completions 228 @return list of possible completions
223 @rtype list of str 229 @rtype list of str
224 """ 230 """
225 language = editor.getApiLanguage() 231 language = editor.getApiLanguage()
226 completeFromDocumentOnly = False 232 completeFromDocumentOnly = False
227 if language in ["", "Guessed"] or language.startswith("Pygments|"): 233 if language in ["", "Guessed"] or language.startswith("Pygments|"):
228 if ( 234 if self.__plugin.getPreferences("AutoCompletionSource") & AcsDocument:
229 self.__plugin.getPreferences("AutoCompletionSource") &
230 AcsDocument
231 ):
232 completeFromDocumentOnly = True 235 completeFromDocumentOnly = True
233 else: 236 else:
234 return [] 237 return []
235 238
236 projectType = self.__getProjectType(editor) 239 projectType = self.__getProjectType(editor)
237 240
238 line, col = editor.getCursorPosition() 241 line, col = editor.getCursorPosition()
239 sep = "" 242 sep = ""
240 if language and context: 243 if language and context:
241 wc = re.sub("\w", "", editor.wordCharacters()) 244 wc = re.sub("\w", "", editor.wordCharacters())
242 pat = re.compile("\w{0}".format(re.escape(wc))) 245 pat = re.compile("\w{0}".format(re.escape(wc)))
243 text = editor.text(line) 246 text = editor.text(line)
244 247
245 beg = text[:col] 248 beg = text[:col]
246 for wsep in editor.getLexer().autoCompletionWordSeparators(): 249 for wsep in editor.getLexer().autoCompletionWordSeparators():
247 if beg.endswith(wsep): 250 if beg.endswith(wsep):
248 sep = wsep 251 sep = wsep
249 break 252 break
250 253
251 depth = 0 254 depth = 0
252 while ( 255 while col > 0 and not pat.match(text[col - 1]):
253 col > 0 and
254 not pat.match(text[col - 1])
255 ):
256 ch = text[col - 1] 256 ch = text[col - 1]
257 if ch == ')': 257 if ch == ")":
258 depth = 1 258 depth = 1
259 259
260 # ignore everything back to the start of the 260 # ignore everything back to the start of the
261 # corresponding parenthesis 261 # corresponding parenthesis
262 col -= 1 262 col -= 1
263 while col > 0: 263 while col > 0:
264 ch = text[col - 1] 264 ch = text[col - 1]
265 if ch == ')': 265 if ch == ")":
266 depth += 1 266 depth += 1
267 elif ch == '(': 267 elif ch == "(":
268 depth -= 1 268 depth -= 1
269 if depth == 0: 269 if depth == 0:
270 break 270 break
271 col -= 1 271 col -= 1
272 elif ch == '(': 272 elif ch == "(":
273 break 273 break
274 col -= 1 274 col -= 1
275 275
276 word = editor.getWordLeft(line, col) 276 word = editor.getWordLeft(line, col)
277 if context and not sep: 277 if context and not sep:
278 # no separator was found -> no context completion 278 # no separator was found -> no context completion
279 context = False 279 context = False
280 if context: 280 if context:
281 self.__lastContext = word 281 self.__lastContext = word
282 else: 282 else:
283 self.__lastContext = None 283 self.__lastContext = None
284 284
285 prefix = "" 285 prefix = ""
286 mod = None 286 mod = None
287 beg = beg[:col + 1] if context else editor.text(line)[:col] 287 beg = beg[: col + 1] if context else editor.text(line)[:col]
288 col = len(beg) 288 col = len(beg)
289 wseps = ( 289 wseps = editor.getLexer().autoCompletionWordSeparators() if language else []
290 editor.getLexer().autoCompletionWordSeparators()
291 if language else
292 []
293 )
294 if wseps: 290 if wseps:
295 wseps.append(" ") 291 wseps.append(" ")
296 if col > 0 and beg[col - 1] in wseps: 292 if col > 0 and beg[col - 1] in wseps:
297 col -= 1 293 col -= 1
298 else: 294 else:
301 if col > 0 and beg[col - 1] != " ": 297 if col > 0 and beg[col - 1] != " ":
302 col -= 1 298 col -= 1
303 prefix = editor.getWordLeft(line, col) 299 prefix = editor.getWordLeft(line, col)
304 if editor.isPyFile(): 300 if editor.isPyFile():
305 from Utilities.ModuleParser import Module 301 from Utilities.ModuleParser import Module
302
306 src = editor.text() 303 src = editor.text()
307 fn = editor.getFileName() 304 fn = editor.getFileName()
308 if fn is None: 305 if fn is None:
309 fn = "" 306 fn = ""
310 mod = Module("", fn, imp.PY_SOURCE) 307 mod = Module("", fn, imp.PY_SOURCE)
311 mod.scan(src) 308 mod.scan(src)
312 309
313 importCompletion = False 310 importCompletion = False
314 if editor.isPyFile(): 311 if editor.isPyFile():
315 # check, if we are completing a from import statement 312 # check, if we are completing a from import statement
316 maxLines = 10 313 maxLines = 10
317 text = editor.text(line).strip() 314 text = editor.text(line).strip()
330 col = len(prefix) - 1 327 col = len(prefix) - 1
331 wseps = editor.getLexer().autoCompletionWordSeparators() 328 wseps = editor.getLexer().autoCompletionWordSeparators()
332 while col >= 0 and prefix[col] not in wseps: 329 while col >= 0 and prefix[col] not in wseps:
333 col -= 1 330 col -= 1
334 if col >= 0: 331 if col >= 0:
335 prefix = prefix[col + 1:] 332 prefix = prefix[col + 1 :]
336 if word == tokens[2]: 333 if word == tokens[2]:
337 word = "" 334 word = ""
338 335
339 if word or importCompletion: 336 if word or importCompletion:
340 completionsList = self.__getCompletions( 337 completionsList = self.__getCompletions(
341 word, context, prefix, language, projectType, mod, editor, 338 word,
342 importCompletion, completeFromDocumentOnly, sep) 339 context,
340 prefix,
341 language,
342 projectType,
343 mod,
344 editor,
345 importCompletion,
346 completeFromDocumentOnly,
347 sep,
348 )
343 if len(completionsList) == 0 and prefix: 349 if len(completionsList) == 0 and prefix:
344 # searching with prefix didn't return anything, try without 350 # searching with prefix didn't return anything, try without
345 completionsList = self.__getCompletions( 351 completionsList = self.__getCompletions(
346 word, context, "", language, projectType, mod, editor, 352 word,
347 importCompletion, completeFromDocumentOnly, sep) 353 context,
354 "",
355 language,
356 projectType,
357 mod,
358 editor,
359 importCompletion,
360 completeFromDocumentOnly,
361 sep,
362 )
348 return completionsList 363 return completionsList
349 364
350 return [] 365 return []
351 366
352 def __getCompletions(self, word, context, prefix, language, projectType, 367 def __getCompletions(
353 module, editor, importCompletion, documentOnly, sep): 368 self,
369 word,
370 context,
371 prefix,
372 language,
373 projectType,
374 module,
375 editor,
376 importCompletion,
377 documentOnly,
378 sep,
379 ):
354 """ 380 """
355 Private method to get the list of possible completions. 381 Private method to get the list of possible completions.
356 382
357 @param word word (or wordpart) to complete 383 @param word word (or wordpart) to complete
358 @type str 384 @type str
359 @param context flag indicating to autocomplete a context 385 @param context flag indicating to autocomplete a context
360 @type bool 386 @type bool
361 @param prefix prefix of the word to be completed 387 @param prefix prefix of the word to be completed
378 @rtype list of str 404 @rtype list of str
379 """ 405 """
380 apiCompletionsList = [] 406 apiCompletionsList = []
381 docCompletionsList = [] 407 docCompletionsList = []
382 projectCompletionList = [] 408 projectCompletionList = []
383 409
384 if not documentOnly: 410 if not documentOnly:
385 if self.__plugin.getPreferences("AutoCompletionSource") & AcsAPIs: 411 if self.__plugin.getPreferences("AutoCompletionSource") & AcsAPIs:
386 api = self.__apisManager.getAPIs( 412 api = self.__apisManager.getAPIs(language, projectType=projectType)
387 language, projectType=projectType)
388 apiCompletionsList = self.__getApiCompletions( 413 apiCompletionsList = self.__getApiCompletions(
389 api, word, context, prefix, module, editor) 414 api, word, context, prefix, module, editor
390 415 )
391 if ( 416
392 self.__plugin.getPreferences("AutoCompletionSource") & 417 if self.__plugin.getPreferences("AutoCompletionSource") & AcsProject:
393 AcsProject
394 ):
395 api = self.__apisManager.getAPIs(ApisNameProject) 418 api = self.__apisManager.getAPIs(ApisNameProject)
396 projectCompletionList = self.__getApiCompletions( 419 projectCompletionList = self.__getApiCompletions(
397 api, word, context, prefix, module, editor) 420 api, word, context, prefix, module, editor
398 421 )
422
399 if ( 423 if (
400 self.__plugin.getPreferences("AutoCompletionSource") & 424 self.__plugin.getPreferences("AutoCompletionSource") & AcsDocument
401 AcsDocument and 425 and not importCompletion
402 not importCompletion
403 ): 426 ):
404 docCompletionsList = self.__getDocumentCompletions( 427 docCompletionsList = self.__getDocumentCompletions(
405 editor, word, context, sep, prefix, module) 428 editor, word, context, sep, prefix, module
406 429 )
430
407 completionsList = list( 431 completionsList = list(
408 set(apiCompletionsList) 432 set(apiCompletionsList)
409 .union(set(docCompletionsList)) 433 .union(set(docCompletionsList))
410 .union(set(projectCompletionList)) 434 .union(set(projectCompletionList))
411 ) 435 )
412 return completionsList 436 return completionsList
413 437
414 def __getApiCompletions(self, api, word, context, prefix, module, editor): 438 def __getApiCompletions(self, api, word, context, prefix, module, editor):
415 """ 439 """
416 Private method to determine a list of completions from an API object. 440 Private method to determine a list of completions from an API object.
417 441
418 @param api reference to the API object to be used 442 @param api reference to the API object to be used
419 @type APIsManager.DbAPIs 443 @type APIsManager.DbAPIs
420 @param word word (or wordpart) to complete 444 @param word word (or wordpart) to complete
421 @type str 445 @type str
422 @param context flag indicating to autocomplete a context 446 @param context flag indicating to autocomplete a context
433 completionsList = [] 457 completionsList = []
434 if api is not None: 458 if api is not None:
435 if prefix and module and prefix == "self": 459 if prefix and module and prefix == "self":
436 line, col = editor.getCursorPosition() 460 line, col = editor.getCursorPosition()
437 for cl in module.classes.values(): 461 for cl in module.classes.values():
438 if ( 462 if line >= cl.lineno and (
439 line >= cl.lineno and 463 cl.endlineno == -1 or line <= cl.endlineno
440 (cl.endlineno == -1 or line <= cl.endlineno)
441 ): 464 ):
442 completions = [] 465 completions = []
443 for superClass in cl.super: 466 for superClass in cl.super:
444 if prefix == word: 467 if prefix == word:
445 completions.extend( 468 completions.extend(
446 api.getCompletions( 469 api.getCompletions(
447 context=superClass, 470 context=superClass,
448 followHierarchy=self.__plugin 471 followHierarchy=self.__plugin.getPreferences(
449 .getPreferences(
450 "AutoCompletionFollowHierarchy" 472 "AutoCompletionFollowHierarchy"
451 ) 473 ),
452 ) 474 )
453 ) 475 )
454 else: 476 else:
455 completions.extend( 477 completions.extend(
456 api.getCompletions( 478 api.getCompletions(
457 start=word, context=superClass, 479 start=word,
458 followHierarchy=self.__plugin 480 context=superClass,
459 .getPreferences( 481 followHierarchy=self.__plugin.getPreferences(
460 "AutoCompletionFollowHierarchy" 482 "AutoCompletionFollowHierarchy"
461 ) 483 ),
462 ) 484 )
463 ) 485 )
464 for completion in completions: 486 for completion in completions:
465 if not completion["context"]: 487 if not completion["context"]:
466 entry = completion["completion"] 488 entry = completion["completion"]
467 else: 489 else:
468 entry = "{0} ({1})".format( 490 entry = "{0} ({1})".format(
469 completion["completion"], 491 completion["completion"], completion["context"]
470 completion["context"]
471 ) 492 )
472 if entry in completionsList: 493 if entry in completionsList:
473 completionsList.remove(entry) 494 completionsList.remove(entry)
474 if completion["pictureId"]: 495 if completion["pictureId"]:
475 entry += "?{0}".format(completion["pictureId"]) 496 entry += "?{0}".format(completion["pictureId"])
476 else: 497 else:
477 cont = False 498 cont = False
478 regexp = re.compile( 499 regexp = re.compile(re.escape(entry) + r"\?\d{,2}")
479 re.escape(entry) + r"\?\d{,2}")
480 for comp in completionsList: 500 for comp in completionsList:
481 if regexp.fullmatch(comp): 501 if regexp.fullmatch(comp):
482 cont = True 502 cont = True
483 break 503 break
484 if cont: 504 if cont:
485 continue 505 continue
486 if entry not in completionsList: 506 if entry not in completionsList:
487 completionsList.append(entry) 507 completionsList.append(entry)
488 508
489 break 509 break
490 elif context: 510 elif context:
491 completions = api.getCompletions(context=word) 511 completions = api.getCompletions(context=word)
492 for completion in completions: 512 for completion in completions:
493 entry = completion["completion"] 513 entry = completion["completion"]
495 entry += "?{0}".format(completion["pictureId"]) 515 entry += "?{0}".format(completion["pictureId"])
496 if entry not in completionsList: 516 if entry not in completionsList:
497 completionsList.append(entry) 517 completionsList.append(entry)
498 else: 518 else:
499 if prefix: 519 if prefix:
500 completions = api.getCompletions( 520 completions = api.getCompletions(start=word, context=prefix)
501 start=word, context=prefix)
502 if not prefix or not completions: 521 if not prefix or not completions:
503 # if no completions were returned try without prefix 522 # if no completions were returned try without prefix
504 completions = api.getCompletions(start=word) 523 completions = api.getCompletions(start=word)
505 for completion in completions: 524 for completion in completions:
506 if not completion["context"]: 525 if not completion["context"]:
507 entry = completion["completion"] 526 entry = completion["completion"]
508 else: 527 else:
509 entry = "{0} ({1})".format( 528 entry = "{0} ({1})".format(
510 completion["completion"], 529 completion["completion"], completion["context"]
511 completion["context"]
512 ) 530 )
513 if entry in completionsList: 531 if entry in completionsList:
514 completionsList.remove(entry) 532 completionsList.remove(entry)
515 if completion["pictureId"]: 533 if completion["pictureId"]:
516 entry += "?{0}".format(completion["pictureId"]) 534 entry += "?{0}".format(completion["pictureId"])
524 if cont: 542 if cont:
525 continue 543 continue
526 if entry not in completionsList: 544 if entry not in completionsList:
527 completionsList.append(entry) 545 completionsList.append(entry)
528 return completionsList 546 return completionsList
529 547
530 def __getDocumentCompletions(self, editor, word, context, sep, prefix, 548 def __getDocumentCompletions(
531 module, doHierarchy=False): 549 self, editor, word, context, sep, prefix, module, doHierarchy=False
550 ):
532 """ 551 """
533 Private method to determine autocompletion proposals from the document. 552 Private method to determine autocompletion proposals from the document.
534 553
535 @param editor reference to the editor object 554 @param editor reference to the editor object
536 @type Editor 555 @type Editor
537 @param word string to be completed 556 @param word string to be completed
538 @type str 557 @type str
539 @param context flag indicating to autocomplete a context 558 @param context flag indicating to autocomplete a context
548 @type bool 567 @type bool
549 @return list of possible completions 568 @return list of possible completions
550 @rtype list of str 569 @rtype list of str
551 """ 570 """
552 completionsList = [] 571 completionsList = []
553 572
554 prefixFound = False 573 prefixFound = False
555 if prefix and module: 574 if prefix and module:
556 from QScintilla.Editor import Editor 575 from QScintilla.Editor import Editor
557 576
558 line, col = editor.getCursorPosition() 577 line, col = editor.getCursorPosition()
559 if prefix in ["cls", "self"]: 578 if prefix in ["cls", "self"]:
560 prefixFound = True 579 prefixFound = True
561 for cl in module.classes.values(): 580 for cl in module.classes.values():
562 if ( 581 if line >= cl.lineno and (
563 line >= cl.lineno and 582 cl.endlineno == -1 or line <= cl.endlineno
564 (cl.endlineno == -1 or line <= cl.endlineno)
565 ): 583 ):
566 comps = [] 584 comps = []
567 for method in cl.methods.values(): 585 for method in cl.methods.values():
568 if method.name == "__init__": 586 if method.name == "__init__":
569 continue 587 continue
573 elif method.isProtected(): 591 elif method.isProtected():
574 iconID = Editor.MethodProtectedID 592 iconID = Editor.MethodProtectedID
575 else: 593 else:
576 iconID = Editor.MethodID 594 iconID = Editor.MethodID
577 if ( 595 if (
578 (prefix == "cls" and 596 prefix == "cls" and method.modifier == method.Class
579 method.modifier == method.Class) or 597 ) or prefix == "self":
580 prefix == "self"
581 ):
582 comps.append((method.name, cl.name, iconID)) 598 comps.append((method.name, cl.name, iconID))
583 if prefix != "cls": 599 if prefix != "cls":
584 for attribute in cl.attributes.values(): 600 for attribute in cl.attributes.values():
585 # determine icon type 601 # determine icon type
586 if attribute.isPrivate(): 602 if attribute.isPrivate():
597 elif attribute.isProtected(): 613 elif attribute.isProtected():
598 iconID = Editor.AttributeProtectedID 614 iconID = Editor.AttributeProtectedID
599 else: 615 else:
600 iconID = Editor.AttributeID 616 iconID = Editor.AttributeID
601 comps.append((attribute.name, cl.name, iconID)) 617 comps.append((attribute.name, cl.name, iconID))
602 618
603 if word != prefix: 619 if word != prefix:
604 completionsList.extend( 620 completionsList.extend(
605 ["{0} ({1})?{2}".format(c[0], c[1], c[2]) 621 [
606 for c in comps if c[0].startswith(word)]) 622 "{0} ({1})?{2}".format(c[0], c[1], c[2])
623 for c in comps
624 if c[0].startswith(word)
625 ]
626 )
607 else: 627 else:
608 completionsList.extend( 628 completionsList.extend(
609 ["{0} ({1})?{2}".format(c[0], c[1], c[2]) 629 [
610 for c in comps]) 630 "{0} ({1})?{2}".format(c[0], c[1], c[2])
611 631 for c in comps
632 ]
633 )
634
612 for sup in cl.super: 635 for sup in cl.super:
613 if sup in module.classes: 636 if sup in module.classes:
614 if word == prefix: 637 if word == prefix:
615 nword = sup 638 nword = sup
616 else: 639 else:
617 nword = word 640 nword = word
618 completionsList.extend( 641 completionsList.extend(
619 self.__getDocumentCompletions( 642 self.__getDocumentCompletions(
620 editor, nword, context, sep, sup, 643 editor,
621 module, doHierarchy=True)) 644 nword,
622 645 context,
646 sep,
647 sup,
648 module,
649 doHierarchy=True,
650 )
651 )
652
623 break 653 break
624 else: 654 else:
625 # possibly completing a named class attribute or method 655 # possibly completing a named class attribute or method
626 if prefix in module.classes: 656 if prefix in module.classes:
627 prefixFound = True 657 prefixFound = True
628 cl = module.classes[prefix] 658 cl = module.classes[prefix]
629 comps = [] 659 comps = []
630 for method in cl.methods.values(): 660 for method in cl.methods.values():
631 if method.name == "__init__": 661 if method.name == "__init__":
632 continue 662 continue
633 if ( 663 if doHierarchy or method.modifier in [
634 doHierarchy or 664 method.Class,
635 method.modifier in [method.Class, method.Static] 665 method.Static,
636 ): 666 ]:
637 # determine icon type 667 # determine icon type
638 if method.isPrivate(): 668 if method.isPrivate():
639 if doHierarchy: 669 if doHierarchy:
640 continue 670 continue
641 iconID = Editor.MethodPrivateID 671 iconID = Editor.MethodPrivateID
651 elif attribute.isProtected(): 681 elif attribute.isProtected():
652 iconID = Editor.AttributeProtectedID 682 iconID = Editor.AttributeProtectedID
653 else: 683 else:
654 iconID = Editor.AttributeID 684 iconID = Editor.AttributeID
655 comps.append((attribute.name, cl.name, iconID)) 685 comps.append((attribute.name, cl.name, iconID))
656 686
657 if word != prefix: 687 if word != prefix:
658 completionsList.extend( 688 completionsList.extend(
659 ["{0} ({1})?{2}".format(c[0], c[1], c[2]) 689 [
660 for c in comps if c[0].startswith(word)]) 690 "{0} ({1})?{2}".format(c[0], c[1], c[2])
691 for c in comps
692 if c[0].startswith(word)
693 ]
694 )
661 else: 695 else:
662 completionsList.extend( 696 completionsList.extend(
663 ["{0} ({1})?{2}".format(c[0], c[1], c[2]) 697 ["{0} ({1})?{2}".format(c[0], c[1], c[2]) for c in comps]
664 for c in comps]) 698 )
665 699
666 for sup in cl.super: 700 for sup in cl.super:
667 if sup in module.classes: 701 if sup in module.classes:
668 if word == prefix: 702 if word == prefix:
669 nword = sup 703 nword = sup
670 else: 704 else:
671 nword = word 705 nword = word
672 completionsList.extend( 706 completionsList.extend(
673 self.__getDocumentCompletions( 707 self.__getDocumentCompletions(
674 editor, nword, context, sep, sup, module, 708 editor,
675 doHierarchy=True)) 709 nword,
676 710 context,
711 sep,
712 sup,
713 module,
714 doHierarchy=True,
715 )
716 )
717
677 if not prefixFound: 718 if not prefixFound:
678 currentPos = editor.currentPosition() 719 currentPos = editor.currentPosition()
679 if context: 720 if context:
680 word += sep 721 word += sep
681 722
682 if editor.isUtf8(): 723 if editor.isUtf8():
683 sword = word.encode("utf-8") 724 sword = word.encode("utf-8")
684 else: 725 else:
685 sword = word 726 sword = word
686 res = editor.findFirstTarget( 727 res = editor.findFirstTarget(
687 sword, False, editor.autoCompletionCaseSensitivity(), 728 sword,
688 False, begline=0, begindex=0, ws_=True) 729 False,
730 editor.autoCompletionCaseSensitivity(),
731 False,
732 begline=0,
733 begindex=0,
734 ws_=True,
735 )
689 while res: 736 while res:
690 start, length = editor.getFoundTarget() 737 start, length = editor.getFoundTarget()
691 pos = start + length 738 pos = start + length
692 if pos != currentPos: 739 if pos != currentPos:
693 if context: 740 if context:
694 completion = "" 741 completion = ""
695 else: 742 else:
696 completion = word 743 completion = word
697 line, index = editor.lineIndexFromPosition(pos) 744 line, index = editor.lineIndexFromPosition(pos)
698 curWord = editor.getWord(line, index, useWordChars=False) 745 curWord = editor.getWord(line, index, useWordChars=False)
699 completion += curWord[len(completion):] 746 completion += curWord[len(completion) :]
700 if ( 747 if (
701 completion and 748 completion
702 completion not in completionsList and 749 and completion not in completionsList
703 completion != word 750 and completion != word
704 ): 751 ):
705 completionsList.append( 752 completionsList.append(
706 "{0}?{1}".format( 753 "{0}?{1}".format(completion, self.__fromDocumentID)
707 completion, self.__fromDocumentID)) 754 )
708 755
709 res = editor.findNextTarget() 756 res = editor.findNextTarget()
710 757
711 completionsList.sort() 758 completionsList.sort()
712 return completionsList 759 return completionsList
713 760
714 ########################### 761 ###########################
715 ## calltips methods below 762 ## calltips methods below
716 ########################### 763 ###########################
717 764
718 def __setCalltipsHook(self, editor): 765 def __setCalltipsHook(self, editor):
719 """ 766 """
720 Private method to set the calltip hook. 767 Private method to set the calltip hook.
721 768
722 @param editor reference to the editor 769 @param editor reference to the editor
723 @type Editor 770 @type Editor
724 """ 771 """
725 editor.addCallTipHook("Assistant", self.calltips) 772 editor.addCallTipHook("Assistant", self.calltips)
726 773
727 def __unsetCalltipsHook(self, editor): 774 def __unsetCalltipsHook(self, editor):
728 """ 775 """
729 Private method to unset the calltip hook. 776 Private method to unset the calltip hook.
730 777
731 @param editor reference to the editor 778 @param editor reference to the editor
732 @type Editor 779 @type Editor
733 """ 780 """
734 editor.removeCallTipHook("Assistant") 781 editor.removeCallTipHook("Assistant")
735 782
736 def calltips(self, editor, pos, commas): 783 def calltips(self, editor, pos, commas):
737 """ 784 """
738 Public method to return a list of calltips. 785 Public method to return a list of calltips.
739 786
740 @param editor reference to the editor 787 @param editor reference to the editor
741 @type Editor 788 @type Editor
742 @param pos position in the text for the calltip 789 @param pos position in the text for the calltip
743 @type int 790 @type int
744 @param commas minimum number of commas contained in the calltip 791 @param commas minimum number of commas contained in the calltip
747 @rtype list of str 794 @rtype list of str
748 """ 795 """
749 language = editor.getApiLanguage() 796 language = editor.getApiLanguage()
750 completeFromDocumentOnly = False 797 completeFromDocumentOnly = False
751 if language in ["", "Guessed"] or language.startswith("Pygments|"): 798 if language in ["", "Guessed"] or language.startswith("Pygments|"):
752 if ( 799 if self.__plugin.getPreferences("AutoCompletionSource") & AcsDocument:
753 self.__plugin.getPreferences("AutoCompletionSource") &
754 AcsDocument
755 ):
756 completeFromDocumentOnly = True 800 completeFromDocumentOnly = True
757 else: 801 else:
758 return [] 802 return []
759 803
760 projectType = self.__getProjectType(editor) 804 projectType = self.__getProjectType(editor)
761 805
762 line, col = editor.lineIndexFromPosition(pos) 806 line, col = editor.lineIndexFromPosition(pos)
763 wc = re.sub("\w", "", editor.wordCharacters()) 807 wc = re.sub("\w", "", editor.wordCharacters())
764 pat = re.compile("\w{0}".format(re.escape(wc))) 808 pat = re.compile("\w{0}".format(re.escape(wc)))
765 text = editor.text(line) 809 text = editor.text(line)
766 while col > 0 and not pat.match(text[col - 1]): 810 while col > 0 and not pat.match(text[col - 1]):
767 col -= 1 811 col -= 1
768 word = editor.getWordLeft(line, col) 812 word = editor.getWordLeft(line, col)
769 813
770 prefix = "" 814 prefix = ""
771 mod = None 815 mod = None
772 beg = editor.text(line)[:col] 816 beg = editor.text(line)[:col]
773 col = len(beg) 817 col = len(beg)
774 wseps = ( 818 wseps = editor.getLexer().autoCompletionWordSeparators() if language else []
775 editor.getLexer().autoCompletionWordSeparators()
776 if language else
777 []
778 )
779 if wseps: 819 if wseps:
780 if col > 0 and beg[col - 1] in wseps: 820 if col > 0 and beg[col - 1] in wseps:
781 col -= 1 821 col -= 1
782 else: 822 else:
783 while col > 0 and beg[col - 1] not in wseps + [" ", "\t"]: 823 while col > 0 and beg[col - 1] not in wseps + [" ", "\t"]:
785 if col >= 0: 825 if col >= 0:
786 col -= 1 826 col -= 1
787 prefix = editor.getWordLeft(line, col) 827 prefix = editor.getWordLeft(line, col)
788 if editor.isPyFile(): 828 if editor.isPyFile():
789 from Utilities.ModuleParser import Module 829 from Utilities.ModuleParser import Module
830
790 src = editor.text() 831 src = editor.text()
791 fn = editor.getFileName() 832 fn = editor.getFileName()
792 if fn is None: 833 if fn is None:
793 fn = "" 834 fn = ""
794 mod = Module("", fn, imp.PY_SOURCE) 835 mod = Module("", fn, imp.PY_SOURCE)
795 mod.scan(src) 836 mod.scan(src)
796 837
797 apiCalltips = [] 838 apiCalltips = []
798 projectCalltips = [] 839 projectCalltips = []
799 documentCalltips = [] 840 documentCalltips = []
800 841
801 if not completeFromDocumentOnly: 842 if not completeFromDocumentOnly:
802 if self.__plugin.getPreferences("AutoCompletionSource") & AcsAPIs: 843 if self.__plugin.getPreferences("AutoCompletionSource") & AcsAPIs:
803 api = self.__apisManager.getAPIs( 844 api = self.__apisManager.getAPIs(language, projectType=projectType)
804 language, projectType=projectType)
805 if api is not None: 845 if api is not None:
806 apiCalltips = self.__getApiCalltips( 846 apiCalltips = self.__getApiCalltips(
807 api, word, commas, prefix, mod, editor) 847 api, word, commas, prefix, mod, editor
808 848 )
809 if ( 849
810 self.__plugin.getPreferences("AutoCompletionSource") & 850 if self.__plugin.getPreferences("AutoCompletionSource") & AcsProject:
811 AcsProject
812 ):
813 api = self.__apisManager.getAPIs(ApisNameProject) 851 api = self.__apisManager.getAPIs(ApisNameProject)
814 projectCalltips = self.__getApiCalltips( 852 projectCalltips = self.__getApiCalltips(
815 api, word, commas, prefix, mod, editor) 853 api, word, commas, prefix, mod, editor
816 854 )
855
817 if self.__plugin.getPreferences("AutoCompletionSource") & AcsDocument: 856 if self.__plugin.getPreferences("AutoCompletionSource") & AcsDocument:
818 documentCalltips = self.__getDocumentCalltips( 857 documentCalltips = self.__getDocumentCalltips(word, prefix, mod, editor)
819 word, prefix, mod, editor) 858
820
821 return sorted( 859 return sorted(
822 set(apiCalltips) 860 set(apiCalltips).union(set(projectCalltips)).union(set(documentCalltips))
823 .union(set(projectCalltips))
824 .union(set(documentCalltips))
825 ) 861 )
826 862
827 def __getApiCalltips(self, api, word, commas, prefix, module, editor): 863 def __getApiCalltips(self, api, word, commas, prefix, module, editor):
828 """ 864 """
829 Private method to determine calltips from APIs. 865 Private method to determine calltips from APIs.
830 866
831 @param api reference to the API object to be used 867 @param api reference to the API object to be used
832 @type APIsManager.DbAPIs 868 @type APIsManager.DbAPIs
833 @param word function to get calltips for 869 @param word function to get calltips for
834 @type str 870 @type str
835 @param commas minimum number of commas contained in the calltip 871 @param commas minimum number of commas contained in the calltip
845 """ 881 """
846 calltips = [] 882 calltips = []
847 if prefix and module and prefix == "self": 883 if prefix and module and prefix == "self":
848 line, col = editor.getCursorPosition() 884 line, col = editor.getCursorPosition()
849 for cl in module.classes.values(): 885 for cl in module.classes.values():
850 if ( 886 if line >= cl.lineno and (cl.endlineno == -1 or line <= cl.endlineno):
851 line >= cl.lineno and
852 (cl.endlineno == -1 or line <= cl.endlineno)
853 ):
854 for superClass in cl.super: 887 for superClass in cl.super:
855 calltips.extend(api.getCalltips( 888 calltips.extend(
856 word, commas, superClass, None, 889 api.getCalltips(
857 self.__plugin.getPreferences( 890 word,
858 "CallTipsContextShown"), 891 commas,
859 followHierarchy=self.__plugin.getPreferences( 892 superClass,
860 "CallTipsFollowHierarchy"))) 893 None,
894 self.__plugin.getPreferences("CallTipsContextShown"),
895 followHierarchy=self.__plugin.getPreferences(
896 "CallTipsFollowHierarchy"
897 ),
898 )
899 )
861 break 900 break
862 else: 901 else:
863 calltips = api.getCalltips( 902 calltips = api.getCalltips(
864 word, commas, self.__lastContext, self.__lastFullContext, 903 word,
865 self.__plugin.getPreferences("CallTipsContextShown")) 904 commas,
866 905 self.__lastContext,
906 self.__lastFullContext,
907 self.__plugin.getPreferences("CallTipsContextShown"),
908 )
909
867 return calltips 910 return calltips
868 911
869 def __getDocumentCalltips(self, word, prefix, module, editor, 912 def __getDocumentCalltips(self, word, prefix, module, editor, doHierarchy=False):
870 doHierarchy=False):
871 """ 913 """
872 Private method to determine calltips from the document. 914 Private method to determine calltips from the document.
873 915
874 @param word function to get calltips for 916 @param word function to get calltips for
875 @type str 917 @type str
876 @param prefix prefix of the word to be completed 918 @param prefix prefix of the word to be completed
877 @type str 919 @type str
878 @param module reference to the scanned module info 920 @param module reference to the scanned module info
890 # prefix can be 'self', 'cls' or a class name 932 # prefix can be 'self', 'cls' or a class name
891 sep = editor.getLexer().autoCompletionWordSeparators()[0] 933 sep = editor.getLexer().autoCompletionWordSeparators()[0]
892 if prefix in ["self", "cls"]: 934 if prefix in ["self", "cls"]:
893 line, col = editor.getCursorPosition() 935 line, col = editor.getCursorPosition()
894 for cl in module.classes.values(): 936 for cl in module.classes.values():
895 if ( 937 if line >= cl.lineno and (
896 line >= cl.lineno and 938 cl.endlineno == -1 or line <= cl.endlineno
897 (cl.endlineno == -1 or line <= cl.endlineno)
898 ): 939 ):
899 if word in cl.methods: 940 if word in cl.methods:
900 method = cl.methods[word] 941 method = cl.methods[word]
901 if ( 942 if prefix == "self" or (
902 prefix == "self" or 943 prefix == "cls" and method.modifier == method.Class
903 (prefix == "cls" and
904 method.modifier == method.Class)
905 ): 944 ):
906 calltips.append( 945 calltips.append(
907 "{0}{1}{2}({3})".format( 946 "{0}{1}{2}({3})".format(
908 cl.name, 947 cl.name,
909 sep, 948 sep,
910 word, 949 word,
911 ', '.join(method.parameters[1:] 950 ", ".join(method.parameters[1:]),
912 ))) 951 )
913 952 )
953
914 for sup in cl.super: 954 for sup in cl.super:
915 calltips.extend(self.__getDocumentCalltips( 955 calltips.extend(
916 word, sup, module, editor, 956 self.__getDocumentCalltips(
917 doHierarchy=True)) 957 word, sup, module, editor, doHierarchy=True
918 958 )
959 )
960
919 break 961 break
920 else: 962 else:
921 if prefix in module.classes: 963 if prefix in module.classes:
922 cl = module.classes[prefix] 964 cl = module.classes[prefix]
923 if word in cl.methods: 965 if word in cl.methods:
924 method = cl.methods[word] 966 method = cl.methods[word]
925 if doHierarchy or method.modifier == method.Class: 967 if doHierarchy or method.modifier == method.Class:
926 calltips.append("{0}{1}{2}({3})".format( 968 calltips.append(
927 cl.name, 969 "{0}{1}{2}({3})".format(
928 sep, 970 cl.name,
929 word, 971 sep,
930 ', '.join(method.parameters[1:]))) 972 word,
931 973 ", ".join(method.parameters[1:]),
974 )
975 )
976
932 for sup in cl.super: 977 for sup in cl.super:
933 calltips.extend(self.__getDocumentCalltips( 978 calltips.extend(
934 word, sup, module, editor, doHierarchy=True)) 979 self.__getDocumentCalltips(
980 word, sup, module, editor, doHierarchy=True
981 )
982 )
935 else: 983 else:
936 # calltip for a module function or class 984 # calltip for a module function or class
937 if word in module.functions: 985 if word in module.functions:
938 fun = module.functions[word] 986 fun = module.functions[word]
939 calltips.append("{0}({1})".format( 987 calltips.append("{0}({1})".format(word, ", ".join(fun.parameters)))
940 word,
941 ', '.join(fun.parameters)))
942 elif word in module.classes: 988 elif word in module.classes:
943 cl = module.classes[word] 989 cl = module.classes[word]
944 if "__init__" in cl.methods: 990 if "__init__" in cl.methods:
945 method = cl.methods["__init__"] 991 method = cl.methods["__init__"]
946 calltips.append("{0}({1})".format( 992 calltips.append(
947 word, 993 "{0}({1})".format(word, ", ".join(method.parameters[1:]))
948 ', '.join(method.parameters[1:]))) 994 )
949 995
950 return calltips 996 return calltips
997
951 998
952 # 999 #
953 # eflag: noqa = M834, W605 1000 # eflag: noqa = M834, W605

eric ide

mercurial