" ============================================================================ " CLASS: Path " " The Path class provides an abstracted representation of a file system " pathname. Various operations on pathnames are provided and a number of " representations of a given path name can be accessed here. " ============================================================================ let s:Path = {} let g:NERDTreePath = s:Path " FUNCTION: Path.AbsolutePathFor(pathStr) {{{1 function! s:Path.AbsolutePathFor(pathStr) let l:prependWorkingDir = 0 if nerdtree#runningWindows() let l:prependWorkingDir = a:pathStr !~# '^.:\(\\\|\/\)\?' && a:pathStr !~# '^\(\\\\\|\/\/\)' else let l:prependWorkingDir = a:pathStr !~# '^/' endif let l:result = a:pathStr if l:prependWorkingDir let l:result = getcwd() if l:result[-1:] == s:Path.Slash() let l:result = l:result . a:pathStr else let l:result = l:result . s:Path.Slash() . a:pathStr endif endif return l:result endfunction " FUNCTION: Path.bookmarkNames() {{{1 function! s:Path.bookmarkNames() if !exists("self._bookmarkNames") call self.cacheDisplayString() endif return self._bookmarkNames endfunction " FUNCTION: Path.cacheDisplayString() {{{1 function! s:Path.cacheDisplayString() abort let self.cachedDisplayString = g:NERDTreeNodeDelimiter . self.getLastPathComponent(1) if self.isExecutable let self.cachedDisplayString = self.addDelimiter(self.cachedDisplayString) . '*' endif let self._bookmarkNames = [] for i in g:NERDTreeBookmark.Bookmarks() if i.path.equals(self) call add(self._bookmarkNames, i.name) endif endfor if !empty(self._bookmarkNames) && g:NERDTreeMarkBookmarks == 1 let self.cachedDisplayString = self.addDelimiter(self.cachedDisplayString) . ' {' . join(self._bookmarkNames) . '}' endif if self.isSymLink let self.cachedDisplayString = self.addDelimiter(self.cachedDisplayString) . ' -> ' . self.symLinkDest endif if self.isReadOnly let self.cachedDisplayString = self.addDelimiter(self.cachedDisplayString) . ' ['.g:NERDTreeGlyphReadOnly.']' endif endfunction " FUNCTION: Path.addDelimiter() {{{1 function! s:Path.addDelimiter(line) if a:line =~# '\(.*' . g:NERDTreeNodeDelimiter . '\)\{2}' return a:line else return a:line . g:NERDTreeNodeDelimiter endif endfunction " FUNCTION: Path.changeToDir() {{{1 function! s:Path.changeToDir() let dir = self.str({'format': 'Cd'}) if self.isDirectory ==# 0 let dir = self.getParent().str({'format': 'Cd'}) endif try execute "cd " . dir call nerdtree#echo("CWD is now: " . getcwd()) catch throw "NERDTree.PathChangeError: cannot change CWD to " . dir endtry endfunction " FUNCTION: Path.compareTo() {{{1 " " Compares this Path to the given path and returns 0 if they are equal, -1 if " this Path is "less than" the given path, or 1 if it is "greater". " " Args: " path: the path object to compare this to " " Return: " 1, -1 or 0 function! s:Path.compareTo(path) let thisPath = self.getLastPathComponent(1) let thatPath = a:path.getLastPathComponent(1) "if the paths are the same then clearly we return 0 if thisPath ==# thatPath return 0 endif let thisSS = self.getSortOrderIndex() let thatSS = a:path.getSortOrderIndex() "compare the sort sequences, if they are different then the return "value is easy if thisSS < thatSS return -1 elseif thisSS > thatSS return 1 else if !g:NERDTreeSortHiddenFirst let thisPath = substitute(thisPath, '^[._]', '', '') let thatPath = substitute(thatPath, '^[._]', '', '') endif "if the sort sequences are the same then compare the paths "alphabetically let pathCompare = g:NERDTreeCaseSensitiveSort ? thisPath <# thatPath : thisPath $" endif return " \\`\|\"#%&,?()\*^<>[]$" endfunction " FUNCTION: Path.getDir() {{{1 " " Returns this path if it is a directory, else this paths parent. " " Return: " a Path object function! s:Path.getDir() if self.isDirectory return self else return self.getParent() endif endfunction " FUNCTION: Path.getParent() {{{1 " " Returns a new path object for this paths parent " " Return: " a new Path object function! s:Path.getParent() if nerdtree#runningWindows() let path = self.drive . '\' . join(self.pathSegments[0:-2], '\') else let path = '/'. join(self.pathSegments[0:-2], '/') endif return s:Path.New(path) endfunction " FUNCTION: Path.getLastPathComponent(dirSlash) {{{1 " " Gets the last part of this path. " " Args: " dirSlash: if 1 then a trailing slash will be added to the returned value for " directory nodes. function! s:Path.getLastPathComponent(dirSlash) if empty(self.pathSegments) return '' endif let toReturn = self.pathSegments[-1] if a:dirSlash && self.isDirectory let toReturn = toReturn . '/' endif return toReturn endfunction " FUNCTION: Path.getSortOrderIndex() {{{1 " returns the index of the pattern in g:NERDTreeSortOrder that this path matches function! s:Path.getSortOrderIndex() let i = 0 while i < len(g:NERDTreeSortOrder) if self.getLastPathComponent(1) =~# g:NERDTreeSortOrder[i] return i endif let i = i + 1 endwhile return index(g:NERDTreeSortOrder, '*') endfunction " FUNCTION: Path._splitChunks(path) {{{1 " returns a list of path chunks function! s:Path._splitChunks(path) let chunks = split(a:path, '\(\D\+\|\d\+\)\zs') let i = 0 while i < len(chunks) "convert number literals to numbers if match(chunks[i], '^\d\+$') == 0 let chunks[i] = str2nr(chunks[i]) endif let i = i + 1 endwhile return chunks endfunction " FUNCTION: Path.getSortKey() {{{1 " returns a key used in compare function for sorting function! s:Path.getSortKey() let l:ascending = index(g:NERDTreeSortOrder,'[[timestamp]]') let l:descending = index(g:NERDTreeSortOrder,'[[-timestamp]]') if !exists("self._sortKey") || g:NERDTreeSortOrder !=# g:NERDTreeOldSortOrder || l:ascending >= 0 || l:descending >= 0 let self._sortKey = [self.getSortOrderIndex()] if l:descending >= 0 call insert(self._sortKey, -getftime(self.str()), l:descending == 0 ? 0 : len(self._sortKey)) elseif l:ascending >= 0 call insert(self._sortKey, getftime(self.str()), l:ascending == 0 ? 0 : len(self._sortKey)) endif let path = self.getLastPathComponent(1) if !g:NERDTreeSortHiddenFirst let path = substitute(path, '^[._]', '', '') endif if !g:NERDTreeCaseSensitiveSort let path = tolower(path) endif call extend(self._sortKey, (g:NERDTreeNaturalSort ? self._splitChunks(path) : [path])) endif return self._sortKey endfunction " FUNCTION: Path.isHiddenUnder(path) {{{1 function! s:Path.isHiddenUnder(path) if !self.isUnder(a:path) return 0 endif let l:startIndex = len(a:path.pathSegments) let l:segments = self.pathSegments[l:startIndex : ] for l:segment in l:segments if l:segment =~# '^\.' return 1 endif endfor return 0 endfunction " FUNCTION: Path.isUnixHiddenFile() {{{1 " check for unix hidden files function! s:Path.isUnixHiddenFile() return self.getLastPathComponent(0) =~# '^\.' endfunction " FUNCTION: Path.isUnixHiddenPath() {{{1 " check for unix path with hidden components function! s:Path.isUnixHiddenPath() if self.getLastPathComponent(0) =~# '^\.' return 1 else for segment in self.pathSegments if segment =~# '^\.' return 1 endif endfor return 0 endif endfunction " FUNCTION: Path.ignore(nerdtree) {{{1 " returns true if this path should be ignored function! s:Path.ignore(nerdtree) "filter out the user specified paths to ignore if a:nerdtree.ui.isIgnoreFilterEnabled() for i in g:NERDTreeIgnore if self._ignorePatternMatches(i) return 1 endif endfor for callback in g:NERDTree.PathFilters() if {callback}({'path': self, 'nerdtree': a:nerdtree}) return 1 endif endfor endif "dont show hidden files unless instructed to if !a:nerdtree.ui.getShowHidden() && self.isUnixHiddenFile() return 1 endif if a:nerdtree.ui.getShowFiles() ==# 0 && self.isDirectory ==# 0 return 1 endif return 0 endfunction " FUNCTION: Path._ignorePatternMatches(pattern) {{{1 " returns true if this path matches the given ignore pattern function! s:Path._ignorePatternMatches(pattern) let pat = a:pattern if strpart(pat,len(pat)-7) == '[[dir]]' if !self.isDirectory return 0 endif let pat = strpart(pat,0, len(pat)-7) elseif strpart(pat,len(pat)-8) == '[[file]]' if self.isDirectory return 0 endif let pat = strpart(pat,0, len(pat)-8) endif return self.getLastPathComponent(0) =~# pat endfunction " FUNCTION: Path.isAncestor(path) {{{1 " return 1 if this path is somewhere above the given path in the filesystem. " " a:path should be a dir function! s:Path.isAncestor(path) if !self.isDirectory return 0 endif let this = self.str() let that = a:path.str() return stridx(that, this) == 0 endfunction " FUNCTION: Path.isUnder(path) {{{1 " return 1 if this path is somewhere under the given path in the filesystem. function! s:Path.isUnder(path) if a:path.isDirectory == 0 return 0 endif let this = self.str() let that = a:path.str() return stridx(this, that . s:Path.Slash()) == 0 endfunction " FUNCTION: Path.JoinPathStrings(...) {{{1 function! s:Path.JoinPathStrings(...) let components = [] for i in a:000 let components = extend(components, split(i, '/')) endfor return '/' . join(components, '/') endfunction " FUNCTION: Path.equals() {{{1 " " Determines whether 2 path objects are "equal". " They are equal if the paths they represent are the same " " Args: " path: the other path obj to compare this with function! s:Path.equals(path) if nerdtree#runningWindows() return self.str() ==? a:path.str() else return self.str() ==# a:path.str() endif endfunction " FUNCTION: Path.New(pathStr) {{{1 function! s:Path.New(pathStr) let l:newPath = copy(self) call l:newPath.readInfoFromDisk(s:Path.AbsolutePathFor(a:pathStr)) let l:newPath.cachedDisplayString = '' let l:newPath.flagSet = g:NERDTreeFlagSet.New() return l:newPath endfunction " FUNCTION: Path.Slash() {{{1 " Return the path separator used by the underlying file system. Special " consideration is taken for the use of the 'shellslash' option on Windows " systems. function! s:Path.Slash() if nerdtree#runningWindows() if exists('+shellslash') && &shellslash return '/' endif return '\' endif return '/' endfunction " FUNCTION: Path.Resolve() {{{1 " Invoke the vim resolve() function and return the result " This is necessary because in some versions of vim resolve() removes trailing " slashes while in other versions it doesn't. This always removes the trailing " slash function! s:Path.Resolve(path) let tmp = resolve(a:path) return tmp =~# '.\+/$' ? substitute(tmp, '/$', '', '') : tmp endfunction " FUNCTION: Path.readInfoFromDisk(fullpath) {{{1 " " " Throws NERDTree.Path.InvalidArguments exception. function! s:Path.readInfoFromDisk(fullpath) call self.extractDriveLetter(a:fullpath) let fullpath = s:Path.WinToUnixPath(a:fullpath) if getftype(fullpath) ==# "fifo" throw "NERDTree.InvalidFiletypeError: Cant handle FIFO files: " . a:fullpath endif let self.pathSegments = filter(split(fullpath, '/'), '!empty(v:val)') let self.isReadOnly = 0 if isdirectory(a:fullpath) let self.isDirectory = 1 elseif filereadable(a:fullpath) let self.isDirectory = 0 let self.isReadOnly = filewritable(a:fullpath) ==# 0 else throw "NERDTree.InvalidArgumentsError: Invalid path = " . a:fullpath endif let self.isExecutable = 0 if !self.isDirectory let self.isExecutable = getfperm(a:fullpath) =~# 'x' endif "grab the last part of the path (minus the trailing slash) let lastPathComponent = self.getLastPathComponent(0) "get the path to the new node with the parent dir fully resolved let hardPath = s:Path.Resolve(self.strTrunk()) . '/' . lastPathComponent "if the last part of the path is a symlink then flag it as such let self.isSymLink = (s:Path.Resolve(hardPath) != hardPath) if self.isSymLink let self.symLinkDest = s:Path.Resolve(fullpath) "if the link is a dir then slap a / on the end of its dest if isdirectory(self.symLinkDest) "we always wanna treat MS windows shortcuts as files for "simplicity if hardPath !~# '\.lnk$' let self.symLinkDest = self.symLinkDest . '/' endif endif endif endfunction " FUNCTION: Path.refresh(nerdtree) {{{1 function! s:Path.refresh(nerdtree) call self.readInfoFromDisk(self.str()) call g:NERDTreePathNotifier.NotifyListeners('refresh', self, a:nerdtree, {}) call self.cacheDisplayString() endfunction " FUNCTION: Path.refreshFlags(nerdtree) {{{1 function! s:Path.refreshFlags(nerdtree) call g:NERDTreePathNotifier.NotifyListeners('refreshFlags', self, a:nerdtree, {}) call self.cacheDisplayString() endfunction " FUNCTION: Path.rename() {{{1 " " Renames this node on the filesystem function! s:Path.rename(newPath) if a:newPath ==# '' throw "NERDTree.InvalidArgumentsError: Invalid newPath for renaming = ". a:newPath endif call s:Path.createParentDirectories(a:newPath) let success = rename(self.str(), a:newPath) if success != 0 throw "NERDTree.PathRenameError: Could not rename: '" . self.str() . "'" . 'to:' . a:newPath endif call self.readInfoFromDisk(a:newPath) for i in self.bookmarkNames() let b = g:NERDTreeBookmark.BookmarkFor(i) call b.setPath(copy(self)) endfor call g:NERDTreeBookmark.Write() endfunction " FUNCTION: Path.str() {{{1 " Return a string representation of this Path object. " " Args: " This function takes a single dictionary (optional) with keys and values that " specify how the returned pathname should be formatted. " " The dictionary may have the following keys: " 'format' " 'escape' " 'truncateTo' " " The 'format' key may have a value of: " 'Cd' - a string to be used with ":cd" and similar commands " 'Edit' - a string to be used with ":edit" and similar commands " 'UI' - a string to be displayed in the NERDTree user interface " " The 'escape' key, if specified, will cause the output to be escaped with " Vim's internal "shellescape()" function. " " The 'truncateTo' key shortens the length of the path to that given by the " value associated with 'truncateTo'. A '<' is prepended. function! s:Path.str(...) let options = a:0 ? a:1 : {} let toReturn = "" if has_key(options, 'format') let format = options['format'] if has_key(self, '_strFor' . format) exec 'let toReturn = self._strFor' . format . '()' else throw 'NERDTree.UnknownFormatError: unknown format "'. format .'"' endif else let toReturn = self._str() endif if nerdtree#has_opt(options, 'escape') let toReturn = shellescape(toReturn) endif if has_key(options, 'truncateTo') let limit = options['truncateTo'] if strdisplaywidth(toReturn) > limit-1 while strdisplaywidth(toReturn) > limit-1 && strchars(toReturn) > 0 let toReturn = substitute(toReturn, '^.', '', '') endwhile if len(split(toReturn, '/')) > 1 let toReturn = '