1 "" File: vtreeexplorer.vim
2 "" Description: tree-like file system explorer for vim
3 "" Version: $Revision: 1.1.1.1 $ $Date: 2006/03/31 18:55:13 $
4 "" Author: TS Urban (thomas.scott.urban@HORMELgmail.net)
5 "" (remove the source of SPAM from my email first)
8 "" 1 - source this file or put in your plugin directory
9 "" 2 - :VTreeExlorer or :VSTreeExplore
10 "" 3 - help at top of screen
11 "" 4 - this script comes with a help text that integrates with the vim help
12 "" system, put vtreeexplorer.txt in your ~/.vim/doc dir, then do
13 "" :helptags ~/.vim/doc
15 "" Global Configuration Variables:
16 "" treeExplVertical : split vertically when starting with VSTreeExplore
17 "" treeExplWinSize : window size (width or height) when doing VSTreeExplore
18 "" treeExplHidden : set to have explorer start with hidden files shown
19 "" treeExplDirSort : start explorer with desired directory sorting:
20 "" 0 : no directory sorting
21 "" 1 : directories sorting first
22 "" -1 : directories sorting last
23 "" treeExplIndent : width of tree indentation in spaces (min 3, max 8)
26 "" - global option for path separator
27 "" - merge in patches for winmanager
28 "" - +/- keymappings, etc
29 "" - recursively collapse binding/function
31 "" prevent multiple loading unless developing with g:treeExplDebug
32 if exists("vloaded_tree_explorer") && !exists("g:treeExplDebug")
35 let vloaded_tree_explorer=1
41 command! -n=? -complete=dir VTreeExplore :call s:TreeExplorer(0, '<a>')
42 command! -n=? -complete=dir VSTreeExplore :call s:TreeExplorer(1, '<a>')
45 autocmd BufNewFile TreeExplorer VTreeExplore
47 "" create a string of chr cnt long - emulate vim7 repeat function
48 function! s:MyRepeat(chr, cnt) " <<<
52 let sret = sret . a:chr
58 "" TreeExplorer() - set up explorer window
59 function! s:TreeExplorer(split, start) " <<<
61 " dir to start in from arg, buff dir, or pwd
62 let fname = (a:start != "") ? a:start : expand ("%:p:h")
63 let fname = (fname != "") ? fname : getcwd ()
65 " construct command to open window
66 if a:split || &modified
67 " if starting with split, get split parameters from globals
68 let splitMode = (exists("g:treeExplVertical")) ? "vertical " : ""
69 let splitSize = (exists("g:treeExplWinSize")) ? g:treeExplWinSize : 20
70 let cmd = splitMode . splitSize . "new TreeExplorer"
72 let cmd = "e TreeExplorer"
76 "" chars to escape in file/dir names - TODO '+' ?
77 let w:escape_chars = " `|\"~'#"
79 " win specific vars from globals if they exist
80 let w:hidden_files = (exists("g:treeExplHidden")) ? 1 : 0
81 let w:dirsort = (exists("g:treeExplDirSort")) ? g:treeExplDirSort : 0
82 if w:dirsort < -1 || w:dirsort > 1
85 let w:escape_chars = w:escape_chars . '+'
88 " tree visual widget configuration, width limited to range [3,16]
89 let w:tree_wid_ind = (exists("g:treeExplIndent")) ? g:treeExplIndent : 3
90 let w:tree_wid_ind = (w:tree_wid_ind < 3) ? 3 : w:tree_wid_ind
91 let w:tree_wid_ind = (w:tree_wid_ind > 8) ? 16 : w:tree_wid_ind
98 let w:tree_par_wid = bar_char . s:MyRepeat (spc_char, w:tree_wid_ind - 2) . spc_char
99 let w:tree_dir_wid = bar_char . s:MyRepeat (dsh_char, w:tree_wid_ind - 2) . spc_char
100 let w:tree_end_wid = grv_char . s:MyRepeat (dsh_char, w:tree_wid_ind - 2) . spc_char
101 let w:tree_spc_wid = s:MyRepeat (spc_char, w:tree_wid_ind)
103 " init help to short version
106 " throwaway buffer options
108 setlocal buftype=nowrite
109 setlocal bufhidden=delete " d
113 " setup folding for markers that will be inserted
114 setlocal foldmethod=marker
115 setlocal foldtext=substitute(getline(v:foldstart),'.{{{.*','','')
118 " syntax highlighting
119 if has("syntax") && exists("g:syntax_on") && !has("syntax_items")
120 syn match treeHlp #^" .*#
121 syn match treeDir "^\.\. (up a directory)$"
123 syn match treeFld "{{{"
124 syn match treeFld "}}}"
126 execute "syn match treePrt #" . w:tree_par_wid . "#"
127 execute "syn match treePrt #" . w:tree_dir_wid . "#"
128 execute "syn match treePrt #" . w:tree_end_wid . "#"
130 syn match treeLnk #[^-| `].* -> # contains=treeFld
131 syn match treeDir #[^-| `].*/\([ {}]\{4\}\)*$# contains=treeFld,treeLnk
132 syn match treeCWD #^/.*$# contains=treeFld
134 hi def link treePrt Normal
135 hi def link treeFld Ignore
136 hi def link treeHlp Special
137 hi def link treeDir Directory
138 hi def link treeCWD Statement
139 hi def link treeLnk Title
142 " for line continuation
146 " set up mappings and commands for this buffer
147 nnoremap <buffer> <cr> :call <SID>Activate()<cr>
148 nnoremap <buffer> o :call <SID>Activate()<cr>
149 nnoremap <buffer> X :call <SID>RecursiveExpand()<cr>
150 nnoremap <buffer> E :call <SID>OpenExplorer()<cr>
151 nnoremap <buffer> C :call <SID>ChangeTop()<cr>
152 nnoremap <buffer> H :call <SID>InitWithDir($HOME)<cr>
153 nnoremap <buffer> u :call <SID>ChdirUp()<cr>
154 nnoremap <buffer> p :call <SID>MoveParent()<cr>
155 nnoremap <buffer> r :call <SID>RefreshDir()<cr>
156 nnoremap <buffer> R :call <SID>InitWithDir("")<cr>
157 nnoremap <buffer> S :call <SID>StartShell()<cr>
158 nnoremap <buffer> D :call <SID>ToggleDirSort()<cr>
159 nnoremap <buffer> a :call <SID>ToggleHiddenFiles()<cr>
160 nnoremap <buffer> ? :call <SID>ToggleHelp()<cr>
161 nnoremap <buffer> <2-leftmouse> :call <SID>Activate()<cr>
163 command! -buffer -complete=dir -nargs=1 CD :call s:TreeCD('<a>')
164 command! -buffer -range -nargs=0 Yank :<line1>,<line2>y |
165 \ let @" = substitute (@", ' [{}]\{3\}', "", "g")
167 let &cpo = cpo_save1 " restore
169 call s:InitWithDir(fname) " load fname dir
172 "" TreeCD() - change to dir from cmdline arg
173 function! s:TreeCD(dir) " <<<
174 if isdirectory (a:dir)
175 call s:InitWithDir (a:dir)
177 echo "can not change to directory: " . a:dir
181 "" InitWithDir() - reload tree with dir
182 function! s:InitWithDir(dir) " <<<
184 execute "lcd " . escape (a:dir, w:escape_chars)
189 let cwd = substitute (cwd, '\\', '/', "g")
190 let is_root = (cwd =~ '^[A-Z]:/$') ? 1 : 0
192 let is_root = (cwd == "/") ? 1 : 0
195 let cwd = substitute (cwd, '/*$', '/', "")
198 setlocal modifiable | silent! normal ggdG
199 setlocal nomodifiable
207 "insert parent link unless we're at / for unix or X:\ for dos
209 let @f=".. (up a directory)"
211 let @f=@f . "\n" . cwd . "\n\n"
213 setlocal modifiable | silent put f | setlocal nomodifiable
217 call s:ReadDir (line("."), cwd) " read dir
222 "" ReadDir() - read dir after current line with tree pieces and foldmarkers
223 function! s:ReadDir(lpn,dir) " <<<
224 let olddir = getcwd ()
226 let lps = getline (a:lpn)
229 let dir = GetAbsPath2 (lpn, 0)
230 if w:firstdirline ! = lpn
238 " TODO - error when dir no longer exists
240 execute "lcd " . escape (dir, w:escape_chars)
242 echo "ERROR: changing to directory: " . dir
246 """ THIS BLOCK DOESN' DO ANYTHING
247 " change dos path to look like unix path
248 "if has("unix") == 0 " TODO - so many dos/win variants, this seemed easier - maybe not correct (e.g. OS2, mac, etc)
249 " let dir = substitute (dir, '\\', '/', "g")
251 "let dir = substitute (dir, '/\?$', '/', "")
252 """ THIS BLOCK DOESN' DO ANYTHING
255 if w:hidden_files == 1
256 let dirlines = glob ('.*') . "\n" . glob ('*')
258 let dirlines = glob ('*')
261 " if empty, don't change line
266 let treeprt = substitute (lps, '[^-| `].*', "", "")
267 let pdirprt = substitute (lps, '^[-| `]*', "", "")
268 let pdirprt = substitute (pdirprt, '[{} ]*$', "", "")
269 let foldprt = substitute (lps, '.*' . pdirprt, "", "")
271 " save states of registers for restoring
272 " @l is used for first line, last line, and if dir sorting is off
273 " @f and @d are used for file and dirs with dir sorting
274 let save_l = @l | let @l = ""
275 let save_d = @d | let @d = ""
276 let save_f = @f | let @f = ""
278 let @l = treeprt . pdirprt . ' {{{'
280 let treeprt = substitute (treeprt, w:tree_end_wid, w:tree_spc_wid, "")
281 let treeprt = substitute (treeprt, w:tree_dir_wid, w:tree_par_wid, "")
283 " parse dir contents by '/'
284 let dirlines = substitute (dirlines, "\n", '/', "g")
286 while strlen (dirlines) > 0
287 let curdir = substitute (dirlines, '/.*', "", "")
288 let dirlines = substitute (dirlines, '[^/]*/\?', "", "")
290 if w:hidden_files == 1 && curdir =~ '^\.\.\?$'
294 let linkedto = resolve (curdir)
295 if linkedto != curdir
296 let curdir = curdir . ' -> ' . linkedto
298 if isdirectory (linkedto)
300 let curdir = curdir . '/'
305 " escape leading characters confused with tree parts
306 if curdir =~ '^[-| `]'
307 let curdir = '\' . curdir
312 let @d = @d . "\n" . treeprt . w:tree_dir_wid . curdir
314 let @f = @f . "\n" . treeprt . w:tree_dir_wid . curdir
317 let @l = @l . "\n" . treeprt . w:tree_dir_wid . curdir
322 let @l = @l . @d . @f . "\n"
323 elseif w:dirsort == -1
324 let @l = @l . @f . @d . "\n"
331 " TODO handle fold open v fold closed
335 setlocal nomodifiable
337 " make sure fold is open so we don't delete the whole thing
338 "if foldclosed (line (".")) != -1
339 if foldclosed (a:lpn) != -1
345 " change last tree part to the final leaf marking, add final fold mark
346 let @l = getline(".")
347 let @l = substitute (@l, w:tree_dir_wid, w:tree_end_wid, "")
348 let @l = @l . foldprt . " }}}\n"
350 setlocal modifiable | silent normal dd
351 silent put! l | setlocal nomodifiable
360 execute "lcd " . escape (olddir, w:escape_chars)
363 "" ChdirUp() - cd up (if possible)
364 function! s:ChdirUp() " <<<
366 if cwd == "/" || cwd =~ '^[^/]..$'
367 echo "already at top dir"
369 call s:InitWithDir("..")
373 "" MoveParent() - move cursor to parent dir
374 function! s:MoveParent() " <<<
376 call s:GetAbsPath2 (ln, 1)
377 if w:firstdirline != 0
378 exec (":" . w:firstdirline)
380 exec (":" . w:helplines)
384 "" ChangeTop() - change top dir to cursor dir
385 function! s:ChangeTop() " <<<
389 " on current top or non-tree line?
400 let curfile = s:GetAbsPath2(ln, 0)
402 let curfile = substitute (curfile, '[^/]*$', "", "")
404 call s:InitWithDir (curfile)
407 "" RecursiveExpand() - expand cursor dir recursively
408 function! s:RecursiveExpand() " <<<
409 echo "recursively expanding, this might take a while (CTRL-C to stop)"
411 let curfile = s:GetAbsPath2(line("."), 0)
413 if w:firstdirline == 0
414 let init_ln = w:helplines
415 let curfile = substitute (getline (init_ln), '[ {]*', "", "")
417 let init_ln = w:firstdirline
420 let init_ind = match (getline (init_ln), '[^-| `]') / w:tree_wid_ind
422 let curfile = substitute (curfile, '[^/]*$', "", "")
424 let l = getline (init_ln)
427 if foldclosed (init_ln) != -1
432 if l !~ ' {{{$' " dir not open
433 call s:ReadDir (init_ln, curfile)
435 if getline (init_ln) !~ ' {{{$' " dir still not open (empty)
436 echo "expansion done"
445 let match_str = '[^-| `]'
446 while init_ind < (match (l, '[^-| `]') / w:tree_wid_ind)
453 if foldclosed (tln) != -1
460 if tl =~ ' -> ' || tl !~ '/[ }]*$'
464 let curfile = s:GetAbsPath2(tln, 0)
466 call s:ReadDir (tln, curfile)
472 echo "expansion done"
475 "" OpenExplorer() - open file explorer on cursor dir
476 function! s:OpenExplorer() " <<<
477 let curfile = s:GetAbsPath2 (line ("."), 0)
479 if w:firstdirline == 0
480 let curfile = getcwd ()
482 " remove file name, if any
483 let curfile = substitute (curfile, '[^/]*$', "", "")
486 let curfile = escape (curfile, w:escape_chars)
490 if oldwin == winnr() || &modified
492 exec ("new " . curfile)
494 exec ("edit " . curfile)
499 "" Activate() - (un)fold read dirs, read unread dirs, open files, cd .. on ..
500 function! s:Activate() " <<<
504 " parent dir, change to it
505 if l =~ '^\.\. (up a directory)$'
510 " directory loaded, toggle folded state
512 if foldclosed(ln) == -1
520 " on top, no folds, or not on tree
526 let curfile = s:GetAbsPath2 (ln, 0)
528 if curfile =~ '/$' " dir
529 call s:ReadDir (ln, curfile)
532 let f = escape (curfile, w:escape_chars)
535 if oldwin == winnr() || (&modified && s:BufInWindows(winbufnr(winnr())) < 2)
544 "" RefreshDir() - refresh current dir
545 function! s:RefreshDir() " <<<
546 let curfile = s:GetAbsPath2(line("."), 0)
548 let init_ln = w:firstdirline
550 " not in tree, or on path line or parent is top
551 if curfile == "" || init_ln == 0
552 call s:InitWithDir("")
558 " remove file name, if any
559 let curfile = substitute (curfile, '[^/]*$', "", "")
561 let @l = getline (init_ln)
563 " if there is no fold, just do normal ReadDir, and return
565 call s:ReadDir (init_ln, curfile)
572 if foldclosed(init_ln) == -1
576 " remove one foldlevel from line
577 let @l = substitute (@l, ' {{{$', "", "")
584 setlocal nomodifiable
586 call s:ReadDir (init_ln, curfile)
591 "" ToggleHiddenFiles() - toggle hidden files
592 function! s:ToggleHiddenFiles() " <<<
593 let w:hidden_files = w:hidden_files ? 0 : 1
594 let msg = w:hidden_files ? "on" : "off"
595 let msg = "hidden files now = " . msg
597 call s:UpdateHeader ()
601 "" ToggleDirSort() - toggle dir sorting
602 function! s:ToggleDirSort() " <<<
605 let msg = "dirs first"
608 let msg = "dirs last"
613 let msg = "dirs sorting now = " . msg
615 call s:UpdateHeader ()
619 "" StartShell() - start shell in cursor dir
620 function! s:StartShell() " <<<
623 let curfile = s:GetAbsPath2 (ln, 1)
624 let prevdir = getcwd()
626 if w:firstdirline == 0
629 let dir = substitute (curfile, '[^/]*$', "", "")
632 execute "lcd " . escape (dir, w:escape_chars)
634 execute "lcd " . escape (prevdir, w:escape_chars)
637 "" GetAbsPath2() - get absolute path at line ln, set w:firstdirline,
638 "" - if ignore_current is 1, don't set line to current line when on a dir
639 function! s:GetAbsPath2(ln,ignore_current) " <<<
641 let l = getline(lnum)
643 let w:firstdirline = 0
645 " in case called from outside the tree
646 if l =~ '^[/".]' || l =~ '^$'
653 let curfile = substitute (l,'^[-| `]*',"","") " remove tree parts
654 let curfile = substitute (curfile,'[ {}]*$',"",'') " remove fold marks
655 "let curfile = substitute (curfile,'[*=@|]$',"","") " remove file class
657 " remove leading escape
658 let curfile = substitute (curfile,'^\\', "", "")
660 if curfile =~ '/$' && a:ignore_current == 0
662 let w:firstdirline = lnum
665 let curfile = substitute (curfile,' -> .*',"","") " remove link to
667 let curfile = substitute (curfile, '/\?$', '/', "")
670 let indent = match(l,'[^-| `]') / w:tree_wid_ind
675 let lp = getline(lnum)
677 let sd = substitute (lp, '[ {]*$', "", "")
682 let lpindent = match(lp,'[^-| `]') / w:tree_wid_ind
684 if w:firstdirline == 0
685 let w:firstdirline = lnum
687 let indent = indent - 1
688 let sd = substitute (lp, '^[-| `]*',"","") " rm tree parts
689 let sd = substitute (sd, '[ {}]*$', "", "") " rm foldmarks
690 let sd = substitute (sd, ' -> .*','/',"") " replace link to with /
692 " remove leading escape
693 let sd = substitute (sd,'^\\', "", "")
700 let curfile = dir . curfile
704 "" ToggleHelp() - toggle between long and short help
705 function! s:ToggleHelp() " <<<
706 let w:helplines = (w:helplines <= 4) ? 6 : 0
707 call s:UpdateHeader ()
710 "" Determine the number of windows open to this buffer number.
711 "" Care of Yegappan Lakshman. Thanks!
712 fun! s:BufInWindows(bnum) " <<<
716 let bufnum = winbufnr(winnum)
723 let winnum = winnum + 1
729 "" UpdateHeader() - update the header
730 function! s:UpdateHeader() " <<<
737 setlocal modifiable | silent! 1,/^" ?/ d _ | setlocal nomodifiable
741 " return to previous mark
750 "" - AddHeader() - add the header with help information
751 function! s:AddHeader() " <<<
754 elseif w:dirsort == 1
755 let dt = "dirs first)\n"
757 let dt = "dirs last)\n"
764 let ln=ln+1 | let @f= "\" <ret> = same as 'o' below\n"
765 let ln=ln+1 | let @f=@f."\" o = (file) open in another window\n"
766 let ln=ln+1 | let @f=@f."\" o = (dir) toggle dir fold or load dir\n"
767 let ln=ln+1 | let @f=@f."\" X = recursive expand cursor dir\n"
768 let ln=ln+1 | let @f=@f."\" E = open Explorer on cursor dir\n"
769 let ln=ln+1 | let @f=@f."\" C = chdir top of tree to cursor dir\n"
770 let ln=ln+1 | let @f=@f."\" H = chdir top of tree to home dir\n"
771 let ln=ln+1 | let @f=@f."\" u = chdir top of tree to parent dir\n"
772 let ln=ln+1 | let @f=@f."\" :CD d = chdir top of tree to dir <d>\n"
773 let ln=ln+1 | let @f=@f."\" p = move cursor to parent dir\n"
774 let ln=ln+1 | let @f=@f."\" r = refresh cursor dir\n"
775 let ln=ln+1 | let @f=@f."\" R = refresh top dir\n"
776 let ln=ln+1 | let @f=@f."\" S = start a shell in cursor dir\n"
777 let ln=ln+1 | let @f=@f."\" :Yank = yank <range> lines withoug fold marks\n"
778 let ln=ln+1 | let @f=@f."\" D = toggle dir sort (now = " . dt
779 let ln=ln+1 | let @f=@f."\" a = toggle hidden files (now = "
780 \ . ((w:hidden_files) ? "on)\n" : "off)\n")
781 let ln=ln+1 | let @f=@f."\" ? = toggle long help\n"
783 let ln=ln+1 | let @f="\" ? : toggle long help\n"
787 setlocal modifiable | silent put! f | setlocal nomodifiable
792 let &cpo = s:cpo_save
794 " vim: set ts=2 sw=2 foldmethod=marker foldmarker=<<<,>>> foldlevel=2 :