Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support hierarchical document symbols #316

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions autoload/vista/executive/coc.vim
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,11 @@ let s:should_display = v:false

" Extract fruitful infomation from raw symbols
function! s:Extract(symbols) abort
let s:data = []

if empty(a:symbols)
return
endif

let g:vista.functions = []
let g:vista.raw = []
call map(a:symbols, 'vista#parser#lsp#CocSymbols(v:val, s:data)')
let s:data = vista#parser#lsp#DispatchDocumentSymbols(a:symbols)

if !empty(s:data)
let [s:reload_only, s:should_display] = vista#renderer#LSPProcess(s:data, s:reload_only, s:should_display)
Expand Down
129 changes: 65 additions & 64 deletions autoload/vista/parser/lsp.vim
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ let s:symbol_kind = {
\ '26': 'TypeParameter',
\ }

" The kind field in the result is a number instead of a readable text, we
" should transform the number to the symbol text first.
function! s:Kind2Symbol(kind) abort
return has_key(s:symbol_kind, a:kind) ? s:symbol_kind[a:kind] : 'Unknown kind '.a:kind
endfunction
Expand All @@ -41,80 +43,79 @@ function! s:IsFileUri(uri) abort
return stridx(a:uri, 'file:///') == 0
endfunction

" The kind field in the result is a number instead of a readable text, we
" should transform the number to the symbol text first.
function! vista#parser#lsp#KindToSymbol(line, container) abort
let line = a:line
" SymbolInformation interface
if has_key(line, 'location')
let location = line.location
if s:IsFileUri(location.uri)
let lnum = location.range.start.line + 1
let col = location.range.start.character + 1
call add(a:container, {
\ 'lnum': lnum,
\ 'col': col,
\ 'kind': s:Kind2Symbol(line.kind),
\ 'text': line.name,
\ })
endif
" DocumentSymbol class
elseif has_key(line, 'range')
let range = line.range
let lnum = range.start.line + 1
let col = range.start.character + 1
call add(a:container, {
\ 'lnum': lnum,
\ 'col': col,
\ 'kind': s:Kind2Symbol(line.kind),
\ 'text': line.name,
\ })
if has_key(line, 'children')
for child in line.children
call vista#parser#lsp#KindToSymbol(child, a:container)
endfor
endif
endif
function! s:LspToLocalSymbol(sym, range)
return {
\ 'lnum': a:range.start.line + 1,
\ 'col': a:range.start.character + 1,
\ 'kind': s:Kind2Symbol(a:sym.kind),
\ 'text': a:sym.name,
\ }
endfunction

function! vista#parser#lsp#CocSymbols(symbol, container) abort
if vista#ShouldIgnore(a:symbol.kind)
return
endif
function! s:LocalToRawSymbol(sym)
return {
\ 'line': a:sym.lnum,
\ 'kind': a:sym.kind,
\ 'name': a:sym.text,
\ }
endfunction

let raw = { 'line': a:symbol.lnum, 'kind': a:symbol.kind, 'name': a:symbol.text }
call add(g:vista.raw, raw)
function! s:IsDocumentSymbol(sym)
return has_key(a:sym, 'selectionRange')
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change? The author of previous patch about this part uses the range.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both range and selectionRange are mandatory in documentSymbol response, the latter is used for consistency with CoC behavior.

endfunction

if a:symbol.kind ==? 'Method' || a:symbol.kind ==? 'Function'
call add(g:vista.functions, a:symbol)
endif
function! s:ParseSymbolInfoList(symbols) abort
let filtered = filter(a:symbols, 's:IsFileUri(v:val.location.uri)')
return map(filtered, 's:LspToLocalSymbol(v:val, v:val.location.range)')
endfunction

call add(a:container, {
\ 'lnum': a:symbol.lnum,
\ 'col': a:symbol.col,
\ 'text': a:symbol.text,
\ 'kind': a:symbol.kind,
\ 'level': a:symbol.level
\ })
function! s:ParseDocumentSymbolsRec(outlist, symbols, level) abort
for lspsym in a:symbols
greye marked this conversation as resolved.
Show resolved Hide resolved
let sym = s:LspToLocalSymbol(lspsym, lspsym.selectionRange)
let sym.level = a:level
call add(a:outlist, sym)
if has_key(lspsym, 'children')
call s:ParseDocumentSymbolsRec(a:outlist, lspsym.children, a:level + 1)
endif
endfor
return a:outlist
endfunction

" https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol
function! vista#parser#lsp#ExtractSymbol(symbol, container) abort
let symbol = a:symbol
function! s:GroupSymbolsByKind(symbols) abort
let groups = {}
for sym in a:symbols
if has_key(groups, sym.kind)
call add(groups[sym.kind], sym)
else
let groups[sym.kind] = [ sym ]
endif
endfor
return groups
endfunction

if vista#ShouldIgnore(symbol.kind)
return
function! vista#parser#lsp#ParseDocumentSymbolPayload(resp) abort
if s:IsDocumentSymbol(a:resp[0])
let symbols = s:ParseDocumentSymbolsRec([], a:resp, 0)
return vista#parser#lsp#DispatchDocumentSymbols(symbols)
else
let symbols = s:ParseSymbolInfoList(a:resp)
call s:FilterDocumentSymbols(symbols)
return s:GroupSymbolsByKind(symbols)
endif
endfunction

if symbol.kind ==? 'Method' || symbol.kind ==? 'Function'
call add(g:vista.functions, symbol)
function! s:FilterDocumentSymbols(symbols) abort
let symlist = a:symbols
if exists('g:vista_ignore_kinds')
call filter(symlist, 'index(g:vista_ignore_kinds, v:val) < 0')
endif
let g:vista.functions =
\ filter(copy(symlist), 'v:val.kind ==? "Method" || v:val.kind ==? "Function"')
Copy link
Owner

@liuchengxu liuchengxu Jul 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure which one is better, if you put this filter logic into s:GroupSymbolsByKind(), then you have only one for loop, these two fitler can be saved and don't have to worry about the copy.

return symlist
endfunction

let picked = {'lnum': symbol.lnum, 'col': symbol.col, 'text': symbol.text}

if has_key(a:container, symbol.kind)
call add(a:container[symbol.kind], picked)
else
let a:container[symbol.kind] = [picked]
endif
function! vista#parser#lsp#DispatchDocumentSymbols(symbols)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be used only once, therefore it's not worthy to define another function.

let symlist = s:FilterDocumentSymbols(a:symbols)
let g:vista.raw = map(copy(symlist), 's:LocalToRawSymbol(v:val)')
return symlist
endfunction
17 changes: 7 additions & 10 deletions autoload/vista/renderer.vim
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,17 @@ function! vista#renderer#Decorate(kind) abort
endif
endfunction

function! s:HasNestingLevel(data)
return !empty(a:data) && type(a:data) == v:t_list && has_key(a:data[0], 'level')
endfunction

function! s:Render(data) abort
if g:vista.provider ==# 'coc'
if g:vista.provider ==# 'coc' || s:HasNestingLevel(a:data)
return vista#renderer#hir#lsp#Coc(a:data)
elseif g:vista.provider ==# 'ctags' && g:vista#renderer#ctags ==# 'default'
return vista#renderer#hir#ctags#Render()
else
" The kind renderer applys to the LSP provider.
" The kind renderer applies to the LSP provider.
return vista#renderer#kind#Render(a:data)
endif
endfunction
Expand All @@ -92,14 +96,7 @@ endfunction
" Convert the number kind to the text kind, and then
" extract the neccessary info from the raw LSP response.
function! vista#renderer#LSPPreprocess(lsp_result) abort
let lines = []
call map(a:lsp_result, 'vista#parser#lsp#KindToSymbol(v:val, lines)')

let processed_data = {}
let g:vista.functions = []
call map(lines, 'vista#parser#lsp#ExtractSymbol(v:val, processed_data)')

return processed_data
return vista#parser#lsp#ParseDocumentSymbolPayload(a:lsp_result)
endfunction

" React on the preprocessed LSP data
Expand Down