" ============================================================================ " 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:] == nerdtree#slash() let l:result = l:result . a:pathStr else let l:result = l:result . nerdtree#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 if g:NERDTreeUseTCD && exists(':tcd') ==# 2 execute 'tcd ' . dir call nerdtree#echo("Tab's CWD is now: " . getcwd()) else execute 'cd ' . dir call nerdtree#echo('CWD is now: ' . getcwd()) endif catch throw 'NERDTree.PathChangeError: cannot change CWD to ' . dir endtry endfunction " FUNCTION: Path.Create(fullpath) {{{1 " " Factory method. " " Creates a path object with the given path. The path is also created on the " filesystem. If the path already exists, a NERDTree.Path.Exists exception is " thrown. If any other errors occur, a NERDTree.Path exception is thrown. " " Args: " fullpath: the full filesystem path to the file/dir to create function! s:Path.Create(fullpath) "bail if the a:fullpath already exists if isdirectory(a:fullpath) || filereadable(a:fullpath) throw "NERDTree.CreatePathError: Directory Exists: '" . a:fullpath . "'" endif try "if it ends with a slash, assume its a dir create it if a:fullpath =~# '\(\\\|\/\)$' "whack the trailing slash off the end if it exists let fullpath = substitute(a:fullpath, '\(\\\|\/\)$', '', '') call mkdir(fullpath, 'p') "assume its a file and create else call s:Path.createParentDirectories(a:fullpath) call writefile([], a:fullpath) endif catch throw "NERDTree.CreatePathError: Could not create path: '" . a:fullpath . "'" endtry return s:Path.New(a:fullpath) endfunction " FUNCTION: Path.copy(dest) {{{1 " " Copies the file/dir represented by this Path to the given location " " Args: " dest: the location to copy this dir/file to function! s:Path.copy(dest) if !s:Path.CopyingSupported() throw 'NERDTree.CopyingNotSupportedError: Copying is not supported on this OS' endif call s:Path.createParentDirectories(a:dest) if exists('g:NERDTreeCopyCmd') let cmd_prefix = g:NERDTreeCopyCmd else let cmd_prefix = (self.isDirectory ? g:NERDTreeCopyDirCmd : g:NERDTreeCopyFileCmd) endif let cmd = cmd_prefix . ' ' . shellescape(self.str()) . ' ' . shellescape(a:dest) let success = system(cmd) if v:shell_error !=# 0 throw "NERDTree.CopyError: Could not copy '". self.str() ."' to: '" . a:dest . "'" endif endfunction " FUNCTION: Path.CopyingSupported() {{{1 " " returns 1 if copying is supported for this OS function! s:Path.CopyingSupported() return exists('g:NERDTreeCopyCmd') || (exists('g:NERDTreeCopyDirCmd') && exists('g:NERDTreeCopyFileCmd')) endfunction " FUNCTION: Path.copyingWillOverwrite(dest) {{{1 " " returns 1 if copy this path to the given location will cause files to " overwritten " " Args: " dest: the location this path will be copied to function! s:Path.copyingWillOverwrite(dest) if filereadable(a:dest) return 1 endif if isdirectory(a:dest) let path = s:Path.JoinPathStrings(a:dest, self.getLastPathComponent(0)) if filereadable(path) return 1 endif endif endfunction " FUNCTION: Path.createParentDirectories(path) {{{1 " " create parent directories for this path if needed " without throwing any errors if those directories already exist " " Args: " path: full path of the node whose parent directories may need to be created function! s:Path.createParentDirectories(path) let dir_path = fnamemodify(a:path, ':h') if !isdirectory(dir_path) call mkdir(dir_path, 'p') endif endfunction " FUNCTION: Path.delete() {{{1 " " Deletes the file or directory represented by this path. " " Throws NERDTree.Path.Deletion exceptions function! s:Path.delete() if self.isDirectory let cmd = g:NERDTreeRemoveDirCmd . self.str({'escape': 1}) let success = system(cmd) if v:shell_error !=# 0 throw "NERDTree.PathDeletionError: Could not delete directory: '" . self.str() . "'" endif else if exists('g:NERDTreeRemoveFileCmd') let cmd = g:NERDTreeRemoveFileCmd . self.str({'escape': 1}) let success = system(cmd) else let success = delete(self.str()) endif if success !=# 0 throw "NERDTree.PathDeletionError: Could not delete file: '" . self.str() . "'" endif endif "delete all bookmarks for this path for i in self.bookmarkNames() let bookmark = g:NERDTreeBookmark.BookmarkFor(i) call bookmark.delete() endfor endfunction " FUNCTION: Path.displayString() {{{1 " " Returns a string that specifies how the path should be represented as a " string function! s:Path.displayString() if self.cachedDisplayString ==# '' call self.cacheDisplayString() endif return self.cachedDisplayString endfunction " FUNCTION: Path.edit() {{{1 function! s:Path.edit() let l:bufname = self.str({'format': 'Edit'}) if bufname('%') !=# l:bufname exec 'edit ' . l:bufname endif endfunction " FUNCTION: Path.extractDriveLetter(fullpath) {{{1 " " If running windows, cache the drive letter for this path function! s:Path.extractDriveLetter(fullpath) if nerdtree#runningWindows() if a:fullpath =~# '^\(\\\\\|\/\/\)' "For network shares, the 'drive' consists of the first two parts of the path, i.e. \\boxname\share let self.drive = substitute(a:fullpath, '^\(\(\\\\\|\/\/\)[^\\\/]*\(\\\|\/\)[^\\\/]*\).*', '\1', '') let self.drive = substitute(self.drive, '/', '\', 'g') else let self.drive = substitute(a:fullpath, '\(^[a-zA-Z]:\).*', '\1', '') endif else let self.drive = '' endif endfunction " FUNCTION: Path.exists() {{{1 " return 1 if this path points to a location that is readable or is a directory function! s:Path.exists() let p = self.str() return filereadable(p) || isdirectory(p) endfunction " FUNCTION: Path._escChars() {{{1 function! s:Path._escChars() if nerdtree#runningWindows() return " `\|\"#%&,?()\*^<>$" 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 g:NERDTreeSortOrder[i] !~? '\[\[-\?\(timestamp\|size\|extension\)\]\]' && \ 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() if !exists('self._sortKey') || g:NERDTreeSortOrder !=# g:NERDTreeOldSortOrder " Look for file metadata tags: [[timestamp]], [[extension]], [[size]] let metadata = [] for tag in g:NERDTreeSortOrder if tag =~? '\[\[-\?timestamp\]\]' let metadata += [self.isDirectory ? 0 : getftime(self.str()) * (tag =~# '-' ? -1 : 1)] elseif tag =~? '\[\[-\?size\]\]' let metadata += [self.isDirectory ? 0 : getfsize(self.str()) * (tag =~# '-' ? -1 : 1)] elseif tag =~? '\[\[extension\]\]' let extension = matchstr(self.getLastPathComponent(0), '[^.]\+\.\zs[^.]\+$') let metadata += [self.isDirectory ? '' : (extension ==# '' ? nr2char(str2nr('0x10ffff',16)) : extension)] endif endfor if g:NERDTreeSortOrder[0] =~# '\[\[.*\]\]' " Apply tags' sorting first if specified first. let self._sortKey = metadata + [self.getSortOrderIndex()] else " Otherwise, do regex grouping first. let self._sortKey = [self.getSortOrderIndex()] + metadata endif let path = self.getLastPathComponent(0) 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 l:Callback in g:NERDTree.PathFilters() let l:Callback = type(l:Callback) ==# type(function('tr')) ? l:Callback : function(l:Callback) if l: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)-8) ==# '[[path]]' let pat = strpart(pat,0, len(pat)-8) return self.str() =~# pat elseif 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(child) return a:child.isUnder(self) endfunction " FUNCTION: Path.isUnder(path) {{{1 " return 1 if this path is somewhere under the given path in the filesystem. function! s:Path.isUnder(parent) if a:parent.isDirectory ==# 0 return 0 endif if nerdtree#runningWindows() && a:parent.drive !=# self.drive return 0 endif let l:this_count = len(self.pathSegments) if l:this_count ==# 0 return 0 endif let l:that_count = len(a:parent.pathSegments) if l:that_count ==# 0 return 1 endif if l:that_count >= l:this_count return 0 endif for i in range(0, l:that_count-1) if self.pathSegments[i] !=# a:parent.pathSegments[i] return 0 endif endfor return 1 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.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 = '