From 7053d468cc45b1afc18a12cc26dea0278fe9567c Mon Sep 17 00:00:00 2001 From: Martino Pilia Date: Fri, 31 May 2019 17:26:53 +0200 Subject: [PATCH 1/3] Add API for custom LSP requests Implement a function `ale#lsp_linter#SendRequest` that allows to send custom LSP requests to an enabled LSP linter. Resolves #2474 --- autoload/ale/lsp.vim | 32 ++++++- autoload/ale/lsp_linter.vim | 26 ++++++ doc/ale.txt | 18 ++++ test/lsp/test_lsp_custom_request.vader | 120 +++++++++++++++++++++++++ 4 files changed, 192 insertions(+), 4 deletions(-) create mode 100644 test/lsp/test_lsp_custom_request.vader diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim index 017096cd..1ffdba2e 100644 --- a/autoload/ale/lsp.vim +++ b/autoload/ale/lsp.vim @@ -5,6 +5,9 @@ let s:connections = get(s:, 'connections', {}) 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, " create a new connection if needed. Return a unique ID for the connection. function! ale#lsp#Register(executable_or_address, project, init_options) abort @@ -296,10 +299,19 @@ function! ale#lsp#HandleMessage(conn_id, message) abort " responses. if l:conn.initialized for l:response in l:response_list - " 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 + if has_key(l:response, 'id') && has_key(s:custom_callbacks, l:response.id) + " Response to a custom request, call the registered one-shot handler. + try + call s:custom_callbacks[l:response.id](l:response) + 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 endif endfunction @@ -525,6 +537,18 @@ function! ale#lsp#Send(conn_id, message) abort return l:id == 0 ? -1 : l:id 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. " If a document is opened, 1 will be returned, otherwise 0 will be returned. function! ale#lsp#OpenDocument(conn_id, buffer, language_id) abort diff --git a/autoload/ale/lsp_linter.vim b/autoload/ale/lsp_linter.vim index 4f439b28..82ae60a2 100644 --- a/autoload/ale/lsp_linter.vim +++ b/autoload/ale/lsp_linter.vim @@ -413,3 +413,29 @@ endfunction function! ale#lsp_linter#SetLSPLinterMap(replacement_map) abort let s:lsp_linter_map = a:replacement_map endfunction + +" Send a custom request to an LSP linter. +function! ale#lsp_linter#SendRequest(buffer, linter_name, method, parameters, Callback) abort + let l:filetype = ale#linter#ResolveFiletype(getbufvar(a:buffer, '&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}) + + if len(l:linter_list) < 1 + throw 'Linter "' . a:linter_name . '" not found!' + endif + + let l:linter = l:linter_list[0] + let l:executable_or_address = '' + + if l:linter.lsp is# 'socket' + let l:executable_or_address = ale#linter#GetAddress(a:buffer, l:linter) + else + let l:executable_or_address = ale#linter#GetExecutable(a:buffer, l:linter) + 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] + + return ale#lsp#SendCustomRequest(l:conn_id, l:message, a:Callback) == 0 +endfunction diff --git a/doc/ale.txt b/doc/ale.txt index 17f1dde6..8dcbea60 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -3191,6 +3191,24 @@ ale#linter#PreventLoading(filetype) *ale#linter#PreventLoading()* |runtimepath| for that filetype. This function can be called from vimrc or similar to prevent ALE from loading linters. + +ale#lsp_linter#SendRequest(buffer, linter_name, method, parameters, Callback) + *ale#lsp_linter#SendRequest()* + + Send a custom request to an LSP linter. + + `buffer` must be a valid buffer number, and `linter_name` is a |String| + 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`, while `parameters` is a |dictionary| of LSP parameters applicable + 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 + 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()* diff --git a/test/lsp/test_lsp_custom_request.vader b/test/lsp/test_lsp_custom_request.vader new file mode 100644 index 00000000..e5fecafc --- /dev/null +++ b/test/lsp/test_lsp_custom_request.vader @@ -0,0 +1,120 @@ +Before: + runtime autoload/ale/linter.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/lsp_linter.vim + + let g:address = 'ccls_address' + let g:callback_result = 0 + let g:conn_id = -1 + let g:executable = 'ccls' + let g:linter_name = 'ccls' + let g:magic_number = 42 + let g:message = -1 + let g:message_id = 1 + let g:method = '$ccls/call' + let g:parameters = {} + let g:project = '/project/root' + let g:return_value = -1 + + let g:linter_list = [{ + \ 'output_stream': 'stdout', + \ 'lint_file': 0, + \ 'language': 'cpp', + \ 'name': g:linter_name, + \ 'project_root': {b -> g:project}, + \ 'aliases': [], + \ 'language_callback': {b -> 'cpp'}, + \ 'read_buffer': 1, + \ 'command': '%e' + \ }] + + " Encode dictionary to jsonrpc + function! Encode(obj) abort + let l:body = json_encode(a:obj) + return 'Content-Length: ' . strlen(l:body) . "\r\n\r\n" . l:body + endfunction + + " Register the server with given executable or address + function! InitServer(executable_or_address) abort + let g:conn_id = ale#lsp#Register(a:executable_or_address, g:project, {}) + call ale#lsp#HandleMessage(g:conn_id, Encode({'method': 'initialize'})) + endfunction + + " Dummy callback + function! Callback(response) abort + let g:callback_result = a:response.result.value + endfunction + + " Replace the GetAll function to mock an LSP linter + function! ale#linter#GetAll(filetype) abort + return g:linter_list + endfunction + + " Replace the Send function to mock an LSP linter + function! ale#lsp#Send(conn_id, message) abort + let g:message = a:message + return g:message_id + endfunction + + " Code for a test case + function! TestCase() abort + " Test sending a custom request + let g:return_value = ale#lsp_linter#SendRequest(bufnr('%'), g:linter_name, g:method, g:parameters, function('Callback')) + + AssertEqual + \ 0, + \ g:return_value + + AssertEqual + \ [0, g:method, g:parameters], + \ g:message + + " 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}})) + + AssertEqual + \ g:magic_number, + \ g:callback_result + endfunction + +After: + if g:conn_id isnot v:null + call ale#lsp#RemoveConnectionWithID(g:conn_id) + endif + + unlet! g:callback_result + unlet! g:conn_id + unlet! g:executable + unlet! g:linter_name + unlet! g:magic_number + unlet! g:message + unlet! g:message_id + unlet! g:method + unlet! g:parameters + unlet! g:project + unlet! g:return_value + + delfunction Encode + delfunction InitServer + delfunction Callback + delfunction TestCase + + runtime autoload/ale/linter.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/lsp_linter.vim + +Given cpp(Empty cpp file): +Execute(Test custom request to server identified by executable): + call InitServer(g:executable) + let g:linter_list[0].executable = {b -> g:executable} + let g:linter_list[0].lsp = 'stdio' + + call TestCase() + +Given cpp(Empty cpp file): +Execute(Test custom request to server identified by address): + call InitServer(g:address) + let g:linter_list[0].address = {b -> g:address} + let g:linter_list[0].lsp = 'socket' + + call TestCase() From 332168594001264e68601c03a6365626142be1fe Mon Sep 17 00:00:00 2001 From: Martino Pilia Date: Fri, 31 May 2019 21:56:38 +0200 Subject: [PATCH 2/3] Refactor LSP custom request handling --- autoload/ale/lsp.vim | 32 ++------------ autoload/ale/lsp_linter.vim | 34 +++++++++++---- doc/ale.txt | 3 -- test/lsp/test_lsp_custom_request.vader | 58 ++++++++++++++++---------- 4 files changed, 67 insertions(+), 60 deletions(-) diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim index 1ffdba2e..017096cd 100644 --- a/autoload/ale/lsp.vim +++ b/autoload/ale/lsp.vim @@ -5,9 +5,6 @@ let s:connections = get(s:, 'connections', {}) 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, " create a new connection if needed. Return a unique ID for the connection. function! ale#lsp#Register(executable_or_address, project, init_options) abort @@ -299,19 +296,10 @@ function! ale#lsp#HandleMessage(conn_id, message) abort " responses. if l:conn.initialized for l:response in l:response_list - if has_key(l:response, 'id') && has_key(s:custom_callbacks, l:response.id) - " Response to a custom request, call the registered one-shot handler. - try - call s:custom_callbacks[l:response.id](l:response) - 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 + " 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 endfor endif endfunction @@ -537,18 +525,6 @@ function! ale#lsp#Send(conn_id, message) abort return l:id == 0 ? -1 : l:id 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. " If a document is opened, 1 will be returned, otherwise 0 will be returned. function! ale#lsp#OpenDocument(conn_id, buffer, language_id) abort diff --git a/autoload/ale/lsp_linter.vim b/autoload/ale/lsp_linter.vim index 82ae60a2..60a93504 100644 --- a/autoload/ale/lsp_linter.vim +++ b/autoload/ale/lsp_linter.vim @@ -8,6 +8,9 @@ if !has_key(s:, 'lsp_linter_map') let s:lsp_linter_map = {} 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. function! s:ShouldIgnore(buffer, linter_name) abort " Ignore all diagnostics if LSP integration is disabled. @@ -407,6 +410,7 @@ endfunction " Clear LSP linter data for the linting engine. function! ale#lsp_linter#ClearLSPData() abort let s:lsp_linter_map = {} + let s:custom_callbacks_map = {} endfunction " Just for tests. @@ -414,6 +418,24 @@ function! ale#lsp_linter#SetLSPLinterMap(replacement_map) abort let s:lsp_linter_map = a:replacement_map 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. function! ale#lsp_linter#SendRequest(buffer, linter_name, method, parameters, Callback) abort 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 let l:linter = l:linter_list[0] - let l:executable_or_address = '' - if l:linter.lsp is# 'socket' - let l:executable_or_address = ale#linter#GetAddress(a:buffer, l:linter) - else - let l:executable_or_address = ale#linter#GetExecutable(a:buffer, l:linter) + if empty(l:linter.lsp) + throw 'Linter "' . a:linter_name . '" does not support LSP!' 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: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 diff --git a/doc/ale.txt b/doc/ale.txt index 8dcbea60..f0c0a468 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -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 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()* diff --git a/test/lsp/test_lsp_custom_request.vader b/test/lsp/test_lsp_custom_request.vader index e5fecafc..f50a4cf2 100644 --- a/test/lsp/test_lsp_custom_request.vader +++ b/test/lsp/test_lsp_custom_request.vader @@ -7,13 +7,15 @@ Before: let g:callback_result = 0 let g:conn_id = -1 let g:executable = 'ccls' + let g:executable_or_address = '' let g:linter_name = 'ccls' let g:magic_number = 42 - let g:message = -1 + let g:message_list = [] let g:message_id = 1 let g:method = '$ccls/call' let g:parameters = {} - let g:project = '/project/root' + let g:project_root = '/project/root' + let g:response = '' let g:return_value = -1 let g:linter_list = [{ @@ -21,7 +23,7 @@ Before: \ 'lint_file': 0, \ 'language': 'cpp', \ 'name': g:linter_name, - \ 'project_root': {b -> g:project}, + \ 'project_root': {b -> g:project_root}, \ 'aliases': [], \ 'language_callback': {b -> 'cpp'}, \ 'read_buffer': 1, @@ -34,10 +36,20 @@ Before: return 'Content-Length: ' . strlen(l:body) . "\r\n\r\n" . l:body endfunction - " Register the server with given executable or address - function! InitServer(executable_or_address) abort - let g:conn_id = ale#lsp#Register(a:executable_or_address, g:project, {}) + " Replace the StartLSP function to mock an LSP linter + function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort + 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'})) + + 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 " Dummy callback @@ -52,25 +64,29 @@ Before: " Replace the Send function to mock an LSP linter function! ale#lsp#Send(conn_id, message) abort - let g:message = a:message + call add(g:message_list, a:message) return g:message_id endfunction " Code for a test case function! TestCase() abort " 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 - \ 0, - \ g:return_value - - AssertEqual - \ [0, g:method, g:parameters], - \ g:message + Assert index(g:message_list, [0, g:method, g:parameters]) >= 0 " 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 \ g:magic_number, @@ -87,15 +103,15 @@ After: unlet! g:executable unlet! g:linter_name unlet! g:magic_number - unlet! g:message + unlet! g:message_list unlet! g:message_id unlet! g:method unlet! g:parameters - unlet! g:project + unlet! g:project_root + unlet! g:response unlet! g:return_value delfunction Encode - delfunction InitServer delfunction Callback delfunction TestCase @@ -105,7 +121,7 @@ After: Given cpp(Empty cpp file): 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].lsp = 'stdio' @@ -113,7 +129,7 @@ Execute(Test custom request to server identified by executable): Given cpp(Empty cpp file): 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].lsp = 'socket' From 5542db1507edc054d654e748a2e7ccdd007e2f95 Mon Sep 17 00:00:00 2001 From: Martino Pilia Date: Sat, 1 Jun 2019 16:27:44 +0200 Subject: [PATCH 3/3] Support custom LSP notifications Allow to send custom notification mesages, that expect no response from the server. --- autoload/ale/lsp_linter.vim | 37 ++++++++++++++---------- doc/ale.txt | 30 ++++++++++++++------ test/lsp/test_lsp_custom_request.vader | 39 ++++++++++++++++++++------ 3 files changed, 74 insertions(+), 32 deletions(-) diff --git a/autoload/ale/lsp_linter.vim b/autoload/ale/lsp_linter.vim index 60a93504..190a16b4 100644 --- a/autoload/ale/lsp_linter.vim +++ b/autoload/ale/lsp_linter.vim @@ -8,8 +8,8 @@ if !has_key(s:, 'lsp_linter_map') let s:lsp_linter_map = {} endif -" A Dictionary to track one-shot callbacks for custom LSP requests -let s:custom_callbacks_map = get(s:, 'custom_callbacks_map', {}) +" A Dictionary to track one-shot handlers for custom LSP requests +let s:custom_handlers_map = get(s:, 'custom_handlers_map', {}) " Check if diagnostics for a particular linter should be ignored. function! s:ShouldIgnore(buffer, linter_name) abort @@ -410,7 +410,7 @@ endfunction " Clear LSP linter data for the linting engine. function! ale#lsp_linter#ClearLSPData() abort let s:lsp_linter_map = {} - let s:custom_callbacks_map = {} + let s:custom_handlers_map = {} endfunction " Just for tests. @@ -420,24 +420,25 @@ 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) + \&& has_key(s:custom_handlers_map, a:response.id) + let l:Handler = remove(s:custom_handlers_map, a:response.id) + call l:Handler(a:response) endif 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:Callback = function('s:HandleLSPResponseToCustomRequests') + let l:request_id = ale#lsp#Send(l:id, a:args.message) - 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 + if l:request_id > 0 && has_key(a:args, 'handler') + let l:Callback = function('s:HandleLSPResponseToCustomRequests') + call ale#lsp#RegisterCallback(l:id, l:Callback) + let s:custom_handlers_map[l:request_id] = a:args.handler + endif endfunction " 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:linter_list = ale#linter#GetAll(l:filetype) 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!' endif - let l:message = [0, a:method, a:parameters] - let l:Callback = function('s:OnReadyForCustomRequests', [l:message, a:Callback]) + let l:is_notification = a:message[0] + 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) endfunction diff --git a/doc/ale.txt b/doc/ale.txt index f0c0a468..f0820be6 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -3192,18 +3192,30 @@ ale#linter#PreventLoading(filetype) *ale#linter#PreventLoading()* 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()* - 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| - 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`, while `parameters` is a |dictionary| of LSP parameters applicable - 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 - the response to the request obtained from the server. + `buffer` A valid buffer number. + + `linter_name` A |String| identifying an LSP linter that is available and + enabled for the |filetype| of `buffer`. + + `message` A |List| in the form `[is_notification, method, parameters]`, + 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) diff --git a/test/lsp/test_lsp_custom_request.vader b/test/lsp/test_lsp_custom_request.vader index f50a4cf2..04f044af 100644 --- a/test/lsp/test_lsp_custom_request.vader +++ b/test/lsp/test_lsp_custom_request.vader @@ -4,12 +4,12 @@ Before: runtime autoload/ale/lsp_linter.vim let g:address = 'ccls_address' - let g:callback_result = 0 let g:conn_id = -1 let g:executable = 'ccls' let g:executable_or_address = '' let g:linter_name = 'ccls' let g:magic_number = 42 + let g:no_result = 0 let g:message_list = [] let g:message_id = 1 let g:method = '$ccls/call' @@ -30,6 +30,8 @@ Before: \ 'command': '%e' \ }] + let g:callback_result = g:no_result + " Encode dictionary to jsonrpc function! Encode(obj) abort let l:body = json_encode(a:obj) @@ -69,16 +71,15 @@ Before: endfunction " Code for a test case - function! TestCase() abort + function! TestCase(is_notification) abort " Test sending a custom request let g:return_value = ale#lsp_linter#SendRequest( \ bufnr('%'), \ g:linter_name, - \ g:method, - \ g:parameters, + \ [a:is_notification, g:method, g:parameters], \ 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 let g:response = Encode({ @@ -89,7 +90,7 @@ Before: call ale#lsp#HandleMessage(g:conn_id, g:response) AssertEqual - \ g:magic_number, + \ a:is_notification ? g:no_result : g:magic_number, \ g:callback_result endfunction @@ -101,11 +102,13 @@ After: unlet! g:callback_result unlet! g:conn_id unlet! g:executable + unlet! g:is_notification unlet! g:linter_name unlet! g:magic_number unlet! g:message_list unlet! g:message_id unlet! g:method + unlet! g:no_result unlet! g:parameters unlet! g:project_root 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:linter_list[0].executable = {b -> g:executable} 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): Execute(Test custom request 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 = 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)