You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
486 lines
15 KiB
VimL
486 lines
15 KiB
VimL
"CLASS: TreeFileNode
|
|
"This class is the parent of the TreeDirNode class and is the
|
|
"'Component' part of the composite design pattern between the treenode
|
|
"classes.
|
|
"============================================================
|
|
let s:TreeFileNode = {}
|
|
let g:NERDTreeFileNode = s:TreeFileNode
|
|
|
|
"FUNCTION: TreeFileNode.activate(...) {{{1
|
|
function! s:TreeFileNode.activate(...)
|
|
call self.open(a:0 ? a:1 : {})
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.bookmark(name) {{{1
|
|
"bookmark this node with a:name
|
|
function! s:TreeFileNode.bookmark(name)
|
|
|
|
"if a bookmark exists with the same name and the node is cached then save
|
|
"it so we can update its display string
|
|
let oldMarkedNode = {}
|
|
try
|
|
let oldMarkedNode = g:NERDTreeBookmark.GetNodeForName(a:name, 1)
|
|
catch /^NERDTree.BookmarkNotFoundError/
|
|
catch /^NERDTree.BookmarkedNodeNotFoundError/
|
|
endtry
|
|
|
|
call g:NERDTreeBookmark.AddBookmark(a:name, self.path)
|
|
call self.path.cacheDisplayString()
|
|
call g:NERDTreeBookmark.Write()
|
|
|
|
if !empty(oldMarkedNode)
|
|
call oldMarkedNode.path.cacheDisplayString()
|
|
endif
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.cacheParent() {{{1
|
|
"initializes self.parent if it isnt already
|
|
function! s:TreeFileNode.cacheParent()
|
|
if empty(self.parent)
|
|
let parentPath = self.path.getParent()
|
|
if parentPath.equals(self.path)
|
|
throw "NERDTree.CannotCacheParentError: already at root"
|
|
endif
|
|
let self.parent = s:TreeFileNode.New(parentPath)
|
|
endif
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.clearBookmarks() {{{1
|
|
function! s:TreeFileNode.clearBookmarks()
|
|
for i in g:NERDTreeBookmark.Bookmarks()
|
|
if i.path.equals(self.path)
|
|
call i.delete()
|
|
end
|
|
endfor
|
|
call self.path.cacheDisplayString()
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.copy(dest) {{{1
|
|
function! s:TreeFileNode.copy(dest)
|
|
call self.path.copy(a:dest)
|
|
let newPath = g:NERDTreePath.New(a:dest)
|
|
let parent = b:NERDTreeRoot.findNode(newPath.getParent())
|
|
if !empty(parent)
|
|
call parent.refresh()
|
|
return parent.findNode(newPath)
|
|
else
|
|
return {}
|
|
endif
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.delete {{{1
|
|
"Removes this node from the tree and calls the Delete method for its path obj
|
|
function! s:TreeFileNode.delete()
|
|
call self.path.delete()
|
|
call self.parent.removeChild(self)
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.displayString() {{{1
|
|
"
|
|
"Returns a string that specifies how the node should be represented as a
|
|
"string
|
|
"
|
|
"Return:
|
|
"a string that can be used in the view to represent this node
|
|
function! s:TreeFileNode.displayString()
|
|
return self.path.displayString()
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.equals(treenode) {{{1
|
|
"
|
|
"Compares this treenode to the input treenode and returns 1 if they are the
|
|
"same node.
|
|
"
|
|
"Use this method instead of == because sometimes when the treenodes contain
|
|
"many children, vim seg faults when doing ==
|
|
"
|
|
"Args:
|
|
"treenode: the other treenode to compare to
|
|
function! s:TreeFileNode.equals(treenode)
|
|
return self.path.str() ==# a:treenode.path.str()
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.findNode(path) {{{1
|
|
"Returns self if this node.path.Equals the given path.
|
|
"Returns {} if not equal.
|
|
"
|
|
"Args:
|
|
"path: the path object to compare against
|
|
function! s:TreeFileNode.findNode(path)
|
|
if a:path.equals(self.path)
|
|
return self
|
|
endif
|
|
return {}
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.findOpenDirSiblingWithVisibleChildren(direction) {{{1
|
|
"
|
|
"Finds the next sibling for this node in the indicated direction. This sibling
|
|
"must be a directory and may/may not have children as specified.
|
|
"
|
|
"Args:
|
|
"direction: 0 if you want to find the previous sibling, 1 for the next sibling
|
|
"
|
|
"Return:
|
|
"a treenode object or {} if no appropriate sibling could be found
|
|
function! s:TreeFileNode.findOpenDirSiblingWithVisibleChildren(direction)
|
|
"if we have no parent then we can have no siblings
|
|
if self.parent != {}
|
|
let nextSibling = self.findSibling(a:direction)
|
|
|
|
while nextSibling != {}
|
|
if nextSibling.path.isDirectory && nextSibling.hasVisibleChildren() && nextSibling.isOpen
|
|
return nextSibling
|
|
endif
|
|
let nextSibling = nextSibling.findSibling(a:direction)
|
|
endwhile
|
|
endif
|
|
|
|
return {}
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.findSibling(direction) {{{1
|
|
"
|
|
"Finds the next sibling for this node in the indicated direction
|
|
"
|
|
"Args:
|
|
"direction: 0 if you want to find the previous sibling, 1 for the next sibling
|
|
"
|
|
"Return:
|
|
"a treenode object or {} if no sibling could be found
|
|
function! s:TreeFileNode.findSibling(direction)
|
|
"if we have no parent then we can have no siblings
|
|
if self.parent != {}
|
|
|
|
"get the index of this node in its parents children
|
|
let siblingIndx = self.parent.getChildIndex(self.path)
|
|
|
|
if siblingIndx != -1
|
|
"move a long to the next potential sibling node
|
|
let siblingIndx = a:direction ==# 1 ? siblingIndx+1 : siblingIndx-1
|
|
|
|
"keep moving along to the next sibling till we find one that is valid
|
|
let numSiblings = self.parent.getChildCount()
|
|
while siblingIndx >= 0 && siblingIndx < numSiblings
|
|
|
|
"if the next node is not an ignored node (i.e. wont show up in the
|
|
"view) then return it
|
|
if self.parent.children[siblingIndx].path.ignore() ==# 0
|
|
return self.parent.children[siblingIndx]
|
|
endif
|
|
|
|
"go to next node
|
|
let siblingIndx = a:direction ==# 1 ? siblingIndx+1 : siblingIndx-1
|
|
endwhile
|
|
endif
|
|
endif
|
|
|
|
return {}
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.getLineNum(){{{1
|
|
"returns the line number this node is rendered on, or -1 if it isnt rendered
|
|
function! s:TreeFileNode.getLineNum()
|
|
"if the node is the root then return the root line no.
|
|
if self.isRoot()
|
|
return s:TreeFileNode.GetRootLineNum()
|
|
endif
|
|
|
|
let totalLines = line("$")
|
|
|
|
"the path components we have matched so far
|
|
let pathcomponents = [substitute(b:NERDTreeRoot.path.str({'format': 'UI'}), '/ *$', '', '')]
|
|
"the index of the component we are searching for
|
|
let curPathComponent = 1
|
|
|
|
let fullpath = self.path.str({'format': 'UI'})
|
|
|
|
|
|
let lnum = s:TreeFileNode.GetRootLineNum()
|
|
while lnum > 0
|
|
let lnum = lnum + 1
|
|
"have we reached the bottom of the tree?
|
|
if lnum ==# totalLines+1
|
|
return -1
|
|
endif
|
|
|
|
let curLine = getline(lnum)
|
|
|
|
let indent = nerdtree#indentLevelFor(curLine)
|
|
if indent ==# curPathComponent
|
|
let curLine = nerdtree#stripMarkupFromLine(curLine, 1)
|
|
|
|
let curPath = join(pathcomponents, '/') . '/' . curLine
|
|
if stridx(fullpath, curPath, 0) ==# 0
|
|
if fullpath ==# curPath || strpart(fullpath, len(curPath)-1,1) ==# '/'
|
|
let curLine = substitute(curLine, '/ *$', '', '')
|
|
call add(pathcomponents, curLine)
|
|
let curPathComponent = curPathComponent + 1
|
|
|
|
if fullpath ==# curPath
|
|
return lnum
|
|
endif
|
|
endif
|
|
endif
|
|
endif
|
|
endwhile
|
|
return -1
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.GetRootForTab(){{{1
|
|
"get the root node for this tab
|
|
function! s:TreeFileNode.GetRootForTab()
|
|
if nerdtree#treeExistsForTab()
|
|
return getbufvar(t:NERDTreeBufName, 'NERDTreeRoot')
|
|
end
|
|
return {}
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.GetRootLineNum(){{{1
|
|
"gets the line number of the root node
|
|
function! s:TreeFileNode.GetRootLineNum()
|
|
let rootLine = 1
|
|
while getline(rootLine) !~# '^\(/\|<\)'
|
|
let rootLine = rootLine + 1
|
|
endwhile
|
|
return rootLine
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.GetSelected() {{{1
|
|
"gets the treenode that the cursor is currently over
|
|
function! s:TreeFileNode.GetSelected()
|
|
try
|
|
let path = nerdtree#getPath(line("."))
|
|
if path ==# {}
|
|
return {}
|
|
endif
|
|
return b:NERDTreeRoot.findNode(path)
|
|
catch /^NERDTree/
|
|
return {}
|
|
endtry
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.isVisible() {{{1
|
|
"returns 1 if this node should be visible according to the tree filters and
|
|
"hidden file filters (and their on/off status)
|
|
function! s:TreeFileNode.isVisible()
|
|
return !self.path.ignore()
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.isRoot() {{{1
|
|
"returns 1 if this node is b:NERDTreeRoot
|
|
function! s:TreeFileNode.isRoot()
|
|
if !nerdtree#treeExistsForBuf()
|
|
throw "NERDTree.NoTreeError: No tree exists for the current buffer"
|
|
endif
|
|
|
|
return self.equals(b:NERDTreeRoot)
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.makeRoot() {{{1
|
|
"Make this node the root of the tree
|
|
function! s:TreeFileNode.makeRoot()
|
|
if self.path.isDirectory
|
|
let b:NERDTreeRoot = self
|
|
else
|
|
call self.cacheParent()
|
|
let b:NERDTreeRoot = self.parent
|
|
endif
|
|
|
|
call b:NERDTreeRoot.open()
|
|
|
|
"change dir to the dir of the new root if instructed to
|
|
if g:NERDTreeChDirMode ==# 2
|
|
exec "cd " . b:NERDTreeRoot.path.str({'format': 'Edit'})
|
|
endif
|
|
|
|
silent doautocmd User NERDTreeNewRoot
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.New(path) {{{1
|
|
"Returns a new TreeNode object with the given path and parent
|
|
"
|
|
"Args:
|
|
"path: a path object representing the full filesystem path to the file/dir that the node represents
|
|
function! s:TreeFileNode.New(path)
|
|
if a:path.isDirectory
|
|
return g:NERDTreeDirNode.New(a:path)
|
|
else
|
|
let newTreeNode = copy(self)
|
|
let newTreeNode.path = a:path
|
|
let newTreeNode.parent = {}
|
|
return newTreeNode
|
|
endif
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.open() {{{1
|
|
function! s:TreeFileNode.open(...)
|
|
let opts = a:0 ? a:1 : {}
|
|
let opener = g:NERDTreeOpener.New(self.path, opts)
|
|
call opener.open(self)
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.openSplit() {{{1
|
|
"Open this node in a new window
|
|
function! s:TreeFileNode.openSplit()
|
|
call nerdtree#deprecated('TreeFileNode.openSplit', 'is deprecated, use .open() instead.')
|
|
call self.open({'where': 'h'})
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.openVSplit() {{{1
|
|
"Open this node in a new vertical window
|
|
function! s:TreeFileNode.openVSplit()
|
|
call nerdtree#deprecated('TreeFileNode.openVSplit', 'is deprecated, use .open() instead.')
|
|
call self.open({'where': 'v'})
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.openInNewTab(options) {{{1
|
|
function! s:TreeFileNode.openInNewTab(options)
|
|
echomsg 'TreeFileNode.openInNewTab is deprecated'
|
|
call self.open(extend({'where': 't'}, a:options))
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.putCursorHere(isJump, recurseUpward){{{1
|
|
"Places the cursor on the line number this node is rendered on
|
|
"
|
|
"Args:
|
|
"isJump: 1 if this cursor movement should be counted as a jump by vim
|
|
"recurseUpward: try to put the cursor on the parent if the this node isnt
|
|
"visible
|
|
function! s:TreeFileNode.putCursorHere(isJump, recurseUpward)
|
|
let ln = self.getLineNum()
|
|
if ln != -1
|
|
if a:isJump
|
|
mark '
|
|
endif
|
|
call cursor(ln, col("."))
|
|
else
|
|
if a:recurseUpward
|
|
let node = self
|
|
while node != {} && node.getLineNum() ==# -1
|
|
let node = node.parent
|
|
call node.open()
|
|
endwhile
|
|
call nerdtree#renderView()
|
|
call node.putCursorHere(a:isJump, 0)
|
|
endif
|
|
endif
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.refresh() {{{1
|
|
function! s:TreeFileNode.refresh()
|
|
call self.path.refresh()
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.rename() {{{1
|
|
"Calls the rename method for this nodes path obj
|
|
function! s:TreeFileNode.rename(newName)
|
|
let newName = substitute(a:newName, '\(\\\|\/\)$', '', '')
|
|
call self.path.rename(newName)
|
|
call self.parent.removeChild(self)
|
|
|
|
let parentPath = self.path.getParent()
|
|
let newParent = b:NERDTreeRoot.findNode(parentPath)
|
|
|
|
if newParent != {}
|
|
call newParent.createChild(self.path, 1)
|
|
call newParent.refresh()
|
|
endif
|
|
endfunction
|
|
|
|
"FUNCTION: TreeFileNode.renderToString {{{1
|
|
"returns a string representation for this tree to be rendered in the view
|
|
function! s:TreeFileNode.renderToString()
|
|
return self._renderToString(0, 0, [], self.getChildCount() ==# 1)
|
|
endfunction
|
|
|
|
"Args:
|
|
"depth: the current depth in the tree for this call
|
|
"drawText: 1 if we should actually draw the line for this node (if 0 then the
|
|
"child nodes are rendered only)
|
|
"vertMap: a binary array that indicates whether a vertical bar should be draw
|
|
"for each depth in the tree
|
|
"isLastChild:true if this curNode is the last child of its parent
|
|
function! s:TreeFileNode._renderToString(depth, drawText, vertMap, isLastChild)
|
|
let output = ""
|
|
if a:drawText ==# 1
|
|
|
|
let treeParts = ''
|
|
|
|
"get all the leading spaces and vertical tree parts for this line
|
|
if a:depth > 1
|
|
for j in a:vertMap[0:-2]
|
|
if g:NERDTreeDirArrows
|
|
let treeParts = treeParts . ' '
|
|
else
|
|
if j ==# 1
|
|
let treeParts = treeParts . '| '
|
|
else
|
|
let treeParts = treeParts . ' '
|
|
endif
|
|
endif
|
|
endfor
|
|
endif
|
|
|
|
"get the last vertical tree part for this line which will be different
|
|
"if this node is the last child of its parent
|
|
if !g:NERDTreeDirArrows
|
|
if a:isLastChild
|
|
let treeParts = treeParts . '`'
|
|
else
|
|
let treeParts = treeParts . '|'
|
|
endif
|
|
endif
|
|
|
|
"smack the appropriate dir/file symbol on the line before the file/dir
|
|
"name itself
|
|
if self.path.isDirectory
|
|
if self.isOpen
|
|
if g:NERDTreeDirArrows
|
|
let treeParts = treeParts . '▾ '
|
|
else
|
|
let treeParts = treeParts . '~'
|
|
endif
|
|
else
|
|
if g:NERDTreeDirArrows
|
|
let treeParts = treeParts . '▸ '
|
|
else
|
|
let treeParts = treeParts . '+'
|
|
endif
|
|
endif
|
|
else
|
|
if g:NERDTreeDirArrows
|
|
let treeParts = treeParts . ' '
|
|
else
|
|
let treeParts = treeParts . '-'
|
|
endif
|
|
endif
|
|
let line = treeParts . self.displayString()
|
|
|
|
let output = output . line . "\n"
|
|
endif
|
|
|
|
"if the node is an open dir, draw its children
|
|
if self.path.isDirectory ==# 1 && self.isOpen ==# 1
|
|
|
|
let childNodesToDraw = self.getVisibleChildren()
|
|
if len(childNodesToDraw) > 0
|
|
|
|
"draw all the nodes children except the last
|
|
let lastIndx = len(childNodesToDraw)-1
|
|
if lastIndx > 0
|
|
for i in childNodesToDraw[0:lastIndx-1]
|
|
let output = output . i._renderToString(a:depth + 1, 1, add(copy(a:vertMap), 1), 0)
|
|
endfor
|
|
endif
|
|
|
|
"draw the last child, indicating that it IS the last
|
|
let output = output . childNodesToDraw[lastIndx]._renderToString(a:depth + 1, 1, add(copy(a:vertMap), 0), 1)
|
|
endif
|
|
endif
|
|
|
|
return output
|
|
endfunction
|
|
|
|
" vim: set sw=4 sts=4 et fdm=marker:
|