blob: 6411b1d2643bec23e73c1bdc33bc4369a2f83dba [file] [log] [blame]
chatsiri rattana9aa80722012-02-12 14:24:58 +07001" ============================================================================
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.
11"
12" ============================================================================
13let s:NERD_tree_version = '4.1.0'
14
15" SECTION: Script init stuff {{{1
16"============================================================
17if exists("loaded_nerd_tree")
18 finish
19endif
20if v:version < 700
21 echoerr "NERDTree: this plugin requires vim >= 7. DOWNLOAD IT! You'll thank me later!"
22 finish
23endif
24let loaded_nerd_tree = 1
25
26"for line continuation - i.e dont want C in &cpo
27let s:old_cpo = &cpo
28set cpo&vim
29
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
33"
34"Args:
35"var: the name of the var to be initialised
36"value: the value to initialise var to
37"
38"Returns:
39"1 if the var is set, 0 otherwise
40function! s:initVariable(var, value)
41 if !exists(a:var)
42 exec 'let ' . a:var . ' = ' . "'" . a:value . "'"
43 return 1
44 endif
45 return 0
46endfunction
47
48"SECTION: Init variable calls and other random constants {{{2
49call s:initVariable("g:NERDChristmasTree", 1)
50call s:initVariable("g:NERDTreeAutoCenter", 1)
51call s:initVariable("g:NERDTreeAutoCenterThreshold", 3)
52call s:initVariable("g:NERDTreeCaseSensitiveSort", 0)
53call s:initVariable("g:NERDTreeChDirMode", 0)
54if !exists("g:NERDTreeIgnore")
55 let g:NERDTreeIgnore = ['\~$']
56endif
57call s:initVariable("g:NERDTreeBookmarksFile", expand('$HOME') . '/.NERDTreeBookmarks')
58call s:initVariable("g:NERDTreeHighlightCursorline", 1)
59call s:initVariable("g:NERDTreeHijackNetrw", 1)
60call s:initVariable("g:NERDTreeMouseMode", 1)
61call s:initVariable("g:NERDTreeNotificationThreshold", 100)
62call s:initVariable("g:NERDTreeQuitOnOpen", 0)
63call s:initVariable("g:NERDTreeShowBookmarks", 0)
64call s:initVariable("g:NERDTreeShowFiles", 1)
65call s:initVariable("g:NERDTreeShowHidden", 0)
66call s:initVariable("g:NERDTreeShowLineNumbers", 0)
67call s:initVariable("g:NERDTreeSortDirs", 1)
68
69if !exists("g:NERDTreeSortOrder")
70 let g:NERDTreeSortOrder = ['\/$', '*', '\.swp$', '\.bak$', '\~$']
71else
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
76endif
77
78"we need to use this number many times for sorting... so we calculate it only
79"once here
80let s:NERDTreeSortStarIndex = index(g:NERDTreeSortOrder, '*')
81
82if !exists('g:NERDTreeStatusline')
83
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():''}"
88
89endif
90call s:initVariable("g:NERDTreeWinPos", "left")
91call s:initVariable("g:NERDTreeWinSize", 31)
92
93let s:running_windows = has("win16") || has("win32") || has("win64")
94
95"init the shell commands that will be used to copy nodes, and remove dir trees
96"
97"Note: the space after the command is important
98if s:running_windows
99 call s:initVariable("g:NERDTreeRemoveDirCmd", 'rmdir /s /q ')
100else
101 call s:initVariable("g:NERDTreeRemoveDirCmd", 'rm -rf ')
102 call s:initVariable("g:NERDTreeCopyCmd", 'cp -r ')
103endif
104
105
106"SECTION: Init variable calls for key mappings {{{2
107call s:initVariable("g:NERDTreeMapActivateNode", "o")
108call s:initVariable("g:NERDTreeMapChangeRoot", "C")
109call s:initVariable("g:NERDTreeMapChdir", "cd")
110call s:initVariable("g:NERDTreeMapCloseChildren", "X")
111call s:initVariable("g:NERDTreeMapCloseDir", "x")
112call s:initVariable("g:NERDTreeMapDeleteBookmark", "D")
113call s:initVariable("g:NERDTreeMapMenu", "m")
114call s:initVariable("g:NERDTreeMapHelp", "?")
115call s:initVariable("g:NERDTreeMapJumpFirstChild", "K")
116call s:initVariable("g:NERDTreeMapJumpLastChild", "J")
117call s:initVariable("g:NERDTreeMapJumpNextSibling", "<C-j>")
118call s:initVariable("g:NERDTreeMapJumpParent", "p")
119call s:initVariable("g:NERDTreeMapJumpPrevSibling", "<C-k>")
120call s:initVariable("g:NERDTreeMapJumpRoot", "P")
121call s:initVariable("g:NERDTreeMapOpenExpl", "e")
122call s:initVariable("g:NERDTreeMapOpenInTab", "t")
123call s:initVariable("g:NERDTreeMapOpenInTabSilent", "T")
124call s:initVariable("g:NERDTreeMapOpenRecursively", "O")
125call s:initVariable("g:NERDTreeMapOpenSplit", "i")
126call s:initVariable("g:NERDTreeMapOpenVSplit", "s")
127call s:initVariable("g:NERDTreeMapPreview", "g" . NERDTreeMapActivateNode)
128call s:initVariable("g:NERDTreeMapPreviewSplit", "g" . NERDTreeMapOpenSplit)
129call s:initVariable("g:NERDTreeMapPreviewVSplit", "g" . NERDTreeMapOpenVSplit)
130call s:initVariable("g:NERDTreeMapQuit", "q")
131call s:initVariable("g:NERDTreeMapRefresh", "r")
132call s:initVariable("g:NERDTreeMapRefreshRoot", "R")
133call s:initVariable("g:NERDTreeMapToggleBookmarks", "B")
134call s:initVariable("g:NERDTreeMapToggleFiles", "F")
135call s:initVariable("g:NERDTreeMapToggleFilters", "f")
136call s:initVariable("g:NERDTreeMapToggleHidden", "I")
137call s:initVariable("g:NERDTreeMapToggleZoom", "A")
138call s:initVariable("g:NERDTreeMapUpdir", "u")
139call s:initVariable("g:NERDTreeMapUpdirKeepOpen", "U")
140
141"SECTION: Script level variable declaration{{{2
142if s:running_windows
143 let s:escape_chars = " `\|\"#%&,?()\*^<>"
144else
145 let s:escape_chars = " \\`\|\"#%&,?()\*^<>"
146endif
147let s:NERDTreeBufName = 'NERD_tree_'
148
149let s:tree_wid = 2
150let s:tree_markup_reg = '^[ `|]*[\-+~]'
151let s:tree_up_dir_line = '.. (up a dir)'
152
153"the number to add to the nerd tree buffer name to make the buf name unique
154let s:next_buffer_number = 1
155
156" SECTION: Commands {{{1
157"============================================================
158"init the command that users start the nerd tree with
159command! -n=? -complete=dir -bar NERDTree :call s:initNerdTree('<args>')
160command! -n=? -complete=dir -bar NERDTreeToggle :call s:toggle('<args>')
161command! -n=0 -bar NERDTreeClose :call s:closeTreeIfOpen()
162command! -n=1 -complete=customlist,s:completeBookmarks -bar NERDTreeFromBookmark call s:initNerdTree('<args>')
163command! -n=0 -bar NERDTreeMirror call s:initNerdTreeMirror()
164command! -n=0 -bar NERDTreeFind call s:findAndRevealPath()
165" SECTION: Auto commands {{{1
166"============================================================
167augroup 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)
172
173 "load all nerdtree plugins after vim starts
174 autocmd VimEnter * runtime! nerdtree_plugin/**/*.vim
175augroup END
176
177if g:NERDTreeHijackNetrw
178 augroup NERDTreeHijackNetrw
179 autocmd VimEnter * silent! autocmd! FileExplorer
180 au BufEnter,VimEnter * call s:checkForBrowse(expand("<amatch>"))
181 augroup END
182endif
183
184"SECTION: Classes {{{1
185"============================================================
186"CLASS: Bookmark {{{2
187"============================================================
188let s:Bookmark = {}
189" FUNCTION: Bookmark.activate() {{{3
190function! 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
199endfunction
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
203function! 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()
212endfunction
213" Function: Bookmark.Bookmarks() {{{3
214" Class method to get all bookmarks. Lazily initializes the bookmarks global
215" variable
216function! s:Bookmark.Bookmarks()
217 if !exists("g:NERDTreeBookmarks")
218 let g:NERDTreeBookmarks = []
219 endif
220 return g:NERDTreeBookmarks
221endfunction
222" Function: Bookmark.BookmarkExistsFor(name) {{{3
223" class method that returns 1 if a bookmark with the given name is found, 0
224" otherwise
225function! 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
232endfunction
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
236function! 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 .'"'
243endfunction
244" Function: Bookmark.BookmarkNames() {{{3
245" Class method to return an array of all bookmark names
246function! s:Bookmark.BookmarkNames()
247 let names = []
248 for i in s:Bookmark.Bookmarks()
249 call add(names, i.name)
250 endfor
251 return names
252endfunction
253" FUNCTION: Bookmark.CacheBookmarks(silent) {{{3
254" Class method to read all bookmarks from the bookmarks file intialize
255" bookmark objects for each one.
256"
257" Args:
258" silent - dont echo an error msg if invalid bookmarks are found
259function! 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
266
267 "ignore blank lines
268 if i != ''
269
270 let name = substitute(i, '^\(.\{-}\) .*$', '\1', '')
271 let path = substitute(i, '^.\{-} \(.*\)$', '\1', '')
272
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
290endfunction
291" FUNCTION: Bookmark.compareTo(otherbookmark) {{{3
292" Compare these two bookmarks for sorting purposes
293function! s:Bookmark.compareTo(otherbookmark)
294 return a:otherbookmark.name < self.name
295endfunction
296" FUNCTION: Bookmark.ClearAll() {{{3
297" Class method to delete all bookmarks.
298function! s:Bookmark.ClearAll()
299 for i in s:Bookmark.Bookmarks()
300 call i.delete()
301 endfor
302 call s:Bookmark.Write()
303endfunction
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
307function! 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()
318endfunction
319" FUNCTION: Bookmark.getNode(searchFromAbsoluteRoot) {{{3
320" Gets the treenode for this bookmark
321"
322" Args:
323" searchFromAbsoluteRoot: specifies whether we should search from the current
324" tree root, or the highest cached node
325function! 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
332endfunction
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.
336function! s:Bookmark.GetNodeForName(name, searchFromAbsoluteRoot)
337 let bookmark = s:Bookmark.BookmarkFor(a:name)
338 return bookmark.getNode(a:searchFromAbsoluteRoot)
339endfunction
340" FUNCTION: Bookmark.GetSelected() {{{3
341" returns the Bookmark the cursor is over, or {}
342function! 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 {}
353endfunction
354
355" Function: Bookmark.InvalidBookmarks() {{{3
356" Class method to get all invalid bookmark strings read from the bookmarks
357" file
358function! s:Bookmark.InvalidBookmarks()
359 if !exists("g:NERDTreeInvalidBookmarks")
360 let g:NERDTreeInvalidBookmarks = []
361 endif
362 return g:NERDTreeInvalidBookmarks
363endfunction
364" FUNCTION: Bookmark.mustExist() {{{3
365function! 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
371endfunction
372" FUNCTION: Bookmark.New(name, path) {{{3
373" Create a new bookmark object with the given name and path object
374function! s:Bookmark.New(name, path)
375 if a:name =~ ' '
376 throw "NERDTree.IllegalBookmarkNameError: illegal name:" . a:name
377 endif
378
379 let newBookmark = copy(self)
380 let newBookmark.name = a:name
381 let newBookmark.path = a:path
382 return newBookmark
383endfunction
384" FUNCTION: Bookmark.openInNewTab(options) {{{3
385" Create a new bookmark object with the given name and path object
386function! 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
394
395 if has_key(a:options, 'stayInCurrentTab')
396 exec "tabnext " . currentTab
397 endif
398endfunction
399" Function: Bookmark.setPath(path) {{{3
400" makes this bookmark point to the given path
401function! s:Bookmark.setPath(path)
402 let self.path = a:path
403endfunction
404" Function: Bookmark.Sort() {{{3
405" Class method that sorts all bookmarks
406function! s:Bookmark.Sort()
407 let CompareFunc = function("s:compareBookmarks")
408 call sort(s:Bookmark.Bookmarks(), CompareFunc)
409endfunction
410" Function: Bookmark.str() {{{3
411" Get the string that should be rendered in the view for this bookmark
412function! s:Bookmark.str()
413 let pathStrMaxLen = winwidth(s:getTreeWinNum()) - 4 - len(self.name)
414 if &nu
415 let pathStrMaxLen = pathStrMaxLen - &numberwidth
416 endif
417
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
423endfunction
424" FUNCTION: Bookmark.toRoot() {{{3
425" Make the node for this bookmark the new tree root
426function! 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
437endfunction
438" FUNCTION: Bookmark.ToRoot(name) {{{3
439" Make the node for this bookmark the new tree root
440function! s:Bookmark.ToRoot(name)
441 let bookmark = s:Bookmark.BookmarkFor(a:name)
442 call bookmark.toRoot()
443endfunction
444
445
446"FUNCTION: Bookmark.validate() {{{3
447function! 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
456endfunction
457
458" Function: Bookmark.Write() {{{3
459" Class method to write all bookmarks to the bookmarks file
460function! s:Bookmark.Write()
461 let bookmarkStrings = []
462 for i in s:Bookmark.Bookmarks()
463 call add(bookmarkStrings, i.name . ' ' . i.path.str())
464 endfor
465
466 "add a blank line before the invalid ones
467 call add(bookmarkStrings, "")
468
469 for j in s:Bookmark.InvalidBookmarks()
470 call add(bookmarkStrings, j)
471 endfor
472 call writefile(bookmarkStrings, g:NERDTreeBookmarksFile)
473endfunction
474"CLASS: KeyMap {{{2
475"============================================================
476let s:KeyMap = {}
477"FUNCTION: KeyMap.All() {{{3
478function! s:KeyMap.All()
479 if !exists("s:keyMaps")
480 let s:keyMaps = []
481 endif
482 return s:keyMaps
483endfunction
484
485"FUNCTION: KeyMap.BindAll() {{{3
486function! s:KeyMap.BindAll()
487 for i in s:KeyMap.All()
488 call i.bind()
489 endfor
490endfunction
491
492"FUNCTION: KeyMap.bind() {{{3
493function! s:KeyMap.bind()
494 exec "nnoremap <silent> <buffer> ". self.key ." :call ". self.callback ."()<cr>"
495endfunction
496
497"FUNCTION: KeyMap.Create(options) {{{3
498function! 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)
504endfunction
505"CLASS: MenuController {{{2
506"============================================================
507let s:MenuController = {}
508"FUNCTION: MenuController.New(menuItems) {{{3
509"create a new menu controller that operates on the given menu items
510function! 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
518endfunction
519
520"FUNCTION: MenuController.showMenu() {{{3
521"start the main loop of the menu and get the user to choose/execute a menu
522"item
523function! s:MenuController.showMenu()
524 call self._saveOptions()
525
526 try
527 let self.selection = 0
528
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
539
540 if self.selection != -1
541 let m = self._current()
542 call m.execute()
543 endif
544endfunction
545
546"FUNCTION: MenuController._echoPrompt() {{{3
547function! s:MenuController._echoPrompt()
548 echo "NERDTree Menu. Use j/k/enter and the shortcuts indicated"
549 echo "=========================================================="
550
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
558endfunction
559
560"FUNCTION: MenuController._current(key) {{{3
561"get the MenuItem that is curently selected
562function! s:MenuController._current()
563 return self.menuItems[self.selection]
564endfunction
565
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
569function! 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
588
589 return 0
590endfunction
591
592"FUNCTION: MenuController._allIndexesFor(shortcut) {{{3
593"get indexes to all menu items with the given shortcut
594function! s:MenuController._allIndexesFor(shortcut)
595 let toReturn = []
596
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
602
603 return toReturn
604endfunction
605
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
609function! 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
615
616 for i in range(0, self.selection)
617 if self.menuItems[i].shortcut == a:shortcut
618 return i
619 endif
620 endfor
621
622 return -1
623endfunction
624
625"FUNCTION: MenuController._setCmdheight() {{{3
626"sets &cmdheight to whatever is needed to display the menu
627function! s:MenuController._setCmdheight()
628 let &cmdheight = len(self.menuItems) + 3
629endfunction
630
631"FUNCTION: MenuController._saveOptions() {{{3
632"set any vim options that are required to make the menu work (saving their old
633"values)
634function! s:MenuController._saveOptions()
635 let self._oldLazyredraw = &lazyredraw
636 let self._oldCmdheight = &cmdheight
637 set nolazyredraw
638 call self._setCmdheight()
639endfunction
640
641"FUNCTION: MenuController._restoreOptions() {{{3
642"restore the options we saved in _saveOptions()
643function! s:MenuController._restoreOptions()
644 let &cmdheight = self._oldCmdheight
645 let &lazyredraw = self._oldLazyredraw
646endfunction
647
648"FUNCTION: MenuController._cursorDown() {{{3
649"move the cursor to the next menu item, skipping separators
650function! 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
658
659 if !self._current().isSeparator()
660 let done = 1
661 endif
662 endwhile
663endfunction
664
665"FUNCTION: MenuController._cursorUp() {{{3
666"move the cursor to the previous menu item, skipping separators
667function! 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
675
676 if !self._current().isSeparator()
677 let done = 1
678 endif
679 endwhile
680endfunction
681
682"CLASS: MenuItem {{{2
683"============================================================
684let s:MenuItem = {}
685"FUNCTION: MenuItem.All() {{{3
686"get all top level menu items
687function! s:MenuItem.All()
688 if !exists("s:menuItems")
689 let s:menuItems = []
690 endif
691 return s:menuItems
692endfunction
693
694"FUNCTION: MenuItem.AllEnabled() {{{3
695"get all top level menu items that are currently enabled
696function! 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
704endfunction
705
706"FUNCTION: MenuItem.Create(options) {{{3
707"make a new menu item and add it to the global list
708function! s:MenuItem.Create(options)
709 let newMenuItem = copy(self)
710
711 let newMenuItem.text = a:options['text']
712 let newMenuItem.shortcut = a:options['shortcut']
713 let newMenuItem.children = []
714
715 let newMenuItem.isActiveCallback = -1
716 if has_key(a:options, 'isActiveCallback')
717 let newMenuItem.isActiveCallback = a:options['isActiveCallback']
718 endif
719
720 let newMenuItem.callback = -1
721 if has_key(a:options, 'callback')
722 let newMenuItem.callback = a:options['callback']
723 endif
724
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
730
731 return newMenuItem
732endfunction
733
734"FUNCTION: MenuItem.CreateSeparator(options) {{{3
735"make a new separator menu item and add it to the global list
736function! s:MenuItem.CreateSeparator(options)
737 let standard_options = { 'text': '--------------------',
738 \ 'shortcut': -1,
739 \ 'callback': -1 }
740 let options = extend(a:options, standard_options, "force")
741
742 return s:MenuItem.Create(options)
743endfunction
744
745"FUNCTION: MenuItem.CreateSubmenu(options) {{{3
746"make a new submenu and add it to global list
747function! s:MenuItem.CreateSubmenu(options)
748 let standard_options = { 'callback': -1 }
749 let options = extend(a:options, standard_options, "force")
750
751 return s:MenuItem.Create(options)
752endfunction
753
754"FUNCTION: MenuItem.enabled() {{{3
755"return 1 if this menu item should be displayed
756"
757"delegates off to the isActiveCallback, and defaults to 1 if no callback was
758"specified
759function! s:MenuItem.enabled()
760 if self.isActiveCallback != -1
761 return {self.isActiveCallback}()
762 endif
763 return 1
764endfunction
765
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
770function! 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
779endfunction
780
781"FUNCTION: MenuItem.isSeparator() {{{3
782"return 1 if this menuitem is a separator
783function! s:MenuItem.isSeparator()
784 return self.callback == -1 && self.children == []
785endfunction
786
787"FUNCTION: MenuItem.isSubmenu() {{{3
788"return 1 if this menuitem is a submenu
789function! s:MenuItem.isSubmenu()
790 return self.callback == -1 && !empty(self.children)
791endfunction
792
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"============================================================
798let s:TreeFileNode = {}
799"FUNCTION: TreeFileNode.activate(forceKeepWinOpen) {{{3
800function! s:TreeFileNode.activate(forceKeepWinOpen)
801 call self.open()
802 if !a:forceKeepWinOpen
803 call s:closeTreeIfQuitOnOpen()
804 end
805endfunction
806"FUNCTION: TreeFileNode.bookmark(name) {{{3
807"bookmark this node with a:name
808function! 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
814
815 call s:Bookmark.AddBookmark(a:name, self.path)
816 call self.path.cacheDisplayString()
817 call s:Bookmark.Write()
818endfunction
819"FUNCTION: TreeFileNode.cacheParent() {{{3
820"initializes self.parent if it isnt already
821function! 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
829endfunction
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..
833"
834"A class level method that compares two nodes
835"
836"Args:
837"n1, n2: the 2 nodes to compare
838function! s:compareNodes(n1, n2)
839 return a:n1.path.compareTo(a:n2.path)
840endfunction
841
842"FUNCTION: TreeFileNode.clearBoomarks() {{{3
843function! 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()
850endfunction
851"FUNCTION: TreeFileNode.copy(dest) {{{3
852function! 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)
860endfunction
861
862"FUNCTION: TreeFileNode.delete {{{3
863"Removes this node from the tree and calls the Delete method for its path obj
864function! s:TreeFileNode.delete()
865 call self.path.delete()
866 call self.parent.removeChild(self)
867endfunction
868
869"FUNCTION: TreeFileNode.displayString() {{{3
870"
871"Returns a string that specifies how the node should be represented as a
872"string
873"
874"Return:
875"a string that can be used in the view to represent this node
876function! s:TreeFileNode.displayString()
877 return self.path.displayString()
878endfunction
879
880"FUNCTION: TreeFileNode.equals(treenode) {{{3
881"
882"Compares this treenode to the input treenode and returns 1 if they are the
883"same node.
884"
885"Use this method instead of == because sometimes when the treenodes contain
886"many children, vim seg faults when doing ==
887"
888"Args:
889"treenode: the other treenode to compare to
890function! s:TreeFileNode.equals(treenode)
891 return self.path.str() ==# a:treenode.path.str()
892endfunction
893
894"FUNCTION: TreeFileNode.findNode(path) {{{3
895"Returns self if this node.path.Equals the given path.
896"Returns {} if not equal.
897"
898"Args:
899"path: the path object to compare against
900function! s:TreeFileNode.findNode(path)
901 if a:path.equals(self.path)
902 return self
903 endif
904 return {}
905endfunction
906"FUNCTION: TreeFileNode.findOpenDirSiblingWithVisibleChildren(direction) {{{3
907"
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.
910"
911"Args:
912"direction: 0 if you want to find the previous sibling, 1 for the next sibling
913"
914"Return:
915"a treenode object or {} if no appropriate sibling could be found
916function! 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)
920
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
928
929 return {}
930endfunction
931"FUNCTION: TreeFileNode.findSibling(direction) {{{3
932"
933"Finds the next sibling for this node in the indicated direction
934"
935"Args:
936"direction: 0 if you want to find the previous sibling, 1 for the next sibling
937"
938"Return:
939"a treenode object or {} if no sibling could be found
940function! s:TreeFileNode.findSibling(direction)
941 "if we have no parent then we can have no siblings
942 if self.parent != {}
943
944 "get the index of this node in its parents children
945 let siblingIndx = self.parent.getChildIndex(self.path)
946
947 if siblingIndx != -1
948 "move a long to the next potential sibling node
949 let siblingIndx = a:direction ==# 1 ? siblingIndx+1 : siblingIndx-1
950
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
954
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
960
961 "go to next node
962 let siblingIndx = a:direction ==# 1 ? siblingIndx+1 : siblingIndx-1
963 endwhile
964 endif
965 endif
966
967 return {}
968endfunction
969
970"FUNCTION: TreeFileNode.getLineNum(){{{3
971"returns the line number this node is rendered on, or -1 if it isnt rendered
972function! 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
977
978 let totalLines = line("$")
979
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
984
985 let fullpath = self.path.str({'format': 'UI'})
986
987
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
995
996 let curLine = getline(lnum)
997
998 let indent = s:indentLevelFor(curLine)
999 if indent ==# curPathComponent
1000 let curLine = s:stripMarkupFromLine(curLine, 1)
1001
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
1008
1009 if fullpath ==# curPath
1010 return lnum
1011 endif
1012 endif
1013 endif
1014 endif
1015 endwhile
1016 return -1
1017endfunction
1018
1019"FUNCTION: TreeFileNode.GetRootForTab(){{{3
1020"get the root node for this tab
1021function! s:TreeFileNode.GetRootForTab()
1022 if s:treeExistsForTab()
1023 return getbufvar(t:NERDTreeBufName, 'NERDTreeRoot')
1024 end
1025 return {}
1026endfunction
1027"FUNCTION: TreeFileNode.GetRootLineNum(){{{3
1028"gets the line number of the root node
1029function! s:TreeFileNode.GetRootLineNum()
1030 let rootLine = 1
1031 while getline(rootLine) !~ '^\(/\|<\)'
1032 let rootLine = rootLine + 1
1033 endwhile
1034 return rootLine
1035endfunction
1036
1037"FUNCTION: TreeFileNode.GetSelected() {{{3
1038"gets the treenode that the cursor is currently over
1039function! 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
1049endfunction
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)
1053function! s:TreeFileNode.isVisible()
1054 return !self.path.ignore()
1055endfunction
1056"FUNCTION: TreeFileNode.isRoot() {{{3
1057"returns 1 if this node is b:NERDTreeRoot
1058function! s:TreeFileNode.isRoot()
1059 if !s:treeExistsForBuf()
1060 throw "NERDTree.NoTreeError: No tree exists for the current buffer"
1061 endif
1062
1063 return self.equals(b:NERDTreeRoot)
1064endfunction
1065
1066"FUNCTION: TreeFileNode.makeRoot() {{{3
1067"Make this node the root of the tree
1068function! 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
1075
1076 call b:NERDTreeRoot.open()
1077
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
1082endfunction
1083"FUNCTION: TreeFileNode.New(path) {{{3
1084"Returns a new TreeNode object with the given path and parent
1085"
1086"Args:
1087"path: a path object representing the full filesystem path to the file/dir that the node represents
1088function! 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
1097endfunction
1098
1099"FUNCTION: TreeFileNode.open() {{{3
1100"Open the file represented by the given node in the current window, splitting
1101"the window if needed
1102"
1103"ARGS:
1104"treenode: file node to open
1105function! s:TreeFileNode.open()
1106 if b:NERDTreeType ==# "secondary"
1107 exec 'edit ' . self.path.str({'format': 'Edit'})
1108 return
1109 endif
1110
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")
1115
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
1135endfunction
1136"FUNCTION: TreeFileNode.openSplit() {{{3
1137"Open this node in a new window
1138function! s:TreeFileNode.openSplit()
1139
1140 if b:NERDTreeType ==# "secondary"
1141 exec "split " . self.path.str({'format': 'Edit'})
1142 return
1143 endif
1144
1145 " Save the user's settings for splitbelow and splitright
1146 let savesplitbelow=&splitbelow
1147 let savesplitright=&splitright
1148
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
1162
1163 " Attempt to go to adjacent window
1164 call s:exec(back)
1165
1166 let onlyOneWin = (winnr("$") ==# 1)
1167
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
1177
1178 let splitMode = onlyOneWin ? "vertical" : ""
1179
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
1189
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
1197
1198 " Restore splitmode settings
1199 let &splitbelow=savesplitbelow
1200 let &splitright=savesplitright
1201endfunction
1202"FUNCTION: TreeFileNode.openVSplit() {{{3
1203"Open this node in a new vertical window
1204function! s:TreeFileNode.openVSplit()
1205 if b:NERDTreeType ==# "secondary"
1206 exec "vnew " . self.path.str({'format': 'Edit'})
1207 return
1208 endif
1209
1210 let winwidth = winwidth(".")
1211 if winnr("$")==#1
1212 let winwidth = g:NERDTreeWinSize
1213 endif
1214
1215 call s:exec("wincmd p")
1216 exec "vnew " . self.path.str({'format': 'Edit'})
1217
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')
1222endfunction
1223"FUNCTION: TreeFileNode.openInNewTab(options) {{{3
1224function! s:TreeFileNode.openInNewTab(options)
1225 let currentTab = tabpagenr()
1226
1227 if !has_key(a:options, 'keepTreeOpen')
1228 call s:closeTreeIfQuitOnOpen()
1229 endif
1230
1231 exec "tabedit " . self.path.str({'format': 'Edit'})
1232
1233 if has_key(a:options, 'stayInCurrentTab') && a:options['stayInCurrentTab']
1234 exec "tabnext " . currentTab
1235 endif
1236
1237endfunction
1238"FUNCTION: TreeFileNode.putCursorHere(isJump, recurseUpward){{{3
1239"Places the cursor on the line number this node is rendered on
1240"
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
1245function! 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
1263endfunction
1264
1265"FUNCTION: TreeFileNode.refresh() {{{3
1266function! s:TreeFileNode.refresh()
1267 call self.path.refresh()
1268endfunction
1269"FUNCTION: TreeFileNode.rename() {{{3
1270"Calls the rename method for this nodes path obj
1271function! s:TreeFileNode.rename(newName)
1272 let newName = substitute(a:newName, '\(\\\|\/\)$', '', '')
1273 call self.path.rename(newName)
1274 call self.parent.removeChild(self)
1275
1276 let parentPath = self.path.getParent()
1277 let newParent = b:NERDTreeRoot.findNode(parentPath)
1278
1279 if newParent != {}
1280 call newParent.createChild(self.path, 1)
1281 call newParent.refresh()
1282 endif
1283endfunction
1284"FUNCTION: TreeFileNode.renderToString {{{3
1285"returns a string representation for this tree to be rendered in the view
1286function! s:TreeFileNode.renderToString()
1287 return self._renderToString(0, 0, [], self.getChildCount() ==# 1)
1288endfunction
1289
1290
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
1298function! s:TreeFileNode._renderToString(depth, drawText, vertMap, isLastChild)
1299 let output = ""
1300 if a:drawText ==# 1
1301
1302 let treeParts = ''
1303
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
1314
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
1322
1323
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()
1336
1337 let output = output . line . "\n"
1338 endif
1339
1340 "if the node is an open dir, draw its children
1341 if self.path.isDirectory ==# 1 && self.isOpen ==# 1
1342
1343 let childNodesToDraw = self.getVisibleChildren()
1344 if len(childNodesToDraw) > 0
1345
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
1353
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
1358
1359 return output
1360endfunction
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"============================================================
1366let s:TreeDirNode = copy(s:TreeFileNode)
1367"FUNCTION: TreeDirNode.AbsoluteTreeRoot(){{{3
1368"class method that returns the highest cached ancestor of the current root
1369function! s:TreeDirNode.AbsoluteTreeRoot()
1370 let currentNode = b:NERDTreeRoot
1371 while currentNode.parent != {}
1372 let currentNode = currentNode.parent
1373 endwhile
1374 return currentNode
1375endfunction
1376"FUNCTION: TreeDirNode.activate(forceKeepWinOpen) {{{3
1377unlet s:TreeDirNode.activate
1378function! s:TreeDirNode.activate(forceKeepWinOpen)
1379 call self.toggleOpen()
1380 call s:renderView()
1381 call self.putCursorHere(0, 0)
1382endfunction
1383"FUNCTION: TreeDirNode.addChild(treenode, inOrder) {{{3
1384"Adds the given treenode to the list of children for this node
1385"
1386"Args:
1387"-treenode: the node to add
1388"-inOrder: 1 if the new node should be inserted in sorted order
1389function! s:TreeDirNode.addChild(treenode, inOrder)
1390 call add(self.children, a:treenode)
1391 let a:treenode.parent = self
1392
1393 if a:inOrder
1394 call self.sortChildren()
1395 endif
1396endfunction
1397
1398"FUNCTION: TreeDirNode.close() {{{3
1399"Closes this directory
1400function! s:TreeDirNode.close()
1401 let self.isOpen = 0
1402endfunction
1403
1404"FUNCTION: TreeDirNode.closeChildren() {{{3
1405"Closes all the child dir nodes of this node
1406function! 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
1413endfunction
1414
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.
1418"
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
1422"
1423"Returns:
1424"the newly created node
1425function! s:TreeDirNode.createChild(path, inOrder)
1426 let newTreeNode = s:TreeFileNode.New(a:path)
1427 call self.addChild(newTreeNode, a:inOrder)
1428 return newTreeNode
1429endfunction
1430
1431"FUNCTION: TreeDirNode.findNode(path) {{{3
1432"Will find one of the children (recursively) that has the given path
1433"
1434"Args:
1435"path: a path object
1436unlet s:TreeDirNode.findNode
1437function! 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
1444
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 {}
1454endfunction
1455"FUNCTION: TreeDirNode.getChildCount() {{{3
1456"Returns the number of children this node has
1457function! s:TreeDirNode.getChildCount()
1458 return len(self.children)
1459endfunction
1460
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.
1464"
1465"This function doesnt not recurse into child dir nodes
1466"
1467"Args:
1468"path: a path object
1469function! s:TreeDirNode.getChild(path)
1470 if stridx(a:path.str(), self.path.str(), 0) ==# -1
1471 return {}
1472 endif
1473
1474 let index = self.getChildIndex(a:path)
1475 if index ==# -1
1476 return {}
1477 else
1478 return self.children[index]
1479 endif
1480
1481endfunction
1482
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.
1489function! 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]
1495endfunction
1496
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.
1500"
1501"This function doesnt not recurse into child dir nodes
1502"
1503"Args:
1504"path: a path object
1505function! s:TreeDirNode.getChildIndex(path)
1506 if stridx(a:path.str(), self.path.str(), 0) ==# -1
1507 return -1
1508 endif
1509
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)
1516
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
1526endfunction
1527
1528"FUNCTION: TreeDirNode.GetSelected() {{{3
1529"Returns the current node if it is a dir node, or else returns the current
1530"nodes parent
1531unlet s:TreeDirNode.GetSelected
1532function! 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
1540endfunction
1541"FUNCTION: TreeDirNode.getVisibleChildCount() {{{3
1542"Returns the number of visible children this node has
1543function! s:TreeDirNode.getVisibleChildCount()
1544 return len(self.getVisibleChildren())
1545endfunction
1546
1547"FUNCTION: TreeDirNode.getVisibleChildren() {{{3
1548"Returns a list of children to display for this node, in the correct order
1549"
1550"Return:
1551"an array of treenodes
1552function! 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
1560endfunction
1561
1562"FUNCTION: TreeDirNode.hasVisibleChildren() {{{3
1563"returns 1 if this node has any childre, 0 otherwise..
1564function! s:TreeDirNode.hasVisibleChildren()
1565 return self.getVisibleChildCount() != 0
1566endfunction
1567
1568"FUNCTION: TreeDirNode._initChildren() {{{3
1569"Removes all childen from this node and re-reads them
1570"
1571"Args:
1572"silent: 1 if the function should not echo any "please wait" messages for
1573"large directories
1574"
1575"Return: the number of child nodes read
1576function! s:TreeDirNode._initChildren(silent)
1577 "remove all the current child nodes
1578 let self.children = []
1579
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")
1585
1586 if !a:silent && len(files) > g:NERDTreeNotificationThreshold
1587 call s:echo("Please wait, caching a large dir ...")
1588 endif
1589
1590 let invalidFilesFound = 0
1591 for i in files
1592
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 !~ '\/\.\/\?$'
1597
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
1607
1608 call self.sortChildren()
1609
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
1613
1614 if invalidFilesFound
1615 call s:echoWarning(invalidFilesFound . " file(s) could not be loaded into the NERD tree")
1616 endif
1617 return self.getChildCount()
1618endfunction
1619"FUNCTION: TreeDirNode.New(path) {{{3
1620"Returns a new TreeNode object with the given path and parent
1621"
1622"Args:
1623"path: a path object representing the full filesystem path to the file/dir that the node represents
1624unlet s:TreeDirNode.New
1625function! 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
1629
1630 let newTreeNode = copy(self)
1631 let newTreeNode.path = a:path
1632
1633 let newTreeNode.isOpen = 0
1634 let newTreeNode.children = []
1635
1636 let newTreeNode.parent = {}
1637
1638 return newTreeNode
1639endfunction
1640"FUNCTION: TreeDirNode.open() {{{3
1641"Reads in all this nodes children
1642"
1643"Return: the number of child nodes read
1644unlet s:TreeDirNode.open
1645function! s:TreeDirNode.open()
1646 let self.isOpen = 1
1647 if self.children ==# []
1648 return self._initChildren(0)
1649 else
1650 return 0
1651 endif
1652endfunction
1653
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)
1657function! 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
1666endfunction
1667"FUNCTION: TreeDirNode.openInNewTab(options) {{{3
1668unlet s:TreeDirNode.openInNewTab
1669function! s:TreeDirNode.openInNewTab(options)
1670 let currentTab = tabpagenr()
1671
1672 if !has_key(a:options, 'keepTreeOpen') || !a:options['keepTreeOpen']
1673 call s:closeTreeIfQuitOnOpen()
1674 endif
1675
1676 tabnew
1677 call s:initNerdTree(self.path.str())
1678
1679 if has_key(a:options, 'stayInCurrentTab') && a:options['stayInCurrentTab']
1680 exec "tabnext " . currentTab
1681 endif
1682endfunction
1683"FUNCTION: TreeDirNode.openRecursively() {{{3
1684"Opens this treenode and all of its children whose paths arent 'ignored'
1685"because of the file filters.
1686"
1687"This method is actually a wrapper for the OpenRecursively2 method which does
1688"the work.
1689function! s:TreeDirNode.openRecursively()
1690 call self._openRecursively2(1)
1691endfunction
1692
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
1697"
1698"Args:
1699"forceOpen: 1 if this node should be opened regardless of file filters
1700function! 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
1706
1707 for i in self.children
1708 if i.path.isDirectory ==# 1
1709 call i._openRecursively2(0)
1710 endif
1711 endfor
1712 endif
1713endfunction
1714
1715"FUNCTION: TreeDirNode.refresh() {{{3
1716unlet s:TreeDirNode.refresh
1717function! s:TreeDirNode.refresh()
1718 call self.path.refresh()
1719
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 !~ '\/\.\/\?$'
1734
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)
1742
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
1749
1750
1751 catch /^NERDTree.InvalidArgumentsError/
1752 let invalidFilesFound = 1
1753 endtry
1754 endif
1755 endfor
1756
1757 "swap this nodes children out for the children we just read/refreshed
1758 let self.children = newChildNodes
1759 call self.sortChildren()
1760
1761 if invalidFilesFound
1762 call s:echoWarning("some files could not be loaded into the NERD tree")
1763 endif
1764 endif
1765endfunction
1766
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
1770function! 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
1774
1775 call self.open()
1776
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
1783
1784 let p = a:path
1785 while !p.getParent().equals(self.path)
1786 let p = p.getParent()
1787 endwhile
1788
1789 let n = self.findNode(p)
1790 call n.reveal(a:path)
1791endfunction
1792"FUNCTION: TreeDirNode.removeChild(treenode) {{{3
1793"
1794"Removes the given treenode from this nodes set of children
1795"
1796"Args:
1797"treenode: the node to remove
1798"
1799"Throws a NERDTree.ChildNotFoundError if the given treenode is not found
1800function! 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
1807
1808 throw "NERDTree.ChildNotFoundError: child node was not found"
1809endfunction
1810
1811"FUNCTION: TreeDirNode.sortChildren() {{{3
1812"
1813"Sorts the children of this node according to alphabetical order and the
1814"directory priority.
1815"
1816function! s:TreeDirNode.sortChildren()
1817 let CompareFunc = function("s:compareNodes")
1818 call sort(self.children, CompareFunc)
1819endfunction
1820
1821"FUNCTION: TreeDirNode.toggleOpen() {{{3
1822"Opens this directory if it is closed and vice versa
1823function! s:TreeDirNode.toggleOpen()
1824 if self.isOpen ==# 1
1825 call self.close()
1826 else
1827 call self.open()
1828 endif
1829endfunction
1830
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
1835"
1836"Arg:
1837"newNode: the node to graft into the tree
1838function! 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
1846endfunction
1847"============================================================
1848"CLASS: Path {{{2
1849"============================================================
1850let s:Path = {}
1851"FUNCTION: Path.AbsolutePathFor(str) {{{3
1852function! 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
1859
1860 let toReturn = a:str
1861 if prependCWD
1862 let toReturn = getcwd() . s:Path.Slash() . a:str
1863 endif
1864
1865 return toReturn
1866endfunction
1867"FUNCTION: Path.bookmarkNames() {{{3
1868function! s:Path.bookmarkNames()
1869 if !exists("self._bookmarkNames")
1870 call self.cacheDisplayString()
1871 endif
1872 return self._bookmarkNames
1873endfunction
1874"FUNCTION: Path.cacheDisplayString() {{{3
1875function! s:Path.cacheDisplayString()
1876 let self.cachedDisplayString = self.getLastPathComponent(1)
1877
1878 if self.isExecutable
1879 let self.cachedDisplayString = self.cachedDisplayString . '*'
1880 endif
1881
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
1891
1892 if self.isSymLink
1893 let self.cachedDisplayString .= ' -> ' . self.symLinkDest
1894 endif
1895
1896 if self.isReadOnly
1897 let self.cachedDisplayString .= ' [RO]'
1898 endif
1899endfunction
1900"FUNCTION: Path.changeToDir() {{{3
1901function! 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
1906
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
1913endfunction
1914
1915"FUNCTION: Path.compareTo() {{{3
1916"
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".
1919"
1920"Args:
1921"path: the path object to compare this to
1922"
1923"Return:
1924"1, -1 or 0
1925function! s:Path.compareTo(path)
1926 let thisPath = self.getLastPathComponent(1)
1927 let thatPath = a:path.getLastPathComponent(1)
1928
1929 "if the paths are the same then clearly we return 0
1930 if thisPath ==# thatPath
1931 return 0
1932 endif
1933
1934 let thisSS = self.getSortOrderIndex()
1935 let thatSS = a:path.getSortOrderIndex()
1936
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
1953endfunction
1954
1955"FUNCTION: Path.Create(fullpath) {{{3
1956"
1957"Factory method.
1958"
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.
1962"
1963"Args:
1964"fullpath: the full filesystem path to the file/dir to create
1965function! 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
1970
1971 try
1972
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, '\(\\\|\/\)$', '', '')
1977
1978 call mkdir(fullpath, 'p')
1979
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
1987
1988 return s:Path.New(a:fullpath)
1989endfunction
1990
1991"FUNCTION: Path.copy(dest) {{{3
1992"
1993"Copies the file/dir represented by this Path to the given location
1994"
1995"Args:
1996"dest: the location to copy this dir/file to
1997function! s:Path.copy(dest)
1998 if !s:Path.CopyingSupported()
1999 throw "NERDTree.CopyingNotSupportedError: Copying is not supported on this OS"
2000 endif
2001
2002 let dest = s:Path.WinToUnixPath(a:dest)
2003
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
2009endfunction
2010
2011"FUNCTION: Path.CopyingSupported() {{{3
2012"
2013"returns 1 if copying is supported for this OS
2014function! s:Path.CopyingSupported()
2015 return exists('g:NERDTreeCopyCmd')
2016endfunction
2017
2018
2019"FUNCTION: Path.copyingWillOverwrite(dest) {{{3
2020"
2021"returns 1 if copy this path to the given location will cause files to
2022"overwritten
2023"
2024"Args:
2025"dest: the location this path will be copied to
2026function! s:Path.copyingWillOverwrite(dest)
2027 if filereadable(a:dest)
2028 return 1
2029 endif
2030
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
2037endfunction
2038
2039"FUNCTION: Path.delete() {{{3
2040"
2041"Deletes the file represented by this path.
2042"Deletion of directories is not supported
2043"
2044"Throws NERDTree.Path.Deletion exceptions
2045function! s:Path.delete()
2046 if self.isDirectory
2047
2048 let cmd = g:NERDTreeRemoveDirCmd . self.str({'escape': 1})
2049 let success = system(cmd)
2050
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
2060
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
2066endfunction
2067
2068"FUNCTION: Path.displayString() {{{3
2069"
2070"Returns a string that specifies how the path should be represented as a
2071"string
2072function! s:Path.displayString()
2073 if self.cachedDisplayString ==# ""
2074 call self.cacheDisplayString()
2075 endif
2076
2077 return self.cachedDisplayString
2078endfunction
2079"FUNCTION: Path.extractDriveLetter(fullpath) {{{3
2080"
2081"If running windows, cache the drive letter for this path
2082function! 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
2088
2089endfunction
2090"FUNCTION: Path.exists() {{{3
2091"return 1 if this path points to a location that is readable or is a directory
2092function! s:Path.exists()
2093 let p = self.str()
2094 return filereadable(p) || isdirectory(p)
2095endfunction
2096"FUNCTION: Path.getDir() {{{3
2097"
2098"Returns this path if it is a directory, else this paths parent.
2099"
2100"Return:
2101"a Path object
2102function! s:Path.getDir()
2103 if self.isDirectory
2104 return self
2105 else
2106 return self.getParent()
2107 endif
2108endfunction
2109"FUNCTION: Path.getParent() {{{3
2110"
2111"Returns a new path object for this paths parent
2112"
2113"Return:
2114"a new Path object
2115function! 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
2121
2122 return s:Path.New(path)
2123endfunction
2124"FUNCTION: Path.getLastPathComponent(dirSlash) {{{3
2125"
2126"Gets the last part of this path.
2127"
2128"Args:
2129"dirSlash: if 1 then a trailing slash will be added to the returned value for
2130"directory nodes.
2131function! 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
2140endfunction
2141
2142"FUNCTION: Path.getSortOrderIndex() {{{3
2143"returns the index of the pattern in g:NERDTreeSortOrder that this path matches
2144function! 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
2153endfunction
2154
2155"FUNCTION: Path.ignore() {{{3
2156"returns true if this path should be ignored
2157function! s:Path.ignore()
2158 let lastPathComponent = self.getLastPathComponent(0)
2159
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
2168
2169 "dont show hidden files unless instructed to
2170 if b:NERDTreeShowHidden ==# 0 && lastPathComponent =~ '^\.'
2171 return 1
2172 endif
2173
2174 if b:NERDTreeShowFiles ==# 0 && self.isDirectory ==# 0
2175 return 1
2176 endif
2177
2178 return 0
2179endfunction
2180
2181"FUNCTION: Path.isUnder(path) {{{3
2182"return 1 if this path is somewhere under the given path in the filesystem.
2183"
2184"a:path should be a dir
2185function! s:Path.isUnder(path)
2186 if a:path.isDirectory == 0
2187 return 0
2188 endif
2189
2190 let this = self.str()
2191 let that = a:path.str()
2192 return stridx(this, that . s:Path.Slash()) == 0
2193endfunction
2194
2195"FUNCTION: Path.JoinPathStrings(...) {{{3
2196function! 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, '/')
2202endfunction
2203
2204"FUNCTION: Path.equals() {{{3
2205"
2206"Determines whether 2 path objects are "equal".
2207"They are equal if the paths they represent are the same
2208"
2209"Args:
2210"path: the other path obj to compare this with
2211function! s:Path.equals(path)
2212 return self.str() ==# a:path.str()
2213endfunction
2214
2215"FUNCTION: Path.New() {{{3
2216"The Constructor for the Path object
2217function! s:Path.New(path)
2218 let newPath = copy(self)
2219
2220 call newPath.readInfoFromDisk(s:Path.AbsolutePathFor(a:path))
2221
2222 let newPath.cachedDisplayString = ""
2223
2224 return newPath
2225endfunction
2226
2227"FUNCTION: Path.Slash() {{{3
2228"return the slash to use for the current OS
2229function! s:Path.Slash()
2230 return s:running_windows ? '\' : '/'
2231endfunction
2232
2233"FUNCTION: Path.readInfoFromDisk(fullpath) {{{3
2234"
2235"
2236"Throws NERDTree.Path.InvalidArguments exception.
2237function! s:Path.readInfoFromDisk(fullpath)
2238 call self.extractDriveLetter(a:fullpath)
2239
2240 let fullpath = s:Path.WinToUnixPath(a:fullpath)
2241
2242 if getftype(fullpath) ==# "fifo"
2243 throw "NERDTree.InvalidFiletypeError: Cant handle FIFO files: " . a:fullpath
2244 endif
2245
2246 let self.pathSegments = split(fullpath, '/')
2247
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
2257
2258 let self.isExecutable = 0
2259 if !self.isDirectory
2260 let self.isExecutable = getfperm(a:fullpath) =~ 'x'
2261 endif
2262
2263 "grab the last part of the path (minus the trailing slash)
2264 let lastPathComponent = self.getLastPathComponent(0)
2265
2266 "get the path to the new node with the parent dir fully resolved
2267 let hardPath = resolve(self.strTrunk()) . '/' . lastPathComponent
2268
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)
2273
2274 "if the link is a dir then slap a / on the end of its dest
2275 if isdirectory(self.symLinkDest)
2276
2277 "we always wanna treat MS windows shortcuts as files for
2278 "simplicity
2279 if hardPath !~ '\.lnk$'
2280
2281 let self.symLinkDest = self.symLinkDest . '/'
2282 endif
2283 endif
2284 endif
2285endfunction
2286
2287"FUNCTION: Path.refresh() {{{3
2288function! s:Path.refresh()
2289 call self.readInfoFromDisk(self.str())
2290 call self.cacheDisplayString()
2291endfunction
2292
2293"FUNCTION: Path.rename() {{{3
2294"
2295"Renames this node on the filesystem
2296function! s:Path.rename(newPath)
2297 if a:newPath ==# ''
2298 throw "NERDTree.InvalidArgumentsError: Invalid newPath for renaming = ". a:newPath
2299 endif
2300
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)
2306
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()
2312endfunction
2313
2314"FUNCTION: Path.str() {{{3
2315"
2316"Returns a string representation of this Path
2317"
2318"Takes an optional dictionary param to specify how the output should be
2319"formatted.
2320"
2321"The dict may have the following keys:
2322" 'format'
2323" 'escape'
2324" 'truncateTo'
2325"
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
2330"
2331"The 'escape' key, if specified will cause the output to be escaped with
2332"shellescape()
2333"
2334"The 'truncateTo' key causes the resulting string to be truncated to the value
2335"'truncateTo' maps to. A '<' char will be prepended.
2336function! s:Path.str(...)
2337 let options = a:0 ? a:1 : {}
2338 let toReturn = ""
2339
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
2350
2351 if has_key(options, 'escape') && options['escape']
2352 let toReturn = shellescape(toReturn)
2353 endif
2354
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
2361
2362 return toReturn
2363endfunction
2364
2365"FUNCTION: Path._strForUI() {{{3
2366function! s:Path._strForUI()
2367 let toReturn = '/' . join(self.pathSegments, '/')
2368 if self.isDirectory && toReturn != '/'
2369 let toReturn = toReturn . '/'
2370 endif
2371 return toReturn
2372endfunction
2373
2374"FUNCTION: Path._strForCd() {{{3
2375"
2376" returns a string that can be used with :cd
2377function! s:Path._strForCd()
2378 return escape(self.str(), s:escape_chars)
2379endfunction
2380"FUNCTION: Path._strForEdit() {{{3
2381"
2382"Return: the string for this path that is suitable to be used with the :edit
2383"command
2384function! s:Path._strForEdit()
2385 let p = self.str({'format': 'UI'})
2386 let cwd = getcwd()
2387
2388 if s:running_windows
2389 let p = tolower(self.str())
2390 let cwd = tolower(getcwd())
2391 endif
2392
2393 let p = escape(p, s:escape_chars)
2394
2395 let cwd = cwd . s:Path.Slash()
2396
2397 "return a relative path if we can
2398 if stridx(p, cwd) ==# 0
2399 let p = strpart(p, strlen(cwd))
2400 endif
2401
2402 if p ==# ''
2403 let p = '.'
2404 endif
2405
2406 return p
2407
2408endfunction
2409"FUNCTION: Path._strForGlob() {{{3
2410function! s:Path._strForGlob()
2411 let lead = s:Path.Slash()
2412
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
2417
2418 let toReturn = lead . join(self.pathSegments, s:Path.Slash())
2419
2420 if !s:running_windows
2421 let toReturn = escape(toReturn, s:escape_chars)
2422 endif
2423 return toReturn
2424endfunction
2425"FUNCTION: Path._str() {{{3
2426"
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
2430function! s:Path._str()
2431 let lead = s:Path.Slash()
2432
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
2437
2438 return lead . join(self.pathSegments, s:Path.Slash())
2439endfunction
2440
2441"FUNCTION: Path.strTrunk() {{{3
2442"Gets the path without the last segment on the end.
2443function! s:Path.strTrunk()
2444 return self.drive . '/' . join(self.pathSegments[0:-2], '/')
2445endfunction
2446
2447"FUNCTION: Path.WinToUnixPath(pathstr){{{3
2448"Takes in a windows path and returns the unix equiv
2449"
2450"A class level method
2451"
2452"Args:
2453"pathstr: the windows path to convert
2454function! s:Path.WinToUnixPath(pathstr)
2455 if !s:running_windows
2456 return a:pathstr
2457 endif
2458
2459 let toReturn = a:pathstr
2460
2461 "remove the x:\ of the front
2462 let toReturn = substitute(toReturn, '^.*:\(\\\|/\)\?', '/', "")
2463
2464 "convert all \ chars to /
2465 let toReturn = substitute(toReturn, '\', '/', "g")
2466
2467 return toReturn
2468endfunction
2469
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!
2476"
2477"Args:
2478"bnum: the subject buffers buffer number
2479function! 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
2492
2493 return cnt
2494endfunction " >>>
2495"FUNCTION: s:checkForBrowse(dir) {{{2
2496"inits a secondary nerd tree in the current buffer if appropriate
2497function! s:checkForBrowse(dir)
2498 if a:dir != '' && isdirectory(a:dir)
2499 call s:initNerdTreeInPlace(a:dir)
2500 endif
2501endfunction
2502"FUNCTION: s:compareBookmarks(first, second) {{{2
2503"Compares two bookmarks
2504function! s:compareBookmarks(first, second)
2505 return a:first.compareTo(a:second)
2506endfunction
2507
2508" FUNCTION: s:completeBookmarks(A,L,P) {{{2
2509" completion function for the bookmark commands
2510function! s:completeBookmarks(A,L,P)
2511 return filter(s:Bookmark.BookmarkNames(), 'v:val =~ "^' . a:A . '"')
2512endfunction
2513" FUNCTION: s:exec(cmd) {{{2
2514" same as :exec cmd but eventignore=all is set for the duration
2515function! s:exec(cmd)
2516 let old_ei = &ei
2517 set ei=all
2518 exec a:cmd
2519 let &ei = old_ei
2520endfunction
2521" FUNCTION: s:findAndRevealPath() {{{2
2522function! 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
2529
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)
2543endfunction
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
2547"
2548"Args:
2549"name: the name of a bookmark or a directory
2550function! 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
2556
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)
2562
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
2573
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
2579
2580 if s:treeExistsForTab()
2581 if s:isTreeOpen()
2582 call s:closeTree()
2583 endif
2584 unlet t:NERDTreeBufName
2585 endif
2586
2587 let newRoot = s:TreeDirNode.New(path)
2588 call newRoot.open()
2589
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
2597
2598 let b:NERDTreeType = "primary"
2599
2600 call s:renderView()
2601 call b:NERDTreeRoot.putCursorHere(0, 0)
2602endfunction
2603
2604"FUNCTION: s:initNerdTreeInPlace(dir) {{{2
2605function! 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
2612
2613 "we want the directory buffer to disappear when we do the :edit below
2614 setlocal bufhidden=wipe
2615
2616 let previousBuf = expand("#")
2617
2618 "we need a unique name for each secondary tree buffer to ensure they are
2619 "all independent
2620 exec "silent edit " . s:nextBufferName()
2621
2622 let b:NERDTreePreviousBuf = bufnr(previousBuf)
2623
2624 let b:NERDTreeRoot = s:TreeDirNode.New(path)
2625 call b:NERDTreeRoot.open()
2626
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
2640
2641 iabc <buffer>
2642
2643 if g:NERDTreeHighlightCursorline
2644 setlocal cursorline
2645 endif
2646
2647 call s:setupStatusline()
2648
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
2654
2655 let b:NERDTreeType = "secondary"
2656
2657 call s:bindMappings()
2658 setfiletype nerdtree
2659 " syntax highlighting
2660 if has("syntax") && exists("g:syntax_on")
2661 call s:setupSyntaxHighlighting()
2662 endif
2663
2664 call s:renderView()
2665endfunction
2666" FUNCTION: s:initNerdTreeMirror() {{{2
2667function! s:initNerdTreeMirror()
2668
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)
2678
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
2689
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
2699
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
2707
2708 if s:treeExistsForTab() && s:isTreeOpen()
2709 call s:closeTree()
2710 endif
2711
2712 let t:NERDTreeBufName = bufferName
2713 call s:createTreeWin()
2714 exec 'buffer ' . bufferName
2715 if !&hidden
2716 call s:renderView()
2717 endif
2718endfunction
2719" FUNCTION: s:nextBufferName() {{{2
2720" returns the buffer name for the next nerd tree
2721function! s:nextBufferName()
2722 let name = s:NERDTreeBufName . s:next_buffer_number
2723 let s:next_buffer_number += 1
2724 return name
2725endfunction
2726" FUNCTION: s:tabpagevar(tabnr, var) {{{2
2727function! s:tabpagevar(tabnr, var)
2728 let currentTab = tabpagenr()
2729 let old_ei = &ei
2730 set ei=all
2731
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
2738
2739 let &ei = old_ei
2740
2741 return v
2742endfunction
2743" Function: s:treeExistsForBuffer() {{{2
2744" Returns 1 if a nerd tree root exists in the current buffer
2745function! s:treeExistsForBuf()
2746 return exists("b:NERDTreeRoot")
2747endfunction
2748" Function: s:treeExistsForTab() {{{2
2749" Returns 1 if a nerd tree root exists in the current tab
2750function! s:treeExistsForTab()
2751 return exists("t:NERDTreeBufName")
2752endfunction
2753" Function: s:unique(list) {{{2
2754" returns a:list without duplicates
2755function! 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
2763endfunction
2764" SECTION: Public API {{{1
2765"============================================================
2766let g:NERDTreePath = s:Path
2767let g:NERDTreeDirNode = s:TreeDirNode
2768let g:NERDTreeFileNode = s:TreeFileNode
2769let g:NERDTreeBookmark = s:Bookmark
2770
2771function! NERDTreeAddMenuItem(options)
2772 call s:MenuItem.Create(a:options)
2773endfunction
2774
2775function! NERDTreeAddMenuSeparator(...)
2776 let opts = a:0 ? a:1 : {}
2777 call s:MenuItem.CreateSeparator(opts)
2778endfunction
2779
2780function! NERDTreeAddSubmenu(options)
2781 return s:MenuItem.Create(a:options)
2782endfunction
2783
2784function! NERDTreeAddKeyMap(options)
2785 call s:KeyMap.Create(a:options)
2786endfunction
2787
2788function! NERDTreeRender()
2789 call s:renderView()
2790endfunction
2791
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)
2797function! 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
2806endfunction
2807"FUNCTION: s:closeTree() {{{2
2808"Closes the primary NERD tree window for this tab
2809function! s:closeTree()
2810 if !s:isTreeOpen()
2811 throw "NERDTree.NoTreeFoundError: no NERDTree is open"
2812 endif
2813
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
2821endfunction
2822
2823"FUNCTION: s:closeTreeIfOpen() {{{2
2824"Closes the NERD tree window if it is open
2825function! s:closeTreeIfOpen()
2826 if s:isTreeOpen()
2827 call s:closeTree()
2828 endif
2829endfunction
2830"FUNCTION: s:closeTreeIfQuitOnOpen() {{{2
2831"Closes the NERD tree window if the close on open option is set
2832function! s:closeTreeIfQuitOnOpen()
2833 if g:NERDTreeQuitOnOpen && s:isTreeOpen()
2834 call s:closeTree()
2835 endif
2836endfunction
2837"FUNCTION: s:createTreeWin() {{{2
2838"Inits the NERD tree window. ie. opens it, sizes it, sets all the local
2839"options etc
2840function! s:createTreeWin()
2841 "create the nerd tree window
2842 let splitLocation = g:NERDTreeWinPos ==# "left" ? "topleft " : "botright "
2843 let splitSize = g:NERDTreeWinSize
2844
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
2853
2854 setlocal winfixwidth
2855
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
2868
2869 iabc <buffer>
2870
2871 if g:NERDTreeHighlightCursorline
2872 setlocal cursorline
2873 endif
2874
2875 call s:setupStatusline()
2876
2877 call s:bindMappings()
2878 setfiletype nerdtree
2879 " syntax highlighting
2880 if has("syntax") && exists("g:syntax_on")
2881 call s:setupSyntaxHighlighting()
2882 endif
2883endfunction
2884
2885"FUNCTION: s:dumpHelp {{{2
2886"prints out the quick help
2887function! 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"
2910
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"
2921
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"
2929
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"
2938
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"
2951
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"
2958
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
2967
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
2985
2986 silent! put h
2987
2988 let @h = old_h
2989endfunction
2990"FUNCTION: s:echo {{{2
2991"A wrapper for :echo. Appends 'NERDTree:' on the front of all messages
2992"
2993"Args:
2994"msg: the message to echo
2995function! s:echo(msg)
2996 redraw
2997 echomsg "NERDTree: " . a:msg
2998endfunction
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
3003function! s:echoWarning(msg)
3004 echohl warningmsg
3005 call s:echo(a:msg)
3006 echohl normal
3007endfunction
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
3012function! s:echoError(msg)
3013 echohl errormsg
3014 call s:echo(a:msg)
3015 echohl normal
3016endfunction
3017"FUNCTION: s:firstUsableWindow(){{{2
3018"find the window number of the first normal window
3019function! 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
3028
3029 let i += 1
3030 endwhile
3031 return -1
3032endfunction
3033"FUNCTION: s:getPath(ln) {{{2
3034"Gets the full path to the node that is rendered on the given line number
3035"
3036"Args:
3037"ln: the line number to get the path for
3038"
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
3043function! s:getPath(ln)
3044 let line = getline(a:ln)
3045
3046 let rootLine = s:TreeFileNode.GetRootLineNum()
3047
3048 "check to see if we have the root node
3049 if a:ln == rootLine
3050 return b:NERDTreeRoot.path
3051 endif
3052
3053 " in case called from outside the tree
3054 if line !~ '^ *[|`]' || line =~ '^$'
3055 return {}
3056 endif
3057
3058 if line ==# s:tree_up_dir_line
3059 return b:NERDTreeRoot.path.getParent()
3060 endif
3061
3062 let indent = s:indentLevelFor(line)
3063
3064 "remove the tree parts and the leading space
3065 let curFile = s:stripMarkupFromLine(line, 0)
3066
3067 let wasdir = 0
3068 if curFile =~ '/$'
3069 let wasdir = 1
3070 let curFile = substitute(curFile, '/\?$', '/', "")
3071 endif
3072
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)
3079
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
3089
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
3098endfunction
3099
3100"FUNCTION: s:getTreeWinNum() {{{2
3101"gets the nerd tree window number for this tab
3102function! s:getTreeWinNum()
3103 if exists("t:NERDTreeBufName")
3104 return bufwinnr(t:NERDTreeBufName)
3105 else
3106 return -1
3107 endif
3108endfunction
3109"FUNCTION: s:indentLevelFor(line) {{{2
3110function! s:indentLevelFor(line)
3111 return match(a:line, '[^ \-+~`|]') / s:tree_wid
3112endfunction
3113"FUNCTION: s:isTreeOpen() {{{2
3114function! s:isTreeOpen()
3115 return s:getTreeWinNum() != -1
3116endfunction
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
3120"
3121"Args:
3122"winnumber: the number of the window in question
3123function! 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
3128
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")
3134
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
3140
3141 if &hidden
3142 return 1
3143 endif
3144
3145 return !modified || s:bufInWindows(winbufnr(a:winnumber)) >= 2
3146endfunction
3147
3148" FUNCTION: s:jumpToChild(direction) {{{2
3149" Args:
3150" direction: 0 if going to first child, 1 if going to last
3151function! 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()
3159
3160 let targetNode = childNodes[0]
3161 if a:direction
3162 let targetNode = childNodes[len(childNodes) - 1]
3163 endif
3164
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
3172
3173 call targetNode.putCursorHere(1, 0)
3174
3175 call s:centerView()
3176endfunction
3177
3178
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
3182"
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
3187function! s:promptToDelBuffer(bufnum, msg)
3188 echo a:msg
3189 if nr2char(getchar()) ==# 'y'
3190 exec "silent bdelete! " . a:bufnum
3191 endif
3192endfunction
3193
3194"FUNCTION: s:putCursorOnBookmarkTable(){{{2
3195"Places the cursor at the top of the bookmarks table
3196function! s:putCursorOnBookmarkTable()
3197 if !b:NERDTreeShowBookmarks
3198 throw "NERDTree.IllegalOperationError: cant find bookmark table, bookmarks arent active"
3199 endif
3200
3201 let rootNodeLine = s:TreeFileNode.GetRootLineNum()
3202
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)
3211endfunction
3212
3213"FUNCTION: s:putCursorInTreeWin(){{{2
3214"Places the cursor in the nerd tree window
3215function! s:putCursorInTreeWin()
3216 if !s:isTreeOpen()
3217 throw "NERDTree.InvalidOperationError: cant put cursor in NERD tree window, no window exists"
3218 endif
3219
3220 call s:exec(s:getTreeWinNum() . "wincmd w")
3221endfunction
3222
3223"FUNCTION: s:renderBookmarks {{{2
3224function! s:renderBookmarks()
3225
3226 call setline(line(".")+1, ">----------Bookmarks----------")
3227 call cursor(line(".")+1, col("."))
3228
3229 for i in s:Bookmark.Bookmarks()
3230 call setline(line(".")+1, i.str())
3231 call cursor(line(".")+1, col("."))
3232 endfor
3233
3234 call setline(line(".")+1, '')
3235 call cursor(line(".")+1, col("."))
3236endfunction
3237"FUNCTION: s:renderView {{{2
3238"The entry function for rendering the tree
3239function! s:renderView()
3240 setlocal modifiable
3241
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")
3247
3248 "delete all lines in the buffer (being careful not to clobber a register)
3249 silent 1,$delete _
3250
3251 call s:dumpHelp()
3252
3253 "delete the blank line before the help and add one after it
3254 call setline(line(".")+1, "")
3255 call cursor(line(".")+1, col("."))
3256
3257 if b:NERDTreeShowBookmarks
3258 call s:renderBookmarks()
3259 endif
3260
3261 "add the 'up a dir' line
3262 call setline(line(".")+1, s:tree_up_dir_line)
3263 call cursor(line(".")+1, col("."))
3264
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("."))
3269
3270 "draw the tree
3271 let old_o = @o
3272 let @o = b:NERDTreeRoot.renderToString()
3273 silent put o
3274 let @o = old_o
3275
3276 "delete the blank line at the top of the buffer
3277 silent 1,1delete _
3278
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
3286
3287 setlocal nomodifiable
3288endfunction
3289
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
3293function! s:renderViewSavingPosition()
3294 let currentNode = s:TreeFileNode.GetSelected()
3295
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
3301
3302 call s:renderView()
3303
3304 if currentNode != {}
3305 call currentNode.putCursorHere(0, 0)
3306 endif
3307endfunction
3308"FUNCTION: s:restoreScreenState() {{{2
3309"
3310"Sets the screen state back to what it was when s:saveScreenState was last
3311"called.
3312"
3313"Assumes the cursor is in the NERDTree window
3314function! s:restoreScreenState()
3315 if !exists("b:NERDTreeOldTopLine") || !exists("b:NERDTreeOldPos") || !exists("b:NERDTreeOldWindowSize")
3316 return
3317 endif
3318 exec("silent vertical resize ".b:NERDTreeOldWindowSize)
3319
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
3326endfunction
3327
3328"FUNCTION: s:saveScreenState() {{{2
3329"Saves the current cursor position in the current buffer and the window
3330"scroll position
3331function! 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
3341endfunction
3342
3343"FUNCTION: s:setupStatusline() {{{2
3344function! s:setupStatusline()
3345 if g:NERDTreeStatusline != -1
3346 let &l:statusline = g:NERDTreeStatusline
3347 endif
3348endfunction
3349"FUNCTION: s:setupSyntaxHighlighting() {{{2
3350function! 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\]#
3355
3356 "highlighting for the .. (up dir) line at the top of the tree
3357 execute "syn match treeUp #". s:tree_up_dir_line ."#"
3358
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
3364
3365 "highlighting for the tree structural parts
3366 syn match treePart #|#
3367 syn match treePart #`#
3368 syn match treePartFile #[|`]-#hs=s+1 contains=treePart
3369
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
3378
3379 "highlighting for readonly files
3380 syn match treeRO #.*\[RO\]#hs=s+2 contains=treeFlag,treeBookmark,treePart,treePartFile
3381
3382 "highlighting for sym links
3383 syn match treeLink #[^-| `].* -> # contains=treeBookmark,treeOpenable,treeClosable,treeDirSlash
3384
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 #^/.*$#
3392
3393 "highlighting for bookmarks
3394 syn match treeBookmark # {.*}#hs=s+1
3395
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
3401
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
3415
3416 hi def link treeBookmarksHeader statement
3417 hi def link treeBookmarksLeader ignore
3418 hi def link treeBookmarkName Identifier
3419 hi def link treeBookmark normal
3420
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
3427
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
3436
3437 hi def link NERDTreeCurrentNode Search
3438endfunction
3439
3440"FUNCTION: s:stripMarkupFromLine(line, removeLeadingSpaces){{{2
3441"returns the given line with all the tree parts stripped off
3442"
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)
3447function! 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,"","")
3451
3452 "strip off any read only flag
3453 let line = substitute (line, ' \[RO\]', "","")
3454
3455 "strip off any bookmark flags
3456 let line = substitute (line, ' {[^}]*}', "","")
3457
3458 "strip off any executable flags
3459 let line = substitute (line, '*\ze\($\| \)', "","")
3460
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
3469
3470 if a:removeLeadingSpaces
3471 let line = substitute (line, '^ *', '', '')
3472 endif
3473
3474 return line
3475endfunction
3476
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)
3480"
3481"Args:
3482"dir: the full path for the root node (is only used if the NERD tree is being
3483"initialized.
3484function! 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
3498endfunction
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.
3504"
3505"args:
3506"forceKeepWindowOpen - dont close the window even if NERDTreeQuitOnOpen is set
3507function! s:activateNode(forceKeepWindowOpen)
3508 if getline(".") ==# s:tree_up_dir_line
3509 return s:upDir(0)
3510 endif
3511
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
3521endfunction
3522
3523"FUNCTION: s:bindMappings() {{{2
3524function! 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>
3529
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>"
3533
3534 exec "nnoremap <silent> <buffer> ". g:NERDTreeMapPreview ." :call <SID>previewNode(0)<cr>"
3535 exec "nnoremap <silent> <buffer> ". g:NERDTreeMapPreviewSplit ." :call <SID>previewNode(1)<cr>"
3536
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>"
3539
3540 exec "nnoremap <silent> <buffer> ". g:NERDTreeMapOpenRecursively ." :call <SID>openNodeRecursively()<cr>"
3541
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>"
3545
3546 exec "nnoremap <silent> <buffer> ". g:NERDTreeMapChdir ." :call <SID>chCwd()<cr>"
3547
3548 exec "nnoremap <silent> <buffer> ". g:NERDTreeMapQuit ." :call <SID>closeTreeWindow()<cr>"
3549
3550 exec "nnoremap <silent> <buffer> ". g:NERDTreeMapRefreshRoot ." :call <SID>refreshRoot()<cr>"
3551 exec "nnoremap <silent> <buffer> ". g:NERDTreeMapRefresh ." :call <SID>refreshCurrent()<cr>"
3552
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>"
3559
3560 exec "nnoremap <silent> <buffer> ". g:NERDTreeMapCloseDir ." :call <SID>closeCurrentDir()<cr>"
3561 exec "nnoremap <silent> <buffer> ". g:NERDTreeMapCloseChildren ." :call <SID>closeChildren()<cr>"
3562
3563 exec "nnoremap <silent> <buffer> ". g:NERDTreeMapMenu ." :call <SID>showMenu()<cr>"
3564
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>"
3571
3572 exec "nnoremap <silent> <buffer> ". g:NERDTreeMapOpenInTab ." :call <SID>openInNewTab(0)<cr>"
3573 exec "nnoremap <silent> <buffer> ". g:NERDTreeMapOpenInTabSilent ." :call <SID>openInNewTab(1)<cr>"
3574
3575 exec "nnoremap <silent> <buffer> ". g:NERDTreeMapOpenExpl ." :call <SID>openExplorer()<cr>"
3576
3577 exec "nnoremap <silent> <buffer> ". g:NERDTreeMapDeleteBookmark ." :call <SID>deleteBookmark()<cr>"
3578
3579 "bind all the user custom maps
3580 call s:KeyMap.BindAll()
3581
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()
3590endfunction
3591
3592" FUNCTION: s:bookmarkNode(name) {{{2
3593" Associate the current node with the given name
3594function! 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
3606endfunction
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)
3611function! 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)
3616
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
3625
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
3633endfunction
3634
3635" FUNCTION: s:chCwd() {{{2
3636function! s:chCwd()
3637 let treenode = s:TreeFileNode.GetSelected()
3638 if treenode ==# {}
3639 call s:echo("Select a node first")
3640 return
3641 endif
3642
3643 try
3644 call treenode.path.changeToDir()
3645 catch /^NERDTree.PathChangeError/
3646 call s:echoWarning("could not change cwd")
3647 endtry
3648endfunction
3649
3650" FUNCTION: s:chRoot() {{{2
3651" changes the current root to the selected one
3652function! s:chRoot()
3653 let treenode = s:TreeFileNode.GetSelected()
3654 if treenode ==# {}
3655 call s:echo("Select a node first")
3656 return
3657 endif
3658
3659 call treenode.makeRoot()
3660 call s:renderView()
3661 call b:NERDTreeRoot.putCursorHere(0, 0)
3662endfunction
3663
3664" FUNCTION: s:clearBookmarks(bookmarks) {{{2
3665function! 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()
3678endfunction
3679" FUNCTION: s:closeChildren() {{{2
3680" closes all childnodes of the current node
3681function! s:closeChildren()
3682 let currentNode = s:TreeDirNode.GetSelected()
3683 if currentNode ==# {}
3684 call s:echo("Select a node first")
3685 return
3686 endif
3687
3688 call currentNode.closeChildren()
3689 call s:renderView()
3690 call currentNode.putCursorHere(0, 0)
3691endfunction
3692" FUNCTION: s:closeCurrentDir() {{{2
3693" closes the parent dir of the current node
3694function! s:closeCurrentDir()
3695 let treenode = s:TreeFileNode.GetSelected()
3696 if treenode ==# {}
3697 call s:echo("Select a node first")
3698 return
3699 endif
3700
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
3709endfunction
3710" FUNCTION: s:closeTreeWindow() {{{2
3711" close the tree window
3712function! 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
3722endfunction
3723" FUNCTION: s:deleteBookmark() {{{2
3724" if the cursor is on a bookmark, prompt to delete
3725function! 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
3731
3732 echo "Are you sure you wish to delete the bookmark:\n\"" . bookmark.name . "\" (yN):"
3733
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
3745
3746endfunction
3747
3748" FUNCTION: s:displayHelp() {{{2
3749" toggles the help display
3750function! s:displayHelp()
3751 let b:treeShowHelp = b:treeShowHelp ? 0 : 1
3752 call s:renderView()
3753 call s:centerView()
3754endfunction
3755
3756" FUNCTION: s:handleMiddleMouse() {{{2
3757function! 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
3763
3764 if curNode.path.isDirectory
3765 call s:openExplorer()
3766 else
3767 call s:openEntrySplit(0,0)
3768 endif
3769endfunction
3770
3771
3772" FUNCTION: s:jumpToFirstChild() {{{2
3773" wrapper for the jump to child method
3774function! s:jumpToFirstChild()
3775 call s:jumpToChild(0)
3776endfunction
3777
3778" FUNCTION: s:jumpToLastChild() {{{2
3779" wrapper for the jump to child method
3780function! s:jumpToLastChild()
3781 call s:jumpToChild(1)
3782endfunction
3783
3784" FUNCTION: s:jumpToParent() {{{2
3785" moves the cursor to the parent of the current node
3786function! 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
3798endfunction
3799
3800" FUNCTION: s:jumpToRoot() {{{2
3801" moves the cursor to the root node
3802function! s:jumpToRoot()
3803 call b:NERDTreeRoot.putCursorHere(1, 0)
3804 call s:centerView()
3805endfunction
3806
3807" FUNCTION: s:jumpToSibling() {{{2
3808" moves the cursor to the sibling of the current node in the given direction
3809