AssistantEric/Assistant.py

Sun, 14 Feb 2010 14:09:11 +0000

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 14 Feb 2010 14:09:11 +0000
changeset 5
e1a56c9a9c9d
parent 2
89cbc07f4bf0
child 18
b8ece0b784e8
permissions
-rw-r--r--

Corrected py3flakes warnings.

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

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

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

from PyQt4.QtCore import *

from E5Gui.E5Application import e5App

from .APIsManager import APIsManager, ApisNameProject

from QScintilla.Editor import Editor

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)
        
        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.connect(self.__pluginManager, SIGNAL("shutdown()"), 
                     self.__shutdown)
        
        self.connect(self.__ui, SIGNAL('preferencesChanged'), 
                     self.__preferencesChanged)
        
        self.connect(self.__viewmanager, SIGNAL("editorOpenedEd"), 
                     self.__editorOpened)
        self.connect(self.__viewmanager, SIGNAL("editorClosedEd"), 
                     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.disconnect(self.__pluginManager, SIGNAL("shutdown()"), 
                        self.__shutdown)
        
        self.disconnect(self.__ui, SIGNAL('preferencesChanged'), 
                        self.__preferencesChanged)
        
        self.disconnect(self.__viewmanager, SIGNAL("editorOpenedEd"), 
                        self.__editorOpened)
        self.disconnect(self.__viewmanager, SIGNAL("editorClosedEd"), 
                        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)
        self.connect(editor, SIGNAL("editorSaved"), 
                     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:
            self.disconnect(editor, SIGNAL("editorSaved"), 
                            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)
        """
        self.connect(editor, SIGNAL('userListActivated(int, const QString)'),
                     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()
        self.disconnect(editor, SIGNAL('userListActivated(int, const QString)'),
                        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
        apiCompletionsList = []
        docCompletionsList = []
        projectCompletionList = []
        sep = ""
        if context:
            wc = editor.wordCharacters()
            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 text[col - 1] not in wc:
                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:
            self.__lastContext = word
        else:
            self.__lastContext = None
        
        if word:
            if self.__plugin.getPreferences("AutoCompletionSource") & AcsAPIs:
                api = self.__apisManager.getAPIs(language)
                apiCompletionsList = self.__getApiCompletions(api, word, context)
            
            if self.__plugin.getPreferences("AutoCompletionSource") & AcsProject:
                api = self.__apisManager.getAPIs(ApisNameProject)
                projectCompletionList = self.__getApiCompletions(api, word, context)
            
            if self.__plugin.getPreferences("AutoCompletionSource") & AcsDocument:
                docCompletionsList = \
                    self.getCompletionsFromDocument(editor, word, context, sep)
            
            completionsList = list(
                set(apiCompletionsList)
                .union(set(docCompletionsList))
                .union(set(projectCompletionList))
                )
            
            if len(completionsList) > 0:
                completionsList.sort()
                editor.showUserList(EditorAutoCompletionListID, completionsList)

    def __getApiCompletions(self, api, word, context):
        """
        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)
        @return list of possible completions (list of strings)
        """
        completionsList = []
        if api is not None:
            if 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:
                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 getCompletionsFromDocument(self, editor, word, context, sep):
        """
        Public method to determine autocompletion proposals from the document.
        
        @param editor reference to the editor object (QScintilla.Editor)
        @param word string to be completed (string)
        @param context flag indicating to autocomplete a context (boolean)
        @param sep separator string (string)
        @return list of possible completions (list of strings)
        """
        currentPos = editor.currentPosition()
        completionsList = []
        if context:
            word += sep
        
        res = editor.findFirstTarget(word, 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
                ch = editor.charAt(pos)
                while editor.isWordCharacter(ch):
                    completion += ch
                    pos += 1
                    ch = editor.charAt(pos)
                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 = editor.wordCharacters()
        text = editor.text(line)
        while col > 0 and text[col - 1] not in wc:
            col -= 1
        word = editor.getWordLeft(line, col)
        
        apiCalltips = []
        projectCalltips = []
        
        if self.__plugin.getPreferences("AutoCompletionSource") & AcsAPIs:
            api = self.__apisManager.getAPIs(language)
            if api is not None:
                apiCalltips = api.getCalltips(word, commas, self.__lastContext, 
                    self.__lastFullContext, 
                    self.__plugin.getPreferences("CallTipsContextShown"))
        
        if self.__plugin.getPreferences("AutoCompletionSource") & AcsProject:
            api = self.__apisManager.getAPIs(ApisNameProject)
            projectCalltips = api.getCalltips(word, commas, self.__lastContext, 
                self.__lastFullContext, 
                self.__plugin.getPreferences("CallTipsContextShown"))
        
        return sorted(set(apiCalltips).union(set(projectCalltips)))

eric ide

mercurial