AssistantEric/Assistant.py

Tue, 10 Dec 2024 15:48:50 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 10 Dec 2024 15:48:50 +0100
branch
eric7
changeset 216
42ded9008f96
parent 212
24d6bae09db6
permissions
-rw-r--r--

Updated copyright for 2025.

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

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

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

import re

from PyQt6.QtCore import QObject

from eric7.EricWidgets.EricApplication import ericApp
from eric7.Utilities.ModuleParser import PY_SOURCE, Module

from .APIsManager import APIsManager, ApisNameProject

try:
    from eric7.QScintilla.Editor import EditorIconId

    AttributeID = EditorIconId.Attribute
    AttributeProtectedID = EditorIconId.AttributeProtected
    AttributePrivateID = EditorIconId.AttributePrivate
    FromDocumentID = EditorIconId.FromDocument
    MethodID = EditorIconId.Method
    MethodProtectedID = EditorIconId.MethodProtected
    MethodPrivateID = EditorIconId.MethodPrivate
except ImportError:
    # backward compatibility for eric < 24.2
    from eric7.QScintilla.Editor import Editor

    AttributeID = Editor.AttributeID
    AttributeProtectedID = Editor.AttributeProtectedID
    AttributePrivateID = Editor.AttributePrivateID
    FromDocumentID = Editor.FromDocumentID
    MethodID = Editor.MethodID
    MethodProtectedID = Editor.MethodProtectedID
    MethodPrivateID = Editor.MethodPrivateID

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


class Assistant(QObject):
    """
    Class implementing the autocompletion and calltips system.
    """

    def __init__(self, plugin, parent=None):
        """
        Constructor

        @param plugin reference to the plugin object
        @type AssistantEricPlugin
        @param parent parent
        @type QObject
        """
        QObject.__init__(self, parent)

        self.__plugin = plugin
        self.__ui = parent
        self.__project = ericApp().getObject("Project")
        self.__viewmanager = ericApp().getObject("ViewManager")
        self.__pluginManager = ericApp().getObject("PluginManager")

        self.__apisManager = APIsManager(self.__ui, self)

        self.__editors = []
        self.__lastContext = None
        self.__lastFullContext = None

    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,  # noqa: U100
        enabled,  # noqa: U100
    ):
        """
        Public method to enable or disable a feature.

        @param key feature to set
        @type str
        @param enabled flag indicating the status
        @type bool
        """
        for editor in self.__editors[:]:
            self.__editorClosed(editor)
        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
        @type Editor
        """
        if self.__plugin.getPreferences("AutoCompletionEnabled"):
            self.__setAutoCompletionHook(editor)
        if self.__plugin.getPreferences("CalltipsEnabled"):
            self.__setCalltipsHook(editor)
        if (api := self.__apisManager.getAPIs(ApisNameProject)) is not None:
            editor.editorSaved.connect(api.editorSaved)
        self.__editors.append(editor)

        # preload the api to give the manager a chance to prepare the database
        language = editor.getApiLanguage()
        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
        @type Editor
        """
        if editor in self.__editors:
            if (api := self.__apisManager.getAPIs(ApisNameProject)) is not None:
                editor.editorSaved.disconnect(api.editorSaved)
            self.__editors.remove(editor)
            if editor.getCompletionListHook("Assistant"):
                self.__unsetAutoCompletionHook(editor)
            if editor.getCallTipHook("Assistant"):
                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()
        projectType = (
            self.__project.getProjectType()
            if (
                self.__project.isOpen()
                and filename
                and self.__project.isProjectFile(filename)
            )
            else ""
        )

        return projectType

    #################################
    ## auto-completion methods below
    #################################

    def __recordSelectedContext(self, userListId, txt):
        """
        Private slot to handle the selection from the completion list to
        record the selected completion context.

        @param userListId the ID of the user list (should be 1)
        @type int
        @param txt the selected text
        @type str
        """
        from eric7.QScintilla.Editor import EditorAutoCompletionListID

        if userListId == 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
        @type Editor
        """
        editor.userListActivated.connect(self.__recordSelectedContext)
        editor.addCompletionListHook("Assistant", self.getCompletionsList)

    def __unsetAutoCompletionHook(self, editor):
        """
        Private method to unset the autocompletion hook.

        @param editor reference to the editor
        @type Editor
        """
        editor.userListActivated.disconnect(self.__recordSelectedContext)
        editor.removeCompletionListHook("Assistant")

    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
        @type Editor
        @param context flag indicating to autocomplete a context
        @type bool
        @return list of possible completions
        @rtype list of str
        """
        language = editor.getApiLanguage()
        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()
        sep = ""
        if language and context:
            wc = re.sub(r"\w", "", editor.wordCharacters())
            pat = re.compile(r"\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
        beg = beg[: col + 1] if context else editor.text(line)[:col]
        col = len(beg)
        wseps = editor.getLexer().autoCompletionWordSeparators() if language else []
        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.isPyFile():
                src = editor.text()
                fn = editor.getFileName()
                if fn is None:
                    fn = ""
                mod = Module("", fn, PY_SOURCE)
                mod.scan(src)

        importCompletion = False
        if editor.isPyFile():
            # 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
        @type str
        @param context flag indicating to autocomplete a context
        @type bool
        @param prefix prefix of the word to be completed
        @type str
        @param language programming language of the source
        @type str
        @param projectType type of the project
        @type str
        @param module reference to the scanned module info
        @type Module
        @param editor reference to the editor object
        @type Editor
        @param importCompletion flag indicating an import completion
        @type bool
        @param documentOnly flag indicating to complete from the document only
        @type bool
        @param sep separator string
        @type str
        @return list of possible completions
        @rtype list of str
        """
        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
        @type APIsManager.DbAPIs
        @param word word (or wordpart) to complete
        @type str
        @param context flag indicating to autocomplete a context
        @type bool
        @param prefix prefix of the word to be completed
        @type str
        @param module reference to the scanned module info
        @type Module
        @param editor reference to the editor object
        @type Editor
        @return list of possible completions
        @rtype list of str
        """
        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 superClass in cl.super:
                            if prefix == word:
                                completions.extend(
                                    api.getCompletions(
                                        context=superClass,
                                        followHierarchy=self.__plugin.getPreferences(
                                            "AutoCompletionFollowHierarchy"
                                        ),
                                    )
                                )
                            else:
                                completions.extend(
                                    api.getCompletions(
                                        start=word,
                                        context=superClass,
                                        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
                                regexp = re.compile(re.escape(entry) + r"\?\d{,2}")
                                for comp in completionsList:
                                    if regexp.fullmatch(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
                        regexp = re.compile(re.escape(entry) + r"\?\d{,2}")
                        for comp in completionsList:
                            if regexp.fullmatch(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
        @type Editor
        @param word string to be completed
        @type str
        @param context flag indicating to autocomplete a context
        @type bool
        @param sep separator string
        @type str
        @param prefix prefix of the word to be completed
        @type str
        @param module reference to the scanned module info
        @type Module
        @param doHierarchy flag indicating a hierarchical search
        @type bool
        @return list of possible completions
        @rtype list of str
        """
        completionsList = []

        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 = MethodPrivateID
                            elif method.isProtected():
                                iconID = MethodProtectedID
                            else:
                                iconID = 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 = AttributePrivateID
                                elif attribute.isProtected():
                                    iconID = AttributeProtectedID
                                else:
                                    iconID = AttributeID
                                comps.append((attribute.name, cl.name, iconID))
                        for attribute in cl.globals.values():
                            # determine icon type
                            if attribute.isPrivate():
                                iconID = AttributePrivateID
                            elif attribute.isProtected():
                                iconID = AttributeProtectedID
                            else:
                                iconID = 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 = MethodPrivateID
                            elif method.isProtected():
                                iconID = MethodProtectedID
                            else:
                                iconID = MethodID
                            comps.append((method.name, cl.name, iconID))
                    for attribute in cl.globals.values():
                        # determine icon type
                        if attribute.isPrivate():
                            iconID = AttributePrivateID
                        elif attribute.isProtected():
                            iconID = AttributeProtectedID
                        else:
                            iconID = 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, 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
        @type Editor
        """
        editor.addCallTipHook("Assistant", self.calltips)

    def __unsetCalltipsHook(self, editor):
        """
        Private method to unset the calltip hook.

        @param editor reference to the editor
        @type Editor
        """
        editor.removeCallTipHook("Assistant")

    def calltips(self, editor, pos, commas):
        """
        Public method to return a list of calltips.

        @param editor reference to the editor
        @type Editor
        @param pos position in the text for the calltip
        @type int
        @param commas minimum number of commas contained in the calltip
        @type int
        @return list of possible calltips
        @rtype list of str
        """
        language = editor.getApiLanguage()
        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(r"\w", "", editor.wordCharacters())
        pat = re.compile(r"\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)
        wseps = editor.getLexer().autoCompletionWordSeparators() if language else []
        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.isPyFile():
                src = editor.text()
                fn = editor.getFileName()
                if fn is None:
                    fn = ""
                mod = Module("", fn, 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 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
        @type APIsManager.DbAPIs
        @param word function to get calltips for
        @type str
        @param commas minimum number of commas contained in the calltip
        @type int
        @param prefix prefix of the word to be completed
        @type str
        @param module reference to the scanned module info
        @type Module
        @param editor reference to the editor object
        @type Editor
        @return list of calltips
        @rtype list of str
        """
        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 superClass in cl.super:
                        calltips.extend(
                            api.getCalltips(
                                word,
                                commas,
                                superClass,
                                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
        @type str
        @param prefix prefix of the word to be completed
        @type str
        @param module reference to the scanned module info
        @type Module
        @param editor reference to the editor object
        @type Editor
        @param doHierarchy flag indicating a hierarchical search
        @type bool
        @return list of calltips
        @rtype list of str
        """
        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


#
# eflag: noqa = M834, W605

eric ide

mercurial