From 09d50ebe3110bfee5ac505aa29704d980ba156f3 Mon Sep 17 00:00:00 2001 From: w0rp Date: Wed, 2 Aug 2017 23:21:30 +0100 Subject: [PATCH] Cover the Rust LSP with tests, allow LSP linters to be named anything, and rename the Rust LSP linter to `rls` --- ale_linters/rust/langserver.vim | 33 ------------------- ale_linters/rust/rls.vim | 33 +++++++++++++++++++ autoload/ale/completion.vim | 2 +- autoload/ale/engine.vim | 22 ++++++++++--- autoload/ale/lsp.vim | 2 +- .../rust-rls-project/Cargo.toml | 0 .../test_rust_rls_callbacks.vader | 32 ++++++++++++++++++ test/test_engine_lsp_response_handling.vader | 12 +++---- 8 files changed, 91 insertions(+), 45 deletions(-) delete mode 100644 ale_linters/rust/langserver.vim create mode 100644 ale_linters/rust/rls.vim create mode 100644 test/command_callback/rust-rls-project/Cargo.toml create mode 100644 test/command_callback/test_rust_rls_callbacks.vader diff --git a/ale_linters/rust/langserver.vim b/ale_linters/rust/langserver.vim deleted file mode 100644 index 5e42a97..0000000 --- a/ale_linters/rust/langserver.vim +++ /dev/null @@ -1,33 +0,0 @@ -" Author: w0rp -" Description: A language server for Rust - -call ale#Set('rust_langserver_executable', 'rls') - -function! ale_linters#rust#langserver#GetExecutable(buffer) abort - return ale#Var(a:buffer, 'rust_langserver_executable') -endfunction - -function! ale_linters#rust#langserver#GetCommand(buffer) abort - let l:executable = ale_linters#rust#langserver#GetExecutable(a:buffer) - - return ale#Escape(l:executable) . ' +nightly' -endfunction - -function! ale_linters#rust#langserver#GetLanguage(buffer) abort - return 'rust' -endfunction - -function! ale_linters#rust#langserver#GetProjectRoot(buffer) abort - let l:cargo_file = ale#path#FindNearestFile(a:buffer, 'Cargo.toml') - - return !empty(l:cargo_file) ? fnamemodify(l:cargo_file, ':h') : '' -endfunction - -call ale#linter#Define('rust', { -\ 'name': 'langserver', -\ 'lsp': 'stdio', -\ 'executable_callback': 'ale_linters#rust#langserver#GetExecutable', -\ 'command_callback': 'ale_linters#rust#langserver#GetCommand', -\ 'language_callback': 'ale_linters#rust#langserver#GetLanguage', -\ 'project_root_callback': 'ale_linters#rust#langserver#GetProjectRoot', -\}) diff --git a/ale_linters/rust/rls.vim b/ale_linters/rust/rls.vim new file mode 100644 index 0000000..c49d268 --- /dev/null +++ b/ale_linters/rust/rls.vim @@ -0,0 +1,33 @@ +" Author: w0rp +" Description: A language server for Rust + +call ale#Set('rust_rls_executable', 'rls') + +function! ale_linters#rust#rls#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'rust_rls_executable') +endfunction + +function! ale_linters#rust#rls#GetCommand(buffer) abort + let l:executable = ale_linters#rust#rls#GetExecutable(a:buffer) + + return ale#Escape(l:executable) . ' +nightly' +endfunction + +function! ale_linters#rust#rls#GetLanguage(buffer) abort + return 'rust' +endfunction + +function! ale_linters#rust#rls#GetProjectRoot(buffer) abort + let l:cargo_file = ale#path#FindNearestFile(a:buffer, 'Cargo.toml') + + return !empty(l:cargo_file) ? fnamemodify(l:cargo_file, ':h') : '' +endfunction + +call ale#linter#Define('rust', { +\ 'name': 'rls', +\ 'lsp': 'stdio', +\ 'executable_callback': 'ale_linters#rust#rls#GetExecutable', +\ 'command_callback': 'ale_linters#rust#rls#GetCommand', +\ 'language_callback': 'ale_linters#rust#rls#GetLanguage', +\ 'project_root_callback': 'ale_linters#rust#rls#GetProjectRoot', +\}) diff --git a/autoload/ale/completion.vim b/autoload/ale/completion.vim index 214891f..3eeb617 100644 --- a/autoload/ale/completion.vim +++ b/autoload/ale/completion.vim @@ -170,7 +170,7 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort return l:results endfunction -function! s:HandleTSServerLSPResponse(response) abort +function! s:HandleTSServerLSPResponse(conn_id, response) abort if !s:CompletionStillValid(get(a:response, 'request_seq')) return endif diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim index 52acfe7..a5a5f52 100644 --- a/autoload/ale/engine.vim +++ b/autoload/ale/engine.vim @@ -11,6 +11,11 @@ if !has_key(s:, 'job_info_map') let s:job_info_map = {} endif +" Associates LSP connection IDs with linter names. +if !has_key(s:, 'lsp_linter_map') + let s:lsp_linter_map = {} +endif + let s:executable_cache_map = {} " Check if files are executable, and if they are, remember that they are @@ -192,12 +197,18 @@ function! s:HandleExit(job_id, exit_code) abort call s:HandleLoclist(l:linter.name, l:buffer, l:loclist) endfunction -function! s:HandleLSPDiagnostics(response) abort +function! s:HandleLSPDiagnostics(conn_id, response) abort + let l:linter_name = s:lsp_linter_map[a:conn_id] let l:filename = ale#path#FromURI(a:response.params.uri) let l:buffer = bufnr(l:filename) + + if l:buffer <= 0 + return + endif + let l:loclist = ale#lsp#response#ReadDiagnostics(a:response) - call s:HandleLoclist('langserver', l:buffer, l:loclist) + call s:HandleLoclist(l:linter_name, l:buffer, l:loclist) endfunction function! s:HandleTSServerDiagnostics(response, error_type) abort @@ -233,14 +244,14 @@ function! s:HandleLSPErrorMessage(error_message) abort endfor endfunction -function! ale#engine#HandleLSPResponse(response) abort +function! ale#engine#HandleLSPResponse(conn_id, response) abort let l:method = get(a:response, 'method', '') if get(a:response, 'jsonrpc', '') ==# '2.0' && has_key(a:response, 'error') " Uncomment this line to print LSP error messages. " call s:HandleLSPErrorMessage(a:response.error.message) elseif l:method ==# 'textDocument/publishDiagnostics' - call s:HandleLSPDiagnostics(a:response) + call s:HandleLSPDiagnostics(a:conn_id, a:response) elseif get(a:response, 'type', '') ==# 'event' \&& get(a:response, 'event', '') ==# 'semanticDiag' call s:HandleTSServerDiagnostics(a:response, 'semantic') @@ -617,6 +628,9 @@ function! s:CheckWithLSP(buffer, linter) abort let l:id = l:lsp_details.connection_id let l:root = l:lsp_details.project_root + " Remember the linter this connection is for. + let s:lsp_linter_map[l:id] = a:linter.name + let l:change_message = a:linter.lsp ==# 'tsserver' \ ? ale#lsp#tsserver_message#Geterr(a:buffer) \ : ale#lsp#message#DidChange(a:buffer) diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim index eb66eb4..bb9a24f 100644 --- a/autoload/ale/lsp.vim +++ b/autoload/ale/lsp.vim @@ -222,7 +222,7 @@ function! ale#lsp#HandleMessage(conn, message) abort " Call all of the registered handlers with the response. for l:Callback in a:conn.callback_list - call ale#util#GetFunction(l:Callback)(l:response) + call ale#util#GetFunction(l:Callback)(a:conn.id, l:response) endfor endif endfor diff --git a/test/command_callback/rust-rls-project/Cargo.toml b/test/command_callback/rust-rls-project/Cargo.toml new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/test_rust_rls_callbacks.vader b/test/command_callback/test_rust_rls_callbacks.vader new file mode 100644 index 0000000..76e6992 --- /dev/null +++ b/test/command_callback/test_rust_rls_callbacks.vader @@ -0,0 +1,32 @@ +Before: + Save g:ale_rust_rls_executable + + unlet! g:ale_rust_rls_executable + + runtime ale_linters/rust/rls.vim + + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The default executable path should be correct): + AssertEqual 'rls', ale_linters#rust#rls#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('rls') . ' +nightly', + \ ale_linters#rust#rls#GetCommand(bufnr('')) + +Execute(The language string should be correct): + AssertEqual 'rust', ale_linters#rust#rls#GetLanguage(bufnr('')) + +Execute(The project root should be detected correctly): + AssertEqual '', ale_linters#rust#rls#GetProjectRoot(bufnr('')) + + call ale#test#SetFilename('rust-rls-project/test.rs') + + AssertEqual + \ g:dir . '/rust-rls-project', + \ ale_linters#rust#rls#GetProjectRoot(bufnr('')) diff --git a/test/test_engine_lsp_response_handling.vader b/test/test_engine_lsp_response_handling.vader index 1f766ba..b3a45b1 100644 --- a/test/test_engine_lsp_response_handling.vader +++ b/test/test_engine_lsp_response_handling.vader @@ -15,7 +15,7 @@ Execute(tsserver syntax error responses should be handled correctly): " When we get syntax errors and no semantic errors, we should keep the " syntax errors. - call ale#engine#HandleLSPResponse({ + call ale#engine#HandleLSPResponse(1, { \ 'seq': 0, \ 'type': 'event', \ 'event': 'syntaxDiag', @@ -37,7 +37,7 @@ Execute(tsserver syntax error responses should be handled correctly): \ ], \ }, \}) - call ale#engine#HandleLSPResponse({ + call ale#engine#HandleLSPResponse(1, { \ 'seq': 0, \ 'type': 'event', \ 'event': 'semanticDiag', @@ -65,7 +65,7 @@ Execute(tsserver syntax error responses should be handled correctly): \ getloclist(0) " After we get empty syntax errors, we should clear them. - call ale#engine#HandleLSPResponse({ + call ale#engine#HandleLSPResponse(1, { \ 'seq': 0, \ 'type': 'event', \ 'event': 'syntaxDiag', @@ -88,7 +88,7 @@ Execute(tsserver semantic error responses should be handled correctly): " When we get syntax errors and no semantic errors, we should keep the " syntax errors. - call ale#engine#HandleLSPResponse({ + call ale#engine#HandleLSPResponse(1, { \ 'seq': 0, \ 'type': 'event', \ 'event': 'syntaxDiag', @@ -98,7 +98,7 @@ Execute(tsserver semantic error responses should be handled correctly): \ ], \ }, \}) - call ale#engine#HandleLSPResponse({ + call ale#engine#HandleLSPResponse(1, { \ 'seq': 0, \ 'type': 'event', \ 'event': 'semanticDiag', @@ -138,7 +138,7 @@ Execute(tsserver semantic error responses should be handled correctly): \ getloclist(0) " After we get empty syntax errors, we should clear them. - call ale#engine#HandleLSPResponse({ + call ale#engine#HandleLSPResponse(1, { \ 'seq': 0, \ 'type': 'event', \ 'event': 'semanticDiag',