PluginRefactoringRope.py

Sun, 22 Mar 2015 17:33:44 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 22 Mar 2015 17:33:44 +0100
changeset 118
d242ba11a04c
parent 116
50cb62506ab2
child 119
a03f2be1997b
permissions
-rw-r--r--

Fixed a few code style issues.

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

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

"""
Module implementing the Rope refactoring plugin.
"""

from __future__ import unicode_literals

import os
import sys

from PyQt5.QtCore import QObject, QTranslator, QCoreApplication, QTimer

from E5Gui.E5Application import e5App

import Preferences
import Utilities

from Preferences.Shortcuts import readShortcuts

# Start-Of-Header
name = "Refactoring Rope Plugin"
author = "Detlev Offenbach <detlev@die-offenbachs.de>"
autoactivate = True
deactivateable = True
version = "4.0.5"
className = "RefactoringRopePlugin"
packageName = "RefactoringRope"
internalPackages = "rope"
shortDescription = "Refactoring using the Rope library."
longDescription = """This plug-in implements refactoring functionality""" \
    """ using the Rope refactoring library. Additonally it implements an """ \
    """ alternative auto-completion and calltips provider. Only""" \
    """ refactoring, completions and calltips in the same Python variant""" \
    """ as Eric is running is allowed."""
pyqtApi = 2
doNotCompile = True
python2Compatible = True
# End-Of-Header

error = ""

refactoringRopePluginObject = None


def createAutoCompletionPage(configDlg):
    """
    Module function to create the autocompletion configuration page.
    
    @param configDlg reference to the configuration dialog
    @return reference to the configuration page
    """
    global refactoringRopePluginObject
    from RefactoringRope.ConfigurationPage.AutoCompletionRopePage \
        import AutoCompletionRopePage
    page = AutoCompletionRopePage(refactoringRopePluginObject)
    return page


def createCallTipsPage(configDlg):
    """
    Module function to create the calltips configuration page.
    
    @param configDlg reference to the configuration dialog
    @return reference to the configuration page
    """
    global refactoringRopePluginObject
    from RefactoringRope.ConfigurationPage.CallTipsRopePage \
        import CallTipsRopePage
    page = CallTipsRopePage(refactoringRopePluginObject)
    return page


def getConfigData():
    """
    Module function returning data as required by the configuration dialog.
    
    @return dictionary containing the relevant data
    """
    return {
        "ropeAutoCompletionPage": [
            QCoreApplication.translate("RefactoringRopePlugin", "Rope"),
            os.path.join("RefactoringRope", "ConfigurationPage",
                         "preferences-refactoring.png"),
            createAutoCompletionPage, "editorAutocompletionPage", None],
        "ropeCallTipsPage": [
            QCoreApplication.translate("RefactoringRopePlugin", "Rope"),
            os.path.join("RefactoringRope", "ConfigurationPage",
                         "preferences-refactoring.png"),
            createCallTipsPage, "editorCalltipsPage", None],
    }


def prepareUninstall():
    """
    Module function to prepare for an uninstallation.
    """
    Preferences.Prefs.settings.remove(RefactoringRopePlugin.PreferencesKey)


class RefactoringRopePlugin(QObject):
    """
    Class implementing the Rope refactoring plugin.
    """
    PreferencesKey = "RefactoringRope"
    
    def __init__(self, ui):
        """
        Constructor
        
        @param ui reference to the user interface object (UI.UserInterface)
        """
        QObject.__init__(self, ui)
        self.__ui = ui
        self.__initialize()
        
        self.__defaults = {
            "CodeAssistEnabled": False,
            "MaxFixes": 10,
            "CodeAssistTimeout": 100,
            "ShowQScintillaCompletions": True,
            
            "CodeAssistCalltipsEnabled": False,
            "CalltipsMaxFixes": 10,
        }
        
        self.__translator = None
        self.__loadTranslator()
        
        self.__acTimer = QTimer(self)
        self.__acTimer.setSingleShot(True)
        self.__acTimer.setInterval(self.getPreferences("CodeAssistTimeout"))
        self.__acTimer.timeout.connect(self.__codeAssist)
    
    def __initialize(self):
        """
        Private slot to (re)initialize the plugin.
        """
        self.__object = None
        self.__caObject = None
        
        self.__mainAct = None
        self.__mainMenu = None
        self.__projectIsOpen = False
        
        self.__editors = []
        
        self.__currentEditor = None
        self.__savedEditorName = None
        self.__oldEditorText = ""
    
    def __checkUiVersion(self):
        """
        Private method to check, if the IDE has a suitable version.
        
        @return flag indicating a suitable version (boolean)
        """
        global error
        
        suitable = self.__ui.versionIsNewer("5.99.99", "20140701")
        if not suitable:
            error = self.tr("Your version of eric6 is not supported.")
        return suitable
    
    def activate(self):
        """
        Public method to activate this plugin.
        
        @return tuple of None and activation status (boolean)
        """
        global error
        error = ""     # clear previous error
        
        if not self.__checkUiVersion():
            return None, False
        
        global refactoringRopePluginObject
        refactoringRopePluginObject = self
        
        from RefactoringRope.Refactoring import Refactoring
        from RefactoringRope.CodeAssist import CodeAssist
        
        self.__caObject = CodeAssist(self, self)
        
        self.__object = Refactoring(self, self.__ui)
        self.__object.initActions()
        e5App().registerPluginObject("RefactoringRope", self.__object)
        try:
            readShortcuts(pluginName="RefactoringRope")
        except TypeError:
            # backwards comaytibility, ignore
            pass
        
        self.__mainMenu = self.__object.initMenu()
        extrasAct = self.__ui.getMenuBarAction("extras")
        self.__mainAct = self.__ui.menuBar().insertMenu(
            extrasAct, self.__mainMenu)
        
        if e5App().getObject("Project").isOpen():
            self.__projectOpened()
        
        if self.__projectIsOpen:
            self.__object.projectOpened()
        
        e5App().getObject("ViewManager").editorOpenedEd.connect(
            self.__editorOpened)
        e5App().getObject("ViewManager").editorClosedEd.connect(
            self.__editorClosed)
        
        e5App().getObject("Project").projectOpened.connect(
            self.__object.projectOpened)
        e5App().getObject("Project").projectPropertiesChanged.connect(
            self.__object.projectOpened)
        e5App().getObject("Project").projectClosed.connect(
            self.__object.projectClosed)
        e5App().getObject("Project").newProject.connect(
            self.__object.projectOpened)
        
        e5App().getObject("Project").projectOpened.connect(
            self.__projectOpened)
        e5App().getObject("Project").projectPropertiesChanged.connect(
            self.__projectOpened)
        e5App().getObject("Project").projectClosed.connect(
            self.__projectClosed)
        e5App().getObject("Project").newProject.connect(
            self.__projectOpened)
        
        if e5App().getObject("Project").isOpen():
            for editor in e5App().getObject("ViewManager").getOpenEditors():
                self.__editorOpened(editor)
        
        return None, True
    
    def deactivate(self):
        """
        Public method to deactivate this plugin.
        """
        e5App().unregisterPluginObject("RefactoringRope")
        
        e5App().getObject("ViewManager").editorOpenedEd.disconnect(
            self.__editorOpened)
        e5App().getObject("ViewManager").editorClosedEd.disconnect(
            self.__editorClosed)
        
        e5App().getObject("Project").projectOpened.disconnect(
            self.__object.projectOpened)
        e5App().getObject("Project").projectPropertiesChanged.disconnect(
            self.__object.projectOpened)
        e5App().getObject("Project").projectClosed.disconnect(
            self.__object.projectClosed)
        e5App().getObject("Project").newProject.disconnect(
            self.__object.projectOpened)
        
        e5App().getObject("Project").projectOpened.disconnect(
            self.__projectOpened)
        e5App().getObject("Project").projectPropertiesChanged.disconnect(
            self.__projectOpened)
        e5App().getObject("Project").projectClosed.disconnect(
            self.__projectClosed)
        e5App().getObject("Project").newProject.disconnect(
            self.__projectOpened)
        
        self.__ui.menuBar().removeAction(self.__mainAct)
        
        for editor in self.__editors[:]:
            self.__editorClosed(editor)
        
        self.__initialize()
    
    def __loadTranslator(self):
        """
        Private method to load the translation file.
        """
        if self.__ui is not None:
            loc = self.__ui.getLocale()
            if loc and loc != "C":
                locale_dir = \
                    os.path.join(os.path.dirname(__file__),
                                 "RefactoringRope", "i18n")
                translation = "rope_%s" % loc
                translator = QTranslator(None)
                loaded = translator.load(translation, locale_dir)
                if loaded:
                    self.__translator = translator
                    e5App().installTranslator(self.__translator)
                else:
                    print("Warning: translation file '{0}' could not"
                          " be loaded.".format(translation))
                    print("Using default.")
    
    def getPreferences(self, key):
        """
        Public method to retrieve the various refactoring settings.
        
        @param key the key of the value to get
        @return the requested refactoring setting
        """
        if key in ["CodeAssistEnabled", "CodeAssistCalltipsEnabled",
                   "ShowQScintillaCompletions"]:
            return Preferences.toBool(Preferences.Prefs.settings.value(
                self.PreferencesKey + "/" + key, self.__defaults[key]))
        else:
            return int(Preferences.Prefs.settings.value(
                self.PreferencesKey + "/" + key, self.__defaults[key]))
    
    def setPreferences(self, key, value):
        """
        Public method to store the various refactoring settings.
        
        @param key the key of the setting to be set (string)
        @param value the value to be set
        """
        Preferences.Prefs.settings.setValue(
            self.PreferencesKey + "/" + key, value)
        
        if key in ["CodeAssistEnabled", "CodeAssistCalltipsEnabled"]:
            if value:
                if e5App().getObject("Project").isOpen():
                    for editor in e5App().getObject("ViewManager")\
                            .getOpenEditors():
                        if editor not in self.__editors:
                            self.__editorOpened(editor)
            else:
                for editor in self.__editors[:]:
                    self.__editorClosed(editor)
        elif key == "CodeAssistTimeout":
            self.__acTimer.setInterval(value)
    
    def __determineLanguage(self):
        """
        Private method to determine the valid language strings.
        
        @return list of valid language strings (list of string)
        """
        if sys.version_info[0] == 3:
            lang = ["Python3", "Pygments|Python 3"]
        elif sys.version_info[0] == 2:
            lang = ["Python", "Python2", "Pygments|Python"]
        else:
            lang = []
        
        return lang
    
    def __projectOpened(self):
        """
        Private slot to handle the projectOpened signal.
        """
        lang = self.__determineLanguage()
        
        enabled = e5App().getObject("Project").getProjectLanguage() in lang
        self.__mainAct.setEnabled(enabled)
        self.__projectIsOpen = enabled
    
    def __projectClosed(self):
        """
        Private slot to handle the projectClosed signal.
        """
        self.__mainAct.setEnabled(False)
        self.__projectIsOpen = False
    
    def __editorOpened(self, editor):
        """
        Private slot called, when a new editor was opened.
        
        @param editor reference to the new editor (QScintilla.Editor)
        """
        lang = self.__determineLanguage()
        
        if editor.getLanguage() in lang:
            self.__connectEditor(editor)
        
        editor.languageChanged.connect(self.__editorLanguageChanged)
        self.__editors.append(editor)
    
    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.languageChanged.disconnect(self.__editorLanguageChanged)
            self.__disconnectEditor(editor)
            self.__editors.remove(editor)
    
    def __editorLanguageChanged(self, language):
        """
        Private slot to handle the language change of an editor.
        
        @param language programming language of the editor (string)
        """
        editor = self.sender()
        lang = self.__determineLanguage()
        
        if language in lang:
            if editor.autoCompletionHook() != self.codeAssist:
                self.__connectEditor(editor)
        else:
            self.__disconnectEditor(editor)
    
    def __connectEditor(self, editor):
        """
        Private method to connect an editor.
        
        @param editor reference to the editor (QScintilla.Editor)
        """
        editor.editorAboutToBeSaved.connect(self.__editorAboutToBeSaved)
        editor.editorSaved.connect(self.__editorSaved)
        
        if self.getPreferences("CodeAssistEnabled"):
            self.__setAutoCompletionHook(editor)
        if self.getPreferences("CodeAssistCalltipsEnabled"):
            self.__setCalltipsHook(editor)
    
    def __disconnectEditor(self, editor):
        """
        Private method to disconnect an editor.
        
        @param editor reference to the editor (QScintilla.Editor)
        """
        try:
            editor.editorAboutToBeSaved.disconnect(self.__editorAboutToBeSaved)
            editor.editorSaved.disconnect(self.__editorSaved)
        except TypeError:
            # just ignore it
            pass
        
        if editor.autoCompletionHook() == self.codeAssist:
            self.__unsetAutoCompletionHook(editor)
        if editor.callTipHook() == self.codeAssistCallTip:
            self.__unsetCalltipsHook(editor)
    
    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 (QString)
        """
        from QScintilla.Editor import EditorAutoCompletionListID
        
        editor = self.sender()
        if id == EditorAutoCompletionListID:
            lst = txt.split()
            if len(lst) > 1:
                txt = lst[0]
            
            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.codeAssist)
    
    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 codeAssist(self, editor, context=False):
        """
        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)
        """
        self.__currentEditor = editor
        if self.getPreferences("CodeAssistTimeout"):
            self.__acTimer.stop()
            self.__acTimer.start()
        else:
            self.__codeAssist()
    
    def __codeAssist(self):
        """
        Private slot to show a list with completion proposals.
        """
        from QScintilla.Editor import EditorAutoCompletionListID
        
        if self.__currentEditor is not None:
            if self.__currentEditor.isListActive():
                self.__currentEditor.cancelList()
            completions = self.__caObject.getCompletions(self.__currentEditor)
            if len(completions) == 0 and \
                    self.getPreferences("ShowQScintillaCompletions"):
                # try QScintilla autocompletion
                self.__currentEditor.autoCompleteQScintilla()
            else:
                completions.sort()
                self.__currentEditor.showUserList(EditorAutoCompletionListID,
                                                  completions)
    
    def __editorAboutToBeSaved(self, filename):
        """
        Private slot to get the old contents of the named file.
        
        @param filename name of the file about to be saved (string)
        """
        if filename and os.path.exists(filename):
            try:
                self.__oldEditorText = Utilities.readEncodedFile(filename)[0]
            except IOError:
                self.__oldEditorText = ""
            self.__savedEditorName = filename
        else:
            self.__savedEditorName = ""
            self.__oldEditorText = ""
    
    def __editorSaved(self, filename):
        """
        Private slot to activate SOA.
        
        @param filename name of the file that was saved (string)
        """
        if filename == self.__savedEditorName and self.__oldEditorText:
            self.__object.reportChanged(self.__savedEditorName,
                                        self.__oldEditorText)
            self.__caObject.reportChanged(self.__savedEditorName,
                                          self.__oldEditorText)
        else:
            self.__object.reportChanged(filename, "")
            self.__caObject.reportChanged(filename, "")
    
    def __setCalltipsHook(self, editor):
        """
        Private method to set the calltip hook.
        
        @param editor reference to the editor (QScintilla.Editor)
        """
        editor.setCallTipHook(self.codeAssistCallTip)
    
    def __unsetCalltipsHook(self, editor):
        """
        Private method to unset the calltip hook.
        
        @param editor reference to the editor (QScintilla.Editor)
        """
        editor.unsetCallTipHook()
    
    def codeAssistCallTip(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)
        """
        cts = self.__caObject.getCallTips(pos, editor)
        return cts

eric ide

mercurial