Files
vim-lsp-mirror/autoload/lsp/utils/tree.vim
2019-12-31 17:12:55 -08:00

277 lines
9.7 KiB
VimL

scriptencoding utf-8
" Callback to retrieve the tree item representation of an object.
function! s:node_get_tree_item_cb(node, object, status, tree_item) abort
if a:status ==? 'success'
let l:new_node = s:node_new(a:node.tree, a:object, a:tree_item, a:node)
call add(a:node.children, l:new_node)
call s:tree_render(l:new_node.tree)
endif
endfunction
" Callback to retrieve the children objects of a node.
function! s:node_get_children_cb(node, status, childObjectList) abort
for l:childObject in a:childObjectList
let l:Callback = function('s:node_get_tree_item_cb', [a:node, l:childObject])
call a:node.tree.provider.getTreeItem(l:Callback, l:childObject)
endfor
endfunction
" Set the node to be collapsed or expanded.
"
" When {collapsed} evaluates to 0 the node is expanded, when it is 1 the node is
" collapsed, when it is equal to -1 the node is toggled (it is expanded if it
" was collapsed, and vice versa).
function! s:node_set_collapsed(collapsed) dict abort
let l:self.collapsed = a:collapsed < 0 ? !l:self.collapsed : !!a:collapsed
endfunction
" Given a funcref {Condition}, return a list of all nodes in the subtree of
" {node} for which {Condition} evaluates to v:true.
function! s:search_subtree(node, Condition) abort
if a:Condition(a:node)
return [a:node]
endif
if len(a:node.children) < 1
return []
endif
let l:result = []
for l:child in a:node.children
let l:result = l:result + s:search_subtree(l:child, a:Condition)
endfor
return l:result
endfunction
" Execute the action associated to a node
function! s:node_exec() dict abort
if has_key(l:self.tree_item, 'command')
call l:self.tree_item.command()
endif
endfunction
" Return the depth level of the node in the tree. The level is defined
" recursively: the root has depth 0, and each node has depth equal to the depth
" of its parent increased by 1.
function! s:node_level() dict abort
if l:self.parent == {}
return 0
endif
return 1 + l:self.parent.level()
endf
" Return the string representation of the node. The {level} argument represents
" the depth level of the node in the tree and it is passed for convenience, to
" simplify the implementation and to avoid re-computing the depth.
function! s:node_render(level) dict abort
let l:indent = repeat(' ', 2 * a:level)
let l:mark = '• '
if len(l:self.children) > 0 || l:self.lazy_open != v:false
let l:mark = l:self.collapsed ? '▸ ' : '▾ '
endif
let l:label = split(l:self.tree_item.label, "\n")
call extend(l:self.tree.index, map(range(len(l:label)), 'l:self'))
let l:repr = l:indent . l:mark . l:label[0]
\ . join(map(l:label[1:], {_, l -> "\n" . l:indent . ' ' . l}))
let l:lines = [l:repr]
if !l:self.collapsed
if l:self.lazy_open
let l:self.lazy_open = v:false
let l:Callback = function('s:node_get_children_cb', [l:self])
call l:self.tree.provider.getChildren(l:Callback, l:self.object)
endif
for l:child in l:self.children
call add(l:lines, l:child.render(a:level + 1))
endfor
endif
return join(l:lines, "\n")
endfunction
" Insert a new node in the tree, internally represented by a unique progressive
" integer identifier {id}. The node represents a certain {object} (children of
" {parent}) belonging to a given {tree}, having an associated action to be
" triggered on execution defined by the function object {exec}. If {collapsed}
" is true the node will be rendered as collapsed in the view. If {lazy_open} is
" true, the children of the node will be fetched when the node is expanded by
" the user.
function! s:node_new(tree, object, tree_item, parent) abort
let a:tree.maxid += 1
return {
\ 'id': a:tree.maxid,
\ 'tree': a:tree,
\ 'object': a:object,
\ 'tree_item': a:tree_item,
\ 'parent': a:parent,
\ 'collapsed': a:tree_item.collapsibleState ==? 'collapsed',
\ 'lazy_open': a:tree_item.collapsibleState !=? 'none',
\ 'children': [],
\ 'level': function('s:node_level'),
\ 'exec': function('s:node_exec'),
\ 'set_collapsed': function('s:node_set_collapsed'),
\ 'render': function('s:node_render'),
\ }
endfunction
" Callback that sets the root node of a given {tree}, creating a new node
" with a {tree_item} representation for the given {object}. If {status} is
" equal to 'success', the root node is set and the tree view is updated
" accordingly, otherwise nothing happens.
function! s:tree_set_root_cb(tree, object, status, tree_item) abort
if a:status ==? 'success'
let a:tree.maxid = -1
let a:tree.root = s:node_new(a:tree, a:object, a:tree_item, {})
call s:tree_render(a:tree)
endif
endfunction
" Return the node currently under the cursor from the given {tree}.
function! s:get_node_under_cursor(tree) abort
let l:index = min([line('.'), len(a:tree.index) - 1])
return a:tree.index[l:index]
endfunction
" Expand or collapse the node under cursor, and render the tree.
" Please refer to *s:node_set_collapsed()* for details about the
" arguments and behaviour.
function! s:tree_set_collapsed_under_cursor(collapsed) dict abort
let l:node = s:get_node_under_cursor(l:self)
call l:node.set_collapsed(a:collapsed)
call s:tree_render(l:self)
endfunction
" Run the action associated to the node currently under the cursor.
function! s:tree_exec_node_under_cursor() dict abort
call s:get_node_under_cursor(l:self).exec()
endfunction
" Render the {tree}. This will replace the content of the buffer with the
" tree view. Clear the index, setting it to a list containing a guard
" value for index 0 (line numbers are one-based).
function! s:tree_render(tree) abort
if &filetype !=# 'lsp-tree'
return
endif
let l:cursor = getpos('.')
let a:tree.index = [-1]
let l:text = a:tree.root.render(0)
setlocal modifiable
silent 1,$delete _
silent 0put=l:text
$d
setlocal nomodifiable
call setpos('.', l:cursor)
endfunction
" If {status} equals 'success', update all nodes of {tree} representing
" an {obect} with given {tree_item} representation.
function! s:node_update(tree, object, status, tree_item) abort
if a:status !=? 'success'
return
endif
for l:node in s:search_subtree(a:tree.root, {n -> n.object == a:object})
let l:node.tree_item = a:tree_item
let l:node.children = []
let l:node.lazy_open = a:tree_item.collapsibleState !=? 'none'
endfor
call s:tree_render(a:tree)
endfunction
" Update the view if nodes have changed. If called with no arguments,
" update the whole tree. If called with an {object} as argument, update
" all the subtrees of nodes corresponding to {object}.
function! s:tree_update(...) dict abort
if a:0 < 1
call l:self.provider.getChildren({status, obj ->
\ l:self.provider.getTreeItem(function('s:tree_set_root_cb', [l:self, obj[0]]), obj[0])})
else
call l:self.provider.getTreeItem(function('s:node_update', [l:self, a:1]), a:1)
endif
endfunction
" Apply syntax to an LspTree buffer
function! s:filetype_syntax() abort
syntax clear
syntax match LspTreeMarkLeaf "•" contained
syntax match LspTreeMarkCollapsed "▸" contained
syntax match LspTreeMarkExpanded "▾" contained
syntax match LspTreeNode "\v^(\s|[▸▾•])*.*"
\ contains=LspTreeMarkLeaf,LspTreeMarkCollapsed,LspTreeMarkExpanded
highlight def link LspTreeMarkLeaf Type
highlight def link LspTreeMarkExpanded Type
highlight def link LspTreeMarkCollapsed Macro
endfunction
" Apply local settings to an LspTree buffer
function! s:filetype_settings() abort
setlocal bufhidden=wipe
setlocal buftype=nofile
setlocal foldcolumn=0
setlocal foldmethod=manual
setlocal nobuflisted
setlocal nofoldenable
setlocal nohlsearch
setlocal nolist
setlocal nomodifiable
setlocal nonumber
setlocal nospell
setlocal noswapfile
setlocal nowrap
nnoremap <silent> <buffer> <Plug>(lsp-tree-toggle-node)
\ :call b:lsp_tree.set_collapsed_under_cursor(-1)<cr>
nnoremap <silent> <buffer> <Plug>(lsp-tree-open-node)
\ :call b:lsp_tree.set_collapsed_under_cursor(v:false)<cr>
nnoremap <silent> <buffer> <Plug>(lsp-tree-close-node)
\ :call b:lsp_tree.set_collapsed_under_cursor(v:true)<cr>
nnoremap <silent> <buffer> <Plug>(lsp-tree-execute-node)
\ :call b:lsp_tree.exec_node_under_cursor()<cr>
if !exists('g:lsp_tree_no_default_maps')
nmap <silent> <buffer> o <Plug>(lsp-tree-toggle-node)
nmap <silent> <buffer> <cr> <Plug>(lsp-tree-execute-node)
nnoremap <silent> <buffer> q :q<cr>
endif
endfunction
" Turns the current buffer into an LspTree tree view. Tree data is retrieved
" from the given {provider}, and the state of the tree is stored in a
" buffer-local variable called b:lsp_tree.
"
" The {bufnr} stores the buffer number of the view, {maxid} is the highest
" known internal identifier of the nodes. The {index} is a list that
" maps line numbers to nodes.
function! lsp#utils#tree#new(provider) abort
let b:lsp_tree = {
\ 'bufnr': bufnr('.'),
\ 'maxid': -1,
\ 'root': {},
\ 'index': [],
\ 'provider': a:provider,
\ 'set_collapsed_under_cursor': function('s:tree_set_collapsed_under_cursor'),
\ 'exec_node_under_cursor': function('s:tree_exec_node_under_cursor'),
\ 'update': function('s:tree_update'),
\ }
augroup vim_lsp_tree
autocmd!
autocmd FileType lsp-tree call s:filetype_syntax() | call s:filetype_settings()
autocmd BufEnter <buffer> call s:tree_render(b:lsp_tree)
augroup END
setlocal filetype=lsp-tree
call b:lsp_tree.update()
endfunction