From 4ef2c81e95529d4175ba8149fbe42e856a36ab10 Mon Sep 17 00:00:00 2001 From: w0rp Date: Wed, 31 Oct 2018 16:13:22 +0000 Subject: Implement LSP symbol search --- autoload/ale/lsp.vim | 6 +++ autoload/ale/lsp/message.vim | 6 +++ autoload/ale/preview.vim | 5 +- autoload/ale/symbol.vim | 109 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 autoload/ale/symbol.vim (limited to 'autoload') diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim index 196cbe80..b7908e74 100644 --- a/autoload/ale/lsp.vim +++ b/autoload/ale/lsp.vim @@ -41,6 +41,7 @@ function! ale#lsp#Register(executable_or_address, project, init_options) abort \ 'completion': 0, \ 'completion_trigger_characters': [], \ 'definition': 0, + \ 'symbol_search': 0, \ }, \} endif @@ -203,6 +204,10 @@ function! s:UpdateCapabilities(conn, capabilities) abort if get(a:capabilities, 'definitionProvider') is v:true let a:conn.capabilities.definition = 1 endif + + if get(a:capabilities, 'workspaceSymbolProvider') is v:true + let a:conn.capabilities.symbol_search = 1 + endif endfunction function! ale#lsp#HandleInitResponse(conn, response) abort @@ -285,6 +290,7 @@ function! ale#lsp#MarkConnectionAsTsserver(conn_id) abort let l:conn.capabilities.completion = 1 let l:conn.capabilities.completion_trigger_characters = ['.'] let l:conn.capabilities.definition = 1 + let l:conn.capabilities.symbol_search = 1 endfunction " Start a program for LSP servers. diff --git a/autoload/ale/lsp/message.vim b/autoload/ale/lsp/message.vim index 9ed41ac4..9fffb83a 100644 --- a/autoload/ale/lsp/message.vim +++ b/autoload/ale/lsp/message.vim @@ -130,6 +130,12 @@ function! ale#lsp#message#References(buffer, line, column) abort \}] endfunction +function! ale#lsp#message#Symbol(query) abort + return [0, 'workspace/symbol', { + \ 'query': a:query, + \}] +endfunction + function! ale#lsp#message#Hover(buffer, line, column) abort return [0, 'textDocument/hover', { \ 'textDocument': { diff --git a/autoload/ale/preview.vim b/autoload/ale/preview.vim index 180a37d0..1f50e0ad 100644 --- a/autoload/ale/preview.vim +++ b/autoload/ale/preview.vim @@ -46,11 +46,14 @@ function! ale#preview#ShowSelection(item_list) abort " Create lines to display to users. for l:item in a:item_list + let l:match = get(l:item, 'match', '') + call add( \ l:lines, \ l:item.filename \ . ':' . l:item.line - \ . ':' . l:item.column, + \ . ':' . l:item.column + \ . (!empty(l:match) ? ' ' . l:match : ''), \) endfor diff --git a/autoload/ale/symbol.vim b/autoload/ale/symbol.vim new file mode 100644 index 00000000..5180cb86 --- /dev/null +++ b/autoload/ale/symbol.vim @@ -0,0 +1,109 @@ +let s:symbol_map = {} + +" Used to get the symbol map in tests. +function! ale#symbol#GetMap() abort + return deepcopy(s:symbol_map) +endfunction + +" Used to set the symbol map in tests. +function! ale#symbol#SetMap(map) abort + let s:symbol_map = a:map +endfunction + +function! ale#symbol#ClearLSPData() abort + let s:symbol_map = {} +endfunction + +function! ale#symbol#HandleLSPResponse(conn_id, response) abort + if has_key(a:response, 'id') + \&& has_key(s:symbol_map, a:response.id) + let l:options = remove(s:symbol_map, a:response.id) + + let l:result = get(a:response, 'result', v:null) + let l:item_list = [] + + if type(l:result) is v:t_list + " Each item looks like this: + " { + " 'name': 'foo', + " 'kind': 123, + " 'deprecated': v:false, + " 'location': { + " 'uri': 'file://...', + " 'range': { + " 'start': {'line': 0, 'character': 0}, + " 'end': {'line': 0, 'character': 0}, + " }, + " }, + " 'containerName': 'SomeContainer', + " } + for l:response_item in l:result + let l:location = l:response_item.location + + call add(l:item_list, { + \ 'filename': ale#path#FromURI(l:location.uri), + \ 'line': l:location.range.start.line + 1, + \ 'column': l:location.range.start.character + 1, + \ 'match': l:response_item.name, + \}) + endfor + endif + + if empty(l:item_list) + call ale#util#Execute('echom ''No symbols found.''') + else + call ale#preview#ShowSelection(l:item_list) + endif + endif +endfunction + +function! s:OnReady(linter, lsp_details, query, ...) abort + let l:buffer = a:lsp_details.buffer + + " If we already made a request, stop here. + if getbufvar(l:buffer, 'ale_symbol_request_made', 0) + return + endif + + let l:id = a:lsp_details.connection_id + + let l:Callback = function('ale#symbol#HandleLSPResponse') + call ale#lsp#RegisterCallback(l:id, l:Callback) + + let l:message = ale#lsp#message#Symbol(a:query) + let l:request_id = ale#lsp#Send(l:id, l:message) + + call setbufvar(l:buffer, 'ale_symbol_request_made', 1) + let s:symbol_map[l:request_id] = { + \ 'buffer': l:buffer, + \} +endfunction + +function! s:Search(linter, buffer, query) abort + let l:lsp_details = ale#lsp_linter#StartLSP(a:buffer, a:linter) + + if !empty(l:lsp_details) + call ale#lsp#WaitForCapability( + \ l:lsp_details.connection_id, + \ 'symbol_search', + \ function('s:OnReady', [a:linter, l:lsp_details, a:query]), + \) + endif +endfunction + +function! ale#symbol#Search(query) abort + if type(a:query) isnot v:t_string || empty(a:query) + throw 'A non-empty string must be provided!' + endif + + let l:buffer = bufnr('') + + " Set a flag so we only make one request. + call setbufvar(l:buffer, 'ale_symbol_request_made', 0) + + for l:linter in ale#linter#Get(getbufvar(l:buffer, '&filetype')) + if !empty(l:linter.lsp) && l:linter.lsp isnot# 'tsserver' + call s:Search(l:linter, l:buffer, a:query) + endif + endfor +endfunction -- cgit v1.2.3