From 44608334f9e71cbee2ad5f4dd436af66ffc313dc Mon Sep 17 00:00:00 2001 From: Illia Bobyr Date: Mon, 3 Apr 2023 17:16:42 -0700 Subject: [PATCH] Allow `workspace_config` to be a function (#1400) It is sometimes useful to generate `workspace_config` using a callback. For example, if the produced config needs to include some information from the actual workspace. --- autoload/lsp.vim | 12 +- autoload/lsp/utils/workspace_config.vim | 25 +++- doc/vim-lsp.txt | 3 +- test/lsp/utils/workspace_config.vimspec | 183 ++++++++++++++++++++++++ 4 files changed, 218 insertions(+), 5 deletions(-) create mode 100644 test/lsp/utils/workspace_config.vimspec diff --git a/autoload/lsp.vim b/autoload/lsp.vim index 34fb6154..65d79f30 100644 --- a/autoload/lsp.vim +++ b/autoload/lsp.vim @@ -716,7 +716,7 @@ function! s:ensure_conf(buf, server_name, cb) abort call s:send_notification(a:server_name, { \ 'method': 'workspace/didChangeConfiguration', \ 'params': { - \ 'settings': l:server_info['workspace_config'], + \ 'settings': lsp#utils#workspace_config#get(a:server_name), \ } \ }) endif @@ -934,7 +934,8 @@ function! s:on_request(server_name, id, request) abort call lsp#utils#workspace_edit#apply_workspace_edit(a:request['params']['edit']) call s:send_response(a:server_name, { 'id': a:request['id'], 'result': { 'applied': v:true } }) elseif a:request['method'] ==# 'workspace/configuration' - let l:response_items = map(a:request['params']['items'], { key, val -> lsp#utils#workspace_config#get_value(a:server_name, val) }) + let l:config = lsp#utils#workspace_config#get(a:server_name) + let l:response_items = map(a:request['params']['items'], { key, val -> lsp#utils#workspace_config#projection(l:config, val) }) call s:send_response(a:server_name, { 'id': a:request['id'], 'result': l:response_items }) elseif a:request['method'] ==# 'workspace/workspaceFolders' let l:server_info = s:servers[a:server_name]['server_info'] @@ -1309,6 +1310,13 @@ function! lsp#update_workspace_config(server_name, workspace_config) abort let l:server = s:servers[a:server_name] let l:server_info = l:server['server_info'] if has_key(l:server_info, 'workspace_config') + if type(l:server_info['workspace_config']) == v:t_func + call lsp#utils#error('''workspace_config'' is a function, so + \ lsp#update_workspace_config() can not be used. Either + \ replace function with a dictionary, or adjust the value + \ generated by the function as necessary.') + return + endif call s:merge_dict(l:server_info['workspace_config'], a:workspace_config) else let l:server_info['workspace_config'] = a:workspace_config diff --git a/autoload/lsp/utils/workspace_config.vim b/autoload/lsp/utils/workspace_config.vim index 5da8541e..0304f29f 100644 --- a/autoload/lsp/utils/workspace_config.vim +++ b/autoload/lsp/utils/workspace_config.vim @@ -1,7 +1,23 @@ -function! lsp#utils#workspace_config#get_value(server_name, item) abort +function! lsp#utils#workspace_config#get(server_name) abort try let l:server_info = lsp#get_server_info(a:server_name) - let l:config = l:server_info['workspace_config'] + let l:config_type = type(l:server_info['workspace_config']) + + if l:config_type == v:t_func + let l:config = l:server_info['workspace_config'](l:server_info) + else + let l:config = l:server_info['workspace_config'] + endif + + return l:config + catch + return v:null + endtry +endfunction + +function! lsp#utils#workspace_config#projection(config, item) abort + try + let l:config = a:config for l:section in split(a:item['section'], '\.') let l:config = l:config[l:section] @@ -12,3 +28,8 @@ function! lsp#utils#workspace_config#get_value(server_name, item) abort return v:null endtry endfunction + +function! lsp#utils#workspace_config#get_value(server_name, item) abort + let l:config = lsp#utils#workspace_config#get(a:server_name) + return lsp#utils#workspace_config#projection(l:config, a:item) +endfunction diff --git a/doc/vim-lsp.txt b/doc/vim-lsp.txt index cc6daed1..6ce04bf9 100644 --- a/doc/vim-lsp.txt +++ b/doc/vim-lsp.txt @@ -1281,7 +1281,8 @@ The vim |dict| containing information about the server. 'blocklist': ['javascript', 'typescript'] < * workspace_config: - optional vim |dict| + optional + vim |dict| or a function returning a vim |dict| Used to pass workspace configuration to the server after initialization. Configuration settings are language-server specific. diff --git a/test/lsp/utils/workspace_config.vimspec b/test/lsp/utils/workspace_config.vimspec new file mode 100644 index 00000000..f6de9336 --- /dev/null +++ b/test/lsp/utils/workspace_config.vimspec @@ -0,0 +1,183 @@ +Describe lsp#utils#workspace_config + + Describe lsp#utils#workspace_config#get + It should return the workspace config, when it is a dict + let l:name = 'Unit Test Server' + + call lsp#register_server({ + \ 'name': l:name, + \ 'workspace_config': { + \ 'a': { + \ 'a1': v:true, + \ 'a2': { + \ 'a21': 'disabled', + \ }, + \ }, + \ 'b': 'path/to/file', + \ } + \ }) + + let l:config = lsp#utils#workspace_config#get(l:name) + + Assert Equals(l:config['a']['a1'], v:true) + Assert Equals(l:config['a']['a2']['a21'], 'disabled') + Assert Equals(l:config['b'], 'path/to/file') + end + + It should return the workspace config, produced by a callback + let l:name = 'Unit Test Server' + + let l:callResult = {} + + call lsp#register_server({ + \ 'name': l:name, + \ 'workspace_config': {server_info->l:callResult}, + \ }) + + let l:config = lsp#utils#workspace_config#get(l:name) + Assert Equals(l:config, {}) + + let l:callResult = { + \ 'a': { + \ 'a1': v:true, + \ 'a2': { + \ 'a21': 'disabled', + \ }, + \ }, + \ 'b': 'path/to/file' + \ } + + let l:config = lsp#utils#workspace_config#get(l:name) + Assert Equals(l:config['a']['a1'], v:true) + Assert Equals(l:config['a']['a2']['a21'], 'disabled') + Assert Equals(l:config['b'], 'path/to/file') + end + End + + Describe lsp#utils#workspace_config#project + It should return a projection of a dictionary + let l:config = { + \ 'a': { + \ 'a1': v:true, + \ 'a2': { + \ 'a21': 'disabled', + \ }, + \ }, + \ 'b': 'path/to/file', + \ } + + let l:config_a_a1 = lsp#utils#workspace_config#projection( + \ l:config, + \ { 'section': 'a.a1' }, + \ ) + let l:config_a_a2_a21 = lsp#utils#workspace_config#projection( + \ l:config, + \ { 'section': 'a.a2.a21' }, + \ ) + let l:config_b = lsp#utils#workspace_config#projection( + \ l:config, + \ { 'section': 'b' }, + \ ) + let l:config_c = lsp#utils#workspace_config#projection( + \ l:config, + \ { 'section': 'c' }, + \ ) + + Assert Equals(l:config_a_a1, v:true) + Assert Equals(l:config_a_a2_a21, 'disabled') + Assert Equals(l:config_b, 'path/to/file') + Assert Equals(l:config_c, v:null) + end + End + + Describe lsp#utils#workspace_config#get_value + It should return a projection of the workspace config, when it is a dict + let l:name = 'Unit Test Server' + + call lsp#register_server({ + \ 'name': l:name, + \ 'workspace_config': { + \ 'a': { + \ 'a1': v:true, + \ 'a2': { + \ 'a21': 'disabled', + \ }, + \ }, + \ 'b': "path/to/file", + \ } + \ }) + + let l:config_a_a1 = lsp#utils#workspace_config#get_value( + \ l:name, + \ { 'section': 'a.a1' }, + \ ) + let l:config_a_a2_a21 = lsp#utils#workspace_config#get_value( + \ l:name, + \ { 'section': 'a.a2.a21' }, + \ ) + let l:config_b = lsp#utils#workspace_config#get_value( + \ l:name, + \ { 'section': 'b' }, + \ ) + let l:config_c = lsp#utils#workspace_config#get_value( + \ l:name, + \ { 'section': 'c' }, + \ ) + + Assert Equals(l:config_a_a1, v:true) + Assert Equals(l:config_a_a2_a21, 'disabled') + Assert Equals(l:config_b, 'path/to/file') + Assert Equals(l:config_c, v:null) + end + + It should return a projection of the workspace config, produced by a callback + let l:name = 'Unit Test Server' + + let l:callResult = {} + + call lsp#register_server({ + \ 'name': l:name, + \ 'workspace_config': {server_info->l:callResult}, + \ }) + + let l:config = lsp#utils#workspace_config#get_value( + \ l:name, + \ { 'section': '' }, + \ ) + Assert Equals(l:config, {}) + + let l:callResult = { + \ 'a': { + \ 'a1': v:true, + \ 'a2': { + \ 'a21': 'disabled', + \ }, + \ }, + \ 'b': "path/to/file", + \ } + + let l:config_a_a1 = lsp#utils#workspace_config#get_value( + \ l:name, + \ { 'section': 'a.a1' }, + \ ) + let l:config_a_a2_a21 = lsp#utils#workspace_config#get_value( + \ l:name, + \ { 'section': 'a.a2.a21' }, + \ ) + let l:config_b = lsp#utils#workspace_config#get_value( + \ l:name, + \ { 'section': 'b' }, + \ ) + let l:config_c = lsp#utils#workspace_config#get_value( + \ l:name, + \ { 'section': 'c' }, + \ ) + + Assert Equals(l:config_a_a1, v:true) + Assert Equals(l:config_a_a2_a21, 'disabled') + Assert Equals(l:config_b, 'path/to/file') + Assert Equals(l:config_c, v:null) + end + End +End +