diff -r ed537ef44f85 -r 68ef15fe34c3 AssistantEric/Assistant.py --- a/AssistantEric/Assistant.py Sat Jul 30 10:55:32 2011 +0200 +++ b/AssistantEric/Assistant.py Sun Sep 25 17:18:48 2011 +0200 @@ -9,6 +9,7 @@ """ import re +import imp from PyQt4.QtCore import QRegExp, QObject @@ -19,6 +20,7 @@ from QScintilla.Editor import Editor import Preferences +from Utilities.ModuleParser import Module AcsAPIs = 0x0001 AcsDocument = 0x0002 @@ -250,6 +252,7 @@ sep = "" if context: wc = re.sub("\w", "", editor.wordCharacters()) + pat = re.compile("\w{0}".format(re.escape(wc))) text = editor.text(line) beg = text[:col] @@ -260,8 +263,7 @@ depth = 0 while col > 0 and \ - (not text[col - 1].isalnum() or \ - (wc and text[col - 1] not in wc)): + not pat.match(text[col - 1]): ch = text[col - 1] if ch == ')': depth = 1 @@ -288,18 +290,45 @@ else: self.__lastContext = None + prefix = "" + mod = None + if context: + beg = beg[:col + 1] + else: + beg = editor.text(line)[:col] + col = len(beg) + wsep = editor.getLexer().autoCompletionWordSeparators() + if wsep: + if beg[col - 1] in wsep: + col -= 1 + else: + while col >= 0 and beg[col - 1] not in wsep: + col -= 1 + if col >= 0: + col -= 1 + prefix = editor.getWordLeft(line, col) + if editor.isPy2File() or editor.isPy3File(): + src = editor.text() + fn = editor.getFileName() + if fn is None: + fn = "" + mod = Module("", fn, imp.PY_SOURCE) + mod.scan(src) + if word: if self.__plugin.getPreferences("AutoCompletionSource") & AcsAPIs: api = self.__apisManager.getAPIs(language) - apiCompletionsList = self.__getApiCompletions(api, word, context) + apiCompletionsList = self.__getApiCompletions( + api, word, context, prefix, mod, editor) if self.__plugin.getPreferences("AutoCompletionSource") & AcsProject: api = self.__apisManager.getAPIs(ApisNameProject) - projectCompletionList = self.__getApiCompletions(api, word, context) + projectCompletionList = self.__getApiCompletions( + api, word, context, prefix, mod, editor) if self.__plugin.getPreferences("AutoCompletionSource") & AcsDocument: - docCompletionsList = \ - self.getCompletionsFromDocument(editor, word, context, sep) + docCompletionsList = self.getCompletionsFromDocument( + editor, word, context, sep, prefix, mod) completionsList = list( set(apiCompletionsList) @@ -311,18 +340,59 @@ completionsList.sort() editor.showUserList(EditorAutoCompletionListID, completionsList) - def __getApiCompletions(self, api, word, context): + def __getApiCompletions(self, api, word, context, prefix, module, editor): """ Private method to determine a list of completions from an API object. @param api reference to the API object to be used (APIsManager.DbAPIs) @param word word (or wordpart) to complete (string) @param context flag indicating to autocomplete a context (boolean) + @param prefix prefix of the word to be completed (string) + @param module reference to the scanned module info (Module) + @param editor reference to the editor object (QScintilla.Editor) @return list of possible completions (list of strings) """ completionsList = [] if api is not None: - if context: + if prefix and module and prefix == "self": + line, col = editor.getCursorPosition() + for cl in module.classes.values(): + if line >= cl.lineno and \ + (cl.endlineno == -1 or line <= cl.endlineno): + completions = [] + for super in cl.super: + if prefix == word: + completions.extend( + api.getCompletions(context=super)) + else: + completions.extend( + api.getCompletions(start=word, context=super)) + for completion in completions: + if not completion["context"]: + entry = completion["completion"] + else: + entry = "{0} ({1})".format( + completion["completion"], + completion["context"] + ) + if entry in completionsList: + completionsList.remove(entry) + if completion["pictureId"]: + entry += "?{0}".format(completion["pictureId"]) + else: + cont = False + re = QRegExp(QRegExp.escape(entry) + "\?\d{,2}") + for comp in completionsList: + if re.exactMatch(comp): + cont = True + break + if cont: + continue + if entry not in completionsList: + completionsList.append(entry) + + break + elif context: completions = api.getCompletions(context=word) for completion in completions: entry = completion["completion"] @@ -357,7 +427,7 @@ completionsList.append(entry) return completionsList - def getCompletionsFromDocument(self, editor, word, context, sep): + def getCompletionsFromDocument(self, editor, word, context, sep, prefix, module): """ Public method to determine autocompletion proposals from the document. @@ -365,36 +435,136 @@ @param word string to be completed (string) @param context flag indicating to autocomplete a context (boolean) @param sep separator string (string) + @param prefix prefix of the word to be completed (string) + @param module reference to the scanned module info (Module) @return list of possible completions (list of strings) """ - currentPos = editor.currentPosition() completionsList = [] - if context: - word += sep - if editor.isUtf8(): - sword = word.encode("utf-8") - else: - sword = word - res = editor.findFirstTarget(sword, False, - editor.autoCompletionCaseSensitivity(), - False, begline=0, begindex=0, ws_=True) - while res: - start, length = editor.getFoundTarget() - pos = start + length - if pos != currentPos: - if context: - completion = "" - else: - completion = word - line, index = editor.lineIndexFromPosition(pos) - curWord = editor.getWord(line, index, useWordChars=False) - completion += curWord[len(completion):] - if completion and completion not in completionsList: - completionsList.append( - "{0}?{1}".format(completion, self.__fromDocumentID)) + prefixFound = False + if prefix and module: + line, col = editor.getCursorPosition() + if prefix in ["cls", "self"]: + prefixFound = True + for cl in module.classes.values(): + if line >= cl.lineno and \ + (cl.endlineno == -1 or line <= cl.endlineno): + comps = [] + for method in cl.methods.values(): + if method.name == "__init__": + continue + # determine icon type + if method.isPrivate(): + iconID = Editor.MethodPrivateID + elif method.isProtected(): + iconID = Editor.MethodProtectedID + else: + iconID = Editor.MethodID + if hasattr(method, "modifier"): + if (prefix == "cls" and \ + method.modifier == method.Class) or \ + prefix == "self": + comps.append((method.name, iconID)) + else: + # eric 5.1 cannot differentiate method types + comps.append((method.name, iconID)) + if prefix != "cls": + for attribute in cl.attributes.values(): + # determine icon type + if attribute.isPrivate(): + iconID = Editor.AttributePrivateID + elif attribute.isProtected(): + iconID = Editor.AttributeProtectedID + else: + iconID = Editor.AttributePrivateID + comps.append((attribute.name, iconID)) + for attribute in cl.globals.values(): + # determine icon type + if attribute.isPrivate(): + iconID = Editor.AttributePrivateID + elif attribute.isProtected(): + iconID = Editor.AttributeProtectedID + else: + iconID = Editor.AttributePrivateID + comps.append((attribute.name, iconID)) + + if word != prefix: + completionsList.extend( + ["{0}?{1}".format(c[0], c[1]) + for c in comps if c[0].startswith(word)]) + else: + completionsList.extend( + ["{0}?{1}".format(c[0], c[1]) + for c in comps]) + break + else: + # possibly completing a named class attribute or method + if prefix in module.classes: + prefixFound = True + cl = module.classes[prefix] + comps = [] + for method in cl.methods.values(): + if method.name == "__init__": + continue + if not hasattr(method, "modifier"): + # eric 5.1 cannot differentiate method types + continue + if method.modifier in [method.Class, method.Static]: + # determine icon type + if method.isPrivate(): + iconID = Editor.MethodPrivateID + elif method.isProtected(): + iconID = Editor.MethodProtectedID + else: + iconID = Editor.MethodID + comps.append((method.name, iconID)) + for attribute in cl.globals.values(): + # determine icon type + if attribute.isPrivate(): + iconID = Editor.AttributePrivateID + elif attribute.isProtected(): + iconID = Editor.AttributeProtectedID + else: + iconID = Editor.AttributePrivateID + comps.append((attribute.name, iconID)) + + if word != prefix: + completionsList.extend( + ["{0}?{1}".format(c[0], c[1]) + for c in comps if c[0].startswith(word)]) + else: + completionsList.extend( + ["{0}?{1}".format(c[0], c[1]) + for c in comps]) + + if not prefixFound: + currentPos = editor.currentPosition() + if context: + word += sep - res = editor.findNextTarget() + if editor.isUtf8(): + sword = word.encode("utf-8") + else: + sword = word + res = editor.findFirstTarget(sword, False, + editor.autoCompletionCaseSensitivity(), + False, begline=0, begindex=0, ws_=True) + while res: + start, length = editor.getFoundTarget() + pos = start + length + if pos != currentPos: + if context: + completion = "" + else: + completion = word + line, index = editor.lineIndexFromPosition(pos) + curWord = editor.getWord(line, index, useWordChars=False) + completion += curWord[len(completion):] + if completion and completion not in completionsList: + completionsList.append( + "{0}?{1}".format(completion, self.__fromDocumentID)) + + res = editor.findNextTarget() completionsList.sort() return completionsList @@ -434,27 +604,154 @@ line, col = editor.lineIndexFromPosition(pos) wc = re.sub("\w", "", editor.wordCharacters()) + pat = re.compile("\w{0}".format(re.escape(wc))) text = editor.text(line) while col > 0 and \ - (not text[col - 1].isalnum() or \ - (wc and text[col - 1] not in wc)): + not pat.match(text[col - 1]): col -= 1 word = editor.getWordLeft(line, col) + prefix = "" + mod = None + beg = editor.text(line)[:col] + col = len(beg) + wsep = editor.getLexer().autoCompletionWordSeparators() + if wsep: + if beg[col - 1] in wsep: + col -= 1 + else: + while col >= 0 and beg[col - 1] not in wsep + [" ", "\t"]: + col -= 1 + if col >= 0: + col -= 1 + prefix = editor.getWordLeft(line, col) + if editor.isPy2File() or editor.isPy3File(): + src = editor.text() + fn = editor.getFileName() + if fn is None: + fn = "" + mod = Module("", fn, imp.PY_SOURCE) + mod.scan(src) + apiCalltips = [] projectCalltips = [] + documentCalltips = [] if self.__plugin.getPreferences("AutoCompletionSource") & AcsAPIs: api = self.__apisManager.getAPIs(language) if api is not None: - apiCalltips = api.getCalltips(word, commas, self.__lastContext, - self.__lastFullContext, - self.__plugin.getPreferences("CallTipsContextShown")) + apiCalltips = self.__getApiCalltips( + api, word, commas, prefix, mod, editor) if self.__plugin.getPreferences("AutoCompletionSource") & AcsProject: api = self.__apisManager.getAPIs(ApisNameProject) - projectCalltips = api.getCalltips(word, commas, self.__lastContext, + projectCalltips = self.__getApiCalltips( + api, word, commas, prefix, mod, editor) + + if self.__plugin.getPreferences("AutoCompletionSource") & AcsDocument: + documentCalltips = self.__getCalltipsFromDocument( + word, prefix, mod, editor) + + return sorted( + set(apiCalltips) + .union(set(projectCalltips)) + .union(set(documentCalltips)) + ) + + def __getApiCalltips(self, api, word, commas, prefix, module, editor): + """ + Private method to determine calltips from APIs. + + @param api reference to the API object to be used (APIsManager.DbAPIs) + @param word function to get calltips for (string) + @param commas minimum number of commas contained in the calltip (integer) + @param prefix prefix of the word to be completed (string) + @param module reference to the scanned module info (Module) + @param editor reference to the editor object (QScintilla.Editor) + @return list of calltips (list of string) + """ + calltips = [] + if prefix and module and prefix == "self": + line, col = editor.getCursorPosition() + for cl in module.classes.values(): + if line >= cl.lineno and \ + (cl.endlineno == -1 or line <= cl.endlineno): + for super in cl.super: + calltips.extend(api.getCalltips(word, commas, super, None, + self.__plugin.getPreferences("CallTipsContextShown"))) + break + else: + calltips = api.getCalltips(word, commas, self.__lastContext, self.__lastFullContext, self.__plugin.getPreferences("CallTipsContextShown")) - return sorted(set(apiCalltips).union(set(projectCalltips))) + return calltips + + def __getCalltipsFromDocument(self, word, prefix, module, editor): + """ + Private method to determine calltips from the document. + + @param word function to get calltips for (string) + @param prefix prefix of the word to be completed (string) + @param module reference to the scanned module info (Module) + @param editor reference to the editor object (QScintilla.Editor) + @return list of calltips (list of string) + """ + calltips = [] + if module: + if prefix: + # prefix can be 'self', 'cls' or a class name + sep = editor.getLexer().autoCompletionWordSeparators()[0] + if prefix in ["self", "cls"]: + line, col = editor.getCursorPosition() + for cl in module.classes.values(): + if line >= cl.lineno and \ + (cl.endlineno == -1 or line <= cl.endlineno): + if word in cl.methods: + method = cl.methods[word] + if hasattr(method, "modifier"): + if prefix == "self" or \ + (prefix == "cls" and \ + method.modifier == method.Class): + calltips.append("{0}{1}{2}({3})".format( + cl.name, + sep, + word, + ', '.join(method.parameters[1:]))) + else: + # eric 5.1 cannot differentiate method types + calltips.append("{0}{1}{2}({3})".format( + cl.name, + sep, + word, + ', '.join(method.parameters[1:]))) + break + else: + if prefix in module.classes: + cl = module.classes[prefix] + if word in cl.methods: + method = cl.methods[word] + if hasattr(method, "modifier") and \ + method.modifier == method.Class: + # only eric 5.2 and newer can differentiate method types + calltips.append("{0}{1}{2}({3})".format( + cl.name, + sep, + word, + ', '.join(method.parameters[1:]))) + else: + # calltip for a module function or class + if word in module.functions: + fun = module.functions[word] + calltips.append("{0}({1})".format( + word, + ', '.join(fun.parameters[1:]))) + elif word in module.classes: + cl = module.classes[word] + if "__init__" in cl.methods: + method = cl.methods["__init__"] + calltips.append("{0}({1})".format( + word, + ', '.join(method.parameters[1:]))) + + return calltips