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
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
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
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
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)
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
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
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
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
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
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]
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
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
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, )
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
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