From b5a7593577e1ada3f81fdaa68862ad4e93dcb5a5 Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Tue, 30 Oct 2018 08:54:40 -0700 Subject: [PATCH] Add a `lsp_config_callback` linter option This is the callback-based variant of the existing `lsp_config` linter option. It serves the same purpose but can be used when more complicated processing is needed. `lsp_config` and `lsp_config_callback` are mutually exclusive options; if both an given, a linter preprocessing error will be raised. The runtime logic has been wrapped in `ale#lsp_linter#GetConfig` for convenience, similar to `ale#lsp_linter#GetOptions`. This also adds documentation and an `AssertLSPConfig` test function for completeness. --- autoload/ale/assert.vim | 13 +++++++++ autoload/ale/linter.vim | 12 ++++++++- autoload/ale/lsp_linter.vim | 20 +++++++++++--- doc/ale-development.txt | 1 + doc/ale.txt | 13 +++++++++ .../test_elixir_ls_command_callbacks.vader | 1 + .../test_golangserver_command_callback.vader | 3 ++- .../test_scala_sbtserver.vader | 2 ++ test/test_linter_defintion_processing.vader | 27 +++++++++++++++++-- 9 files changed, 85 insertions(+), 7 deletions(-) diff --git a/autoload/ale/assert.vim b/autoload/ale/assert.vim index a1bfd0b7..ed08ed09 100644 --- a/autoload/ale/assert.vim +++ b/autoload/ale/assert.vim @@ -85,6 +85,14 @@ function! ale#assert#LSPOptions(expected_options) abort AssertEqual a:expected_options, l:initialization_options endfunction +function! ale#assert#LSPConfig(expected_config) abort + let l:buffer = bufnr('') + let l:linter = s:GetLinter() + let l:config = ale#lsp_linter#GetConfig(l:buffer, l:linter) + + AssertEqual a:expected_config, l:config +endfunction + function! ale#assert#LSPLanguage(expected_language) abort let l:buffer = bufnr('') let l:linter = s:GetLinter() @@ -147,6 +155,7 @@ function! ale#assert#SetUpLinterTest(filetype, name) abort command! -nargs=+ AssertLinter :call ale#assert#Linter() command! -nargs=0 AssertLinterNotExecuted :call ale#assert#LinterNotExecuted() command! -nargs=+ AssertLSPOptions :call ale#assert#LSPOptions() + command! -nargs=+ AssertLSPConfig :call ale#assert#LSPConfig() command! -nargs=+ AssertLSPLanguage :call ale#assert#LSPLanguage() command! -nargs=+ AssertLSPProject :call ale#assert#LSPProject() command! -nargs=+ AssertLSPAddress :call ale#assert#LSPAddress() @@ -172,6 +181,10 @@ function! ale#assert#TearDownLinterTest() abort delcommand AssertLSPOptions endif + if exists(':AssertLSPConfig') + delcommand AssertLSPConfig + endif + if exists(':AssertLSPLanguage') delcommand AssertLSPLanguage endif diff --git a/autoload/ale/linter.vim b/autoload/ale/linter.vim index dbf9f221..114765e6 100644 --- a/autoload/ale/linter.vim +++ b/autoload/ale/linter.vim @@ -257,7 +257,17 @@ function! ale#linter#PreProcess(filetype, linter) abort let l:obj.initialization_options = a:linter.initialization_options endif - if has_key(a:linter, 'lsp_config') + if has_key(a:linter, 'lsp_config_callback') + if has_key(a:linter, 'lsp_config') + throw 'Only one of `lsp_config` or `lsp_config_callback` should be set' + endif + + let l:obj.lsp_config_callback = a:linter.lsp_config_callback + + if !s:IsCallback(l:obj.lsp_config_callback) + throw '`lsp_config_callback` must be a callback if defined' + endif + elseif has_key(a:linter, 'lsp_config') if type(a:linter.lsp_config) isnot v:t_dict throw '`lsp_config` must be a Dictionary' endif diff --git a/autoload/ale/lsp_linter.vim b/autoload/ale/lsp_linter.vim index fa4d2f86..2ffa6522 100644 --- a/autoload/ale/lsp_linter.vim +++ b/autoload/ale/lsp_linter.vim @@ -140,6 +140,18 @@ function! ale#lsp_linter#GetOptions(buffer, linter) abort return l:initialization_options endfunction +function! ale#lsp_linter#GetConfig(buffer, linter) abort + let l:config = {} + + if has_key(a:linter, 'lsp_config_callback') + let l:config = ale#util#GetFunction(a:linter.lsp_config_callback)(a:buffer) + elseif has_key(a:linter, 'lsp_config') + let l:config = a:linter.lsp_config + endif + + return l:config +endfunction + " Given a buffer, an LSP linter, start up an LSP linter and get ready to " receive messages for the document. function! ale#lsp_linter#StartLSP(buffer, linter) abort @@ -188,14 +200,16 @@ function! ale#lsp_linter#StartLSP(buffer, linter) abort call ale#lsp#MarkConnectionAsTsserver(l:conn_id) endif - let l:language_id = ale#util#GetFunction(a:linter.language_callback)(a:buffer) + let l:config = ale#lsp_linter#GetConfig(a:buffer, a:linter) - if !empty(get(a:linter, 'lsp_config')) + if !empty(l:config) " set LSP configuration options (workspace/didChangeConfiguration) - let l:config_message = ale#lsp#message#DidChangeConfiguration(a:buffer, a:linter.lsp_config) + let l:config_message = ale#lsp#message#DidChangeConfiguration(a:buffer, l:config) call ale#lsp#Send(l:conn_id, l:config_message) endif + let l:language_id = ale#util#GetFunction(a:linter.language_callback)(a:buffer) + let l:details = { \ 'buffer': a:buffer, \ 'connection_id': l:conn_id, diff --git a/doc/ale-development.txt b/doc/ale-development.txt index ac72d615..1e168130 100644 --- a/doc/ale-development.txt +++ b/doc/ale-development.txt @@ -306,6 +306,7 @@ given the above setup are as follows. `AssertLinterNotExecuted` - Check that linters will not be executed. `AssertLSPLanguage language` - Check the language given to an LSP server. `AssertLSPOptions options_dict` - Check the options given to an LSP server. +`AssertLSPConfig config_dict` - Check the config given to an LSP server. `AssertLSPProject project_root` - Check the root given to an LSP server. `AssertLSPAddress address` - Check the address to an LSP server. diff --git a/doc/ale.txt b/doc/ale.txt index 21fab16c..72de1cc5 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -2666,6 +2666,9 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* `initialization_options_callback` may be defined to pass initialization options to the LSP. + An optional `lsp_config` or `lsp_config_callback` may + be defined to pass configuration settings to the LSP. + `address_callback` A |String| or |Funcref| for a callback function accepting a buffer number. A |String| should be returned with an address to connect to. @@ -2726,6 +2729,16 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* This can be used in place of `initialization_options` when more complicated processing is needed. + `lsp_config` A |Dictionary| of configuration settings for LSPs. + This will be fed (as JSON) to the LSP in the + workspace/didChangeConfiguration command. + + `lsp_config_callback` A |String| or |Funcref| for a callback function + accepting a buffer number. A |Dictionary| should be + returned for configuration settings to pass the LSP. + This can be used in place of `lsp_config` when more + complicated processing is needed. + Only one of `command`, `command_callback`, or `command_chain` should be specified. `command_callback` is generally recommended when a command string needs to be generated dynamically, or any global options are used. diff --git a/test/command_callback/test_elixir_ls_command_callbacks.vader b/test/command_callback/test_elixir_ls_command_callbacks.vader index 0d00354b..f79be9b4 100644 --- a/test/command_callback/test_elixir_ls_command_callbacks.vader +++ b/test/command_callback/test_elixir_ls_command_callbacks.vader @@ -26,4 +26,5 @@ Execute(should set correct LSP values): AssertLSPLanguage 'elixir' AssertLSPOptions {} + AssertLSPConfig {} AssertLSPProject ale#path#Simplify(g:dir . '/mix_paths/wrapped_project') diff --git a/test/command_callback/test_golangserver_command_callback.vader b/test/command_callback/test_golangserver_command_callback.vader index ee88e1a4..90fdc26f 100644 --- a/test/command_callback/test_golangserver_command_callback.vader +++ b/test/command_callback/test_golangserver_command_callback.vader @@ -56,7 +56,7 @@ Execute(should set go-langserver for go app1): call ale#test#SetFilename('go_paths/go1/prj1/file.go') AssertLSPLanguage 'go' - AssertLSPOptions {} + AssertLSPConfig {} AssertLSPProject ale#path#Simplify(g:dir . '/go_paths/go1') Execute(should set go-langserver for go app2): @@ -64,4 +64,5 @@ Execute(should set go-langserver for go app2): AssertLSPLanguage 'go' AssertLSPOptions {} + AssertLSPConfig {} AssertLSPProject ale#path#Simplify(g:dir . '/go_paths/go2') diff --git a/test/command_callback/test_scala_sbtserver.vader b/test/command_callback/test_scala_sbtserver.vader index 1b708bd9..1c7d8472 100644 --- a/test/command_callback/test_scala_sbtserver.vader +++ b/test/command_callback/test_scala_sbtserver.vader @@ -9,11 +9,13 @@ Execute(should set sbtserver for sbt project with build.sbt): call ale#test#SetFilename('../scala_fixtures/valid_sbt_project/Main.scala') AssertLSPLanguage 'scala' AssertLSPOptions {} + AssertLSPConfig {} AssertLSPProject ale#path#Simplify(g:dir . 'command_callback/../scala_fixtures/valid_sbt_project') AssertLSPAddress '127.0.0.1:4273' Execute(should not set sbtserver for sbt project without build.sbt): call ale#test#SetFilename('../scala_fixtures/invalid_sbt_project/Main.scala') AssertLSPLanguage 'scala' AssertLSPOptions {} + AssertLSPConfig {} AssertLSPProject '' AssertLSPAddress '127.0.0.1:4273' diff --git a/test/test_linter_defintion_processing.vader b/test/test_linter_defintion_processing.vader index a28edf9e..d967761d 100644 --- a/test/test_linter_defintion_processing.vader +++ b/test/test_linter_defintion_processing.vader @@ -501,6 +501,31 @@ Execute(PreProcess should throw when initialization_options_callback is not a ca \}) AssertEqual '`initialization_options_callback` must be a callback if defined', g:vader_exception +Execute(PreProcess should complain about using lsp_config and lsp_config_callback together): + let g:linter = { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address_callback': 'X', + \ 'language': 'x', + \ 'project_root_callback': 'x', + \ 'lsp_config': 'x', + \ 'lsp_config_callback': 'x', + \} + + AssertThrows call ale#linter#PreProcess('testft', g:linter) + AssertEqual 'Only one of `lsp_config` or `lsp_config_callback` should be set', g:vader_exception + +Execute(PreProcess should throw when lsp_config_callback is not a callback): + AssertThrows call ale#linter#PreProcess('testft', { + \ 'name': 'foo', + \ 'lsp': 'socket', + \ 'address_callback': 'X', + \ 'language': 'x', + \ 'project_root_callback': 'x', + \ 'lsp_config_callback': {}, + \}) + AssertEqual '`lsp_config_callback` must be a callback if defined', g:vader_exception + Execute(PreProcess should accept LSP configuration options via lsp_config): let g:ale_lsp_configuration = { \ 'foo': 'bar' @@ -517,7 +542,6 @@ Execute(PreProcess should accept LSP configuration options via lsp_config): AssertEqual {'foo': 'bar'}, ale#linter#PreProcess('testft', g:linter).lsp_config - Execute(PreProcess should throw when lsp_config is not a Dictionary): AssertThrows call ale#linter#PreProcess('testft', { \ 'name': 'foo', @@ -528,4 +552,3 @@ Execute(PreProcess should throw when lsp_config is not a Dictionary): \ 'lsp_config': 'x', \}) AssertEqual '`lsp_config` must be a Dictionary', g:vader_exception -