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
This commit is contained in:
micchy326
2021-01-01 03:51:20 +09:00
committed by GitHub
parent ee854b4c55
commit 7770b7d5a3
4 changed files with 197 additions and 0 deletions

View File

@@ -31,6 +31,7 @@ augroup _lsp_silent_
autocmd User lsp_float_closed silent
autocmd User lsp_buffer_enabled silent
autocmd User lsp_diagnostics_updated silent
autocmd User lsp_progress_updated silent
augroup END
function! lsp#log_verbose(...) abort
@@ -65,6 +66,7 @@ function! lsp#enable() abort
call lsp#internal#document_highlight#_enable()
call lsp#internal#diagnostics#_enable()
call lsp#internal#show_message_request#_enable()
call lsp#internal#work_done_progress#_enable()
call s:register_events()
endfunction
@@ -79,6 +81,7 @@ function! lsp#disable() abort
call lsp#internal#document_highlight#_disable()
call lsp#internal#diagnostics#_disable()
call lsp#internal#show_message_request#_disable()
call lsp#internal#work_done_progress#_disable()
call s:unregister_events()
let s:enabled = 0
endfunction
@@ -1132,6 +1135,15 @@ function! lsp#get_buffer_first_error_line() abort
return lsp#ui#vim#diagnostics#get_buffer_first_error_line()
endfunction
" Return UI list with window/workDoneProgress
" The list is most recently update order.
" [{ 'server': 'clangd', 'token': 'backgroundIndexProgress', 'title': 'indexing', 'messages': '50/100', 'percentage': 50 },
" { 'server': 'rust-analyzer', 'token': 'rustAnalyzer/indexing', 'title': 'indexing', 'messages': '9/262 (std)', 'percentage': 3 }]
" 'percentage': 0 - 100 or not exist
function! lsp#get_progress() abort
return lsp#internal#work_done_progress#get_progress()
endfunction
function! s:merge_dict(dict_old, dict_new) abort
for l:key in keys(a:dict_new)
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

View File

@@ -0,0 +1,70 @@
" https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
let s:progress_ui = []
let s:enabled = 0
function! lsp#internal#work_done_progress#_enable() abort
if !g:lsp_work_done_progress_enabled | return | endif
if s:enabled | return | endif
let s:enabled = 1
let s:progress_ui = []
let s:Dispose = lsp#callbag#pipe(
\ lsp#stream(),
\ lsp#callbag#filter({x->has_key(x, 'response') && has_key(x['response'], 'method')
\ && x['response']['method'] ==# '$/progress' && has_key(x['response'], 'params')
\ && has_key(x['response']['params'], 'value') && type(x['response']['params']['value']) == type({})}),
\ lsp#callbag#subscribe({'next': {x->s:handle_work_done_progress(x['server'], x['response'])}})
\ )
endfunction
function! s:handle_work_done_progress(server, response) abort
let l:value = a:response['params']['value']
let l:token = a:response['params']['token']
let l:new = {
\ 'server': a:server,
\ 'token': l:token,
\ 'title': '',
\ 'message': '',
\ }
if l:value['kind'] ==# 'end'
let l:new['message'] = ''
let l:new['percentage'] = 100
call filter(s:progress_ui, {_, x->x['token'] !=# l:token || x['server'] !=# a:server})
elseif l:value['kind'] ==# 'begin'
let l:new['title'] = l:value['title']
call filter(s:progress_ui, {_, x->x['token'] !=# l:token || x['server'] !=# a:server})
call insert(s:progress_ui, l:new)
elseif l:value['kind'] ==# 'report'
let l:new['message'] = get(l:value, 'message', '')
if has_key(l:value, 'percentage')
" l:value['percentage'] is uinteger in specification.
" But some implementation return float. (e.g. clangd11)
" So we round it.
let l:new['percentage'] = float2nr(l:value['percentage'] + 0.5)
endif
let l:idx = match(s:progress_ui, l:token)
let l:new['title'] = s:progress_ui[l:idx]['title']
call filter(s:progress_ui, {_, x->x['token'] !=# l:token || x['server'] !=# a:server})
call insert(s:progress_ui, l:new)
endif
doautocmd <nomodeline> User lsp_progress_updated
endfunction
function! lsp#internal#work_done_progress#_disable() abort
if !s:enabled | return | endif
if exists('s:Dispose')
call s:Dispose()
unlet s:Dispose
endif
let s:enabled = 0
let s:progress_ui = []
endfunction
function! lsp#internal#work_done_progress#get_progress() abort
return s:progress_ui
endfunction

View File

@@ -77,6 +77,7 @@ CONTENTS *vim-lsp-contents*
|lsp#utils#find_nearest_parent_file_directory()|
lsp#get_buffer_diagnostics_counts() |lsp#get_buffer_diagnostics_counts()|
lsp#get_buffer_first_error_line() |lsp#get_buffer_first_error_line()|
lsp#get_progress() |lsp#get_progress()|
Commands |vim-lsp-commands|
LspCodeAction |:LspCodeAction|
LspCodeActionSync |:LspCodeActionSync|
@@ -125,6 +126,7 @@ CONTENTS *vim-lsp-contents*
lsp_server_exit |lsp_server_exit|
lsp_buffer_enabled |lsp_buffer_enabled|
lsp_diagnostics_updated |lsp_diagnostics_updated|
lsp_progress_updated |lsp_progress_updated|
Mappings |vim-lsp-mappings|
<plug>(lsp-preview-close) |<plug>(lsp-preview-close)|
<plug>(lsp-preview-focus) |<plug>(lsp-preview-focus)|
@@ -1273,6 +1275,23 @@ Get line number of first error in current buffer.
Returns |Number| or |v:null| if there are no errors.
lsp#get_progress() *lsp#get_progress()*
Return UI |List| of |Dict| with window/workDoneProgress
The |List| is most recently update order.
The |Dict| has keys as follows.
* server
Type: |String|
* token
Type: |String|
* title
Type: |String|
* messages
Type: |String|
* percentage
Type: |Number|
0 - 100 or not exist
==============================================================================
Commands *vim-lsp-commands*
@@ -1557,6 +1576,10 @@ processed by vim-lsp.
autocmd User lsp_diagnostics_updated call DoSomething()
augroup END
<
lsp_progress_updated *lsp_progress_updated*
This autocommand is run after every time after progress updated and
processed by vim-lsp. Used for statusline plugins.
==============================================================================
Mappings *vim-lsp-mappings*

View File

@@ -0,0 +1,92 @@
Describe lsp#internal#work_done_progress
Before each
let g:lsp_work_done_progress_enabled = 1
call lsp#internal#work_done_progress#_enable()
End
After each
let g:lsp_work_done_progress_enabled = 0
call lsp#internal#work_done_progress#_disable()
End
It should be able to subscribe to $progress stream
let l:server1_response1 = {'method': '$/progress', 'params':{'token':'token text','value':{'kind':'begin', 'title':'title'}}}
let l:server1_response2 = {'method': '$/progress', 'params':{'token':'token text','value':{'percentage':50,'message':'test message','kind':'report'}}}
let l:server1_response3 = {'method': '$/progress', 'params':{'token':'token text','value':{'kind':'end'}}}
Assert Equals(lsp#internal#work_done_progress#get_progress(), [])
call lsp#stream(1, { 'server': 'server1', 'response': l:server1_response1 })
Assert Equals(lsp#internal#work_done_progress#get_progress(),
\ [{'message': '', 'token': 'token text', 'title': 'title', 'server': 'server1'}])
call lsp#stream(1, { 'server': 'server1', 'response': l:server1_response2 })
Assert Equals(lsp#internal#work_done_progress#get_progress(),
\ [{'message': 'test message', 'token': 'token text', 'percentage': 50, 'title': 'title', 'server': 'server1'}])
call lsp#stream(1, { 'server': 'server1', 'response': l:server1_response3 })
Assert Equals(lsp#internal#work_done_progress#get_progress(), [])
End
It should be able to subscribe to multi $progress stream
let l:server1_response1 = {'method': '$/progress', 'params':{'token':'token text','value':{'kind':'begin', 'title':'title1'}}}
let l:server1_response2 = {'method': '$/progress', 'params':{'token':'token text','value':{'percentage':50,'message':'msg1','kind':'report'}}}
let l:server1_response3 = {'method': '$/progress', 'params':{'token':'token text','value':{'percentage':90,'message':'msg1','kind':'report'}}}
let l:server1_response4 = {'method': '$/progress', 'params':{'token':'token text','value':{'kind':'end'}}}
let l:server2_response1 = {'method': '$/progress', 'params':{'token':'server2_token','value':{'kind':'begin', 'title':'title2'}}}
let l:server2_response2 = {'method': '$/progress', 'params':{'token':'server2_token','value':{'percentage':0,'message':'msg2','kind':'report'}}}
let l:server2_response3 = {'method': '$/progress', 'params':{'token':'server2_token','value':{'kind':'end'}}}
Assert Equals(lsp#internal#work_done_progress#get_progress(), [])
call lsp#stream(1, { 'server': 'server1', 'response': l:server1_response1 })
Assert Equals(lsp#internal#work_done_progress#get_progress(),
\ [{'message': '', 'token': 'token text', 'title': 'title1', 'server': 'server1'}])
call lsp#stream(1, { 'server': 'server2', 'response': l:server2_response1 })
Assert Equals(lsp#internal#work_done_progress#get_progress(),
\ [{'message': '', 'token': 'server2_token', 'title': 'title2', 'server': 'server2'},
\ {'message': '', 'token': 'token text', 'title': 'title1', 'server': 'server1'}])
call lsp#stream(1, { 'server': 'server2', 'response': l:server2_response2 })
Assert Equals(lsp#internal#work_done_progress#get_progress(),
\ [{'message': 'msg2', 'token': 'server2_token', 'percentage':0, 'title': 'title2', 'server': 'server2'},
\ {'message': '', 'token': 'token text', 'title': 'title1', 'server': 'server1'}])
call lsp#stream(1, { 'server': 'server1', 'response': l:server1_response2 })
Assert Equals(lsp#internal#work_done_progress#get_progress(),
\ [{'message': 'msg1', 'token': 'token text', 'percentage':50, 'title': 'title1', 'server': 'server1'},
\ {'message': 'msg2', 'token': 'server2_token', 'percentage':0, 'title': 'title2', 'server': 'server2'}])
call lsp#stream(1, { 'server': 'server1', 'response': l:server1_response3 })
Assert Equals(lsp#internal#work_done_progress#get_progress(),
\ [{'message': 'msg1', 'token': 'token text', 'percentage':90, 'title': 'title1', 'server': 'server1'},
\ {'message': 'msg2', 'token': 'server2_token', 'percentage':0, 'title': 'title2', 'server': 'server2'}])
call lsp#stream(1, { 'server': 'server1', 'response': l:server1_response4 })
Assert Equals(lsp#internal#work_done_progress#get_progress(),
\ [{'message': 'msg2', 'token': 'server2_token', 'percentage':0, 'title': 'title2', 'server': 'server2'}])
call lsp#stream(1, { 'server': 'server2', 'response': l:server2_response3 })
Assert Equals(lsp#internal#work_done_progress#get_progress(), [])
End
It should be returned correctly even if percentage and message do not exist.
let l:server1_response1 = {'method': '$/progress', 'params':{'token':'token text','value':{'kind':'begin', 'title':'title'}}}
let l:server1_response2 = {'method': '$/progress', 'params':{'token':'token text','value':{'kind':'report'}}}
let l:server1_response3 = {'method': '$/progress', 'params':{'token':'token text','value':{'kind':'end'}}}
Assert Equals(lsp#internal#work_done_progress#get_progress(), [])
call lsp#stream(1, { 'server': 'server1', 'response': l:server1_response1 })
Assert Equals(lsp#internal#work_done_progress#get_progress(),
\ [{'message': '', 'token': 'token text', 'title': 'title', 'server': 'server1'}])
call lsp#stream(1, { 'server': 'server1', 'response': l:server1_response2 })
Assert Equals(lsp#internal#work_done_progress#get_progress(),
\ [{'message': '', 'token': 'token text', 'title': 'title', 'server': 'server1'}])
call lsp#stream(1, { 'server': 'server1', 'response': l:server1_response3 })
Assert Equals(lsp#internal#work_done_progress#get_progress(), [])
End
End