Refactor LSP custom request handling

This commit is contained in:
Martino Pilia 2019-05-31 21:56:38 +02:00
parent 7053d468cc
commit 3321685940
No known key found for this signature in database
GPG key ID: CDEE463095565E17
4 changed files with 67 additions and 60 deletions

View file

@ -5,9 +5,6 @@
let s:connections = get(s:, 'connections', {}) let s:connections = get(s:, 'connections', {})
let g:ale_lsp_next_message_id = 1 let g:ale_lsp_next_message_id = 1
" A Dictionary to track one-shot callbacks for custom LSP requests
let s:custom_callbacks = get(s:, 'custom_callbacks', {})
" Given an id, which can be an executable or address, and a project path, " Given an id, which can be an executable or address, and a project path,
" create a new connection if needed. Return a unique ID for the connection. " create a new connection if needed. Return a unique ID for the connection.
function! ale#lsp#Register(executable_or_address, project, init_options) abort function! ale#lsp#Register(executable_or_address, project, init_options) abort
@ -299,19 +296,10 @@ function! ale#lsp#HandleMessage(conn_id, message) abort
" responses. " responses.
if l:conn.initialized if l:conn.initialized
for l:response in l:response_list for l:response in l:response_list
if has_key(l:response, 'id') && has_key(s:custom_callbacks, l:response.id) " Call all of the registered handlers with the response.
" Response to a custom request, call the registered one-shot handler. for l:Callback in l:conn.callback_list
try call ale#util#GetFunction(l:Callback)(a:conn_id, l:response)
call s:custom_callbacks[l:response.id](l:response) endfor
finally
call remove(s:custom_callbacks, l:response.id)
endtry
else
" Call all of the registered handlers with the response.
for l:Callback in l:conn.callback_list
call ale#util#GetFunction(l:Callback)(a:conn_id, l:response)
endfor
endif
endfor endfor
endif endif
endfunction endfunction
@ -537,18 +525,6 @@ function! ale#lsp#Send(conn_id, message) abort
return l:id == 0 ? -1 : l:id return l:id == 0 ? -1 : l:id
endfunction endfunction
" Send a custom request to an LSP server.
" The given callback is called on response.
function! ale#lsp#SendCustomRequest(conn_id, message, Callback) abort
let l:id = ale#lsp#Send(a:conn_id, a:message)
if l:id > 0
let s:custom_callbacks[l:id] = a:Callback
endif
return l:id
endfunction
" Notify LSP servers or tsserver if a document is opened, if needed. " Notify LSP servers or tsserver if a document is opened, if needed.
" If a document is opened, 1 will be returned, otherwise 0 will be returned. " If a document is opened, 1 will be returned, otherwise 0 will be returned.
function! ale#lsp#OpenDocument(conn_id, buffer, language_id) abort function! ale#lsp#OpenDocument(conn_id, buffer, language_id) abort

View file

@ -8,6 +8,9 @@ if !has_key(s:, 'lsp_linter_map')
let s:lsp_linter_map = {} let s:lsp_linter_map = {}
endif endif
" A Dictionary to track one-shot callbacks for custom LSP requests
let s:custom_callbacks_map = get(s:, 'custom_callbacks_map', {})
" Check if diagnostics for a particular linter should be ignored. " Check if diagnostics for a particular linter should be ignored.
function! s:ShouldIgnore(buffer, linter_name) abort function! s:ShouldIgnore(buffer, linter_name) abort
" Ignore all diagnostics if LSP integration is disabled. " Ignore all diagnostics if LSP integration is disabled.
@ -407,6 +410,7 @@ endfunction
" Clear LSP linter data for the linting engine. " Clear LSP linter data for the linting engine.
function! ale#lsp_linter#ClearLSPData() abort function! ale#lsp_linter#ClearLSPData() abort
let s:lsp_linter_map = {} let s:lsp_linter_map = {}
let s:custom_callbacks_map = {}
endfunction endfunction
" Just for tests. " Just for tests.
@ -414,6 +418,24 @@ function! ale#lsp_linter#SetLSPLinterMap(replacement_map) abort
let s:lsp_linter_map = a:replacement_map let s:lsp_linter_map = a:replacement_map
endfunction endfunction
function! s:HandleLSPResponseToCustomRequests(conn_id, response) abort
if has_key(a:response, 'id')
\&& has_key(s:custom_callbacks_map, a:response.id)
let l:Callback = remove(s:custom_callbacks_map, a:response.id)
call l:Callback(a:response)
endif
endfunction
function! s:OnReadyForCustomRequests(message, Callback, linter, lsp_details) abort
let l:id = a:lsp_details.connection_id
let l:Callback = function('s:HandleLSPResponseToCustomRequests')
call ale#lsp#RegisterCallback(l:id, l:Callback)
let l:request_id = ale#lsp#Send(l:id, a:message)
let s:custom_callbacks_map[l:request_id] = a:Callback
endfunction
" Send a custom request to an LSP linter. " Send a custom request to an LSP linter.
function! ale#lsp_linter#SendRequest(buffer, linter_name, method, parameters, Callback) abort function! ale#lsp_linter#SendRequest(buffer, linter_name, method, parameters, Callback) abort
let l:filetype = ale#linter#ResolveFiletype(getbufvar(a:buffer, '&filetype')) let l:filetype = ale#linter#ResolveFiletype(getbufvar(a:buffer, '&filetype'))
@ -425,17 +447,13 @@ function! ale#lsp_linter#SendRequest(buffer, linter_name, method, parameters, Ca
endif endif
let l:linter = l:linter_list[0] let l:linter = l:linter_list[0]
let l:executable_or_address = ''
if l:linter.lsp is# 'socket' if empty(l:linter.lsp)
let l:executable_or_address = ale#linter#GetAddress(a:buffer, l:linter) throw 'Linter "' . a:linter_name . '" does not support LSP!'
else
let l:executable_or_address = ale#linter#GetExecutable(a:buffer, l:linter)
endif endif
let l:root = ale#util#GetFunction(l:linter.project_root)(a:buffer)
let l:conn_id = l:executable_or_address . ':' . l:root
let l:message = [0, a:method, a:parameters] let l:message = [0, a:method, a:parameters]
let l:Callback = function('s:OnReadyForCustomRequests', [l:message, a:Callback])
return ale#lsp#SendCustomRequest(l:conn_id, l:message, a:Callback) == 0 return ale#lsp_linter#StartLSP(a:buffer, l:linter, l:Callback)
endfunction endfunction

View file

@ -3205,9 +3205,6 @@ ale#lsp_linter#SendRequest(buffer, linter_name, method, parameters, Callback)
request is received, and takes as unique argument a dictionary representing request is received, and takes as unique argument a dictionary representing
the response to the request obtained from the server. the response to the request obtained from the server.
The function returns zero if the request is succesfully sent, non-zero
otherwise.
ale#other_source#ShowResults(buffer, linter_name, loclist) ale#other_source#ShowResults(buffer, linter_name, loclist)
*ale#other_source#ShowResults()* *ale#other_source#ShowResults()*

View file

@ -7,13 +7,15 @@ Before:
let g:callback_result = 0 let g:callback_result = 0
let g:conn_id = -1 let g:conn_id = -1
let g:executable = 'ccls' let g:executable = 'ccls'
let g:executable_or_address = ''
let g:linter_name = 'ccls' let g:linter_name = 'ccls'
let g:magic_number = 42 let g:magic_number = 42
let g:message = -1 let g:message_list = []
let g:message_id = 1 let g:message_id = 1
let g:method = '$ccls/call' let g:method = '$ccls/call'
let g:parameters = {} let g:parameters = {}
let g:project = '/project/root' let g:project_root = '/project/root'
let g:response = ''
let g:return_value = -1 let g:return_value = -1
let g:linter_list = [{ let g:linter_list = [{
@ -21,7 +23,7 @@ Before:
\ 'lint_file': 0, \ 'lint_file': 0,
\ 'language': 'cpp', \ 'language': 'cpp',
\ 'name': g:linter_name, \ 'name': g:linter_name,
\ 'project_root': {b -> g:project}, \ 'project_root': {b -> g:project_root},
\ 'aliases': [], \ 'aliases': [],
\ 'language_callback': {b -> 'cpp'}, \ 'language_callback': {b -> 'cpp'},
\ 'read_buffer': 1, \ 'read_buffer': 1,
@ -34,10 +36,20 @@ Before:
return 'Content-Length: ' . strlen(l:body) . "\r\n\r\n" . l:body return 'Content-Length: ' . strlen(l:body) . "\r\n\r\n" . l:body
endfunction endfunction
" Register the server with given executable or address " Replace the StartLSP function to mock an LSP linter
function! InitServer(executable_or_address) abort function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort
let g:conn_id = ale#lsp#Register(a:executable_or_address, g:project, {}) let g:conn_id = ale#lsp#Register(g:executable_or_address, g:project_root, {})
call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer)
call ale#lsp#HandleMessage(g:conn_id, Encode({'method': 'initialize'})) call ale#lsp#HandleMessage(g:conn_id, Encode({'method': 'initialize'}))
let l:details = {
\ 'command': g:executable,
\ 'buffer': a:buffer,
\ 'connection_id': g:conn_id,
\ 'project_root': g:project_root,
\}
call ale#lsp_linter#OnInit(a:linter, l:details, a:Callback)
endfunction endfunction
" Dummy callback " Dummy callback
@ -52,25 +64,29 @@ Before:
" Replace the Send function to mock an LSP linter " Replace the Send function to mock an LSP linter
function! ale#lsp#Send(conn_id, message) abort function! ale#lsp#Send(conn_id, message) abort
let g:message = a:message call add(g:message_list, a:message)
return g:message_id return g:message_id
endfunction endfunction
" Code for a test case " Code for a test case
function! TestCase() abort function! TestCase() abort
" Test sending a custom request " Test sending a custom request
let g:return_value = ale#lsp_linter#SendRequest(bufnr('%'), g:linter_name, g:method, g:parameters, function('Callback')) let g:return_value = ale#lsp_linter#SendRequest(
\ bufnr('%'),
\ g:linter_name,
\ g:method,
\ g:parameters,
\ function('Callback'))
AssertEqual Assert index(g:message_list, [0, g:method, g:parameters]) >= 0
\ 0,
\ g:return_value
AssertEqual
\ [0, g:method, g:parameters],
\ g:message
" Mock an incoming response to the request " Mock an incoming response to the request
call ale#lsp#HandleMessage(g:conn_id, Encode({'id': g:message_id, 'jsonrpc': '2.0', 'result': {'value': g:magic_number}})) let g:response = Encode({
\ 'id': g:message_id,
\ 'jsonrpc': '2.0',
\ 'result': {'value': g:magic_number}
\ })
call ale#lsp#HandleMessage(g:conn_id, g:response)
AssertEqual AssertEqual
\ g:magic_number, \ g:magic_number,
@ -87,15 +103,15 @@ After:
unlet! g:executable unlet! g:executable
unlet! g:linter_name unlet! g:linter_name
unlet! g:magic_number unlet! g:magic_number
unlet! g:message unlet! g:message_list
unlet! g:message_id unlet! g:message_id
unlet! g:method unlet! g:method
unlet! g:parameters unlet! g:parameters
unlet! g:project unlet! g:project_root
unlet! g:response
unlet! g:return_value unlet! g:return_value
delfunction Encode delfunction Encode
delfunction InitServer
delfunction Callback delfunction Callback
delfunction TestCase delfunction TestCase
@ -105,7 +121,7 @@ After:
Given cpp(Empty cpp file): Given cpp(Empty cpp file):
Execute(Test custom request to server identified by executable): Execute(Test custom request to server identified by executable):
call InitServer(g:executable) let g:executable_or_address = g:executable
let g:linter_list[0].executable = {b -> g:executable} let g:linter_list[0].executable = {b -> g:executable}
let g:linter_list[0].lsp = 'stdio' let g:linter_list[0].lsp = 'stdio'
@ -113,7 +129,7 @@ Execute(Test custom request to server identified by executable):
Given cpp(Empty cpp file): Given cpp(Empty cpp file):
Execute(Test custom request to server identified by address): Execute(Test custom request to server identified by address):
call InitServer(g:address) let g:executable_or_address = g:address
let g:linter_list[0].address = {b -> g:address} let g:linter_list[0].address = {b -> g:address}
let g:linter_list[0].lsp = 'socket' let g:linter_list[0].lsp = 'socket'