AssistantEric/Assistant.py

changeset 32
68ef15fe34c3
parent 30
8f4d794d8ee0
child 35
6b706b02c2dd
equal deleted inserted replaced
31:ed537ef44f85 32:68ef15fe34c3
7 Module implementing the eric assistant, an alternative autocompletion and 7 Module implementing the eric assistant, an alternative autocompletion and
8 calltips system. 8 calltips system.
9 """ 9 """
10 10
11 import re 11 import re
12 import imp
12 13
13 from PyQt4.QtCore import QRegExp, QObject 14 from PyQt4.QtCore import QRegExp, QObject
14 15
15 from E5Gui.E5Application import e5App 16 from E5Gui.E5Application import e5App
16 17
17 from .APIsManager import APIsManager, ApisNameProject 18 from .APIsManager import APIsManager, ApisNameProject
18 19
19 from QScintilla.Editor import Editor 20 from QScintilla.Editor import Editor
20 21
21 import Preferences 22 import Preferences
23 from Utilities.ModuleParser import Module
22 24
23 AcsAPIs = 0x0001 25 AcsAPIs = 0x0001
24 AcsDocument = 0x0002 26 AcsDocument = 0x0002
25 AcsProject = 0x0004 27 AcsProject = 0x0004
26 AcsOther = 0x1000 28 AcsOther = 0x1000
248 docCompletionsList = [] 250 docCompletionsList = []
249 projectCompletionList = [] 251 projectCompletionList = []
250 sep = "" 252 sep = ""
251 if context: 253 if context:
252 wc = re.sub("\w", "", editor.wordCharacters()) 254 wc = re.sub("\w", "", editor.wordCharacters())
255 pat = re.compile("\w{0}".format(re.escape(wc)))
253 text = editor.text(line) 256 text = editor.text(line)
254 257
255 beg = text[:col] 258 beg = text[:col]
256 for wsep in editor.getLexer().autoCompletionWordSeparators(): 259 for wsep in editor.getLexer().autoCompletionWordSeparators():
257 if beg.endswith(wsep): 260 if beg.endswith(wsep):
258 sep = wsep 261 sep = wsep
259 break 262 break
260 263
261 depth = 0 264 depth = 0
262 while col > 0 and \ 265 while col > 0 and \
263 (not text[col - 1].isalnum() or \ 266 not pat.match(text[col - 1]):
264 (wc and text[col - 1] not in wc)):
265 ch = text[col - 1] 267 ch = text[col - 1]
266 if ch == ')': 268 if ch == ')':
267 depth = 1 269 depth = 1
268 270
269 # ignore everything back to the start of the 271 # ignore everything back to the start of the
286 if context: 288 if context:
287 self.__lastContext = word 289 self.__lastContext = word
288 else: 290 else:
289 self.__lastContext = None 291 self.__lastContext = None
290 292
293 prefix = ""
294 mod = None
295 if context:
296 beg = beg[:col + 1]
297 else:
298 beg = editor.text(line)[:col]
299 col = len(beg)
300 wsep = editor.getLexer().autoCompletionWordSeparators()
301 if wsep:
302 if beg[col - 1] in wsep:
303 col -= 1
304 else:
305 while col >= 0 and beg[col - 1] not in wsep:
306 col -= 1
307 if col >= 0:
308 col -= 1
309 prefix = editor.getWordLeft(line, col)
310 if editor.isPy2File() or editor.isPy3File():
311 src = editor.text()
312 fn = editor.getFileName()
313 if fn is None:
314 fn = ""
315 mod = Module("", fn, imp.PY_SOURCE)
316 mod.scan(src)
317
291 if word: 318 if word:
292 if self.__plugin.getPreferences("AutoCompletionSource") & AcsAPIs: 319 if self.__plugin.getPreferences("AutoCompletionSource") & AcsAPIs:
293 api = self.__apisManager.getAPIs(language) 320 api = self.__apisManager.getAPIs(language)
294 apiCompletionsList = self.__getApiCompletions(api, word, context) 321 apiCompletionsList = self.__getApiCompletions(
322 api, word, context, prefix, mod, editor)
295 323
296 if self.__plugin.getPreferences("AutoCompletionSource") & AcsProject: 324 if self.__plugin.getPreferences("AutoCompletionSource") & AcsProject:
297 api = self.__apisManager.getAPIs(ApisNameProject) 325 api = self.__apisManager.getAPIs(ApisNameProject)
298 projectCompletionList = self.__getApiCompletions(api, word, context) 326 projectCompletionList = self.__getApiCompletions(
327 api, word, context, prefix, mod, editor)
299 328
300 if self.__plugin.getPreferences("AutoCompletionSource") & AcsDocument: 329 if self.__plugin.getPreferences("AutoCompletionSource") & AcsDocument:
301 docCompletionsList = \ 330 docCompletionsList = self.getCompletionsFromDocument(
302 self.getCompletionsFromDocument(editor, word, context, sep) 331 editor, word, context, sep, prefix, mod)
303 332
304 completionsList = list( 333 completionsList = list(
305 set(apiCompletionsList) 334 set(apiCompletionsList)
306 .union(set(docCompletionsList)) 335 .union(set(docCompletionsList))
307 .union(set(projectCompletionList)) 336 .union(set(projectCompletionList))
309 338
310 if len(completionsList) > 0: 339 if len(completionsList) > 0:
311 completionsList.sort() 340 completionsList.sort()
312 editor.showUserList(EditorAutoCompletionListID, completionsList) 341 editor.showUserList(EditorAutoCompletionListID, completionsList)
313 342
314 def __getApiCompletions(self, api, word, context): 343 def __getApiCompletions(self, api, word, context, prefix, module, editor):
315 """ 344 """
316 Private method to determine a list of completions from an API object. 345 Private method to determine a list of completions from an API object.
317 346
318 @param api reference to the API object to be used (APIsManager.DbAPIs) 347 @param api reference to the API object to be used (APIsManager.DbAPIs)
319 @param word word (or wordpart) to complete (string) 348 @param word word (or wordpart) to complete (string)
320 @param context flag indicating to autocomplete a context (boolean) 349 @param context flag indicating to autocomplete a context (boolean)
350 @param prefix prefix of the word to be completed (string)
351 @param module reference to the scanned module info (Module)
352 @param editor reference to the editor object (QScintilla.Editor)
321 @return list of possible completions (list of strings) 353 @return list of possible completions (list of strings)
322 """ 354 """
323 completionsList = [] 355 completionsList = []
324 if api is not None: 356 if api is not None:
325 if context: 357 if prefix and module and prefix == "self":
358 line, col = editor.getCursorPosition()
359 for cl in module.classes.values():
360 if line >= cl.lineno and \
361 (cl.endlineno == -1 or line <= cl.endlineno):
362 completions = []
363 for super in cl.super:
364 if prefix == word:
365 completions.extend(
366 api.getCompletions(context=super))
367 else:
368 completions.extend(
369 api.getCompletions(start=word, context=super))
370 for completion in completions:
371 if not completion["context"]:
372 entry = completion["completion"]
373 else:
374 entry = "{0} ({1})".format(
375 completion["completion"],
376 completion["context"]
377 )
378 if entry in completionsList:
379 completionsList.remove(entry)
380 if completion["pictureId"]:
381 entry += "?{0}".format(completion["pictureId"])
382 else:
383 cont = False
384 re = QRegExp(QRegExp.escape(entry) + "\?\d{,2}")
385 for comp in completionsList:
386 if re.exactMatch(comp):
387 cont = True
388 break
389 if cont:
390 continue
391 if entry not in completionsList:
392 completionsList.append(entry)
393
394 break
395 elif context:
326 completions = api.getCompletions(context=word) 396 completions = api.getCompletions(context=word)
327 for completion in completions: 397 for completion in completions:
328 entry = completion["completion"] 398 entry = completion["completion"]
329 if completion["pictureId"]: 399 if completion["pictureId"]:
330 entry += "?{0}".format(completion["pictureId"]) 400 entry += "?{0}".format(completion["pictureId"])
355 continue 425 continue
356 if entry not in completionsList: 426 if entry not in completionsList:
357 completionsList.append(entry) 427 completionsList.append(entry)
358 return completionsList 428 return completionsList
359 429
360 def getCompletionsFromDocument(self, editor, word, context, sep): 430 def getCompletionsFromDocument(self, editor, word, context, sep, prefix, module):
361 """ 431 """
362 Public method to determine autocompletion proposals from the document. 432 Public method to determine autocompletion proposals from the document.
363 433
364 @param editor reference to the editor object (QScintilla.Editor) 434 @param editor reference to the editor object (QScintilla.Editor)
365 @param word string to be completed (string) 435 @param word string to be completed (string)
366 @param context flag indicating to autocomplete a context (boolean) 436 @param context flag indicating to autocomplete a context (boolean)
367 @param sep separator string (string) 437 @param sep separator string (string)
438 @param prefix prefix of the word to be completed (string)
439 @param module reference to the scanned module info (Module)
368 @return list of possible completions (list of strings) 440 @return list of possible completions (list of strings)
369 """ 441 """
370 currentPos = editor.currentPosition()
371 completionsList = [] 442 completionsList = []
372 if context: 443
373 word += sep 444 prefixFound = False
374 445 if prefix and module:
375 if editor.isUtf8(): 446 line, col = editor.getCursorPosition()
376 sword = word.encode("utf-8") 447 if prefix in ["cls", "self"]:
377 else: 448 prefixFound = True
378 sword = word 449 for cl in module.classes.values():
379 res = editor.findFirstTarget(sword, False, 450 if line >= cl.lineno and \
380 editor.autoCompletionCaseSensitivity(), 451 (cl.endlineno == -1 or line <= cl.endlineno):
381 False, begline=0, begindex=0, ws_=True) 452 comps = []
382 while res: 453 for method in cl.methods.values():
383 start, length = editor.getFoundTarget() 454 if method.name == "__init__":
384 pos = start + length 455 continue
385 if pos != currentPos: 456 # determine icon type
386 if context: 457 if method.isPrivate():
387 completion = "" 458 iconID = Editor.MethodPrivateID
388 else: 459 elif method.isProtected():
389 completion = word 460 iconID = Editor.MethodProtectedID
390 line, index = editor.lineIndexFromPosition(pos) 461 else:
391 curWord = editor.getWord(line, index, useWordChars=False) 462 iconID = Editor.MethodID
392 completion += curWord[len(completion):] 463 if hasattr(method, "modifier"):
393 if completion and completion not in completionsList: 464 if (prefix == "cls" and \
394 completionsList.append( 465 method.modifier == method.Class) or \
395 "{0}?{1}".format(completion, self.__fromDocumentID)) 466 prefix == "self":
467 comps.append((method.name, iconID))
468 else:
469 # eric 5.1 cannot differentiate method types
470 comps.append((method.name, iconID))
471 if prefix != "cls":
472 for attribute in cl.attributes.values():
473 # determine icon type
474 if attribute.isPrivate():
475 iconID = Editor.AttributePrivateID
476 elif attribute.isProtected():
477 iconID = Editor.AttributeProtectedID
478 else:
479 iconID = Editor.AttributePrivateID
480 comps.append((attribute.name, iconID))
481 for attribute in cl.globals.values():
482 # determine icon type
483 if attribute.isPrivate():
484 iconID = Editor.AttributePrivateID
485 elif attribute.isProtected():
486 iconID = Editor.AttributeProtectedID
487 else:
488 iconID = Editor.AttributePrivateID
489 comps.append((attribute.name, iconID))
490
491 if word != prefix:
492 completionsList.extend(
493 ["{0}?{1}".format(c[0], c[1])
494 for c in comps if c[0].startswith(word)])
495 else:
496 completionsList.extend(
497 ["{0}?{1}".format(c[0], c[1])
498 for c in comps])
499 break
500 else:
501 # possibly completing a named class attribute or method
502 if prefix in module.classes:
503 prefixFound = True
504 cl = module.classes[prefix]
505 comps = []
506 for method in cl.methods.values():
507 if method.name == "__init__":
508 continue
509 if not hasattr(method, "modifier"):
510 # eric 5.1 cannot differentiate method types
511 continue
512 if method.modifier in [method.Class, method.Static]:
513 # determine icon type
514 if method.isPrivate():
515 iconID = Editor.MethodPrivateID
516 elif method.isProtected():
517 iconID = Editor.MethodProtectedID
518 else:
519 iconID = Editor.MethodID
520 comps.append((method.name, iconID))
521 for attribute in cl.globals.values():
522 # determine icon type
523 if attribute.isPrivate():
524 iconID = Editor.AttributePrivateID
525 elif attribute.isProtected():
526 iconID = Editor.AttributeProtectedID
527 else:
528 iconID = Editor.AttributePrivateID
529 comps.append((attribute.name, iconID))
530
531 if word != prefix:
532 completionsList.extend(
533 ["{0}?{1}".format(c[0], c[1])
534 for c in comps if c[0].startswith(word)])
535 else:
536 completionsList.extend(
537 ["{0}?{1}".format(c[0], c[1])
538 for c in comps])
539
540 if not prefixFound:
541 currentPos = editor.currentPosition()
542 if context:
543 word += sep
396 544
397 res = editor.findNextTarget() 545 if editor.isUtf8():
546 sword = word.encode("utf-8")
547 else:
548 sword = word
549 res = editor.findFirstTarget(sword, False,
550 editor.autoCompletionCaseSensitivity(),
551 False, begline=0, begindex=0, ws_=True)
552 while res:
553 start, length = editor.getFoundTarget()
554 pos = start + length
555 if pos != currentPos:
556 if context:
557 completion = ""
558 else:
559 completion = word
560 line, index = editor.lineIndexFromPosition(pos)
561 curWord = editor.getWord(line, index, useWordChars=False)
562 completion += curWord[len(completion):]
563 if completion and completion not in completionsList:
564 completionsList.append(
565 "{0}?{1}".format(completion, self.__fromDocumentID))
566
567 res = editor.findNextTarget()
398 568
399 completionsList.sort() 569 completionsList.sort()
400 return completionsList 570 return completionsList
401 571
402 ########################### 572 ###########################
432 if language == "": 602 if language == "":
433 return 603 return
434 604
435 line, col = editor.lineIndexFromPosition(pos) 605 line, col = editor.lineIndexFromPosition(pos)
436 wc = re.sub("\w", "", editor.wordCharacters()) 606 wc = re.sub("\w", "", editor.wordCharacters())
607 pat = re.compile("\w{0}".format(re.escape(wc)))
437 text = editor.text(line) 608 text = editor.text(line)
438 while col > 0 and \ 609 while col > 0 and \
439 (not text[col - 1].isalnum() or \ 610 not pat.match(text[col - 1]):
440 (wc and text[col - 1] not in wc)):
441 col -= 1 611 col -= 1
442 word = editor.getWordLeft(line, col) 612 word = editor.getWordLeft(line, col)
443 613
614 prefix = ""
615 mod = None
616 beg = editor.text(line)[:col]
617 col = len(beg)
618 wsep = editor.getLexer().autoCompletionWordSeparators()
619 if wsep:
620 if beg[col - 1] in wsep:
621 col -= 1
622 else:
623 while col >= 0 and beg[col - 1] not in wsep + [" ", "\t"]:
624 col -= 1
625 if col >= 0:
626 col -= 1
627 prefix = editor.getWordLeft(line, col)
628 if editor.isPy2File() or editor.isPy3File():
629 src = editor.text()
630 fn = editor.getFileName()
631 if fn is None:
632 fn = ""
633 mod = Module("", fn, imp.PY_SOURCE)
634 mod.scan(src)
635
444 apiCalltips = [] 636 apiCalltips = []
445 projectCalltips = [] 637 projectCalltips = []
638 documentCalltips = []
446 639
447 if self.__plugin.getPreferences("AutoCompletionSource") & AcsAPIs: 640 if self.__plugin.getPreferences("AutoCompletionSource") & AcsAPIs:
448 api = self.__apisManager.getAPIs(language) 641 api = self.__apisManager.getAPIs(language)
449 if api is not None: 642 if api is not None:
450 apiCalltips = api.getCalltips(word, commas, self.__lastContext, 643 apiCalltips = self.__getApiCalltips(
451 self.__lastFullContext, 644 api, word, commas, prefix, mod, editor)
452 self.__plugin.getPreferences("CallTipsContextShown"))
453 645
454 if self.__plugin.getPreferences("AutoCompletionSource") & AcsProject: 646 if self.__plugin.getPreferences("AutoCompletionSource") & AcsProject:
455 api = self.__apisManager.getAPIs(ApisNameProject) 647 api = self.__apisManager.getAPIs(ApisNameProject)
456 projectCalltips = api.getCalltips(word, commas, self.__lastContext, 648 projectCalltips = self.__getApiCalltips(
649 api, word, commas, prefix, mod, editor)
650
651 if self.__plugin.getPreferences("AutoCompletionSource") & AcsDocument:
652 documentCalltips = self.__getCalltipsFromDocument(
653 word, prefix, mod, editor)
654
655 return sorted(
656 set(apiCalltips)
657 .union(set(projectCalltips))
658 .union(set(documentCalltips))
659 )
660
661 def __getApiCalltips(self, api, word, commas, prefix, module, editor):
662 """
663 Private method to determine calltips from APIs.
664
665 @param api reference to the API object to be used (APIsManager.DbAPIs)
666 @param word function to get calltips for (string)
667 @param commas minimum number of commas contained in the calltip (integer)
668 @param prefix prefix of the word to be completed (string)
669 @param module reference to the scanned module info (Module)
670 @param editor reference to the editor object (QScintilla.Editor)
671 @return list of calltips (list of string)
672 """
673 calltips = []
674 if prefix and module and prefix == "self":
675 line, col = editor.getCursorPosition()
676 for cl in module.classes.values():
677 if line >= cl.lineno and \
678 (cl.endlineno == -1 or line <= cl.endlineno):
679 for super in cl.super:
680 calltips.extend(api.getCalltips(word, commas, super, None,
681 self.__plugin.getPreferences("CallTipsContextShown")))
682 break
683 else:
684 calltips = api.getCalltips(word, commas, self.__lastContext,
457 self.__lastFullContext, 685 self.__lastFullContext,
458 self.__plugin.getPreferences("CallTipsContextShown")) 686 self.__plugin.getPreferences("CallTipsContextShown"))
459 687
460 return sorted(set(apiCalltips).union(set(projectCalltips))) 688 return calltips
689
690 def __getCalltipsFromDocument(self, word, prefix, module, editor):
691 """
692 Private method to determine calltips from the document.
693
694 @param word function to get calltips for (string)
695 @param prefix prefix of the word to be completed (string)
696 @param module reference to the scanned module info (Module)
697 @param editor reference to the editor object (QScintilla.Editor)
698 @return list of calltips (list of string)
699 """
700 calltips = []
701 if module:
702 if prefix:
703 # prefix can be 'self', 'cls' or a class name
704 sep = editor.getLexer().autoCompletionWordSeparators()[0]
705 if prefix in ["self", "cls"]:
706 line, col = editor.getCursorPosition()
707 for cl in module.classes.values():
708 if line >= cl.lineno and \
709 (cl.endlineno == -1 or line <= cl.endlineno):
710 if word in cl.methods:
711 method = cl.methods[word]
712 if hasattr(method, "modifier"):
713 if prefix == "self" or \
714 (prefix == "cls" and \
715 method.modifier == method.Class):
716 calltips.append("{0}{1}{2}({3})".format(
717 cl.name,
718 sep,
719 word,
720 ', '.join(method.parameters[1:])))
721 else:
722 # eric 5.1 cannot differentiate method types
723 calltips.append("{0}{1}{2}({3})".format(
724 cl.name,
725 sep,
726 word,
727 ', '.join(method.parameters[1:])))
728 break
729 else:
730 if prefix in module.classes:
731 cl = module.classes[prefix]
732 if word in cl.methods:
733 method = cl.methods[word]
734 if hasattr(method, "modifier") and \
735 method.modifier == method.Class:
736 # only eric 5.2 and newer can differentiate method types
737 calltips.append("{0}{1}{2}({3})".format(
738 cl.name,
739 sep,
740 word,
741 ', '.join(method.parameters[1:])))
742 else:
743 # calltip for a module function or class
744 if word in module.functions:
745 fun = module.functions[word]
746 calltips.append("{0}({1})".format(
747 word,
748 ', '.join(fun.parameters[1:])))
749 elif word in module.classes:
750 cl = module.classes[word]
751 if "__init__" in cl.methods:
752 method = cl.methods["__init__"]
753 calltips.append("{0}({1})".format(
754 word,
755 ', '.join(method.parameters[1:])))
756
757 return calltips

eric ide

mercurial