Clipboard plugin.
[profile.git] / .vim / plugin / NERD_tree.vim
1 " ============================================================================
2 " File:        NERD_tree.vim
3 " Description: vim global plugin that provides a nice tree explorer
4 " Maintainer:  Martin Grenfell <martin_grenfell at msn dot com>
5 " Last Change: 7 Jun, 2009
6 " License:     This program is free software. It comes without any warranty,
7 "              to the extent permitted by applicable law. You can redistribute
8 "              it and/or modify it under the terms of the Do What The Fuck You
9 "              Want To Public License, Version 2, as published by Sam Hocevar.
10 "              See http://sam.zoy.org/wtfpl/COPYING for more details.
11 "
12 " ============================================================================
13 let s:NERD_tree_version = '3.1.1'
14
15 " SECTION: Script init stuff {{{1
16 "============================================================
17 if exists("loaded_nerd_tree")
18     finish
19 endif
20 if v:version < 700
21     finish
22 endif
23 let loaded_nerd_tree = 1
24
25 "for line continuation - i.e dont want C in &cpo
26 let s:old_cpo = &cpo
27 set cpo&vim
28
29 "Function: s:initVariable() function {{{2
30 "This function is used to initialise a given variable to a given value. The
31 "variable is only initialised if it does not exist prior
32 "
33 "Args:
34 "var: the name of the var to be initialised
35 "value: the value to initialise var to
36 "
37 "Returns:
38 "1 if the var is set, 0 otherwise
39 function! s:initVariable(var, value)
40     if !exists(a:var)
41         exec 'let ' . a:var . ' = ' . "'" . a:value . "'"
42         return 1
43     endif
44     return 0
45 endfunction
46
47 "SECTION: Init variable calls and other random constants {{{2
48 call s:initVariable("g:NERDChristmasTree", 1)
49 call s:initVariable("g:NERDTreeAutoCenter", 1)
50 call s:initVariable("g:NERDTreeAutoCenterThreshold", 3)
51 call s:initVariable("g:NERDTreeCaseSensitiveSort", 0)
52 call s:initVariable("g:NERDTreeChDirMode", 0)
53 if !exists("g:NERDTreeIgnore")
54     let g:NERDTreeIgnore = ['\~$']
55 endif
56 call s:initVariable("g:NERDTreeBookmarksFile", expand('$HOME') . '/.NERDTreeBookmarks')
57 call s:initVariable("g:NERDTreeHighlightCursorline", 1)
58 call s:initVariable("g:NERDTreeHijackNetrw", 1)
59 call s:initVariable("g:NERDTreeMouseMode", 1)
60 call s:initVariable("g:NERDTreeNotificationThreshold", 100)
61 call s:initVariable("g:NERDTreeQuitOnOpen", 0)
62 call s:initVariable("g:NERDTreeShowBookmarks", 0)
63 call s:initVariable("g:NERDTreeShowFiles", 1)
64 call s:initVariable("g:NERDTreeShowHidden", 0)
65 call s:initVariable("g:NERDTreeShowLineNumbers", 0)
66 call s:initVariable("g:NERDTreeSortDirs", 1)
67
68 if !exists("g:NERDTreeSortOrder")
69     let g:NERDTreeSortOrder = ['\/$', '*', '\.swp$',  '\.bak$', '\~$']
70 else
71     "if there isnt a * in the sort sequence then add one
72     if count(g:NERDTreeSortOrder, '*') < 1
73         call add(g:NERDTreeSortOrder, '*')
74     endif
75 endif
76
77 "we need to use this number many times for sorting... so we calculate it only
78 "once here
79 let s:NERDTreeSortStarIndex = index(g:NERDTreeSortOrder, '*')
80
81 call s:initVariable("g:NERDTreeStatusline", "%{b:NERDTreeRoot.path.strForOS(0)}")
82 call s:initVariable("g:NERDTreeWinPos", "left")
83 call s:initVariable("g:NERDTreeWinSize", 31)
84
85 let s:running_windows = has("win16") || has("win32") || has("win64")
86
87 "init the shell commands that will be used to copy nodes, and remove dir trees
88 "
89 "Note: the space after the command is important
90 if s:running_windows
91     call s:initVariable("g:NERDTreeRemoveDirCmd", 'rmdir /s /q ')
92 else
93     call s:initVariable("g:NERDTreeRemoveDirCmd", 'rm -rf ')
94     call s:initVariable("g:NERDTreeCopyCmd", 'cp -r ')
95 endif
96
97
98 "SECTION: Init variable calls for key mappings {{{2
99 call s:initVariable("g:NERDTreeMapActivateNode", "o")
100 call s:initVariable("g:NERDTreeMapChangeRoot", "C")
101 call s:initVariable("g:NERDTreeMapChdir", "cd")
102 call s:initVariable("g:NERDTreeMapCloseChildren", "X")
103 call s:initVariable("g:NERDTreeMapCloseDir", "x")
104 call s:initVariable("g:NERDTreeMapDeleteBookmark", "D")
105 call s:initVariable("g:NERDTreeMapExecute", "!")
106 call s:initVariable("g:NERDTreeMapFilesystemMenu", "m")
107 call s:initVariable("g:NERDTreeMapHelp", "?")
108 call s:initVariable("g:NERDTreeMapJumpFirstChild", "K")
109 call s:initVariable("g:NERDTreeMapJumpLastChild", "J")
110 call s:initVariable("g:NERDTreeMapJumpNextSibling", "<C-j>")
111 call s:initVariable("g:NERDTreeMapJumpParent", "p")
112 call s:initVariable("g:NERDTreeMapJumpPrevSibling", "<C-k>")
113 call s:initVariable("g:NERDTreeMapJumpRoot", "P")
114 call s:initVariable("g:NERDTreeMapOpenExpl", "e")
115 call s:initVariable("g:NERDTreeMapOpenInTab", "t")
116 call s:initVariable("g:NERDTreeMapOpenInTabSilent", "T")
117 call s:initVariable("g:NERDTreeMapOpenRecursively", "O")
118 call s:initVariable("g:NERDTreeMapOpenSplit", "i")
119 call s:initVariable("g:NERDTreeMapOpenVSplit", "s")
120 call s:initVariable("g:NERDTreeMapPreview", "g" . NERDTreeMapActivateNode)
121 call s:initVariable("g:NERDTreeMapPreviewSplit", "g" . NERDTreeMapOpenSplit)
122 call s:initVariable("g:NERDTreeMapPreviewVSplit", "g" . NERDTreeMapOpenVSplit)
123 call s:initVariable("g:NERDTreeMapQuit", "q")
124 call s:initVariable("g:NERDTreeMapRefresh", "r")
125 call s:initVariable("g:NERDTreeMapRefreshRoot", "R")
126 call s:initVariable("g:NERDTreeMapToggleBookmarks", "B")
127 call s:initVariable("g:NERDTreeMapToggleFiles", "F")
128 call s:initVariable("g:NERDTreeMapToggleFilters", "f")
129 call s:initVariable("g:NERDTreeMapToggleHidden", "I")
130 call s:initVariable("g:NERDTreeMapUpdir", "u")
131 call s:initVariable("g:NERDTreeMapUpdirKeepOpen", "U")
132
133 "SECTION: Script level variable declaration{{{2
134 let s:escape_chars =  " \\`\|\"#%&,?()\*^<>"
135 let s:NERDTreeBufName = 'NERD_tree_'
136
137 let s:tree_wid = 2
138 let s:tree_markup_reg = '^[ `|]*[\-+~]'
139 let s:tree_up_dir_line = '.. (up a dir)'
140
141 let s:os_slash = '/'
142 if s:running_windows
143     let s:os_slash = '\'
144 endif
145
146 "the number to add to the nerd tree buffer name to make the buf name unique
147 let s:next_buffer_number = 1
148
149 " SECTION: Commands {{{1
150 "============================================================
151 "init the command that users start the nerd tree with
152 command! -n=? -complete=dir -bar NERDTree :call s:initNerdTree('<args>')
153 command! -n=? -complete=dir -bar NERDTreeToggle :call s:toggle('<args>')
154 command! -n=0 -bar NERDTreeClose :call s:closeTreeIfOpen()
155 command! -n=1 -complete=customlist,s:completeBookmarks -bar NERDTreeFromBookmark call s:initNerdTree('<args>')
156 command! -n=0 -bar NERDTreeMirror call s:initNerdTreeMirror()
157 " SECTION: Auto commands {{{1
158 "============================================================
159 augroup NERDTree
160     "Save the cursor position whenever we close the nerd tree
161     exec "autocmd BufWinLeave *". s:NERDTreeBufName ." call <SID>saveScreenState()"
162     "cache bookmarks when vim loads
163     autocmd VimEnter * call s:Bookmark.CacheBookmarks(0)
164 augroup END
165
166 if g:NERDTreeHijackNetrw
167     augroup NERDTreeHijackNetrw
168         autocmd VimEnter * silent! autocmd! FileExplorer
169         au BufEnter,VimEnter * call s:checkForBrowse(expand("<amatch>"))
170     augroup END
171 endif
172
173 "SECTION: Classes {{{1
174 "============================================================
175 "CLASS: Bookmark {{{2
176 "============================================================
177 let s:Bookmark = {}
178 " FUNCTION: Bookmark.AddBookmark(name, path) {{{3
179 " Class method to add a new bookmark to the list, if a previous bookmark exists
180 " with the same name, just update the path for that bookmark
181 function! s:Bookmark.AddBookmark(name, path)
182     for i in s:Bookmark.Bookmarks()
183         if i.name ==# a:name
184             let i.path = a:path
185             return
186         endif
187     endfor
188     call add(s:Bookmark.Bookmarks(), s:Bookmark.New(a:name, a:path))
189     call s:Bookmark.Sort()
190 endfunction
191 " Function: Bookmark.Bookmarks()   {{{3
192 " Class method to get all bookmarks. Lazily initializes the bookmarks global
193 " variable
194 function! s:Bookmark.Bookmarks()
195     if !exists("g:NERDTreeBookmarks")
196         let g:NERDTreeBookmarks = []
197     endif
198     return g:NERDTreeBookmarks
199 endfunction
200 " Function: Bookmark.BookmarkExistsFor(name)   {{{3
201 " class method that returns 1 if a bookmark with the given name is found, 0
202 " otherwise
203 function! s:Bookmark.BookmarkExistsFor(name)
204     try
205         call s:Bookmark.BookmarkFor(a:name)
206         return 1
207     catch /^NERDTree.BookmarkNotFoundError/
208         return 0
209     endtry
210 endfunction
211 " Function: Bookmark.BookmarkFor(name)   {{{3
212 " Class method to get the bookmark that has the given name. {} is return if no
213 " bookmark is found
214 function! s:Bookmark.BookmarkFor(name)
215     for i in s:Bookmark.Bookmarks()
216         if i.name ==# a:name
217             return i
218         endif
219     endfor
220     throw "NERDTree.BookmarkNotFoundError: no bookmark found for name: \"". a:name  .'"'
221 endfunction
222 " Function: Bookmark.BookmarkNames()   {{{3
223 " Class method to return an array of all bookmark names
224 function! s:Bookmark.BookmarkNames()
225     let names = []
226     for i in s:Bookmark.Bookmarks()
227         call add(names, i.name)
228     endfor
229     return names
230 endfunction
231 " FUNCTION: Bookmark.CacheBookmarks(silent) {{{3
232 " Class method to read all bookmarks from the bookmarks file intialize
233 " bookmark objects for each one.
234 "
235 " Args:
236 " silent - dont echo an error msg if invalid bookmarks are found
237 function! s:Bookmark.CacheBookmarks(silent)
238     if filereadable(g:NERDTreeBookmarksFile)
239         let g:NERDTreeBookmarks = []
240         let g:NERDTreeInvalidBookmarks = []
241         let bookmarkStrings = readfile(g:NERDTreeBookmarksFile)
242         let invalidBookmarksFound = 0
243         for i in bookmarkStrings
244
245             "ignore blank lines
246             if i != ''
247
248                 let name = substitute(i, '^\(.\{-}\) .*$', '\1', '')
249                 let path = substitute(i, '^.\{-} \(.*\)$', '\1', '')
250
251                 try
252                     let bookmark = s:Bookmark.New(name, s:Path.New(path))
253                     call add(g:NERDTreeBookmarks, bookmark)
254                 catch /^NERDTree.InvalidArgumentsError/
255                     call add(g:NERDTreeInvalidBookmarks, i)
256                     let invalidBookmarksFound += 1
257                 endtry
258             endif
259         endfor
260         if invalidBookmarksFound
261             call s:Bookmark.Write()
262             if !a:silent
263                 call s:echo(invalidBookmarksFound . " invalid bookmarks were read. See :help NERDTreeInvalidBookmarks for info.")
264             endif
265         endif
266         call s:Bookmark.Sort()
267     endif
268 endfunction
269 " FUNCTION: Bookmark.compareTo(otherbookmark) {{{3
270 " Compare these two bookmarks for sorting purposes
271 function! s:Bookmark.compareTo(otherbookmark)
272     return a:otherbookmark.name < self.name
273 endfunction
274 " FUNCTION: Bookmark.ClearAll() {{{3
275 " Class method to delete all bookmarks.
276 function! s:Bookmark.ClearAll()
277     for i in s:Bookmark.Bookmarks()
278         call i.delete()
279     endfor
280     call s:Bookmark.Write()
281 endfunction
282 " FUNCTION: Bookmark.delete() {{{3
283 " Delete this bookmark. If the node for this bookmark is under the current
284 " root, then recache bookmarks for its Path object
285 function! s:Bookmark.delete()
286     let node = {}
287     try
288         let node = self.getNode(1)
289     catch /^NERDTree.BookmarkedNodeNotFoundError/
290     endtry
291     call remove(s:Bookmark.Bookmarks(), index(s:Bookmark.Bookmarks(), self))
292     if !empty(node)
293         call node.path.cacheDisplayString()
294     endif
295     call s:Bookmark.Write()
296 endfunction
297 " FUNCTION: Bookmark.getNode(searchFromAbsoluteRoot) {{{3
298 " Gets the treenode for this bookmark
299 "
300 " Args:
301 " searchFromAbsoluteRoot: specifies whether we should search from the current
302 " tree root, or the highest cached node
303 function! s:Bookmark.getNode(searchFromAbsoluteRoot)
304     let searchRoot = a:searchFromAbsoluteRoot ? s:TreeDirNode.AbsoluteTreeRoot() : b:NERDTreeRoot
305     let targetNode = searchRoot.findNode(self.path)
306     if empty(targetNode)
307         throw "NERDTree.BookmarkedNodeNotFoundError: no node was found for bookmark: " . self.name
308     endif
309     return targetNode
310 endfunction
311 " FUNCTION: Bookmark.GetNodeForName(name, searchFromAbsoluteRoot) {{{3
312 " Class method that finds the bookmark with the given name and returns the
313 " treenode for it.
314 function! s:Bookmark.GetNodeForName(name, searchFromAbsoluteRoot)
315     let bookmark = s:Bookmark.BookmarkFor(a:name)
316     return bookmark.getNode(a:searchFromAbsoluteRoot)
317 endfunction
318 " Function: Bookmark.InvalidBookmarks()   {{{3
319 " Class method to get all invalid bookmark strings read from the bookmarks
320 " file
321 function! s:Bookmark.InvalidBookmarks()
322     if !exists("g:NERDTreeInvalidBookmarks")
323         let g:NERDTreeInvalidBookmarks = []
324     endif
325     return g:NERDTreeInvalidBookmarks
326 endfunction
327 " FUNCTION: Bookmark.mustExist() {{{3
328 function! s:Bookmark.mustExist()
329     if !self.path.exists()
330         call s:Bookmark.CacheBookmarks(1)
331         throw "NERDTree.BookmarkPointsToInvalidLocationError: the bookmark \"".
332             \ self.name ."\" points to a non existing location: \"". self.path.strForOS(0)
333     endif
334 endfunction
335 " FUNCTION: Bookmark.New(name, path) {{{3
336 " Create a new bookmark object with the given name and path object
337 function! s:Bookmark.New(name, path)
338     if a:name =~ ' '
339         throw "NERDTree.IllegalBookmarkNameError: illegal name:" . a:name
340     endif
341
342     let newBookmark = copy(self)
343     let newBookmark.name = a:name
344     let newBookmark.path = a:path
345     return newBookmark
346 endfunction
347 " Function: Bookmark.setPath(path)   {{{3
348 " makes this bookmark point to the given path
349 function! s:Bookmark.setPath(path)
350     let self.path = a:path
351 endfunction
352 " Function: Bookmark.Sort()   {{{3
353 " Class method that sorts all bookmarks
354 function! s:Bookmark.Sort()
355     let CompareFunc = function("s:compareBookmarks")
356     call sort(s:Bookmark.Bookmarks(), CompareFunc)
357 endfunction
358 " Function: Bookmark.str()   {{{3
359 " Get the string that should be rendered in the view for this bookmark
360 function! s:Bookmark.str()
361     let pathStrMaxLen = winwidth(s:getTreeWinNum()) - 4 - len(self.name)
362     if &nu
363         let pathStrMaxLen = pathStrMaxLen - &numberwidth
364     endif
365
366     let pathStr = self.path.strForOS(0)
367     if len(pathStr) > pathStrMaxLen
368         let pathStr = '<' . strpart(pathStr, len(pathStr) - pathStrMaxLen)
369     endif
370     return '>' . self.name . ' ' . pathStr
371 endfunction
372 " FUNCTION: Bookmark.toRoot() {{{3
373 " Make the node for this bookmark the new tree root
374 function! s:Bookmark.toRoot()
375     if self.validate()
376         try
377             let targetNode = self.getNode(1)
378         catch /^NERDTree.BookmarkedNodeNotFoundError/
379             let targetNode = s:TreeFileNode.New(s:Bookmark.BookmarkFor(self.name).path)
380         endtry
381         call targetNode.makeRoot()
382         call s:renderView()
383         call targetNode.putCursorHere(0, 0)
384     endif
385 endfunction
386 " FUNCTION: Bookmark.ToRoot(name) {{{3
387 " Make the node for this bookmark the new tree root
388 function! s:Bookmark.ToRoot(name)
389     let bookmark = s:Bookmark.BookmarkFor(a:name)
390     call bookmark.toRoot()
391 endfunction
392
393
394 "FUNCTION: Bookmark.validate() {{{3
395 function! s:Bookmark.validate()
396     if self.path.exists()
397         return 1
398     else
399         call s:Bookmark.CacheBookmarks(1)
400         call s:renderView()
401         call s:echo(self.name . "now points to an invalid location. See :help NERDTreeInvalidBookmarks for info.")
402         return 0
403     endif
404 endfunction
405
406 " Function: Bookmark.Write()   {{{3
407 " Class method to write all bookmarks to the bookmarks file
408 function! s:Bookmark.Write()
409     let bookmarkStrings = []
410     for i in s:Bookmark.Bookmarks()
411         call add(bookmarkStrings, i.name . ' ' . i.path.strForOS(0))
412     endfor
413
414     "add a blank line before the invalid ones
415     call add(bookmarkStrings, "")
416
417     for j in s:Bookmark.InvalidBookmarks()
418         call add(bookmarkStrings, j)
419     endfor
420     call writefile(bookmarkStrings, g:NERDTreeBookmarksFile)
421 endfunction
422 "CLASS: TreeFileNode {{{2
423 "This class is the parent of the TreeDirNode class and constitures the
424 "'Component' part of the composite design pattern between the treenode
425 "classes.
426 "============================================================
427 let s:TreeFileNode = {}
428 "FUNCTION: TreeFileNode.bookmark(name) {{{3
429 "bookmark this node with a:name
430 function! s:TreeFileNode.bookmark(name)
431     try
432         let oldMarkedNode = s:Bookmark.GetNodeForName(a:name, 1)
433         call oldMarkedNode.path.cacheDisplayString()
434     catch /^NERDTree.BookmarkNotFoundError/
435     endtry
436
437     call s:Bookmark.AddBookmark(a:name, self.path)
438     call self.path.cacheDisplayString()
439     call s:Bookmark.Write()
440 endfunction
441 "FUNCTION: TreeFileNode.cacheParent() {{{3
442 "initializes self.parent if it isnt already
443 function! s:TreeFileNode.cacheParent()
444     if empty(self.parent)
445         let parentPath = self.path.getParent()
446         if parentPath.equals(self.path)
447             throw "NERDTree.CannotCacheParentError: already at root"
448         endif
449         let self.parent = s:TreeFileNode.New(parentPath)
450     endif
451 endfunction
452 "FUNCTION: TreeFileNode.compareNodes {{{3
453 "This is supposed to be a class level method but i cant figure out how to
454 "get func refs to work from a dict..
455 "
456 "A class level method that compares two nodes
457 "
458 "Args:
459 "n1, n2: the 2 nodes to compare
460 function! s:compareNodes(n1, n2)
461     return a:n1.path.compareTo(a:n2.path)
462 endfunction
463
464 "FUNCTION: TreeFileNode.clearBoomarks() {{{3
465 function! s:TreeFileNode.clearBoomarks()
466     for i in s:Bookmark.Bookmarks()
467         if i.path.equals(self.path)
468             call i.delete()
469         end
470     endfor
471     call self.path.cacheDisplayString()
472 endfunction
473 "FUNCTION: TreeFileNode.copy(dest) {{{3
474 function! s:TreeFileNode.copy(dest)
475     call self.path.copy(a:dest)
476     let newPath = s:Path.New(a:dest)
477     let parent = b:NERDTreeRoot.findNode(newPath.getParent())
478     if !empty(parent)
479         call parent.refresh()
480     endif
481     return parent.findNode(newPath)
482 endfunction
483
484 "FUNCTION: TreeFileNode.delete {{{3
485 "Removes this node from the tree and calls the Delete method for its path obj
486 function! s:TreeFileNode.delete()
487     call self.path.delete()
488     call self.parent.removeChild(self)
489 endfunction
490
491 "FUNCTION: TreeFileNode.renderToString {{{3
492 "returns a string representation for this tree to be rendered in the view
493 function! s:TreeFileNode.renderToString()
494     return self._renderToString(0, 0, [], self.getChildCount() ==# 1)
495 endfunction
496
497
498 "Args:
499 "depth: the current depth in the tree for this call
500 "drawText: 1 if we should actually draw the line for this node (if 0 then the
501 "child nodes are rendered only)
502 "vertMap: a binary array that indicates whether a vertical bar should be draw
503 "for each depth in the tree
504 "isLastChild:true if this curNode is the last child of its parent
505 function! s:TreeFileNode._renderToString(depth, drawText, vertMap, isLastChild)
506     let output = ""
507     if a:drawText ==# 1
508
509         let treeParts = ''
510
511         "get all the leading spaces and vertical tree parts for this line
512         if a:depth > 1
513             for j in a:vertMap[0:-2]
514                 if j ==# 1
515                     let treeParts = treeParts . '| '
516                 else
517                     let treeParts = treeParts . '  '
518                 endif
519             endfor
520         endif
521
522         "get the last vertical tree part for this line which will be different
523         "if this node is the last child of its parent
524         if a:isLastChild
525             let treeParts = treeParts . '`'
526         else
527             let treeParts = treeParts . '|'
528         endif
529
530
531         "smack the appropriate dir/file symbol on the line before the file/dir
532         "name itself
533         if self.path.isDirectory
534             if self.isOpen
535                 let treeParts = treeParts . '~'
536             else
537                 let treeParts = treeParts . '+'
538             endif
539         else
540             let treeParts = treeParts . '-'
541         endif
542         let line = treeParts . self.strDisplay()
543
544         let output = output . line . "\n"
545     endif
546
547     "if the node is an open dir, draw its children
548     if self.path.isDirectory ==# 1 && self.isOpen ==# 1
549
550         let childNodesToDraw = self.getVisibleChildren()
551         if len(childNodesToDraw) > 0
552
553             "draw all the nodes children except the last
554             let lastIndx = len(childNodesToDraw)-1
555             if lastIndx > 0
556                 for i in childNodesToDraw[0:lastIndx-1]
557                     let output = output . i._renderToString(a:depth + 1, 1, add(copy(a:vertMap), 1), 0)
558                 endfor
559             endif
560
561             "draw the last child, indicating that it IS the last
562             let output = output . childNodesToDraw[lastIndx]._renderToString(a:depth + 1, 1, add(copy(a:vertMap), 0), 1)
563         endif
564     endif
565
566     return output
567 endfunction
568 "FUNCTION: TreeFileNode.equals(treenode) {{{3
569 "
570 "Compares this treenode to the input treenode and returns 1 if they are the
571 "same node.
572 "
573 "Use this method instead of ==  because sometimes when the treenodes contain
574 "many children, vim seg faults when doing ==
575 "
576 "Args:
577 "treenode: the other treenode to compare to
578 function! s:TreeFileNode.equals(treenode)
579     return self.path.str(1) ==# a:treenode.path.str(1)
580 endfunction
581
582 "FUNCTION: TreeFileNode.findNode(path) {{{3
583 "Returns self if this node.path.Equals the given path.
584 "Returns {} if not equal.
585 "
586 "Args:
587 "path: the path object to compare against
588 function! s:TreeFileNode.findNode(path)
589     if a:path.equals(self.path)
590         return self
591     endif
592     return {}
593 endfunction
594 "FUNCTION: TreeFileNode.findOpenDirSiblingWithVisibleChildren(direction) {{{3
595 "
596 "Finds the next sibling for this node in the indicated direction. This sibling
597 "must be a directory and may/may not have children as specified.
598 "
599 "Args:
600 "direction: 0 if you want to find the previous sibling, 1 for the next sibling
601 "
602 "Return:
603 "a treenode object or {} if no appropriate sibling could be found
604 function! s:TreeFileNode.findOpenDirSiblingWithVisibleChildren(direction)
605     "if we have no parent then we can have no siblings
606     if self.parent != {}
607         let nextSibling = self.findSibling(a:direction)
608
609         while nextSibling != {}
610             if nextSibling.path.isDirectory && nextSibling.hasVisibleChildren() && nextSibling.isOpen
611                 return nextSibling
612             endif
613             let nextSibling = nextSibling.findSibling(a:direction)
614         endwhile
615     endif
616
617     return {}
618 endfunction
619 "FUNCTION: TreeFileNode.findSibling(direction) {{{3
620 "
621 "Finds the next sibling for this node in the indicated direction
622 "
623 "Args:
624 "direction: 0 if you want to find the previous sibling, 1 for the next sibling
625 "
626 "Return:
627 "a treenode object or {} if no sibling could be found
628 function! s:TreeFileNode.findSibling(direction)
629     "if we have no parent then we can have no siblings
630     if self.parent != {}
631
632         "get the index of this node in its parents children
633         let siblingIndx = self.parent.getChildIndex(self.path)
634
635         if siblingIndx != -1
636             "move a long to the next potential sibling node
637             let siblingIndx = a:direction ==# 1 ? siblingIndx+1 : siblingIndx-1
638
639             "keep moving along to the next sibling till we find one that is valid
640             let numSiblings = self.parent.getChildCount()
641             while siblingIndx >= 0 && siblingIndx < numSiblings
642
643                 "if the next node is not an ignored node (i.e. wont show up in the
644                 "view) then return it
645                 if self.parent.children[siblingIndx].path.ignore() ==# 0
646                     return self.parent.children[siblingIndx]
647                 endif
648
649                 "go to next node
650                 let siblingIndx = a:direction ==# 1 ? siblingIndx+1 : siblingIndx-1
651             endwhile
652         endif
653     endif
654
655     return {}
656 endfunction
657
658 "FUNCTION: TreeFileNode.getLineNum(){{{3
659 "returns the line number this node is rendered on, or -1 if it isnt rendered
660 function! s:TreeFileNode.getLineNum()
661     "if the node is the root then return the root line no.
662     if self.isRoot()
663         return s:TreeFileNode.GetRootLineNum()
664     endif
665
666     let totalLines = line("$")
667
668     "the path components we have matched so far
669     let pathcomponents = [substitute(b:NERDTreeRoot.path.str(0), '/ *$', '', '')]
670     "the index of the component we are searching for
671     let curPathComponent = 1
672
673     let fullpath = self.path.str(0)
674
675
676     let lnum = s:TreeFileNode.GetRootLineNum()
677     while lnum > 0
678         let lnum = lnum + 1
679         "have we reached the bottom of the tree?
680         if lnum ==# totalLines+1
681             return -1
682         endif
683
684         let curLine = getline(lnum)
685
686         let indent = s:indentLevelFor(curLine)
687         if indent ==# curPathComponent
688             let curLine = s:stripMarkupFromLine(curLine, 1)
689
690             let curPath =  join(pathcomponents, '/') . '/' . curLine
691             if stridx(fullpath, curPath, 0) ==# 0
692                 if fullpath ==# curPath || strpart(fullpath, len(curPath)-1,1) ==# '/'
693                     let curLine = substitute(curLine, '/ *$', '', '')
694                     call add(pathcomponents, curLine)
695                     let curPathComponent = curPathComponent + 1
696
697                     if fullpath ==# curPath
698                         return lnum
699                     endif
700                 endif
701             endif
702         endif
703     endwhile
704     return -1
705 endfunction
706
707 "FUNCTION: TreeFileNode.GetRootLineNum(){{{3
708 "gets the line number of the root node
709 function! s:TreeFileNode.GetRootLineNum()
710     let rootLine = 1
711     while getline(rootLine) !~ '^/'
712         let rootLine = rootLine + 1
713     endwhile
714     return rootLine
715 endfunction
716
717 "FUNCTION: TreeFileNode.GetSelected() {{{3
718 "gets the treenode that the cursor is currently over
719 function! s:TreeFileNode.GetSelected()
720     try
721         let path = s:getPath(line("."))
722         if path ==# {}
723             return {}
724         endif
725         return b:NERDTreeRoot.findNode(path)
726     catch /NERDTree/
727         return {}
728     endtry
729 endfunction
730 "FUNCTION: TreeFileNode.isVisible() {{{3
731 "returns 1 if this node should be visible according to the tree filters and
732 "hidden file filters (and their on/off status)
733 function! s:TreeFileNode.isVisible()
734     return !self.path.ignore()
735 endfunction
736 "FUNCTION: TreeFileNode.isRoot() {{{3
737 "returns 1 if this node is b:NERDTreeRoot
738 function! s:TreeFileNode.isRoot()
739     if !s:treeExistsForBuf()
740         throw "NERDTree.NoTreeError: No tree exists for the current buffer"
741     endif
742
743     return self.equals(b:NERDTreeRoot)
744 endfunction
745
746 "FUNCTION: TreeFileNode.makeRoot() {{{3
747 "Make this node the root of the tree
748 function! s:TreeFileNode.makeRoot()
749     if self.path.isDirectory
750         let b:NERDTreeRoot = self
751     else
752         call self.cacheParent()
753         let b:NERDTreeRoot = self.parent
754     endif
755
756     call b:NERDTreeRoot.open()
757
758     "change dir to the dir of the new root if instructed to
759     if g:NERDTreeChDirMode ==# 2
760         exec "cd " . b:NERDTreeRoot.path.strForEditCmd()
761     endif
762 endfunction
763 "FUNCTION: TreeFileNode.New(path) {{{3
764 "Returns a new TreeNode object with the given path and parent
765 "
766 "Args:
767 "path: a path object representing the full filesystem path to the file/dir that the node represents
768 function! s:TreeFileNode.New(path)
769     if a:path.isDirectory
770         return s:TreeDirNode.New(a:path)
771     else
772         let newTreeNode = {}
773         let newTreeNode = copy(self)
774         let newTreeNode.path = a:path
775         let newTreeNode.parent = {}
776         return newTreeNode
777     endif
778 endfunction
779
780 "FUNCTION: TreeFileNode.open() {{{3
781 "Open the file represented by the given node in the current window, splitting
782 "the window if needed
783 "
784 "ARGS:
785 "treenode: file node to open
786 function! s:TreeFileNode.open()
787     if b:NERDTreeType ==# "secondary"
788         exec 'edit ' . self.path.strForEditCmd()
789         return
790     endif
791
792     "if the file is already open in this tab then just stick the cursor in it
793     let winnr = bufwinnr('^' . self.path.strForOS(0) . '$')
794     if winnr != -1
795         call s:exec(winnr . "wincmd w")
796
797     else
798         if !s:isWindowUsable(winnr("#")) && s:firstUsableWindow() ==# -1
799             call self.openSplit()
800         else
801             try
802                 if !s:isWindowUsable(winnr("#"))
803                     call s:exec(s:firstUsableWindow() . "wincmd w")
804                 else
805                     call s:exec('wincmd p')
806                 endif
807                 exec ("edit " . self.path.strForEditCmd())
808             catch /^Vim\%((\a\+)\)\=:E37/
809                 call s:putCursorInTreeWin()
810                 throw "NERDTree.FileAlreadyOpenAndModifiedError: ". self.path.str(0) ." is already open and modified."
811             catch /^Vim\%((\a\+)\)\=:/
812                 echo v:exception
813             endtry
814         endif
815     endif
816 endfunction
817 "FUNCTION: TreeFileNode.openSplit() {{{3
818 "Open this node in a new window
819 function! s:TreeFileNode.openSplit()
820
821     if b:NERDTreeType ==# "secondary"
822         exec "split " . self.path.strForEditCmd()
823         return
824     endif
825
826     " Save the user's settings for splitbelow and splitright
827     let savesplitbelow=&splitbelow
828     let savesplitright=&splitright
829
830     " 'there' will be set to a command to move from the split window
831     " back to the explorer window
832     "
833     " 'back' will be set to a command to move from the explorer window
834     " back to the newly split window
835     "
836     " 'right' and 'below' will be set to the settings needed for
837     " splitbelow and splitright IF the explorer is the only window.
838     "
839     let there= g:NERDTreeWinPos ==# "left" ? "wincmd h" : "wincmd l"
840     let back = g:NERDTreeWinPos ==# "left" ? "wincmd l" : "wincmd h"
841     let right= g:NERDTreeWinPos ==# "left"
842     let below=0
843
844     " Attempt to go to adjacent window
845     call s:exec(back)
846
847     let onlyOneWin = (winnr("$") ==# 1)
848
849     " If no adjacent window, set splitright and splitbelow appropriately
850     if onlyOneWin
851         let &splitright=right
852         let &splitbelow=below
853     else
854         " found adjacent window - invert split direction
855         let &splitright=!right
856         let &splitbelow=!below
857     endif
858
859     let splitMode = onlyOneWin ? "vertical" : ""
860
861     " Open the new window
862     try
863         exec(splitMode." sp " . self.path.strForEditCmd())
864     catch /^Vim\%((\a\+)\)\=:E37/
865         call s:putCursorInTreeWin()
866         throw "NERDTree.FileAlreadyOpenAndModifiedError: ". self.path.str(0) ." is already open and modified."
867     catch /^Vim\%((\a\+)\)\=:/
868         "do nothing
869     endtry
870
871     "resize the tree window if no other window was open before
872     if onlyOneWin
873         let size = exists("b:NERDTreeOldWindowSize") ? b:NERDTreeOldWindowSize : g:NERDTreeWinSize
874         call s:exec(there)
875         exec("silent ". splitMode ." resize ". size)
876         call s:exec('wincmd p')
877     endif
878
879     " Restore splitmode settings
880     let &splitbelow=savesplitbelow
881     let &splitright=savesplitright
882 endfunction
883 "FUNCTION: TreeFileNode.openVSplit() {{{3
884 "Open this node in a new vertical window
885 function! s:TreeFileNode.openVSplit()
886     if b:NERDTreeType ==# "secondary"
887         exec "vnew " . self.path.strForEditCmd()
888         return
889     endif
890
891     let winwidth = winwidth(".")
892     if winnr("$")==#1
893         let winwidth = g:NERDTreeWinSize
894     endif
895
896     call s:exec("wincmd p")
897     exec "vnew " . self.path.strForEditCmd()
898
899     "resize the nerd tree back to the original size
900     call s:putCursorInTreeWin()
901     exec("silent vertical resize ". winwidth)
902     call s:exec('wincmd p')
903 endfunction
904 "FUNCTION: TreeFileNode.putCursorHere(isJump, recurseUpward){{{3
905 "Places the cursor on the line number this node is rendered on
906 "
907 "Args:
908 "isJump: 1 if this cursor movement should be counted as a jump by vim
909 "recurseUpward: try to put the cursor on the parent if the this node isnt
910 "visible
911 function! s:TreeFileNode.putCursorHere(isJump, recurseUpward)
912     let ln = self.getLineNum()
913     if ln != -1
914         if a:isJump
915             mark '
916         endif
917         call cursor(ln, col("."))
918     else
919         if a:recurseUpward
920             let node = self
921             while node != {} && node.getLineNum() ==# -1
922                 let node = node.parent
923                 call node.open()
924             endwhile
925             call s:renderView()
926             call node.putCursorHere(a:isJump, 0)
927         endif
928     endif
929 endfunction
930
931 "FUNCTION: TreeFileNode.refresh() {{{3
932 function! s:TreeFileNode.refresh()
933     call self.path.refresh()
934 endfunction
935 "FUNCTION: TreeFileNode.rename() {{{3
936 "Calls the rename method for this nodes path obj
937 function! s:TreeFileNode.rename(newName)
938     let newName = substitute(a:newName, '\(\\\|\/\)$', '', '')
939     call self.path.rename(newName)
940     call self.parent.removeChild(self)
941
942     let parentPath = self.path.getPathTrunk()
943     let newParent = b:NERDTreeRoot.findNode(parentPath)
944
945     if newParent != {}
946         call newParent.createChild(self.path, 1)
947         call newParent.refresh()
948     endif
949 endfunction
950 "FUNCTION: TreeFileNode.strDisplay() {{{3
951 "
952 "Returns a string that specifies how the node should be represented as a
953 "string
954 "
955 "Return:
956 "a string that can be used in the view to represent this node
957 function! s:TreeFileNode.strDisplay()
958     return self.path.strDisplay()
959 endfunction
960
961 "CLASS: TreeDirNode {{{2
962 "This class is a child of the TreeFileNode class and constitutes the
963 "'Composite' part of the composite design pattern between the treenode
964 "classes.
965 "============================================================
966 let s:TreeDirNode = copy(s:TreeFileNode)
967 "FUNCTION: TreeDirNode.AbsoluteTreeRoot(){{{3
968 "class method that returns the highest cached ancestor of the current root
969 function! s:TreeDirNode.AbsoluteTreeRoot()
970     let currentNode = b:NERDTreeRoot
971     while currentNode.parent != {}
972         let currentNode = currentNode.parent
973     endwhile
974     return currentNode
975 endfunction
976 "FUNCTION: TreeDirNode.addChild(treenode, inOrder) {{{3
977 "Adds the given treenode to the list of children for this node
978 "
979 "Args:
980 "-treenode: the node to add
981 "-inOrder: 1 if the new node should be inserted in sorted order
982 function! s:TreeDirNode.addChild(treenode, inOrder)
983     call add(self.children, a:treenode)
984     let a:treenode.parent = self
985
986     if a:inOrder
987         call self.sortChildren()
988     endif
989 endfunction
990
991 "FUNCTION: TreeDirNode.close() {{{3
992 "Closes this directory
993 function! s:TreeDirNode.close()
994     let self.isOpen = 0
995 endfunction
996
997 "FUNCTION: TreeDirNode.closeChildren() {{{3
998 "Closes all the child dir nodes of this node
999 function! s:TreeDirNode.closeChildren()
1000     for i in self.children
1001         if i.path.isDirectory
1002             call i.close()
1003             call i.closeChildren()
1004         endif
1005     endfor
1006 endfunction
1007
1008 "FUNCTION: TreeDirNode.createChild(path, inOrder) {{{3
1009 "Instantiates a new child node for this node with the given path. The new
1010 "nodes parent is set to this node.
1011 "
1012 "Args:
1013 "path: a Path object that this node will represent/contain
1014 "inOrder: 1 if the new node should be inserted in sorted order
1015 "
1016 "Returns:
1017 "the newly created node
1018 function! s:TreeDirNode.createChild(path, inOrder)
1019     let newTreeNode = s:TreeFileNode.New(a:path)
1020     call self.addChild(newTreeNode, a:inOrder)
1021     return newTreeNode
1022 endfunction
1023
1024 "FUNCTION: TreeDirNode.findNode(path) {{{3
1025 "Will find one of the children (recursively) that has the given path
1026 "
1027 "Args:
1028 "path: a path object
1029 unlet s:TreeDirNode.findNode
1030 function! s:TreeDirNode.findNode(path)
1031     if a:path.equals(self.path)
1032         return self
1033     endif
1034     if stridx(a:path.str(1), self.path.str(1), 0) ==# -1
1035         return {}
1036     endif
1037
1038     if self.path.isDirectory
1039         for i in self.children
1040             let retVal = i.findNode(a:path)
1041             if retVal != {}
1042                 return retVal
1043             endif
1044         endfor
1045     endif
1046     return {}
1047 endfunction
1048 "FUNCTION: TreeDirNode.getChildCount() {{{3
1049 "Returns the number of children this node has
1050 function! s:TreeDirNode.getChildCount()
1051     return len(self.children)
1052 endfunction
1053
1054 "FUNCTION: TreeDirNode.getChild(path) {{{3
1055 "Returns child node of this node that has the given path or {} if no such node
1056 "exists.
1057 "
1058 "This function doesnt not recurse into child dir nodes
1059 "
1060 "Args:
1061 "path: a path object
1062 function! s:TreeDirNode.getChild(path)
1063     if stridx(a:path.str(1), self.path.str(1), 0) ==# -1
1064         return {}
1065     endif
1066
1067     let index = self.getChildIndex(a:path)
1068     if index ==# -1
1069         return {}
1070     else
1071         return self.children[index]
1072     endif
1073
1074 endfunction
1075
1076 "FUNCTION: TreeDirNode.getChildByIndex(indx, visible) {{{3
1077 "returns the child at the given index
1078 "Args:
1079 "indx: the index to get the child from
1080 "visible: 1 if only the visible children array should be used, 0 if all the
1081 "children should be searched.
1082 function! s:TreeDirNode.getChildByIndex(indx, visible)
1083     let array_to_search = a:visible? self.getVisibleChildren() : self.children
1084     if a:indx > len(array_to_search)
1085         throw "NERDTree.InvalidArgumentsError: Index is out of bounds."
1086     endif
1087     return array_to_search[a:indx]
1088 endfunction
1089
1090 "FUNCTION: TreeDirNode.getChildIndex(path) {{{3
1091 "Returns the index of the child node of this node that has the given path or
1092 "-1 if no such node exists.
1093 "
1094 "This function doesnt not recurse into child dir nodes
1095 "
1096 "Args:
1097 "path: a path object
1098 function! s:TreeDirNode.getChildIndex(path)
1099     if stridx(a:path.str(1), self.path.str(1), 0) ==# -1
1100         return -1
1101     endif
1102
1103     "do a binary search for the child
1104     let a = 0
1105     let z = self.getChildCount()
1106     while a < z
1107         let mid = (a+z)/2
1108         let diff = a:path.compareTo(self.children[mid].path)
1109
1110         if diff ==# -1
1111             let z = mid
1112         elseif diff ==# 1
1113             let a = mid+1
1114         else
1115             return mid
1116         endif
1117     endwhile
1118     return -1
1119 endfunction
1120
1121 "FUNCTION: TreeDirNode.GetSelected() {{{3
1122 "Returns the current node if it is a dir node, or else returns the current
1123 "nodes parent
1124 unlet s:TreeDirNode.GetSelected
1125 function! s:TreeDirNode.GetSelected()
1126     let currentDir = s:TreeFileNode.GetSelected()
1127     if currentDir != {} && !currentDir.isRoot()
1128         if currentDir.path.isDirectory ==# 0
1129             let currentDir = currentDir.parent
1130         endif
1131     endif
1132     return currentDir
1133 endfunction
1134 "FUNCTION: TreeDirNode.getVisibleChildCount() {{{3
1135 "Returns the number of visible children this node has
1136 function! s:TreeDirNode.getVisibleChildCount()
1137     return len(self.getVisibleChildren())
1138 endfunction
1139
1140 "FUNCTION: TreeDirNode.getVisibleChildren() {{{3
1141 "Returns a list of children to display for this node, in the correct order
1142 "
1143 "Return:
1144 "an array of treenodes
1145 function! s:TreeDirNode.getVisibleChildren()
1146     let toReturn = []
1147     for i in self.children
1148         if i.path.ignore() ==# 0
1149             call add(toReturn, i)
1150         endif
1151     endfor
1152     return toReturn
1153 endfunction
1154
1155 "FUNCTION: TreeDirNode.hasVisibleChildren() {{{3
1156 "returns 1 if this node has any childre, 0 otherwise..
1157 function! s:TreeDirNode.hasVisibleChildren()
1158     return self.getVisibleChildCount() != 0
1159 endfunction
1160
1161 "FUNCTION: TreeDirNode._initChildren() {{{3
1162 "Removes all childen from this node and re-reads them
1163 "
1164 "Args:
1165 "silent: 1 if the function should not echo any "please wait" messages for
1166 "large directories
1167 "
1168 "Return: the number of child nodes read
1169 function! s:TreeDirNode._initChildren(silent)
1170     "remove all the current child nodes
1171     let self.children = []
1172
1173     "get an array of all the files in the nodes dir
1174     let dir = self.path
1175     let filesStr = globpath(dir.strForGlob(), '*') . "\n" . globpath(dir.strForGlob(), '.*')
1176     let files = split(filesStr, "\n")
1177
1178     if !a:silent && len(files) > g:NERDTreeNotificationThreshold
1179         call s:echo("Please wait, caching a large dir ...")
1180     endif
1181
1182     let invalidFilesFound = 0
1183     for i in files
1184
1185         "filter out the .. and . directories
1186         "Note: we must match .. AND ../ cos sometimes the globpath returns
1187         "../ for path with strange chars (eg $)
1188         if i !~ '\.\.\/\?$' && i !~ '\.\/\?$'
1189
1190             "put the next file in a new node and attach it
1191             try
1192                 let path = s:Path.New(i)
1193                 call self.createChild(path, 0)
1194             catch /^NERDTree.\(InvalidArguments\|InvalidFiletype\)Error/
1195                 let invalidFilesFound += 1
1196             endtry
1197         endif
1198     endfor
1199
1200     call self.sortChildren()
1201
1202     if !a:silent && len(files) > g:NERDTreeNotificationThreshold
1203         call s:echo("Please wait, caching a large dir ... DONE (". self.getChildCount() ." nodes cached).")
1204     endif
1205
1206     if invalidFilesFound
1207         call s:echoWarning(invalidFilesFound . " file(s) could not be loaded into the NERD tree")
1208     endif
1209     return self.getChildCount()
1210 endfunction
1211 "FUNCTION: TreeDirNode.New(path) {{{3
1212 "Returns a new TreeNode object with the given path and parent
1213 "
1214 "Args:
1215 "path: a path object representing the full filesystem path to the file/dir that the node represents
1216 unlet s:TreeDirNode.New
1217 function! s:TreeDirNode.New(path)
1218     if a:path.isDirectory != 1
1219         throw "NERDTree.InvalidArgumentsError: A TreeDirNode object must be instantiated with a directory Path object."
1220     endif
1221
1222     let newTreeNode = copy(self)
1223     let newTreeNode.path = a:path
1224
1225     let newTreeNode.isOpen = 0
1226     let newTreeNode.children = []
1227
1228     let newTreeNode.parent = {}
1229
1230     return newTreeNode
1231 endfunction
1232 "FUNCTION: TreeDirNode.open() {{{3
1233 "Reads in all this nodes children
1234 "
1235 "Return: the number of child nodes read
1236 unlet s:TreeDirNode.open
1237 function! s:TreeDirNode.open()
1238     let self.isOpen = 1
1239     if self.children ==# []
1240         return self._initChildren(0)
1241     else
1242         return 0
1243     endif
1244 endfunction
1245
1246 " FUNCTION: TreeDirNode.openExplorer() {{{3
1247 " opens an explorer window for this node in the previous window (could be a
1248 " nerd tree or a netrw)
1249 function! s:TreeDirNode.openExplorer()
1250     let oldwin = winnr()
1251     call s:exec('wincmd p')
1252     if oldwin ==# winnr() || (&modified && s:bufInWindows(winbufnr(winnr())) < 2)
1253         call s:exec('wincmd p')
1254         call self.openSplit()
1255     else
1256         exec ("silent edit " . self.path.strForEditCmd())
1257     endif
1258 endfunction
1259 "FUNCTION: TreeDirNode.openRecursively() {{{3
1260 "Opens this treenode and all of its children whose paths arent 'ignored'
1261 "because of the file filters.
1262 "
1263 "This method is actually a wrapper for the OpenRecursively2 method which does
1264 "the work.
1265 function! s:TreeDirNode.openRecursively()
1266     call self._openRecursively2(1)
1267 endfunction
1268
1269 "FUNCTION: TreeDirNode._openRecursively2() {{{3
1270 "Opens this all children of this treenode recursively if either:
1271 "   *they arent filtered by file filters
1272 "   *a:forceOpen is 1
1273 "
1274 "Args:
1275 "forceOpen: 1 if this node should be opened regardless of file filters
1276 function! s:TreeDirNode._openRecursively2(forceOpen)
1277     if self.path.ignore() ==# 0 || a:forceOpen
1278         let self.isOpen = 1
1279         if self.children ==# []
1280             call self._initChildren(1)
1281         endif
1282
1283         for i in self.children
1284             if i.path.isDirectory ==# 1
1285                 call i._openRecursively2(0)
1286             endif
1287         endfor
1288     endif
1289 endfunction
1290
1291 "FUNCTION: TreeDirNode.refresh() {{{3
1292 unlet s:TreeDirNode.refresh
1293 function! s:TreeDirNode.refresh()
1294     call self.path.refresh()
1295
1296     "if this node was ever opened, refresh its children
1297     if self.isOpen || !empty(self.children)
1298         "go thru all the files/dirs under this node
1299         let newChildNodes = []
1300         let invalidFilesFound = 0
1301         let dir = self.path
1302         let filesStr = globpath(dir.strForGlob(), '*') . "\n" . globpath(dir.strForGlob(), '.*')
1303         let files = split(filesStr, "\n")
1304         for i in files
1305             if i !~ '\.\.$' && i !~ '\.$'
1306
1307                 try
1308                     "create a new path and see if it exists in this nodes children
1309                     let path = s:Path.New(i)
1310                     let newNode = self.getChild(path)
1311                     if newNode != {}
1312                         call newNode.refresh()
1313                         call add(newChildNodes, newNode)
1314
1315                     "the node doesnt exist so create it
1316                     else
1317                         let newNode = s:TreeFileNode.New(path)
1318                         let newNode.parent = self
1319                         call add(newChildNodes, newNode)
1320                     endif
1321
1322
1323                 catch /^NERDTree.InvalidArgumentsError/
1324                     let invalidFilesFound = 1
1325                 endtry
1326             endif
1327         endfor
1328
1329         "swap this nodes children out for the children we just read/refreshed
1330         let self.children = newChildNodes
1331         call self.sortChildren()
1332
1333         if invalidFilesFound
1334             call s:echoWarning("some files could not be loaded into the NERD tree")
1335         endif
1336     endif
1337 endfunction
1338
1339 "FUNCTION: TreeDirNode.removeChild(treenode) {{{3
1340 "
1341 "Removes the given treenode from this nodes set of children
1342 "
1343 "Args:
1344 "treenode: the node to remove
1345 "
1346 "Throws a NERDTree.ChildNotFoundError if the given treenode is not found
1347 function! s:TreeDirNode.removeChild(treenode)
1348     for i in range(0, self.getChildCount()-1)
1349         if self.children[i].equals(a:treenode)
1350             call remove(self.children, i)
1351             return
1352         endif
1353     endfor
1354
1355     throw "NERDTree.ChildNotFoundError: child node was not found"
1356 endfunction
1357
1358 "FUNCTION: TreeDirNode.sortChildren() {{{3
1359 "
1360 "Sorts the children of this node according to alphabetical order and the
1361 "directory priority.
1362 "
1363 function! s:TreeDirNode.sortChildren()
1364     let CompareFunc = function("s:compareNodes")
1365     call sort(self.children, CompareFunc)
1366 endfunction
1367
1368 "FUNCTION: TreeDirNode.toggleOpen() {{{3
1369 "Opens this directory if it is closed and vice versa
1370 function! s:TreeDirNode.toggleOpen()
1371     if self.isOpen ==# 1
1372         call self.close()
1373     else
1374         call self.open()
1375     endif
1376 endfunction
1377
1378 "FUNCTION: TreeDirNode.transplantChild(newNode) {{{3
1379 "Replaces the child of this with the given node (where the child node's full
1380 "path matches a:newNode's fullpath). The search for the matching node is
1381 "non-recursive
1382 "
1383 "Arg:
1384 "newNode: the node to graft into the tree
1385 function! s:TreeDirNode.transplantChild(newNode)
1386     for i in range(0, self.getChildCount()-1)
1387         if self.children[i].equals(a:newNode)
1388             let self.children[i] = a:newNode
1389             let a:newNode.parent = self
1390             break
1391         endif
1392     endfor
1393 endfunction
1394 "============================================================
1395 "CLASS: Path {{{2
1396 "============================================================
1397 let s:Path = {}
1398 "FUNCTION: Path.AbsolutePathFor(str) {{{3
1399 function! s:Path.AbsolutePathFor(str)
1400     let prependCWD = 0
1401     if s:running_windows
1402         let prependCWD = a:str !~ '^.:\(\\\|\/\)'
1403     else
1404         let prependCWD = a:str !~ '^/'
1405     endif
1406
1407     let toReturn = a:str
1408     if prependCWD
1409         let toReturn = getcwd() . s:os_slash . a:str
1410     endif
1411
1412     return toReturn
1413 endfunction
1414 "FUNCTION: Path.bookmarkNames() {{{3
1415 function! s:Path.bookmarkNames()
1416     if !exists("self._bookmarkNames")
1417         call self.cacheDisplayString()
1418     endif
1419     return self._bookmarkNames
1420 endfunction
1421 "FUNCTION: Path.cacheDisplayString() {{{3
1422 function! s:Path.cacheDisplayString()
1423     let self.cachedDisplayString = self.getLastPathComponent(1)
1424
1425     if self.isExecutable
1426         let self.cachedDisplayString = self.cachedDisplayString . '*'
1427     endif
1428
1429     let self._bookmarkNames = []
1430     for i in s:Bookmark.Bookmarks()
1431         if i.path.equals(self)
1432             call add(self._bookmarkNames, i.name)
1433         endif
1434     endfor
1435     if !empty(self._bookmarkNames)
1436         let self.cachedDisplayString .= ' {' . join(self._bookmarkNames) . '}'
1437     endif
1438
1439     if self.isSymLink
1440         let self.cachedDisplayString .=  ' -> ' . self.symLinkDest
1441     endif
1442
1443     if self.isReadOnly
1444         let self.cachedDisplayString .=  ' [RO]'
1445     endif
1446 endfunction
1447 "FUNCTION: Path.changeToDir() {{{3
1448 function! s:Path.changeToDir()
1449     let dir = self.strForCd()
1450     if self.isDirectory ==# 0
1451         let dir = self.getPathTrunk().strForCd()
1452     endif
1453
1454     try
1455         execute "cd " . dir
1456         call s:echo("CWD is now: " . getcwd())
1457     catch
1458         throw "NERDTree.PathChangeError: cannot change CWD to " . dir
1459     endtry
1460 endfunction
1461
1462 "FUNCTION: Path.compareTo() {{{3
1463 "
1464 "Compares this Path to the given path and returns 0 if they are equal, -1 if
1465 "this Path is "less than" the given path, or 1 if it is "greater".
1466 "
1467 "Args:
1468 "path: the path object to compare this to
1469 "
1470 "Return:
1471 "1, -1 or 0
1472 function! s:Path.compareTo(path)
1473     let thisPath = self.getLastPathComponent(1)
1474     let thatPath = a:path.getLastPathComponent(1)
1475
1476     "if the paths are the same then clearly we return 0
1477     if thisPath ==# thatPath
1478         return 0
1479     endif
1480
1481     let thisSS = self.getSortOrderIndex()
1482     let thatSS = a:path.getSortOrderIndex()
1483
1484     "compare the sort sequences, if they are different then the return
1485     "value is easy
1486     if thisSS < thatSS
1487         return -1
1488     elseif thisSS > thatSS
1489         return 1
1490     else
1491         "if the sort sequences are the same then compare the paths
1492         "alphabetically
1493         let pathCompare = g:NERDTreeCaseSensitiveSort ? thisPath <# thatPath : thisPath <? thatPath
1494         if pathCompare
1495             return -1
1496         else
1497             return 1
1498         endif
1499     endif
1500 endfunction
1501
1502 "FUNCTION: Path.Create(fullpath) {{{3
1503 "
1504 "Factory method.
1505 "
1506 "Creates a path object with the given path. The path is also created on the
1507 "filesystem. If the path already exists, a NERDTree.Path.Exists exception is
1508 "thrown. If any other errors occur, a NERDTree.Path exception is thrown.
1509 "
1510 "Args:
1511 "fullpath: the full filesystem path to the file/dir to create
1512 function! s:Path.Create(fullpath)
1513     "bail if the a:fullpath already exists
1514     if isdirectory(a:fullpath) || filereadable(a:fullpath)
1515         throw "NERDTree.CreatePathError: Directory Exists: '" . a:fullpath . "'"
1516     endif
1517
1518     try
1519
1520         "if it ends with a slash, assume its a dir create it
1521         if a:fullpath =~ '\(\\\|\/\)$'
1522             "whack the trailing slash off the end if it exists
1523             let fullpath = substitute(a:fullpath, '\(\\\|\/\)$', '', '')
1524
1525             call mkdir(fullpath, 'p')
1526
1527         "assume its a file and create
1528         else
1529             call writefile([], a:fullpath)
1530         endif
1531     catch
1532         throw "NERDTree.CreatePathError: Could not create path: '" . a:fullpath . "'"
1533     endtry
1534
1535     return s:Path.New(a:fullpath)
1536 endfunction
1537
1538 "FUNCTION: Path.copy(dest) {{{3
1539 "
1540 "Copies the file/dir represented by this Path to the given location
1541 "
1542 "Args:
1543 "dest: the location to copy this dir/file to
1544 function! s:Path.copy(dest)
1545     if !s:Path.CopyingSupported()
1546         throw "NERDTree.CopyingNotSupportedError: Copying is not supported on this OS"
1547     endif
1548
1549     let dest = s:Path.WinToUnixPath(a:dest)
1550
1551     let cmd = g:NERDTreeCopyCmd . " " . self.strForOS(0) . " " . dest
1552     let success = system(cmd)
1553     if success != 0
1554         throw "NERDTree.CopyError: Could not copy ''". self.strForOS(0) ."'' to: '" . a:dest . "'"
1555     endif
1556 endfunction
1557
1558 "FUNCTION: Path.CopyingSupported() {{{3
1559 "
1560 "returns 1 if copying is supported for this OS
1561 function! s:Path.CopyingSupported()
1562     return exists('g:NERDTreeCopyCmd')
1563 endfunction
1564
1565
1566 "FUNCTION: Path.copyingWillOverwrite(dest) {{{3
1567 "
1568 "returns 1 if copy this path to the given location will cause files to
1569 "overwritten
1570 "
1571 "Args:
1572 "dest: the location this path will be copied to
1573 function! s:Path.copyingWillOverwrite(dest)
1574     if filereadable(a:dest)
1575         return 1
1576     endif
1577
1578     if isdirectory(a:dest)
1579         let path = s:Path.JoinPathStrings(a:dest, self.getLastPathComponent(0))
1580         if filereadable(path)
1581             return 1
1582         endif
1583     endif
1584 endfunction
1585
1586 "FUNCTION: Path.delete() {{{3
1587 "
1588 "Deletes the file represented by this path.
1589 "Deletion of directories is not supported
1590 "
1591 "Throws NERDTree.Path.Deletion exceptions
1592 function! s:Path.delete()
1593     if self.isDirectory
1594
1595         let cmd = ""
1596         if s:running_windows
1597             "if we are runnnig windows then put quotes around the pathstring
1598             let cmd = g:NERDTreeRemoveDirCmd . self.strForOS(1)
1599         else
1600             let cmd = g:NERDTreeRemoveDirCmd . self.strForOS(1)
1601         endif
1602         let success = system(cmd)
1603
1604         if v:shell_error != 0
1605             throw "NERDTree.PathDeletionError: Could not delete directory: '" . self.strForOS(0) . "'"
1606         endif
1607     else
1608         let success = delete(self.strForOS(0))
1609         if success != 0
1610             throw "NERDTree.PathDeletionError: Could not delete file: '" . self.str(0) . "'"
1611         endif
1612     endif
1613
1614     "delete all bookmarks for this path
1615     for i in self.bookmarkNames()
1616         let bookmark = s:Bookmark.BookmarkFor(i)
1617         call bookmark.delete()
1618     endfor
1619 endfunction
1620
1621 "FUNCTION: Path.extractDriveLetter(fullpath) {{{3
1622 "
1623 "If running windows, cache the drive letter for this path
1624 function! s:Path.extractDriveLetter(fullpath)
1625     if s:running_windows
1626         let self.drive = substitute(a:fullpath, '\(^[a-zA-Z]:\).*', '\1', '')
1627     else
1628         let self.drive = ''
1629     endif
1630
1631 endfunction
1632 "FUNCTION: Path.exists() {{{3
1633 "return 1 if this path points to a location that is readable or is a directory
1634 function! s:Path.exists()
1635     return filereadable(self.strForOS(0)) || isdirectory(self.strForOS(0))
1636 endfunction
1637 "FUNCTION: Path.getDir() {{{3
1638 "
1639 "Returns this path if it is a directory, else this paths parent.
1640 "
1641 "Return:
1642 "a Path object
1643 function! s:Path.getDir()
1644     if self.isDirectory
1645         return self
1646     else
1647         return self.getParent()
1648     endif
1649 endfunction
1650 "FUNCTION: Path.getParent() {{{3
1651 "
1652 "Returns a new path object for this paths parent
1653 "
1654 "Return:
1655 "a new Path object
1656 function! s:Path.getParent()
1657     let path = '/'. join(self.pathSegments[0:-2], '/')
1658     return s:Path.New(path)
1659 endfunction
1660 "FUNCTION: Path.getLastPathComponent(dirSlash) {{{3
1661 "
1662 "Gets the last part of this path.
1663 "
1664 "Args:
1665 "dirSlash: if 1 then a trailing slash will be added to the returned value for
1666 "directory nodes.
1667 function! s:Path.getLastPathComponent(dirSlash)
1668     if empty(self.pathSegments)
1669         return ''
1670     endif
1671     let toReturn = self.pathSegments[-1]
1672     if a:dirSlash && self.isDirectory
1673         let toReturn = toReturn . '/'
1674     endif
1675     return toReturn
1676 endfunction
1677
1678 "FUNCTION: Path.getPathTrunk() {{{3
1679 "Gets the path without the last segment on the end.
1680 function! s:Path.getPathTrunk()
1681     return s:Path.New(self.strTrunk())
1682 endfunction
1683
1684 "FUNCTION: Path.getSortOrderIndex() {{{3
1685 "returns the index of the pattern in g:NERDTreeSortOrder that this path matches
1686 function! s:Path.getSortOrderIndex()
1687     let i = 0
1688     while i < len(g:NERDTreeSortOrder)
1689         if  self.getLastPathComponent(1) =~ g:NERDTreeSortOrder[i]
1690             return i
1691         endif
1692         let i = i + 1
1693     endwhile
1694     return s:NERDTreeSortStarIndex
1695 endfunction
1696
1697 "FUNCTION: Path.ignore() {{{3
1698 "returns true if this path should be ignored
1699 function! s:Path.ignore()
1700     let lastPathComponent = self.getLastPathComponent(0)
1701
1702     "filter out the user specified paths to ignore
1703     if b:NERDTreeIgnoreEnabled
1704         for i in g:NERDTreeIgnore
1705             if lastPathComponent =~ i
1706                 return 1
1707             endif
1708         endfor
1709     endif
1710
1711     "dont show hidden files unless instructed to
1712     if b:NERDTreeShowHidden ==# 0 && lastPathComponent =~ '^\.'
1713         return 1
1714     endif
1715
1716     if b:NERDTreeShowFiles ==# 0 && self.isDirectory ==# 0
1717         return 1
1718     endif
1719
1720     return 0
1721 endfunction
1722
1723 "FUNCTION: Path.JoinPathStrings(...) {{{3
1724 function! s:Path.JoinPathStrings(...)
1725     let components = []
1726     for i in a:000
1727         let components = extend(components, split(i, '/'))
1728     endfor
1729     return '/' . join(components, '/')
1730 endfunction
1731
1732 "FUNCTION: Path.equals() {{{3
1733 "
1734 "Determines whether 2 path objects are "equal".
1735 "They are equal if the paths they represent are the same
1736 "
1737 "Args:
1738 "path: the other path obj to compare this with
1739 function! s:Path.equals(path)
1740     return self.str(0) ==# a:path.str(0)
1741 endfunction
1742
1743 "FUNCTION: Path.New() {{{3
1744 "The Constructor for the Path object
1745 function! s:Path.New(path)
1746     let newPath = copy(self)
1747
1748     call newPath.readInfoFromDisk(s:Path.AbsolutePathFor(a:path))
1749
1750     let newPath.cachedDisplayString = ""
1751
1752     return newPath
1753 endfunction
1754
1755 "FUNCTION: Path.readInfoFromDisk(fullpath) {{{3
1756 "
1757 "
1758 "Throws NERDTree.Path.InvalidArguments exception.
1759 function! s:Path.readInfoFromDisk(fullpath)
1760     call self.extractDriveLetter(a:fullpath)
1761
1762     let fullpath = s:Path.WinToUnixPath(a:fullpath)
1763
1764     if getftype(fullpath) ==# "fifo"
1765         throw "NERDTree.InvalidFiletypeError: Cant handle FIFO files: " . a:fullpath
1766     endif
1767
1768     let self.pathSegments = split(fullpath, '/')
1769
1770     let self.isReadOnly = 0
1771     if isdirectory(a:fullpath)
1772         let self.isDirectory = 1
1773     elseif filereadable(a:fullpath)
1774         let self.isDirectory = 0
1775         let self.isReadOnly = filewritable(a:fullpath) ==# 0
1776     else
1777         throw "NERDTree.InvalidArgumentsError: Invalid path = " . a:fullpath
1778     endif
1779
1780     let self.isExecutable = 0
1781     if !self.isDirectory
1782         let self.isExecutable = getfperm(a:fullpath) =~ 'x'
1783     endif
1784
1785     "grab the last part of the path (minus the trailing slash)
1786     let lastPathComponent = self.getLastPathComponent(0)
1787
1788     "get the path to the new node with the parent dir fully resolved
1789     let hardPath = resolve(self.strTrunk()) . '/' . lastPathComponent
1790
1791     "if  the last part of the path is a symlink then flag it as such
1792     let self.isSymLink = (resolve(hardPath) != hardPath)
1793     if self.isSymLink
1794         let self.symLinkDest = resolve(fullpath)
1795
1796         "if the link is a dir then slap a / on the end of its dest
1797         if isdirectory(self.symLinkDest)
1798
1799             "we always wanna treat MS windows shortcuts as files for
1800             "simplicity
1801             if hardPath !~ '\.lnk$'
1802
1803                 let self.symLinkDest = self.symLinkDest . '/'
1804             endif
1805         endif
1806     endif
1807 endfunction
1808
1809 "FUNCTION: Path.refresh() {{{3
1810 function! s:Path.refresh()
1811     call self.readInfoFromDisk(self.strForOS(0))
1812     call self.cacheDisplayString()
1813 endfunction
1814
1815 "FUNCTION: Path.rename() {{{3
1816 "
1817 "Renames this node on the filesystem
1818 function! s:Path.rename(newPath)
1819     if a:newPath ==# ''
1820         throw "NERDTree.InvalidArgumentsError: Invalid newPath for renaming = ". a:newPath
1821     endif
1822
1823     let success =  rename(self.strForOS(0), a:newPath)
1824     if success != 0
1825         throw "NERDTree.PathRenameError: Could not rename: '" . self.strForOS(0) . "'" . 'to:' . a:newPath
1826     endif
1827     call self.readInfoFromDisk(a:newPath)
1828
1829     for i in self.bookmarkNames()
1830         let b = s:Bookmark.BookmarkFor(i)
1831         call b.setPath(copy(self))
1832     endfor
1833     call s:Bookmark.Write()
1834 endfunction
1835
1836 "FUNCTION: Path.str(esc) {{{3
1837 "
1838 "Gets the actual string path that this obj represents.
1839 "
1840 "Args:
1841 "esc: if 1 then all the tricky chars in the returned string will be escaped
1842 function! s:Path.str(esc)
1843     let toReturn = '/' . join(self.pathSegments, '/')
1844     if self.isDirectory && toReturn != '/'
1845         let toReturn  = toReturn . '/'
1846     endif
1847
1848     if a:esc
1849         let toReturn = escape(toReturn, s:escape_chars)
1850     endif
1851     return toReturn
1852 endfunction
1853
1854 "FUNCTION: Path.strAbs() {{{3
1855 "
1856 "Returns a string representing this path with all the symlinks resolved
1857 "
1858 "Return:
1859 "string
1860 function! s:Path.strAbs()
1861     return resolve(self.str(1))
1862 endfunction
1863
1864 "FUNCTION: Path.strForCd() {{{3
1865 "
1866 " returns a string that can be used with :cd
1867 "
1868 "Return:
1869 "a string that can be used in the view to represent this path
1870 function! s:Path.strForCd()
1871     if s:running_windows
1872         return self.strForOS(0)
1873     else
1874         return self.strForOS(1)
1875     endif
1876 endfunction
1877 "FUNCTION: Path.strDisplay() {{{3
1878 "
1879 "Returns a string that specifies how the path should be represented as a
1880 "string
1881 "
1882 "Return:
1883 "a string that can be used in the view to represent this path
1884 function! s:Path.strDisplay()
1885     if self.cachedDisplayString ==# ""
1886         call self.cacheDisplayString()
1887     endif
1888
1889     return self.cachedDisplayString
1890 endfunction
1891
1892 "FUNCTION: Path.strForEditCmd() {{{3
1893 "
1894 "Return: the string for this path that is suitable to be used with the :edit
1895 "command
1896 function! s:Path.strForEditCmd()
1897     let p = self.str(1)
1898     let cwd = getcwd()
1899
1900     if s:running_windows
1901         let p = tolower(self.strForOS(0))
1902         let cwd = tolower(getcwd())
1903     endif
1904
1905     let cwd = cwd . s:os_slash
1906
1907     "return a relative path if we can
1908     if stridx(p, cwd) ==# 0
1909         let p = strpart(p, strlen(cwd))
1910     endif
1911
1912     if p ==# ''
1913         let p = '.'
1914     endif
1915
1916     return p
1917
1918 endfunction
1919 "FUNCTION: Path.strForGlob() {{{3
1920 function! s:Path.strForGlob()
1921     let lead = s:os_slash
1922
1923     "if we are running windows then slap a drive letter on the front
1924     if s:running_windows
1925         let lead = self.drive . '\'
1926     endif
1927
1928     let toReturn = lead . join(self.pathSegments, s:os_slash)
1929
1930     if !s:running_windows
1931         let toReturn = escape(toReturn, s:escape_chars)
1932     endif
1933     return toReturn
1934 endfunction
1935 "FUNCTION: Path.strForOS(esc) {{{3
1936 "
1937 "Gets the string path for this path object that is appropriate for the OS.
1938 "EG, in windows c:\foo\bar
1939 "    in *nix  /foo/bar
1940 "
1941 "Args:
1942 "esc: if 1 then all the tricky chars in the returned string will be
1943 " escaped. If we are running windows then the str is double quoted instead.
1944 function! s:Path.strForOS(esc)
1945     let lead = s:os_slash
1946
1947     "if we are running windows then slap a drive letter on the front
1948     if s:running_windows
1949         let lead = self.drive . '\'
1950     endif
1951
1952     let toReturn = lead . join(self.pathSegments, s:os_slash)
1953
1954     if a:esc
1955         if s:running_windows
1956             let toReturn = '"' .  toReturn . '"'
1957         else
1958             let toReturn = escape(toReturn, s:escape_chars)
1959         endif
1960     endif
1961     return toReturn
1962 endfunction
1963
1964 "FUNCTION: Path.strTrunk() {{{3
1965 "Gets the path without the last segment on the end.
1966 function! s:Path.strTrunk()
1967     return self.drive . '/' . join(self.pathSegments[0:-2], '/')
1968 endfunction
1969
1970 "FUNCTION: Path.WinToUnixPath(pathstr){{{3
1971 "Takes in a windows path and returns the unix equiv
1972 "
1973 "A class level method
1974 "
1975 "Args:
1976 "pathstr: the windows path to convert
1977 function! s:Path.WinToUnixPath(pathstr)
1978     if !s:running_windows
1979         return a:pathstr
1980     endif
1981
1982     let toReturn = a:pathstr
1983
1984     "remove the x:\ of the front
1985     let toReturn = substitute(toReturn, '^.*:\(\\\|/\)\?', '/', "")
1986
1987     "convert all \ chars to /
1988     let toReturn = substitute(toReturn, '\', '/', "g")
1989
1990     return toReturn
1991 endfunction
1992
1993 " SECTION: General Functions {{{1
1994 "============================================================
1995 "FUNCTION: s:bufInWindows(bnum){{{2
1996 "[[STOLEN FROM VTREEEXPLORER.VIM]]
1997 "Determine the number of windows open to this buffer number.
1998 "Care of Yegappan Lakshman.  Thanks!
1999 "
2000 "Args:
2001 "bnum: the subject buffers buffer number
2002 function! s:bufInWindows(bnum)
2003     let cnt = 0
2004     let winnum = 1
2005     while 1
2006         let bufnum = winbufnr(winnum)
2007         if bufnum < 0
2008             break
2009         endif
2010         if bufnum ==# a:bnum
2011             let cnt = cnt + 1
2012         endif
2013         let winnum = winnum + 1
2014     endwhile
2015
2016     return cnt
2017 endfunction " >>>
2018 "FUNCTION: s:checkForBrowse(dir) {{{2
2019 "inits a secondary nerd tree in the current buffer if appropriate
2020 function! s:checkForBrowse(dir)
2021     if a:dir != '' && isdirectory(a:dir)
2022         call s:initNerdTreeInPlace(a:dir)
2023     endif
2024 endfunction
2025 "FUNCTION: s:compareBookmarks(first, second) {{{2
2026 "Compares two bookmarks
2027 function! s:compareBookmarks(first, second)
2028     return a:first.compareTo(a:second)
2029 endfunction
2030
2031 " FUNCTION: s:completeBookmarks(A,L,P) {{{2
2032 " completion function for the bookmark commands
2033 function! s:completeBookmarks(A,L,P)
2034     return filter(s:Bookmark.BookmarkNames(), 'v:val =~ "^' . a:A . '"')
2035 endfunction
2036 " FUNCTION: s:exec(cmd) {{{2
2037 " same as :exec cmd  but eventignore=all is set for the duration
2038 function! s:exec(cmd)
2039     let old_ei = &ei
2040     set ei=all
2041     exec a:cmd
2042     let &ei = old_ei
2043 endfunction
2044 "FUNCTION: s:initNerdTree(name) {{{2
2045 "Initialise the nerd tree for this tab. The tree will start in either the
2046 "given directory, or the directory associated with the given bookmark
2047 "
2048 "Args:
2049 "name: the name of a bookmark or a directory
2050 function! s:initNerdTree(name)
2051     let path = {}
2052     if s:Bookmark.BookmarkExistsFor(a:name)
2053         let path = s:Bookmark.BookmarkFor(a:name).path
2054     else
2055         let dir = a:name ==# '' ? getcwd() : a:name
2056
2057         "hack to get an absolute path if a relative path is given
2058         if dir =~ '^\.'
2059             let dir = getcwd() . s:os_slash . dir
2060         endif
2061         let dir = resolve(dir)
2062
2063         try
2064             let path = s:Path.New(dir)
2065         catch /^NERDTree.InvalidArgumentsError/
2066             call s:echo("No bookmark or directory found for: " . a:name)
2067             return
2068         endtry
2069     endif
2070     if !path.isDirectory
2071         let path = path.getParent()
2072     endif
2073
2074     "if instructed to, then change the vim CWD to the dir the NERDTree is
2075     "inited in
2076     if g:NERDTreeChDirMode != 0
2077         exec 'cd ' . path.strForCd()
2078     endif
2079
2080     if s:treeExistsForTab()
2081         if s:isTreeOpen()
2082             call s:closeTree()
2083         endif
2084         unlet t:NERDTreeBufName
2085     endif
2086
2087     let newRoot = s:TreeDirNode.New(path)
2088     call newRoot.open()
2089
2090     call s:createTreeWin()
2091     let b:treeShowHelp = 0
2092     let b:NERDTreeIgnoreEnabled = 1
2093     let b:NERDTreeShowFiles = g:NERDTreeShowFiles
2094     let b:NERDTreeShowHidden = g:NERDTreeShowHidden
2095     let b:NERDTreeShowBookmarks = g:NERDTreeShowBookmarks
2096     let b:NERDTreeRoot = newRoot
2097
2098     let b:NERDTreeType = "primary"
2099
2100     call s:renderView()
2101     call b:NERDTreeRoot.putCursorHere(0, 0)
2102 endfunction
2103
2104 "FUNCTION: s:initNerdTreeInPlace(dir) {{{2
2105 function! s:initNerdTreeInPlace(dir)
2106     try
2107         let path = s:Path.New(a:dir)
2108     catch /^NERDTree.InvalidArgumentsError/
2109         call s:echo("Invalid directory name:" . a:name)
2110         return
2111     endtry
2112
2113     "we want the directory buffer to disappear when we do the :edit below
2114     setlocal bufhidden=wipe
2115
2116     let previousBuf = expand("#")
2117
2118     "we need a unique name for each secondary tree buffer to ensure they are
2119     "all independent
2120     exec "silent edit " . s:nextBufferName()
2121
2122     let b:NERDTreePreviousBuf = bufnr(previousBuf)
2123
2124     let b:NERDTreeRoot = s:TreeDirNode.New(path)
2125     call b:NERDTreeRoot.open()
2126
2127     "throwaway buffer options
2128     setlocal noswapfile
2129     setlocal buftype=nofile
2130     setlocal bufhidden=hide
2131     setlocal nowrap
2132     setlocal foldcolumn=0
2133     setlocal nobuflisted
2134     setlocal nospell
2135     if g:NERDTreeShowLineNumbers
2136         setlocal nu
2137     else
2138         setlocal nonu
2139     endif
2140
2141     iabc <buffer>
2142
2143     if g:NERDTreeHighlightCursorline
2144         setlocal cursorline
2145     endif
2146
2147     call s:setupStatusline()
2148
2149     let b:treeShowHelp = 0
2150     let b:NERDTreeIgnoreEnabled = 1
2151     let b:NERDTreeShowFiles = g:NERDTreeShowFiles
2152     let b:NERDTreeShowHidden = g:NERDTreeShowHidden
2153     let b:NERDTreeShowBookmarks = g:NERDTreeShowBookmarks
2154
2155     let b:NERDTreeType = "secondary"
2156
2157     call s:bindMappings()
2158     setfiletype nerdtree
2159     " syntax highlighting
2160     if has("syntax") && exists("g:syntax_on") && !has("syntax_items")
2161         call s:setupSyntaxHighlighting()
2162     endif
2163
2164     call s:renderView()
2165 endfunction
2166 " FUNCTION: s:initNerdTreeMirror() {{{2
2167 function! s:initNerdTreeMirror()
2168
2169     "get the names off all the nerd tree buffers
2170     let treeBufNames = []
2171     for i in range(1, tabpagenr("$"))
2172         let nextName = s:tabpagevar(i, 'NERDTreeBufName')
2173         if nextName != -1 && (!exists("t:NERDTreeBufName") || nextName != t:NERDTreeBufName)
2174             call add(treeBufNames, nextName)
2175         endif
2176     endfor
2177     let treeBufNames = s:unique(treeBufNames)
2178
2179     "map the option names (that the user will be prompted with) to the nerd
2180     "tree buffer names
2181     let options = {}
2182     let i = 0
2183     while i < len(treeBufNames)
2184         let bufName = treeBufNames[i]
2185         let treeRoot = getbufvar(bufName, "NERDTreeRoot")
2186         let options[i+1 . '. ' . treeRoot.path.strForOS(0) . '  (buf name: ' . bufName . ')'] = bufName
2187         let i = i + 1
2188     endwhile
2189
2190     "work out which tree to mirror, if there is more than 1 then ask the user
2191     let bufferName = ''
2192     if len(keys(options)) > 1
2193         let choices = ["Choose a tree to mirror"]
2194         let choices = extend(choices, sort(keys(options)))
2195         let choice = inputlist(choices)
2196         if choice < 1 || choice > len(options) || choice ==# ''
2197             return
2198         endif
2199
2200         let bufferName = options[keys(options)[choice-1]]
2201     elseif len(keys(options)) ==# 1
2202         let bufferName = values(options)[0]
2203     else
2204         call s:echo("No trees to mirror")
2205         return
2206     endif
2207
2208     if s:treeExistsForTab() && s:isTreeOpen()
2209         call s:closeTree()
2210     endif
2211
2212     let t:NERDTreeBufName = bufferName
2213     call s:createTreeWin()
2214     exec 'buffer ' .  bufferName
2215     if !&hidden
2216         call s:renderView()
2217     endif
2218 endfunction
2219 " FUNCTION: s:nextBufferName() {{{2
2220 " returns the buffer name for the next nerd tree
2221 function! s:nextBufferName()
2222     let name = s:NERDTreeBufName . s:next_buffer_number
2223     let s:next_buffer_number += 1
2224     return name
2225 endfunction
2226 " FUNCTION: s:tabpagevar(tabnr, var) {{{2
2227 function! s:tabpagevar(tabnr, var)
2228     let currentTab = tabpagenr()
2229     let old_ei = &ei
2230     set ei=all
2231
2232     exec "tabnext " . a:tabnr
2233     let v = -1
2234     if exists('t:' . a:var)
2235         exec 'let v = t:' . a:var
2236     endif
2237     exec "tabnext " . currentTab
2238
2239     let &ei = old_ei
2240
2241     return v
2242 endfunction
2243 " Function: s:treeExistsForBuffer()   {{{2
2244 " Returns 1 if a nerd tree root exists in the current buffer
2245 function! s:treeExistsForBuf()
2246     return exists("b:NERDTreeRoot")
2247 endfunction
2248 " Function: s:treeExistsForTab()   {{{2
2249 " Returns 1 if a nerd tree root exists in the current tab
2250 function! s:treeExistsForTab()
2251     return exists("t:NERDTreeBufName")
2252 endfunction
2253 " Function: s:unique(list)   {{{2
2254 " returns a:list without duplicates
2255 function! s:unique(list)
2256   let uniqlist = []
2257   for elem in a:list
2258     if index(uniqlist, elem) ==# -1
2259       let uniqlist += [elem]
2260     endif
2261   endfor
2262   return uniqlist
2263 endfunction
2264 " SECTION: Public Functions {{{1
2265 "============================================================
2266 "Returns the node that the cursor is currently on.
2267 "
2268 "If the cursor is not in the NERDTree window, it is temporarily put there.
2269 "
2270 "If no NERD tree window exists for the current tab, a NERDTree.NoTreeForTab
2271 "exception is thrown.
2272 "
2273 "If the cursor is not on a node then an empty dictionary {} is returned.
2274 function! NERDTreeGetCurrentNode()
2275     if !s:treeExistsForTab() || !s:isTreeOpen()
2276         throw "NERDTree.NoTreeForTabError: there is no NERD tree open for the current tab"
2277     endif
2278
2279     let winnr = winnr()
2280     if winnr != s:getTreeWinNum()
2281         call s:putCursorInTreeWin()
2282     endif
2283
2284     let treenode = s:TreeFileNode.GetSelected()
2285
2286     if winnr != winnr()
2287         call s:exec('wincmd w')
2288     endif
2289
2290     return treenode
2291 endfunction
2292
2293 "Returns the path object for the current node.
2294 "
2295 "Subject to the same conditions as NERDTreeGetCurrentNode
2296 function! NERDTreeGetCurrentPath()
2297     let node = NERDTreeGetCurrentNode()
2298     if node != {}
2299         return node.path
2300     else
2301         return {}
2302     endif
2303 endfunction
2304
2305 " SECTION: View Functions {{{1
2306 "============================================================
2307 "FUNCTION: s:centerView() {{{2
2308 "centers the nerd tree window around the cursor (provided the nerd tree
2309 "options permit)
2310 function! s:centerView()
2311     if g:NERDTreeAutoCenter
2312         let current_line = winline()
2313         let lines_to_top = current_line
2314         let lines_to_bottom = winheight(s:getTreeWinNum()) - current_line
2315         if lines_to_top < g:NERDTreeAutoCenterThreshold || lines_to_bottom < g:NERDTreeAutoCenterThreshold
2316             normal! zz
2317         endif
2318     endif
2319 endfunction
2320 "FUNCTION: s:closeTree() {{{2
2321 "Closes the NERD tree window
2322 function! s:closeTree()
2323     if !s:isTreeOpen()
2324         throw "NERDTree.NoTreeFoundError: no NERDTree is open"
2325     endif
2326
2327     if winnr("$") != 1
2328         call s:exec(s:getTreeWinNum() . " wincmd w")
2329         close
2330         call s:exec("wincmd p")
2331     else
2332         :q
2333     endif
2334 endfunction
2335
2336 "FUNCTION: s:closeTreeIfOpen() {{{2
2337 "Closes the NERD tree window if it is open
2338 function! s:closeTreeIfOpen()
2339    if s:isTreeOpen()
2340       call s:closeTree()
2341    endif
2342 endfunction
2343 "FUNCTION: s:closeTreeIfQuitOnOpen() {{{2
2344 "Closes the NERD tree window if the close on open option is set
2345 function! s:closeTreeIfQuitOnOpen()
2346     if g:NERDTreeQuitOnOpen
2347         call s:closeTree()
2348     endif
2349 endfunction
2350 "FUNCTION: s:createTreeWin() {{{2
2351 "Inits the NERD tree window. ie. opens it, sizes it, sets all the local
2352 "options etc
2353 function! s:createTreeWin()
2354     "create the nerd tree window
2355     let splitLocation = g:NERDTreeWinPos ==# "left" ? "topleft " : "botright "
2356     let splitSize = g:NERDTreeWinSize
2357
2358     if !exists('t:NERDTreeBufName')
2359         let t:NERDTreeBufName = s:nextBufferName()
2360         silent! exec splitLocation . 'vertical ' . splitSize . ' new'
2361         silent! exec "edit " . t:NERDTreeBufName
2362     else
2363         silent! exec splitLocation . 'vertical ' . splitSize . ' split'
2364         silent! exec "buffer " . t:NERDTreeBufName
2365     endif
2366
2367     setlocal winfixwidth
2368
2369     "throwaway buffer options
2370     setlocal noswapfile
2371     setlocal buftype=nofile
2372     setlocal nowrap
2373     setlocal foldcolumn=0
2374     setlocal nobuflisted
2375     setlocal nospell
2376     if g:NERDTreeShowLineNumbers
2377         setlocal nu
2378     else
2379         setlocal nonu
2380     endif
2381
2382     iabc <buffer>
2383
2384     if g:NERDTreeHighlightCursorline
2385         setlocal cursorline
2386     endif
2387
2388     call s:setupStatusline()
2389
2390     call s:bindMappings()
2391     setfiletype nerdtree
2392     " syntax highlighting
2393     if has("syntax") && exists("g:syntax_on") && !has("syntax_items")
2394         call s:setupSyntaxHighlighting()
2395     endif
2396 endfunction
2397
2398 "FUNCTION: s:dumpHelp  {{{2
2399 "prints out the quick help
2400 function! s:dumpHelp()
2401     let old_h = @h
2402     if b:treeShowHelp ==# 1
2403         let @h=   "\" NERD tree (" . s:NERD_tree_version . ") quickhelp~\n"
2404         let @h=@h."\" ============================\n"
2405         let @h=@h."\" File node mappings~\n"
2406         let @h=@h."\" ". (g:NERDTreeMouseMode ==# 3 ? "single" : "double") ."-click,\n"
2407         let @h=@h."\" <CR>,\n"
2408         if b:NERDTreeType ==# "primary"
2409             let @h=@h."\" ". g:NERDTreeMapActivateNode .": open in prev window\n"
2410         else
2411             let @h=@h."\" ". g:NERDTreeMapActivateNode .": open in current window\n"
2412         endif
2413         if b:NERDTreeType ==# "primary"
2414             let @h=@h."\" ". g:NERDTreeMapPreview .": preview\n"
2415         endif
2416         let @h=@h."\" ". g:NERDTreeMapOpenInTab.": open in new tab\n"
2417         let @h=@h."\" ". g:NERDTreeMapOpenInTabSilent .": open in new tab silently\n"
2418         let @h=@h."\" middle-click,\n"
2419         let @h=@h."\" ". g:NERDTreeMapOpenSplit .": open split\n"
2420         let @h=@h."\" ". g:NERDTreeMapPreviewSplit .": preview split\n"
2421         let @h=@h."\" ". g:NERDTreeMapOpenVSplit .": open vsplit\n"
2422         let @h=@h."\" ". g:NERDTreeMapPreviewVSplit .": preview vsplit\n"
2423         let @h=@h."\" ". g:NERDTreeMapExecute.": Execute file\n"
2424
2425         let @h=@h."\"\n\" ----------------------------\n"
2426         let @h=@h."\" Directory node mappings~\n"
2427         let @h=@h."\" ". (g:NERDTreeMouseMode ==# 1 ? "double" : "single") ."-click,\n"
2428         let @h=@h."\" ". g:NERDTreeMapActivateNode .": open & close node\n"
2429         let @h=@h."\" ". g:NERDTreeMapOpenRecursively .": recursively open node\n"
2430         let @h=@h."\" ". g:NERDTreeMapCloseDir .": close parent of node\n"
2431         let @h=@h."\" ". g:NERDTreeMapCloseChildren .": close all child nodes of\n"
2432         let @h=@h."\"    current node recursively\n"
2433         let @h=@h."\" middle-click,\n"
2434         let @h=@h."\" ". g:NERDTreeMapOpenExpl.": explore selected dir\n"
2435
2436         let @h=@h."\"\n\" ----------------------------\n"
2437         let @h=@h."\" Bookmark table mappings~\n"
2438         let @h=@h."\" double-click,\n"
2439         let @h=@h."\" ". g:NERDTreeMapActivateNode .": open bookmark\n"
2440         let @h=@h."\" ". g:NERDTreeMapOpenInTab.": open in new tab\n"
2441         let @h=@h."\" ". g:NERDTreeMapOpenInTabSilent .": open in new tab silently\n"
2442         let @h=@h."\" ". g:NERDTreeMapDeleteBookmark .": delete bookmark\n"
2443
2444         let @h=@h."\"\n\" ----------------------------\n"
2445         let @h=@h."\" Tree navigation mappings~\n"
2446         let @h=@h."\" ". g:NERDTreeMapJumpRoot .": go to root\n"
2447         let @h=@h."\" ". g:NERDTreeMapJumpParent .": go to parent\n"
2448         let @h=@h."\" ". g:NERDTreeMapJumpFirstChild  .": go to first child\n"
2449         let @h=@h."\" ". g:NERDTreeMapJumpLastChild   .": go to last child\n"
2450         let @h=@h."\" ". g:NERDTreeMapJumpNextSibling .": go to next sibling\n"
2451         let @h=@h."\" ". g:NERDTreeMapJumpPrevSibling .": go to prev sibling\n"
2452
2453         let @h=@h."\"\n\" ----------------------------\n"
2454         let @h=@h."\" Filesystem mappings~\n"
2455         let @h=@h."\" ". g:NERDTreeMapChangeRoot .": change tree root to the\n"
2456         let @h=@h."\"    selected dir\n"
2457         let @h=@h."\" ". g:NERDTreeMapUpdir .": move tree root up a dir\n"
2458         let @h=@h."\" ". g:NERDTreeMapUpdirKeepOpen .": move tree root up a dir\n"
2459         let @h=@h."\"    but leave old root open\n"
2460         let @h=@h."\" ". g:NERDTreeMapRefresh .": refresh cursor dir\n"
2461         let @h=@h."\" ". g:NERDTreeMapRefreshRoot .": refresh current root\n"
2462         let @h=@h."\" ". g:NERDTreeMapFilesystemMenu .": Show filesystem menu\n"
2463         let @h=@h."\" ". g:NERDTreeMapChdir .":change the CWD to the\n"
2464         let @h=@h."\"    selected dir\n"
2465
2466         let @h=@h."\"\n\" ----------------------------\n"
2467         let @h=@h."\" Tree filtering mappings~\n"
2468         let @h=@h."\" ". g:NERDTreeMapToggleHidden .": hidden files (" . (b:NERDTreeShowHidden ? "on" : "off") . ")\n"
2469         let @h=@h."\" ". g:NERDTreeMapToggleFilters .": file filters (" . (b:NERDTreeIgnoreEnabled ? "on" : "off") . ")\n"
2470         let @h=@h."\" ". g:NERDTreeMapToggleFiles .": files (" . (b:NERDTreeShowFiles ? "on" : "off") . ")\n"
2471         let @h=@h."\" ". g:NERDTreeMapToggleBookmarks .": bookmarks (" . (b:NERDTreeShowBookmarks ? "on" : "off") . ")\n"
2472
2473         let @h=@h."\"\n\" ----------------------------\n"
2474         let @h=@h."\" Other mappings~\n"
2475         let @h=@h."\" ". g:NERDTreeMapQuit .": Close the NERDTree window\n"
2476         let @h=@h."\" ". g:NERDTreeMapHelp .": toggle help\n"
2477         let @h=@h."\"\n\" ----------------------------\n"
2478         let @h=@h."\" Bookmark commands~\n"
2479         let @h=@h."\" :Bookmark <name>\n"
2480         let @h=@h."\" :BookmarkToRoot <name>\n"
2481         let @h=@h."\" :RevealBookmark <name>\n"
2482         let @h=@h."\" :OpenBookmark <name>\n"
2483         let @h=@h."\" :ClearBookmarks [<names>]\n"
2484         let @h=@h."\" :ClearAllBookmarks\n"
2485     else
2486         let @h="\" Press ". g:NERDTreeMapHelp ." for help\n"
2487     endif
2488
2489     silent! put h
2490
2491     let @h = old_h
2492 endfunction
2493 "FUNCTION: s:echo  {{{2
2494 "A wrapper for :echo. Appends 'NERDTree:' on the front of all messages
2495 "
2496 "Args:
2497 "msg: the message to echo
2498 function! s:echo(msg)
2499     redraw
2500     echomsg "NERDTree: " . a:msg
2501 endfunction
2502 "FUNCTION: s:echoWarning {{{2
2503 "Wrapper for s:echo, sets the message type to warningmsg for this message
2504 "Args:
2505 "msg: the message to echo
2506 function! s:echoWarning(msg)
2507     echohl warningmsg
2508     call s:echo(a:msg)
2509     echohl normal
2510 endfunction
2511 "FUNCTION: s:echoError {{{2
2512 "Wrapper for s:echo, sets the message type to errormsg for this message
2513 "Args:
2514 "msg: the message to echo
2515 function! s:echoError(msg)
2516     echohl errormsg
2517     call s:echo(a:msg)
2518     echohl normal
2519 endfunction
2520 "FUNCTION: s:firstUsableWindow(){{{2
2521 "find the window number of the first normal window
2522 function! s:firstUsableWindow()
2523     let i = 1
2524     while i <= winnr("$")
2525         let bnum = winbufnr(i)
2526         if bnum != -1 && getbufvar(bnum, '&buftype') ==# ''
2527                     \ && !getwinvar(i, '&previewwindow')
2528                     \ && (!getbufvar(bnum, '&modified') || &hidden)
2529             return i
2530         endif
2531
2532         let i += 1
2533     endwhile
2534     return -1
2535 endfunction
2536 "FUNCTION: s:getPath(ln) {{{2
2537 "Gets the full path to the node that is rendered on the given line number
2538 "
2539 "Args:
2540 "ln: the line number to get the path for
2541 "
2542 "Return:
2543 "A path if a node was selected, {} if nothing is selected.
2544 "If the 'up a dir' line was selected then the path to the parent of the
2545 "current root is returned
2546 function! s:getPath(ln)
2547     let line = getline(a:ln)
2548
2549     "check to see if we have the root node
2550     if line =~ '^\/'
2551         return b:NERDTreeRoot.path
2552     endif
2553
2554     " in case called from outside the tree
2555     if line !~ '^ *[|`]' || line =~ '^$'
2556         return {}
2557     endif
2558
2559     if line ==# s:tree_up_dir_line
2560         return b:NERDTreeRoot.path.getParent()
2561     endif
2562
2563     let indent = s:indentLevelFor(line)
2564
2565     "remove the tree parts and the leading space
2566     let curFile = s:stripMarkupFromLine(line, 0)
2567
2568     let wasdir = 0
2569     if curFile =~ '/$'
2570         let wasdir = 1
2571         let curFile = substitute(curFile, '/\?$', '/', "")
2572     endif
2573
2574
2575     let dir = ""
2576     let lnum = a:ln
2577     while lnum > 0
2578         let lnum = lnum - 1
2579         let curLine = getline(lnum)
2580         let curLineStripped = s:stripMarkupFromLine(curLine, 1)
2581
2582         "have we reached the top of the tree?
2583         if curLine =~ '^/'
2584             let dir = substitute (curLine, ' *$', "", "") . dir
2585             break
2586         endif
2587         if curLineStripped =~ '/$'
2588             let lpindent = s:indentLevelFor(curLine)
2589             if lpindent < indent
2590                 let indent = indent - 1
2591
2592                 let dir = substitute (curLineStripped,'^\\', "", "") . dir
2593                 continue
2594             endif
2595         endif
2596     endwhile
2597     let curFile = b:NERDTreeRoot.path.drive . dir . curFile
2598     let toReturn = s:Path.New(curFile)
2599     return toReturn
2600 endfunction
2601
2602 "FUNCTION: s:getSelectedBookmark() {{{2
2603 "returns the bookmark the cursor is over in the bookmarks table or {}
2604 function! s:getSelectedBookmark()
2605     let line = getline(".")
2606     let name = substitute(line, '^>\(.\{-}\) .\+$', '\1', '')
2607     if name != line
2608         try
2609             return s:Bookmark.BookmarkFor(name)
2610         catch /^NERDTree.BookmarkNotFoundError/
2611             return {}
2612         endtry
2613     endif
2614     return {}
2615 endfunction
2616
2617 "FUNCTION: s:getTreeWinNum() {{{2
2618 "gets the nerd tree window number for this tab
2619 function! s:getTreeWinNum()
2620     if exists("t:NERDTreeBufName")
2621         return bufwinnr(t:NERDTreeBufName)
2622     else
2623         return -1
2624     endif
2625 endfunction
2626 "FUNCTION: s:indentLevelFor(line) {{{2
2627 function! s:indentLevelFor(line)
2628     return match(a:line, '[^ \-+~`|]') / s:tree_wid
2629 endfunction
2630 "FUNCTION: s:isTreeOpen() {{{2
2631 function! s:isTreeOpen()
2632     return s:getTreeWinNum() != -1
2633 endfunction
2634 "FUNCTION: s:isWindowUsable(winnumber) {{{2
2635 "Returns 0 if opening a file from the tree in the given window requires it to
2636 "be split, 1 otherwise
2637 "
2638 "Args:
2639 "winnumber: the number of the window in question
2640 function! s:isWindowUsable(winnumber)
2641     "gotta split if theres only one window (i.e. the NERD tree)
2642     if winnr("$") ==# 1
2643         return 0
2644     endif
2645
2646     let oldwinnr = winnr()
2647     call s:exec(a:winnumber . "wincmd p")
2648     let specialWindow = getbufvar("%", '&buftype') != '' || getwinvar('%', '&previewwindow')
2649     let modified = &modified
2650     call s:exec(oldwinnr . "wincmd p")
2651
2652     "if its a special window e.g. quickfix or another explorer plugin then we
2653     "have to split
2654     if specialWindow
2655         return 0
2656     endif
2657
2658     if &hidden
2659         return 1
2660     endif
2661
2662     return !modified || s:bufInWindows(winbufnr(a:winnumber)) >= 2
2663 endfunction
2664
2665 " FUNCTION: s:jumpToChild(direction) {{{2
2666 " Args:
2667 " direction: 0 if going to first child, 1 if going to last
2668 function! s:jumpToChild(direction)
2669     let currentNode = s:TreeFileNode.GetSelected()
2670     if currentNode ==# {} || currentNode.isRoot()
2671         call s:echo("cannot jump to " . (a:direction ? "last" : "first") .  " child")
2672         return
2673     end
2674     let dirNode = currentNode.parent
2675     let childNodes = dirNode.getVisibleChildren()
2676
2677     let targetNode = childNodes[0]
2678     if a:direction
2679         let targetNode = childNodes[len(childNodes) - 1]
2680     endif
2681
2682     if targetNode.equals(currentNode)
2683         let siblingDir = currentNode.parent.findOpenDirSiblingWithVisibleChildren(a:direction)
2684         if siblingDir != {}
2685             let indx = a:direction ? siblingDir.getVisibleChildCount()-1 : 0
2686             let targetNode = siblingDir.getChildByIndex(indx, 1)
2687         endif
2688     endif
2689
2690     call targetNode.putCursorHere(1, 0)
2691
2692     call s:centerView()
2693 endfunction
2694
2695
2696 "FUNCTION: s:promptToDelBuffer(bufnum, msg){{{2
2697 "prints out the given msg and, if the user responds by pushing 'y' then the
2698 "buffer with the given bufnum is deleted
2699 "
2700 "Args:
2701 "bufnum: the buffer that may be deleted
2702 "msg: a message that will be echoed to the user asking them if they wish to
2703 "     del the buffer
2704 function! s:promptToDelBuffer(bufnum, msg)
2705     echo a:msg
2706     if nr2char(getchar()) ==# 'y'
2707         exec "silent bdelete! " . a:bufnum
2708     endif
2709 endfunction
2710
2711 "FUNCTION: s:putCursorOnBookmarkTable(){{{2
2712 "Places the cursor at the top of the bookmarks table
2713 function! s:putCursorOnBookmarkTable()
2714     if !b:NERDTreeShowBookmarks
2715         throw "NERDTree.IllegalOperationError: cant find bookmark table, bookmarks arent active"
2716     endif
2717
2718     let rootNodeLine = s:TreeFileNode.GetRootLineNum()
2719
2720     let line = 1
2721     while getline(line) !~ '^>-\+Bookmarks-\+$'
2722         let line = line + 1
2723         if line >= rootNodeLine
2724             throw "NERDTree.BookmarkTableNotFoundError: didnt find the bookmarks table"
2725         endif
2726     endwhile
2727     call cursor(line, 0)
2728 endfunction
2729
2730 "FUNCTION: s:putCursorInTreeWin(){{{2
2731 "Places the cursor in the nerd tree window
2732 function! s:putCursorInTreeWin()
2733     if !s:isTreeOpen()
2734         throw "NERDTree.InvalidOperationError: cant put cursor in NERD tree window, no window exists"
2735     endif
2736
2737     call s:exec(s:getTreeWinNum() . "wincmd w")
2738 endfunction
2739
2740 "FUNCTION: s:renderBookmarks {{{2
2741 function! s:renderBookmarks()
2742
2743     call setline(line(".")+1, ">----------Bookmarks----------")
2744     call cursor(line(".")+1, col("."))
2745
2746     for i in s:Bookmark.Bookmarks()
2747         call setline(line(".")+1, i.str())
2748         call cursor(line(".")+1, col("."))
2749     endfor
2750
2751     call setline(line(".")+1, '')
2752     call cursor(line(".")+1, col("."))
2753 endfunction
2754 "FUNCTION: s:renderView {{{2
2755 "The entry function for rendering the tree
2756 function! s:renderView()
2757     setlocal modifiable
2758
2759     "remember the top line of the buffer and the current line so we can
2760     "restore the view exactly how it was
2761     let curLine = line(".")
2762     let curCol = col(".")
2763     let topLine = line("w0")
2764
2765     "delete all lines in the buffer (being careful not to clobber a register)
2766     silent 1,$delete _
2767
2768     call s:dumpHelp()
2769
2770     "delete the blank line before the help and add one after it
2771     call setline(line(".")+1, "")
2772     call cursor(line(".")+1, col("."))
2773
2774     if b:NERDTreeShowBookmarks
2775         call s:renderBookmarks()
2776     endif
2777
2778     "add the 'up a dir' line
2779     call setline(line(".")+1, s:tree_up_dir_line)
2780     call cursor(line(".")+1, col("."))
2781
2782     "draw the header line
2783     call setline(line(".")+1, b:NERDTreeRoot.path.str(0))
2784     call cursor(line(".")+1, col("."))
2785
2786     "draw the tree
2787     let old_o = @o
2788     let @o = b:NERDTreeRoot.renderToString()
2789     silent put o
2790     let @o = old_o
2791
2792     "delete the blank line at the top of the buffer
2793     silent 1,1delete _
2794
2795     "restore the view
2796     let old_scrolloff=&scrolloff
2797     let &scrolloff=0
2798     call cursor(topLine, 1)
2799     normal! zt
2800     call cursor(curLine, curCol)
2801     let &scrolloff = old_scrolloff
2802
2803     setlocal nomodifiable
2804 endfunction
2805
2806 "FUNCTION: s:renderViewSavingPosition {{{2
2807 "Renders the tree and ensures the cursor stays on the current node or the
2808 "current nodes parent if it is no longer available upon re-rendering
2809 function! s:renderViewSavingPosition()
2810     let currentNode = s:TreeFileNode.GetSelected()
2811
2812     "go up the tree till we find a node that will be visible or till we run
2813     "out of nodes
2814     while currentNode != {} && !currentNode.isVisible() && !currentNode.isRoot()
2815         let currentNode = currentNode.parent
2816     endwhile
2817
2818     call s:renderView()
2819
2820     if currentNode != {}
2821         call currentNode.putCursorHere(0, 0)
2822     endif
2823 endfunction
2824 "FUNCTION: s:restoreScreenState() {{{2
2825 "
2826 "Sets the screen state back to what it was when s:saveScreenState was last
2827 "called.
2828 "
2829 "Assumes the cursor is in the NERDTree window
2830 function! s:restoreScreenState()
2831     if !exists("b:NERDTreeOldTopLine") || !exists("b:NERDTreeOldPos") || !exists("b:NERDTreeOldWindowSize")
2832         return
2833     endif
2834     exec("silent vertical resize ".b:NERDTreeOldWindowSize)
2835
2836     let old_scrolloff=&scrolloff
2837     let &scrolloff=0
2838     call cursor(b:NERDTreeOldTopLine, 0)
2839     normal! zt
2840     call setpos(".", b:NERDTreeOldPos)
2841     let &scrolloff=old_scrolloff
2842 endfunction
2843
2844 "FUNCTION: s:saveScreenState() {{{2
2845 "Saves the current cursor position in the current buffer and the window
2846 "scroll position
2847 function! s:saveScreenState()
2848     let win = winnr()
2849     try
2850         call s:putCursorInTreeWin()
2851         let b:NERDTreeOldPos = getpos(".")
2852         let b:NERDTreeOldTopLine = line("w0")
2853         let b:NERDTreeOldWindowSize = winwidth("")
2854         call s:exec(win . "wincmd w")
2855     catch /^NERDTree.InvalidOperationError/
2856     endtry
2857 endfunction
2858
2859 "FUNCTION: s:setupStatusline() {{{2
2860 function! s:setupStatusline()
2861     if g:NERDTreeStatusline != -1
2862         let &l:statusline = g:NERDTreeStatusline
2863     endif
2864 endfunction
2865 "FUNCTION: s:setupSyntaxHighlighting() {{{2
2866 function! s:setupSyntaxHighlighting()
2867     "treeFlags are syntax items that should be invisible, but give clues as to
2868     "how things should be highlighted
2869     syn match treeFlag #\~#
2870     syn match treeFlag #\[RO\]#
2871
2872     "highlighting for the .. (up dir) line at the top of the tree
2873     execute "syn match treeUp #". s:tree_up_dir_line ."#"
2874
2875     "highlighting for the ~/+ symbols for the directory nodes
2876     syn match treeClosable #\~\<#
2877     syn match treeClosable #\~\.#
2878     syn match treeOpenable #+\<#
2879     syn match treeOpenable #+\.#he=e-1
2880
2881     "highlighting for the tree structural parts
2882     syn match treePart #|#
2883     syn match treePart #`#
2884     syn match treePartFile #[|`]-#hs=s+1 contains=treePart
2885
2886     "quickhelp syntax elements
2887     syn match treeHelpKey #" \{1,2\}[^ ]*:#hs=s+2,he=e-1
2888     syn match treeHelpKey #" \{1,2\}[^ ]*,#hs=s+2,he=e-1
2889     syn match treeHelpTitle #" .*\~#hs=s+2,he=e-1 contains=treeFlag
2890     syn match treeToggleOn #".*(on)#hs=e-2,he=e-1 contains=treeHelpKey
2891     syn match treeToggleOff #".*(off)#hs=e-3,he=e-1 contains=treeHelpKey
2892     syn match treeHelpCommand #" :.\{-}\>#hs=s+3
2893     syn match treeHelp  #^".*# contains=treeHelpKey,treeHelpTitle,treeFlag,treeToggleOff,treeToggleOn,treeHelpCommand
2894
2895     "highlighting for readonly files
2896     syn match treeRO #.*\[RO\]#hs=s+2 contains=treeFlag,treeBookmark,treePart,treePartFile
2897
2898     "highlighting for sym links
2899     syn match treeLink #[^-| `].* -> # contains=treeBookmark,treeOpenable,treeClosable,treeDirSlash
2900
2901     "highlighing for directory nodes and file nodes
2902     syn match treeDirSlash #/#
2903     syn match treeDir #[^-| `].*/# contains=treeLink,treeDirSlash,treeOpenable,treeClosable
2904     syn match treeExecFile  #[|`]-.*\*\($\| \)# contains=treeLink,treePart,treeRO,treePartFile,treeBookmark
2905     syn match treeFile  #|-.*# contains=treeLink,treePart,treeRO,treePartFile,treeBookmark,treeExecFile
2906     syn match treeFile  #`-.*# contains=treeLink,treePart,treeRO,treePartFile,treeBookmark,treeExecFile
2907     syn match treeCWD #^/.*$#
2908
2909     "highlighting for bookmarks
2910     syn match treeBookmark # {.*}#hs=s+1
2911
2912     "highlighting for the bookmarks table
2913     syn match treeBookmarksLeader #^>#
2914     syn match treeBookmarksHeader #^>-\+Bookmarks-\+$# contains=treeBookmarksLeader
2915     syn match treeBookmarkName #^>.\{-} #he=e-1 contains=treeBookmarksLeader
2916     syn match treeBookmark #^>.*$# contains=treeBookmarksLeader,treeBookmarkName,treeBookmarksHeader
2917
2918     if g:NERDChristmasTree
2919         hi def link treePart Special
2920         hi def link treePartFile Type
2921         hi def link treeFile Normal
2922         hi def link treeExecFile Title
2923         hi def link treeDirSlash Identifier
2924         hi def link treeClosable Type
2925     else
2926         hi def link treePart Normal
2927         hi def link treePartFile Normal
2928         hi def link treeFile Normal
2929         hi def link treeClosable Title
2930     endif
2931
2932     hi def link treeBookmarksHeader statement
2933     hi def link treeBookmarksLeader ignore
2934     hi def link treeBookmarkName Identifier
2935     hi def link treeBookmark normal
2936
2937     hi def link treeHelp String
2938     hi def link treeHelpKey Identifier
2939     hi def link treeHelpCommand Identifier
2940     hi def link treeHelpTitle Macro
2941     hi def link treeToggleOn Question
2942     hi def link treeToggleOff WarningMsg
2943
2944     hi def link treeDir Directory
2945     hi def link treeUp Directory
2946     hi def link treeCWD Statement
2947     hi def link treeLink Macro
2948     hi def link treeOpenable Title
2949     hi def link treeFlag ignore
2950     hi def link treeRO WarningMsg
2951     hi def link treeBookmark Statement
2952
2953     hi def link NERDTreeCurrentNode Search
2954 endfunction
2955
2956 "FUNCTION: s:stripMarkupFromLine(line, removeLeadingSpaces){{{2
2957 "returns the given line with all the tree parts stripped off
2958 "
2959 "Args:
2960 "line: the subject line
2961 "removeLeadingSpaces: 1 if leading spaces are to be removed (leading spaces =
2962 "any spaces before the actual text of the node)
2963 function! s:stripMarkupFromLine(line, removeLeadingSpaces)
2964     let line = a:line
2965     "remove the tree parts and the leading space
2966     let line = substitute (line, s:tree_markup_reg,"","")
2967
2968     "strip off any read only flag
2969     let line = substitute (line, ' \[RO\]', "","")
2970
2971     "strip off any bookmark flags
2972     let line = substitute (line, ' {[^}]*}', "","")
2973
2974     "strip off any executable flags
2975     let line = substitute (line, '*\ze\($\| \)', "","")
2976
2977     let wasdir = 0
2978     if line =~ '/$'
2979         let wasdir = 1
2980     endif
2981     let line = substitute (line,' -> .*',"","") " remove link to
2982     if wasdir ==# 1
2983         let line = substitute (line, '/\?$', '/', "")
2984     endif
2985
2986     if a:removeLeadingSpaces
2987         let line = substitute (line, '^ *', '', '')
2988     endif
2989
2990     return line
2991 endfunction
2992
2993 "FUNCTION: s:toggle(dir) {{{2
2994 "Toggles the NERD tree. I.e the NERD tree is open, it is closed, if it is
2995 "closed it is restored or initialized (if it doesnt exist)
2996 "
2997 "Args:
2998 "dir: the full path for the root node (is only used if the NERD tree is being
2999 "initialized.
3000 function! s:toggle(dir)
3001     if s:treeExistsForTab()
3002         if !s:isTreeOpen()
3003             call s:createTreeWin()
3004             call s:restoreScreenState()
3005             if !&hidden
3006                 call s:renderView()
3007             endif
3008         else
3009             call s:closeTree()
3010         endif
3011     else
3012         call s:initNerdTree(a:dir)
3013     endif
3014 endfunction
3015 "SECTION: Interface bindings {{{1
3016 "============================================================
3017 "FUNCTION: s:activateNode(forceKeepWindowOpen) {{{2
3018 "If the current node is a file, open it in the previous window (or a new one
3019 "if the previous is modified). If it is a directory then it is opened.
3020 "
3021 "args:
3022 "forceKeepWindowOpen - dont close the window even if NERDTreeQuitOnOpen is set
3023 function! s:activateNode(forceKeepWindowOpen)
3024     if getline(".") ==# s:tree_up_dir_line
3025         return s:upDir(0)
3026     endif
3027
3028     let treenode = s:TreeFileNode.GetSelected()
3029     if treenode != {}
3030         if treenode.path.isDirectory
3031             call treenode.toggleOpen()
3032             call s:renderView()
3033             call treenode.putCursorHere(0, 0)
3034         else
3035             call treenode.open()
3036             if !a:forceKeepWindowOpen
3037                 call s:closeTreeIfQuitOnOpen()
3038             end
3039         endif
3040     else
3041         let bookmark = s:getSelectedBookmark()
3042         if !empty(bookmark)
3043             if bookmark.path.isDirectory
3044                 call bookmark.toRoot()
3045             else
3046                 if bookmark.validate()
3047                     let n = s:TreeFileNode.New(bookmark.path)
3048                     call n.open()
3049                 endif
3050             endif
3051         endif
3052     endif
3053 endfunction
3054
3055 "FUNCTION: s:bindMappings() {{{2
3056 function! s:bindMappings()
3057     " set up mappings and commands for this buffer
3058     nnoremap <silent> <buffer> <middlerelease> :call <SID>handleMiddleMouse()<cr>
3059     nnoremap <silent> <buffer> <leftrelease> <leftrelease>:call <SID>checkForActivate()<cr>
3060     nnoremap <silent> <buffer> <2-leftmouse> :call <SID>activateNode(0)<cr>
3061
3062     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapActivateNode . " :call <SID>activateNode(0)<cr>"
3063     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapOpenSplit ." :call <SID>openEntrySplit(0,0)<cr>"
3064     exec "nnoremap <silent> <buffer> <cr> :call <SID>activateNode(0)<cr>"
3065
3066     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapPreview ." :call <SID>previewNode(0)<cr>"
3067     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapPreviewSplit ." :call <SID>previewNode(1)<cr>"
3068
3069     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapOpenVSplit ." :call <SID>openEntrySplit(1,0)<cr>"
3070     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapPreviewVSplit ." :call <SID>previewNode(2)<cr>"
3071
3072     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapExecute ." :call <SID>executeNode()<cr>"
3073
3074     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapOpenRecursively ." :call <SID>openNodeRecursively()<cr>"
3075
3076     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapUpdirKeepOpen ." :call <SID>upDir(1)<cr>"
3077     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapUpdir ." :call <SID>upDir(0)<cr>"
3078     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapChangeRoot ." :call <SID>chRoot()<cr>"
3079
3080     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapChdir ." :call <SID>chCwd()<cr>"
3081
3082     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapQuit ." :call <SID>closeTreeWindow()<cr>"
3083
3084     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapRefreshRoot ." :call <SID>refreshRoot()<cr>"
3085     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapRefresh ." :call <SID>refreshCurrent()<cr>"
3086
3087     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapHelp ." :call <SID>displayHelp()<cr>"
3088     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapToggleHidden ." :call <SID>toggleShowHidden()<cr>"
3089     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapToggleFilters ." :call <SID>toggleIgnoreFilter()<cr>"
3090     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapToggleFiles ." :call <SID>toggleShowFiles()<cr>"
3091     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapToggleBookmarks ." :call <SID>toggleShowBookmarks()<cr>"
3092
3093     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapCloseDir ." :call <SID>closeCurrentDir()<cr>"
3094     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapCloseChildren ." :call <SID>closeChildren()<cr>"
3095
3096     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapFilesystemMenu ." :call <SID>showFileSystemMenu()<cr>"
3097
3098     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapJumpParent ." :call <SID>jumpToParent()<cr>"
3099     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapJumpNextSibling ." :call <SID>jumpToSibling(1)<cr>"
3100     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapJumpPrevSibling ." :call <SID>jumpToSibling(0)<cr>"
3101     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapJumpFirstChild ." :call <SID>jumpToFirstChild()<cr>"
3102     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapJumpLastChild ." :call <SID>jumpToLastChild()<cr>"
3103     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapJumpRoot ." :call <SID>jumpToRoot()<cr>"
3104
3105     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapOpenInTab ." :call <SID>openInNewTab(0)<cr>"
3106     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapOpenInTabSilent ." :call <SID>openInNewTab(1)<cr>"
3107
3108     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapOpenExpl ." :call <SID>openExplorer()<cr>"
3109
3110     exec "nnoremap <silent> <buffer> ". g:NERDTreeMapDeleteBookmark ." :call <SID>deleteBookmark()<cr>"
3111
3112     command! -buffer -nargs=1 Bookmark :call <SID>bookmarkNode('<args>')
3113     command! -buffer -complete=customlist,s:completeBookmarks -nargs=1 RevealBookmark :call <SID>revealBookmark('<args>')
3114     command! -buffer -complete=customlist,s:completeBookmarks -nargs=1 OpenBookmark :call <SID>openBookmark('<args>')
3115     command! -buffer -complete=customlist,s:completeBookmarks -nargs=* ClearBookmarks call <SID>clearBookmarks('<args>')
3116     command! -buffer -complete=customlist,s:completeBookmarks -nargs=+ BookmarkToRoot call s:Bookmark.ToRoot('<args>')
3117     command! -buffer -nargs=0 ClearAllBookmarks call s:Bookmark.ClearAll() <bar> call <SID>renderView()
3118     command! -buffer -nargs=0 ReadBookmarks call s:Bookmark.CacheBookmarks(0) <bar> call <SID>renderView()
3119     command! -buffer -nargs=0 WriteBookmarks call s:Bookmark.Write()
3120 endfunction
3121
3122 " FUNCTION: s:bookmarkNode(name) {{{2
3123 " Associate the current node with the given name
3124 function! s:bookmarkNode(name)
3125     let currentNode = s:TreeFileNode.GetSelected()
3126     if currentNode != {}
3127         try
3128             call currentNode.bookmark(a:name)
3129             call s:renderView()
3130         catch /^NERDTree.IllegalBookmarkNameError/
3131             call s:echo("bookmark names must not contain spaces")
3132         endtry
3133     else
3134         call s:echo("select a node first")
3135     endif
3136 endfunction
3137 "FUNCTION: s:checkForActivate() {{{2
3138 "Checks if the click should open the current node, if so then activate() is
3139 "called (directories are automatically opened if the symbol beside them is
3140 "clicked)
3141 function! s:checkForActivate()
3142     let currentNode = s:TreeFileNode.GetSelected()
3143     if currentNode != {}
3144         let startToCur = strpart(getline(line(".")), 0, col("."))
3145         let char = strpart(startToCur, strlen(startToCur)-1, 1)
3146
3147         "if they clicked a dir, check if they clicked on the + or ~ sign
3148         "beside it
3149         if currentNode.path.isDirectory
3150             if startToCur =~ s:tree_markup_reg . '$' && char =~ '[+~]'
3151                 call s:activateNode(0)
3152                 return
3153             endif
3154         endif
3155
3156         if (g:NERDTreeMouseMode ==# 2 && currentNode.path.isDirectory) || g:NERDTreeMouseMode ==# 3
3157             if char !~ s:tree_markup_reg && startToCur !~ '\/$'
3158                 call s:activateNode(0)
3159                 return
3160             endif
3161         endif
3162     endif
3163 endfunction
3164
3165 " FUNCTION: s:chCwd() {{{2
3166 function! s:chCwd()
3167     let treenode = s:TreeFileNode.GetSelected()
3168     if treenode ==# {}
3169         call s:echo("Select a node first")
3170         return
3171     endif
3172
3173     try
3174         call treenode.path.changeToDir()
3175     catch /^NERDTree.PathChangeError/
3176         call s:echoWarning("could not change cwd")
3177     endtry
3178 endfunction
3179
3180 " FUNCTION: s:chRoot() {{{2
3181 " changes the current root to the selected one
3182 function! s:chRoot()
3183     let treenode = s:TreeFileNode.GetSelected()
3184     if treenode ==# {}
3185         call s:echo("Select a node first")
3186         return
3187     endif
3188
3189     call treenode.makeRoot()
3190     call s:renderView()
3191     call b:NERDTreeRoot.putCursorHere(0, 0)
3192 endfunction
3193
3194 " FUNCTION: s:clearBookmarks(bookmarks) {{{2
3195 function! s:clearBookmarks(bookmarks)
3196     if a:bookmarks ==# ''
3197         let currentNode = s:TreeFileNode.GetSelected()
3198         if currentNode != {}
3199             call currentNode.clearBoomarks()
3200         endif
3201     else
3202         for name in split(a:bookmarks, ' ')
3203             let bookmark = s:Bookmark.BookmarkFor(name)
3204             call bookmark.delete()
3205         endfor
3206     endif
3207     call s:renderView()
3208 endfunction
3209 " FUNCTION: s:closeChildren() {{{2
3210 " closes all childnodes of the current node
3211 function! s:closeChildren()
3212     let currentNode = s:TreeDirNode.GetSelected()
3213     if currentNode ==# {}
3214         call s:echo("Select a node first")
3215         return
3216     endif
3217
3218     call currentNode.closeChildren()
3219     call s:renderView()
3220     call currentNode.putCursorHere(0, 0)
3221 endfunction
3222 " FUNCTION: s:closeCurrentDir() {{{2
3223 " closes the parent dir of the current node
3224 function! s:closeCurrentDir()
3225     let treenode = s:TreeFileNode.GetSelected()
3226     if treenode ==# {}
3227         call s:echo("Select a node first")
3228         return
3229     endif
3230
3231     let parent = treenode.parent
3232     if parent.isRoot()
3233         call s:echo("cannot close tree root")
3234     else
3235         call treenode.parent.close()
3236         call s:renderView()
3237         call treenode.parent.putCursorHere(0, 0)
3238     endif
3239 endfunction
3240 " FUNCTION: s:closeTreeWindow() {{{2
3241 " close the tree window
3242 function! s:closeTreeWindow()
3243     if b:NERDTreeType ==# "secondary" && b:NERDTreePreviousBuf != -1
3244         exec "buffer " . b:NERDTreePreviousBuf
3245     else
3246         if winnr("$") > 1
3247             wincmd c
3248         else
3249             call s:echo("Cannot close last window")
3250         endif
3251     endif
3252 endfunction
3253 " FUNCTION: s:copyNode() {{{2
3254 function! s:copyNode()
3255     let currentNode = s:TreeFileNode.GetSelected()
3256     if currentNode ==# {}
3257         call s:echo("Put the cursor on a file node first")
3258         return
3259     endif
3260
3261     let newNodePath = input("Copy the current node\n" .
3262                           \ "==========================================================\n" .
3263                           \ "Enter the new path to copy the node to:                   \n" .
3264                           \ "", currentNode.path.str(0))
3265
3266     if newNodePath != ""
3267         "strip trailing slash
3268         let newNodePath = substitute(newNodePath, '\/$', '', '')
3269
3270         let confirmed = 1
3271         if currentNode.path.copyingWillOverwrite(newNodePath)
3272             call s:echo("\nWarning: copying may overwrite files! Continue? (yN)")
3273             let choice = nr2char(getchar())
3274             let confirmed = choice ==# 'y'
3275         endif
3276
3277         if confirmed
3278             try
3279                 let newNode = currentNode.copy(newNodePath)
3280                 call s:renderView()
3281                 call newNode.putCursorHere(0, 0)
3282             catch /^NERDTree/
3283                 call s:echoWarning("Could not copy node")
3284             endtry
3285         endif
3286     else
3287         call s:echo("Copy aborted.")
3288     endif
3289     redraw
3290 endfunction
3291
3292 " FUNCTION: s:deleteBookmark() {{{2
3293 " if the cursor is on a bookmark, prompt to delete
3294 function! s:deleteBookmark()
3295     let bookmark = s:getSelectedBookmark()
3296     if bookmark ==# {}
3297         call s:echo("Put the cursor on a bookmark")
3298         return
3299     endif
3300
3301     echo  "Are you sure you wish to delete the bookmark:\n\"" . bookmark.name . "\" (yN):"
3302
3303     if  nr2char(getchar()) ==# 'y'
3304         try
3305             call bookmark.delete()
3306             call s:renderView()
3307             redraw
3308         catch /^NERDTree/
3309             call s:echoWarning("Could not remove bookmark")
3310         endtry
3311     else
3312         call s:echo("delete aborted" )
3313     endif
3314
3315 endfunction
3316
3317 " FUNCTION: s:deleteNode() {{{2
3318 " if the current node is a file, pops up a dialog giving the user the option
3319 " to delete it
3320 function! s:deleteNode()
3321     let currentNode = s:TreeFileNode.GetSelected()
3322     if currentNode ==# {}
3323         call s:echo("Put the cursor on a file node first")
3324         return
3325     endif
3326
3327     let confirmed = 0
3328
3329     if currentNode.path.isDirectory
3330         let choice =input("Delete the current node\n" .
3331                          \ "==========================================================\n" .
3332                          \ "STOP! To delete this entire directory, type 'yes'\n" .
3333                          \ "" . currentNode.path.strForOS(0) . ": ")
3334         let confirmed = choice ==# 'yes'
3335     else
3336         echo "Delete the current node\n" .
3337            \ "==========================================================\n".
3338            \ "Are you sure you wish to delete the node:\n" .
3339            \ "" . currentNode.path.strForOS(0) . " (yN):"
3340         let choice = nr2char(getchar())
3341         let confirmed = choice ==# 'y'
3342     endif
3343
3344
3345     if confirmed
3346         try
3347             call currentNode.delete()
3348             call s:renderView()
3349
3350             "if the node is open in a buffer, ask the user if they want to
3351             "close that buffer
3352             let bufnum = bufnr(currentNode.path.str(0))
3353             if buflisted(bufnum)
3354                 let prompt = "\nNode deleted.\n\nThe file is open in buffer ". bufnum . (bufwinnr(bufnum) ==# -1 ? " (hidden)" : "") .". Delete this buffer? (yN)"
3355                 call s:promptToDelBuffer(bufnum, prompt)
3356             endif
3357
3358             redraw
3359         catch /^NERDTree/
3360             call s:echoWarning("Could not remove node")
3361         endtry
3362     else
3363         call s:echo("delete aborted" )
3364     endif
3365
3366 endfunction
3367
3368 " FUNCTION: s:displayHelp() {{{2
3369 " toggles the help display
3370 function! s:displayHelp()
3371     let b:treeShowHelp = b:treeShowHelp ? 0 : 1
3372     call s:renderView()
3373     call s:centerView()
3374 endfunction
3375
3376 " FUNCTION: s:executeNode() {{{2
3377 function! s:executeNode()
3378     let treenode = s:TreeFileNode.GetSelected()
3379     if treenode ==# {} || treenode.path.isDirectory
3380         call s:echo("Select an executable file node first" )
3381     else
3382         echo "NERDTree executor\n" .
3383            \ "==========================================================\n".
3384            \ "Complete the command to execute (add arguments etc): \n\n"
3385         let cmd = treenode.path.strForOS(1)
3386         let cmd = input(':!', cmd . ' ')
3387
3388         if cmd != ''
3389             exec ':!' . cmd
3390         else
3391             call s:echo("command aborted")
3392         endif
3393     endif
3394 endfunction
3395
3396 " FUNCTION: s:handleMiddleMouse() {{{2
3397 function! s:handleMiddleMouse()
3398     let curNode = s:TreeFileNode.GetSelected()
3399     if curNode ==# {}
3400         call s:echo("Put the cursor on a node first" )
3401         return
3402     endif
3403
3404     if curNode.path.isDirectory
3405         call s:openExplorer()
3406     else
3407         call s:openEntrySplit(0,0)
3408     endif
3409 endfunction
3410
3411
3412 " FUNCTION: s:insertNewNode() {{{2
3413 " Adds a new node to the filesystem and then into the tree
3414 function! s:insertNewNode()
3415     let curDirNode = s:TreeDirNode.GetSelected()
3416     if curDirNode ==# {}
3417         call s:echo("Put the cursor on a node first" )
3418         return
3419     endif
3420
3421     let newNodeName = input("Add a childnode\n".
3422                           \ "==========================================================\n".
3423                           \ "Enter the dir/file name to be created. Dirs end with a '/'\n" .
3424                           \ "", curDirNode.path.strForGlob() . s:os_slash)
3425
3426     if newNodeName ==# ''
3427         call s:echo("Node Creation Aborted.")
3428         return
3429     endif
3430
3431     try
3432         let newPath = s:Path.Create(newNodeName)
3433         let parentNode = b:NERDTreeRoot.findNode(newPath.getPathTrunk())
3434
3435         let newTreeNode = s:TreeFileNode.New(newPath)
3436         if parentNode.isOpen || !empty(parentNode.children)
3437             call parentNode.addChild(newTreeNode, 1)
3438             call s:renderView()
3439             call newTreeNode.putCursorHere(1, 0)
3440         endif
3441     catch /^NERDTree/
3442         call s:echoWarning("Node Not Created.")
3443     endtry
3444 endfunction
3445
3446 " FUNCTION: s:jumpToFirstChild() {{{2
3447 " wrapper for the jump to child method
3448 function! s:jumpToFirstChild()
3449     call s:jumpToChild(0)
3450 endfunction
3451
3452 " FUNCTION: s:jumpToLastChild() {{{2
3453 " wrapper for the jump to child method
3454 function! s:jumpToLastChild()
3455     call s:jumpToChild(1)
3456 endfunction
3457
3458 " FUNCTION: s:jumpToParent() {{{2
3459 " moves the cursor to the parent of the current node
3460 function! s:jumpToParent()
3461     let currentNode = s:TreeFileNode.GetSelected()
3462     if !empty(currentNode)
3463         if !empty(currentNode.parent)
3464             call currentNode.parent.putCursorHere(1, 0)
3465             call s:centerView()
3466         else
3467             call s:echo("cannot jump to parent")
3468         endif
3469     else
3470         call s:echo("put the cursor on a node first")
3471     endif
3472 endfunction
3473
3474 " FUNCTION: s:jumpToRoot() {{{2
3475 " moves the cursor to the root node
3476 function! s:jumpToRoot()
3477     call b:NERDTreeRoot.putCursorHere(1, 0)
3478     call s:centerView()
3479 endfunction
3480
3481 " FUNCTION: s:jumpToSibling() {{{2
3482 " moves the cursor to the sibling of the current node in the given direction
3483 "
3484 " Args:
3485 " forward: 1 if the cursor should move to the next sibling, 0 if it should
3486 " move back to the previous sibling
3487 function! s:jumpToSibling(forward)
3488     let currentNode = s:TreeFileNode.GetSelected()
3489     if !empty(currentNode)
3490         let sibling = currentNode.findSibling(a:forward)
3491
3492         if !empty(sibling)
3493             call sibling.putCursorHere(1, 0)
3494             call s:centerView()
3495         endif
3496     else
3497         call s:echo("put the cursor on a node first")
3498     endif
3499 endfunction
3500
3501 " FUNCTION: s:openBookmark(name) {{{2
3502 " put the cursor on the given bookmark and, if its a file, open it
3503 function! s:openBookmark(name)
3504     try
3505         let targetNode = s:Bookmark.GetNodeForName(a:name, 0)
3506         call targetNode.putCursorHere(0, 1)
3507         redraw!
3508     catch /^NERDTree.BookmarkedNodeNotFoundError/
3509         call s:echo("note - target node is not cached")
3510         let bookmark = s:Bookmark.BookmarkFor(a:name)
3511         let targetNode = s:TreeFileNode.New(bookmark.path)
3512     endtry
3513     if targetNode.path.isDirectory
3514         call targetNode.openExplorer()
3515     else
3516         call targetNode.open()
3517     endif
3518 endfunction
3519 " FUNCTION: s:openEntrySplit(vertical, forceKeepWindowOpen) {{{2
3520 "Opens the currently selected file from the explorer in a
3521 "new window
3522 "
3523 "args:
3524 "forceKeepWindowOpen - dont close the window even if NERDTreeQuitOnOpen is set
3525 function! s:openEntrySplit(vertical, forceKeepWindowOpen)
3526     let treenode = s:TreeFileNode.GetSelected()
3527     if treenode != {}
3528         if a:vertical
3529             call treenode.openVSplit()
3530         else
3531             call treenode.openSplit()
3532         endif
3533         if !a:forceKeepWindowOpen
3534             call s:closeTreeIfQuitOnOpen()
3535         endif
3536     else
3537         call s:echo("select a node first")
3538     endif
3539 endfunction
3540
3541 " FUNCTION: s:openExplorer() {{{2
3542 function! s:openExplorer()
3543     let treenode = s:TreeDirNode.GetSelected()
3544     if treenode != {}
3545         call treenode.openExplorer()
3546     else
3547         call s:echo("select a node first")
3548     endif
3549 endfunction
3550
3551 " FUNCTION: s:openInNewTab(stayCurrentTab) {{{2
3552 " Opens the selected node or bookmark in a new tab
3553 " Args:
3554 " stayCurrentTab: if 1 then vim will stay in the current tab, if 0 then vim
3555 " will go to the tab where the new file is opened
3556 function! s:openInNewTab(stayCurrentTab)
3557     let currentTab = tabpagenr()
3558
3559     let treenode = s:TreeFileNode.GetSelected()
3560     if treenode != {}
3561         if treenode.path.isDirectory
3562             tabnew
3563             call s:initNerdTree(treenode.path.strForOS(0))
3564         else
3565             exec "tabedit " . treenode.path.strForEditCmd()
3566         endif
3567     else
3568         let bookmark = s:getSelectedBookmark()
3569         if bookmark != {}
3570             if bookmark.path.isDirectory
3571                 tabnew
3572                 call s:initNerdTree(bookmark.name)
3573             else
3574                 exec "tabedit " . bookmark.path.strForEditCmd()
3575             endif
3576         endif
3577     endif
3578     if a:stayCurrentTab
3579         exec "tabnext " . currentTab
3580     endif
3581 endfunction
3582
3583 " FUNCTION: s:openNodeRecursively() {{{2
3584 function! s:openNodeRecursively()
3585     let treenode = s:TreeFileNode.GetSelected()
3586     if treenode ==# {} || treenode.path.isDirectory ==# 0
3587         call s:echo("Select a directory node first" )
3588     else
3589         call s:echo("Recursively opening node. Please wait...")
3590         call treenode.openRecursively()
3591         call s:renderView()
3592         redraw
3593         call s:echo("Recursively opening node. Please wait... DONE")
3594     endif
3595
3596 endfunction
3597
3598 "FUNCTION: s:previewNode() {{{2
3599 "Args:
3600 "   openNewWin: if 0, use the previous window, if 1 open in new split, if 2
3601 "               open in a vsplit
3602 function! s:previewNode(openNewWin)
3603     let currentBuf = bufnr("")
3604     if a:openNewWin > 0
3605         call s:openEntrySplit(a:openNewWin ==# 2,1)
3606     else
3607         call s:activateNode(1)
3608     end
3609     call s:exec(bufwinnr(currentBuf) . "wincmd w")
3610 endfunction
3611
3612 " FUNCTION: s:revealBookmark(name) {{{2
3613 " put the cursor on the node associate with the given name
3614 function! s:revealBookmark(name)
3615     try
3616         let targetNode = s:Bookmark.GetNodeForName(a:name, 0)
3617         call targetNode.putCursorHere(0, 1)
3618     catch /^NERDTree.BookmarkNotFoundError/
3619         call s:echo("Bookmark isnt cached under the current root")
3620     endtry
3621 endfunction
3622 " FUNCTION: s:refreshRoot() {{{2
3623 " Reloads the current root. All nodes below this will be lost and the root dir
3624 " will be reloaded.
3625 function! s:refreshRoot()
3626     call s:echo("Refreshing the root node. This could take a while...")
3627     call b:NERDTreeRoot.refresh()
3628     call s:renderView()
3629     redraw
3630     call s:echo("Refreshing the root node. This could take a while... DONE")
3631 endfunction
3632
3633 " FUNCTION: s:refreshCurrent() {{{2
3634 " refreshes the root for the current node
3635 function! s:refreshCurrent()
3636     let treenode = s:TreeDirNode.GetSelected()
3637     if treenode ==# {}
3638         call s:echo("Refresh failed. Select a node first")
3639         return
3640     endif
3641
3642     call s:echo("Refreshing node. This could take a while...")
3643     call treenode.refresh()
3644     call s:renderView()
3645     redraw
3646     call s:echo("Refreshing node. This could take a while... DONE")
3647 endfunction
3648 " FUNCTION: s:renameCurrent() {{{2
3649 " allows the user to rename the current node
3650 function! s:renameCurrent()
3651     let curNode = s:TreeFileNode.GetSelected()
3652     if curNode ==# {}
3653         call s:echo("Put the cursor on a node first" )
3654         return
3655     endif
3656
3657     let newNodePath = input("Rename the current node\n" .
3658                           \ "==========================================================\n" .
3659                           \ "Enter the new path for the node:                          \n" .
3660                           \ "", curNode.path.strForOS(0))
3661
3662     if newNodePath ==# ''
3663         call s:echo("Node Renaming Aborted.")
3664         return
3665     endif
3666
3667     try
3668         let bufnum = bufnr(curNode.path.str(0))
3669
3670         call curNode.rename(newNodePath)
3671         call s:renderView()
3672
3673         "if the node is open in a buffer, ask the user if they want to
3674         "close that buffer
3675         if bufnum != -1
3676             let prompt = "\nNode renamed.\n\nThe old file is open in buffer ". bufnum . (bufwinnr(bufnum) ==# -1 ? " (hidden)" : "") .". Delete this buffer? (yN)"
3677             call s:promptToDelBuffer(bufnum, prompt)
3678         endif
3679
3680         call curNode.putCursorHere(1, 0)
3681
3682         redraw
3683     catch /^NERDTree/
3684         call s:echoWarning("Node Not Renamed.")
3685     endtry
3686 endfunction
3687
3688 " FUNCTION: s:showFileSystemMenu() {{{2
3689 function! s:showFileSystemMenu()
3690     let curNode = s:TreeFileNode.GetSelected()
3691     if curNode ==# {}
3692         call s:echo("Put the cursor on a node first" )
3693         return
3694     endif
3695
3696
3697     let prompt = "NERDTree Filesystem Menu\n" .
3698        \ "==========================================================\n".
3699        \ "Select the desired operation:                             \n" .
3700        \ " (a)dd a childnode\n".
3701        \ " (m)ove the current node\n".
3702        \ " (d)elete the current node\n"
3703     if s:Path.CopyingSupported()
3704         let prompt = prompt . " (c)opy the current node\n\n"
3705     else
3706         let prompt = prompt . " \n"
3707     endif
3708
3709     echo prompt
3710
3711     let choice = nr2char(getchar())
3712
3713     if choice ==? "a"
3714         call s:insertNewNode()
3715     elseif choice ==? "m"
3716         call s:renameCurrent()
3717     elseif choice ==? "d"
3718         call s:deleteNode()
3719     elseif choice ==? "c" && s:Path.CopyingSupported()
3720         call s:copyNode()
3721     endif
3722 endfunction
3723
3724 " FUNCTION: s:toggleIgnoreFilter() {{{2
3725 " toggles the use of the NERDTreeIgnore option
3726 function! s:toggleIgnoreFilter()
3727     let b:NERDTreeIgnoreEnabled = !b:NERDTreeIgnoreEnabled
3728     call s:renderViewSavingPosition()
3729     call s:centerView()
3730 endfunction
3731
3732 " FUNCTION: s:toggleShowBookmarks() {{{2
3733 " toggles the display of bookmarks
3734 function! s:toggleShowBookmarks()
3735     let b:NERDTreeShowBookmarks = !b:NERDTreeShowBookmarks
3736     if b:NERDTreeShowBookmarks
3737         call s:renderView()
3738         call s:putCursorOnBookmarkTable()
3739     else
3740         call s:renderViewSavingPosition()
3741     endif
3742     call s:centerView()
3743 endfunction
3744 " FUNCTION: s:toggleShowFiles() {{{2
3745 " toggles the display of hidden files
3746 function! s:toggleShowFiles()
3747     let b:NERDTreeShowFiles = !b:NERDTreeShowFiles
3748     call s:renderViewSavingPosition()
3749     call s:centerView()
3750 endfunction
3751
3752 " FUNCTION: s:toggleShowHidden() {{{2
3753 " toggles the display of hidden files
3754 function! s:toggleShowHidden()
3755     let b:NERDTreeShowHidden = !b:NERDTreeShowHidden
3756     call s:renderViewSavingPosition()
3757     call s:centerView()
3758 endfunction
3759
3760 "FUNCTION: s:upDir(keepState) {{{2
3761 "moves the tree up a level
3762 "
3763 "Args:
3764 "keepState: 1 if the current root should be left open when the tree is
3765 "re-rendered
3766 function! s:upDir(keepState)
3767     let cwd = b:NERDTreeRoot.path.str(0)
3768     if cwd ==# "/" || cwd =~ '^[^/]..$'
3769         call s:echo("already at top dir")
3770     else
3771         if !a:keepState
3772             call b:NERDTreeRoot.close()
3773         endif
3774
3775         let oldRoot = b:NERDTreeRoot
3776
3777         if empty(b:NERDTreeRoot.parent)
3778             let path = b:NERDTreeRoot.path.getPathTrunk()
3779             let newRoot = s:TreeDirNode.New(path)
3780             call newRoot.open()
3781             call newRoot.transplantChild(b:NERDTreeRoot)
3782             let b:NERDTreeRoot = newRoot
3783         else
3784             let b:NERDTreeRoot = b:NERDTreeRoot.parent
3785         endif
3786
3787         if g:NERDTreeChDirMode ==# 2
3788             exec 'cd ' . b:NERDTreeRoot.path.strForCd()
3789         endif
3790
3791         call s:renderView()
3792         call oldRoot.putCursorHere(0, 0)
3793     endif
3794 endfunction
3795
3796
3797 "reset &cpo back to users setting
3798 let &cpo = s:old_cpo
3799
3800 " vim: set sw=4 sts=4 et fdm=marker: