Support custom LSP notifications

Allow to send custom notification mesages, that expect no response from
the server.
This commit is contained in:
Martino Pilia
2019-06-01 16:27:44 +02:00
parent 3321685940
commit 5542db1507
3 changed files with 74 additions and 32 deletions

View File

@@ -8,8 +8,8 @@ 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 " A Dictionary to track one-shot handlers for custom LSP requests
let s:custom_callbacks_map = get(s:, 'custom_callbacks_map', {}) let s:custom_handlers_map = get(s:, 'custom_handlers_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
@@ -410,7 +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 = {} let s:custom_handlers_map = {}
endfunction endfunction
" Just for tests. " Just for tests.
@@ -420,24 +420,25 @@ endfunction
function! s:HandleLSPResponseToCustomRequests(conn_id, response) abort function! s:HandleLSPResponseToCustomRequests(conn_id, response) abort
if has_key(a:response, 'id') if has_key(a:response, 'id')
\&& has_key(s:custom_callbacks_map, a:response.id) \&& has_key(s:custom_handlers_map, a:response.id)
let l:Callback = remove(s:custom_callbacks_map, a:response.id) let l:Handler = remove(s:custom_handlers_map, a:response.id)
call l:Callback(a:response) call l:Handler(a:response)
endif endif
endfunction endfunction
function! s:OnReadyForCustomRequests(message, Callback, linter, lsp_details) abort function! s:OnReadyForCustomRequests(args, linter, lsp_details) abort
let l:id = a:lsp_details.connection_id let l:id = a:lsp_details.connection_id
let l:Callback = function('s:HandleLSPResponseToCustomRequests') let l:request_id = ale#lsp#Send(l:id, a:args.message)
call ale#lsp#RegisterCallback(l:id, l:Callback) if l:request_id > 0 && has_key(a:args, 'handler')
let l:Callback = function('s:HandleLSPResponseToCustomRequests')
let l:request_id = ale#lsp#Send(l:id, a:message) call ale#lsp#RegisterCallback(l:id, l:Callback)
let s:custom_callbacks_map[l:request_id] = a:Callback let s:custom_handlers_map[l:request_id] = a:args.handler
endif
endfunction 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, message, ...) abort
let l:filetype = ale#linter#ResolveFiletype(getbufvar(a:buffer, '&filetype')) let l:filetype = ale#linter#ResolveFiletype(getbufvar(a:buffer, '&filetype'))
let l:linter_list = ale#linter#GetAll(l:filetype) let l:linter_list = ale#linter#GetAll(l:filetype)
let l:linter_list = filter(l:linter_list, {_, v -> v.name is# a:linter_name}) let l:linter_list = filter(l:linter_list, {_, v -> v.name is# a:linter_name})
@@ -452,8 +453,14 @@ function! ale#lsp_linter#SendRequest(buffer, linter_name, method, parameters, Ca
throw 'Linter "' . a:linter_name . '" does not support LSP!' throw 'Linter "' . a:linter_name . '" does not support LSP!'
endif endif
let l:message = [0, a:method, a:parameters] let l:is_notification = a:message[0]
let l:Callback = function('s:OnReadyForCustomRequests', [l:message, a:Callback]) let l:callback_args = {'message': a:message}
if !l:is_notification && a:0
let l:callback_args.handler = a:1
endif
let l:Callback = function('s:OnReadyForCustomRequests', [l:callback_args])
return ale#lsp_linter#StartLSP(a:buffer, l:linter, l:Callback) return ale#lsp_linter#StartLSP(a:buffer, l:linter, l:Callback)
endfunction endfunction

View File

@@ -3192,18 +3192,30 @@ ale#linter#PreventLoading(filetype) *ale#linter#PreventLoading()*
similar to prevent ALE from loading linters. similar to prevent ALE from loading linters.
ale#lsp_linter#SendRequest(buffer, linter_name, method, parameters, Callback) ale#lsp_linter#SendRequest(buffer, linter_name, message, [Handler])
*ale#lsp_linter#SendRequest()* *ale#lsp_linter#SendRequest()*
Send a custom request to an LSP linter. Send a custom request to an LSP linter. The arguments are defined as
follows:
`buffer` must be a valid buffer number, and `linter_name` is a |String| `buffer` A valid buffer number.
identifying an LSP linter that is available and enabled for the |filetype|
of `buffer`. `method` is a |String| identifying an LSP method supported by `linter_name` A |String| identifying an LSP linter that is available and
`linter`, while `parameters` is a |dictionary| of LSP parameters applicable enabled for the |filetype| of `buffer`.
to `method`. `Callback` is a |Funcref| that is called when a response to the
request is received, and takes as unique argument a dictionary representing `message` A |List| in the form `[is_notification, method, parameters]`,
the response to the request obtained from the server. containing three elements:
`is_notification` - an |Integer| that has value 1 if the
request is a notification, 0 otherwise;
`methdod` - a |String|, identifying an LSP method supported
by `linter`;
`parameters` - a |dictionary| of LSP parameters that are
applicable to `method`.
`Handler` Optional argument, meaningful only when `message[0]` is 0.
A |Funcref| that is called when a response to the request is
received, and takes as unique argument a dictionary
representing the response obtained from the server.
ale#other_source#ShowResults(buffer, linter_name, loclist) ale#other_source#ShowResults(buffer, linter_name, loclist)

View File

@@ -4,12 +4,12 @@ Before:
runtime autoload/ale/lsp_linter.vim runtime autoload/ale/lsp_linter.vim
let g:address = 'ccls_address' let g:address = 'ccls_address'
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: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:no_result = 0
let g:message_list = [] let g:message_list = []
let g:message_id = 1 let g:message_id = 1
let g:method = '$ccls/call' let g:method = '$ccls/call'
@@ -30,6 +30,8 @@ Before:
\ 'command': '%e' \ 'command': '%e'
\ }] \ }]
let g:callback_result = g:no_result
" Encode dictionary to jsonrpc " Encode dictionary to jsonrpc
function! Encode(obj) abort function! Encode(obj) abort
let l:body = json_encode(a:obj) let l:body = json_encode(a:obj)
@@ -69,16 +71,15 @@ Before:
endfunction endfunction
" Code for a test case " Code for a test case
function! TestCase() abort function! TestCase(is_notification) abort
" Test sending a custom request " Test sending a custom request
let g:return_value = ale#lsp_linter#SendRequest( let g:return_value = ale#lsp_linter#SendRequest(
\ bufnr('%'), \ bufnr('%'),
\ g:linter_name, \ g:linter_name,
\ g:method, \ [a:is_notification, g:method, g:parameters],
\ g:parameters,
\ function('Callback')) \ function('Callback'))
Assert index(g:message_list, [0, g:method, g:parameters]) >= 0 Assert index(g:message_list, [a:is_notification, g:method, g:parameters]) >= 0
" Mock an incoming response to the request " Mock an incoming response to the request
let g:response = Encode({ let g:response = Encode({
@@ -89,7 +90,7 @@ Before:
call ale#lsp#HandleMessage(g:conn_id, g:response) call ale#lsp#HandleMessage(g:conn_id, g:response)
AssertEqual AssertEqual
\ g:magic_number, \ a:is_notification ? g:no_result : g:magic_number,
\ g:callback_result \ g:callback_result
endfunction endfunction
@@ -101,11 +102,13 @@ After:
unlet! g:callback_result unlet! g:callback_result
unlet! g:conn_id unlet! g:conn_id
unlet! g:executable unlet! g:executable
unlet! g:is_notification
unlet! g:linter_name unlet! g:linter_name
unlet! g:magic_number unlet! g:magic_number
unlet! g:message_list unlet! g:message_list
unlet! g:message_id unlet! g:message_id
unlet! g:method unlet! g:method
unlet! g:no_result
unlet! g:parameters unlet! g:parameters
unlet! g:project_root unlet! g:project_root
unlet! g:response unlet! g:response
@@ -124,13 +127,33 @@ Execute(Test custom request to server identified by executable):
let g:executable_or_address = 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'
let g:is_notification = 0
call TestCase() call TestCase(g:is_notification)
Given cpp(Empty cpp file):
Execute(Test custom notification to server identified by executable):
let g:executable_or_address = g:executable
let g:linter_list[0].executable = {b -> g:executable}
let g:linter_list[0].lsp = 'stdio'
let g:is_notification = 1
call TestCase(g:is_notification)
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):
let g:executable_or_address = 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'
let g:is_notification = 0
call TestCase() call TestCase(g:is_notification)
Given cpp(Empty cpp file):
Execute(Test custom notification to server identified by address):
let g:executable_or_address = g:address
let g:linter_list[0].address = {b -> g:address}
let g:linter_list[0].lsp = 'socket'
let g:is_notification = 1
call TestCase(g:is_notification)