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