Skip to content

Commit 7770b7d

Browse files
authored
Add public api get window/workDoneProgress (#979)
* impl lsp#get_progress() for statusline plugin. * add workDoneProgress spec link * refactor s:handle_work_done_progress() * [workDoneProgress] prevent to subscribe multiple times * [workDoneProgress] Fixed s:lsp_progress['percentage'] to always be float * [workDoneProgress] support multiple progress registration. * [workDoneProgress] Fixed s:lsp_progress['percentage'] to uinteger * [workDoneProgress] fix for vint * [workDoneProgress] rename variable * [workDoneProgress] add test * [workDoneProgress] write document * [workDoneProgress] initialize s:progress_ui when enable/disable * [workDoneProgress] refactor test code * [workDoneProgress] add lsp_progress_updated * [workDoneProgress] fix typo * [workDoneProgress] refactor token handling * [workDoneProgress] Fixed differences from specifications(messages->message) * [workDoneProgress] fix test
1 parent ee854b4 commit 7770b7d

File tree

4 files changed

+197
-0
lines changed

4 files changed

+197
-0
lines changed

autoload/lsp.vim

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ augroup _lsp_silent_
3131
autocmd User lsp_float_closed silent
3232
autocmd User lsp_buffer_enabled silent
3333
autocmd User lsp_diagnostics_updated silent
34+
autocmd User lsp_progress_updated silent
3435
augroup END
3536

3637
function! lsp#log_verbose(...) abort
@@ -65,6 +66,7 @@ function! lsp#enable() abort
6566
call lsp#internal#document_highlight#_enable()
6667
call lsp#internal#diagnostics#_enable()
6768
call lsp#internal#show_message_request#_enable()
69+
call lsp#internal#work_done_progress#_enable()
6870
call s:register_events()
6971
endfunction
7072

@@ -79,6 +81,7 @@ function! lsp#disable() abort
7981
call lsp#internal#document_highlight#_disable()
8082
call lsp#internal#diagnostics#_disable()
8183
call lsp#internal#show_message_request#_disable()
84+
call lsp#internal#work_done_progress#_disable()
8285
call s:unregister_events()
8386
let s:enabled = 0
8487
endfunction
@@ -1132,6 +1135,15 @@ function! lsp#get_buffer_first_error_line() abort
11321135
return lsp#ui#vim#diagnostics#get_buffer_first_error_line()
11331136
endfunction
11341137

1138+
" Return UI list with window/workDoneProgress
1139+
" The list is most recently update order.
1140+
" [{ 'server': 'clangd', 'token': 'backgroundIndexProgress', 'title': 'indexing', 'messages': '50/100', 'percentage': 50 },
1141+
" { 'server': 'rust-analyzer', 'token': 'rustAnalyzer/indexing', 'title': 'indexing', 'messages': '9/262 (std)', 'percentage': 3 }]
1142+
" 'percentage': 0 - 100 or not exist
1143+
function! lsp#get_progress() abort
1144+
return lsp#internal#work_done_progress#get_progress()
1145+
endfunction
1146+
11351147
function! s:merge_dict(dict_old, dict_new) abort
11361148
for l:key in keys(a:dict_new)
11371149
if has_key(a:dict_old, l:key) && type(a:dict_old[l:key]) == v:t_dict && type(a:dict_new[l:key]) == v:t_dict
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
" https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
2+
3+
let s:progress_ui = []
4+
let s:enabled = 0
5+
6+
function! lsp#internal#work_done_progress#_enable() abort
7+
if !g:lsp_work_done_progress_enabled | return | endif
8+
9+
if s:enabled | return | endif
10+
let s:enabled = 1
11+
let s:progress_ui = []
12+
13+
let s:Dispose = lsp#callbag#pipe(
14+
\ lsp#stream(),
15+
\ lsp#callbag#filter({x->has_key(x, 'response') && has_key(x['response'], 'method')
16+
\ && x['response']['method'] ==# '$/progress' && has_key(x['response'], 'params')
17+
\ && has_key(x['response']['params'], 'value') && type(x['response']['params']['value']) == type({})}),
18+
\ lsp#callbag#subscribe({'next': {x->s:handle_work_done_progress(x['server'], x['response'])}})
19+
\ )
20+
endfunction
21+
22+
function! s:handle_work_done_progress(server, response) abort
23+
let l:value = a:response['params']['value']
24+
let l:token = a:response['params']['token']
25+
let l:new = {
26+
\ 'server': a:server,
27+
\ 'token': l:token,
28+
\ 'title': '',
29+
\ 'message': '',
30+
\ }
31+
32+
if l:value['kind'] ==# 'end'
33+
let l:new['message'] = ''
34+
let l:new['percentage'] = 100
35+
call filter(s:progress_ui, {_, x->x['token'] !=# l:token || x['server'] !=# a:server})
36+
elseif l:value['kind'] ==# 'begin'
37+
let l:new['title'] = l:value['title']
38+
call filter(s:progress_ui, {_, x->x['token'] !=# l:token || x['server'] !=# a:server})
39+
call insert(s:progress_ui, l:new)
40+
elseif l:value['kind'] ==# 'report'
41+
let l:new['message'] = get(l:value, 'message', '')
42+
if has_key(l:value, 'percentage')
43+
" l:value['percentage'] is uinteger in specification.
44+
" But some implementation return float. (e.g. clangd11)
45+
" So we round it.
46+
let l:new['percentage'] = float2nr(l:value['percentage'] + 0.5)
47+
endif
48+
let l:idx = match(s:progress_ui, l:token)
49+
let l:new['title'] = s:progress_ui[l:idx]['title']
50+
call filter(s:progress_ui, {_, x->x['token'] !=# l:token || x['server'] !=# a:server})
51+
call insert(s:progress_ui, l:new)
52+
endif
53+
doautocmd <nomodeline> User lsp_progress_updated
54+
endfunction
55+
56+
function! lsp#internal#work_done_progress#_disable() abort
57+
if !s:enabled | return | endif
58+
59+
if exists('s:Dispose')
60+
call s:Dispose()
61+
unlet s:Dispose
62+
endif
63+
64+
let s:enabled = 0
65+
let s:progress_ui = []
66+
endfunction
67+
68+
function! lsp#internal#work_done_progress#get_progress() abort
69+
return s:progress_ui
70+
endfunction

doc/vim-lsp.txt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ CONTENTS *vim-lsp-contents*
7777
|lsp#utils#find_nearest_parent_file_directory()|
7878
lsp#get_buffer_diagnostics_counts() |lsp#get_buffer_diagnostics_counts()|
7979
lsp#get_buffer_first_error_line() |lsp#get_buffer_first_error_line()|
80+
lsp#get_progress() |lsp#get_progress()|
8081
Commands |vim-lsp-commands|
8182
LspCodeAction |:LspCodeAction|
8283
LspCodeActionSync |:LspCodeActionSync|
@@ -125,6 +126,7 @@ CONTENTS *vim-lsp-contents*
125126
lsp_server_exit |lsp_server_exit|
126127
lsp_buffer_enabled |lsp_buffer_enabled|
127128
lsp_diagnostics_updated |lsp_diagnostics_updated|
129+
lsp_progress_updated |lsp_progress_updated|
128130
Mappings |vim-lsp-mappings|
129131
<plug>(lsp-preview-close) |<plug>(lsp-preview-close)|
130132
<plug>(lsp-preview-focus) |<plug>(lsp-preview-focus)|
@@ -1273,6 +1275,23 @@ Get line number of first error in current buffer.
12731275

12741276
Returns |Number| or |v:null| if there are no errors.
12751277

1278+
lsp#get_progress() *lsp#get_progress()*
1279+
1280+
Return UI |List| of |Dict| with window/workDoneProgress
1281+
The |List| is most recently update order.
1282+
The |Dict| has keys as follows.
1283+
* server
1284+
Type: |String|
1285+
* token
1286+
Type: |String|
1287+
* title
1288+
Type: |String|
1289+
* messages
1290+
Type: |String|
1291+
* percentage
1292+
Type: |Number|
1293+
0 - 100 or not exist
1294+
12761295
==============================================================================
12771296
Commands *vim-lsp-commands*
12781297

@@ -1557,6 +1576,10 @@ processed by vim-lsp.
15571576
autocmd User lsp_diagnostics_updated call DoSomething()
15581577
augroup END
15591578
<
1579+
lsp_progress_updated *lsp_progress_updated*
1580+
1581+
This autocommand is run after every time after progress updated and
1582+
processed by vim-lsp. Used for statusline plugins.
15601583

15611584
==============================================================================
15621585
Mappings *vim-lsp-mappings*
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
Describe lsp#internal#work_done_progress
2+
Before each
3+
let g:lsp_work_done_progress_enabled = 1
4+
call lsp#internal#work_done_progress#_enable()
5+
End
6+
7+
After each
8+
let g:lsp_work_done_progress_enabled = 0
9+
call lsp#internal#work_done_progress#_disable()
10+
End
11+
12+
It should be able to subscribe to $progress stream
13+
let l:server1_response1 = {'method': '$/progress', 'params':{'token':'token text','value':{'kind':'begin', 'title':'title'}}}
14+
let l:server1_response2 = {'method': '$/progress', 'params':{'token':'token text','value':{'percentage':50,'message':'test message','kind':'report'}}}
15+
let l:server1_response3 = {'method': '$/progress', 'params':{'token':'token text','value':{'kind':'end'}}}
16+
17+
Assert Equals(lsp#internal#work_done_progress#get_progress(), [])
18+
19+
call lsp#stream(1, { 'server': 'server1', 'response': l:server1_response1 })
20+
Assert Equals(lsp#internal#work_done_progress#get_progress(),
21+
\ [{'message': '', 'token': 'token text', 'title': 'title', 'server': 'server1'}])
22+
23+
call lsp#stream(1, { 'server': 'server1', 'response': l:server1_response2 })
24+
Assert Equals(lsp#internal#work_done_progress#get_progress(),
25+
\ [{'message': 'test message', 'token': 'token text', 'percentage': 50, 'title': 'title', 'server': 'server1'}])
26+
27+
call lsp#stream(1, { 'server': 'server1', 'response': l:server1_response3 })
28+
Assert Equals(lsp#internal#work_done_progress#get_progress(), [])
29+
End
30+
31+
It should be able to subscribe to multi $progress stream
32+
let l:server1_response1 = {'method': '$/progress', 'params':{'token':'token text','value':{'kind':'begin', 'title':'title1'}}}
33+
let l:server1_response2 = {'method': '$/progress', 'params':{'token':'token text','value':{'percentage':50,'message':'msg1','kind':'report'}}}
34+
let l:server1_response3 = {'method': '$/progress', 'params':{'token':'token text','value':{'percentage':90,'message':'msg1','kind':'report'}}}
35+
let l:server1_response4 = {'method': '$/progress', 'params':{'token':'token text','value':{'kind':'end'}}}
36+
let l:server2_response1 = {'method': '$/progress', 'params':{'token':'server2_token','value':{'kind':'begin', 'title':'title2'}}}
37+
let l:server2_response2 = {'method': '$/progress', 'params':{'token':'server2_token','value':{'percentage':0,'message':'msg2','kind':'report'}}}
38+
let l:server2_response3 = {'method': '$/progress', 'params':{'token':'server2_token','value':{'kind':'end'}}}
39+
40+
Assert Equals(lsp#internal#work_done_progress#get_progress(), [])
41+
42+
call lsp#stream(1, { 'server': 'server1', 'response': l:server1_response1 })
43+
Assert Equals(lsp#internal#work_done_progress#get_progress(),
44+
\ [{'message': '', 'token': 'token text', 'title': 'title1', 'server': 'server1'}])
45+
46+
call lsp#stream(1, { 'server': 'server2', 'response': l:server2_response1 })
47+
Assert Equals(lsp#internal#work_done_progress#get_progress(),
48+
\ [{'message': '', 'token': 'server2_token', 'title': 'title2', 'server': 'server2'},
49+
\ {'message': '', 'token': 'token text', 'title': 'title1', 'server': 'server1'}])
50+
51+
call lsp#stream(1, { 'server': 'server2', 'response': l:server2_response2 })
52+
Assert Equals(lsp#internal#work_done_progress#get_progress(),
53+
\ [{'message': 'msg2', 'token': 'server2_token', 'percentage':0, 'title': 'title2', 'server': 'server2'},
54+
\ {'message': '', 'token': 'token text', 'title': 'title1', 'server': 'server1'}])
55+
56+
call lsp#stream(1, { 'server': 'server1', 'response': l:server1_response2 })
57+
Assert Equals(lsp#internal#work_done_progress#get_progress(),
58+
\ [{'message': 'msg1', 'token': 'token text', 'percentage':50, 'title': 'title1', 'server': 'server1'},
59+
\ {'message': 'msg2', 'token': 'server2_token', 'percentage':0, 'title': 'title2', 'server': 'server2'}])
60+
61+
call lsp#stream(1, { 'server': 'server1', 'response': l:server1_response3 })
62+
Assert Equals(lsp#internal#work_done_progress#get_progress(),
63+
\ [{'message': 'msg1', 'token': 'token text', 'percentage':90, 'title': 'title1', 'server': 'server1'},
64+
\ {'message': 'msg2', 'token': 'server2_token', 'percentage':0, 'title': 'title2', 'server': 'server2'}])
65+
66+
call lsp#stream(1, { 'server': 'server1', 'response': l:server1_response4 })
67+
Assert Equals(lsp#internal#work_done_progress#get_progress(),
68+
\ [{'message': 'msg2', 'token': 'server2_token', 'percentage':0, 'title': 'title2', 'server': 'server2'}])
69+
70+
call lsp#stream(1, { 'server': 'server2', 'response': l:server2_response3 })
71+
Assert Equals(lsp#internal#work_done_progress#get_progress(), [])
72+
End
73+
74+
It should be returned correctly even if percentage and message do not exist.
75+
let l:server1_response1 = {'method': '$/progress', 'params':{'token':'token text','value':{'kind':'begin', 'title':'title'}}}
76+
let l:server1_response2 = {'method': '$/progress', 'params':{'token':'token text','value':{'kind':'report'}}}
77+
let l:server1_response3 = {'method': '$/progress', 'params':{'token':'token text','value':{'kind':'end'}}}
78+
79+
Assert Equals(lsp#internal#work_done_progress#get_progress(), [])
80+
81+
call lsp#stream(1, { 'server': 'server1', 'response': l:server1_response1 })
82+
Assert Equals(lsp#internal#work_done_progress#get_progress(),
83+
\ [{'message': '', 'token': 'token text', 'title': 'title', 'server': 'server1'}])
84+
85+
call lsp#stream(1, { 'server': 'server1', 'response': l:server1_response2 })
86+
Assert Equals(lsp#internal#work_done_progress#get_progress(),
87+
\ [{'message': '', 'token': 'token text', 'title': 'title', 'server': 'server1'}])
88+
89+
call lsp#stream(1, { 'server': 'server1', 'response': l:server1_response3 })
90+
Assert Equals(lsp#internal#work_done_progress#get_progress(), [])
91+
End
92+
End

0 commit comments

Comments
 (0)