X-Git-Url: https://git.madduck.net/etc/vim.git/blobdiff_plain/5a4872f466ebd76ddd532bdf2798554421c53df4..fe3919e725e156d751069662d11e38f7b4791de1:/.vim/bundle/vim-lsp/autoload/lsp.vim diff --git a/.vim/bundle/vim-lsp/autoload/lsp.vim b/.vim/bundle/vim-lsp/autoload/lsp.vim new file mode 100644 index 00000000..dbb9d77b --- /dev/null +++ b/.vim/bundle/vim-lsp/autoload/lsp.vim @@ -0,0 +1,1364 @@ +let s:enabled = 0 +let s:already_setup = 0 +let s:Stream = lsp#callbag#makeSubject() +" workspace_folders = { 'uri': { uri, name } } +let s:servers = {} " { lsp_id, server_info, workspace_folders, init_callbacks, init_result, buffers: { path: { changed_tick } } +let s:last_command_id = 0 +let s:notification_callbacks = [] " { name, callback } + +" This hold previous content for each language servers to make +" DidChangeTextDocumentParams. The key is buffer numbers: +" { +" 1: { +" "golsp": [ "first-line", "next-line", ... ], +" "bingo": [ "first-line", "next-line", ... ] +" }, +" 2: { +" "pylsp": [ "first-line", "next-line", ... ] +" } +" } +let s:file_content = {} + +" do nothing, place it here only to avoid the message +augroup _lsp_silent_ + autocmd! + autocmd User lsp_setup silent + autocmd User lsp_register_server silent + autocmd User lsp_unregister_server silent + autocmd User lsp_server_init silent + autocmd User lsp_server_exit silent + autocmd User lsp_complete_done silent + autocmd User lsp_float_opened silent + autocmd User lsp_float_closed silent + autocmd User lsp_float_focused 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 + if g:lsp_log_verbose + call call(function('lsp#log'), a:000) + endif +endfunction + +function! lsp#log(...) abort + if !empty(g:lsp_log_file) + call writefile([strftime('%c') . ':' . json_encode(a:000)], g:lsp_log_file, 'a') + endif +endfunction + +function! lsp#enable() abort + if s:enabled + return + endif + if !s:already_setup + doautocmd User lsp_setup + let s:already_setup = 1 + endif + let s:enabled = 1 + if g:lsp_signature_help_enabled + call lsp#ui#vim#signature_help#setup() + endif + call lsp#ui#vim#completion#_setup() + call lsp#internal#document_highlight#_enable() + call lsp#internal#diagnostics#_enable() + call lsp#internal#document_code_action#signs#_enable() + call lsp#internal#semantic#_enable() + call lsp#internal#show_message_request#_enable() + call lsp#internal#show_message#_enable() + call lsp#internal#work_done_progress#_enable() + call lsp#internal#completion#documentation#_enable() + call lsp#internal#inlay_hints#_enable() + call s:register_events() +endfunction + +function! lsp#disable() abort + if !s:enabled + return + endif + call lsp#ui#vim#signature_help#_disable() + call lsp#ui#vim#completion#_disable() + call lsp#internal#document_highlight#_disable() + call lsp#internal#diagnostics#_disable() + call lsp#internal#document_code_action#signs#_disable() + call lsp#internal#semantic#_disable() + call lsp#internal#show_message_request#_disable() + call lsp#internal#show_message#_disable() + call lsp#internal#work_done_progress#_disable() + call lsp#internal#completion#documentation#_disable() + call s:unregister_events() + let s:enabled = 0 +endfunction + +function! lsp#get_server_names() abort + return keys(s:servers) +endfunction + +function! lsp#is_valid_server_name(name) abort + return has_key(s:servers, a:name) +endfunction + +function! lsp#get_server_info(server_name) abort + return get(get(s:servers, a:server_name, {}), 'server_info', {}) +endfunction + +function! lsp#get_server_root_uri(server_name) abort + return get(s:servers[a:server_name]['server_info'], '_root_uri_resolved', '') +endfunction + +function! lsp#get_server_capabilities(server_name) abort + let l:server = s:servers[a:server_name] + return has_key(l:server, 'init_result') ? l:server['init_result']['result']['capabilities'] : {} +endfunction + +function! s:server_status(server_name) abort + if !has_key(s:servers, a:server_name) + return 'unknown server' + endif + let l:server = s:servers[a:server_name] + if has_key(l:server, 'exited') + return 'exited' + endif + if has_key(l:server, 'init_callbacks') + return 'starting' + endif + if has_key(l:server, 'failed') + return 'failed' + endif + if has_key(l:server, 'init_result') + return 'running' + endif + return 'not running' +endfunction + +function! lsp#is_server_running(name) abort + if !has_key(s:servers, a:name) + return 0 + endif + + let l:server = s:servers[a:name] + + return has_key(l:server, 'init_result') + \ && !has_key(l:server, 'exited') + \ && !has_key(l:server, 'init_callbacks') + \ && !has_key(l:server, 'failed') +endfunction + +" Returns the current status of all servers (if called with no arguments) or +" the given server (if given an argument). Can be one of "unknown server", +" "exited", "starting", "failed", "running", "not running" +function! lsp#get_server_status(...) abort + if a:0 == 0 + let l:strs = map(keys(s:servers), {k, v -> v . ': ' . s:server_status(v)}) + return join(l:strs, "\n") + else + return s:server_status(a:1) + endif +endfunction + +let s:color_map = { +\ 'exited': 'Error', +\ 'starting': 'MoreMsg', +\ 'failed': 'WarningMsg', +\ 'running': 'Keyword', +\ 'not running': 'Comment' +\} + +" Collect the current status of all servers +function! lsp#collect_server_status() abort + let l:results = {} + for l:k in keys(s:servers) + let l:status = s:server_status(l:k) + " Copy to prevent callers from corrupting our config. + let l:info = deepcopy(s:servers[l:k].server_info) + let l:results[l:k] = { + \ 'status': l:status, + \ 'info': l:info, + \ } + endfor + return l:results +endfunction + +" Print the current status of all servers +function! lsp#print_server_status() abort + for l:k in sort(keys(s:servers)) + let l:status = s:server_status(l:k) + echon l:k . ': ' + exec 'echohl' s:color_map[l:status] + echon l:status + echohl None + echo '' + if &verbose + let l:cfg = { 'workspace_config': s:servers[l:k].server_info.workspace_config } + if get(g:, 'loaded_scriptease', 0) + call scriptease#pp_command(0, -1, l:cfg) + else + echo json_encode(l:cfg) + endif + echo '' + endif + endfor +endfunction + +" @params {server_info} = { +" 'name': 'go-langserver', " required, must be unique +" 'allowlist': ['go'], " optional, array of filetypes to allow, * for all filetypes +" 'blocklist': [], " optional, array of filetypes to block, * for all filetypes, +" 'cmd': {server_info->['go-langserver]} " function that takes server_info and returns array of cmd and args, return empty if you don't want to start the server +" } +function! lsp#register_server(server_info) abort + let l:server_name = a:server_info['name'] + if has_key(s:servers, l:server_name) + call lsp#log('lsp#register_server', 'server already registered', l:server_name) + endif + " NOTE: workspace_folders is dict for faster lookup instead of array + let s:servers[l:server_name] = { + \ 'server_info': a:server_info, + \ 'lsp_id': 0, + \ 'buffers': {}, + \ 'workspace_folders': {}, + \ } + call lsp#log('lsp#register_server', 'server registered', l:server_name) + doautocmd User lsp_register_server +endfunction + +" +" lsp#register_command +" +" @param {command_name} = string +" @param {callback} = funcref +" +function! lsp#register_command(command_name, callback) abort + call lsp#ui#vim#execute_command#_register(a:command_name, a:callback) +endfunction + +function! lsp#register_notifications(name, callback) abort + call add(s:notification_callbacks, { 'name': a:name, 'callback': a:callback }) +endfunction + +function! lsp#unregister_notifications(name) abort + " TODO +endfunction + +function! lsp#stop_server(server_name) abort + if has_key(s:servers, a:server_name) && s:servers[a:server_name]['lsp_id'] > 0 + call lsp#client#stop(s:servers[a:server_name]['lsp_id']) + endif +endfunction + +function! s:register_events() abort + augroup lsp + autocmd! + autocmd BufNewFile * call s:on_text_document_did_open() + autocmd BufReadPost * call s:on_text_document_did_open() + autocmd BufWritePost * call s:on_text_document_did_save() + autocmd BufWinLeave * call s:on_text_document_did_close() + autocmd BufWipeout * call s:on_buf_wipeout(expand('')) + autocmd InsertLeave * call s:on_text_document_did_change() + autocmd TextChanged * call s:on_text_document_did_change() + if exists('##TextChangedP') + autocmd TextChangedP * call s:on_text_document_did_change() + endif + if g:lsp_untitled_buffer_enabled + autocmd FileType * call s:on_filetype_changed(bufnr(expand(''))) + endif + augroup END + + for l:bufnr in range(1, bufnr('$')) + if bufexists(l:bufnr) && bufloaded(l:bufnr) + call s:on_text_document_did_open(l:bufnr) + endif + endfor +endfunction + +function! s:unregister_events() abort + augroup lsp + autocmd! + augroup END + doautocmd User lsp_unregister_server +endfunction + +function! s:on_filetype_changed(buf) abort + call s:on_buf_wipeout(a:buf) + " TODO: stop unused servers + call s:on_text_document_did_open() +endfunction + +function! s:on_text_document_did_open(...) abort + let l:buf = a:0 > 0 ? a:1 : bufnr('%') + if getbufvar(l:buf, '&buftype') ==# 'terminal' | return | endif + if getcmdwintype() !=# '' | return | endif + call lsp#log('s:on_text_document_did_open()', l:buf, &filetype, getcwd(), lsp#utils#get_buffer_uri(l:buf)) + + " Some language server notify diagnostics to the buffer that has not been loaded yet. + " This diagnostics was stored `autoload/lsp/internal/diagnostics/state.vim` but not highlighted. + " So we should refresh highlights when buffer opened. + call lsp#internal#diagnostics#state#_force_notify_buffer(l:buf) + + for l:server_name in lsp#get_allowed_servers(l:buf) + call s:ensure_flush(l:buf, l:server_name, function('s:fire_lsp_buffer_enabled', [l:server_name, l:buf])) + endfor +endfunction + +function! lsp#activate() abort + call s:on_text_document_did_open() +endfunction + +function! s:on_text_document_did_save() abort + let l:buf = bufnr('%') + if getbufvar(l:buf, '&buftype') ==# 'terminal' | return | endif + call lsp#log('s:on_text_document_did_save()', l:buf) + for l:server_name in lsp#get_allowed_servers(l:buf) + if g:lsp_text_document_did_save_delay >= 0 + " We delay the callback by one loop iteration as calls to ensure_flush + " can introduce mmap'd file locks that linger on Windows and collide + " with the second lang server call preventing saves (see #455) + call s:ensure_flush(l:buf, l:server_name, {result->timer_start(g:lsp_text_document_did_save_delay, {timer->s:call_did_save(l:buf, l:server_name, result, function('s:Noop'))})}) + else + call s:ensure_flush(l:buf, l:server_name, {result->s:call_did_save(l:buf, l:server_name, result, function('s:Noop'))}) + endif + endfor +endfunction + +function! s:on_text_document_did_change() abort + let l:buf = bufnr('%') + if getbufvar(l:buf, '&buftype') ==# 'terminal' | return | endif + call lsp#log('s:on_text_document_did_change()', l:buf) + call s:add_didchange_queue(l:buf) +endfunction + +function! s:call_did_save(buf, server_name, result, cb) abort + if lsp#client#is_error(a:result['response']) + return + endif + + let l:server = s:servers[a:server_name] + let l:path = lsp#utils#get_buffer_uri(a:buf) + + let [l:supports_did_save, l:did_save_options] = lsp#capabilities#get_text_document_save_registration_options(a:server_name) + if !l:supports_did_save + let l:msg = s:new_rpc_success('---> ignoring textDocument/didSave. not supported by server', { 'server_name': a:server_name, 'path': l:path }) + call lsp#log(l:msg) + call a:cb(l:msg) + return + endif + + call s:update_file_content(a:buf, a:server_name, lsp#utils#buffer#_get_lines(a:buf)) + + let l:buffers = l:server['buffers'] + let l:buffer_info = l:buffers[l:path] + + let l:params = { + \ 'textDocument': s:get_text_document_identifier(a:buf), + \ } + + if l:did_save_options['includeText'] + let l:params['text'] = s:get_text_document_text(a:buf, a:server_name) + endif + call s:send_notification(a:server_name, { + \ 'method': 'textDocument/didSave', + \ 'params': l:params, + \ }) + + let l:msg = s:new_rpc_success('textDocument/didSave sent', { 'server_name': a:server_name, 'path': l:path }) + call lsp#log(l:msg) + call a:cb(l:msg) +endfunction + +function! s:on_text_document_did_close() abort + let l:buf = bufnr('%') + if getbufvar(l:buf, '&buftype') ==# 'terminal' | return | endif + call lsp#log('s:on_text_document_did_close()', l:buf) +endfunction + +function! s:get_last_file_content(buf, server_name) abort + if has_key(s:file_content, a:buf) && has_key(s:file_content[a:buf], a:server_name) + return s:file_content[a:buf][a:server_name] + endif + return [] +endfunction + +function! s:update_file_content(buf, server_name, new) abort + if !has_key(s:file_content, a:buf) + let s:file_content[a:buf] = {} + endif + call lsp#log('s:update_file_content()', a:buf) + let s:file_content[a:buf][a:server_name] = a:new +endfunction + +function! s:on_buf_wipeout(buf) abort + if has_key(s:file_content, a:buf) + call remove(s:file_content, a:buf) + endif +endfunction + +function! s:ensure_flush_all(buf, server_names) abort + for l:server_name in a:server_names + call s:ensure_flush(a:buf, l:server_name, function('s:Noop')) + endfor +endfunction + +function! s:fire_lsp_buffer_enabled(server_name, buf, ...) abort + if a:buf == bufnr('%') + doautocmd User lsp_buffer_enabled + else + " Not using ++once in autocmd for compatibility of VIM8.0 + let l:cmd = printf('autocmd BufEnter doautocmd User lsp_buffer_enabled', a:buf) + exec printf('augroup _lsp_fire_buffer_enabled | exec "%s | autocmd! _lsp_fire_buffer_enabled BufEnter " | augroup END', l:cmd) + endif +endfunction + +function! s:Noop(...) abort +endfunction + +function! s:is_step_error(s) abort + return lsp#client#is_error(a:s.result[0]['response']) +endfunction + +function! s:throw_step_error(s) abort + call a:s.callback(a:s.result[0]) +endfunction + +function! s:new_rpc_success(message, data) abort + return { + \ 'response': { + \ 'message': a:message, + \ 'data': extend({ '__data__': 'vim-lsp'}, a:data), + \ } + \ } +endfunction + +function! s:new_rpc_error(message, data) abort + return { + \ 'response': { + \ 'error': { + \ 'code': 0, + \ 'message': a:message, + \ 'data': extend({ '__error__': 'vim-lsp'}, a:data), + \ }, + \ } + \ } +endfunction + +function! s:ensure_flush(buf, server_name, cb) abort + call lsp#utils#step#start([ + \ {s->s:ensure_start(a:buf, a:server_name, s.callback)}, + \ {s->s:is_step_error(s) ? s:throw_step_error(s) : s:ensure_init(a:buf, a:server_name, s.callback)}, + \ {s->s:is_step_error(s) ? s:throw_step_error(s) : s:ensure_conf(a:buf, a:server_name, s.callback)}, + \ {s->s:is_step_error(s) ? s:throw_step_error(s) : s:ensure_open(a:buf, a:server_name, s.callback)}, + \ {s->s:is_step_error(s) ? s:throw_step_error(s) : s:ensure_changed(a:buf, a:server_name, s.callback)}, + \ {s->a:cb(s.result[0])} + \ ]) +endfunction + +function! s:ensure_start(buf, server_name, cb) abort + let l:path = lsp#utils#get_buffer_path(a:buf) + + if lsp#utils#is_remote_uri(l:path) || !has_key(s:servers, a:server_name) + let l:msg = s:new_rpc_error('ignoring start server due to remote uri', { 'server_name': a:server_name, 'uri': l:path}) + call lsp#log(l:msg) + call a:cb(l:msg) + return + endif + + let l:server = s:servers[a:server_name] + let l:server_info = l:server['server_info'] + if l:server['lsp_id'] > 0 + let l:msg = s:new_rpc_success('server already started', { 'server_name': a:server_name }) + call lsp#log_verbose(l:msg) + call a:cb(l:msg) + return + endif + + if has_key(l:server_info, 'tcp') + let l:tcp = l:server_info['tcp'](l:server_info) + let l:lsp_id = lsp#client#start({ + \ 'tcp': l:tcp, + \ 'on_stderr': function('s:on_stderr', [a:server_name]), + \ 'on_exit': function('s:on_exit', [a:server_name]), + \ 'on_notification': function('s:on_notification', [a:server_name]), + \ 'on_request': function('s:on_request', [a:server_name]), + \ }) + elseif has_key(l:server_info, 'cmd') + let l:cmd_type = type(l:server_info['cmd']) + if l:cmd_type == v:t_list + let l:cmd = l:server_info['cmd'] + else + let l:cmd = l:server_info['cmd'](l:server_info) + endif + + if empty(l:cmd) + let l:msg = s:new_rpc_error('ignore server start since cmd is empty', { 'server_name': a:server_name }) + call lsp#log(l:msg) + call a:cb(l:msg) + return + endif + + call lsp#log('Starting server', a:server_name, l:cmd) + let l:opts = { + \ 'cmd': l:cmd, + \ 'on_stderr': function('s:on_stderr', [a:server_name]), + \ 'on_exit': function('s:on_exit', [a:server_name]), + \ 'on_notification': function('s:on_notification', [a:server_name]), + \ 'on_request': function('s:on_request', [a:server_name]), + \ } + if has_key(l:server_info, 'env') + let l:opts.env = l:server_info.env + endif + let l:lsp_id = lsp#client#start(l:opts) + endif + + if l:lsp_id > 0 + let l:server['lsp_id'] = l:lsp_id + let l:msg = s:new_rpc_success('started lsp server successfully', { 'server_name': a:server_name, 'lsp_id': l:lsp_id }) + call lsp#log(l:msg) + call a:cb(l:msg) + else + let l:msg = s:new_rpc_error('failed to start server', { 'server_name': a:server_name, 'cmd': l:cmd }) + call lsp#log(l:msg) + call a:cb(l:msg) + endif +endfunction + +function! lsp#default_get_supported_capabilities(server_info) abort + " Sorted alphabetically + return { + \ 'textDocument': { + \ 'callHierarchy': { + \ 'dynamicRegistration': v:false, + \ }, + \ 'codeAction': { + \ 'dynamicRegistration': v:false, + \ 'codeActionLiteralSupport': { + \ 'codeActionKind': { + \ 'valueSet': ['', 'quickfix', 'refactor', 'refactor.extract', 'refactor.inline', 'refactor.rewrite', 'source', 'source.organizeImports'], + \ } + \ }, + \ 'isPreferredSupport': v:true, + \ 'disabledSupport': v:true, + \ }, + \ 'codeLens': { + \ 'dynamicRegistration': v:false, + \ }, + \ 'completion': { + \ 'dynamicRegistration': v:false, + \ 'completionItem': { + \ 'documentationFormat': ['markdown', 'plaintext'], + \ 'snippetSupport': v:false, + \ 'resolveSupport': { + \ 'properties': ['additionalTextEdits'] + \ } + \ }, + \ 'completionItemKind': { + \ 'valueSet': lsp#omni#get_completion_item_kinds() + \ } + \ }, + \ 'declaration': { + \ 'dynamicRegistration': v:false, + \ 'linkSupport' : v:true + \ }, + \ 'definition': { + \ 'dynamicRegistration': v:false, + \ 'linkSupport' : v:true + \ }, + \ 'documentHighlight': { + \ 'dynamicRegistration': v:false, + \ }, + \ 'documentSymbol': { + \ 'dynamicRegistration': v:false, + \ 'symbolKind': { + \ 'valueSet': lsp#ui#vim#utils#get_symbol_kinds() + \ }, + \ 'hierarchicalDocumentSymbolSupport': v:false, + \ 'labelSupport': v:false + \ }, + \ 'foldingRange': { + \ 'dynamicRegistration': v:false, + \ 'lineFoldingOnly': v:true, + \ 'rangeLimit': 5000, + \ }, + \ 'formatting': { + \ 'dynamicRegistration': v:false, + \ }, + \ 'hover': { + \ 'dynamicRegistration': v:false, + \ 'contentFormat': ['markdown', 'plaintext'], + \ }, + \ 'inlayHint': { + \ 'dynamicRegistration': v:false, + \ }, + \ 'implementation': { + \ 'dynamicRegistration': v:false, + \ 'linkSupport' : v:true + \ }, + \ 'publishDiagnostics': { + \ 'relatedInformation': v:true, + \ }, + \ 'rangeFormatting': { + \ 'dynamicRegistration': v:false, + \ }, + \ 'references': { + \ 'dynamicRegistration': v:false, + \ }, + \ 'rename': { + \ 'dynamicRegistration': v:false, + \ 'prepareSupport': v:true, + \ 'prepareSupportDefaultBehavior': 1 + \ }, + \ 'semanticTokens': { + \ 'dynamicRegistration': v:false, + \ 'requests': { + \ 'range': v:false, + \ 'full': lsp#internal#semantic#is_enabled() + \ ? {'delta': v:true} + \ : v:false + \ + \ }, + \ 'tokenTypes': [ + \ 'type', 'class', 'enum', 'interface', 'struct', + \ 'typeParameter', 'parameter', 'variable', 'property', + \ 'enumMember', 'event', 'function', 'method', 'macro', + \ 'keyword', 'modifier', 'comment', 'string', 'number', + \ 'regexp', 'operator' + \ ], + \ 'tokenModifiers': [], + \ 'formats': ['relative'], + \ 'overlappingTokenSupport': v:false, + \ 'multilineTokenSupport': v:false, + \ 'serverCancelSupport': v:false + \ }, + \ 'signatureHelp': { + \ 'dynamicRegistration': v:false, + \ }, + \ 'synchronization': { + \ 'didSave': v:true, + \ 'dynamicRegistration': v:false, + \ 'willSave': v:false, + \ 'willSaveWaitUntil': v:false, + \ }, + \ 'typeDefinition': { + \ 'dynamicRegistration': v:false, + \ 'linkSupport' : v:true + \ }, + \ 'typeHierarchy': { + \ 'dynamicRegistration': v:false + \ }, + \ }, + \ 'window': { + \ 'workDoneProgress': g:lsp_work_done_progress_enabled ? v:true : v:false, + \ }, + \ 'workspace': { + \ 'applyEdit': v:true, + \ 'configuration': v:true, + \ 'symbol': { + \ 'dynamicRegistration': v:false, + \ }, + \ 'workspaceFolders': g:lsp_experimental_workspace_folders ? v:true : v:false, + \ }, + \ } +endfunction + +function! s:ensure_init(buf, server_name, cb) abort + let l:server = s:servers[a:server_name] + + if has_key(l:server, 'init_result') + let l:msg = s:new_rpc_success('lsp server already initialized', { 'server_name': a:server_name, 'init_result': l:server['init_result'] }) + call lsp#log_verbose(l:msg) + call a:cb(l:msg) + return + endif + + if has_key(l:server, 'init_callbacks') + " waiting for initialize response + call add(l:server['init_callbacks'], a:cb) + let l:msg = s:new_rpc_success('waiting for lsp server to initialize', { 'server_name': a:server_name }) + call lsp#log(l:msg) + return + endif + + " server has already started, but not initialized + + let l:server_info = l:server['server_info'] + let l:root_uri = has_key(l:server_info, 'root_uri') ? l:server_info['root_uri'](l:server_info) : '' + if empty(l:root_uri) + let l:msg = s:new_rpc_error('ignore initialization lsp server due to empty root_uri', { 'server_name': a:server_name, 'lsp_id': l:server['lsp_id'] }) + call lsp#log(l:msg) + let l:root_uri = lsp#utils#get_default_root_uri() + endif + let l:server['server_info']['_root_uri_resolved'] = l:root_uri + let l:server['workspace_folders'][l:root_uri] = { 'name': l:root_uri, 'uri': l:root_uri } + + if has_key(l:server_info, 'capabilities') + let l:capabilities = l:server_info['capabilities'] + else + let l:capabilities = call(g:lsp_get_supported_capabilities[0], [l:server_info]) + endif + + let l:request = { + \ 'method': 'initialize', + \ 'params': { + \ 'processId': getpid(), + \ 'clientInfo': { 'name': 'vim-lsp' }, + \ 'capabilities': l:capabilities, + \ 'rootUri': l:root_uri, + \ 'rootPath': lsp#utils#uri_to_path(l:root_uri), + \ 'trace': 'off', + \ }, + \ } + + let l:workspace_capabilities = get(l:capabilities, 'workspace', {}) + if get(l:workspace_capabilities, 'workspaceFolders', v:false) + " TODO: extract folder name for l:root_uri + let l:server_info['workspaceFolders'] = [ + \ { 'uri': l:root_uri, 'name': l:root_uri } + \ ] + let l:request['params']['workspaceFolders'] = l:server_info['workspaceFolders'] + endif + + if has_key(l:server_info, 'initialization_options') + let l:request.params['initializationOptions'] = l:server_info['initialization_options'] + endif + + let l:server['init_callbacks'] = [a:cb] + + call s:send_request(a:server_name, l:request) +endfunction + +function! s:ensure_conf(buf, server_name, cb) 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') && !get(l:server_info, '_workspace_config_sent', v:false) + let l:server_info['_workspace_config_sent'] = v:true + call s:send_notification(a:server_name, { + \ 'method': 'workspace/didChangeConfiguration', + \ 'params': { + \ 'settings': lsp#utils#workspace_config#get(a:server_name), + \ } + \ }) + endif + let l:msg = s:new_rpc_success('configuration sent', { 'server_name': a:server_name }) + call lsp#log_verbose(l:msg) + call a:cb(l:msg) +endfunction + +function! s:text_changes(buf, server_name) abort + let l:sync_kind = lsp#capabilities#get_text_document_change_sync_kind(a:server_name) + " When syncKind is None, return null for contentChanges. + if l:sync_kind == 0 + return v:null + endif + + " When syncKind is Incremental and previous content is saved. + if l:sync_kind == 2 && has_key(s:file_content, a:buf) && has_key(s:file_content[a:buf], a:server_name) + " compute diff + let l:old_content = s:get_last_file_content(a:buf, a:server_name) + let l:new_content = lsp#utils#buffer#_get_lines(a:buf) + let l:changes = lsp#utils#diff#compute(l:old_content, l:new_content) + if empty(l:changes.text) && l:changes.rangeLength ==# 0 + return [] + endif + call s:update_file_content(a:buf, a:server_name, l:new_content) + return [l:changes] + endif + + let l:new_content = lsp#utils#buffer#_get_lines(a:buf) + let l:changes = {'text': join(l:new_content, "\n")} + call s:update_file_content(a:buf, a:server_name, l:new_content) + return [l:changes] +endfunction + +function! s:ensure_changed(buf, server_name, cb) abort + let l:server = s:servers[a:server_name] + let l:path = lsp#utils#get_buffer_uri(a:buf) + + let l:buffers = l:server['buffers'] + if !has_key(l:buffers, l:path) + let l:msg = s:new_rpc_success('file is not managed', { 'server_name': a:server_name, 'path': l:path }) + call lsp#log(l:msg) + call a:cb(l:msg) + return + endif + let l:buffer_info = l:buffers[l:path] + + let l:changed_tick = getbufvar(a:buf, 'changedtick') + + if l:buffer_info['changed_tick'] == l:changed_tick + let l:msg = s:new_rpc_success('not dirty', { 'server_name': a:server_name, 'path': l:path }) + call lsp#log_verbose(l:msg) + call a:cb(l:msg) + return + endif + + let l:buffer_info['changed_tick'] = l:changed_tick + let l:buffer_info['version'] = l:buffer_info['version'] + 1 + + call s:send_notification(a:server_name, { + \ 'method': 'textDocument/didChange', + \ 'params': { + \ 'textDocument': s:get_versioned_text_document_identifier(a:buf, l:buffer_info), + \ 'contentChanges': s:text_changes(a:buf, a:server_name), + \ } + \ }) + call lsp#ui#vim#folding#send_request(a:server_name, a:buf, 0) + + let l:msg = s:new_rpc_success('textDocument/didChange sent', { 'server_name': a:server_name, 'path': l:path }) + call lsp#log(l:msg) + call a:cb(l:msg) +endfunction + +function! s:ensure_open(buf, server_name, cb) abort + let l:server = s:servers[a:server_name] + let l:path = lsp#utils#get_buffer_uri(a:buf) + + if empty(l:path) + let l:msg = s:new_rpc_success('ignore open since not a valid uri', { 'server_name': a:server_name, 'path': l:path }) + call lsp#log(l:msg) + call a:cb(l:msg) + return + endif + + let l:buffers = l:server['buffers'] + + if has_key(l:buffers, l:path) + let l:msg = s:new_rpc_success('already opened', { 'server_name': a:server_name, 'path': l:path }) + call lsp#log_verbose(l:msg) + call a:cb(l:msg) + return + endif + + if lsp#capabilities#has_workspace_folders_change_notifications(a:server_name) + call s:workspace_add_folder(a:server_name) + endif + + call s:update_file_content(a:buf, a:server_name, lsp#utils#buffer#_get_lines(a:buf)) + + let l:buffer_info = { 'changed_tick': getbufvar(a:buf, 'changedtick'), 'version': 1, 'uri': l:path } + let l:buffers[l:path] = l:buffer_info + + call s:send_notification(a:server_name, { + \ 'method': 'textDocument/didOpen', + \ 'params': { + \ 'textDocument': s:get_text_document(a:buf, a:server_name, l:buffer_info) + \ }, + \ }) + + call lsp#ui#vim#folding#send_request(a:server_name, a:buf, 0) + + let l:msg = s:new_rpc_success('textDocument/open sent', { 'server_name': a:server_name, 'path': l:path, 'filetype': getbufvar(a:buf, '&filetype') }) + call lsp#log(l:msg) + call a:cb(l:msg) +endfunction + +function! s:workspace_add_folder(server_name) abort + if !g:lsp_experimental_workspace_folders | return | endif + let l:server = s:servers[a:server_name] + let l:server_info = l:server['server_info'] + let l:root_uri = has_key(l:server_info, 'root_uri') ? l:server_info['root_uri'](l:server_info) : lsp#utils#get_default_root_uri() + if !has_key(l:server['workspace_folders'], l:root_uri) + let l:workspace_folder = { 'name': l:root_uri, 'uri': l:root_uri } + call lsp#log('adding workspace folder', a:server_name, l:workspace_folder) + call s:send_notification(a:server_name, { + \ 'method': 'workspace/didChangeWorkspaceFolders', + \ 'params': { + \ 'added': [l:workspace_folder], + \ } + \ }) + let l:server['workspace_folders'][l:root_uri] = l:workspace_folder + endif +endfunction + +function! s:send_request(server_name, data) abort + let l:lsp_id = s:servers[a:server_name]['lsp_id'] + let l:data = copy(a:data) + if has_key(l:data, 'on_notification') + let l:data['on_notification'] = '---funcref---' + endif + call lsp#log_verbose('--->', l:lsp_id, a:server_name, l:data) + return lsp#client#send_request(l:lsp_id, a:data) +endfunction + +function! s:send_notification(server_name, data) abort + let l:lsp_id = s:servers[a:server_name]['lsp_id'] + let l:data = copy(a:data) + if has_key(l:data, 'on_notification') + let l:data['on_notification'] = '---funcref---' + endif + call lsp#log_verbose('--->', l:lsp_id, a:server_name, l:data) + call lsp#client#send_notification(l:lsp_id, a:data) +endfunction + +function! s:send_response(server_name, data) abort + let l:lsp_id = s:servers[a:server_name]['lsp_id'] + let l:data = copy(a:data) + call lsp#log_verbose('--->', l:lsp_id, a:server_name, l:data) + call lsp#client#send_response(l:lsp_id, a:data) +endfunction + +function! s:on_stderr(server_name, id, data, event) abort + call lsp#log_verbose('<---(stderr)', a:id, a:server_name, a:data) +endfunction + +function! s:on_exit(server_name, id, data, event) abort + call lsp#log('s:on_exit', a:id, a:server_name, 'exited', a:data) + if has_key(s:servers, a:server_name) + let l:server = s:servers[a:server_name] + let l:server['lsp_id'] = 0 + let l:server['buffers'] = {} + let l:server['exited'] = 1 + if has_key(l:server, 'init_result') + unlet l:server['init_result'] + endif + let l:server['workspace_folders'] = {} + call lsp#stream(1, { 'server': '$vimlsp', + \ 'response': { 'method': '$/vimlsp/lsp_server_exit', 'params': { 'server': a:server_name } } }) + doautocmd User lsp_server_exit + endif +endfunction + +function! s:on_notification(server_name, id, data, event) abort + call lsp#log_verbose('<---', a:id, a:server_name, a:data) + let l:response = a:data['response'] + let l:server = s:servers[a:server_name] + let l:server_info = l:server['server_info'] + + let l:stream_data = { 'server': a:server_name, 'response': l:response } + if has_key(a:data, 'request') + let l:stream_data['request'] = a:data['request'] + endif + call lsp#stream(1, l:stream_data) " notify stream before callbacks + + if !lsp#client#is_server_instantiated_notification(a:data) + let l:request = a:data['request'] + let l:method = l:request['method'] + if l:method ==# 'initialize' + call s:handle_initialize(a:server_name, a:data) + endif + endif + + for l:callback_info in s:notification_callbacks + call l:callback_info.callback(a:server_name, a:data) + endfor +endfunction + +function! s:on_request(server_name, id, request) abort + call lsp#log_verbose('<---', 's:on_request', a:id, a:request) + + let l:stream_data = { 'server': a:server_name, 'request': a:request } + call lsp#stream(1, l:stream_data) " notify stream before callbacks + + if a:request['method'] ==# 'workspace/applyEdit' + 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: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'] + if has_key(l:server_info, 'workspaceFolders') + call s:send_response(a:server_name, { 'id': a:request['id'], 'result': l:server_info['workspaceFolders']}) + endif + elseif a:request['method'] ==# 'window/workDoneProgress/create' + call s:send_response(a:server_name, { 'id': a:request['id'], 'result': v:null}) + else + " TODO: for now comment this out until we figure out a better solution. + " We need to comment this out so that others outside of vim-lsp can + " hook into the stream and provide their own response. + " " Error returned according to json-rpc specification. + " call s:send_response(a:server_name, { 'id': a:request['id'], 'error': { 'code': -32601, 'message': 'Method not found' } }) + endif +endfunction + +function! s:handle_initialize(server_name, data) abort + let l:response = a:data['response'] + let l:server = s:servers[a:server_name] + + if has_key(l:server, 'exited') + unlet l:server['exited'] + endif + + let l:init_callbacks = l:server['init_callbacks'] + unlet l:server['init_callbacks'] + + if !lsp#client#is_error(l:response) + let l:server['init_result'] = l:response + " Delete cache of trigger chars + if has_key(b:, 'lsp_signature_help_trigger_character') + unlet b:lsp_signature_help_trigger_character + endif + else + let l:server['failed'] = l:response['error'] + call lsp#utils#error('Failed to initialize ' . a:server_name . ' with error ' . l:response['error']['code'] . ': ' . l:response['error']['message']) + endif + + call s:send_notification(a:server_name, { 'method': 'initialized', 'params': {} }) + + for l:Init_callback in l:init_callbacks + call l:Init_callback(a:data) + endfor + + doautocmd User lsp_server_init +endfunction + +function! lsp#get_whitelisted_servers(...) abort + return call(function('lsp#get_allowed_servers'), a:000) +endfunction + +" call lsp#get_allowed_servers() +" call lsp#get_allowed_servers(bufnr('%')) +" call lsp#get_allowed_servers('typescript') +function! lsp#get_allowed_servers(...) abort + if a:0 == 0 + let l:buffer_filetype = &filetype + else + if type(a:1) == type('') + let l:buffer_filetype = a:1 + else + let l:buffer_filetype = getbufvar(a:1, '&filetype') + endif + endif + + " TODO: cache active servers per buffer + let l:active_servers = [] + + for l:server_name in keys(s:servers) + let l:server_info = s:servers[l:server_name]['server_info'] + let l:blocked = 0 + + if has_key(l:server_info, 'blocklist') + let l:blocklistkey = 'blocklist' + else + let l:blocklistkey = 'blacklist' + endif + if has_key(l:server_info, l:blocklistkey) + for l:filetype in l:server_info[l:blocklistkey] + if l:filetype ==? l:buffer_filetype || l:filetype ==# '*' + let l:blocked = 1 + break + endif + endfor + endif + + if l:blocked + continue + endif + + if has_key(l:server_info, 'allowlist') + let l:allowlistkey = 'allowlist' + else + let l:allowlistkey = 'whitelist' + endif + if has_key(l:server_info, l:allowlistkey) + for l:filetype in l:server_info[l:allowlistkey] + if l:filetype ==? l:buffer_filetype || l:filetype ==# '*' + let l:active_servers += [l:server_name] + break + endif + endfor + endif + endfor + + return l:active_servers +endfunction + +function! s:get_text_document_text(buf, server_name) abort + return join(s:get_last_file_content(a:buf, a:server_name), "\n") +endfunction + +function! s:get_text_document(buf, server_name, buffer_info) abort + let l:server = s:servers[a:server_name] + let l:server_info = l:server['server_info'] + let l:language_id = has_key(l:server_info, 'languageId') ? l:server_info['languageId'](l:server_info) : getbufvar(a:buf, '&filetype') + return { + \ 'uri': lsp#utils#get_buffer_uri(a:buf), + \ 'languageId': l:language_id, + \ 'version': a:buffer_info['version'], + \ 'text': s:get_text_document_text(a:buf, a:server_name), + \ } +endfunction + +function! lsp#get_text_document_identifier(...) abort + let l:buf = a:0 > 0 ? a:1 : bufnr('%') + return { 'uri': lsp#utils#get_buffer_uri(l:buf) } +endfunction + +function! lsp#get_position(...) abort + let l:line = line('.') + let l:char = lsp#utils#to_char('%', l:line, col('.')) + return { 'line': l:line - 1, 'character': l:char } +endfunction + +function! s:get_text_document_identifier(buf) abort + return { 'uri': lsp#utils#get_buffer_uri(a:buf) } +endfunction + +function! s:get_versioned_text_document_identifier(buf, buffer_info) abort + return { + \ 'uri': lsp#utils#get_buffer_uri(a:buf), + \ 'version': a:buffer_info['version'], + \ } +endfunction + +" lsp#stream {{{ +" +" example 1: +" +" function! s:on_textDocumentDiagnostics(x) abort +" echom 'Diagnostics for ' . a:x['server'] . ' ' . json_encode(a:x['response']) +" endfunction +" +" au User lsp_setup call lsp#callbag#pipe( +" \ lsp#stream(), +" \ lsp#callbag#filter({x-> has_key(x, 'response') && !has_key(x['response'], 'error') && get(x['response'], 'method', '') == 'textDocument/publishDiagnostics'}), +" \ lsp#callbag#subscribe({ 'next':{x->s:on_textDocumentDiagnostics(x)} }), +" \ ) +" +" example 2: +" call lsp#stream(1, { 'command': 'DocumentFormat' }) +function! lsp#stream(...) abort + if a:0 == 0 + return lsp#callbag#share(s:Stream) + else + call s:Stream(a:1, a:2) + endif +endfunction +" }}} + +" lsp#request {{{ +function! lsp#request(server_name, request) abort + let l:ctx = { + \ 'server_name': a:server_name, + \ 'request': copy(a:request), + \ 'request_id': 0, + \ 'done': 0, + \ 'cancelled': 0, + \ } + return lsp#callbag#create(function('s:request_create', [l:ctx])) +endfunction + +function! s:request_create(ctx, next, error, complete) abort + let a:ctx['next'] = a:next + let a:ctx['error'] = a:error + let a:ctx['complete'] = a:complete + let a:ctx['bufnr'] = get(a:ctx['request'], 'bufnr', bufnr('%')) + let a:ctx['request']['on_notification'] = function('s:request_on_notification', [a:ctx]) + call lsp#utils#step#start([ + \ {s->s:ensure_flush(a:ctx['bufnr'], a:ctx['server_name'], s.callback)}, + \ {s->s:is_step_error(s) ? s:request_error(a:ctx, s.result[0]) : s:request_send(a:ctx) }, + \ ]) + return function('s:request_cancel', [a:ctx]) +endfunction + +function! s:request_send(ctx) abort + if a:ctx['cancelled'] | return | endif " caller already unsubscribed so don't bother sending request + let a:ctx['request_id'] = s:send_request(a:ctx['server_name'], a:ctx['request']) +endfunction + +function! s:request_error(ctx, error) abort + if a:ctx['cancelled'] | return | endif " caller already unsubscribed so don't bother notifying + let a:ctx['done'] = 1 + call a:ctx['error'](a:error) +endfunction + +function! s:request_on_notification(ctx, id, data, event) abort + if a:ctx['cancelled'] | return | endif " caller already unsubscribed so don't bother notifying + let a:ctx['done'] = 1 + call a:ctx['next'](extend({ 'server_name': a:ctx['server_name'] }, a:data)) + call a:ctx['complete']() +endfunction + +function! s:request_cancel(ctx) abort + if a:ctx['cancelled'] | return | endif + let a:ctx['cancelled'] = 1 + if a:ctx['request_id'] <= 0 || a:ctx['done'] | return | endif " we have not made the request yet or request is complete, so nothing to cancel + if lsp#get_server_status(a:ctx['server_name']) !=# 'running' | return | endif " if server is not running we cant send the request + " send the actual cancel request + let a:ctx['dispose'] = lsp#callbag#pipe( + \ lsp#notification(a:ctx['server_name'], { + \ 'method': '$/cancelRequest', + \ 'params': { 'id': a:ctx['request_id'] }, + \ }), + \ lsp#callbag#subscribe({ + \ 'error':{e->s:send_request_dispose(a:ctx)}, + \ 'complete':{->s:send_request_dispose(a:ctx)}, + \ }) + \) +endfunction + +function! lsp#send_request(server_name, request) abort + let l:ctx = { + \ 'server_name': a:server_name, + \ 'request': copy(a:request), + \ 'cb': has_key(a:request, 'on_notification') ? a:request['on_notification'] : function('s:Noop'), + \ } + let l:ctx['dispose'] = lsp#callbag#pipe( + \ lsp#request(a:server_name, a:request), + \ lsp#callbag#subscribe({ + \ 'next':{d->l:ctx['cb'](d)}, + \ 'error':{e->s:send_request_error(l:ctx, e)}, + \ 'complete':{->s:send_request_dispose(l:ctx)}, + \ }) + \) +endfunction + +function! s:send_request_dispose(ctx) abort + " dispose function may not have been created so check before calling + if has_key(a:ctx, 'dispose') + call a:ctx['dispose']() + endif +endfunction + +function! s:send_request_error(ctx, error) abort + call a:ctx['cb'](a:error) + call s:send_request_dispose(a:ctx) +endfunction +" }}} + +" lsp#notification {{{ +function! lsp#notification(server_name, request) abort + return lsp#callbag#lazy(function('s:send_notification', [a:server_name, a:request])) +endfunction +" }}} + +" omnicompletion +function! lsp#complete(...) abort + return call('lsp#omni#complete', a:000) +endfunction + +function! lsp#tagfunc(...) abort + return call('lsp#tag#tagfunc', a:000) +endfunction + +let s:didchange_queue = [] +let s:didchange_timer = -1 + +function! s:add_didchange_queue(buf) abort + if g:lsp_use_event_queue == 0 + for l:server_name in lsp#get_allowed_servers(a:buf) + call s:ensure_flush(a:buf, l:server_name, function('s:Noop')) + endfor + return + endif + if index(s:didchange_queue, a:buf) == -1 + call add(s:didchange_queue, a:buf) + endif + call lsp#log('s:send_didchange_queue() will be triggered') + call timer_stop(s:didchange_timer) + let l:lazy = &updatetime > 1000 ? &updatetime : 1000 + let s:didchange_timer = timer_start(l:lazy, function('s:send_didchange_queue')) +endfunction + +function! s:send_didchange_queue(...) abort + call lsp#log('s:send_event_queue()') + for l:buf in s:didchange_queue + if !bufexists(l:buf) + continue + endif + for l:server_name in lsp#get_allowed_servers(l:buf) + call s:ensure_flush(l:buf, l:server_name, function('s:Noop')) + endfor + endfor + let s:didchange_queue = [] +endfunction + +function! lsp#enable_diagnostics_for_buffer(...) abort + let l:bufnr = a:0 > 0 ? a:1 : bufnr('%') + call lsp#internal#diagnostics#state#_enable_for_buffer(l:bufnr) +endfunction + +function! lsp#disable_diagnostics_for_buffer(...) abort + let l:bufnr = a:0 > 0 ? a:1 : bufnr('%') + call lsp#internal#diagnostics#state#_disable_for_buffer(l:bufnr) +endfunction + +" Return dict with diagnostic counts for current buffer +" { 'error': 1, 'warning': 0, 'information': 0, 'hint': 0 } +function! lsp#get_buffer_diagnostics_counts() abort + return lsp#internal#diagnostics#state#_get_diagnostics_count_for_buffer(bufnr('%')) +endfunction + +" Return first error line or v:null if there are no errors +function! lsp#get_buffer_first_error_line() abort + return lsp#internal#diagnostics#first_line#get_first_error_line({'bufnr': bufnr('%')}) +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! lsp#document_hover_preview_winid() abort + return lsp#internal#document_hover#under_cursor#getpreviewwinid() +endfunction + +" +" Scroll vim-lsp related windows. +" +" NOTE: This method can be used to mapping. +" +function! lsp#scroll(count) abort + let l:ctx = {} + function! l:ctx.callback(count) abort + let l:Window = vital#lsp#import('VS.Vim.Window') + for l:winid in l:Window.find({ winid -> l:Window.is_floating(winid) }) + call l:Window.scroll(l:winid, l:Window.info(l:winid).topline + a:count) + endfor + endfunction + call timer_start(0, { -> l:ctx.callback(a:count) }) + return "\" +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 + call s:merge_dict(a:dict_old[l:key], a:dict_new[l:key]) + else + let a:dict_old[l:key] = a:dict_new[l:key] + endif + endfor +endfunction + +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 + endif + let l:server_info['_workspace_config_sent'] = v:false + call s:ensure_conf(bufnr('%'), a:server_name, function('s:Noop')) +endfunction + +function! lsp#server_complete(lead, line, pos) abort + return filter(sort(keys(s:servers)), 'stridx(v:val, a:lead)==0 && has_key(s:servers[v:val], "init_result")') +endfunction + +function! lsp#server_complete_running(lead, line, pos) abort + let l:all_servers = sort(keys(s:servers)) + return filter(l:all_servers, {idx, name -> + \ stridx(name, a:lead) == 0 && lsp#is_server_running(name) + \ }) +endfunction + +function! lsp#_new_command() abort + let s:last_command_id += 1 + call lsp#stream(1, { 'command': 1 }) + return s:last_command_id +endfunction + +function! lsp#_last_command() abort + return s:last_command_id +endfunction