Esempio n. 1
0
        def getRopeProject(self, fileName=""):
            " Provides existed or creates a new rope project "
            if self.project.isLoaded():
                return self.project.getRopeProject()

            # There is no current project so create a temporary one.
            # Two cases: the buffer has been saved
            #            not saved buffer
            if os.path.isabs(fileName):
                dirName = os.path.dirname(fileName)
            else:
                # Unsaved buffer, make an assumption that
                # it is in home directory
                dirName = str(QDir.homePath())

            prefs = copy.deepcopy(ropePreferences)

            # Exclude nested dirs as it could take too long
            # Get only dir names and do not get those dirs
            # where __init__.py[3] are present
            subdirsToExclude = getSubdirs(dirName, True, True)

            if "ignored_resources" in prefs:
                prefs["ignored_resources"] += subdirsToExclude
            else:
                prefs["ignored_resources"] = subdirsToExclude

            project = RopeProject(dirName, None, None, **prefs)
            project.validate(project.root)
            return project
Esempio n. 2
0
        def getRopeProject( self, fileName = "" ):
            " Provides existed or creates a new rope project "
            if self.project.isLoaded():
                return self.project.getRopeProject()

            # There is no current project so create a temporary one.
            # Two cases: the buffer has been saved
            #            not saved buffer
            if os.path.isabs( fileName ):
                dirName = os.path.dirname( fileName )
            else:
                # Unsaved buffer, make an assumption that
                # it is in home directory
                dirName = str( QDir.homePath() )

            prefs = copy.deepcopy( ropePreferences )

            # Exclude nested dirs as it could take too long
            # Get only dir names and do not get those dirs
            # where __init__.py[3] are present
            subdirsToExclude = getSubdirs( dirName, True, True )

            if "ignored_resources" in prefs:
                prefs[ "ignored_resources" ] += subdirsToExclude
            else:
                prefs[ "ignored_resources" ] = subdirsToExclude

            project = RopeProject( dirName, None, None, **prefs )
            project.validate( project.root )
            return project
Esempio n. 3
0
 def _get_other_projects(path_only=False):
     """
     Gets the list of secondary projects (all except current).
     """
     projects = []
     current = api.project.get_current_project()
     for path in api.project.get_projects():
         if path == current:
             continue
         if not path_only:
             prj = Project(path, ropefolder=api.project.FOLDER,
                           fscommands=FileSystemCommands())
             prj.validate()
         else:
             prj = path
         projects.append(prj)
     return projects
Esempio n. 4
0
 def _get_other_projects(path_only=False):
     """
     Gets the list of secondary projects (all except current).
     """
     projects = []
     current = api.project.get_current_project()
     for path in api.project.get_projects():
         if path == current:
             continue
         if not path_only:
             prj = Project(path,
                           ropefolder=api.project.FOLDER,
                           fscommands=FileSystemCommands())
             prj.validate()
         else:
             prj = path
         projects.append(prj)
     return projects
Esempio n. 5
0
class CodimensionProject(QObject):
    " Provides codimension project singleton facility "

    # Constants for the projectChanged signal
    CompleteProject = 0  # It is a completely new project
    Properties = 1  # Project properties were updated

    projectChanged = pyqtSignal(int)
    fsChanged = pyqtSignal(list)

    def __init__(self):
        QObject.__init__(self)

        self.__dirWatcher = None
        self.__formatOK = True

        # Avoid pylint complains
        self.fileName = ""
        self.userProjectDir = ""  # Directory in ~/.codimension/uuidNN/
        self.filesList = set()

        self.scriptName = ""  # Script to run the project
        self.creationDate = ""
        self.author = ""
        self.license = ""
        self.copyright = ""
        self.version = ""
        self.email = ""
        self.description = ""
        self.uuid = ""

        self.__ropeProject = None
        self.__ropeSourceDirs = []

        # Coming from separate files from ~/.codimension/uuidN/
        self.todos = []
        self.bookmarks = []
        self.runParamsCache = RunParametersCache()
        self.topLevelDirs = []
        self.findHistory = []
        self.findNameHistory = []
        self.findFileHistory = []
        self.replaceHistory = []
        self.tabsStatus = []

        self.findFilesWhat = []
        self.findFilesDirs = []
        self.findFilesMasks = []

        self.findClassHistory = []
        self.findFuncHistory = []
        self.findGlobalHistory = []

        self.recentFiles = []
        self.importDirs = []
        self.fileBrowserPaths = []

        self.ignoredExcpt = []
        self.breakpoints = []
        self.watchpoints = []

        # Precompile the exclude filters
        self.__excludeFilter = []
        for flt in Settings().projectFilesFilters:
            self.__excludeFilter.append(re.compile(flt))

        return

    def shouldExclude(self, name):
        " Tests if a file must be excluded "
        for excl in self.__excludeFilter:
            if excl.match(name):
                return True
        return False

    def __resetValues(self):
        """ Initializes or resets all the project members """

        # Empty file name means that the project has not been loaded or
        # created. This must be an absolute path.
        self.fileName = ""
        self.userProjectDir = ""

        # Generated having the project dir Full paths are stored.
        # The set holds all files and directories. The dirs end with os.path.sep
        self.filesList = set()

        self.scriptName = ""
        self.creationDate = ""
        self.author = ""
        self.license = ""
        self.copyright = ""
        self.version = ""
        self.email = ""
        self.description = ""
        self.uuid = ""

        # Coming from separate files from ~/.codimension/uuidN/
        self.todos = []
        self.bookmarks = []
        self.runParamsCache = RunParametersCache()
        self.topLevelDirs = []
        self.findHistory = []
        self.findNameHistory = []
        self.findFileHistory = []
        self.replaceHistory = []
        self.tabsStatus = []

        self.findFilesWhat = []
        self.findFilesDirs = []
        self.findFilesMasks = []

        self.findClassHistory = []
        self.findFuncHistory = []
        self.findGlobalHistory = []

        self.recentFiles = []
        self.importDirs = []
        self.fileBrowserPaths = []

        self.ignoredExcpt = []
        self.breakpoints = []
        self.watchpoints = []

        # Reset the dir watchers if so
        if self.__dirWatcher is not None:
            del self.__dirWatcher
        self.__dirWatcher = None
        return

    def createNew(self, fileName, scriptName, importDirs, author, lic,
                  copyRight, description, creationDate, version, email):
        " Creates a new project "

        # Try to create the user project directory
        projectUuid = str(uuid.uuid1())
        userProjectDir = settingsDir + projectUuid + sep
        if not os.path.exists(userProjectDir):
            try:
                os.mkdir(userProjectDir)
            except:
                logging.error("Cannot create user project directory: " +
                              self.userProjectDir + ". Please check the "
                              "available disk space and re-create the "
                              "project.")
                raise
        else:
            logging.warning("The user project directory existed! "
                            "The content will be overwritten.")
            self.__removeProjectFiles(userProjectDir)

        # Basic pre-requisites are met. We can reset the current project
        self.__resetValues()

        self.fileName = str(fileName)
        self.importDirs = importDirs
        self.scriptName = scriptName
        self.creationDate = creationDate
        self.author = author
        self.license = lic
        self.copyright = copyRight
        self.version = version
        self.email = email
        self.description = description
        self.uuid = projectUuid
        self.userProjectDir = userProjectDir

        self.__createProjectFile()  # ~/.codimension/uuidNN/project

        self.__generateFilesList()

        self.saveProject()

        # Update the watcher
        self.__dirWatcher = Watcher(Settings().projectFilesFilters,
                                    self.getProjectDir())
        self.__dirWatcher.fsChanged.connect(self.onFSChanged)

        self.__createRopeProject()
        self.projectChanged.emit(self.CompleteProject)
        return

    @staticmethod
    def __safeRemove(path):
        " Safe file removal "
        try:
            os.remove(path)
        except:
            return

    def __removeProjectFiles(self, userProjectDir):
        " Removes user project files "

        self.__safeRemove(userProjectDir + "project")
        self.__safeRemove(userProjectDir + "bookmarks")
        self.__safeRemove(userProjectDir + "todos")
        self.__safeRemove(userProjectDir + "searchhistory")
        self.__safeRemove(userProjectDir + "topleveldirs")
        self.__safeRemove(userProjectDir + "tabsstatus")
        self.__safeRemove(userProjectDir + "findinfiles")
        self.__safeRemove(userProjectDir + "recentfiles")
        self.__safeRemove(userProjectDir + "filebrowser")
        self.__safeRemove(userProjectDir + "ignoredexcpt")
        self.__safeRemove(userProjectDir + "breakpoints")
        self.__safeRemove(userProjectDir + "watchpoints")
        return

    def __createProjectFile(self):
        " Helper function to create the user project file "
        try:
            f = open(self.userProjectDir + "project", "w")
            f.write(self.fileName)
            f.close()
        except:
            return

    def saveProject(self):
        " Writes all the settings into the file "
        if not self.isLoaded():
            return

        # Project properties part
        propertiesPart = "[properties]\n" \
                         "scriptname=" + self.scriptName + "\n" \
                         "creationdate=" + self.creationDate + "\n" \
                         "author=" + self.author + "\n" \
                         "license=" + self.license + "\n" \
                         "copyright=" + self.copyright + "\n" \
                         "description=" + \
                            self.description.replace( '\n', '<CR><LF>' ) + \
                            "\n" \
                         "version=" + self.version + "\n" \
                         "email=" + self.email + "\n" \
                         "uuid=" + self.uuid + "\n"

        # It could be another user project file without write permissions
        skipProjectFile = False
        if os.path.exists(self.fileName):
            if not os.access(self.fileName, os.W_OK):
                skipProjectFile = True
        else:
            if not os.access(os.path.dirname(self.fileName), os.W_OK):
                skipProjectFile = True

        if not skipProjectFile:
            f = open(self.fileName, "w")
            self.__writeHeader(f)
            self.__writeList(f, "importdirs", "dir", self.importDirs)
            f.write(propertiesPart + "\n\n\n")
            f.close()

        self.serializeRunParameters()
        self.__saveTopLevelDirs()
        self.__saveSearchHistory()
        self.__saveTabsStatus()
        self.__saveFindFiles()
        self.__saveFindObjects()
        self.__saveRecentFiles()
        self.__saveIgnoredExcpt()
        self.__saveBreakpoints()
        self.__saveWatchpoints()

        self.__formatOK = True
        return

    def serializeRunParameters(self):
        " Saves the run parameters cache "
        self.runParamsCache.serialize(self.userProjectDir + "runparamscache")
        return

    def __saveTabsStatus(self):
        " Helper to save tabs status "
        if self.isLoaded():
            f = open(self.userProjectDir + "tabsstatus", "w")
            self.__writeHeader(f)
            self.__writeList(f, "tabsstatus", "tab", self.tabsStatus)
            f.close()
        return

    def __saveSearchHistory(self):
        " Helper to save the project search history "
        if self.isLoaded():
            f = open(self.userProjectDir + "searchhistory", "w")
            self.__writeHeader(f)
            self.__writeList(f, "findhistory", "find", self.findHistory)
            self.__writeList(f, "replacehistory", "replace",
                             self.replaceHistory)
            self.__writeList(f, "findnamehistory", "find",
                             self.findNameHistory)
            self.__writeList(f, "findfilehistory", "find",
                             self.findFileHistory)
            f.close()
        return

    def __saveTopLevelDirs(self):
        " Helper to save the project top level dirs "
        if self.isLoaded():
            f = open(self.userProjectDir + "topleveldirs", "w")
            self.__writeHeader(f)
            self.__writeList(f, "topleveldirs", "dir", self.topLevelDirs)
            f.close()
        return

    def __saveFindFiles(self):
        " Helper to save the find in files history "
        if self.isLoaded():
            f = open(self.userProjectDir + "findinfiles", "w")
            self.__writeHeader(f)
            self.__writeList(f, "whathistory", "what", self.findFilesWhat)
            self.__writeList(f, "dirhistory", "dir", self.findFilesDirs)
            self.__writeList(f, "maskhistory", "mask", self.findFilesMasks)
            f.close()
        return

    def __saveFindObjects(self):
        " Helper to save find objects history "
        if self.isLoaded():
            f = open(self.userProjectDir + "findobjects", "w")
            self.__writeHeader(f)
            self.__writeList(f, "classhistory", "class", self.findClassHistory)
            self.__writeList(f, "funchistory", "func", self.findFuncHistory)
            self.__writeList(f, "globalhistory", "global",
                             self.findGlobalHistory)
            f.close()
        return

    def __saveSectionToFile(self, fileName, sectionName, itemName, values):
        " Saves the given values into a file "
        if self.isLoaded():
            f = open(self.userProjectDir + fileName, "w")
            self.__writeHeader(f)
            self.__writeList(f, sectionName, itemName, values)
            f.close()
        return

    def __saveRecentFiles(self):
        " Helper to save recent files list "
        self.__saveSectionToFile("recentfiles", "recentfiles", "file",
                                 self.recentFiles)
        return

    def __saveIgnoredExcpt(self):
        " Helper to save ignored exceptions list "
        self.__saveSectionToFile("ignoredexcpt", "ignoredexcpt", "excpttype",
                                 self.ignoredExcpt)
        return

    def __saveBreakpoints(self):
        " Helper to save breakpoints "
        self.__saveSectionToFile("breakpoints", "breakpoints", "bpoint",
                                 self.breakpoints)
        return

    def __saveWatchpoints(self):
        " helper to save watchpoints "
        self.__saveSectionToFile("watchpoints", "watchpoints", "wpoint",
                                 self.watchpoints)
        return

    @staticmethod
    def __writeHeader(fileObj):
        " Helper to write a header with a warning "
        fileObj.write("#\n"
                      "# Generated automatically.\n"
                      "# Don't edit it manually unless you "
                      "know what you are doing.\n"
                      "#\n\n")
        return

    @staticmethod
    def __writeList(fileObj, header, prefix, items):
        " Helper to write a list "
        fileObj.write("[" + header + "]\n")
        index = 0
        for item in items:
            fileObj.write(prefix + str(index) + "=" + item + "\n")
            index += 1
        fileObj.write("\n")
        return

    def __getStr(self, conf, sec, key, default):
        " Helper to read a config value "
        try:
            return conf.get(sec, key).strip()
        except:
            self.__formatOK = False
        return default

    def loadProject(self, projectFile):
        """ Loads a project from the given file """

        absPath = os.path.abspath(projectFile)
        if not os.path.exists(absPath):
            raise Exception("Cannot open project file " + projectFile)
        if not absPath.endswith(".cdm"):
            raise Exception("Unexpected project file extension. "
                            "Expected: .cdm")

        config = ConfigParser.ConfigParser()

        try:
            config.read(absPath)
        except:
            # Bad error - cannot load project file at all
            config = None
            raise Exception("Bad project file")

        self.__resetValues()
        self.fileName = str(absPath)

        # Properties part
        self.scriptName = self.__getStr(config, 'properties', 'scriptname', '')
        self.creationDate = self.__getStr(config, 'properties', 'creationdate',
                                          '')
        self.author = self.__getStr(config, 'properties', 'author', '')
        self.license = self.__getStr(config, 'properties', 'license', '')
        self.copyright = self.__getStr(config, 'properties', 'copyright', '')
        self.description = self.__getStr(config, 'properties', 'description',
                                         '').replace('<CR><LF>', '\n')
        self.version = self.__getStr(config, 'properties', 'version', '')
        self.email = self.__getStr(config, 'properties', 'email', '')
        self.uuid = self.__getStr(config, 'properties', 'uuid', '')
        if self.uuid == "":
            logging.warning("Project file does not have UUID. "
                            "Re-generate it...")
            self.uuid = str(uuid.uuid1())
        self.userProjectDir = settingsDir + self.uuid + sep
        if not os.path.exists(self.userProjectDir):
            os.mkdir(self.userProjectDir)

        # import dirs part
        index = 0
        try:
            while True:
                dirName = config.get('importdirs', 'dir' + str(index)).strip()
                index += 1
                if os.path.isabs(dirName):
                    absPath = dirName
                else:
                    absPath = self.getProjectDir() + dirName
                if not os.path.exists(absPath):
                    logging.error("Codimension project: cannot find "
                                  "import directory: " + dirName)
                elif not isdir(absPath):
                    logging.error("Codimension project: the import path: " +
                                  dirName + " is not a directory")
                self.importDirs.append(dirName)
        except ConfigParser.NoSectionError:
            self.__formatOK = False
        except ConfigParser.NoOptionError:
            # just continue
            pass
        except:
            self.__formatOK = False

        config = None

        # Read the other config files
        self.__loadTopLevelDirs()
        self.__loadSearchHistory()
        self.__loadTabsStatus()
        self.__loadFindFiles()
        self.__loadFindObjects()
        self.__loadRecentFiles()
        self.__loadProjectBrowserExpandedDirs()
        self.__loadIgnoredExceptions()
        self.__loadBreakpoints()
        self.__loadWatchpoints()

        # The project might have been moved...
        self.__createProjectFile()  # ~/.codimension/uuidNN/project
        self.__generateFilesList()

        if os.path.exists(self.userProjectDir + "runparamscache"):
            self.runParamsCache.deserialize(self.userProjectDir +
                                            "runparamscache")

        if not self.__formatOK:
            logging.info("Project files are broken or absent. "
                         "Overwriting the project files.")
            self.saveProject()

        # Update the recent list
        Settings().addRecentProject(self.fileName)

        # Setup the new watcher
        self.__dirWatcher = Watcher(Settings().projectFilesFilters,
                                    self.getProjectDir())
        self.__dirWatcher.fsChanged.connect(self.onFSChanged)

        self.__createRopeProject()
        self.projectChanged.emit(self.CompleteProject)
        self.emit(SIGNAL('restoreProjectExpandedDirs'))
        return

    def getImportDirsAsAbsolutePaths(self):
        " Provides a list of import dirs as absolute paths "
        result = []
        for path in self.importDirs:
            if os.path.isabs(path):
                result.append(path)
            else:
                result.append(self.getProjectDir() + path)
        return result

    def __getImportDirsForRope(self):
        " Provides the list of import dirs for the rope library "
        result = []
        for path in self.importDirs:
            if not os.path.isabs(path):
                # The only relative paths can be accepted by rope
                result.append(path)
        result.sort()
        return result

    def getRopeProject(self):
        " Provides the rope project "
        if self.__getImportDirsForRope() != self.__ropeSourceDirs:
            # The user changed the project import dirs, so let's
            # re-create the project
            self.__createRopeProject()
        return self.__ropeProject

    def validateRopeProject(self, fileName):
        " Validates the rope project "
        # Currently rope can validate a directory only so the argument
        # is ignored
        self.__ropeProject.validate()
        return

    def __createRopeProject(self):
        " Creates a rope library project "
        if self.__ropeProject is not None:
            self.__ropeProject.close()
            self.__ropeProject = None
            self.__ropeSourceDirs = []

        # Deal with import dirs and preferences first
        self.__ropeSourceDirs = self.__getImportDirsForRope()
        prefs = copy.deepcopy(ropePreferences)
        if len(self.__ropeSourceDirs) != 0:
            prefs["source_folders"] = self.__ropeSourceDirs

        # Rope folder is default here, so it will be created
        self.__ropeProject = RopeProject(self.getProjectDir(), **prefs)
        self.__ropeProject.validate(self.__ropeProject.root)
        return

    def onFSChanged(self, items):
        " Triggered when the watcher detects changes "
        ##        report = "REPORT: "
        ##        projectItems = []
        for item in items:
            item = str(item)

            #            if not islink( item ):
            #                realPath = realpath( item[ 1: ] )
            #                isDir = item.endswith( sep )
            #                if isDir:
            #                    if self.isProjectDir( realPath + sep ):
            #                        item = item[ 0 ] + realPath + sep
            #                else:
            #                    if self.isProjectFile( realPath + sep ):
            #                        item = item[ 0 ] + realPath

            #            projectItems.append( item )
            ##            report += " " + item
            try:
                if item.startswith('+'):
                    self.filesList.add(item[1:])
                else:
                    self.filesList.remove(item[1:])
##                projectItems.append( item )
            except:
                #                print "EXCEPTION for '" + item + "'"
                pass


#        print "'" + report + "'"

        self.fsChanged.emit(items)
        #        self.__dirWatcher.debug()
        return

    def __loadTabsStatus(self):
        " Loads the last tabs status "
        configFile = self.userProjectDir + "tabsstatus"
        if not os.path.exists(configFile):
            logging.info("Cannot find tabsstatus project file. "
                         "Expected here: " + configFile)
            self.__formatOK = False
            return

        config = ConfigParser.ConfigParser()
        try:
            config.read(configFile)
        except:
            # Bad error - cannot load project file at all
            config = None
            self.__formatOK = False
            logging.warning("Cannot read tabsstatus project file "
                            "from here: " + configFile)
            return

        # tabs part
        self.tabsStatus = self.__loadListSection(config, 'tabsstatus', 'tab')

        config = None
        return

    def __loadProjectBrowserExpandedDirs(self):
        " Loads the project browser expanded dirs "
        configFile = self.userProjectDir + "filebrowser"
        if not os.path.exists(configFile):
            self.fileBrowserPaths = []
            return

        config = ConfigParser.ConfigParser()
        try:
            config.read(configFile)
        except:
            # Bad error - cannot load project file at all
            config = None
            self.fileBrowserPaths = []
            return

        # dirs part
        self.fileBrowserPaths = self.__loadListSection(config, 'filebrowser',
                                                       'path')
        config = None
        return

    def __loadTopLevelDirs(self):
        " Loads the top level dirs "
        configFile = self.userProjectDir + "topleveldirs"
        if not os.path.exists(configFile):
            logging.info("Cannot find topleveldirs project file. "
                         "Expected here: " + configFile)
            self.__formatOK = False
            return

        config = ConfigParser.ConfigParser()
        try:
            config.read(configFile)
        except:
            # Bad error - cannot load project file at all
            config = None
            self.__formatOK = False
            logging.warning("Cannot read topleveldirs project file "
                            "from here: " + configFile)
            return

        # dirs part
        self.topLevelDirs = self.__loadListSection(config, 'topleveldirs',
                                                   'dir')

        config = None
        return

    def __loadSearchHistory(self):
        " Loads the search history file content "
        confFile = self.userProjectDir + "searchhistory"
        if not os.path.exists(confFile):
            logging.info("Cannot find searchhistory project file. "
                         "Expected here: " + confFile)
            self.__formatOK = False
            return

        config = ConfigParser.ConfigParser()
        try:
            config.read(confFile)
        except:
            # Bad error - cannot load project file at all
            config = None
            self.__formatOK = False
            logging.warning("Cannot read searchhistory project file "
                            "from here: " + confFile)
            return

        # find part
        self.findHistory = self.__loadListSection(config, 'findhistory',
                                                  'find')
        self.findNameHistory = self.__loadListSection(config,
                                                      'findnamehistory',
                                                      'find')
        self.findFileHistory = self.__loadListSection(config,
                                                      'findfilehistory',
                                                      'find')

        # replace part
        self.replaceHistory = self.__loadListSection(config, 'replacehistory',
                                                     'replace')

        config = None
        return

    def __loadFindObjects(self):
        " Loads the find objects history "
        confFile = self.userProjectDir + "findobjects"
        if not os.path.exists(confFile):
            logging.info("Cannot find findobjects project file. "
                         "Expected here: " + confFile)
            self.__formatOK = False
            return

        config = ConfigParser.ConfigParser()
        try:
            config.read(confFile)
        except:
            # Bad error - cannot load project file at all
            config = None
            self.__formatOK = False
            logging.warning("Cannot read findobjects project file "
                            "from here: " + confFile)
            return

        self.findClassHistory = self.__loadListSection(config, 'classhistory',
                                                       'class')
        self.findFuncHistory = self.__loadListSection(config, 'funchistory',
                                                      'func')
        self.findGlobalHistory = self.__loadListSection(
            config, 'globalhistory', 'global')
        config = None
        return

    def __loadFindFiles(self):
        " Loads the find in files history "
        confFile = self.userProjectDir + "findinfiles"
        if not os.path.exists(confFile):
            logging.info("Cannot find findinfiles project file. "
                         "Expected here: " + confFile)
            self.__formatOK = False
            return

        config = ConfigParser.ConfigParser()
        try:
            config.read(confFile)
        except:
            # Bad error - cannot load project file at all
            config = None
            self.__formatOK = False
            logging.warning("Cannot read findinfiles project file "
                            "from here: " + confFile)
            return

        self.findFilesWhat = self.__loadListSection(config, 'whathistory',
                                                    'what')
        self.findFilesDirs = self.__loadListSection(config, 'dirhistory',
                                                    'dir')
        self.findFilesMasks = self.__loadListSection(config, 'maskhistory',
                                                     'mask')
        config = None
        return

    def __loadRecentFiles(self):
        " Loads the recent files list "
        confFile = self.userProjectDir + "recentfiles"
        if not os.path.exists(confFile):
            logging.info("Cannot find recentfiles project file. "
                         "Expected here: " + confFile)
            self.__formatOK = False
            return

        config = ConfigParser.ConfigParser()
        try:
            config.read(confFile)
        except:
            # Bad error - cannot load project file at all
            config = None
            self.__formatOK = False
            logging.warning("Cannot read recentfiles project file "
                            "from here: " + confFile)
            return

        self.recentFiles = self.__loadListSection(config, 'recentfiles',
                                                  'file')

        # Due to a bug there could be the same files twice in the list.
        # The difference is doubled path separator. Fix it here.
        temp = set()
        for path in self.recentFiles:
            temp.add(os.path.normpath(path))
        self.recentFiles = list(temp)

        config = None
        return

    def __loadSectionFromFile(self, fileName, sectionName, itemName,
                              errMessageEntity):
        " Loads the given section from the given file "
        confFile = self.userProjectDir + fileName
        if not os.path.exists(confFile):
            logging.info("Cannot find " + errMessageEntity + " file. "
                         "Expected here: " + confFile)
            self.__formatOK = False
            return []

        config = ConfigParser.ConfigParser()
        try:
            config.read(confFile)
        except:
            # Bad error - cannot load project file at all
            config = None
            self.__formatOK = False
            logging.warning("Cannot read " + errMessageEntity + " file "
                            "from here: " + confFile)
            return []

        values = self.__loadListSection(config, sectionName, itemName)
        config = None
        return values

    def __loadIgnoredExceptions(self):
        " Loads the ignored exceptions list "
        self.ignoredExcpt = self.__loadSectionFromFile("ignoredexcpt",
                                                       "ignoredexcpt",
                                                       "excpttype",
                                                       "ignored exceptions")
        return

    def __loadBreakpoints(self):
        " Loads the project breakpoints "
        self.breakpoints = self.__loadSectionFromFile("breakpoints",
                                                      "breakpoints", "bpoint",
                                                      "breakpoints")
        return

    def __loadWatchpoints(self):
        " Loads the project watchpoints "
        self.watchpoints = self.__loadSectionFromFile("watchpoints",
                                                      "watchpoints", "wpoint",
                                                      "watchpoints")
        return

    def __loadListSection(self, config, section, listPrefix):
        " Loads a list off the given section from the given file "
        items = []
        index = 0
        try:
            while True:
                item = config.get(section, listPrefix + str(index)).strip()
                index += 1
                items.append(item)
        except ConfigParser.NoSectionError:
            self.__formatOK = False
        except ConfigParser.NoOptionError:
            pass  # Just continue
        except:
            self.__formatOK = False
        return items

    def unloadProject(self, emitSignal=True):
        """ Unloads the current project if required """
        self.emit(SIGNAL('projectAboutToUnload'))
        if self.isLoaded():
            self.__saveProjectBrowserExpandedDirs()
        self.__resetValues()
        if emitSignal:
            # No need to send a signal e.g. if IDE is closing
            self.projectChanged.emit(self.CompleteProject)
        if self.__ropeProject is not None:
            try:
                # If the project directory is read only then closing the
                # rope project generates exception
                self.__ropeProject.close()
            except:
                pass
        self.__ropeProject = None
        self.__ropeSourceDirs = []
        return

    def __saveProjectBrowserExpandedDirs(self):
        " Saves the pathes expanded in the project browser "
        if not self.isLoaded():
            return

        try:
            f = open(self.userProjectDir + "filebrowser", "w")
            self.__writeHeader(f)
            self.__writeList(f, "filebrowser", "path", self.fileBrowserPaths)
            f.close()
        except:
            pass
        return

    def setImportDirs(self, paths):
        " Sets a new set of the project import dirs "
        if self.importDirs != paths:
            self.importDirs = paths
            self.saveProject()
            self.projectChanged.emit(self.Properties)
        return

    def __generateFilesList(self):
        """ Generates the files list having the list of dirs """
        self.filesList = set()
        path = self.getProjectDir()
        self.filesList.add(path)
        self.__scanDir(path)
        return

    def __scanDir(self, path):
        """ Recursive function to scan one dir """
        # The path is with '/' at the end
        for item in os.listdir(path):
            if self.shouldExclude(item):
                continue

            # Exclude symlinks if they point to the other project
            # covered pieces
            candidate = path + item
            if islink(candidate):
                realItem = realpath(candidate)
                if isdir(realItem):
                    if self.isProjectDir(realItem):
                        continue
                else:
                    if self.isProjectDir(os.path.dirname(realItem)):
                        continue

            if isdir(candidate):
                self.filesList.add(candidate + sep)
                self.__scanDir(candidate + sep)
                continue
            self.filesList.add(candidate)
        return

    def isProjectDir(self, path):
        " Returns True if the path belongs to the project "
        if not self.isLoaded():
            return False
        path = realpath(str(path))  # it could be a symlink
        if not path.endswith(sep):
            path += sep
        return path.startswith(self.getProjectDir())

    def isProjectFile(self, path):
        " Returns True if the path belongs to the project "
        if not self.isLoaded():
            return False
        return self.isProjectDir(os.path.dirname(path))

    def isTopLevelDir(self, path):
        " Checks if the path is a top level dir "
        if not path.endswith(sep):
            path += sep
        return path in self.topLevelDirs

    def addTopLevelDir(self, path):
        " Adds the path to the top level dirs list "
        if not path.endswith(sep):
            path += sep
        if path in self.topLevelDirs:
            logging.warning("Top level dir " + path +
                            " is already in the list of dirs. "
                            "Ignore adding...")
            return
        self.topLevelDirs.append(path)
        self.__saveTopLevelDirs()
        return

    def removeTopLevelDir(self, path):
        " Removes the path from the top level dirs list "
        if not path.endswith(sep):
            path += sep
        if path not in self.topLevelDirs:
            logging.warning("Top level dir " + path +
                            " is not in the list of dirs. Ignore removing...")
            return
        self.topLevelDirs.remove(path)
        self.__saveTopLevelDirs()
        return

    def setFindNameHistory(self, history):
        " Sets the new find name history and saves it into a file "
        self.findNameHistory = history
        self.__saveSearchHistory()
        return

    def setFindFileHistory(self, history):
        " Sets the new find file history and saves it into a file "
        self.findFileHistory = history
        self.__saveSearchHistory()
        return

    def setFindHistory(self, history):
        " Sets the new find history and save it into a file "
        self.findHistory = history
        self.__saveSearchHistory()
        return

    def setReplaceHistory(self, whatHistory, toHistory):
        " Sets the new replace history and save it into a file "
        self.findHistory = whatHistory
        self.replaceHistory = toHistory
        self.__saveSearchHistory()
        return

    def setTabsStatus(self, status):
        " Sets the new tabs status and save it into a file "
        self.tabsStatus = status
        self.__saveTabsStatus()
        return

    def setFindInFilesHistory(self, what, dirs, masks):
        " Sets the new lists and save them into a file "
        self.findFilesWhat = what
        self.findFilesDirs = dirs
        self.findFilesMasks = masks
        self.__saveFindFiles()
        return

    def setFindClassHistory(self, history):
        " Sets the new history and saves it into a file "
        self.findClassHistory = history
        self.__saveFindObjects()
        return

    def setFindFuncHistory(self, history):
        " Sets the new history and saves it into a file "
        self.findFuncHistory = history
        self.__saveFindObjects()
        return

    def setFindGlobalHistory(self, history):
        " Sets the new history and saves it into a file "
        self.findGlobalHistory = history
        self.__saveFindObjects()
        return

    def updateProperties(self, scriptName, importDirs, creationDate, author,
                         lic, copy_right, version, email, description):
        " Updates the project properties "

        if self.scriptName == scriptName and \
           self.creationDate == creationDate and \
           self.author == author and \
           self.license == lic and \
           self.copyright == copy_right and \
           self.version == version and \
           self.email == email and \
           self.description == description and \
           self.importDirs == importDirs:
            # No real changes
            return

        self.importDirs = importDirs
        self.scriptName = scriptName
        self.creationDate = creationDate
        self.author = author
        self.license = lic
        self.copyright = copy_right
        self.version = version
        self.email = email
        self.description = description
        self.saveProject()
        self.projectChanged.emit(self.Properties)
        return

    def onProjectFileUpdated(self):
        " Called when a project file is updated via direct editing "

        scriptName, importDirs, \
        creationDate, author, \
        lic, copy_right, \
        description, version, \
        email, projectUuid = getProjectProperties( self.fileName )

        self.importDirs = importDirs
        self.scriptName = scriptName
        self.creationDate = creationDate
        self.author = author
        self.license = lic
        self.copyright = copy_right
        self.version = version
        self.email = email
        self.description = description

        # no need to save, but signal just in case
        self.projectChanged.emit(self.Properties)
        return

    def isLoaded(self):
        " returns True if a project is loaded "
        return self.fileName != ""

    def getProjectDir(self):
        " Provides an absolute path to the project dir "
        if not self.isLoaded():
            return ""
        return os.path.dirname(realpath(self.fileName)) + sep

    def getProjectScript(self):
        " Provides the project script file name "
        if not self.isLoaded():
            return ""
        if self.scriptName == "":
            return ""
        if os.path.isabs(self.scriptName):
            return self.scriptName
        return os.path.normpath(self.getProjectDir() + self.scriptName)

    def addRecentFile(self, path):
        " Adds a single recent file. True if a new file was inserted. "
        if path in self.recentFiles:
            self.recentFiles.remove(path)
            self.recentFiles.insert(0, path)
            self.__saveRecentFiles()
            return False
        self.recentFiles.insert(0, path)
        self.__saveRecentFiles()
        if len(self.recentFiles) > 32:
            self.recentFiles = self.recentFiles[0:32]
        self.emit(SIGNAL('recentFilesChanged'))
        return True

    def removeRecentFile(self, path):
        " Removes a single recent file "
        if path in self.recentFiles:
            self.recentFiles.remove(path)
            self.__saveRecentFiles()
        return

    def addExceptionFilter(self, excptType):
        " Adds a new ignored exception type "
        if excptType not in self.ignoredExcpt:
            self.ignoredExcpt.append(excptType)
            self.__saveIgnoredExcpt()
        return

    def deleteExceptionFilter(self, excptType):
        " Remove ignored exception type "
        if excptType in self.ignoredExcpt:
            self.ignoredExcpt.remove(excptType)
            self.__saveIgnoredExcpt()
        return

    def setExceptionFilters(self, newFilters):
        " Sets the new filters "
        self.ignoredExcpt = newFilters
        self.__saveIgnoredExcpt()
        return

    def addBreakpoint(self, bpointStr):
        " Adds serialized breakpoint "
        if bpointStr not in self.breakpoints:
            self.breakpoints.append(bpointStr)
            self.__saveBreakpoints()
        return

    def deleteBreakpoint(self, bpointStr):
        " Deletes serialized breakpoint "
        if bpointStr in self.breakpoints:
            self.breakpoints.remove(bpointStr)
            self.__saveBreakpoints()
        return

    def setBreakpoints(self, bpointStrList):
        " Sets breakpoints list "
        self.breakpoints = bpointStrList
        self.__saveBreakpoints()
        return

    def addWatchpoint(self, wpointStr):
        " Adds serialized watchpoint "
        if wpointStr not in self.watchpoints:
            self.watchpoints.append(wpointStr)
            self.__saveWatchpoints()
        return

    def deleteWatchpoint(self, wpointStr):
        " Deletes serialized watchpoint "
        if wpointStr in self.watchpoints:
            self.watchpoints.remove(wpointStr)
            self.__saveWatchpoints()
        return

    def setWatchpoints(self, wpointStrList):
        " Sets watchpoints list "
        self.watchpoints = wpointStrList
        self.__saveWatchpoints()
        return
Esempio n. 6
0
class rename:

    def __init__(self, project_path=None, module_path=None):
        """ initialize using project path and optional sub module path to limit rename scope """
        if project_path:
            self.project = Project(project_path)
        self.module_path = module_path
        self.words = set()
        self.files = []
        self.namemap = {}
        self.classname_regex = re.compile('class\s+(?P<name>[a-zA-Z0-9_]+)')
        self.methodname_regex = re.compile('def\s+(?P<name>[a-zA-Z0-9_]+)')

    def load_words(self):
        """ load all known words """
        with open('words') as f:
            for line in f:
                self.words.add(line.strip().lower())

    def load_dict(self, dictionary_path):
        """ load a custom dict with new (recognized) and restricted (unrecognized) words """
        doc = etree.parse(dictionary_path)
        for recognized in doc.xpath('/dictionary/recognized/word'):
            self.words.add(recognized.text)
        for unrecognized in doc.xpath('/dictionary/unrecognized/word'):
            if unrecognized.text in self.words:
                self.words.remove(unrecognized.text)

    def wash_word(self, string):
        """ clean up word by separating prefix, suffix and word without underscores """
        prefix = string[:string.find(string.lstrip('_'))]
        suffix = string[len(string.rstrip('_')):]
        word = string.lower().replace('_', '')
        return (prefix, word, suffix)

    def find_word(self, string, index):
        """ find the longest word from index """
        word = ''
        i = index + 1
        while i <= len(string):
            if string[index:i] in self.words:
                word = string[index:i]
            i += 1
        return word

    def reverse_find_word(self, string, index):
        """ backwards find the longest word from index """
        word = ''
        i = index - 1
        while i >= 0:
            if string[i:index] in self.words:
                word = string[i:index]
            i -= 1
        return word

    def find_words(self, string, index=-1):
        """ find all known words in a string """
        words = []
        if index == -1:
            index = len(string)
        if index == 0:
            return words
        word = self.reverse_find_word(string, index)
        if word:
            words.insert(0, word)
            index -= len(word)
        else:
            index -= 1
        w = self.find_words(string, index)
        w.extend(words)
        words = w
        #words.extend(self.find_words(string, index))
        return words

    def rename(self, string):
        """ rename string to PEP8 standard """
        index = 0
        last_index = 0
        new_name = ''
        prefix, old_name, suffix = self.wash_word(string)
        for word in self.find_words(old_name):
            index = old_name.find(word, index)
            if last_index != index:
                new_name += old_name[last_index: index]
            if len(new_name) > 0:
                new_name += '_'
            new_name += word
            index += len(word)
            last_index = index
        if last_index != len(old_name):
            if len(new_name) > 0:
                new_name += '_'
            new_name += old_name[last_index:]
        return '%s%s%s' % (prefix, new_name, suffix)

    def index_file(self, content):
        """ get all indexes for methods to rename in context
            return list of old name, position and new name """
        index = 0
        methods = []
        running = True
        while running:
            method = self.methodname_regex.search(content, index)
            if method:
                old_name = method.group('name')
                pos = method.start() + method.string[method.start():].find(old_name)
                new_name = self.rename(old_name)
                if old_name != new_name:
                    methods.append((old_name, pos, new_name))
                index = pos + len(old_name)
            else:
                running = False
        return methods

    def get_files(self):
        """ iterator for all valid project files """
        for file_resource in self.project.get_files():
            if self.module_path and not self.module_path in file_resource.real_path:
                continue
            yield file_resource

    def dry_run(self):
        """ list all methods to be renamed without updating any files """
        for file_resource in self.get_files():
            methods = self.index_file(file_resource.read())
            print('%s' % file_resource.path)
            for method in methods:
                print('    %s:%d->%s' % (method[0], method[1], method[2]))

    def refactor(self):
        """ renames all methods to PEP8 standard """
        for file_resource in self.get_files():
            while True:
                self.project.validate()
                methods = self.index_file(file_resource.read())
                if len(methods) == 0:
                    break
                method = methods[0]
                old_name = method[0]
                pos = method[1]
                new_name = method[2]
                print('rename: %s:%d->%s' % (old_name, pos, new_name))
                changes = Rename(self.project, file_resource, pos).get_changes(new_name)
                self.project.do(changes)
Esempio n. 7
0
class Refactor(QtGui.QWidget):

    def __init__(self, editorTabWidget, busyWidget, parent=None):
        QtGui.QWidget.__init__(self, parent)

        self.editorTabWidget = editorTabWidget
        self.busyWidget = busyWidget
        self.root = editorTabWidget.pathDict["sourcedir"]
        ropeFolder = editorTabWidget.pathDict["ropeFolder"]

        prefs = {
            'ignored_resources': ['*.pyc', '*~', '.ropeproject',
                                  '.hg', '.svn', '_svn', '.git',
                                  '__pycache__'],
            'python_files': ['*.py'],
            'save_objectdb': True,
            'compress_objectdb': False,
            'automatic_soa': True,
            'soa_followed_calls': 0,
            'perform_doa': True,
            'validate_objectdb': True,
            'max_history_items': 32,
            'save_history': True,
            'compress_history': False,
            'indent_size': 4,
            'extension_modules': [
                "PyQt4", "PyQt4.QtGui", "QtGui", "PyQt4.QtCore", "QtCore",
                "PyQt4.QtScript", "QtScript", "os.path", "numpy", "scipy", "PIL",
                "OpenGL", "array", "audioop", "binascii", "cPickle", "cStringIO",
                "cmath", "collections", "datetime", "errno", "exceptions", "gc",
                "imageop", "imp", "itertools", "marshal", "math", "mmap", "msvcrt",
                "nt", "operator", "os", "parser", "rgbimg", "signal", "strop", "sys",
                "thread", "time", "wx", "wxPython", "xxsubtype", "zipimport", "zlib"
            ],
            'import_dynload_stdmods': True,
            'ignore_syntax_errors': True,
            'ignore_bad_imports': True
        }

        self.ropeProject = Project(
            projectroot=self.root, ropefolder=ropeFolder, **prefs)
        self.ropeProject.prefs.add('python_path', 'c:/Python33')
        self.ropeProject.prefs.add('source_folders', 'c:/Python33/Lib')
        self.ropeProject.validate()

        self.noProject = Project(projectroot="temp", ropefolder=None)

        self.findThread = FindUsageThread()
        self.findThread.finished.connect(self.findOccurrencesFinished)

        self.renameThread = RenameThread()
        self.renameThread.finished.connect(self.renameFinished)

        self.inlineThread = InlineThread()
        self.inlineThread.finished.connect(self.inlineFinished)

        self.localToFieldThread = LocalToFieldThread()
        self.localToFieldThread.finished.connect(self.localToFieldFinished)

        self.moduleToPackageThread = ModuleToPackageThread()
        self.moduleToPackageThread.finished.connect(
            self.moduleToPackageFinished)

        self.createActions()

        self.refactorMenu = QtGui.QMenu("Refactor")
        self.refactorMenu.addAction(self.renameAttributeAct)
        self.refactorMenu.addAction(self.inlineAct)
        self.refactorMenu.addAction(self.localToFieldAct)

    def close(self):
        self.ropeProject.close()

    def createActions(self):
        self.findDefAct = \
            QtGui.QAction(
                QtGui.QIcon(os.path.join("Resources", "images", "map_marker")),
                "Go-to Definition", self, statusTip="Go-to Definition",
                triggered=self.findDefinition)

        self.findOccurrencesAct = \
            QtGui.QAction("Usages", self, statusTip="Usages",
                          triggered=self.findOccurrences)

        self.moduleToPackageAct = \
            QtGui.QAction(
                "Convert to Package", self, statusTip="Convert to Package",
                triggered=self.moduleToPackage)

        self.renameModuleAct = \
            QtGui.QAction("Rename", self, statusTip="Rename",
                          triggered=self.renameModule)

        self.renameAttributeAct = \
            QtGui.QAction("Rename", self, statusTip="Rename",
                          triggered=self.renameAttribute)

        self.inlineAct = \
            QtGui.QAction("Inline", self, statusTip="Inline",
                          triggered=self.inline)

        self.localToFieldAct = \
            QtGui.QAction("Local-to-Field", self, statusTip="Local-to-Field",
                          triggered=self.localToField)

        self.getDocAct = \
            QtGui.QAction("Documentation", self, statusTip="Documentation",
                          triggered=self.getDoc)

        self.getCallTipAct = \
            QtGui.QAction("CallTip", self, statusTip="CallTip",
                          triggered=self.getCallTip)

    def renameModule(self):
        index = self.editorTabWidget.currentIndex()
        moduleName = self.editorTabWidget.tabText(index)
        moduleName = os.path.splitext(moduleName)[0]
        newName = GetName("Rename", moduleName, self)
        project = self.getProject()
        if newName.accepted:
            saved = self.editorTabWidget.saveProject()
            if saved:
                path = self.editorTabWidget.getEditorData("filePath")
                self.renameThread.rename(newName.text, path, project, None)
                self.busyWidget.showBusy(True, "Renaming... please wait!")

    def renameAttribute(self):
        objectName = self.editorTabWidget.get_current_word()
        newName = GetName("Rename", objectName, self)
        if newName.accepted:
            project = self.getProject()
            saved = self.editorTabWidget.saveProject()
            if saved:
                offset = self.getOffset()
                path = self.editorTabWidget.getEditorData("filePath")
                self.renameThread.rename(newName.text, path, project, offset)
                self.busyWidget.showBusy(True, "Renaming... please wait!")

    def renameFinished(self):
        self.busyWidget.showBusy(False)
        if self.renameThread.error is not None:
            message = QtGui.QMessageBox.warning(self, "Failed Rename",
                                                self.renameThread.error)
            return
        if self.renameThread.offset is None:
            # filename has been changed
            oldPath = self.editorTabWidget.getEditorData("filePath")
            ext = os.path.splitext(oldPath)[1]
            newPath = os.path.join(os.path.dirname(oldPath),
                                   self.renameThread.new_name + ext)
            self.editorTabWidget.updateEditorData("filePath", newPath)
        else:
            if len(self.renameThread.changedFiles) > 0:
                self.editorTabWidget.reloadModules(
                    self.renameThread.changedFiles)

    def inline(self):
        offset = self.getOffset()
        path = self.editorTabWidget.getEditorData("filePath")
        project = self.getProject()
        resource = project.get_file(path)
        saved = self.editorTabWidget.saveProject()
        if saved:
            self.inlineThread.inline(project, resource, offset)
            self.busyWidget.showBusy(True, "Inlining... please wait!")

    def inlineFinished(self):
        self.busyWidget.showBusy(False)
        if self.inlineThread.error is not None:
            message = QtGui.QMessageBox.warning(self, "Failed Inline",
                                                self.inlineThread.error)
            return
        if len(self.inlineThread.changedFiles) > 0:
            self.editorTabWidget.reloadModules(self.inlineThread.changedFiles)

    def localToField(self):
        offset = self.getOffset()
        path = self.editorTabWidget.getEditorData("filePath")
        project = self.getProject()
        resource = project.get_file(path)
        saved = self.editorTabWidget.saveProject()
        if saved:
            self.localToFieldThread.convert(project, resource, offset)
            self.busyWidget.showBusy(
                True, "Converting Local to Field... please wait!")

    def localToFieldFinished(self):
        self.busyWidget.showBusy(False)
        if self.localToFieldThread.error is not None:
            message = QtGui.QMessageBox.warning(self, "Failed Local-to-Field",
                                                self.localToFieldThread.error)
            return
        if len(self.localToFieldThread.changedFiles) > 0:
            self.editorTabWidget.reloadModules(
                self.localToFieldThread.changedFiles)

    def findDefinition(self):
        saved = self.editorTabWidget.saveProject()
        if saved:
            offset = self.getOffset()
            path = self.editorTabWidget.getEditorData("filePath")
            project = self.getProject()
            resource = project.get_file(path)
            try:
                result = find_definition(project,
                                         self.editorTabWidget.getSource(), offset, resource)
                if result is None:
                    self.editorTabWidget.showNotification(
                        "No definition found.")
                else:
                    start, end = result.region
                    offset = result.offset
                    line = result.lineno
                    result_path = result.resource.path
                    sourcePath = self.editorTabWidget.pathDict["sourcedir"]
                    if not os.path.isabs(result_path):
                        result_path = os.path.join(sourcePath, result_path)
                    if os.path.samefile(result_path, path):
                        pass
                    else:
                        self.editorTabWidget.loadfile(result_path)
                    editor = self.editorTabWidget.focusedEditor()
                    start = editor.lineIndexFromPosition(start)
                    end = editor.lineIndexFromPosition(end)
                    editor.setSelection(start[0], start[1], end[0], end[1])
                    editor.ensureLineVisible(line - 1)
            except Exception as err:
                self.editorTabWidget.showNotification(str(err))

    def moduleToPackage(self):
        path = self.editorTabWidget.getEditorData("filePath")
        project = self.getProject()
        saved = self.editorTabWidget.saveProject()
        if saved:
            self.moduleToPackageThread.convert(path, project)
            self.busyWidget.showBusy(True, "Converting... please wait!")

    def moduleToPackageFinished(self):
        self.busyWidget.showBusy(False)
        if self.moduleToPackageThread.error is not None:
            message = QtGui.QMessageBox.warning(self, "Failed to convert",
                                                self.moduleToPackageThread.error)

    def findOccurrences(self):
        offset = self.getOffset()
        project = self.getProject()
        saved = self.editorTabWidget.saveProject()
        if saved:
            path = self.editorTabWidget.getEditorData("filePath")
            self.objectName = self.editorTabWidget.get_current_word()
            self.findThread.find(path, project, offset)
            self.busyWidget.showBusy(True, "Finding usages... please wait!")

    def findOccurrencesFinished(self):
        self.busyWidget.showBusy(False)
        if self.findThread.error is not None:
            self.editorTabWidget.showNotification(self.findThread.error)
            return
        if len(self.findThread.itemsDict) > 0:
            foundList = []
            for parent, lines in self.findThread.itemsDict.items():
                parentItem = QtGui.QTreeWidgetItem()
                parentItem.setForeground(0, QtGui.QBrush(
                    QtGui.QColor("#003366")))
                parentItem.setText(0, parent)
                for line in lines:
                    childItem = QtGui.QTreeWidgetItem()
                    childItem.setText(0, str(line))
                    childItem.setFirstColumnSpanned(True)
                    parentItem.addChild(childItem)
                    foundList.append(parentItem)
            usageDialog = UsageDialog(
                self.editorTabWidget, "Usages: " + self.objectName, foundList, self)
        else:
            self.editorTabWidget.showNotification("No usages found.")

    def getOffset(self):
        return self.editorTabWidget.getOffset()

    def get_absolute_coordinates(self):
        editor = self.editorTabWidget.focusedEditor()
        point = editor.get_absolute_coordinates()
        return point

    def getCompletions(self):
        offset = self.getOffset()
        project = self.getProject()
        proposals = codeassist.code_assist(project,
                                           self.editorTabWidget.getSource(), offset)
        proposals = codeassist.sorted_proposals(proposals)
        if len(proposals) > 0:
            cmpl = []
            for i in proposals:
                cmpl.append(str(i))

            return cmpl
        else:
            return []

    def getDoc(self, offset=None):
        if offset is None:
            offset = self.getOffset()
        project = self.getProject()
        try:
            doc = codeassist.get_doc(project,
                                     self.editorTabWidget.getSource(), offset)
            return doc
        except Exception as err:
            return None

    def getCallTip(self, offset=None):
        if offset is None:
            offset = self.getOffset()
        project = self.getProject()
        try:
            calltip = codeassist.get_calltip(project,
                                             self.editorTabWidget.getSource(), offset)

            return calltip
        except Exception as err:
            return None

    def getProject(self):
        path = self.editorTabWidget.getEditorData("filePath")
        if path is None:
            return self.noProject
        if path.startswith(self.editorTabWidget.pathDict["sourcedir"]):
            return self.ropeProject
        else:
            return self.noProject
Esempio n. 8
0
class Workspace(object):

    M_PUBLISH_DIAGNOSTICS = 'textDocument/publishDiagnostics'
    M_APPLY_EDIT = 'workspace/applyEdit'
    M_SHOW_MESSAGE = 'window/showMessage'
    PRELOADED_MODULES = get_preferred_submodules()

    def __init__(self, root_uri, lang_server=None):
        self._root_uri = root_uri
        self._root_uri_scheme = uris.urlparse(self._root_uri)[0]
        self._root_path = uris.to_fs_path(self._root_uri)
        self._docs = {}
        self._lang_server = lang_server

        # Whilst incubating, keep private
        self.__rope = Project(self._root_path)
        self.__rope.prefs.set('extension_modules', self.PRELOADED_MODULES)

    @property
    def _rope(self):
        # TODO: we could keep track of dirty files and validate only those
        self.__rope.validate()
        return self.__rope

    @property
    def documents(self):
        return self._docs

    @property
    def root_path(self):
        return self._root_path

    @property
    def root_uri(self):
        return self._root_uri

    def is_local(self):
        return (self._root_uri_scheme == '' or self._root_uri_scheme == 'file') and os.path.exists(self._root_path)

    def get_document(self, doc_uri):
        return self._docs[doc_uri]

    def put_document(self, doc_uri, content, version=None):
        path = uris.to_fs_path(doc_uri)
        self._docs[doc_uri] = Document(
            doc_uri, content, sys_path=self.syspath_for_path(path), version=version, rope=self._rope
        )

    def rm_document(self, doc_uri):
        self._docs.pop(doc_uri)

    def update_document(self, doc_uri, change, version=None):
        self._docs[doc_uri].apply_change(change)
        self._docs[doc_uri].version = version

    def apply_edit(self, edit, on_result=None, on_error=None):
        return self._lang_server.call(
            self.M_APPLY_EDIT, {'edit': edit},
            on_result=on_result, on_error=on_error
        )

    def publish_diagnostics(self, doc_uri, diagnostics):
        params = {'uri': doc_uri, 'diagnostics': diagnostics}
        self._lang_server.notify(self.M_PUBLISH_DIAGNOSTICS, params)

    def show_message(self, message, msg_type=lsp.MessageType.Info):
        params = {'type': msg_type, 'message': message}
        self._lang_server.notify(self.M_SHOW_MESSAGE, params)

    def syspath_for_path(self, path):
        """Construct a sensible sys path to use for the given file path.

        Since the workspace root may not be the root of the Python project we instead
        append the closest parent directory containing a setup.py file.
        """
        files = _utils.find_parents(self._root_path, path, ['setup.py']) or []
        path = [os.path.dirname(setup_py) for setup_py in files]

        # Check to see if we're in a virtualenv
        if 'VIRTUAL_ENV' in os.environ:
            log.info("Using virtualenv %s", os.environ['VIRTUAL_ENV'])
            path.extend(jedi.evaluate.sys_path.get_venv_path(os.environ['VIRTUAL_ENV']))
        else:
            path.extend(sys.path)
        return path
Esempio n. 9
0
class Refactor(QtGui.QWidget):
    def __init__(self, editorTabWidget, busyWidget, parent=None):
        QtGui.QWidget.__init__(self, parent)

        self.editorTabWidget = editorTabWidget
        self.busyWidget = busyWidget
        self.root = editorTabWidget.pathDict["sourcedir"]
        ropeFolder = editorTabWidget.pathDict["ropeFolder"]

        prefs = {
            'ignored_resources': [
                '*.pyc', '*~', '.ropeproject', '.hg', '.svn', '_svn', '.git',
                '__pycache__'
            ],
            'python_files': ['*.py'],
            'save_objectdb':
            True,
            'compress_objectdb':
            False,
            'automatic_soa':
            True,
            'soa_followed_calls':
            0,
            'perform_doa':
            True,
            'validate_objectdb':
            True,
            'max_history_items':
            32,
            'save_history':
            True,
            'compress_history':
            False,
            'indent_size':
            4,
            'extension_modules': [
                "PyQt4", "PyQt4.QtGui", "QtGui", "PyQt4.QtCore", "QtCore",
                "PyQt4.QtScript", "QtScript", "os.path", "numpy", "scipy",
                "PIL", "OpenGL", "array", "audioop", "binascii", "cPickle",
                "cStringIO", "cmath", "collections", "datetime", "errno",
                "exceptions", "gc", "imageop", "imp", "itertools", "marshal",
                "math", "mmap", "msvcrt", "nt", "operator", "os", "parser",
                "rgbimg", "signal", "strop", "sys", "thread", "time", "wx",
                "wxPython", "xxsubtype", "zipimport", "zlib"
            ],
            'import_dynload_stdmods':
            True,
            'ignore_syntax_errors':
            True,
            'ignore_bad_imports':
            True
        }

        self.ropeProject = Project(projectroot=self.root,
                                   ropefolder=ropeFolder,
                                   **prefs)
        self.ropeProject.prefs.add('python_path', 'c:/Python33')
        self.ropeProject.prefs.add('source_folders', 'c:/Python33/Lib')
        self.ropeProject.validate()

        self.noProject = Project(projectroot="temp", ropefolder=None)

        self.findThread = FindUsageThread()
        self.findThread.finished.connect(self.findOccurrencesFinished)

        self.renameThread = RenameThread()
        self.renameThread.finished.connect(self.renameFinished)

        self.inlineThread = InlineThread()
        self.inlineThread.finished.connect(self.inlineFinished)

        self.localToFieldThread = LocalToFieldThread()
        self.localToFieldThread.finished.connect(self.localToFieldFinished)

        self.moduleToPackageThread = ModuleToPackageThread()
        self.moduleToPackageThread.finished.connect(
            self.moduleToPackageFinished)

        self.createActions()

        self.refactorMenu = QtGui.QMenu("Refactor")
        self.refactorMenu.addAction(self.renameAttributeAct)
        self.refactorMenu.addAction(self.inlineAct)
        self.refactorMenu.addAction(self.localToFieldAct)

    def close(self):
        self.ropeProject.close()

    def createActions(self):
        self.findDefAct = \
            QtGui.QAction(
                QtGui.QIcon(os.path.join("Resources", "images", "map_marker")),
                "Go-to Definition", self, statusTip="Go-to Definition",
                triggered=self.findDefinition)

        self.findOccurrencesAct = \
            QtGui.QAction("Usages", self, statusTip="Usages",
                          triggered=self.findOccurrences)

        self.moduleToPackageAct = \
            QtGui.QAction(
                "Convert to Package", self, statusTip="Convert to Package",
                triggered=self.moduleToPackage)

        self.renameModuleAct = \
            QtGui.QAction("Rename", self, statusTip="Rename",
                          triggered=self.renameModule)

        self.renameAttributeAct = \
            QtGui.QAction("Rename", self, statusTip="Rename",
                          triggered=self.renameAttribute)

        self.inlineAct = \
            QtGui.QAction("Inline", self, statusTip="Inline",
                          triggered=self.inline)

        self.localToFieldAct = \
            QtGui.QAction("Local-to-Field", self, statusTip="Local-to-Field",
                          triggered=self.localToField)

        self.getDocAct = \
            QtGui.QAction("Documentation", self, statusTip="Documentation",
                          triggered=self.getDoc)

        self.getCallTipAct = \
            QtGui.QAction("CallTip", self, statusTip="CallTip",
                          triggered=self.getCallTip)

    def renameModule(self):
        index = self.editorTabWidget.currentIndex()
        moduleName = self.editorTabWidget.tabText(index)
        moduleName = os.path.splitext(moduleName)[0]
        newName = GetName("Rename", moduleName, self)
        project = self.getProject()
        if newName.accepted:
            saved = self.editorTabWidget.saveProject()
            if saved:
                path = self.editorTabWidget.getEditorData("filePath")
                self.renameThread.rename(newName.text, path, project, None)
                self.busyWidget.showBusy(True, "Renaming... please wait!")

    def renameAttribute(self):
        objectName = self.editorTabWidget.get_current_word()
        newName = GetName("Rename", objectName, self)
        if newName.accepted:
            project = self.getProject()
            saved = self.editorTabWidget.saveProject()
            if saved:
                offset = self.getOffset()
                path = self.editorTabWidget.getEditorData("filePath")
                self.renameThread.rename(newName.text, path, project, offset)
                self.busyWidget.showBusy(True, "Renaming... please wait!")

    def renameFinished(self):
        self.busyWidget.showBusy(False)
        if self.renameThread.error is not None:
            message = QtGui.QMessageBox.warning(self, "Failed Rename",
                                                self.renameThread.error)
            return
        if self.renameThread.offset is None:
            # filename has been changed
            oldPath = self.editorTabWidget.getEditorData("filePath")
            ext = os.path.splitext(oldPath)[1]
            newPath = os.path.join(os.path.dirname(oldPath),
                                   self.renameThread.new_name + ext)
            self.editorTabWidget.updateEditorData("filePath", newPath)
        else:
            if len(self.renameThread.changedFiles) > 0:
                self.editorTabWidget.reloadModules(
                    self.renameThread.changedFiles)

    def inline(self):
        offset = self.getOffset()
        path = self.editorTabWidget.getEditorData("filePath")
        project = self.getProject()
        resource = project.get_file(path)
        saved = self.editorTabWidget.saveProject()
        if saved:
            self.inlineThread.inline(project, resource, offset)
            self.busyWidget.showBusy(True, "Inlining... please wait!")

    def inlineFinished(self):
        self.busyWidget.showBusy(False)
        if self.inlineThread.error is not None:
            message = QtGui.QMessageBox.warning(self, "Failed Inline",
                                                self.inlineThread.error)
            return
        if len(self.inlineThread.changedFiles) > 0:
            self.editorTabWidget.reloadModules(self.inlineThread.changedFiles)

    def localToField(self):
        offset = self.getOffset()
        path = self.editorTabWidget.getEditorData("filePath")
        project = self.getProject()
        resource = project.get_file(path)
        saved = self.editorTabWidget.saveProject()
        if saved:
            self.localToFieldThread.convert(project, resource, offset)
            self.busyWidget.showBusy(
                True, "Converting Local to Field... please wait!")

    def localToFieldFinished(self):
        self.busyWidget.showBusy(False)
        if self.localToFieldThread.error is not None:
            message = QtGui.QMessageBox.warning(self, "Failed Local-to-Field",
                                                self.localToFieldThread.error)
            return
        if len(self.localToFieldThread.changedFiles) > 0:
            self.editorTabWidget.reloadModules(
                self.localToFieldThread.changedFiles)

    def findDefinition(self):
        saved = self.editorTabWidget.saveProject()
        if saved:
            offset = self.getOffset()
            path = self.editorTabWidget.getEditorData("filePath")
            project = self.getProject()
            resource = project.get_file(path)
            try:
                result = find_definition(project,
                                         self.editorTabWidget.getSource(),
                                         offset, resource)
                if result is None:
                    self.editorTabWidget.showNotification(
                        "No definition found.")
                else:
                    start, end = result.region
                    offset = result.offset
                    line = result.lineno
                    result_path = result.resource.path
                    sourcePath = self.editorTabWidget.pathDict["sourcedir"]
                    if not os.path.isabs(result_path):
                        result_path = os.path.join(sourcePath, result_path)
                    if os.path.samefile(result_path, path):
                        pass
                    else:
                        self.editorTabWidget.loadfile(result_path)
                    editor = self.editorTabWidget.focusedEditor()
                    start = editor.lineIndexFromPosition(start)
                    end = editor.lineIndexFromPosition(end)
                    editor.setSelection(start[0], start[1], end[0], end[1])
                    editor.ensureLineVisible(line - 1)
            except Exception as err:
                self.editorTabWidget.showNotification(str(err))

    def moduleToPackage(self):
        path = self.editorTabWidget.getEditorData("filePath")
        project = self.getProject()
        saved = self.editorTabWidget.saveProject()
        if saved:
            self.moduleToPackageThread.convert(path, project)
            self.busyWidget.showBusy(True, "Converting... please wait!")

    def moduleToPackageFinished(self):
        self.busyWidget.showBusy(False)
        if self.moduleToPackageThread.error is not None:
            message = QtGui.QMessageBox.warning(
                self, "Failed to convert", self.moduleToPackageThread.error)

    def findOccurrences(self):
        offset = self.getOffset()
        project = self.getProject()
        saved = self.editorTabWidget.saveProject()
        if saved:
            path = self.editorTabWidget.getEditorData("filePath")
            self.objectName = self.editorTabWidget.get_current_word()
            self.findThread.find(path, project, offset)
            self.busyWidget.showBusy(True, "Finding usages... please wait!")

    def findOccurrencesFinished(self):
        self.busyWidget.showBusy(False)
        if self.findThread.error is not None:
            self.editorTabWidget.showNotification(self.findThread.error)
            return
        if len(self.findThread.itemsDict) > 0:
            foundList = []
            for parent, lines in self.findThread.itemsDict.items():
                parentItem = QtGui.QTreeWidgetItem()
                parentItem.setForeground(0,
                                         QtGui.QBrush(QtGui.QColor("#003366")))
                parentItem.setText(0, parent)
                for line in lines:
                    childItem = QtGui.QTreeWidgetItem()
                    childItem.setText(0, str(line))
                    childItem.setFirstColumnSpanned(True)
                    parentItem.addChild(childItem)
                    foundList.append(parentItem)
            usageDialog = UsageDialog(self.editorTabWidget,
                                      "Usages: " + self.objectName, foundList,
                                      self)
        else:
            self.editorTabWidget.showNotification("No usages found.")

    def getOffset(self):
        return self.editorTabWidget.getOffset()

    def get_absolute_coordinates(self):
        editor = self.editorTabWidget.focusedEditor()
        point = editor.get_absolute_coordinates()
        return point

    def getCompletions(self):
        offset = self.getOffset()
        project = self.getProject()
        proposals = codeassist.code_assist(project,
                                           self.editorTabWidget.getSource(),
                                           offset)
        proposals = codeassist.sorted_proposals(proposals)
        if len(proposals) > 0:
            cmpl = []
            for i in proposals:
                cmpl.append(str(i))

            return cmpl
        else:
            return []

    def getDoc(self, offset=None):
        if offset is None:
            offset = self.getOffset()
        project = self.getProject()
        try:
            doc = codeassist.get_doc(project, self.editorTabWidget.getSource(),
                                     offset)
            return doc
        except Exception as err:
            return None

    def getCallTip(self, offset=None):
        if offset is None:
            offset = self.getOffset()
        project = self.getProject()
        try:
            calltip = codeassist.get_calltip(project,
                                             self.editorTabWidget.getSource(),
                                             offset)

            return calltip
        except Exception as err:
            return None

    def getProject(self):
        path = self.editorTabWidget.getEditorData("filePath")
        if path is None:
            return self.noProject
        if path.startswith(self.editorTabWidget.pathDict["sourcedir"]):
            return self.ropeProject
        else:
            return self.noProject
Esempio n. 10
0
def find_usages(_, main_project, other_projects, file_path, offset):
    """
    Find usages of symbol under cursor.
    """
    try:
        occurrences = []
        if other_projects:
            for path in [main_project] + other_projects:
                prj = Project(path,
                              ropefolder=api.project.FOLDER,
                              fscommands=FileSystemCommands())
                prj.validate()
                mod = libutils.path_to_resource(prj, file_path)
                occurrences += find_occurrences(prj,
                                                mod,
                                                offset,
                                                unsure=False,
                                                in_hierarchy=True)
                prj.close()
        else:
            prj = Project(main_project,
                          ropefolder=api.project.FOLDER,
                          fscommands=FileSystemCommands())
            prj.validate()
            mod = libutils.path_to_resource(prj, file_path)
            occurrences = find_occurrences(prj,
                                           mod,
                                           offset,
                                           unsure=False,
                                           in_hierarchy=True)
        # transform results to a serialisable list of usages that is ready
        # to use by the find_results widget.
        occurrences_map = {}
        for location in occurrences:
            path = location.resource.real_path
            lineno = location.lineno - 1
            # convert file region to line region
            content = location.resource.read()
            offset = location.offset
            char = content[offset]
            while char != '\n':  # find start of line
                offset -= 1
                char = content[offset]
            # update offsets
            start = location.offset - offset - 1
            end = location.region[1] - offset - 1
            line_text = content.splitlines()[lineno]
            data = (lineno, line_text, [(start, end)])
            if path not in occurrences_map:
                occurrences_map[path] = [data]
            else:
                occurrences_map[path].append(data)
        results = []
        for key, value in occurrences_map.items():
            results.append((key, value))
        results = sorted(results, key=lambda x: x[0])
        return results
    except RopeError as e:
        error = RefactoringError()
        error.exc = str(e)
        error.traceback = traceback.format_exc()
        error.critical = False
        return error
    except Exception as e:
        error = RefactoringError()
        error.exc = str(e)
        error.traceback = traceback.format_exc()
        error.critical = True
        return error
Esempio n. 11
0
class PyRefactor(plugins.WorkspacePlugin):
    """
    Adds some refactoring capabilities to the IDE (using the rope library).

    Supported operations:
        - Rename
        - Extract method
        - Extract variable
        - Find occurrences
        - Organize imports

    """
    def activate(self):
        self._preview_dock = None
        self._occurrences_dock = None
        self._occurrences_results = None
        self._review_widget = None
        api.signals.connect_slot(api.signals.CURRENT_PROJECT_CHANGED,
                                 self._on_current_project_changed)
        api.signals.connect_slot(api.signals.EDITOR_CREATED,
                                 self._on_editor_created)
        api.signals.connect_slot(api.signals.CURRENT_EDITOR_CHANGED,
                                 self._update_edit_actions_state)
        path = api.project.get_current_project()
        self._main_project = Project(path,
                                     ropefolder=api.project.FOLDER,
                                     fscommands=FileSystemCommands())
        self._main_project.validate()
        api.signals.connect_slot(api.signals.DOCUMENT_SAVED,
                                 self._on_document_saved)

    def close(self):
        self._main_project.close()

    def rename(self):
        """
        Renames word under cursor.
        """
        editor = api.editor.get_current_editor()
        if editor is None:
            return
        editor.file.save()
        assert isinstance(editor, PyCodeEdit)
        module = libutils.path_to_resource(self._main_project,
                                           editor.file.path)
        self._main_project.validate()
        cursor_position = self._get_real_position(
            editor.textCursor().position())
        try:
            renamer = Rename(self._main_project, module, cursor_position)
        except RopeError:
            return
        if not renamer.get_old_name():
            return
        preview, replacement = DlgRope.rename(self.main_window,
                                              renamer.get_old_name())
        if preview is None and replacement is None:
            return
        multiproj = self._has_multiple_projects()
        other_projects = self._get_other_projects()
        main_project = self._main_project
        self._preview = preview
        api.tasks.start(_('Refactoring: rename'),
                        rename_symbol,
                        self._on_changes_available,
                        args=(main_project, multiproj, other_projects,
                              editor.file.path, cursor_position, replacement),
                        cancellable=True,
                        use_thread=True)

    def organise_imports(self):
        editor = api.editor.get_current_editor()
        api.editor.save_all_editors()
        if editor is None:
            return
        self._preview = True
        file_path = editor.file.path
        project = self.get_project_for_file(file_path)
        if project:
            api.tasks.start(_('Refactoring: organize imports'),
                            organize_imports,
                            self._on_changes_available,
                            args=(project, file_path),
                            cancellable=True,
                            use_thread=True)

    def extract_method(self):
        """
        Extracts a method from the selected text (if possible, otherwise a
        warning message will appear).
        """
        api.editor.save_all_editors()
        self._main_project.validate()
        editor = api.editor.get_current_editor()
        if editor is None or not editor.textCursor().hasSelection():
            return
        editor.file.save()
        if not editor.textCursor().hasSelection():
            TextHelper(editor).select_whole_line()
        start = self._get_real_position(editor.textCursor().selectionStart())
        end = self._get_real_position(editor.textCursor().selectionEnd())
        preview, replacement = DlgRope.extract_method(self.main_window)
        if preview is None and replacement is None:
            return
        multiproj = self._has_multiple_projects()
        other_projects = self._get_other_projects()
        main_project = self._main_project
        cursor_position_start = start
        cursor_position_end = end
        replacement = replacement
        self._preview = preview
        api.tasks.start(_('Refactoring: extract method'),
                        extract_method,
                        self._on_changes_available,
                        args=(multiproj, main_project, other_projects,
                              editor.file.path, cursor_position_start,
                              cursor_position_end, replacement),
                        cancellable=True,
                        use_thread=True)

    def extract_variable(self):
        """
        Extracts a variable from the selected statement (if possible).
        """
        api.editor.save_all_editors()
        self._main_project.validate()
        editor = api.editor.get_current_editor()
        if editor is None or not editor.textCursor().hasSelection():
            return
        editor.file.save()
        if not editor.textCursor().hasSelection():
            TextHelper(editor).select_whole_line()
        start = self._get_real_position(editor.textCursor().selectionStart())
        end = self._get_real_position(editor.textCursor().selectionEnd())
        preview, replacement = DlgRope.extract_variable(self.main_window)
        if preview is None and replacement is None:
            return
        multiproj = self._has_multiple_projects()
        other_projects = self._get_other_projects()
        main_project = self._main_project
        cursor_position_start = start
        cursor_position_end = end
        replacement = replacement
        self._preview = preview
        api.tasks.start(_('Refactoring: extract variable'),
                        extract_variable,
                        self._on_changes_available,
                        args=(multiproj, main_project, other_projects,
                              editor.file.path, cursor_position_start,
                              cursor_position_end, replacement),
                        cancellable=True,
                        use_thread=True)

    def find_usages(self):
        """
        Find all usages of the word under cursor.
        """
        api.editor.save_all_editors()
        if api.editor.get_current_editor() is None:
            return
        file_path = api.editor.get_current_path()
        api.editor.save_current_editor()
        offset = self._get_real_position(
            api.editor.get_current_editor().textCursor().position())
        self._occurrence_to_search = worder.get_name_at(
            libutils.path_to_resource(self._main_project, file_path), offset)
        main_project = api.project.get_current_project()
        other_projects = self._get_other_projects(path_only=True)
        file_path = file_path
        api.tasks.start(_('Refactoring: find usages'),
                        find_usages,
                        self._on_find_usages_finished,
                        args=(main_project, other_projects, file_path, offset),
                        cancellable=True,
                        use_thread=True)

    def get_project_for_file(self, path):
        for prj in [self._main_project] + self._get_other_projects():
            p = prj.address + os.path.sep
            if p in path:
                return prj
        return None

    @staticmethod
    def clean_changes(pending_changes):
        if hasattr(pending_changes, '__iter__'):
            cleaned = []
            for prj, changeset in pending_changes:
                for change in changeset.changes:
                    if isinstance(change.resource.project, NoProject):
                        break
                else:
                    cleaned.append((prj, changeset))
            return cleaned
        return pending_changes

    def _on_changes_available(self, changes):
        if isinstance(changes, RefactoringError):
            api.events.post(RefactoringErrorEvent(changes), False)
            return
        _, self._pending_changes = changes

        self._pending_changes = self.clean_changes(self._pending_changes)

        if self._pending_changes is None:
            return
        if self._preview and self._pending_changes:
            self._create_review_dock()
            self._update_preview()
        else:
            self._refactor()

    def _on_find_usages_finished(self, results):
        if results is None:
            # todo: show no results notification
            return
        if isinstance(results, RefactoringError):
            api.events.post(RefactoringErrorEvent(results), False)
            return
        if self._occurrences_results is None:
            self._create_occurrences_dock()
        else:
            self._occurrences_dock.show()
            self._occurrences_dock.button.show()
            self._occurrences_dock.button.action.setVisible(True)
        self._occurrences_results.show_results(results,
                                               self._occurrence_to_search)

    @staticmethod
    def apply_preferences():
        for editor in api.editor.get_all_editors(True):
            if isinstance(editor, PyCodeEdit):
                actions = editor.refactoring_actions
                items = [('usages', 'Find usages', _('Find usages'), 'Alt+F7'),
                         ('rename', 'Refactor: rename', _('Refactor: rename'),
                          'Shift+F6'),
                         ('extract_method', 'Refactor: extract method',
                          _('Refactor: extract method'), 'Ctrl+Alt+M'),
                         ('extract_var', 'Refactor: extract variable',
                          _('Refactor: extract variable'), 'Ctrl+Alt+V'),
                         ('imports', 'Refactor: organize imports',
                          _('Refactor: organize imports'), 'Alt+F8')]
                for id, name, text, default in items:
                    actions[id].setShortcut(
                        api.shortcuts.get(name, text, default))

                actions['extract_var'].setIcon(special_icons.variable_icon())

    def create_refactor_menu(self, editor):
        """
        Creates the refactor menu; this menu will appear in the menu bar of the
        main window and in the context menu of any python editor.
        """
        mnu_refactor = QtWidgets.QMenu(editor)
        mnu_refactor.setTitle(_('Refactor'))
        mnu_refactor.setIcon(QtGui.QIcon.fromTheme('edit-rename'))

        action_find_usages = mnu_refactor.addAction(_('Find usages'))
        action_find_usages.setToolTip(
            _('Find occurrences of symbol under cursor'))
        action_find_usages.setIcon(QtGui.QIcon.fromTheme('edit-find'))
        action_find_usages.setShortcut(
            api.shortcuts.get('Find usages', _('Find usages'), 'Alt+F7'))
        action_find_usages.triggered.connect(self.find_usages)

        # Rename
        action_rename = mnu_refactor.addAction(_('Rename'))
        action_rename.setToolTip(_('Rename symnbol under cursor'))
        action_rename.setIcon(QtGui.QIcon.fromTheme('edit-find-replace'))
        action_rename.setShortcut(
            api.shortcuts.get('Refactor: rename', _('Refactor: rename'),
                              'Shift+F6'))
        action_rename.triggered.connect(self.rename)

        # Extract variable
        action_extract_var = mnu_refactor.addAction(_('Extract variable'))
        action_extract_var.setToolTip(
            _('Extract variable (a statement must be selected)'))
        action_extract_var.setIcon(special_icons.variable_icon())
        action_extract_var.setShortcut(
            api.shortcuts.get('Refactor: extract variable',
                              _('Refactor: extract variable'), 'Ctrl+Alt+V'))
        action_extract_var.triggered.connect(self.extract_variable)

        # Extract method
        action_extract_method = mnu_refactor.addAction(_('Extract method'))
        action_extract_method.setToolTip(
            _('Extract method (some statements must be selected)'))
        action_extract_method.setIcon(special_icons.function_icon())
        action_extract_method.setShortcut(
            api.shortcuts.get('Refactor: extract method',
                              _('Refactor: extract method'), 'Ctrl+Alt+M'))
        action_extract_method.triggered.connect(self.extract_method)

        mnu_refactor.addSeparator()

        action_organize_imports = mnu_refactor.addAction(_('Organize imports'))
        action_organize_imports.setToolTip(
            _('Organize top level imports (sort, remove unused,...)'))
        action_organize_imports.setIcon(special_icons.namespace_icon())
        action_organize_imports.setShortcut(
            api.shortcuts.get('Refactor: organize imports',
                              _('Refactor: organize imports'), 'Alt+F8'))
        action_organize_imports.triggered.connect(self.organise_imports)

        actions = {
            'usages': action_find_usages,
            'rename': action_rename,
            'extract_method': action_extract_method,
            'extract_var': action_extract_var,
            'imports': action_organize_imports
        }

        return mnu_refactor, actions

    def _on_current_project_changed(self, path):
        """
        Changes the active rope project when the current project changed in
        the IDE.
        :param path: Path of the new project.
        """
        self._main_project.close()
        self._main_project = Project(path,
                                     ropefolder=api.project.FOLDER,
                                     fscommands=FileSystemCommands())
        self._main_project.validate()

    def _on_editor_created(self, editor):
        """
        Adds the refactor menu to the editor that have just been created.
        :param editor: editor to modify.
        """
        if isinstance(editor, PyCodeEdit):
            sep = QtWidgets.QAction(editor)
            sep.setSeparator(True)
            menu, actions = self.create_refactor_menu(editor)
            editor.insert_action(menu.menuAction(), 0)
            editor.refactoring_actions = actions
            editor.insert_action(sep, 1)
            editor.cursorPositionChanged.connect(
                self._update_edit_actions_state)

    @staticmethod
    def _update_edit_actions_state(editor=None):
        if editor is None:
            editor = api.editor.get_current_editor()
        if isinstance(editor, PyCodeEdit):
            flg = bool(
                TextHelper(editor).word_under_cursor(
                    select_whole_word=True).selectedText())
            try:
                editor.refactoring_actions
            except AttributeError:
                return
            else:
                editor.refactoring_actions['usages'].setEnabled(flg)
                editor.refactoring_actions['rename'].setEnabled(flg)
                flg = editor.textCursor().hasSelection()
                editor.refactoring_actions['extract_method'].setEnabled(flg)
                editor.refactoring_actions['extract_var'].setEnabled(flg)

    def _create_occurrences_dock(self):
        """
        Creates the dock widget that shows all the occurrences of a python
        name.
        """
        self._occurrences_widget = QtWidgets.QWidget()
        vlayout = QtWidgets.QVBoxLayout()
        # buttons
        self._setup_occurrences_buttons(vlayout)
        self._occurrences_results = FindResultsWidget(term='usages')
        self._occurrences_results.itemActivated.connect(
            self._on_occurrence_activated)
        vlayout.addWidget(self._occurrences_results)
        self._occurrences_widget.setLayout(vlayout)
        # Dock widget
        self._occurrences_dock = api.window.add_dock_widget(
            self._occurrences_widget, _('Find usages'),
            QtGui.QIcon.fromTheme('edit-find'), QtCore.Qt.BottomDockWidgetArea)

    @staticmethod
    def _on_occurrence_activated(item):
        assert isinstance(item, QtWidgets.QTreeWidgetItem)
        data = item.data(0, QtCore.Qt.UserRole)
        try:
            l = data['line']
        except TypeError:
            return  # file item or root item
        l = data['line']
        start = data['start']
        lenght = data['end'] - start
        if data is not None:
            # open editor and go to line/column
            editor = api.editor.open_file(data['path'], data['line'],
                                          data['start'])
            if editor is None:
                return
            # select text
            helper = TextHelper(editor)
            cursor = helper.select_lines(start=l, end=l)
            cursor.movePosition(cursor.StartOfBlock)
            cursor.movePosition(cursor.Right, cursor.MoveAnchor, start)
            cursor.movePosition(cursor.Right, cursor.KeepAnchor, lenght)
            editor.setTextCursor(cursor)

    def _setup_occurrences_buttons(self, vlayout):
        """
        Creates the occurrences dock widget buttons
        :param vlayout: main layout
        """
        buttons = QtWidgets.QWidget()
        buttons_layout = QtWidgets.QHBoxLayout()
        buttons_layout.setContentsMargins(0, 0, 0, 0)
        # Close
        bt = QtWidgets.QPushButton()
        bt.setText(_('Close'))
        bt.clicked.connect(self._remove_occurrences_dock)
        buttons_layout.addWidget(bt)
        # Spacer
        buttons_layout.addSpacerItem(
            QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Expanding))
        buttons.setLayout(buttons_layout)
        vlayout.addWidget(buttons)

    def _create_review_dock(self):
        """
        Creates the dock widget that shows the refactor diff.
        """
        if self._review_widget:
            self._preview_dock.show()
            self._preview_dock.button.show()
            self._preview_dock.button.action.setVisible(True)
            return
        self._review_widget = QtWidgets.QWidget()
        vlayout = QtWidgets.QVBoxLayout()
        # buttons
        bt_refactor = self._setup_review_buttons(vlayout)
        # Diff viewer
        self._viewer = DiffViewer()
        vlayout.addWidget(self._viewer)
        self._review_widget.setLayout(vlayout)

        # Dock widget
        self._preview_dock = api.window.add_dock_widget(
            self._review_widget, _('Review'),
            QtGui.QIcon.fromTheme('edit-find'), QtCore.Qt.BottomDockWidgetArea)
        bt_refactor.setFocus()

    def _update_preview(self):
        try:
            texts = []
            for prj, changes in self._pending_changes:
                lines = ''
                for subchanges in changes.changes:
                    resource = subchanges.get_changed_resources()[0]
                    resource_path = resource.real_path
                    if prj.address + os.sep in resource_path:
                        desc = subchanges.get_description()
                        lines += desc
                if lines:
                    texts.append('%s\n\n*** Project: %s\n\n%s\n' %
                                 (str(changes), prj.address, lines))
            self._viewer.setPlainText('\n'.join(texts))
        except TypeError:
            # not multiproj, one single change set
            self._viewer.setPlainText(self._pending_changes.get_description())

    def _setup_review_buttons(self, vlayout):
        """
        Creates the buttons of the preview dock widget
        :param vlayout: main layout
        """
        buttons = QtWidgets.QWidget()
        buttons_layout = QtWidgets.QHBoxLayout()
        buttons_layout.setContentsMargins(0, 0, 0, 0)
        bt_refactor = bt = QtWidgets.QPushButton()
        bt.setText(_('Refactor'))
        bt.clicked.connect(self._refactor)
        buttons_layout.addWidget(bt)
        # Close
        bt = QtWidgets.QPushButton()
        bt.setText(_('Cancel'))
        bt.clicked.connect(self._remove_preview_dock)
        buttons_layout.addWidget(bt)
        # Spacer
        buttons_layout.addSpacerItem(
            QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Expanding))
        buttons.setLayout(buttons_layout)
        vlayout.addWidget(buttons)

        return bt_refactor

    def _remove_preview_dock(self):
        """
        Removes the preview dock widget
        """
        if self._preview_dock is not None:
            self._preview_dock.hide()
            self._preview_dock.button.hide()
            self._preview_dock.button.action.setVisible(False)

    def _remove_occurrences_dock(self):
        """
        Removes the occurrences dock widget.
        """
        if self._occurrences_dock is not None:
            self._occurrences_dock.hide()
            self._occurrences_dock.button.hide()
            self._occurrences_dock.button.action.setVisible(False)

    def _refactor(self):
        """
        Performs the refactoring.
        """
        main_project = self._main_project
        multiproj = self._has_multiple_projects()
        pending_changes = self._pending_changes
        api.tasks.start(_('Refactoring: apply pending changes'),
                        apply_pending_changes,
                        self._on_refactoring_finished,
                        args=(main_project, multiproj, pending_changes),
                        use_thread=True)

    def _on_refactoring_finished(self, ret_val):
        self._remove_preview_dock()
        if ret_val is not True:
            api.events.post(RefactoringErrorEvent(ret_val), False)

    @staticmethod
    def _get_other_projects(path_only=False):
        """
        Gets the list of secondary projects (all except current).
        """
        projects = []
        current = api.project.get_current_project()
        for path in api.project.get_projects():
            if path == current:
                continue
            if not path_only:
                prj = Project(path,
                              ropefolder=api.project.FOLDER,
                              fscommands=FileSystemCommands())
                prj.validate()
            else:
                prj = path
            projects.append(prj)
        return projects

    @staticmethod
    def _has_multiple_projects():
        """
        Checks whether multiple project have been opened in the main window.
        :return: True if window has multiple project, False if window only has
                 one project.
        """
        return len(api.project.get_projects()) > 1

    @staticmethod
    def _on_document_saved(path, old_content):
        if not path:
            return
        project = None
        for project in api.project.get_projects():
            prj_path = project + os.sep
            if prj_path in path:
                project = Project(prj_path,
                                  ropefolder=api.project.FOLDER,
                                  fscommands=FileSystemCommands())
                break
        if project:
            if path.endswith('_rc.py'):
                return
            api.tasks.start(_('Refactoring: reporting changes'),
                            report_changes,
                            None,
                            args=(project, path, old_content),
                            use_thread=False)

    @staticmethod
    def _get_real_position(position):
        """
        Gets the real cursor position (there might be a difference between
        editor content and file system content because of clean_trailing
        whitespaces on save). This function will converts the absolute position
        into a line/column pair and use this info to get the position in the
        file.
        """
        tc = api.editor.get_current_editor().textCursor()
        tc.setPosition(position)
        l = tc.blockNumber()
        c = tc.positionInBlock()
        e = api.editor.get_current_editor().file.encoding
        path = api.editor.get_current_editor().file.path
        try:
            with open(path, 'r', encoding=e) as f:
                lines = f.readlines()
        except OSError:
            _logger().exception('failed to read file %s', path)
            lines = []
        real_pos = 0
        for i, line in enumerate(lines):
            if i == l:
                real_pos += c
                break
            else:
                real_pos += len(line)
        return real_pos
Esempio n. 12
0
class Workspace(object):

    M_PUBLISH_DIAGNOSTICS = 'textDocument/publishDiagnostics'
    M_APPLY_EDIT = 'workspace/applyEdit'
    M_SHOW_MESSAGE = 'window/showMessage'
    PRELOADED_MODULES = get_preferred_submodules()

    def __init__(self, root_uri, lang_server=None):
        self._root_uri = root_uri
        self._root_uri_scheme = uris.urlparse(self._root_uri)[0]
        self._root_path = uris.to_fs_path(self._root_uri)
        self._docs = {}
        self._lang_server = lang_server

        # Whilst incubating, keep private
        self.__rope = Project(self._root_path)
        self.__rope.prefs.set('extension_modules', self.PRELOADED_MODULES)

    @property
    def _rope(self):
        # TODO: we could keep track of dirty files and validate only those
        self.__rope.validate()
        return self.__rope

    @property
    def documents(self):
        return self._docs

    @property
    def root_path(self):
        return self._root_path

    @property
    def root_uri(self):
        return self._root_uri

    def is_local(self):
        return (self._root_uri_scheme == '' or self._root_uri_scheme == 'file') and os.path.exists(self._root_path)

    def get_document(self, doc_uri):
        return self._docs[doc_uri]

    def put_document(self, doc_uri, content, version=None):
        path = uris.to_fs_path(doc_uri)
        self._docs[doc_uri] = Document(
            doc_uri, content,
            extra_sys_path=self.source_roots(path), version=version, rope=self._rope
        )

    def rm_document(self, doc_uri):
        self._docs.pop(doc_uri)

    def update_document(self, doc_uri, change, version=None):
        self._docs[doc_uri].apply_change(change)
        self._docs[doc_uri].version = version

    def apply_edit(self, edit, on_result=None, on_error=None):
        return self._lang_server.call(
            self.M_APPLY_EDIT, {'edit': edit},
            on_result=on_result, on_error=on_error
        )

    def publish_diagnostics(self, doc_uri, diagnostics):
        params = {'uri': doc_uri, 'diagnostics': diagnostics}
        self._lang_server.notify(self.M_PUBLISH_DIAGNOSTICS, params)

    def show_message(self, message, msg_type=lsp.MessageType.Info):
        params = {'type': msg_type, 'message': message}
        self._lang_server.notify(self.M_SHOW_MESSAGE, params)

    def source_roots(self, document_path):
        """Return the source roots for the given document."""
        files = _utils.find_parents(self._root_path, document_path, ['setup.py']) or []
        return [os.path.dirname(setup_py) for setup_py in files]
Esempio n. 13
0
class Workspace(object):

    M_PUBLISH_DIAGNOSTICS = 'textDocument/publishDiagnostics'
    M_APPLY_EDIT = 'workspace/applyEdit'
    M_SHOW_MESSAGE = 'window/showMessage'

    def __init__(self, root_uri, endpoint):
        self._root_uri = root_uri
        self._endpoint = endpoint
        self._root_uri_scheme = uris.urlparse(self._root_uri)[0]
        self._root_path = uris.to_fs_path(self._root_uri)
        self._docs = {}

        # Whilst incubating, keep rope private
        self.__rope = None
        self.__rope_config = None

    def _rope_project_builder(self, rope_config):
        from rope.base.project import Project

        # TODO: we could keep track of dirty files and validate only those
        if self.__rope is None or self.__rope_config != rope_config:
            rope_folder = rope_config.get('ropeFolder')
            self.__rope = Project(self._root_path, ropefolder=rope_folder)
            self.__rope.prefs.set('extension_modules', rope_config.get('extensionModules', []))
            self.__rope.prefs.set('ignore_syntax_errors', True)
            self.__rope.prefs.set('ignore_bad_imports', True)
        self.__rope.validate()
        return self.__rope

    @property
    def documents(self):
        return self._docs

    @property
    def root_path(self):
        return self._root_path

    @property
    def root_uri(self):
        return self._root_uri

    def is_local(self):
        return (self._root_uri_scheme == '' or self._root_uri_scheme == 'file') and os.path.exists(self._root_path)

    def get_document(self, doc_uri):
        """Return a managed document if-present, else create one pointing at disk.

        See https://github.com/Microsoft/language-server-protocol/issues/177
        """
        return self._docs.get(doc_uri) or self._create_document(doc_uri)

    def put_document(self, doc_uri, source, version=None):
        self._docs[doc_uri] = self._create_document(doc_uri, source=source, version=version)

    def rm_document(self, doc_uri):
        self._docs.pop(doc_uri)

    def update_document(self, doc_uri, change, version=None):
        self._docs[doc_uri].apply_change(change)
        self._docs[doc_uri].version = version

    def apply_edit(self, edit):
        return self._endpoint.request(self.M_APPLY_EDIT, {'edit': edit})

    def publish_diagnostics(self, doc_uri, diagnostics):
        self._endpoint.notify(self.M_PUBLISH_DIAGNOSTICS, params={'uri': doc_uri, 'diagnostics': diagnostics})

    def show_message(self, message, msg_type=lsp.MessageType.Info):
        self._endpoint.notify(self.M_SHOW_MESSAGE, params={'type': msg_type, 'message': message})

    def source_roots(self, document_path):
        """Return the source roots for the given document."""
        files = _utils.find_parents(self._root_path, document_path, ['setup.py']) or []
        return [os.path.dirname(setup_py) for setup_py in files]

    def _create_document(self, doc_uri, source=None, version=None):
        path = uris.to_fs_path(doc_uri)
        return Document(
            doc_uri, source=source, version=version,
            extra_sys_path=self.get_extra_venv_sys_paths() + self.source_roots(path),
            rope_project_builder=self._rope_project_builder,
        )

    @jedi.cache.memoize_method
    def get_extra_venv_sys_paths(self):
        """
        Figure out the extra sys paths for the configured virtualenvs.

        This is memoized because it can be slow, and it's not something
        we'd want to do for every document.
        """
        paths = []

        # We support having the language server looking into other virtualenvs besides the
        # one that it is running out of.
        extra_venv_paths = [x for x in os.environ.get('VIRTUALENV_PATHS', '').split(':') if x]

        # We iterate over the extra venvs in reverse because we prepend the list of extra paths.
        # This preserves the order that the user passed in via the env var.
        # Passing in A:B:C as extra virtualenv paths results in the paths of A, B, and then C being searched.
        for extra_venv in reversed(extra_venv_paths):
            try:
                env = jedi.api.environment.create_environment(extra_venv)
            except jedi.api.environment.InvalidPythonEnvironment:
                log.warning("Virtualenv path %s does not seem to be a valid Python environment", extra_venv)
                continue

            paths = env.get_sys_path() + paths

        return paths
Esempio n. 14
0
def find_usages(_, main_project, other_projects, file_path, offset):
    """
    Find usages of symbol under cursor.
    """
    try:
        occurrences = []
        if other_projects:
            for path in [main_project] + other_projects:
                prj = Project(path, ropefolder=api.project.FOLDER,
                              fscommands=FileSystemCommands())
                prj.validate()
                mod = libutils.path_to_resource(prj, file_path)
                occurrences += find_occurrences(
                    prj, mod, offset, unsure=False, in_hierarchy=True)
                prj.close()
        else:
            prj = Project(main_project, ropefolder=api.project.FOLDER,
                          fscommands=FileSystemCommands())
            prj.validate()
            mod = libutils.path_to_resource(prj, file_path)
            occurrences = find_occurrences(prj, mod, offset, unsure=False,
                                           in_hierarchy=True)
        # transform results to a serialisable list of usages that is ready
        # to use by the find_results widget.
        occurrences_map = {}
        for location in occurrences:
            path = location.resource.real_path
            lineno = location.lineno - 1
            # convert file region to line region
            content = location.resource.read()
            offset = location.offset
            char = content[offset]
            while char != '\n':  # find start of line
                offset -= 1
                char = content[offset]
            # update offsets
            start = location.offset - offset - 1
            end = location.region[1] - offset - 1
            line_text = content.splitlines()[lineno]
            data = (lineno, line_text, [(start, end)])
            if path not in occurrences_map:
                occurrences_map[path] = [data]
            else:
                occurrences_map[path].append(data)
        results = []
        for key, value in occurrences_map.items():
            results.append((key, value))
        results = sorted(results, key=lambda x: x[0])
        return results
    except RopeError as e:
        error = RefactoringError()
        error.exc = str(e)
        error.traceback = traceback.format_exc()
        error.critical = False
        return error
    except Exception as e:
        error = RefactoringError()
        error.exc = str(e)
        error.traceback = traceback.format_exc()
        error.critical = True
        return error
Esempio n. 15
0
class Workspace(object):

    M_PUBLISH_DIAGNOSTICS = 'textDocument/publishDiagnostics'
    M_APPLY_EDIT = 'workspace/applyEdit'
    M_SHOW_MESSAGE = 'window/showMessage'

    def __init__(self, root_uri, endpoint, config=None):
        self._config = config
        self._root_uri = root_uri
        self._endpoint = endpoint
        self._root_uri_scheme = uris.urlparse(self._root_uri)[0]
        self._root_path = uris.to_fs_path(self._root_uri)
        self._docs = {}

        # Cache jedi environments
        self._environments = {}

        # Whilst incubating, keep rope private
        self.__rope = None
        self.__rope_config = None

    def _rope_project_builder(self, rope_config):
        from rope.base.project import Project

        # TODO: we could keep track of dirty files and validate only those
        if self.__rope is None or self.__rope_config != rope_config:
            rope_folder = rope_config.get('ropeFolder')
            self.__rope = Project(self._root_path, ropefolder=rope_folder)
            self.__rope.prefs.set('extension_modules',
                                  rope_config.get('extensionModules', []))
            self.__rope.prefs.set('ignore_syntax_errors', True)
            self.__rope.prefs.set('ignore_bad_imports', True)
        self.__rope.validate()
        return self.__rope

    @property
    def documents(self):
        return self._docs

    @property
    def root_path(self):
        return self._root_path

    @property
    def root_uri(self):
        return self._root_uri

    def is_local(self):
        return (self._root_uri_scheme == '' or self._root_uri_scheme
                == 'file') and os.path.exists(self._root_path)

    def get_document(self, doc_uri):
        """Return a managed document if-present, else create one pointing at disk.

        See https://github.com/Microsoft/language-server-protocol/issues/177
        """
        return self._docs.get(doc_uri) or self._create_document(doc_uri)

    def get_maybe_document(self, doc_uri):
        return self._docs.get(doc_uri)

    def put_document(self, doc_uri, source, version=None):
        self._docs[doc_uri] = self._create_document(doc_uri,
                                                    source=source,
                                                    version=version)

    def rm_document(self, doc_uri):
        self._docs.pop(doc_uri)

    def update_document(self, doc_uri, change, version=None):
        self._docs[doc_uri].apply_change(change)
        self._docs[doc_uri].version = version

    def update_config(self, settings):
        self._config.update((settings or {}).get('pyls', {}))
        for doc_uri in self.documents:
            self.get_document(doc_uri).update_config(settings)

    def apply_edit(self, edit):
        return self._endpoint.request(self.M_APPLY_EDIT, {'edit': edit})

    def publish_diagnostics(self, doc_uri, diagnostics):
        self._endpoint.notify(self.M_PUBLISH_DIAGNOSTICS,
                              params={
                                  'uri': doc_uri,
                                  'diagnostics': diagnostics
                              })

    def show_message(self, message, msg_type=lsp.MessageType.Info):
        self._endpoint.notify(self.M_SHOW_MESSAGE,
                              params={
                                  'type': msg_type,
                                  'message': message
                              })

    def source_roots(self, document_path):
        """Return the source roots for the given document."""
        files = _utils.find_parents(self._root_path, document_path,
                                    ['setup.py', 'pyproject.toml']) or []
        return list({os.path.dirname(project_file)
                     for project_file in files}) or [self._root_path]

    def _create_document(self, doc_uri, source=None, version=None):
        path = uris.to_fs_path(doc_uri)
        return Document(
            doc_uri,
            self,
            source=source,
            version=version,
            extra_sys_path=self.source_roots(path),
            rope_project_builder=self._rope_project_builder,
        )
class Workspace(object):

    M_PUBLISH_DIAGNOSTICS = 'textDocument/publishDiagnostics'
    M_APPLY_EDIT = 'workspace/applyEdit'
    M_SHOW_MESSAGE = 'window/showMessage'

    def __init__(self, root_uri, endpoint):
        self._root_uri = root_uri
        self._endpoint = endpoint
        self._root_uri_scheme = uris.urlparse(self._root_uri)[0]
        self._root_path = uris.to_fs_path(self._root_uri)
        self._docs = {}

        # Whilst incubating, keep rope private
        self.__rope = None
        self.__rope_config = None

    def _rope_project_builder(self, rope_config):
        from rope.base.project import Project

        # TODO: we could keep track of dirty files and validate only those
        if self.__rope is None or self.__rope_config != rope_config:
            rope_folder = rope_config.get('ropeFolder')
            self.__rope = Project(self._root_path, ropefolder=rope_folder)
            self.__rope.prefs.set('extension_modules',
                                  rope_config.get('extensionModules', []))
            self.__rope.prefs.set('ignore_syntax_errors', True)
            self.__rope.prefs.set('ignore_bad_imports', True)
        self.__rope.validate()
        return self.__rope

    @property
    def documents(self):
        return self._docs

    @property
    def root_path(self):
        return self._root_path

    @property
    def root_uri(self):
        return self._root_uri

    def is_local(self):
        return (self._root_uri_scheme == '' or self._root_uri_scheme
                == 'file') and os.path.exists(self._root_path)

    def get_document(self, doc_uri):
        """Return a managed document if-present, else create one pointing at disk.

        See https://github.com/Microsoft/language-server-protocol/issues/177
        """
        return self._docs.get(doc_uri) or self._create_document(doc_uri)

    def put_document(self, doc_uri, source, version=None):
        self._docs[doc_uri] = self._create_document(doc_uri,
                                                    source=source,
                                                    version=version)

    def rm_document(self, doc_uri):
        self._docs.pop(doc_uri)

    def update_document(self, doc_uri, change, version=None):
        self._docs[doc_uri].apply_change(change)
        self._docs[doc_uri].version = version

    def apply_edit(self, edit):
        return self._endpoint.request(self.M_APPLY_EDIT, {'edit': edit})

    def publish_diagnostics(self, doc_uri, diagnostics):
        self._endpoint.notify(self.M_PUBLISH_DIAGNOSTICS,
                              params={
                                  'uri': doc_uri,
                                  'diagnostics': diagnostics
                              })

    def show_message(self, message, msg_type=lsp.MessageType.Info):
        self._endpoint.notify(self.M_SHOW_MESSAGE,
                              params={
                                  'type': msg_type,
                                  'message': message
                              })

    def source_roots(self, document_path):
        """Return the source roots for the given document."""
        files = _utils.find_parents(self._root_path, document_path,
                                    ['setup.py']) or []
        return [os.path.dirname(setup_py) for setup_py in files]

    def venv_roots(self, document_path):
        _versions = []
        for _file in _utils.find_parents("/", document_path,
                                         [".python-version"]) or []:
            log.info(_file)
            with open(_file, 'r') as fh:
                _versions.append(fh.read().strip())

        _paths = []
        for _version in _versions:
            python_exec = subprocess.run(
                ["pyenv", "which", "python"],
                stdout=subprocess.PIPE,
                env=dict(
                    list(os.environ.items()) +
                    list({"PYENV_VERSION": _version}.items())))
            python_exec_path = python_exec.stdout.decode().strip()
            # log.info(python_exec_path)

            other_syspath = subprocess.run([
                python_exec_path, "-c",
                "import sys;import pickle;sys.stdout.buffer.write(pickle.dumps(sys.path))"
            ],
                                           stdout=subprocess.PIPE,
                                           env={})
            _paths += pickle.loads(other_syspath.stdout)

        return sorted([p for p in set(_paths)], key=_paths.index)

    def _create_document(self, doc_uri, source=None, version=None):
        path = uris.to_fs_path(doc_uri)
        log.info(doc_uri)
        roots = self.venv_roots(path) + self.source_roots(path)
        # dedupe, maintaining order
        roots = sorted([r for r in set(roots)], key=roots.index)
        log.info(roots)
        return Document(
            doc_uri,
            source=source,
            version=version,
            extra_sys_path=roots,
            rope_project_builder=self._rope_project_builder,
        )
Esempio n. 17
0
class CodimensionProject( QObject ):
    " Provides codimension project singleton facility "

    # Constants for the projectChanged signal
    CompleteProject = 0     # It is a completely new project
    Properties      = 1     # Project properties were updated

    projectChanged = pyqtSignal( int )
    fsChanged = pyqtSignal( list )

    def __init__( self ):
        QObject.__init__( self )

        self.__dirWatcher = None
        self.__formatOK = True

        # Avoid pylint complains
        self.fileName = ""
        self.userProjectDir = ""    # Directory in ~/.codimension/uuidNN/
        self.filesList = set()

        self.scriptName = ""        # Script to run the project
        self.creationDate = ""
        self.author = ""
        self.license = ""
        self.copyright = ""
        self.version = ""
        self.email = ""
        self.description = ""
        self.uuid = ""

        self.__ropeProject = None
        self.__ropeSourceDirs = []

        # Coming from separate files from ~/.codimension/uuidN/
        self.todos = []
        self.bookmarks = []
        self.runParamsCache = RunParametersCache()
        self.topLevelDirs = []
        self.findHistory = []
        self.findNameHistory = []
        self.findFileHistory = []
        self.replaceHistory = []
        self.tabsStatus = []

        self.findFilesWhat = []
        self.findFilesDirs = []
        self.findFilesMasks = []

        self.findClassHistory = []
        self.findFuncHistory = []
        self.findGlobalHistory = []

        self.recentFiles = []
        self.importDirs = []
        self.fileBrowserPaths = []

        self.ignoredExcpt = []
        self.breakpoints = []
        self.watchpoints = []

        # Precompile the exclude filters
        self.__excludeFilter = []
        for flt in Settings().projectFilesFilters:
            self.__excludeFilter.append( re.compile( flt ) )

        return

    def shouldExclude( self, name ):
        " Tests if a file must be excluded "
        for excl in self.__excludeFilter:
            if excl.match( name ):
                return True
        return False

    def __resetValues( self ):
        """ Initializes or resets all the project members """

        # Empty file name means that the project has not been loaded or
        # created. This must be an absolute path.
        self.fileName = ""
        self.userProjectDir = ""

        # Generated having the project dir Full paths are stored.
        # The set holds all files and directories. The dirs end with os.path.sep
        self.filesList = set()

        self.scriptName = ""
        self.creationDate = ""
        self.author = ""
        self.license = ""
        self.copyright = ""
        self.version = ""
        self.email = ""
        self.description = ""
        self.uuid = ""

        # Coming from separate files from ~/.codimension/uuidN/
        self.todos = []
        self.bookmarks = []
        self.runParamsCache = RunParametersCache()
        self.topLevelDirs = []
        self.findHistory = []
        self.findNameHistory = []
        self.findFileHistory = []
        self.replaceHistory = []
        self.tabsStatus = []

        self.findFilesWhat = []
        self.findFilesDirs = []
        self.findFilesMasks = []

        self.findClassHistory = []
        self.findFuncHistory = []
        self.findGlobalHistory = []

        self.recentFiles = []
        self.importDirs = []
        self.fileBrowserPaths = []

        self.ignoredExcpt = []
        self.breakpoints = []
        self.watchpoints = []

        # Reset the dir watchers if so
        if self.__dirWatcher is not None:
            del self.__dirWatcher
        self.__dirWatcher = None
        return

    def createNew( self, fileName, scriptName, importDirs, author, lic,
                   copyRight, description, creationDate, version, email ):
        " Creates a new project "

        # Try to create the user project directory
        projectUuid = str( uuid.uuid1() )
        userProjectDir = settingsDir + projectUuid + sep
        if not os.path.exists( userProjectDir ):
            try:
                os.mkdir( userProjectDir )
            except:
                logging.error( "Cannot create user project directory: " +
                               self.userProjectDir + ". Please check the "
                               "available disk space and re-create the "
                               "project." )
                raise
        else:
            logging.warning( "The user project directory existed! "
                             "The content will be overwritten." )
            self.__removeProjectFiles( userProjectDir )

        # Basic pre-requisites are met. We can reset the current project
        self.__resetValues()

        self.fileName = str( fileName )
        self.importDirs = importDirs
        self.scriptName = scriptName
        self.creationDate = creationDate
        self.author = author
        self.license = lic
        self.copyright = copyRight
        self.version = version
        self.email = email
        self.description = description
        self.uuid = projectUuid
        self.userProjectDir = userProjectDir

        self.__createProjectFile()  # ~/.codimension/uuidNN/project

        self.__generateFilesList()

        self.saveProject()

        # Update the watcher
        self.__dirWatcher = Watcher( Settings().projectFilesFilters,
                                     self.getProjectDir() )
        self.__dirWatcher.fsChanged.connect( self.onFSChanged )

        self.__createRopeProject()
        self.projectChanged.emit( self.CompleteProject )
        return

    @staticmethod
    def __safeRemove( path ):
        " Safe file removal "
        try:
            os.remove( path )
        except:
            return

    def __removeProjectFiles( self, userProjectDir ):
        " Removes user project files "

        self.__safeRemove( userProjectDir + "project" )
        self.__safeRemove( userProjectDir + "bookmarks" )
        self.__safeRemove( userProjectDir + "todos" )
        self.__safeRemove( userProjectDir + "searchhistory" )
        self.__safeRemove( userProjectDir + "topleveldirs" )
        self.__safeRemove( userProjectDir + "tabsstatus" )
        self.__safeRemove( userProjectDir + "findinfiles" )
        self.__safeRemove( userProjectDir + "recentfiles" )
        self.__safeRemove( userProjectDir + "filebrowser" )
        self.__safeRemove( userProjectDir + "ignoredexcpt" )
        self.__safeRemove( userProjectDir + "breakpoints" )
        self.__safeRemove( userProjectDir + "watchpoints" )
        return

    def __createProjectFile( self ):
        " Helper function to create the user project file "
        try:
            f = open( self.userProjectDir + "project", "w" )
            f.write( self.fileName )
            f.close()
        except:
            return

    def saveProject( self ):
        " Writes all the settings into the file "
        if not self.isLoaded():
            return

        # Project properties part
        propertiesPart = "[properties]\n" \
                         "scriptname=" + self.scriptName + "\n" \
                         "creationdate=" + self.creationDate + "\n" \
                         "author=" + self.author + "\n" \
                         "license=" + self.license + "\n" \
                         "copyright=" + self.copyright + "\n" \
                         "description=" + \
                            self.description.replace( '\n', '<CR><LF>' ) + \
                            "\n" \
                         "version=" + self.version + "\n" \
                         "email=" + self.email + "\n" \
                         "uuid=" + self.uuid + "\n"

        # It could be another user project file without write permissions
        skipProjectFile = False
        if os.path.exists( self.fileName ):
            if not os.access( self.fileName, os.W_OK ):
                skipProjectFile = True
        else:
            if not os.access( os.path.dirname( self.fileName ), os.W_OK ):
                skipProjectFile = True

        if not skipProjectFile:
            f = open( self.fileName, "w" )
            self.__writeHeader( f )
            self.__writeList( f, "importdirs", "dir", self.importDirs )
            f.write( propertiesPart + "\n\n\n" )
            f.close()

        self.serializeRunParameters()
        self.__saveTopLevelDirs()
        self.__saveSearchHistory()
        self.__saveTabsStatus()
        self.__saveFindFiles()
        self.__saveFindObjects()
        self.__saveRecentFiles()
        self.__saveIgnoredExcpt()
        self.__saveBreakpoints()
        self.__saveWatchpoints()

        self.__formatOK = True
        return

    def serializeRunParameters( self ):
        " Saves the run parameters cache "
        self.runParamsCache.serialize( self.userProjectDir + "runparamscache" )
        return

    def __saveTabsStatus( self ):
        " Helper to save tabs status "
        if self.isLoaded():
            f = open( self.userProjectDir + "tabsstatus", "w" )
            self.__writeHeader( f )
            self.__writeList( f, "tabsstatus", "tab", self.tabsStatus )
            f.close()
        return

    def __saveSearchHistory( self ):
        " Helper to save the project search history "
        if self.isLoaded():
            f = open( self.userProjectDir + "searchhistory", "w" )
            self.__writeHeader( f )
            self.__writeList( f, "findhistory", "find",
                              self.findHistory )
            self.__writeList( f, "replacehistory", "replace",
                              self.replaceHistory )
            self.__writeList( f, "findnamehistory", "find",
                              self.findNameHistory )
            self.__writeList( f, "findfilehistory", "find",
                              self.findFileHistory )
            f.close()
        return

    def __saveTopLevelDirs( self ):
        " Helper to save the project top level dirs "
        if self.isLoaded():
            f = open( self.userProjectDir + "topleveldirs", "w" )
            self.__writeHeader( f )
            self.__writeList( f, "topleveldirs", "dir", self.topLevelDirs )
            f.close()
        return

    def __saveFindFiles( self ):
        " Helper to save the find in files history "
        if self.isLoaded():
            f = open( self.userProjectDir + "findinfiles", "w" )
            self.__writeHeader( f )
            self.__writeList( f, "whathistory", "what", self.findFilesWhat )
            self.__writeList( f, "dirhistory", "dir", self.findFilesDirs )
            self.__writeList( f, "maskhistory", "mask", self.findFilesMasks )
            f.close()
        return

    def __saveFindObjects( self ):
        " Helper to save find objects history "
        if self.isLoaded():
            f = open( self.userProjectDir + "findobjects", "w" )
            self.__writeHeader( f )
            self.__writeList( f, "classhistory", "class",
                              self.findClassHistory )
            self.__writeList( f, "funchistory", "func",
                              self.findFuncHistory )
            self.__writeList( f, "globalhistory", "global",
                              self.findGlobalHistory )
            f.close()
        return

    def __saveSectionToFile( self, fileName, sectionName, itemName, values ):
        " Saves the given values into a file "
        if self.isLoaded():
            f = open( self.userProjectDir + fileName, "w" )
            self.__writeHeader( f )
            self.__writeList( f, sectionName, itemName, values )
            f.close()
        return

    def __saveRecentFiles( self ):
        " Helper to save recent files list "
        self.__saveSectionToFile( "recentfiles", "recentfiles", "file",
                                  self.recentFiles )
        return

    def __saveIgnoredExcpt( self ):
        " Helper to save ignored exceptions list "
        self.__saveSectionToFile( "ignoredexcpt", "ignoredexcpt",
                                  "excpttype", self.ignoredExcpt )
        return

    def __saveBreakpoints( self ):
        " Helper to save breakpoints "
        self.__saveSectionToFile( "breakpoints", "breakpoints",
                                  "bpoint", self.breakpoints )
        return

    def __saveWatchpoints( self ):
        " helper to save watchpoints "
        self.__saveSectionToFile( "watchpoints", "watchpoints",
                                  "wpoint", self.watchpoints )
        return

    @staticmethod
    def __writeHeader( fileObj ):
        " Helper to write a header with a warning "
        fileObj.write( "#\n"
                       "# Generated automatically.\n"
                       "# Don't edit it manually unless you "
                       "know what you are doing.\n"
                       "#\n\n" )
        return

    @staticmethod
    def __writeList( fileObj, header, prefix, items ):
        " Helper to write a list "
        fileObj.write( "[" + header + "]\n" )
        index = 0
        for item in items:
            fileObj.write( prefix + str( index ) + "=" + item + "\n" )
            index += 1
        fileObj.write( "\n" )
        return

    def __getStr( self, conf, sec, key, default ):
        " Helper to read a config value "
        try:
            return conf.get( sec, key ).strip()
        except:
            self.__formatOK = False
        return default

    def loadProject( self, projectFile ):
        """ Loads a project from the given file """

        absPath = os.path.abspath( projectFile )
        if not os.path.exists( absPath ):
            raise Exception( "Cannot open project file " + projectFile )
        if not absPath.endswith( ".cdm" ):
            raise Exception( "Unexpected project file extension. "
                             "Expected: .cdm" )

        config = ConfigParser.ConfigParser()

        try:
            config.read( absPath )
        except:
            # Bad error - cannot load project file at all
            config = None
            raise Exception( "Bad project file" )


        self.__resetValues()
        self.fileName = str( absPath )

        # Properties part
        self.scriptName = self.__getStr( config, 'properties',
                                                 'scriptname', '' )
        self.creationDate = self.__getStr( config, 'properties',
                                                   'creationdate', '' )
        self.author = self.__getStr( config, 'properties', 'author', '' )
        self.license = self.__getStr( config, 'properties', 'license', '' )
        self.copyright = self.__getStr( config, 'properties', 'copyright', '' )
        self.description = self.__getStr( config, 'properties', 'description',
                                          '' ).replace( '<CR><LF>', '\n' )
        self.version = self.__getStr( config, 'properties', 'version', '' )
        self.email = self.__getStr( config, 'properties', 'email', '' )
        self.uuid = self.__getStr( config, 'properties', 'uuid', '' )
        if self.uuid == "":
            logging.warning( "Project file does not have UUID. "
                             "Re-generate it..." )
            self.uuid = str( uuid.uuid1() )
        self.userProjectDir = settingsDir + self.uuid + sep
        if not os.path.exists( self.userProjectDir ):
            os.mkdir( self.userProjectDir )

        # import dirs part
        index = 0
        try:
            while True:
                dirName = config.get( 'importdirs',
                                      'dir' + str( index ) ).strip()
                index += 1
                if os.path.isabs( dirName ):
                    absPath = dirName
                else:
                    absPath = self.getProjectDir() + dirName
                if not os.path.exists( absPath ):
                    logging.error( "Codimension project: cannot find "
                                   "import directory: " + dirName )
                elif not isdir( absPath ):
                    logging.error( "Codimension project: the import path: " +
                                   dirName + " is not a directory" )
                self.importDirs.append( dirName )
        except ConfigParser.NoSectionError:
            self.__formatOK = False
        except ConfigParser.NoOptionError:
            # just continue
            pass
        except:
            self.__formatOK = False

        config = None

        # Read the other config files
        self.__loadTopLevelDirs()
        self.__loadSearchHistory()
        self.__loadTabsStatus()
        self.__loadFindFiles()
        self.__loadFindObjects()
        self.__loadRecentFiles()
        self.__loadProjectBrowserExpandedDirs()
        self.__loadIgnoredExceptions()
        self.__loadBreakpoints()
        self.__loadWatchpoints()

        # The project might have been moved...
        self.__createProjectFile()  # ~/.codimension/uuidNN/project
        self.__generateFilesList()

        if os.path.exists( self.userProjectDir + "runparamscache" ):
            self.runParamsCache.deserialize( self.userProjectDir +
                                             "runparamscache" )

        if not self.__formatOK:
            logging.info( "Project files are broken or absent. "
                          "Overwriting the project files." )
            self.saveProject()

        # Update the recent list
        Settings().addRecentProject( self.fileName )

        # Setup the new watcher
        self.__dirWatcher = Watcher( Settings().projectFilesFilters,
                                     self.getProjectDir() )
        self.__dirWatcher.fsChanged.connect( self.onFSChanged )

        self.__createRopeProject()
        self.projectChanged.emit( self.CompleteProject )
        self.emit( SIGNAL( 'restoreProjectExpandedDirs' ) )
        return

    def getImportDirsAsAbsolutePaths( self ):
        " Provides a list of import dirs as absolute paths "
        result = []
        for path in self.importDirs:
            if os.path.isabs( path ):
                result.append( path )
            else:
                result.append( self.getProjectDir() + path )
        return result

    def __getImportDirsForRope( self ):
        " Provides the list of import dirs for the rope library "
        result = []
        for path in self.importDirs:
            if not os.path.isabs( path ):
                # The only relative paths can be accepted by rope
                result.append( path )
        result.sort()
        return result

    def getRopeProject( self ):
        " Provides the rope project "
        if self.__getImportDirsForRope() != self.__ropeSourceDirs:
            # The user changed the project import dirs, so let's
            # re-create the project
            self.__createRopeProject()
        return self.__ropeProject

    def validateRopeProject( self, fileName ):
        " Validates the rope project "
        # Currently rope can validate a directory only so the argument
        # is ignored
        self.__ropeProject.validate()
        return

    def __createRopeProject( self ):
        " Creates a rope library project "
        if self.__ropeProject is not None:
            self.__ropeProject.close()
            self.__ropeProject = None
            self.__ropeSourceDirs = []

        # Deal with import dirs and preferences first
        self.__ropeSourceDirs = self.__getImportDirsForRope()
        prefs = copy.deepcopy( ropePreferences )
        if len( self.__ropeSourceDirs ) != 0:
            prefs[ "source_folders" ] = self.__ropeSourceDirs

        # Rope folder is default here, so it will be created
        self.__ropeProject = RopeProject( self.getProjectDir(),
                                          **prefs )
        self.__ropeProject.validate( self.__ropeProject.root )
        return

    def onFSChanged( self, items ):
        " Triggered when the watcher detects changes "
##        report = "REPORT: "
##        projectItems = []
        for item in items:
            item = str( item )

#            if not islink( item ):
#                realPath = realpath( item[ 1: ] )
#                isDir = item.endswith( sep )
#                if isDir:
#                    if self.isProjectDir( realPath + sep ):
#                        item = item[ 0 ] + realPath + sep
#                else:
#                    if self.isProjectFile( realPath + sep ):
#                        item = item[ 0 ] + realPath

#            projectItems.append( item )
##            report += " " + item
            try:
                if item.startswith( '+' ):
                    self.filesList.add( item[ 1: ] )
                else:
                    self.filesList.remove( item[ 1: ] )
##                projectItems.append( item )
            except:
#                print "EXCEPTION for '" + item + "'"
                pass
#        print "'" + report + "'"

        self.fsChanged.emit( items )
#        self.__dirWatcher.debug()
        return

    def __loadTabsStatus( self ):
        " Loads the last tabs status "
        configFile = self.userProjectDir + "tabsstatus"
        if not os.path.exists( configFile ):
            logging.info( "Cannot find tabsstatus project file. "
                          "Expected here: " + configFile )
            self.__formatOK = False
            return

        config = ConfigParser.ConfigParser()
        try:
            config.read( configFile )
        except:
            # Bad error - cannot load project file at all
            config = None
            self.__formatOK = False
            logging.warning( "Cannot read tabsstatus project file "
                             "from here: " + configFile )
            return

        # tabs part
        self.tabsStatus = self.__loadListSection( config, 'tabsstatus', 'tab' )

        config = None
        return

    def __loadProjectBrowserExpandedDirs( self ):
        " Loads the project browser expanded dirs "
        configFile = self.userProjectDir + "filebrowser"
        if not os.path.exists( configFile ):
            self.fileBrowserPaths = []
            return

        config = ConfigParser.ConfigParser()
        try:
            config.read( configFile )
        except:
            # Bad error - cannot load project file at all
            config = None
            self.fileBrowserPaths = []
            return

        # dirs part
        self.fileBrowserPaths = self.__loadListSection( config, 'filebrowser',
                                                        'path' )
        config = None
        return

    def __loadTopLevelDirs( self ):
        " Loads the top level dirs "
        configFile = self.userProjectDir + "topleveldirs"
        if not os.path.exists( configFile ):
            logging.info( "Cannot find topleveldirs project file. "
                          "Expected here: " + configFile )
            self.__formatOK = False
            return

        config = ConfigParser.ConfigParser()
        try:
            config.read( configFile )
        except:
            # Bad error - cannot load project file at all
            config = None
            self.__formatOK = False
            logging.warning( "Cannot read topleveldirs project file "
                             "from here: " + configFile )
            return

        # dirs part
        self.topLevelDirs = self.__loadListSection(
                config, 'topleveldirs', 'dir' )

        config = None
        return

    def __loadSearchHistory( self ):
        " Loads the search history file content "
        confFile = self.userProjectDir + "searchhistory"
        if not os.path.exists( confFile ):
            logging.info( "Cannot find searchhistory project file. "
                          "Expected here: " + confFile )
            self.__formatOK = False
            return

        config = ConfigParser.ConfigParser()
        try:
            config.read( confFile )
        except:
            # Bad error - cannot load project file at all
            config = None
            self.__formatOK = False
            logging.warning( "Cannot read searchhistory project file "
                             "from here: " + confFile )
            return

        # find part
        self.findHistory = self.__loadListSection(
                config, 'findhistory', 'find' )
        self.findNameHistory = self.__loadListSection(
                config, 'findnamehistory', 'find' )
        self.findFileHistory = self.__loadListSection(
                config, 'findfilehistory', 'find' )

        # replace part
        self.replaceHistory = self.__loadListSection(
                config, 'replacehistory', 'replace' )

        config = None
        return

    def __loadFindObjects( self ):
        " Loads the find objects history "
        confFile = self.userProjectDir + "findobjects"
        if not os.path.exists( confFile ):
            logging.info( "Cannot find findobjects project file. "
                          "Expected here: " + confFile )
            self.__formatOK = False
            return

        config = ConfigParser.ConfigParser()
        try:
            config.read( confFile )
        except:
            # Bad error - cannot load project file at all
            config = None
            self.__formatOK = False
            logging.warning( "Cannot read findobjects project file "
                             "from here: " + confFile )
            return

        self.findClassHistory = self.__loadListSection(
                config, 'classhistory', 'class' )
        self.findFuncHistory = self.__loadListSection(
                config, 'funchistory', 'func' )
        self.findGlobalHistory = self.__loadListSection(
                config, 'globalhistory', 'global' )
        config = None
        return

    def __loadFindFiles( self ):
        " Loads the find in files history "
        confFile = self.userProjectDir + "findinfiles"
        if not os.path.exists( confFile ):
            logging.info( "Cannot find findinfiles project file. "
                          "Expected here: " + confFile )
            self.__formatOK = False
            return

        config = ConfigParser.ConfigParser()
        try:
            config.read( confFile )
        except:
            # Bad error - cannot load project file at all
            config = None
            self.__formatOK = False
            logging.warning( "Cannot read findinfiles project file "
                             "from here: " + confFile )
            return

        self.findFilesWhat = self.__loadListSection(
                config, 'whathistory', 'what' )
        self.findFilesDirs = self.__loadListSection(
                config, 'dirhistory', 'dir' )
        self.findFilesMasks = self.__loadListSection(
                config, 'maskhistory', 'mask' )
        config = None
        return

    def __loadRecentFiles( self ):
        " Loads the recent files list "
        confFile = self.userProjectDir + "recentfiles"
        if not os.path.exists( confFile ):
            logging.info( "Cannot find recentfiles project file. "
                          "Expected here: " + confFile )
            self.__formatOK = False
            return

        config = ConfigParser.ConfigParser()
        try:
            config.read( confFile )
        except:
            # Bad error - cannot load project file at all
            config = None
            self.__formatOK = False
            logging.warning( "Cannot read recentfiles project file "
                             "from here: " + confFile )
            return

        self.recentFiles = self.__loadListSection(
                config, 'recentfiles', 'file' )

        # Due to a bug there could be the same files twice in the list.
        # The difference is doubled path separator. Fix it here.
        temp = set()
        for path in self.recentFiles:
            temp.add( os.path.normpath( path ) )
        self.recentFiles = list( temp )

        config = None
        return

    def __loadSectionFromFile( self, fileName, sectionName, itemName,
                               errMessageEntity ):
        " Loads the given section from the given file "
        confFile = self.userProjectDir + fileName
        if not os.path.exists( confFile ):
            logging.info( "Cannot find " + errMessageEntity + " file. "
                          "Expected here: " + confFile )
            self.__formatOK = False
            return []

        config = ConfigParser.ConfigParser()
        try:
            config.read( confFile )
        except:
            # Bad error - cannot load project file at all
            config = None
            self.__formatOK = False
            logging.warning( "Cannot read " + errMessageEntity + " file "
                             "from here: " + confFile )
            return []

        values = self.__loadListSection( config, sectionName, itemName )
        config = None
        return values

    def __loadIgnoredExceptions( self ):
        " Loads the ignored exceptions list "
        self.ignoredExcpt = self.__loadSectionFromFile(
                                    "ignoredexcpt", "ignoredexcpt",
                                    "excpttype", "ignored exceptions" )
        return

    def __loadBreakpoints( self ):
        " Loads the project breakpoints "
        self.breakpoints = self.__loadSectionFromFile(
                                    "breakpoints", "breakpoints",
                                    "bpoint", "breakpoints" )
        return

    def __loadWatchpoints( self ):
        " Loads the project watchpoints "
        self.watchpoints = self.__loadSectionFromFile(
                                    "watchpoints", "watchpoints",
                                    "wpoint", "watchpoints" )
        return

    def __loadListSection( self, config, section, listPrefix ):
        " Loads a list off the given section from the given file "
        items = []
        index = 0
        try:
            while True:
                item = config.get( section, listPrefix + str(index) ).strip()
                index += 1
                items.append( item )
        except ConfigParser.NoSectionError:
            self.__formatOK = False
        except ConfigParser.NoOptionError:
            pass    # Just continue
        except:
            self.__formatOK = False
        return items

    def unloadProject( self, emitSignal = True ):
        """ Unloads the current project if required """
        self.emit( SIGNAL( 'projectAboutToUnload' ) )
        if self.isLoaded():
            self.__saveProjectBrowserExpandedDirs()
        self.__resetValues()
        if emitSignal:
            # No need to send a signal e.g. if IDE is closing
            self.projectChanged.emit( self.CompleteProject )
        if self.__ropeProject is not None:
            try:
                # If the project directory is read only then closing the
                # rope project generates exception
                self.__ropeProject.close()
            except:
                pass
        self.__ropeProject = None
        self.__ropeSourceDirs = []
        return

    def __saveProjectBrowserExpandedDirs( self ):
        " Saves the pathes expanded in the project browser "
        if not self.isLoaded():
            return

        try:
            f = open( self.userProjectDir + "filebrowser", "w" )
            self.__writeHeader( f )
            self.__writeList( f, "filebrowser", "path", self.fileBrowserPaths )
            f.close()
        except:
            pass
        return

    def setImportDirs( self, paths ):
        " Sets a new set of the project import dirs "
        if self.importDirs != paths:
            self.importDirs = paths
            self.saveProject()
            self.projectChanged.emit( self.Properties )
        return

    def __generateFilesList( self ):
        """ Generates the files list having the list of dirs """
        self.filesList = set()
        path = self.getProjectDir()
        self.filesList.add( path )
        self.__scanDir( path )
        return

    def __scanDir( self, path ):
        """ Recursive function to scan one dir """
        # The path is with '/' at the end
        for item in os.listdir( path ):
            if self.shouldExclude( item ):
                continue

            # Exclude symlinks if they point to the other project
            # covered pieces
            candidate = path + item
            if islink( candidate ):
                realItem = realpath( candidate )
                if isdir( realItem ):
                    if self.isProjectDir( realItem ):
                        continue
                else:
                    if self.isProjectDir( os.path.dirname( realItem ) ):
                        continue

            if isdir( candidate ):
                self.filesList.add( candidate + sep )
                self.__scanDir( candidate + sep )
                continue
            self.filesList.add( candidate )
        return

    def isProjectDir( self, path ):
        " Returns True if the path belongs to the project "
        if not self.isLoaded():
            return False
        path = realpath( str( path ) )     # it could be a symlink
        if not path.endswith( sep ):
            path += sep
        return path.startswith( self.getProjectDir() )

    def isProjectFile( self, path ):
        " Returns True if the path belongs to the project "
        if not self.isLoaded():
            return False
        return self.isProjectDir( os.path.dirname( path ) )

    def isTopLevelDir( self, path ):
        " Checks if the path is a top level dir "
        if not path.endswith( sep ):
            path += sep
        return path in self.topLevelDirs

    def addTopLevelDir( self, path ):
        " Adds the path to the top level dirs list "
        if not path.endswith( sep ):
            path += sep
        if path in self.topLevelDirs:
            logging.warning( "Top level dir " + path +
                             " is already in the list of dirs. "
                             "Ignore adding..." )
            return
        self.topLevelDirs.append( path )
        self.__saveTopLevelDirs()
        return

    def removeTopLevelDir( self, path ):
        " Removes the path from the top level dirs list "
        if not path.endswith( sep ):
            path += sep
        if path not in self.topLevelDirs:
            logging.warning( "Top level dir " + path +
                             " is not in the list of dirs. Ignore removing..." )
            return
        self.topLevelDirs.remove( path )
        self.__saveTopLevelDirs()
        return

    def setFindNameHistory( self, history ):
        " Sets the new find name history and saves it into a file "
        self.findNameHistory = history
        self.__saveSearchHistory()
        return

    def setFindFileHistory( self, history ):
        " Sets the new find file history and saves it into a file "
        self.findFileHistory = history
        self.__saveSearchHistory()
        return

    def setFindHistory( self, history ):
        " Sets the new find history and save it into a file "
        self.findHistory = history
        self.__saveSearchHistory()
        return

    def setReplaceHistory( self, whatHistory, toHistory ):
        " Sets the new replace history and save it into a file "
        self.findHistory = whatHistory
        self.replaceHistory = toHistory
        self.__saveSearchHistory()
        return

    def setTabsStatus( self, status ):
        " Sets the new tabs status and save it into a file "
        self.tabsStatus = status
        self.__saveTabsStatus()
        return

    def setFindInFilesHistory( self, what, dirs, masks ):
        " Sets the new lists and save them into a file "
        self.findFilesWhat = what
        self.findFilesDirs = dirs
        self.findFilesMasks = masks
        self.__saveFindFiles()
        return

    def setFindClassHistory( self, history ):
        " Sets the new history and saves it into a file "
        self.findClassHistory = history
        self.__saveFindObjects()
        return

    def setFindFuncHistory( self, history ):
        " Sets the new history and saves it into a file "
        self.findFuncHistory = history
        self.__saveFindObjects()
        return

    def setFindGlobalHistory( self, history ):
        " Sets the new history and saves it into a file "
        self.findGlobalHistory = history
        self.__saveFindObjects()
        return

    def updateProperties( self, scriptName, importDirs, creationDate, author,
                          lic, copy_right, version,
                          email, description ):
        " Updates the project properties "

        if self.scriptName == scriptName and \
           self.creationDate == creationDate and \
           self.author == author and \
           self.license == lic and \
           self.copyright == copy_right and \
           self.version == version and \
           self.email == email and \
           self.description == description and \
           self.importDirs == importDirs:
            # No real changes
            return

        self.importDirs = importDirs
        self.scriptName = scriptName
        self.creationDate = creationDate
        self.author = author
        self.license = lic
        self.copyright = copy_right
        self.version = version
        self.email = email
        self.description = description
        self.saveProject()
        self.projectChanged.emit( self.Properties )
        return

    def onProjectFileUpdated( self ):
        " Called when a project file is updated via direct editing "

        scriptName, importDirs, \
        creationDate, author, \
        lic, copy_right, \
        description, version, \
        email, projectUuid = getProjectProperties( self.fileName )

        self.importDirs = importDirs
        self.scriptName = scriptName
        self.creationDate = creationDate
        self.author = author
        self.license = lic
        self.copyright = copy_right
        self.version = version
        self.email = email
        self.description = description

        # no need to save, but signal just in case
        self.projectChanged.emit( self.Properties )
        return

    def isLoaded( self ):
        " returns True if a project is loaded "
        return self.fileName != ""

    def getProjectDir( self ):
        " Provides an absolute path to the project dir "
        if not self.isLoaded():
            return ""
        return os.path.dirname( realpath( self.fileName ) ) + sep

    def getProjectScript( self ):
        " Provides the project script file name "
        if not self.isLoaded():
            return ""
        if self.scriptName == "":
            return ""
        if os.path.isabs( self.scriptName ):
            return self.scriptName
        return os.path.normpath( self.getProjectDir() + self.scriptName )

    def addRecentFile( self, path ):
        " Adds a single recent file. True if a new file was inserted. "
        if path in self.recentFiles:
            self.recentFiles.remove( path )
            self.recentFiles.insert( 0, path )
            self.__saveRecentFiles()
            return False
        self.recentFiles.insert( 0, path )
        self.__saveRecentFiles()
        if len( self.recentFiles ) > 32:
            self.recentFiles = self.recentFiles[ 0 : 32 ]
        self.emit( SIGNAL( 'recentFilesChanged' ) )
        return True

    def removeRecentFile( self, path ):
        " Removes a single recent file "
        if path in self.recentFiles:
            self.recentFiles.remove( path )
            self.__saveRecentFiles()
        return

    def addExceptionFilter( self, excptType ):
        " Adds a new ignored exception type "
        if excptType not in self.ignoredExcpt:
            self.ignoredExcpt.append( excptType )
            self.__saveIgnoredExcpt()
        return

    def deleteExceptionFilter( self, excptType ):
        " Remove ignored exception type "
        if excptType in self.ignoredExcpt:
            self.ignoredExcpt.remove( excptType )
            self.__saveIgnoredExcpt()
        return

    def setExceptionFilters( self, newFilters ):
        " Sets the new filters "
        self.ignoredExcpt = newFilters
        self.__saveIgnoredExcpt()
        return

    def addBreakpoint( self, bpointStr ):
        " Adds serialized breakpoint "
        if bpointStr not in self.breakpoints:
            self.breakpoints.append( bpointStr )
            self.__saveBreakpoints()
        return

    def deleteBreakpoint( self, bpointStr ):
        " Deletes serialized breakpoint "
        if bpointStr in self.breakpoints:
            self.breakpoints.remove( bpointStr )
            self.__saveBreakpoints()
        return

    def setBreakpoints( self, bpointStrList ):
        " Sets breakpoints list "
        self.breakpoints = bpointStrList
        self.__saveBreakpoints()
        return

    def addWatchpoint( self, wpointStr ):
        " Adds serialized watchpoint "
        if wpointStr not in self.watchpoints:
            self.watchpoints.append( wpointStr )
            self.__saveWatchpoints()
        return

    def deleteWatchpoint( self, wpointStr ):
        " Deletes serialized watchpoint "
        if wpointStr in self.watchpoints:
            self.watchpoints.remove( wpointStr )
            self.__saveWatchpoints()
        return

    def setWatchpoints( self, wpointStrList ):
        " Sets watchpoints list "
        self.watchpoints = wpointStrList
        self.__saveWatchpoints()
        return
Esempio n. 18
0
class PyRefactor(plugins.WorkspacePlugin):
    """
    Adds some refactoring capabilities to the IDE (using the rope library).

    Supported operations:
        - Rename
        - Extract method
        - Extract variable
        - Find occurrences
        - Organize imports

    """
    def activate(self):
        self._preview_dock = None
        self._occurrences_dock = None
        self._occurrences_results = None
        self._review_widget = None
        api.signals.connect_slot(api.signals.CURRENT_PROJECT_CHANGED,
                                 self._on_current_project_changed)
        api.signals.connect_slot(api.signals.EDITOR_CREATED,
                                 self._on_editor_created)
        api.signals.connect_slot(api.signals.CURRENT_EDITOR_CHANGED,
                                 self._update_edit_actions_state)
        path = api.project.get_current_project()
        self._main_project = Project(path, ropefolder=api.project.FOLDER,
                                     fscommands=FileSystemCommands())
        self._main_project.validate()
        api.signals.connect_slot(api.signals.DOCUMENT_SAVED,
                                 self._on_document_saved)

    def close(self):
        self._main_project.close()

    def rename(self):
        """
        Renames word under cursor.
        """
        editor = api.editor.get_current_editor()
        if editor is None:
            return
        editor.file.save()
        assert isinstance(editor, PyCodeEdit)
        module = libutils.path_to_resource(
            self._main_project, editor.file.path)
        self._main_project.validate()
        cursor_position = self._get_real_position(
            editor.textCursor().position())
        try:
            renamer = Rename(self._main_project, module, cursor_position)
        except RopeError:
            return
        if not renamer.get_old_name():
            return
        preview, replacement = DlgRope.rename(
            self.main_window, renamer.get_old_name())
        if preview is None and replacement is None:
            return
        multiproj = self._has_multiple_projects()
        other_projects = self._get_other_projects()
        main_project = self._main_project
        self._preview = preview
        api.tasks.start(_('Refactoring: rename'), rename_symbol,
                        self._on_changes_available,
                        args=(
                            main_project, multiproj, other_projects,
                            editor.file.path, cursor_position, replacement),
                        cancellable=True, use_thread=True)

    def organise_imports(self):
        editor = api.editor.get_current_editor()
        api.editor.save_all_editors()
        if editor is None:
            return
        self._preview = True
        file_path = editor.file.path
        project = self.get_project_for_file(file_path)
        if project:
            api.tasks.start(
                _('Refactoring: organize imports'), organize_imports,
                self._on_changes_available,
                args=(project, file_path),
                cancellable=True, use_thread=True)

    def extract_method(self):
        """
        Extracts a method from the selected text (if possible, otherwise a
        warning message will appear).
        """
        api.editor.save_all_editors()
        self._main_project.validate()
        editor = api.editor.get_current_editor()
        if editor is None or not editor.textCursor().hasSelection():
            return
        editor.file.save()
        if not editor.textCursor().hasSelection():
            TextHelper(editor).select_whole_line()
        start = self._get_real_position(editor.textCursor().selectionStart())
        end = self._get_real_position(editor.textCursor().selectionEnd())
        preview, replacement = DlgRope.extract_method(self.main_window)
        if preview is None and replacement is None:
            return
        multiproj = self._has_multiple_projects()
        other_projects = self._get_other_projects()
        main_project = self._main_project
        cursor_position_start = start
        cursor_position_end = end
        replacement = replacement
        self._preview = preview
        api.tasks.start(
            _('Refactoring: extract method'), extract_method,
            self._on_changes_available,
            args=(multiproj, main_project, other_projects, editor.file.path,
                  cursor_position_start, cursor_position_end, replacement),
            cancellable=True, use_thread=True)

    def extract_variable(self):
        """
        Extracts a variable from the selected statement (if possible).
        """
        api.editor.save_all_editors()
        self._main_project.validate()
        editor = api.editor.get_current_editor()
        if editor is None or not editor.textCursor().hasSelection():
            return
        editor.file.save()
        if not editor.textCursor().hasSelection():
            TextHelper(editor).select_whole_line()
        start = self._get_real_position(editor.textCursor().selectionStart())
        end = self._get_real_position(editor.textCursor().selectionEnd())
        preview, replacement = DlgRope.extract_variable(self.main_window)
        if preview is None and replacement is None:
            return
        multiproj = self._has_multiple_projects()
        other_projects = self._get_other_projects()
        main_project = self._main_project
        cursor_position_start = start
        cursor_position_end = end
        replacement = replacement
        self._preview = preview
        api.tasks.start(
            _('Refactoring: extract variable'), extract_variable,
            self._on_changes_available,
            args=(multiproj, main_project, other_projects, editor.file.path,
                  cursor_position_start, cursor_position_end, replacement),
            cancellable=True, use_thread=True)

    def find_usages(self):
        """
        Find all usages of the word under cursor.
        """
        api.editor.save_all_editors()
        if api.editor.get_current_editor() is None:
            return
        file_path = api.editor.get_current_path()
        api.editor.save_current_editor()
        offset = self._get_real_position(
            api.editor.get_current_editor().textCursor().position())
        self._occurrence_to_search = worder.get_name_at(
            libutils.path_to_resource(self._main_project, file_path), offset)
        main_project = api.project.get_current_project()
        other_projects = self._get_other_projects(path_only=True)
        file_path = file_path
        api.tasks.start(
            _('Refactoring: find usages'), find_usages,
            self._on_find_usages_finished,
            args=(main_project, other_projects, file_path, offset),
            cancellable=True, use_thread=True)

    def get_project_for_file(self, path):
        for prj in [self._main_project] + self._get_other_projects():
            p = prj.address + os.path.sep
            if p in path:
                return prj
        return None

    @staticmethod
    def clean_changes(pending_changes):
        if hasattr(pending_changes, '__iter__'):
            cleaned = []
            for prj, changeset in pending_changes:
                for change in changeset.changes:
                    if isinstance(change.resource.project, NoProject):
                        break
                else:
                    cleaned.append((prj, changeset))
            return cleaned
        return pending_changes

    def _on_changes_available(self, changes):
        if isinstance(changes, RefactoringError):
            api.events.post(RefactoringErrorEvent(changes), False)
            return
        _, self._pending_changes = changes

        self._pending_changes = self.clean_changes(self._pending_changes)

        if self._pending_changes is None:
            return
        if self._preview and self._pending_changes:
            self._create_review_dock()
            self._update_preview()
        else:
            self._refactor()

    def _on_find_usages_finished(self, results):
        if results is None:
            # todo: show no results notification
            return
        if isinstance(results, RefactoringError):
            api.events.post(RefactoringErrorEvent(results), False)
            return
        if self._occurrences_results is None:
            self._create_occurrences_dock()
        else:
            self._occurrences_dock.show()
            self._occurrences_dock.button.show()
            self._occurrences_dock.button.action.setVisible(True)
        self._occurrences_results.show_results(
            results, self._occurrence_to_search)

    @staticmethod
    def apply_preferences():
        for editor in api.editor.get_all_editors(True):
            if isinstance(editor, PyCodeEdit):
                actions = editor.refactoring_actions
                items = [
                    ('usages', 'Find usages', _('Find usages'), 'Alt+F7'),
                    ('rename', 'Refactor: rename', _('Refactor: rename'),
                     'Shift+F6'),
                    ('extract_method', 'Refactor: extract method',
                     _('Refactor: extract method'), 'Ctrl+Alt+M'),
                    ('extract_var', 'Refactor: extract variable',
                     _('Refactor: extract variable'), 'Ctrl+Alt+V'),
                    ('imports', 'Refactor: organize imports',
                     _('Refactor: organize imports'), 'Alt+F8')
                ]
                for id, name, text, default in items:
                    actions[id].setShortcut(api.shortcuts.get(
                        name, text, default))

                actions['extract_var'].setIcon(special_icons.variable_icon())

    def create_refactor_menu(self, editor):
        """
        Creates the refactor menu; this menu will appear in the menu bar of the
        main window and in the context menu of any python editor.
        """
        mnu_refactor = QtWidgets.QMenu(editor)
        mnu_refactor.setTitle(_('Refactor'))
        mnu_refactor.setIcon(QtGui.QIcon.fromTheme('edit-rename'))

        action_find_usages = mnu_refactor.addAction(_('Find usages'))
        action_find_usages.setToolTip(
            _('Find occurrences of symbol under cursor'))
        action_find_usages.setIcon(QtGui.QIcon.fromTheme('edit-find'))
        action_find_usages.setShortcut(
            api.shortcuts.get('Find usages', _('Find usages'), 'Alt+F7'))
        action_find_usages.triggered.connect(self.find_usages)

        # Rename
        action_rename = mnu_refactor.addAction(_('Rename'))
        action_rename.setToolTip(_('Rename symnbol under cursor'))
        action_rename.setIcon(QtGui.QIcon.fromTheme('edit-find-replace'))
        action_rename.setShortcut(
            api.shortcuts.get('Refactor: rename', _('Refactor: rename'),
                              'Shift+F6'))
        action_rename.triggered.connect(self.rename)

        # Extract variable
        action_extract_var = mnu_refactor.addAction(_('Extract variable'))
        action_extract_var.setToolTip(
            _('Extract variable (a statement must be selected)'))
        action_extract_var.setIcon(special_icons.variable_icon())
        action_extract_var.setShortcut(
            api.shortcuts.get('Refactor: extract variable',
                              _('Refactor: extract variable'), 'Ctrl+Alt+V'))
        action_extract_var.triggered.connect(self.extract_variable)

        # Extract method
        action_extract_method = mnu_refactor.addAction(_('Extract method'))
        action_extract_method.setToolTip(
            _('Extract method (some statements must be selected)'))
        action_extract_method.setIcon(special_icons.function_icon())
        action_extract_method.setShortcut(
            api.shortcuts.get('Refactor: extract method',
                              _('Refactor: extract method'), 'Ctrl+Alt+M'))
        action_extract_method.triggered.connect(self.extract_method)

        mnu_refactor.addSeparator()

        action_organize_imports = mnu_refactor.addAction(_('Organize imports'))
        action_organize_imports.setToolTip(
            _('Organize top level imports (sort, remove unused,...)'))
        action_organize_imports.setIcon(special_icons.namespace_icon())
        action_organize_imports.setShortcut(
            api.shortcuts.get('Refactor: organize imports',
                              _('Refactor: organize imports'), 'Alt+F8'))
        action_organize_imports.triggered.connect(self.organise_imports)

        actions = {
            'usages': action_find_usages,
            'rename': action_rename,
            'extract_method': action_extract_method,
            'extract_var': action_extract_var,
            'imports': action_organize_imports
        }

        return mnu_refactor, actions

    def _on_current_project_changed(self, path):
        """
        Changes the active rope project when the current project changed in
        the IDE.
        :param path: Path of the new project.
        """
        self._main_project.close()
        self._main_project = Project(path, ropefolder=api.project.FOLDER,
                                     fscommands=FileSystemCommands())
        self._main_project.validate()

    def _on_editor_created(self, editor):
        """
        Adds the refactor menu to the editor that have just been created.
        :param editor: editor to modify.
        """
        if isinstance(editor, PyCodeEdit):
            sep = QtWidgets.QAction(editor)
            sep.setSeparator(True)
            menu, actions = self.create_refactor_menu(editor)
            editor.insert_action(menu.menuAction(), 0)
            editor.refactoring_actions = actions
            editor.insert_action(sep, 1)
            editor.cursorPositionChanged.connect(
                self._update_edit_actions_state)

    @staticmethod
    def _update_edit_actions_state(editor=None):
        if editor is None:
            editor = api.editor.get_current_editor()
        if isinstance(editor, PyCodeEdit):
            flg = bool(TextHelper(editor).word_under_cursor(
                select_whole_word=True).selectedText())
            try:
                editor.refactoring_actions
            except AttributeError:
                return
            else:
                editor.refactoring_actions['usages'].setEnabled(flg)
                editor.refactoring_actions['rename'].setEnabled(flg)
                flg = editor.textCursor().hasSelection()
                editor.refactoring_actions['extract_method'].setEnabled(flg)
                editor.refactoring_actions['extract_var'].setEnabled(flg)

    def _create_occurrences_dock(self):
        """
        Creates the dock widget that shows all the occurrences of a python
        name.
        """
        self._occurrences_widget = QtWidgets.QWidget()
        vlayout = QtWidgets.QVBoxLayout()
        # buttons
        self._setup_occurrences_buttons(vlayout)
        self._occurrences_results = FindResultsWidget(term='usages')
        self._occurrences_results.itemActivated.connect(
            self._on_occurrence_activated)
        vlayout.addWidget(self._occurrences_results)
        self._occurrences_widget.setLayout(vlayout)
        # Dock widget
        self._occurrences_dock = api.window.add_dock_widget(
            self._occurrences_widget, _('Find usages'),
            QtGui.QIcon.fromTheme('edit-find'),
            QtCore.Qt.BottomDockWidgetArea)

    @staticmethod
    def _on_occurrence_activated(item):
        assert isinstance(item, QtWidgets.QTreeWidgetItem)
        data = item.data(0, QtCore.Qt.UserRole)
        try:
            l = data['line']
        except TypeError:
            return  # file item or root item
        l = data['line']
        start = data['start']
        lenght = data['end'] - start
        if data is not None:
            # open editor and go to line/column
            editor = api.editor.open_file(
                data['path'], data['line'], data['start'])
            if editor is None:
                return
            # select text
            helper = TextHelper(editor)
            cursor = helper.select_lines(start=l, end=l)
            cursor.movePosition(cursor.StartOfBlock)
            cursor.movePosition(cursor.Right, cursor.MoveAnchor, start)
            cursor.movePosition(cursor.Right, cursor.KeepAnchor, lenght)
            editor.setTextCursor(cursor)

    def _setup_occurrences_buttons(self, vlayout):
        """
        Creates the occurrences dock widget buttons
        :param vlayout: main layout
        """
        buttons = QtWidgets.QWidget()
        buttons_layout = QtWidgets.QHBoxLayout()
        buttons_layout.setContentsMargins(0, 0, 0, 0)
        # Close
        bt = QtWidgets.QPushButton()
        bt.setText(_('Close'))
        bt.clicked.connect(self._remove_occurrences_dock)
        buttons_layout.addWidget(bt)
        # Spacer
        buttons_layout.addSpacerItem(QtWidgets.QSpacerItem(
            20, 20, QtWidgets.QSizePolicy.Expanding))
        buttons.setLayout(buttons_layout)
        vlayout.addWidget(buttons)

    def _create_review_dock(self):
        """
        Creates the dock widget that shows the refactor diff.
        """
        if self._review_widget:
            self._preview_dock.show()
            self._preview_dock.button.show()
            self._preview_dock.button.action.setVisible(True)
            return
        self._review_widget = QtWidgets.QWidget()
        vlayout = QtWidgets.QVBoxLayout()
        # buttons
        bt_refactor = self._setup_review_buttons(vlayout)
        # Diff viewer
        self._viewer = DiffViewer()
        vlayout.addWidget(self._viewer)
        self._review_widget.setLayout(vlayout)

        # Dock widget
        self._preview_dock = api.window.add_dock_widget(
            self._review_widget, _('Review'),
            QtGui.QIcon.fromTheme('edit-find'),
            QtCore.Qt.BottomDockWidgetArea)
        bt_refactor.setFocus()

    def _update_preview(self):
        try:
            texts = []
            for prj, changes in self._pending_changes:
                lines = ''
                for subchanges in changes.changes:
                    resource = subchanges.get_changed_resources()[0]
                    resource_path = resource.real_path
                    if prj.address + os.sep in resource_path:
                        desc = subchanges.get_description()
                        lines += desc
                if lines:
                    texts.append('%s\n\n*** Project: %s\n\n%s\n' %
                                 (str(changes), prj.address, lines))
            self._viewer.setPlainText('\n'.join(texts))
        except TypeError:
            # not multiproj, one single change set
            self._viewer.setPlainText(self._pending_changes.get_description())

    def _setup_review_buttons(self, vlayout):
        """
        Creates the buttons of the preview dock widget
        :param vlayout: main layout
        """
        buttons = QtWidgets.QWidget()
        buttons_layout = QtWidgets.QHBoxLayout()
        buttons_layout.setContentsMargins(0, 0, 0, 0)
        bt_refactor = bt = QtWidgets.QPushButton()
        bt.setText(_('Refactor'))
        bt.clicked.connect(self._refactor)
        buttons_layout.addWidget(bt)
        # Close
        bt = QtWidgets.QPushButton()
        bt.setText(_('Cancel'))
        bt.clicked.connect(self._remove_preview_dock)
        buttons_layout.addWidget(bt)
        # Spacer
        buttons_layout.addSpacerItem(QtWidgets.QSpacerItem(
            20, 20, QtWidgets.QSizePolicy.Expanding))
        buttons.setLayout(buttons_layout)
        vlayout.addWidget(buttons)

        return bt_refactor

    def _remove_preview_dock(self):
        """
        Removes the preview dock widget
        """
        if self._preview_dock is not None:
            self._preview_dock.hide()
            self._preview_dock.button.hide()
            self._preview_dock.button.action.setVisible(False)

    def _remove_occurrences_dock(self):
        """
        Removes the occurrences dock widget.
        """
        if self._occurrences_dock is not None:
            self._occurrences_dock.hide()
            self._occurrences_dock.button.hide()
            self._occurrences_dock.button.action.setVisible(False)

    def _refactor(self):
        """
        Performs the refactoring.
        """
        main_project = self._main_project
        multiproj = self._has_multiple_projects()
        pending_changes = self._pending_changes
        api.tasks.start(_('Refactoring: apply pending changes'),
                        apply_pending_changes, self._on_refactoring_finished,
                        args=(main_project, multiproj, pending_changes),
                        use_thread=True)

    def _on_refactoring_finished(self, ret_val):
        self._remove_preview_dock()
        if ret_val is not True:
            api.events.post(RefactoringErrorEvent(ret_val), False)

    @staticmethod
    def _get_other_projects(path_only=False):
        """
        Gets the list of secondary projects (all except current).
        """
        projects = []
        current = api.project.get_current_project()
        for path in api.project.get_projects():
            if path == current:
                continue
            if not path_only:
                prj = Project(path, ropefolder=api.project.FOLDER,
                              fscommands=FileSystemCommands())
                prj.validate()
            else:
                prj = path
            projects.append(prj)
        return projects

    @staticmethod
    def _has_multiple_projects():
        """
        Checks whether multiple project have been opened in the main window.
        :return: True if window has multiple project, False if window only has
                 one project.
        """
        return len(api.project.get_projects()) > 1

    @staticmethod
    def _on_document_saved(path, old_content):
        if not path:
            return
        project = None
        for project in api.project.get_projects():
            prj_path = project + os.sep
            if prj_path in path:
                project = Project(prj_path, ropefolder=api.project.FOLDER,
                                  fscommands=FileSystemCommands())
                break
        if project:
            if path.endswith('_rc.py'):
                return
            api.tasks.start(_('Refactoring: reporting changes'),
                            report_changes, None,
                            args=(project, path, old_content),
                            use_thread=False)

    @staticmethod
    def _get_real_position(position):
        """
        Gets the real cursor position (there might be a difference between
        editor content and file system content because of clean_trailing
        whitespaces on save). This function will converts the absolute position
        into a line/column pair and use this info to get the position in the
        file.
        """
        tc = api.editor.get_current_editor().textCursor()
        tc.setPosition(position)
        l = tc.blockNumber()
        c = tc.positionInBlock()
        e = api.editor.get_current_editor().file.encoding
        path = api.editor.get_current_editor().file.path
        try:
            with open(path, 'r', encoding=e) as f:
                lines = f.readlines()
        except OSError:
            _logger().exception('failed to read file %s', path)
            lines = []
        real_pos = 0
        for i, line in enumerate(lines):
            if i == l:
                real_pos += c
                break
            else:
                real_pos += len(line)
        return real_pos