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 4 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
147 changes: 86 additions & 61 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,103 @@ 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
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! s:LocalToRawSymbol(sym)
return {
\ 'line': a:sym.lnum,
\ 'kind': a:sym.kind,
\ 'name': a:sym.text,
\ }
endfunction

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

function! s:ParseSymbolInfoList(outlist, symbols) abort
liuchengxu marked this conversation as resolved.
Show resolved Hide resolved
for lspsym in a:symbols
let l:loc = lspsym.location
if s:IsFileUri(l:loc.uri)
call add(a:outlist, s:LspToLocalSymbol(lspsym, l:loc.range))
endif
endif
endfor
endfunction

function! vista#parser#lsp#CocSymbols(symbol, container) abort
if vista#ShouldIgnore(a:symbol.kind)
return
endif
function! s:ParseDocumentSymbolsRec(outlist, symbols, level) abort
for lspsym in a:symbols
greye marked this conversation as resolved.
Show resolved Hide resolved
let l:sym = s:LspToLocalSymbol(lspsym, lspsym.selectionRange)
let l:sym.level = a:level
call add(a:outlist, l:sym)
if has_key(lspsym, 'children')
call s:ParseDocumentSymbolsRec(a:outlist, lspsym.children, a:level + 1)
endif
endfor
endfunction

let raw = { 'line': a:symbol.lnum, 'kind': a:symbol.kind, 'name': a:symbol.text }
call add(g:vista.raw, raw)
function! s:GroupSymbolsByKind(symbols) abort
let l:groups = {}
for l:sym in a:symbols
let l:list = get(l:groups, l:sym.kind)
if !has_key(l:groups, l:sym.kind)
let l:list = []
let l:groups[l:sym.kind] = l:list
endif
call add(l:list, l:sym)
greye marked this conversation as resolved.
Show resolved Hide resolved
endfor
return l:groups
endfunction

if a:symbol.kind ==? 'Method' || a:symbol.kind ==? 'Function'
call add(g:vista.functions, a:symbol)
function! vista#parser#lsp#ParseDocumentSymbolPayload(resp) abort
let l:symbols = []
if s:IsDocumentSymbol(a:resp[0])
call s:ParseDocumentSymbolsRec(l:symbols, a:resp, 0)
liuchengxu marked this conversation as resolved.
Show resolved Hide resolved
call vista#parser#lsp#FilterDocumentSymbols(l:symbols)
call vista#parser#lsp#DispatchDocumentSymbols(l:symbols)
return l:symbols
else
call s:ParseSymbolInfoList(l:symbols, a:resp)
call vista#parser#lsp#FilterDocumentSymbols(l:symbols)
return s:GroupSymbolsByKind(l:symbols)
endif
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! vista#parser#lsp#FilterDocumentSymbols(symbols) abort
Copy link
Owner

Choose a reason for hiding this comment

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

This function don't have to be public.

Copy link
Author

Choose a reason for hiding this comment

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

DispatchDocumentSymbols and FilterDocumentSymbols were made public to allow to reuse them in actual CoC handler: the latter contains this exact code.

Copy link
Owner

Choose a reason for hiding this comment

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

hmm, where is the actual coc handler?

Copy link
Author

Choose a reason for hiding this comment

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

lsp.vim:133: vista#parser#lsp#CocSymbols

Copy link
Author

Choose a reason for hiding this comment

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

Well, not really: CocSymbols is to be replaced, handler is in executive/coc.vim: s:Extract

let l:symlist = a:symbols
if exists('g:vista_ignore_kinds')
call filter(l:symlist, 'index(g:vista_ignore_kinds, v:val) < 0')
endif
let g:vista.functions = []
for l:sym in l:symlist
if l:sym.kind ==? 'Method' || l:sym.kind ==? 'Function'
call add(g:vista.functions, l:sym)
endif
endfor
greye marked this conversation as resolved.
Show resolved Hide resolved
return l:symlist
endfunction

" https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol
function! vista#parser#lsp#ExtractSymbol(symbol, container) abort
let symbol = a:symbol
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 g:vista.raw = map(copy(a:symbols), { _, sym -> s:LocalToRawSymbol(sym) })
return g:vista.raw
endfunction

if vista#ShouldIgnore(symbol.kind)
function! vista#parser#lsp#CocSymbols(symbol, container) abort
if vista#ShouldIgnore(a:symbol.kind)
return
endif

if symbol.kind ==? 'Method' || symbol.kind ==? 'Function'
call add(g:vista.functions, symbol)
endif

let picked = {'lnum': symbol.lnum, 'col': symbol.col, 'text': symbol.text}
call add(g:vista.raw, s:LocalToRawSymbol(a:symbol))

if has_key(a:container, symbol.kind)
call add(a:container[symbol.kind], picked)
else
let a:container[symbol.kind] = [picked]
if a:symbol.kind ==? 'Method' || a:symbol.kind ==? 'Function'
call add(g:vista.functions, a:symbol)
endif

call add(a:container, copy(a:symbol))
greye marked this conversation as resolved.
Show resolved Hide resolved
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