AssistantEric/Assistant.py

Wed, 02 Jan 2013 10:52:45 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 02 Jan 2013 10:52:45 +0100
changeset 65
71f50b4cc169
parent 54
89d28458ff40
child 66
f57b151c9761
permissions
-rw-r--r--

Updated copyright for 2013.

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

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

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

import re
import imp

from PyQt4.QtCore import QRegExp, QObject

from E5Gui.E5Application import e5App

from .APIsManager import APIsManager, ApisNameProject

from QScintilla.Editor import Editor

import Preferences
from Utilities.ModuleParser import Module

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)
        
        self.__editors = []
        self.__completingContext = False
        self.__lastContext = None
        self.__lastFullContext = None
        
        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
        language = editor.getLanguage()
        if language == "":
            return
        self.__apisManager.getAPIs(language)
    
    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)
            if editor.autoCompletionHook() == self.autocomplete:
                self.__unsetAutoCompletionHook(editor)
            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 __getCharacter(self, pos, editor):
        """
        Private method to get the character to the left of the current position
        in the current line.
        
        @param pos position to get character at (integer)
        @param editor reference to the editor object to work with (QScintilla.Editor)
        @return requested character or "", if there are no more (string) and
            the next position (i.e. pos - 1)
        """
        if pos <= 0:
            return "", pos
        
        pos -= 1
        ch = editor.charAt(pos)
        
        # Don't go past the end of the previous line
        if ch == '\n' or ch == '\r':
            return "", pos
        
        return ch, pos
    
    #################################
    ## autocompletion 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 __setAutoCompletionHook(self, editor):
        """
        Private method to set the autocompletion hook.
        
        @param editor reference to the editor (QScintilla.Editor)
        """
        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)
        """
        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()
        
        language = editor.getLanguage()
        if language == "":
            return
        
        line, col = editor.getCursorPosition()
        self.__completingContext = context
        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]
            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)
        wsep = editor.getLexer().autoCompletionWordSeparators()
        if wsep:
            wsep.append(" ")
            if col > 0 and beg[col - 1] in wsep:
                col -= 1
            else:
                while col > 0 and beg[col - 1] not in wsep:
                    col -= 1
                if col > 0 and beg[col - 1] != " ":
                    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)
        
        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
                    wsep = editor.getLexer().autoCompletionWordSeparators()
                    while col >= 0 and prefix[col] not in wsep:
                        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,
                                                    mod, editor, importCompletion, sep)
            if len(completionsList) == 0 and prefix:
                # searching with prefix didn't return anything, try without
                completionsList = self.__getCompletions(word, context, "", language,
                                                        mod, editor, importCompletion,
                                                        sep)
            if len(completionsList) > 0:
                completionsList.sort()
                editor.showUserList(EditorAutoCompletionListID, completionsList)

    def __getCompletions(self, word, context, prefix, language, module, editor,
                         importCompletion, 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 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 sep separator string (string)
        @return list of possible completions (list of strings)
        """
        apiCompletionsList = []
        docCompletionsList = []
        projectCompletionList = []
        
        if self.__plugin.getPreferences("AutoCompletionSource") & AcsAPIs:
            api = self.__apisManager.getAPIs(language)
            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:
            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, cl.name, iconID))
                            else:
                                # eric 5.1 cannot differentiate method types
                                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 not doHierarchy and not hasattr(method, "modifier"):
                            # eric 5.1 cannot differentiate method types
                            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:
                        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)
        """
        editor.setCallTipHook(self.calltips)
    
    def __unsetCalltipsHook(self, editor):
        """
        Private method to unset the calltip hook.
        
        @param editor reference to the editor (QScintilla.Editor)
        """
        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)
        """
        language = editor.getLanguage()
        if language == "":
            return
        
        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)
        wsep = editor.getLexer().autoCompletionWordSeparators()
        if wsep:
            if col > 0 and 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 = 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 (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:
            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:])))
                            
                            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 \
                               (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:])))
                        
                        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