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