" Author: Eric Van Dewoestine
"
" License: {{{
"
" Copyright (C) 2005 - 2014 Eric Van Dewoestine
"
" This program is free software: you can redistribute it and/or modify
" it under the terms of the GNU General Public License as published by
" the Free Software Foundation, either version 3 of the License, or
" (at your option) any later version.
"
" This program is distributed in the hope that it will be useful,
" but WITHOUT ANY WARRANTY; without even the implied warranty of
" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
" GNU General Public License for more details.
"
" You should have received a copy of the GNU General Public License
" along with this program. If not, see .
"
" }}}
" Global Variables {{{
if !exists("g:TreeDirHighlight")
let g:TreeDirHighlight = "Statement"
endif
if !exists("g:TreeFileHighlight")
let g:TreeFileHighlight = "Normal"
endif
if !exists("g:TreeFileExecutableHighlight")
let g:TreeFileExecutableHighlight = "Constant"
endif
if !exists("g:TreeActionHighlight")
let g:TreeActionHighlight = "Statement"
endif
if !exists('g:TreeExpandSingleDirs')
let g:TreeExpandSingleDirs = 0
endif
if !exists('g:TreeIndent')
let g:TreeIndent = 4
endif
if g:TreeIndent < 2
call eclim#util#EchoWarning('g:TreeIndent cannot be less than 2.')
let g:TreeIndent = 2
endif
" }}}
" Script Variables {{{
let s:node_prefix = ''
let index = 0
while index < (g:TreeIndent - 2)
let s:node_prefix .= ' '
let index += 1
endwhile
let s:dir_opened_prefix = '- '
let s:dir_closed_prefix = '+ '
let s:file_prefix = ' '
let s:indent_length = len(s:node_prefix) + len(s:file_prefix)
let s:node_regex = s:node_prefix . '\(' .
\ s:dir_opened_prefix . '\|' .
\ s:dir_closed_prefix . '\|' .
\ s:file_prefix . '\)'
" \1 - indent, \2, node prefix + element prefix, \3 name
let s:nodevalue_regex = '\(\s*\)' . s:node_regex . '\(.*\)'
let s:root_regex = '^[/[:alpha:]]'
let s:settings_loaded = 0
let s:tree_count = 0
let s:refresh_nesting = 0
let s:has_ls = executable('ls') && !(has('win32') || has('win64'))
let s:vcol = 0
" }}}
function! eclim#tree#TreeHome() " {{{
let name = "Tree"
if s:tree_count > 0
let name .= s:tree_count
endif
let s:tree_count += 1
call eclim#tree#Tree(name, [eclim#UserHome()], [], 1, [])
endfunction " }}}
function! eclim#tree#TreePanes() " {{{
call eclim#tree#TreeHome()
vertical new
call eclim#tree#TreeHome()
1winc w
endfunction " }}}
function! eclim#tree#Tree(name, roots, aliases, expand, filters) " {{{
" name - The name to use for the tree buffer.
" roots - List of paths to use as tree roots.
" aliases - List of aliases for root paths, or an empty list for no aliasing.
" expand - 1 to pre expand the root directories, 0 otherwise.
" filters - List of file name patterns to include in directory listings, or an
" empty list for no filtering.
silent exec 'edit ' . escape(a:name, ' ')
setlocal ft=tree
setlocal nowrap
setlocal noswapfile
setlocal nobuflisted
setlocal buftype=nofile
setlocal bufhidden=wipe
setlocal foldmethod=manual
setlocal foldtext=getline(v:foldstart)
setlocal sidescrolloff=0
call s:Mappings()
call eclim#tree#Syntax()
" initialize autocmds before loading custom settings so that settings can
" add autocmd events.
augroup eclim_tree
"autocmd! BufEnter,User
autocmd BufEnter silent doautocmd eclim_tree User
exec 'autocmd BufDelete,BufUnload ' .
\ 'autocmd! eclim_tree * '
augroup END
" register setting prior to listing any directories
if exists("g:TreeSettingsFunction")
let l:Settings = function(g:TreeSettingsFunction)
call l:Settings()
let s:settings_loaded = 1
endif
setlocal noreadonly modifiable
silent 1,$delete _
let roots = map(copy(a:roots), 'substitute(v:val, "\\([^/]\\)$", "\\1/", "")')
let [roots, _] = s:NormalizeEntries(roots)
let b:roots = copy(roots)
let b:filters = a:filters
let b:view_hidden = 0
if len(a:aliases) > 0
let b:aliases = {}
let index = 0
for alias in a:aliases
if alias != ''
let b:aliases[alias] = roots[index]
endif
let index += 1
endfor
call map(roots, 's:PathToAlias(v:val)')
endif
call append(line('$'), roots)
if a:expand
let index = len(roots)
while index > 0
call cursor(index + 1, 1)
call eclim#tree#ExpandDir()
let index = index - 1
endwhile
endif
" delete empty first line.
setlocal modifiable
1,1delete _
setlocal nomodifiable
endfunction " }}}
function! eclim#tree#ToggleCollapsedDir(Expand) " {{{
if eclim#tree#GetPath() =~ '/$'
if getline('.') =~ '\s*' . s:node_prefix . s:dir_closed_prefix ||
\ (getline('.') =~ s:root_regex && eclim#tree#GetLastChildPosition() == line('.'))
call a:Expand()
else
call s:CollapseDir()
endif
endif
endfunction " }}}
function! eclim#tree#ToggleFoldedDir(Expand) " {{{
if eclim#tree#GetPath() =~ '/$'
if foldclosed(line('.')) != -1
call s:UnfoldDir()
elseif getline('.') =~ '\s*' . s:node_prefix . s:dir_opened_prefix ||
\ (getline('.') =~ s:root_regex && eclim#tree#GetLastChildPosition() != line('.'))
call s:FoldDir()
else
call a:Expand()
endif
endif
endfunction " }}}
function! eclim#tree#ToggleViewHidden() " {{{
let b:view_hidden = (b:view_hidden + 1) % 2
let line = getline('.')
let path = eclim#tree#GetPath()
call cursor(1, 1)
call eclim#tree#Refresh()
while search(s:root_regex, 'W') != 0
call eclim#tree#Refresh()
endwhile
call cursor(1, 1)
while search(line, 'W') != 0 && eclim#tree#GetPath() != path
endwhile
call eclim#tree#Cursor(line, 0)
endfunction " }}}
function! eclim#tree#GetFileInfo(file) " {{{
if executable('ls')
return split(eclim#util#System("ls -ld '" . a:file . "'"), '\n')[0]
endif
return ''
endfunction "}}}
function! eclim#tree#GetPath(...) " {{{
" Optional args:
" resolve_links
let line = getline('.')
let node = substitute(line, s:nodevalue_regex, '\3', '')
let node = eclim#tree#GetParent(a:0 ? a:1 : 1) . node
let path = s:AliasToPath(node)
" handle symbolic links
if path =~ '->'
if !a:0 || a:1 " resolve links
let link = substitute(path, '.* -> \(.*\)', '\1', '')
if link !~ '^/' && link !~ '^[a-zA-Z]:/'
let parent = substitute(path, '\(.*\) -> .*', '\1', '')
let parent = fnamemodify(substitute(parent, '/$', '', ''), ':h')
let link = parent . '/' . link
endif
let path = link
else
let path = substitute(path, '\(.*\) -> .*', '\1', '')
if node =~ '/$'
let path .= '/'
endif
endif
endif
" handle executable files.
if path =~ '\*$'
let path = strpart(path, 0, len(path) - 1)
endif
return path
endfunction "}}}
function! eclim#tree#GetParent(...) " {{{
" Optional args:
" resolve_links
let parent = ''
let lnum = eclim#tree#GetParentPosition()
if lnum
let pos = getpos('.')
call cursor(lnum, 1)
let parent = eclim#tree#GetPath(a:0 ? a:1 : 1)
call setpos('.', pos)
endif
return parent
endfunction " }}}
function! eclim#tree#GetParentPosition() " {{{
let lnum = 0
let line = getline('.')
if line =~ '\s*' . s:node_prefix
if line =~ '^' . s:node_regex . '\S'
let search = s:root_regex
else
let search = '^'
let index = 0
let indent = s:GetIndent(line('.'))
while index < indent - s:indent_length
let search .= ' '
let index += 1
endwhile
let search .= s:node_prefix . s:dir_opened_prefix
endif
let lnum = search(search, 'bnW')
" hack: most likely one of:
" - refresh from comment line
" - refresh from first level dir/file with g:TreeIndent < 4
if lnum == 0 && search != s:root_regex
let lnum = search(s:root_regex, 'bnW')
endif
endif
return lnum
endfunction " }}}
function! eclim#tree#GetLastChildPosition() " {{{
let line = getline('.')
" a root node
if line =~ s:root_regex
let lnum = search(s:root_regex, 'nW')
return lnum > 0 ? lnum - 1 : s:GetLastLine()
endif
" non root node
let sibling = '^' .
\ substitute(line, s:nodevalue_regex, '\1' . escape(s:node_regex. '[.[:alnum:]_]', '\'), '')
let lnum = line('.') + 1
let indent = s:GetIndent(line('.'))
while getline(lnum) !~ sibling &&
\ s:GetIndent(lnum) >= indent &&
\ lnum != s:GetLastLine()
let lnum += 1
endwhile
" back up one if on a node of equal or less depth
if s:GetIndent(lnum) <= indent
let lnum -= 1
endif
" no sibling below, use parent's value
if lnum == line('.') && getline(lnum + 1) !~ sibling
let pos = getpos('.')
call cursor(eclim#tree#GetParentPosition(), 1)
let lnum = eclim#tree#GetLastChildPosition()
call setpos('.', pos)
endif
return lnum
endfunction " }}}
function! eclim#tree#Execute(alt) " {{{
if getline('.') =~ '^"\|^\s*$'
return
endif
let path = eclim#tree#GetPath()
" execute action on dir
if path =~ '/$'
if a:alt || foldclosed(line('.')) != -1
call eclim#tree#ToggleFoldedDir(function('eclim#tree#ExpandDir'))
else
call eclim#tree#ToggleCollapsedDir(function('eclim#tree#ExpandDir'))
endif
" execute action on file
else
if !filereadable(path)
echo "File is not readable or has been deleted."
endif
let actions = eclim#tree#GetFileActions(path)
if len(actions) == 0
echo "No registered actions for file: " . path
return
endif
if a:alt
call eclim#tree#DisplayActionChooser(
\ path, actions, 'eclim#tree#ExecuteAction')
else
call eclim#tree#ExecuteAction(path, actions[0].action)
endif
endif
endfunction " }}}
function! eclim#tree#ExecuteAction(file, command) " {{{
let file = eclim#util#Simplify(a:file)
let file = escape(file, ' &()')
let file = escape(file, ' &()') " need to double escape
let file = escape(file, '&') " '&' needs to be escaped 3 times.
let command = a:command
let command = substitute(command, '', file, 'g')
if command =~ '^!\w'
silent call eclim#util#Exec(command)
redraw!
else
exec command
endif
if command =~ '^!\w' && v:shell_error
call eclim#util#EchoError('Error executing command: ' . command)
endif
endfunction " }}}
function! eclim#tree#RegisterFileAction(regex, name, action) " {{{
" regex - Pattern to match the file name against.
" name - Name of the action used for display purposes.
" action - The action to execute where is replaced with the filename.
if !exists('b:file_actions')
let b:file_actions = []
endif
let entry = {}
for e in b:file_actions
if e.regex == a:regex
let entry = e
break
endif
endfor
if len(entry) == 0
let entry = {'regex': a:regex, 'actions': []}
call add(b:file_actions, entry)
endif
call add(entry.actions, {'name': a:name, 'action': a:action})
endfunction " }}}
function! eclim#tree#RegisterDirAction(action) " {{{
" action - A funcref which will be invoked when expanding a directory with the
" directory path and a mutable list of current directory contents.
if !exists('b:dir_actions')
let b:dir_actions = []
endif
call add(b:dir_actions, a:action)
endfunction " }}}
function! eclim#tree#GetFileActions(file) " {{{
" Returns a list of dictionaries with keys 'name' and 'action'.
let actions = []
let thefile = tolower(a:file)
let bufnr = bufnr('%')
for entry in b:file_actions
if thefile =~ entry.regex
let actions += entry.actions
endif
endfor
return actions
endfunction " }}}
function! eclim#tree#Shell(external) " {{{
" Opens a shell either in the current vim session or externally.
let path = eclim#tree#GetPath()
if !isdirectory(path)
let path = fnamemodify(path, ':h')
endif
let cwd = getcwd()
silent exec "lcd " . escape(path, ' &')
if a:external
if !exists("g:TreeExternalShell")
echo "No external shell configured via 'g:TreeExternalShell' variable."
else
silent call eclim#util#Exec(g:TreeExternalShell)
redraw!
endif
else
shell
endif
silent exec "lcd " . escape(cwd, ' &')
endfunction " }}}
function! eclim#tree#Cursor(line, prevline) " {{{
let lnum = a:line
let line = getline(lnum)
if line =~ s:root_regex
call cursor(lnum, 1)
else
" get the starting column of the current line and the previous line
let start = len(line) - len(substitute(line, '^\s\+\W', '', ''))
" only use the real previous line if we've only moved one line
let moved = a:prevline - lnum
if moved < 0
let moved = -moved
endif
let pline = moved == 1 ? getline(a:prevline) : ''
let pstart = pline != '' ?
\ len(pline) - len(substitute(pline, '^\s\+\W', '', '')) : -1
" only change the cursor column if the hasn't user has moved it to the
" right to view more of the entry
let cnum = start == pstart ? 0 : start
call cursor(lnum, cnum)
" attempt to maximize the amount of text on the current line that is in
" view, but only if we've changed column position
let winwidth = winwidth(winnr())
let vcol = exists('s:vcol') ? s:vcol : 0
let col = col('.')
if cnum != 0 && (!vcol || ((len(line) - vcol) > winwidth))
if len(line) > winwidth
normal! zs
" scroll back enough to keep the start of the parent in view
normal! 6zh
let s:vcol = col - 6
endif
endif
" when the text view is shifted by vim it appears to always shift back one
" half of the window width, so recalculate our visible column accordingly
" if we detect such a shift... may not always be accurate.
if s:vcol > col
let s:vcol = max([start - (winwidth / 2), 0])
endif
endif
endfunction " }}}
function! eclim#tree#GetRoot() " {{{
if getline('.') =~ s:root_regex
return s:AliasToPath(getline('.'))
endif
let start = search(s:root_regex, 'bcnW')
return s:AliasToPath(getline(start))
endfunction " }}}
function! eclim#tree#SetRoot(path) " {{{
let path = s:AliasToPath(a:path)
let path = s:NormalizeEntries([fnamemodify(path, ':p')])[0][0]
if !isdirectory(path)
echo 'Directory does not exist or may have been deleted.'
return
endif
let path = s:PathToAlias(path)
" if on a root node
if getline('.') =~ s:root_regex
let start = line('.')
" not on a root node
else
let start = search(s:root_regex, 'bW')
endif
let end = eclim#tree#GetLastChildPosition()
setlocal noreadonly modifiable
silent exec start . ',' . end . 'delete _'
let line = line('.')
if line == 1
let line = 0
endif
call append(line, path)
" delete blank first line if any
if getline(1) =~ '^$'
silent 1,1delete _
endif
" delete blank last line if any
if getline('$') =~ '^$'
silent exec line('$') . ',' . line('$') . 'delete _'
endif
call cursor(line + 1, 1)
call eclim#tree#ExpandDir()
setlocal nomodifiable
endfunction " }}}
function! eclim#tree#Refresh() " {{{
" FIXME: in need of a serious rewrite (probably need to rewrite the whole
" plugin)
let ignore_pattern = ''
if &wildignore != ''
let ignore_pattern = substitute(escape(&wildignore, '.'), '\*', '.*', 'g')
let ignore_pattern = '\(' . join(split(ignore_pattern, ','), '\|') . '\)$'
endif
let clnum = line('.')
let ccnum = col('.')
let startpath = eclim#tree#GetPath()
if s:refresh_nesting == 0
let s:startpath = startpath
" let vim track shifts in line numbers with a mark
mark Z
endif
" if on a file or closed directory, refresh it's parent
if startpath !~ '/$' ||
\ getline('.') =~ '^\s*' . s:node_prefix . s:dir_closed_prefix
call cursor(eclim#tree#GetParentPosition(), 1)
let startpath = eclim#tree#GetPath()
endif
let start = line('.')
let end = eclim#tree#GetLastChildPosition()
" first check the node we are on
if (!isdirectory(startpath) && !filereadable(startpath)) ||
\ (getline('.') !~ s:root_regex && s:IsHidden(startpath, ignore_pattern))
setlocal modifiable
silent exec start . ',' . end . 'delete _'
setlocal nomodifiable
silent doautocmd eclim_tree User
return
endif
if s:refresh_nesting == 0
call eclim#util#Echo('Refreshing...')
endif
let s:refresh_nesting += 1
" move cursor to first child
call cursor(start + 1, 1)
" get pattern to use to match children.
let match = substitute(getline('.'), '^' . s:nodevalue_regex, '\1', '')
let match = '^' . match . s:node_regex . '[.[:alnum:]_]'
" walk the tree
let lnum = line('.')
while lnum <= end && lnum <= s:GetLastLine()
let line = getline('.')
" open dir that needs to be refreshed as well.
if line =~ '\s*' . s:node_prefix . s:dir_opened_prefix
call eclim#tree#Refresh()
let lnum = eclim#tree#GetLastChildPosition()
let ldiff = lnum - line('.')
let end += ldiff
call cursor(lnum, 1)
endif
let path = eclim#tree#GetPath()
" delete files, and dirs that do not exist, or are hidden.
if (path =~ '/$' && !isdirectory(path)) ||
\ (path !~ '/$' && !filereadable(path)) ||
\ s:IsHidden(path, ignore_pattern)
let last = eclim#tree#GetLastChildPosition()
setlocal modifiable
silent exec lnum . ',' . last . 'delete _'
setlocal nomodifiable
let end -= (last - lnum) + 1
continue
endif
let lnum += 1
call cursor(lnum, 1)
endwhile
call cursor(start + 1, ccnum)
" merge in any dirs that have been added
let contents = eclim#tree#ListDir(startpath)
let [dirs, files] = s:NormalizeEntries(contents)
let contents = dirs + files
let root = eclim#tree#GetRoot()
let indent = eclim#tree#GetChildIndent(start)
let lnum = line('.')
setlocal modifiable
for entry in contents
let path = eclim#tree#GetPath(0)
let path_link = eclim#tree#GetPath(0)
let rewrote = s:RewriteSpecial(entry)
let norm_entry = substitute(entry, '[*@]$', '', '')
if rewrote =~ '/$' && norm_entry !~ '/$'
let norm_entry .= '/'
endif
" ugly
if exists('b:links')
for [link, target] in items(b:links)
if path =~ '^' . root . link
let path = substitute(path, root . link, target, '')
break
endif
endfor
endif
if path != norm_entry && path_link != norm_entry
" if we are adding a new entry we'll just add one that has the correct
" index + prefix and let the next block set the proper display path.
if s:MatchesFilter(norm_entry)
if isdirectory(entry)
let initial = fnamemodify(substitute(entry, '/$', '', ''), ':t') . '/'
else
let initial = fnamemodify(entry, ':t')
endif
if index(dirs, entry) != -1
let display_entry = indent . s:node_prefix . s:dir_closed_prefix . initial
else
let display_entry = indent . s:node_prefix . s:file_prefix . initial
endif
if lnum <= s:GetLastLine()
call append(lnum - 1, display_entry)
else
call append(s:GetLastLine(), display_entry)
endif
call cursor(lnum, 0)
endif
endif
let parent = eclim#tree#GetParent(0)
let parent_link = eclim#tree#GetParent()
if index(dirs, entry) != -1
let dir_prefix = s:dir_closed_prefix
if getline(lnum) =~ '\s*' . s:node_prefix . s:dir_opened_prefix
let dir_prefix = s:dir_opened_prefix
endif
let display_entry = indent . s:node_prefix . dir_prefix .
\ substitute(substitute(rewrote, parent, '', ''), parent_link, '', '')
else
let display_entry = indent . s:node_prefix . s:file_prefix .
\ substitute(substitute(rewrote, parent, '', ''), parent_link, '', '')
endif
call setline(lnum, display_entry)
if getline(lnum) =~ '\s*' . s:node_prefix . s:dir_opened_prefix
call cursor(eclim#tree#GetLastChildPosition() + 1, 1)
let lnum = line('.')
else
let lnum += 1
call cursor(lnum, 1)
endif
endfor
setlocal nomodifiable
call cursor(clnum, ccnum)
let s:refresh_nesting -= 1
if s:refresh_nesting == 0
call eclim#util#Echo(' ')
" return to marked position.
call cursor(line("'Z"), col("`Z"))
" if the entry that we started on is gone, move the cursor up a line.
if s:startpath != eclim#tree#GetPath()
call cursor(line('.') - 1, col('.'))
endif
endif
silent doautocmd eclim_tree User
endfunction " }}}
function! eclim#tree#MoveToLastChild() " {{{
mark '
if getline('.') !~ '^\s*' . s:node_prefix . s:dir_opened_prefix . '[.[:alnum:]_]'
call cursor(eclim#tree#GetParentPosition(), 1)
endif
call eclim#tree#Cursor(eclim#tree#GetLastChildPosition(), 0)
endfunction " }}}
function! eclim#tree#MoveToParent() " {{{
mark '
call eclim#tree#Cursor(eclim#tree#GetParentPosition(), 0)
endfunction " }}}
function! eclim#tree#Mkdir() " {{{
let path = eclim#tree#GetPath()
if !isdirectory(path)
let path = fnamemodify(path, ':h') . '/'
endif
let response = input('mkdir: ', path, 'dir')
if response == '' || response == path
return
endif
" work around apparent vim bug attempting to create a dir with a trailing
" slash.
if response[-1:] == '/'
let response = response[:-2]
endif
call mkdir(response, 'p')
call eclim#tree#Refresh()
endfunction " }}}
function! s:AliasToPath(alias) " {{{
if exists('b:aliases')
let alias = ''
for alias in keys(b:aliases)
if alias != '' && a:alias =~ '^' . alias . '\>/'
return substitute(a:alias, '^' . alias . '/', b:aliases[alias], '')
endif
endfor
endif
return a:alias
endfunction " }}}
function! s:PathToAlias(path) " {{{
if exists('b:aliases')
let path = ''
for alias in keys(b:aliases)
let path = b:aliases[alias]
if alias != '' && a:path =~ '^' . path
return substitute(a:path, '^' . path, alias . '/', '')
endif
endfor
endif
return a:path
endfunction " }}}
function! s:Depth() " {{{
return len(split(eclim#tree#GetPath(), '/'))
endfunction " }}}
function! eclim#tree#ExpandDir() " {{{
let dir = eclim#tree#GetPath()
if !isdirectory(dir)
echo "Not a directory or directory may have been removed."
return
endif
let contents = eclim#tree#ListDir(dir)
let [dirs, files] = s:NormalizeEntries(contents)
if s:has_ls
call map(dirs, 'substitute(v:val, "@$", "", "")')
call map(files, 'substitute(v:val, "@$", "", "")')
endif
" filter files
let filtered = []
for file in files
if s:MatchesFilter(file)
call add(filtered, file)
endif
endfor
let files = filtered
" rewrite any special files (executables, symbolic links, etc).
call map(dirs, 's:RewriteSpecial(v:val)')
call map(files, 's:RewriteSpecial(v:val)')
call eclim#tree#WriteContents(dir, dirs, files)
if g:TreeExpandSingleDirs && len(files) == 0 && len(dirs) == 1 && s:Depth() < 50
TreeNextPrevLine j
call eclim#tree#ExpandDir()
endif
endfunction " }}}
function! eclim#tree#ExpandPath(name, path) " {{{
" Given the buffer name of a tree and a full path in that tree, either with an
" alias or real root path at the beginning, expand the tree node to reveal
" that path.
let winnr = winnr()
let treewin = bufwinnr(a:name)
if treewin == -1
return
endif
exec treewin . 'winc w'
let path = a:path
let root = ''
for r in b:roots
let r = substitute(r, '/$', '', '')
if path =~ '^' . r . '\>'
let root = r
break
endif
endfor
" try aliases
if root == ''
let path = substitute(path, '^/', '', '')
for r in keys(b:aliases)
if path =~ '^' . r . '\>'
let root = r
break
endif
endfor
endif
if root != ''
let path = substitute(path, '^' . root, '', '')
call cursor(1, 1)
for dir in split(path, '/')
call eclim#tree#MoveToLastChild()
let line = search('[+-] \<' . dir . '\>/', 'nbW')
if line
call eclim#tree#Cursor(line, 0)
if getline(line) =~ '^\s*+'
call eclim#tree#Execute(1)
endif
else
break
endif
endfor
endif
exec winnr . 'winc w'
endfunction " }}}
function! eclim#tree#WriteContents(dir, dirs, files) " {{{
let dirs = a:dirs
let files = a:files
let indent = eclim#tree#GetChildIndent(line('.'))
call map(dirs,
\ 'substitute(v:val, a:dir, indent . s:node_prefix . s:dir_closed_prefix, "")')
call map(files,
\ 'substitute(v:val, a:dir, indent . s:node_prefix . s:file_prefix, "")')
" update current line
call s:UpdateLine(s:node_prefix . s:dir_closed_prefix,
\ s:node_prefix . s:dir_opened_prefix)
setlocal noreadonly modifiable
let content = dirs + files
call append(line('.'), content)
setlocal nomodifiable
return content
endfunction " }}}
function! s:RewriteSpecial(file) " {{{
let file = a:file
if s:has_ls
let info = ''
let file = substitute(file, '@$', '', '')
" symbolic links
let tmpfile = file =~ '/$' ? strpart(file, 0, len(file) - 1) : file
if getftype(tmpfile) == 'link'
if info == ''
let info = eclim#util#System('ls -ldF ' . tmpfile)
endif
let linkto = substitute(info, '.*-> \(.*\)\n', '\1', '')
if linkto =~ '//$'
let linkto = strpart(linkto, 0, len(linkto) - 1)
endif
let file = tmpfile . ' -> ' . linkto
endif
endif
if exists('b:links')
if file =~ '/$'
let path = substitute(file, '/$', '', '')
let entry = substitute(path, eclim#tree#GetRoot(), '', '')
if has_key(b:links, entry)
let file = path . ' -> ' . get(b:links, entry)
let file = substitute(file, '\([^/]\)$', '\1/', '')
endif
endif
endif
return file
endfunction " }}}
function! s:CollapseDir() " {{{
" update current line
call s:UpdateLine(s:node_prefix . s:dir_opened_prefix,
\ s:node_prefix . s:dir_closed_prefix)
let lnum = line('.')
let cnum = col('.')
let start = lnum + 1
let end = eclim#tree#GetLastChildPosition()
if start > end
return
endif
setlocal noreadonly modifiable
silent exec start . ',' . end . 'delete _'
setlocal nomodifiable
call cursor(lnum, cnum)
endfunction " }}}
function! s:UnfoldDir() " {{{
foldopen
endfunction " }}}
function! s:FoldDir() " {{{
let start = line('.')
let end = eclim#tree#GetLastChildPosition()
exec start . ',' . end . 'fold'
endfunction " }}}
function! eclim#tree#ListDir(dir, ...) " {{{
" Optional args:
" execute_actions
if s:has_ls
let ls = 'ls -1F'
if b:view_hidden
let ls .= 'A'
endif
let contents = split(eclim#util#System(ls . " '" . a:dir . "'"), '\n')
if !b:view_hidden && &wildignore != ''
let pattern = substitute(escape(&wildignore, '.~'), '\*', '.*', 'g')
let pattern = '\(' . join(split(pattern, ','), '\|') . '\)$'
" Note: symlinks have a trailing @, so remove that before comparing
" against pattern
call filter(contents, 'substitute(v:val, "@$", "", "") !~ pattern')
endif
call map(contents, 'a:dir . v:val')
else
if !b:view_hidden
let contents = split(eclim#util#Globpath(escape(a:dir, ','), '*', 1), '\n')
else
let contents = split(eclim#util#Globpath(escape(a:dir, ','), '*'), '\n')
let contents = split(eclim#util#Globpath(escape(a:dir, ','), '.*'), '\n') + contents
endif
" append trailing '/' to dirs if necessary
call map(contents,
\ 'isdirectory(v:val) ? substitute(v:val, "\\([^/]\\)$", "\\1/", "") : v:val')
endif
if exists('b:dir_actions') && (!a:0 || a:1)
for l:Action in b:dir_actions
call l:Action(a:dir, contents)
endfor
endif
return contents
endfunction " }}}
function! s:GetIndent(line) " {{{
let indent = indent(a:line)
if getline(a:line) =~ s:file_prefix . '[.[:alnum:]_]' && s:file_prefix =~ '^\s*$'
let indent -= len(s:file_prefix)
endif
if s:node_prefix =~ '^\s*$'
let indent -= len(s:node_prefix)
endif
return indent
endfunction " }}}
function! s:GetLastLine() " {{{
let line = line('$')
while getline(line) =~ '^"\|^\s*$' && line > 1
let line -= 1
endwhile
return line
endfunction " }}}
function! eclim#tree#GetChildIndent(line) " {{{
let indent = ''
if getline(a:line) =~ '\s*' . s:node_prefix
let num = indent(a:line)
if s:node_prefix =~ '^\s*$'
let num -= len(s:node_prefix)
endif
let index = 0
while index < num + s:indent_length
let indent .= ' '
let index += 1
endwhile
endif
return indent
endfunction " }}}
function! s:MatchesFilter(file) " {{{
if len(b:filters) > 0
for filter in b:filters
if entry =~ filter
return 1
endif
endfor
return 0
endif
return 1
endfunction " }}}
function! s:IsHidden(path, ignore_pattern) " {{{
if !b:view_hidden
let path = a:path
if isdirectory(path)
let path = fnamemodify(path, ':h')
endif
let path = fnamemodify(path, ':t')
return path =~ '^\.' || (a:ignore_pattern != '' && path =~ a:ignore_pattern)
endif
return 0
endfunction " }}}
function! s:NormalizeEntries(dirs) " {{{
" normalize path separators
call map(a:dirs, 'substitute(v:val, "\\\\", "/", "g")')
let dirs = filter(copy(a:dirs),
\ 'v:val =~ "/$" || (v:val =~ "@$" && isdirectory(substitute(v:val, "@$", "", "")))')
let files = filter(copy(a:dirs), 'index(dirs, v:val) == -1')
return [dirs, files]
endfunction " }}}
function! s:UpdateLine(pattern, substitution) " {{{
let lnum = line('.')
let line = getline(lnum)
let line = substitute(line, a:pattern, a:substitution, '')
setlocal noreadonly modifiable
call append(lnum, line)
silent exec lnum . ',' . lnum . 'delete _'
setlocal nomodifiable
endfunction " }}}
function! eclim#tree#DisplayActionChooser(file, actions, executeFunc) " {{{
keepalt new
let height = len(a:actions) + 1
exec 'resize ' . height
setlocal noreadonly modifiable
let b:actions = a:actions
let b:file = a:file
for action in a:actions
call append(line('$'), action.name)
endfor
exec 'nnoremap ' .
\ ':call eclim#tree#ActionExecute("' . a:executeFunc . '")'
nnoremap q :q
exec "hi link TreeAction " . g:TreeActionHighlight
syntax match TreeAction /.*/
1,1delete _
setlocal nomodifiable
setlocal noswapfile
setlocal buftype=nofile
setlocal bufhidden=wipe
endfunction "}}}
function! eclim#tree#ActionExecute(executeFunc) " {{{
let command = ''
let line = getline('.')
for action in b:actions
if action.name == line
let command = action.action
break
endif
endfor
let file = b:file
close
call function(a:executeFunc)(file, command)
endfunction "}}}
function! s:Mappings() " {{{
nnoremap :call eclim#tree#Execute(0)
nnoremap o :call eclim#tree#Execute(1)
nnoremap i :call eclim#util#Echo(
\ eclim#tree#GetFileInfo(eclim#tree#GetPath()))
nnoremap I :call eclim#util#Echo(
\ eclim#tree#GetFileInfo(eclim#tree#GetPath()))
nnoremap s :call eclim#tree#Shell(0)
nnoremap S :call eclim#tree#Shell(1)
nnoremap R :call eclim#tree#Refresh()
nnoremap A :call eclim#tree#ToggleViewHidden()
nnoremap ~ :call eclim#tree#SetRoot(eclim#UserHome())
nnoremap C :call eclim#tree#SetRoot(eclim#tree#GetPath())
nnoremap K :call eclim#tree#SetRoot(substitute(
\ PathToAlias(eclim#tree#GetRoot()),
\ '^\([^/]*/\).*', '\1', ''))
nnoremap B :call eclim#tree#SetRoot(
\ fnamemodify(eclim#tree#GetRoot(), ':h:h'))
nnoremap j :TreeNextPrevLine j
nnoremap k :TreeNextPrevLine k
nnoremap p :call eclim#tree#MoveToParent()
nnoremap P :call eclim#tree#MoveToLastChild()
nnoremap D :call eclim#tree#Mkdir()
let ctrl_l = escape(maparg(''), '|')
exec 'nnoremap :silent doautocmd eclim_tree User ' . ctrl_l
command! -nargs=1 -complete=dir -buffer CD :call eclim#tree#SetRoot('')
command! -nargs=1 -complete=dir -buffer Cd :call eclim#tree#SetRoot('')
" only needed as a command to support counts on the j/k mappings
command! -nargs=? -count=1 -buffer TreeNextPrevLine
\ let c = |
\ let c = c > 1 ? c - line('.') + 1 : c |
\ let prev = line('.') |
\ exec 'normal! ' . c . '' |
\ call eclim#tree#Cursor(line('.'), prev)
endfunction " }}}
function! eclim#tree#Syntax() " {{{
exec "hi link TreeDir " . g:TreeDirHighlight
exec "hi link TreeFile " . g:TreeFileHighlight
exec "hi link TreeFileExecutable " . g:TreeFileExecutableHighlight
hi link TreeMarker Normal
syntax match TreeMarker /^\s*[-+]/
syntax match TreeDir /\S.*\// contains=TreeMarker
syntax match TreeFile /\S.*[^\/]$/
syntax match TreeFileExecutable /\S.*[^\/]\*$/
syntax match Comment /^".*/
endfunction " }}}
" vim:ft=vim:fdm=marker