AssistantEric/Assistant.py

Sat, 31 Dec 2016 13:50:18 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 31 Dec 2016 13:50:18 +0100
changeset 135
08cb4f36ad47
parent 132
eb12cd27384f
child 136
5cfe53b474a9
permissions
-rw-r--r--

Updated copyright for 2017.

# -*- coding: utf-8 -*-

# Copyright (c) 2008 - 2017 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the eric assistant, an alternative autocompletion and
calltips system.
"""

from __future__ import unicode_literals

import re
import imp

from PyQt5.QtCore import QRegExp, QObject

from E5Gui.E5Application import e5App

from .APIsManager import APIsManager, ApisNameProject

import Preferences

AcsAPIs = 0x0001
AcsDocument = 0x0002
AcsProject = 0x0004
AcsOther = 0x1000


class Assistant(QObject):
    """
    Class implementing the autocompletion and calltips system.
    """
    def __init__(self, plugin, parent=None):
        """
        Constructor
        
        @param plugin reference to the plugin object
        @param parent parent (QObject)
        """
        QObject.__init__(self, parent)
        
        self.__plugin = plugin
        self.__ui = parent
        self.__project = e5App().getObject("Project")
        self.__viewmanager = e5App().getObject("ViewManager")
        self.__pluginManager = e5App().getObject("PluginManager")
        
        self.__apisManager = APIsManager(self.__ui, self)
        
        self.__editors = []
        self.__completingContext = False
        self.__lastContext = None
        self.__lastFullContext = None
        
        from QScintilla.Editor import Editor
        self.__fromDocumentID = Editor.FromDocumentID
    
    def activate(self):
        """
        Public method to perform actions upon activation.
        """
        self.__pluginManager.shutdown.connect(self.__shutdown)
        
        self.__ui.preferencesChanged.connect(self.__preferencesChanged)
        
        self.__viewmanager.editorOpenedEd.connect(self.__editorOpened)
        self.__viewmanager.editorClosedEd.connect(self.__editorClosed)
        
        # preload the project APIs object
        self.__apisManager.getAPIs(ApisNameProject)
        
        for editor in self.__viewmanager.getOpenEditors():
            self.__editorOpened(editor)
    
    def deactivate(self):
        """
        Public method to perform actions upon deactivation.
        """
        self.__pluginManager.shutdown.disconnect(self.__shutdown)
        
        self.__ui.preferencesChanged.disconnect(self.__preferencesChanged)
        
        self.__viewmanager.editorOpenedEd.disconnect(self.__editorOpened)
        self.__viewmanager.editorClosedEd.disconnect(self.__editorClosed)
        
        self.__shutdown()
    
    def __shutdown(self):
        """
        Private slot to handle the shutdown signal.
        """
        for editor in self.__editors[:]:
            self.__editorClosed(editor)
        
        self.__apisManager.deactivate()
    
    def setEnabled(self, key, enabled):
        """
        Public method to enable or disable a feature.
        
        @param key feature to set (string)
        @param enabled flag indicating the status (boolean)
        """
        for editor in self.__editors[:]:
            self.__editorClosed(editor)
        if enabled:
            for editor in self.__viewmanager.getOpenEditors():
                self.__editorOpened(editor)
    
    def __editorOpened(self, editor):
        """
        Private slot called, when a new editor was opened.
        
        @param editor reference to the new editor (QScintilla.Editor)
        """
        if self.__plugin.getPreferences("AutoCompletionEnabled"):
            self.__setAutoCompletionHook(editor)
        if self.__plugin.getPreferences("CalltipsEnabled"):
            self.__setCalltipsHook(editor)
        editor.editorSaved.connect(
            self.__apisManager.getAPIs(ApisNameProject).editorSaved)
        self.__editors.append(editor)
        
        # preload the api to give the manager a chance to prepare the database
        try:
            language = editor.getApiLanguage()
        except AttributeError:
            # backward compatibility
            language = editor.apiLanguage
        if language:
            projectType = self.__getProjectType(editor)
            self.__apisManager.getAPIs(language, projectType=projectType)
    
    def __editorClosed(self, editor):
        """
        Private slot called, when an editor was closed.
        
        @param editor reference to the editor (QScintilla.Editor)
        """
        if editor in self.__editors:
            editor.editorSaved.disconnect(
                self.__apisManager.getAPIs(ApisNameProject).editorSaved)
            self.__editors.remove(editor)
            try:
                if editor.getCompletionListHook("Assistant"):
                    self.__unsetAutoCompletionHook(editor)
            except AttributeError:
                # old interface (before 6.1.0)
                if editor.autoCompletionHook() == self.autocomplete:
                    self.__unsetAutoCompletionHook(editor)
            try:
                if editor.getCallTipHook("Assistant"):
                    self.__unsetCalltipsHook(editor)
            except AttributeError:
                # old interface (before 6.1.0)
                if editor.callTipHook() == self.calltips:
                    self.__unsetCalltipsHook(editor)
    
    def __preferencesChanged(self):
        """
        Private method to handle a change of the global configuration.
        """
        self.__apisManager.reloadAPIs()
    
    def __getProjectType(self, editor):
        """
        Private method to determine the project type to be used.
        
        @param editor reference to the editor to check
        @type Editor
        @return project type
        @rtype str
        """
        filename = editor.getFileName()
        if self.__project.isOpen() and filename and \
                self.__project.isProjectFile(filename):
            projectType = self.__project.getProjectType()
        else:
            projectType = ""
        
        return projectType
    
    #################################
    ## auto-completion methods below
    #################################
    
    def __completionListSelected(self, id, txt):
        """
        Private slot to handle the selection from the completion list.
        
        @param id the ID of the user list (should be 1) (integer)
        @param txt the selected text (string)
        """
        from QScintilla.Editor import EditorAutoCompletionListID
        
        editor = self.sender()
        if id == EditorAutoCompletionListID:
            lst = txt.split()
            if len(lst) > 1:
                txt = lst[0]
                self.__lastFullContext = lst[1][1:].split(")")[0]
            else:
                self.__lastFullContext = None
            
            if Preferences.getEditor("AutoCompletionReplaceWord"):
                editor.selectCurrentWord()
                editor.removeSelectedText()
                line, col = editor.getCursorPosition()
            else:
                line, col = editor.getCursorPosition()
                wLeft = editor.getWordLeft(line, col)
                if not txt.startswith(wLeft):
                    editor.selectCurrentWord()
                    editor.removeSelectedText()
                    line, col = editor.getCursorPosition()
                elif wLeft:
                    txt = txt[len(wLeft):]
            editor.insert(txt)
            editor.setCursorPosition(line, col + len(txt))
    
    def __recordSelectedContext(self, id, txt):
        """
        Private slot to handle the selection from the completion list to
        record the selected completion context.
        
        @param id the ID of the user list (should be 1) (integer)
        @param txt the selected text (string)
        """
        from QScintilla.Editor import EditorAutoCompletionListID
        
        if id == EditorAutoCompletionListID:
            lst = txt.split()
            if len(lst) > 1:
                self.__lastFullContext = lst[1][1:].split(")")[0]
            else:
                self.__lastFullContext = None
    
    def __setAutoCompletionHook(self, editor):
        """
        Private method to set the autocompletion hook.
        
        @param editor reference to the editor (QScintilla.Editor)
        """
        try:
            editor.userListActivated.connect(self.__recordSelectedContext)
            editor.addCompletionListHook("Assistant", self.getCompletionsList)
        except AttributeError:
            # old interface (before 6.1.0)
            editor.userListActivated.connect(self.__completionListSelected)
            editor.setAutoCompletionHook(self.autocomplete)
    
    def __unsetAutoCompletionHook(self, editor):
        """
        Private method to unset the autocompletion hook.
        
        @param editor reference to the editor (QScintilla.Editor)
        """
        try:
            editor.userListActivated.disconnect(self.__recordSelectedContext)
            editor.removeCompletionListHook("Assistant")
        except AttributeError:
            # old interface (before 6.1.0)
            editor.unsetAutoCompletionHook()
            editor.userListActivated.disconnect(self.__completionListSelected)
    
    def autocomplete(self, editor, context):
        """
        Public method to determine the autocompletion proposals.
        
        @param editor reference to the editor object, that called this method
            (QScintilla.Editor)
        @param context flag indicating to autocomplete a context (boolean)
        """
        from QScintilla.Editor import EditorAutoCompletionListID
        
        if editor.isListActive():
            editor.cancelList()
            
        completionsList = self.getCompletionsList(editor, context)
        if len(completionsList) > 0:
            completionsList.sort()
            editor.showUserList(EditorAutoCompletionListID,
                                completionsList)
    
    def getCompletionsList(self, editor, context):
        """
        Public method to get a list of possible completions.
        
        @param editor reference to the editor object, that called this method
            (QScintilla.Editor)
        @param context flag indicating to autocomplete a context (boolean)
        @return list of possible completions (list of strings)
        """
        try:
            language = editor.getApiLanguage()
        except AttributeError:
            # backward compatibility
            language = editor.apiLanguage
        
        completeFromDocumentOnly = False
        if language in ["", "Guessed"] or language.startswith("Pygments|"):
            if self.__plugin.getPreferences("AutoCompletionSource") & \
                    AcsDocument:
                completeFromDocumentOnly = True
            else:
                return []
        
        projectType = self.__getProjectType(editor)
        
        line, col = editor.getCursorPosition()
        self.__completingContext = context
        sep = ""
        if language and context:
            wc = re.sub("\w", "", editor.wordCharacters())
            pat = re.compile("\w{0}".format(re.escape(wc)))
            text = editor.text(line)
            
            beg = text[:col]
            for wsep in editor.getLexer().autoCompletionWordSeparators():
                if beg.endswith(wsep):
                    sep = wsep
                    break
            
            depth = 0
            while col > 0 and \
                    not pat.match(text[col - 1]):
                ch = text[col - 1]
                if ch == ')':
                    depth = 1
                    
                    # ignore everything back to the start of the
                    # corresponding parenthesis
                    col -= 1
                    while col > 0:
                        ch = text[col - 1]
                        if ch == ')':
                            depth += 1
                        elif ch == '(':
                            depth -= 1
                            if depth == 0:
                                break
                        col -= 1
                elif ch == '(':
                    break
                col -= 1
        
        word = editor.getWordLeft(line, col)
        if context and not sep:
            # no separator was found -> no context completion
            context = False
        if context:
            self.__lastContext = word
        else:
            self.__lastContext = None
        
        prefix = ""
        mod = None
        if context:
            beg = beg[:col + 1]
        else:
            beg = editor.text(line)[:col]
        col = len(beg)
        if language:
            wseps = editor.getLexer().autoCompletionWordSeparators()
        else:
            wseps = []
        if wseps:
            wseps.append(" ")
            if col > 0 and beg[col - 1] in wseps:
                col -= 1
            else:
                while col > 0 and beg[col - 1] not in wseps:
                    col -= 1
                if col > 0 and beg[col - 1] != " ":
                    col -= 1
            prefix = editor.getWordLeft(line, col)
            if editor.isPy2File() or editor.isPy3File():
                from Utilities.ModuleParser import Module
                src = editor.text()
                fn = editor.getFileName()
                if fn is None:
                    fn = ""
                mod = Module("", fn, imp.PY_SOURCE)
                mod.scan(src)
        
        importCompletion = False
        if editor.isPy2File() or editor.isPy3File():
            # check, if we are completing a from import statement
            maxLines = 10
            text = editor.text(line).strip()
            while maxLines and line > 0 and not text.startswith("from"):
                line -= 1
                textm1 = editor.text(line).strip()
                if not textm1.endswith("\\"):
                    break
                text = textm1[:-1] + text
                maxLines -= 1
            if text.startswith("from"):
                tokens = text.split()
                if len(tokens) >= 3 and tokens[2] == "import":
                    importCompletion = True
                    prefix = tokens[1]
                    col = len(prefix) - 1
                    wseps = editor.getLexer().autoCompletionWordSeparators()
                    while col >= 0 and prefix[col] not in wseps:
                        col -= 1
                    if col >= 0:
                        prefix = prefix[col + 1:]
                    if word == tokens[2]:
                        word = ""
        
        if word or importCompletion:
            completionsList = self.__getCompletions(
                word, context, prefix, language, projectType, mod, editor,
                importCompletion, completeFromDocumentOnly, sep)
            if len(completionsList) == 0 and prefix:
                # searching with prefix didn't return anything, try without
                completionsList = self.__getCompletions(
                    word, context, "", language, projectType, mod, editor,
                    importCompletion, completeFromDocumentOnly, sep)
            return completionsList
        
        return []
    
    def __getCompletions(self, word, context, prefix, language, projectType,
                         module, editor, importCompletion, documentOnly, sep):
        """
        Private method to get the list of possible completions.
        
        @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 language programming language of the source (string)
        @param projectType type of the project (string)
        @param module reference to the scanned module info (Module)
        @param editor reference to the editor object (QScintilla.Editor.Editor)
        @param importCompletion flag indicating an import completion (boolean)
        @param documentOnly flag indicating to complete from the document only
            (boolean)
        @param sep separator string (string)
        @return list of possible completions (list of strings)
        """
        apiCompletionsList = []
        docCompletionsList = []
        projectCompletionList = []
        
        if not documentOnly:
            if self.__plugin.getPreferences("AutoCompletionSource") & AcsAPIs:
                api = self.__apisManager.getAPIs(
                    language, projectType=projectType)
                apiCompletionsList = self.__getApiCompletions(
                    api, word, context, prefix, module, editor)
            
            if self.__plugin.getPreferences("AutoCompletionSource") & \
                    AcsProject:
                api = self.__apisManager.getAPIs(ApisNameProject)
                projectCompletionList = self.__getApiCompletions(
                    api, word, context, prefix, module, editor)
        
        if self.__plugin.getPreferences("AutoCompletionSource") & AcsDocument \
                and not importCompletion:
            docCompletionsList = self.__getDocumentCompletions(
                editor, word, context, sep, prefix, module)
        
        completionsList = list(
            set(apiCompletionsList)
            .union(set(docCompletionsList))
            .union(set(projectCompletionList))
        )
        return completionsList
    
    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.Editor)
        @return list of possible completions (list of strings)
        """
        completionsList = []
        if api is not None:
            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,
                                        followHierarchy=self.__plugin
                                        .getPreferences(
                                            "AutoCompletionFollowHierarchy"
                                        )
                                    )
                                )
                            else:
                                completions.extend(
                                    api.getCompletions(
                                        start=word, context=super,
                                        followHierarchy=self.__plugin
                                        .getPreferences(
                                            "AutoCompletionFollowHierarchy"
                                        )
                                    )
                                )
                        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"]
                    if completion["pictureId"]:
                        entry += "?{0}".format(completion["pictureId"])
                    if entry not in completionsList:
                        completionsList.append(entry)
            else:
                if prefix:
                    completions = api.getCompletions(
                        start=word, context=prefix)
                if not prefix or not completions:
                    # if no completions were returned try without prefix
                    completions = api.getCompletions(start=word)
                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)
        return completionsList
    
    def __getDocumentCompletions(self, editor, word, context, sep, prefix,
                                 module, doHierarchy=False):
        """
        Private method to determine autocompletion proposals from the document.
        
        @param editor reference to the editor object (QScintilla.Editor.Editor)
        @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)
        @keyparam doHierarchy flag indicating a hierarchical search (boolean)
        @return list of possible completions (list of strings)
        """
        completionsList = []
        
        prefixFound = False
        if prefix and module:
            from QScintilla.Editor import Editor
            
            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 (prefix == "cls" and
                                method.modifier == method.Class) or \
                               prefix == "self":
                                comps.append((method.name, cl.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.AttributeID
                                comps.append((attribute.name, cl.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.AttributeID
                            comps.append((attribute.name, cl.name, iconID))
                        
                        if word != prefix:
                            completionsList.extend(
                                ["{0} ({1})?{2}".format(c[0], c[1], c[2])
                                 for c in comps if c[0].startswith(word)])
                        else:
                            completionsList.extend(
                                ["{0} ({1})?{2}".format(c[0], c[1], c[2])
                                 for c in comps])
                        
                        for sup in cl.super:
                            if sup in module.classes:
                                if word == prefix:
                                    nword = sup
                                else:
                                    nword = word
                                completionsList.extend(
                                    self.__getDocumentCompletions(
                                        editor, nword, context, sep, sup,
                                        module, doHierarchy=True))
                        
                        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 doHierarchy or \
                           method.modifier in [method.Class, method.Static]:
                            # determine icon type
                            if method.isPrivate():
                                if doHierarchy:
                                    continue
                                iconID = Editor.MethodPrivateID
                            elif method.isProtected():
                                iconID = Editor.MethodProtectedID
                            else:
                                iconID = Editor.MethodID
                            comps.append((method.name, cl.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.AttributeID
                        comps.append((attribute.name, cl.name, iconID))
                    
                    if word != prefix:
                        completionsList.extend(
                            ["{0} ({1})?{2}".format(c[0], c[1], c[2])
                             for c in comps if c[0].startswith(word)])
                    else:
                        completionsList.extend(
                            ["{0} ({1})?{2}".format(c[0], c[1], c[2])
                             for c in comps])
                    
                    for sup in cl.super:
                        if sup in module.classes:
                            if word == prefix:
                                nword = sup
                            else:
                                nword = word
                            completionsList.extend(
                                self.__getDocumentCompletions(
                                    editor, nword, context, sep, sup, module,
                                    doHierarchy=True))
        
        if not prefixFound:
            currentPos = editor.currentPosition()
            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 and \
                            completion != word:
                        completionsList.append(
                            "{0}?{1}".format(
                                completion, self.__fromDocumentID))
                
                res = editor.findNextTarget()
        
        completionsList.sort()
        return completionsList
    
    ###########################
    ## calltips methods below
    ###########################
    
    def __setCalltipsHook(self, editor):
        """
        Private method to set the calltip hook.
        
        @param editor reference to the editor (QScintilla.Editor)
        """
        try:
            editor.addCallTipHook("Assistant", self.calltips)
        except AttributeError:
            # old interface (before 6.1.0)
            editor.setCallTipHook(self.calltips)
    
    def __unsetCalltipsHook(self, editor):
        """
        Private method to unset the calltip hook.
        
        @param editor reference to the editor (QScintilla.Editor)
        """
        try:
            editor.removeCallTipHook("Assistant")
        except AttributeError:
            # old interface (before 6.1.0)
            editor.unsetCallTipHook()
    
    def calltips(self, editor, pos, commas):
        """
        Public method to return a list of calltips.
        
        @param editor reference to the editor (QScintilla.Editor)
        @param pos position in the text for the calltip (integer)
        @param commas minimum number of commas contained in the calltip
            (integer)
        @return list of possible calltips (list of strings)
        """
        try:
            language = editor.getApiLanguage()
        except AttributeError:
            # backward compatibility
            language = editor.apiLanguage
        
        completeFromDocumentOnly = False
        if language in ["", "Guessed"] or language.startswith("Pygments|"):
            if self.__plugin.getPreferences("AutoCompletionSource") & \
                    AcsDocument:
                completeFromDocumentOnly = True
            else:
                return []
        
        projectType = self.__getProjectType(editor)
        
        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 pat.match(text[col - 1]):
            col -= 1
        word = editor.getWordLeft(line, col)
        
        prefix = ""
        mod = None
        beg = editor.text(line)[:col]
        col = len(beg)
        if language:
            wseps = editor.getLexer().autoCompletionWordSeparators()
        else:
            wseps = []
        if wseps:
            if col > 0 and beg[col - 1] in wseps:
                col -= 1
            else:
                while col > 0 and beg[col - 1] not in wseps + [" ", "\t"]:
                    col -= 1
                if col >= 0:
                    col -= 1
            prefix = editor.getWordLeft(line, col)
            if editor.isPy2File() or editor.isPy3File():
                from Utilities.ModuleParser import Module
                src = editor.text()
                fn = editor.getFileName()
                if fn is None:
                    fn = ""
                mod = Module("", fn, imp.PY_SOURCE)
                mod.scan(src)
        
        apiCalltips = []
        projectCalltips = []
        documentCalltips = []
        
        if not completeFromDocumentOnly:
            if self.__plugin.getPreferences("AutoCompletionSource") & AcsAPIs:
                api = self.__apisManager.getAPIs(
                    language, projectType=projectType)
                if api is not None:
                    apiCalltips = self.__getApiCalltips(
                        api, word, commas, prefix, mod, editor)
            
            if self.__plugin.getPreferences("AutoCompletionSource") & \
                    AcsProject:
                api = self.__apisManager.getAPIs(ApisNameProject)
                projectCalltips = self.__getApiCalltips(
                    api, word, commas, prefix, mod, editor)
        
        if self.__plugin.getPreferences("AutoCompletionSource") & AcsDocument:
            documentCalltips = self.__getDocumentCalltips(
                word, prefix, mod, editor)
        
        return list(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"),
                            followHierarchy=self.__plugin.getPreferences(
                                "CallTipsFollowHierarchy")))
                    break
        else:
            calltips = api.getCalltips(
                word, commas, self.__lastContext, self.__lastFullContext,
                self.__plugin.getPreferences("CallTipsContextShown"))
        
        return calltips
    
    def __getDocumentCalltips(self, word, prefix, module, editor,
                              doHierarchy=False):
        """
        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)
        @keyparam doHierarchy flag indicating a hierarchical search (boolean)
        @return list of calltips (list of string)
        """
        calltips = []
        if module and bool(editor.getLexer()):
            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 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:]
                                                      )))
                            
                            for sup in cl.super:
                                calltips.extend(self.__getDocumentCalltips(
                                    word, sup, module, editor,
                                    doHierarchy=True))
                            
                            break
                else:
                    if prefix in module.classes:
                        cl = module.classes[prefix]
                        if word in cl.methods:
                            method = cl.methods[word]
                            if doHierarchy or method.modifier == method.Class:
                                calltips.append("{0}{1}{2}({3})".format(
                                    cl.name,
                                    sep,
                                    word,
                                    ', '.join(method.parameters[1:])))
                        
                        for sup in cl.super:
                            calltips.extend(self.__getDocumentCalltips(
                                word, sup, module, editor, doHierarchy=True))
            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)))
                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

eric ide

mercurial