All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
2 let s:already_setup = 0
3 let s:Stream = lsp#callbag#makeSubject()
4 " workspace_folders = { 'uri': { uri, name } }
5 let s:servers = {} " { lsp_id, server_info, workspace_folders, init_callbacks, init_result, buffers: { path: { changed_tick } }
6 let s:last_command_id = 0
7 let s:notification_callbacks = [] " { name, callback }
9 " This hold previous content for each language servers to make
10 " DidChangeTextDocumentParams. The key is buffer numbers:
13 " "golsp": [ "first-line", "next-line", ... ],
14 " "bingo": [ "first-line", "next-line", ... ]
17 " "pylsp": [ "first-line", "next-line", ... ]
20 let s:file_content = {}
22 " do nothing, place it here only to avoid the message
25 autocmd User lsp_setup silent
26 autocmd User lsp_register_server silent
27 autocmd User lsp_unregister_server silent
28 autocmd User lsp_server_init silent
29 autocmd User lsp_server_exit silent
30 autocmd User lsp_complete_done silent
31 autocmd User lsp_float_opened silent
32 autocmd User lsp_float_closed silent
33 autocmd User lsp_float_focused silent
34 autocmd User lsp_buffer_enabled silent
35 autocmd User lsp_diagnostics_updated silent
36 autocmd User lsp_progress_updated silent
39 function! lsp#log_verbose(...) abort
41 call call(function('lsp#log'), a:000)
45 function! lsp#log(...) abort
46 if !empty(g:lsp_log_file)
47 call writefile([strftime('%c') . ':' . json_encode(a:000)], g:lsp_log_file, 'a')
51 function! lsp#enable() abort
56 doautocmd <nomodeline> User lsp_setup
57 let s:already_setup = 1
60 if g:lsp_signature_help_enabled
61 call lsp#ui#vim#signature_help#setup()
63 call lsp#ui#vim#completion#_setup()
64 call lsp#internal#document_highlight#_enable()
65 call lsp#internal#diagnostics#_enable()
66 call lsp#internal#document_code_action#signs#_enable()
67 call lsp#internal#semantic#_enable()
68 call lsp#internal#show_message_request#_enable()
69 call lsp#internal#show_message#_enable()
70 call lsp#internal#work_done_progress#_enable()
71 call lsp#internal#completion#documentation#_enable()
72 call lsp#internal#inlay_hints#_enable()
73 call s:register_events()
76 function! lsp#disable() abort
80 call lsp#ui#vim#signature_help#_disable()
81 call lsp#ui#vim#completion#_disable()
82 call lsp#internal#document_highlight#_disable()
83 call lsp#internal#diagnostics#_disable()
84 call lsp#internal#document_code_action#signs#_disable()
85 call lsp#internal#semantic#_disable()
86 call lsp#internal#show_message_request#_disable()
87 call lsp#internal#show_message#_disable()
88 call lsp#internal#work_done_progress#_disable()
89 call lsp#internal#completion#documentation#_disable()
90 call s:unregister_events()
94 function! lsp#get_server_names() abort
95 return keys(s:servers)
98 function! lsp#is_valid_server_name(name) abort
99 return has_key(s:servers, a:name)
102 function! lsp#get_server_info(server_name) abort
103 return get(get(s:servers, a:server_name, {}), 'server_info', {})
106 function! lsp#get_server_root_uri(server_name) abort
107 return get(s:servers[a:server_name]['server_info'], '_root_uri_resolved', '')
110 function! lsp#get_server_capabilities(server_name) abort
111 let l:server = s:servers[a:server_name]
112 return has_key(l:server, 'init_result') ? l:server['init_result']['result']['capabilities'] : {}
115 function! s:server_status(server_name) abort
116 if !has_key(s:servers, a:server_name)
117 return 'unknown server'
119 let l:server = s:servers[a:server_name]
120 if has_key(l:server, 'exited')
123 if has_key(l:server, 'init_callbacks')
126 if has_key(l:server, 'failed')
129 if has_key(l:server, 'init_result')
135 function! lsp#is_server_running(name) abort
136 if !has_key(s:servers, a:name)
140 let l:server = s:servers[a:name]
142 return has_key(l:server, 'init_result')
143 \ && !has_key(l:server, 'exited')
144 \ && !has_key(l:server, 'init_callbacks')
145 \ && !has_key(l:server, 'failed')
148 " Returns the current status of all servers (if called with no arguments) or
149 " the given server (if given an argument). Can be one of "unknown server",
150 " "exited", "starting", "failed", "running", "not running"
151 function! lsp#get_server_status(...) abort
153 let l:strs = map(keys(s:servers), {k, v -> v . ': ' . s:server_status(v)})
154 return join(l:strs, "\n")
156 return s:server_status(a:1)
162 \ 'starting': 'MoreMsg',
163 \ 'failed': 'WarningMsg',
164 \ 'running': 'Keyword',
165 \ 'not running': 'Comment'
168 " Collect the current status of all servers
169 function! lsp#collect_server_status() abort
171 for l:k in keys(s:servers)
172 let l:status = s:server_status(l:k)
173 " Copy to prevent callers from corrupting our config.
174 let l:info = deepcopy(s:servers[l:k].server_info)
175 let l:results[l:k] = {
176 \ 'status': l:status,
183 " Print the current status of all servers
184 function! lsp#print_server_status() abort
185 for l:k in sort(keys(s:servers))
186 let l:status = s:server_status(l:k)
188 exec 'echohl' s:color_map[l:status]
193 let l:cfg = { 'workspace_config': s:servers[l:k].server_info.workspace_config }
194 if get(g:, 'loaded_scriptease', 0)
195 call scriptease#pp_command(0, -1, l:cfg)
197 echo json_encode(l:cfg)
204 " @params {server_info} = {
205 " 'name': 'go-langserver', " required, must be unique
206 " 'allowlist': ['go'], " optional, array of filetypes to allow, * for all filetypes
207 " 'blocklist': [], " optional, array of filetypes to block, * for all filetypes,
208 " '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
210 function! lsp#register_server(server_info) abort
211 let l:server_name = a:server_info['name']
212 if has_key(s:servers, l:server_name)
213 call lsp#log('lsp#register_server', 'server already registered', l:server_name)
215 " NOTE: workspace_folders is dict for faster lookup instead of array
216 let s:servers[l:server_name] = {
217 \ 'server_info': a:server_info,
220 \ 'workspace_folders': {},
222 call lsp#log('lsp#register_server', 'server registered', l:server_name)
223 doautocmd <nomodeline> User lsp_register_server
227 " lsp#register_command
229 " @param {command_name} = string
230 " @param {callback} = funcref
232 function! lsp#register_command(command_name, callback) abort
233 call lsp#ui#vim#execute_command#_register(a:command_name, a:callback)
236 function! lsp#register_notifications(name, callback) abort
237 call add(s:notification_callbacks, { 'name': a:name, 'callback': a:callback })
240 function! lsp#unregister_notifications(name) abort
244 function! lsp#stop_server(server_name) abort
245 if has_key(s:servers, a:server_name) && s:servers[a:server_name]['lsp_id'] > 0
246 call lsp#client#stop(s:servers[a:server_name]['lsp_id'])
250 function! s:register_events() abort
253 autocmd BufNewFile * call s:on_text_document_did_open()
254 autocmd BufReadPost * call s:on_text_document_did_open()
255 autocmd BufWritePost * call s:on_text_document_did_save()
256 autocmd BufWinLeave * call s:on_text_document_did_close()
257 autocmd BufWipeout * call s:on_buf_wipeout(expand('<afile>'))
258 autocmd InsertLeave * call s:on_text_document_did_change()
259 autocmd TextChanged * call s:on_text_document_did_change()
260 if exists('##TextChangedP')
261 autocmd TextChangedP * call s:on_text_document_did_change()
263 if g:lsp_untitled_buffer_enabled
264 autocmd FileType * call s:on_filetype_changed(bufnr(expand('<afile>')))
268 for l:bufnr in range(1, bufnr('$'))
269 if bufexists(l:bufnr) && bufloaded(l:bufnr)
270 call s:on_text_document_did_open(l:bufnr)
275 function! s:unregister_events() abort
279 doautocmd <nomodeline> User lsp_unregister_server
282 function! s:on_filetype_changed(buf) abort
283 call s:on_buf_wipeout(a:buf)
284 " TODO: stop unused servers
285 call s:on_text_document_did_open()
288 function! s:on_text_document_did_open(...) abort
289 let l:buf = a:0 > 0 ? a:1 : bufnr('%')
290 if getbufvar(l:buf, '&buftype') ==# 'terminal' | return | endif
291 if getcmdwintype() !=# '' | return | endif
292 call lsp#log('s:on_text_document_did_open()', l:buf, &filetype, getcwd(), lsp#utils#get_buffer_uri(l:buf))
294 " Some language server notify diagnostics to the buffer that has not been loaded yet.
295 " This diagnostics was stored `autoload/lsp/internal/diagnostics/state.vim` but not highlighted.
296 " So we should refresh highlights when buffer opened.
297 call lsp#internal#diagnostics#state#_force_notify_buffer(l:buf)
299 for l:server_name in lsp#get_allowed_servers(l:buf)
300 call s:ensure_flush(l:buf, l:server_name, function('s:fire_lsp_buffer_enabled', [l:server_name, l:buf]))
304 function! lsp#activate() abort
305 call s:on_text_document_did_open()
308 function! s:on_text_document_did_save() abort
309 let l:buf = bufnr('%')
310 if getbufvar(l:buf, '&buftype') ==# 'terminal' | return | endif
311 call lsp#log('s:on_text_document_did_save()', l:buf)
312 for l:server_name in lsp#get_allowed_servers(l:buf)
313 if g:lsp_text_document_did_save_delay >= 0
314 " We delay the callback by one loop iteration as calls to ensure_flush
315 " can introduce mmap'd file locks that linger on Windows and collide
316 " with the second lang server call preventing saves (see #455)
317 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'))})})
319 call s:ensure_flush(l:buf, l:server_name, {result->s:call_did_save(l:buf, l:server_name, result, function('s:Noop'))})
324 function! s:on_text_document_did_change() abort
325 let l:buf = bufnr('%')
326 if getbufvar(l:buf, '&buftype') ==# 'terminal' | return | endif
327 call lsp#log('s:on_text_document_did_change()', l:buf)
328 call s:add_didchange_queue(l:buf)
331 function! s:call_did_save(buf, server_name, result, cb) abort
332 if lsp#client#is_error(a:result['response'])
336 let l:server = s:servers[a:server_name]
337 let l:path = lsp#utils#get_buffer_uri(a:buf)
339 let [l:supports_did_save, l:did_save_options] = lsp#capabilities#get_text_document_save_registration_options(a:server_name)
340 if !l:supports_did_save
341 let l:msg = s:new_rpc_success('---> ignoring textDocument/didSave. not supported by server', { 'server_name': a:server_name, 'path': l:path })
347 call s:update_file_content(a:buf, a:server_name, lsp#utils#buffer#_get_lines(a:buf))
349 let l:buffers = l:server['buffers']
350 let l:buffer_info = l:buffers[l:path]
353 \ 'textDocument': s:get_text_document_identifier(a:buf),
356 if l:did_save_options['includeText']
357 let l:params['text'] = s:get_text_document_text(a:buf, a:server_name)
359 call s:send_notification(a:server_name, {
360 \ 'method': 'textDocument/didSave',
361 \ 'params': l:params,
364 let l:msg = s:new_rpc_success('textDocument/didSave sent', { 'server_name': a:server_name, 'path': l:path })
369 function! s:on_text_document_did_close() abort
370 let l:buf = bufnr('%')
371 if getbufvar(l:buf, '&buftype') ==# 'terminal' | return | endif
372 call lsp#log('s:on_text_document_did_close()', l:buf)
375 function! s:get_last_file_content(buf, server_name) abort
376 if has_key(s:file_content, a:buf) && has_key(s:file_content[a:buf], a:server_name)
377 return s:file_content[a:buf][a:server_name]
382 function! s:update_file_content(buf, server_name, new) abort
383 if !has_key(s:file_content, a:buf)
384 let s:file_content[a:buf] = {}
386 call lsp#log('s:update_file_content()', a:buf)
387 let s:file_content[a:buf][a:server_name] = a:new
390 function! s:on_buf_wipeout(buf) abort
391 if has_key(s:file_content, a:buf)
392 call remove(s:file_content, a:buf)
396 function! s:ensure_flush_all(buf, server_names) abort
397 for l:server_name in a:server_names
398 call s:ensure_flush(a:buf, l:server_name, function('s:Noop'))
402 function! s:fire_lsp_buffer_enabled(server_name, buf, ...) abort
403 if a:buf == bufnr('%')
404 doautocmd <nomodeline> User lsp_buffer_enabled
406 " Not using ++once in autocmd for compatibility of VIM8.0
407 let l:cmd = printf('autocmd BufEnter <buffer=%d> doautocmd <nomodeline> User lsp_buffer_enabled', a:buf)
408 exec printf('augroup _lsp_fire_buffer_enabled | exec "%s | autocmd! _lsp_fire_buffer_enabled BufEnter <buffer>" | augroup END', l:cmd)
412 function! s:Noop(...) abort
415 function! s:is_step_error(s) abort
416 return lsp#client#is_error(a:s.result[0]['response'])
419 function! s:throw_step_error(s) abort
420 call a:s.callback(a:s.result[0])
423 function! s:new_rpc_success(message, data) abort
426 \ 'message': a:message,
427 \ 'data': extend({ '__data__': 'vim-lsp'}, a:data),
432 function! s:new_rpc_error(message, data) abort
437 \ 'message': a:message,
438 \ 'data': extend({ '__error__': 'vim-lsp'}, a:data),
444 function! s:ensure_flush(buf, server_name, cb) abort
445 call lsp#utils#step#start([
446 \ {s->s:ensure_start(a:buf, a:server_name, s.callback)},
447 \ {s->s:is_step_error(s) ? s:throw_step_error(s) : s:ensure_init(a:buf, a:server_name, s.callback)},
448 \ {s->s:is_step_error(s) ? s:throw_step_error(s) : s:ensure_conf(a:buf, a:server_name, s.callback)},
449 \ {s->s:is_step_error(s) ? s:throw_step_error(s) : s:ensure_open(a:buf, a:server_name, s.callback)},
450 \ {s->s:is_step_error(s) ? s:throw_step_error(s) : s:ensure_changed(a:buf, a:server_name, s.callback)},
451 \ {s->a:cb(s.result[0])}
455 function! s:ensure_start(buf, server_name, cb) abort
456 let l:path = lsp#utils#get_buffer_path(a:buf)
458 if lsp#utils#is_remote_uri(l:path) || !has_key(s:servers, a:server_name)
459 let l:msg = s:new_rpc_error('ignoring start server due to remote uri', { 'server_name': a:server_name, 'uri': l:path})
465 let l:server = s:servers[a:server_name]
466 let l:server_info = l:server['server_info']
467 if l:server['lsp_id'] > 0
468 let l:msg = s:new_rpc_success('server already started', { 'server_name': a:server_name })
469 call lsp#log_verbose(l:msg)
474 if has_key(l:server_info, 'tcp')
475 let l:tcp = l:server_info['tcp'](l:server_info)
476 let l:lsp_id = lsp#client#start({
478 \ 'on_stderr': function('s:on_stderr', [a:server_name]),
479 \ 'on_exit': function('s:on_exit', [a:server_name]),
480 \ 'on_notification': function('s:on_notification', [a:server_name]),
481 \ 'on_request': function('s:on_request', [a:server_name]),
483 elseif has_key(l:server_info, 'cmd')
484 let l:cmd_type = type(l:server_info['cmd'])
485 if l:cmd_type == v:t_list
486 let l:cmd = l:server_info['cmd']
488 let l:cmd = l:server_info['cmd'](l:server_info)
492 let l:msg = s:new_rpc_error('ignore server start since cmd is empty', { 'server_name': a:server_name })
498 call lsp#log('Starting server', a:server_name, l:cmd)
501 \ 'on_stderr': function('s:on_stderr', [a:server_name]),
502 \ 'on_exit': function('s:on_exit', [a:server_name]),
503 \ 'on_notification': function('s:on_notification', [a:server_name]),
504 \ 'on_request': function('s:on_request', [a:server_name]),
506 if has_key(l:server_info, 'env')
507 let l:opts.env = l:server_info.env
509 let l:lsp_id = lsp#client#start(l:opts)
513 let l:server['lsp_id'] = l:lsp_id
514 let l:msg = s:new_rpc_success('started lsp server successfully', { 'server_name': a:server_name, 'lsp_id': l:lsp_id })
518 let l:msg = s:new_rpc_error('failed to start server', { 'server_name': a:server_name, 'cmd': l:cmd })
524 function! lsp#default_get_supported_capabilities(server_info) abort
525 " Sorted alphabetically
529 \ 'dynamicRegistration': v:false,
532 \ 'dynamicRegistration': v:false,
533 \ 'codeActionLiteralSupport': {
534 \ 'codeActionKind': {
535 \ 'valueSet': ['', 'quickfix', 'refactor', 'refactor.extract', 'refactor.inline', 'refactor.rewrite', 'source', 'source.organizeImports'],
538 \ 'isPreferredSupport': v:true,
539 \ 'disabledSupport': v:true,
542 \ 'dynamicRegistration': v:false,
545 \ 'dynamicRegistration': v:false,
546 \ 'completionItem': {
547 \ 'documentationFormat': ['markdown', 'plaintext'],
548 \ 'snippetSupport': v:false,
549 \ 'resolveSupport': {
550 \ 'properties': ['additionalTextEdits']
553 \ 'completionItemKind': {
554 \ 'valueSet': lsp#omni#get_completion_item_kinds()
558 \ 'dynamicRegistration': v:false,
559 \ 'linkSupport' : v:true
562 \ 'dynamicRegistration': v:false,
563 \ 'linkSupport' : v:true
565 \ 'documentHighlight': {
566 \ 'dynamicRegistration': v:false,
568 \ 'documentSymbol': {
569 \ 'dynamicRegistration': v:false,
571 \ 'valueSet': lsp#ui#vim#utils#get_symbol_kinds()
573 \ 'hierarchicalDocumentSymbolSupport': v:false,
574 \ 'labelSupport': v:false
577 \ 'dynamicRegistration': v:false,
578 \ 'lineFoldingOnly': v:true,
579 \ 'rangeLimit': 5000,
582 \ 'dynamicRegistration': v:false,
585 \ 'dynamicRegistration': v:false,
586 \ 'contentFormat': ['markdown', 'plaintext'],
589 \ 'dynamicRegistration': v:false,
591 \ 'implementation': {
592 \ 'dynamicRegistration': v:false,
593 \ 'linkSupport' : v:true
595 \ 'publishDiagnostics': {
596 \ 'relatedInformation': v:true,
598 \ 'rangeFormatting': {
599 \ 'dynamicRegistration': v:false,
602 \ 'dynamicRegistration': v:false,
605 \ 'dynamicRegistration': v:false,
606 \ 'prepareSupport': v:true,
607 \ 'prepareSupportDefaultBehavior': 1
609 \ 'semanticTokens': {
610 \ 'dynamicRegistration': v:false,
613 \ 'full': lsp#internal#semantic#is_enabled()
614 \ ? {'delta': v:true}
619 \ 'type', 'class', 'enum', 'interface', 'struct',
620 \ 'typeParameter', 'parameter', 'variable', 'property',
621 \ 'enumMember', 'event', 'function', 'method', 'macro',
622 \ 'keyword', 'modifier', 'comment', 'string', 'number',
623 \ 'regexp', 'operator'
625 \ 'tokenModifiers': [],
626 \ 'formats': ['relative'],
627 \ 'overlappingTokenSupport': v:false,
628 \ 'multilineTokenSupport': v:false,
629 \ 'serverCancelSupport': v:false
632 \ 'dynamicRegistration': v:false,
634 \ 'synchronization': {
636 \ 'dynamicRegistration': v:false,
637 \ 'willSave': v:false,
638 \ 'willSaveWaitUntil': v:false,
640 \ 'typeDefinition': {
641 \ 'dynamicRegistration': v:false,
642 \ 'linkSupport' : v:true
645 \ 'dynamicRegistration': v:false
649 \ 'workDoneProgress': g:lsp_work_done_progress_enabled ? v:true : v:false,
652 \ 'applyEdit': v:true,
653 \ 'configuration': v:true,
655 \ 'dynamicRegistration': v:false,
657 \ 'workspaceFolders': g:lsp_experimental_workspace_folders ? v:true : v:false,
662 function! s:ensure_init(buf, server_name, cb) abort
663 let l:server = s:servers[a:server_name]
665 if has_key(l:server, 'init_result')
666 let l:msg = s:new_rpc_success('lsp server already initialized', { 'server_name': a:server_name, 'init_result': l:server['init_result'] })
667 call lsp#log_verbose(l:msg)
672 if has_key(l:server, 'init_callbacks')
673 " waiting for initialize response
674 call add(l:server['init_callbacks'], a:cb)
675 let l:msg = s:new_rpc_success('waiting for lsp server to initialize', { 'server_name': a:server_name })
680 " server has already started, but not initialized
682 let l:server_info = l:server['server_info']
683 let l:root_uri = has_key(l:server_info, 'root_uri') ? l:server_info['root_uri'](l:server_info) : ''
685 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'] })
687 let l:root_uri = lsp#utils#get_default_root_uri()
689 let l:server['server_info']['_root_uri_resolved'] = l:root_uri
690 let l:server['workspace_folders'][l:root_uri] = { 'name': l:root_uri, 'uri': l:root_uri }
692 if has_key(l:server_info, 'capabilities')
693 let l:capabilities = l:server_info['capabilities']
695 let l:capabilities = call(g:lsp_get_supported_capabilities[0], [l:server_info])
699 \ 'method': 'initialize',
701 \ 'processId': getpid(),
702 \ 'clientInfo': { 'name': 'vim-lsp' },
703 \ 'capabilities': l:capabilities,
704 \ 'rootUri': l:root_uri,
705 \ 'rootPath': lsp#utils#uri_to_path(l:root_uri),
710 let l:workspace_capabilities = get(l:capabilities, 'workspace', {})
711 if get(l:workspace_capabilities, 'workspaceFolders', v:false)
712 " TODO: extract folder name for l:root_uri
713 let l:server_info['workspaceFolders'] = [
714 \ { 'uri': l:root_uri, 'name': l:root_uri }
716 let l:request['params']['workspaceFolders'] = l:server_info['workspaceFolders']
719 if has_key(l:server_info, 'initialization_options')
720 let l:request.params['initializationOptions'] = l:server_info['initialization_options']
723 let l:server['init_callbacks'] = [a:cb]
725 call s:send_request(a:server_name, l:request)
728 function! s:ensure_conf(buf, server_name, cb) abort
729 let l:server = s:servers[a:server_name]
730 let l:server_info = l:server['server_info']
731 if has_key(l:server_info, 'workspace_config') && !get(l:server_info, '_workspace_config_sent', v:false)
732 let l:server_info['_workspace_config_sent'] = v:true
733 call s:send_notification(a:server_name, {
734 \ 'method': 'workspace/didChangeConfiguration',
736 \ 'settings': lsp#utils#workspace_config#get(a:server_name),
740 let l:msg = s:new_rpc_success('configuration sent', { 'server_name': a:server_name })
741 call lsp#log_verbose(l:msg)
745 function! s:text_changes(buf, server_name) abort
746 let l:sync_kind = lsp#capabilities#get_text_document_change_sync_kind(a:server_name)
747 " When syncKind is None, return null for contentChanges.
752 " When syncKind is Incremental and previous content is saved.
753 if l:sync_kind == 2 && has_key(s:file_content, a:buf) && has_key(s:file_content[a:buf], a:server_name)
755 let l:old_content = s:get_last_file_content(a:buf, a:server_name)
756 let l:new_content = lsp#utils#buffer#_get_lines(a:buf)
757 let l:changes = lsp#utils#diff#compute(l:old_content, l:new_content)
758 if empty(l:changes.text) && l:changes.rangeLength ==# 0
761 call s:update_file_content(a:buf, a:server_name, l:new_content)
765 let l:new_content = lsp#utils#buffer#_get_lines(a:buf)
766 let l:changes = {'text': join(l:new_content, "\n")}
767 call s:update_file_content(a:buf, a:server_name, l:new_content)
771 function! s:ensure_changed(buf, server_name, cb) abort
772 let l:server = s:servers[a:server_name]
773 let l:path = lsp#utils#get_buffer_uri(a:buf)
775 let l:buffers = l:server['buffers']
776 if !has_key(l:buffers, l:path)
777 let l:msg = s:new_rpc_success('file is not managed', { 'server_name': a:server_name, 'path': l:path })
782 let l:buffer_info = l:buffers[l:path]
784 let l:changed_tick = getbufvar(a:buf, 'changedtick')
786 if l:buffer_info['changed_tick'] == l:changed_tick
787 let l:msg = s:new_rpc_success('not dirty', { 'server_name': a:server_name, 'path': l:path })
788 call lsp#log_verbose(l:msg)
793 let l:buffer_info['changed_tick'] = l:changed_tick
794 let l:buffer_info['version'] = l:buffer_info['version'] + 1
796 call s:send_notification(a:server_name, {
797 \ 'method': 'textDocument/didChange',
799 \ 'textDocument': s:get_versioned_text_document_identifier(a:buf, l:buffer_info),
800 \ 'contentChanges': s:text_changes(a:buf, a:server_name),
803 call lsp#ui#vim#folding#send_request(a:server_name, a:buf, 0)
805 let l:msg = s:new_rpc_success('textDocument/didChange sent', { 'server_name': a:server_name, 'path': l:path })
810 function! s:ensure_open(buf, server_name, cb) abort
811 let l:server = s:servers[a:server_name]
812 let l:path = lsp#utils#get_buffer_uri(a:buf)
815 let l:msg = s:new_rpc_success('ignore open since not a valid uri', { 'server_name': a:server_name, 'path': l:path })
821 let l:buffers = l:server['buffers']
823 if has_key(l:buffers, l:path)
824 let l:msg = s:new_rpc_success('already opened', { 'server_name': a:server_name, 'path': l:path })
825 call lsp#log_verbose(l:msg)
830 if lsp#capabilities#has_workspace_folders_change_notifications(a:server_name)
831 call s:workspace_add_folder(a:server_name)
834 call s:update_file_content(a:buf, a:server_name, lsp#utils#buffer#_get_lines(a:buf))
836 let l:buffer_info = { 'changed_tick': getbufvar(a:buf, 'changedtick'), 'version': 1, 'uri': l:path }
837 let l:buffers[l:path] = l:buffer_info
839 call s:send_notification(a:server_name, {
840 \ 'method': 'textDocument/didOpen',
842 \ 'textDocument': s:get_text_document(a:buf, a:server_name, l:buffer_info)
846 call lsp#ui#vim#folding#send_request(a:server_name, a:buf, 0)
848 let l:msg = s:new_rpc_success('textDocument/open sent', { 'server_name': a:server_name, 'path': l:path, 'filetype': getbufvar(a:buf, '&filetype') })
853 function! s:workspace_add_folder(server_name) abort
854 if !g:lsp_experimental_workspace_folders | return | endif
855 let l:server = s:servers[a:server_name]
856 let l:server_info = l:server['server_info']
857 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()
858 if !has_key(l:server['workspace_folders'], l:root_uri)
859 let l:workspace_folder = { 'name': l:root_uri, 'uri': l:root_uri }
860 call lsp#log('adding workspace folder', a:server_name, l:workspace_folder)
861 call s:send_notification(a:server_name, {
862 \ 'method': 'workspace/didChangeWorkspaceFolders',
864 \ 'added': [l:workspace_folder],
867 let l:server['workspace_folders'][l:root_uri] = l:workspace_folder
871 function! s:send_request(server_name, data) abort
872 let l:lsp_id = s:servers[a:server_name]['lsp_id']
873 let l:data = copy(a:data)
874 if has_key(l:data, 'on_notification')
875 let l:data['on_notification'] = '---funcref---'
877 call lsp#log_verbose('--->', l:lsp_id, a:server_name, l:data)
878 return lsp#client#send_request(l:lsp_id, a:data)
881 function! s:send_notification(server_name, data) abort
882 let l:lsp_id = s:servers[a:server_name]['lsp_id']
883 let l:data = copy(a:data)
884 if has_key(l:data, 'on_notification')
885 let l:data['on_notification'] = '---funcref---'
887 call lsp#log_verbose('--->', l:lsp_id, a:server_name, l:data)
888 call lsp#client#send_notification(l:lsp_id, a:data)
891 function! s:send_response(server_name, data) abort
892 let l:lsp_id = s:servers[a:server_name]['lsp_id']
893 let l:data = copy(a:data)
894 call lsp#log_verbose('--->', l:lsp_id, a:server_name, l:data)
895 call lsp#client#send_response(l:lsp_id, a:data)
898 function! s:on_stderr(server_name, id, data, event) abort
899 call lsp#log_verbose('<---(stderr)', a:id, a:server_name, a:data)
902 function! s:on_exit(server_name, id, data, event) abort
903 call lsp#log('s:on_exit', a:id, a:server_name, 'exited', a:data)
904 if has_key(s:servers, a:server_name)
905 let l:server = s:servers[a:server_name]
906 let l:server['lsp_id'] = 0
907 let l:server['buffers'] = {}
908 let l:server['exited'] = 1
909 if has_key(l:server, 'init_result')
910 unlet l:server['init_result']
912 let l:server['workspace_folders'] = {}
913 call lsp#stream(1, { 'server': '$vimlsp',
914 \ 'response': { 'method': '$/vimlsp/lsp_server_exit', 'params': { 'server': a:server_name } } })
915 doautocmd <nomodeline> User lsp_server_exit
919 function! s:on_notification(server_name, id, data, event) abort
920 call lsp#log_verbose('<---', a:id, a:server_name, a:data)
921 let l:response = a:data['response']
922 let l:server = s:servers[a:server_name]
923 let l:server_info = l:server['server_info']
925 let l:stream_data = { 'server': a:server_name, 'response': l:response }
926 if has_key(a:data, 'request')
927 let l:stream_data['request'] = a:data['request']
929 call lsp#stream(1, l:stream_data) " notify stream before callbacks
931 if !lsp#client#is_server_instantiated_notification(a:data)
932 let l:request = a:data['request']
933 let l:method = l:request['method']
934 if l:method ==# 'initialize'
935 call s:handle_initialize(a:server_name, a:data)
939 for l:callback_info in s:notification_callbacks
940 call l:callback_info.callback(a:server_name, a:data)
944 function! s:on_request(server_name, id, request) abort
945 call lsp#log_verbose('<---', 's:on_request', a:id, a:request)
947 let l:stream_data = { 'server': a:server_name, 'request': a:request }
948 call lsp#stream(1, l:stream_data) " notify stream before callbacks
950 if a:request['method'] ==# 'workspace/applyEdit'
951 call lsp#utils#workspace_edit#apply_workspace_edit(a:request['params']['edit'])
952 call s:send_response(a:server_name, { 'id': a:request['id'], 'result': { 'applied': v:true } })
953 elseif a:request['method'] ==# 'workspace/configuration'
954 let l:config = lsp#utils#workspace_config#get(a:server_name)
955 let l:response_items = map(a:request['params']['items'], { key, val -> lsp#utils#workspace_config#projection(l:config, val) })
956 call s:send_response(a:server_name, { 'id': a:request['id'], 'result': l:response_items })
957 elseif a:request['method'] ==# 'workspace/workspaceFolders'
958 let l:server_info = s:servers[a:server_name]['server_info']
959 if has_key(l:server_info, 'workspaceFolders')
960 call s:send_response(a:server_name, { 'id': a:request['id'], 'result': l:server_info['workspaceFolders']})
962 elseif a:request['method'] ==# 'window/workDoneProgress/create'
963 call s:send_response(a:server_name, { 'id': a:request['id'], 'result': v:null})
965 " TODO: for now comment this out until we figure out a better solution.
966 " We need to comment this out so that others outside of vim-lsp can
967 " hook into the stream and provide their own response.
968 " " Error returned according to json-rpc specification.
969 " call s:send_response(a:server_name, { 'id': a:request['id'], 'error': { 'code': -32601, 'message': 'Method not found' } })
973 function! s:handle_initialize(server_name, data) abort
974 let l:response = a:data['response']
975 let l:server = s:servers[a:server_name]
977 if has_key(l:server, 'exited')
978 unlet l:server['exited']
981 let l:init_callbacks = l:server['init_callbacks']
982 unlet l:server['init_callbacks']
984 if !lsp#client#is_error(l:response)
985 let l:server['init_result'] = l:response
986 " Delete cache of trigger chars
987 if has_key(b:, 'lsp_signature_help_trigger_character')
988 unlet b:lsp_signature_help_trigger_character
991 let l:server['failed'] = l:response['error']
992 call lsp#utils#error('Failed to initialize ' . a:server_name . ' with error ' . l:response['error']['code'] . ': ' . l:response['error']['message'])
995 call s:send_notification(a:server_name, { 'method': 'initialized', 'params': {} })
997 for l:Init_callback in l:init_callbacks
998 call l:Init_callback(a:data)
1001 doautocmd <nomodeline> User lsp_server_init
1004 function! lsp#get_whitelisted_servers(...) abort
1005 return call(function('lsp#get_allowed_servers'), a:000)
1008 " call lsp#get_allowed_servers()
1009 " call lsp#get_allowed_servers(bufnr('%'))
1010 " call lsp#get_allowed_servers('typescript')
1011 function! lsp#get_allowed_servers(...) abort
1013 let l:buffer_filetype = &filetype
1015 if type(a:1) == type('')
1016 let l:buffer_filetype = a:1
1018 let l:buffer_filetype = getbufvar(a:1, '&filetype')
1022 " TODO: cache active servers per buffer
1023 let l:active_servers = []
1025 for l:server_name in keys(s:servers)
1026 let l:server_info = s:servers[l:server_name]['server_info']
1029 if has_key(l:server_info, 'blocklist')
1030 let l:blocklistkey = 'blocklist'
1032 let l:blocklistkey = 'blacklist'
1034 if has_key(l:server_info, l:blocklistkey)
1035 for l:filetype in l:server_info[l:blocklistkey]
1036 if l:filetype ==? l:buffer_filetype || l:filetype ==# '*'
1047 if has_key(l:server_info, 'allowlist')
1048 let l:allowlistkey = 'allowlist'
1050 let l:allowlistkey = 'whitelist'
1052 if has_key(l:server_info, l:allowlistkey)
1053 for l:filetype in l:server_info[l:allowlistkey]
1054 if l:filetype ==? l:buffer_filetype || l:filetype ==# '*'
1055 let l:active_servers += [l:server_name]
1062 return l:active_servers
1065 function! s:get_text_document_text(buf, server_name) abort
1066 return join(s:get_last_file_content(a:buf, a:server_name), "\n")
1069 function! s:get_text_document(buf, server_name, buffer_info) abort
1070 let l:server = s:servers[a:server_name]
1071 let l:server_info = l:server['server_info']
1072 let l:language_id = has_key(l:server_info, 'languageId') ? l:server_info['languageId'](l:server_info) : getbufvar(a:buf, '&filetype')
1074 \ 'uri': lsp#utils#get_buffer_uri(a:buf),
1075 \ 'languageId': l:language_id,
1076 \ 'version': a:buffer_info['version'],
1077 \ 'text': s:get_text_document_text(a:buf, a:server_name),
1081 function! lsp#get_text_document_identifier(...) abort
1082 let l:buf = a:0 > 0 ? a:1 : bufnr('%')
1083 return { 'uri': lsp#utils#get_buffer_uri(l:buf) }
1086 function! lsp#get_position(...) abort
1087 let l:line = line('.')
1088 let l:char = lsp#utils#to_char('%', l:line, col('.'))
1089 return { 'line': l:line - 1, 'character': l:char }
1092 function! s:get_text_document_identifier(buf) abort
1093 return { 'uri': lsp#utils#get_buffer_uri(a:buf) }
1096 function! s:get_versioned_text_document_identifier(buf, buffer_info) abort
1098 \ 'uri': lsp#utils#get_buffer_uri(a:buf),
1099 \ 'version': a:buffer_info['version'],
1107 " function! s:on_textDocumentDiagnostics(x) abort
1108 " echom 'Diagnostics for ' . a:x['server'] . ' ' . json_encode(a:x['response'])
1111 " au User lsp_setup call lsp#callbag#pipe(
1113 " \ lsp#callbag#filter({x-> has_key(x, 'response') && !has_key(x['response'], 'error') && get(x['response'], 'method', '') == 'textDocument/publishDiagnostics'}),
1114 " \ lsp#callbag#subscribe({ 'next':{x->s:on_textDocumentDiagnostics(x)} }),
1118 " call lsp#stream(1, { 'command': 'DocumentFormat' })
1119 function! lsp#stream(...) abort
1121 return lsp#callbag#share(s:Stream)
1123 call s:Stream(a:1, a:2)
1129 function! lsp#request(server_name, request) abort
1131 \ 'server_name': a:server_name,
1132 \ 'request': copy(a:request),
1137 return lsp#callbag#create(function('s:request_create', [l:ctx]))
1140 function! s:request_create(ctx, next, error, complete) abort
1141 let a:ctx['next'] = a:next
1142 let a:ctx['error'] = a:error
1143 let a:ctx['complete'] = a:complete
1144 let a:ctx['bufnr'] = get(a:ctx['request'], 'bufnr', bufnr('%'))
1145 let a:ctx['request']['on_notification'] = function('s:request_on_notification', [a:ctx])
1146 call lsp#utils#step#start([
1147 \ {s->s:ensure_flush(a:ctx['bufnr'], a:ctx['server_name'], s.callback)},
1148 \ {s->s:is_step_error(s) ? s:request_error(a:ctx, s.result[0]) : s:request_send(a:ctx) },
1150 return function('s:request_cancel', [a:ctx])
1153 function! s:request_send(ctx) abort
1154 if a:ctx['cancelled'] | return | endif " caller already unsubscribed so don't bother sending request
1155 let a:ctx['request_id'] = s:send_request(a:ctx['server_name'], a:ctx['request'])
1158 function! s:request_error(ctx, error) abort
1159 if a:ctx['cancelled'] | return | endif " caller already unsubscribed so don't bother notifying
1160 let a:ctx['done'] = 1
1161 call a:ctx['error'](a:error)
1164 function! s:request_on_notification(ctx, id, data, event) abort
1165 if a:ctx['cancelled'] | return | endif " caller already unsubscribed so don't bother notifying
1166 let a:ctx['done'] = 1
1167 call a:ctx['next'](extend({ 'server_name': a:ctx['server_name'] }, a:data))
1168 call a:ctx['complete']()
1171 function! s:request_cancel(ctx) abort
1172 if a:ctx['cancelled'] | return | endif
1173 let a:ctx['cancelled'] = 1
1174 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
1175 if lsp#get_server_status(a:ctx['server_name']) !=# 'running' | return | endif " if server is not running we cant send the request
1176 " send the actual cancel request
1177 let a:ctx['dispose'] = lsp#callbag#pipe(
1178 \ lsp#notification(a:ctx['server_name'], {
1179 \ 'method': '$/cancelRequest',
1180 \ 'params': { 'id': a:ctx['request_id'] },
1182 \ lsp#callbag#subscribe({
1183 \ 'error':{e->s:send_request_dispose(a:ctx)},
1184 \ 'complete':{->s:send_request_dispose(a:ctx)},
1189 function! lsp#send_request(server_name, request) abort
1191 \ 'server_name': a:server_name,
1192 \ 'request': copy(a:request),
1193 \ 'cb': has_key(a:request, 'on_notification') ? a:request['on_notification'] : function('s:Noop'),
1195 let l:ctx['dispose'] = lsp#callbag#pipe(
1196 \ lsp#request(a:server_name, a:request),
1197 \ lsp#callbag#subscribe({
1198 \ 'next':{d->l:ctx['cb'](d)},
1199 \ 'error':{e->s:send_request_error(l:ctx, e)},
1200 \ 'complete':{->s:send_request_dispose(l:ctx)},
1205 function! s:send_request_dispose(ctx) abort
1206 " dispose function may not have been created so check before calling
1207 if has_key(a:ctx, 'dispose')
1208 call a:ctx['dispose']()
1212 function! s:send_request_error(ctx, error) abort
1213 call a:ctx['cb'](a:error)
1214 call s:send_request_dispose(a:ctx)
1218 " lsp#notification {{{
1219 function! lsp#notification(server_name, request) abort
1220 return lsp#callbag#lazy(function('s:send_notification', [a:server_name, a:request]))
1225 function! lsp#complete(...) abort
1226 return call('lsp#omni#complete', a:000)
1229 function! lsp#tagfunc(...) abort
1230 return call('lsp#tag#tagfunc', a:000)
1233 let s:didchange_queue = []
1234 let s:didchange_timer = -1
1236 function! s:add_didchange_queue(buf) abort
1237 if g:lsp_use_event_queue == 0
1238 for l:server_name in lsp#get_allowed_servers(a:buf)
1239 call s:ensure_flush(a:buf, l:server_name, function('s:Noop'))
1243 if index(s:didchange_queue, a:buf) == -1
1244 call add(s:didchange_queue, a:buf)
1246 call lsp#log('s:send_didchange_queue() will be triggered')
1247 call timer_stop(s:didchange_timer)
1248 let l:lazy = &updatetime > 1000 ? &updatetime : 1000
1249 let s:didchange_timer = timer_start(l:lazy, function('s:send_didchange_queue'))
1252 function! s:send_didchange_queue(...) abort
1253 call lsp#log('s:send_event_queue()')
1254 for l:buf in s:didchange_queue
1255 if !bufexists(l:buf)
1258 for l:server_name in lsp#get_allowed_servers(l:buf)
1259 call s:ensure_flush(l:buf, l:server_name, function('s:Noop'))
1262 let s:didchange_queue = []
1265 function! lsp#enable_diagnostics_for_buffer(...) abort
1266 let l:bufnr = a:0 > 0 ? a:1 : bufnr('%')
1267 call lsp#internal#diagnostics#state#_enable_for_buffer(l:bufnr)
1270 function! lsp#disable_diagnostics_for_buffer(...) abort
1271 let l:bufnr = a:0 > 0 ? a:1 : bufnr('%')
1272 call lsp#internal#diagnostics#state#_disable_for_buffer(l:bufnr)
1275 " Return dict with diagnostic counts for current buffer
1276 " { 'error': 1, 'warning': 0, 'information': 0, 'hint': 0 }
1277 function! lsp#get_buffer_diagnostics_counts() abort
1278 return lsp#internal#diagnostics#state#_get_diagnostics_count_for_buffer(bufnr('%'))
1281 " Return first error line or v:null if there are no errors
1282 function! lsp#get_buffer_first_error_line() abort
1283 return lsp#internal#diagnostics#first_line#get_first_error_line({'bufnr': bufnr('%')})
1286 " Return UI list with window/workDoneProgress
1287 " The list is most recently update order.
1288 " [{ 'server': 'clangd', 'token': 'backgroundIndexProgress', 'title': 'indexing', 'messages': '50/100', 'percentage': 50 },
1289 " { 'server': 'rust-analyzer', 'token': 'rustAnalyzer/indexing', 'title': 'indexing', 'messages': '9/262 (std)', 'percentage': 3 }]
1290 " 'percentage': 0 - 100 or not exist
1291 function! lsp#get_progress() abort
1292 return lsp#internal#work_done_progress#get_progress()
1295 function! lsp#document_hover_preview_winid() abort
1296 return lsp#internal#document_hover#under_cursor#getpreviewwinid()
1300 " Scroll vim-lsp related windows.
1302 " NOTE: This method can be used to <expr> mapping.
1304 function! lsp#scroll(count) abort
1306 function! l:ctx.callback(count) abort
1307 let l:Window = vital#lsp#import('VS.Vim.Window')
1308 for l:winid in l:Window.find({ winid -> l:Window.is_floating(winid) })
1309 call l:Window.scroll(l:winid, l:Window.info(l:winid).topline + a:count)
1312 call timer_start(0, { -> l:ctx.callback(a:count) })
1316 function! s:merge_dict(dict_old, dict_new) abort
1317 for l:key in keys(a:dict_new)
1318 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
1319 call s:merge_dict(a:dict_old[l:key], a:dict_new[l:key])
1321 let a:dict_old[l:key] = a:dict_new[l:key]
1326 function! lsp#update_workspace_config(server_name, workspace_config) abort
1327 let l:server = s:servers[a:server_name]
1328 let l:server_info = l:server['server_info']
1329 if has_key(l:server_info, 'workspace_config')
1330 if type(l:server_info['workspace_config']) == v:t_func
1331 call lsp#utils#error('''workspace_config'' is a function, so
1332 \ lsp#update_workspace_config() can not be used. Either
1333 \ replace function with a dictionary, or adjust the value
1334 \ generated by the function as necessary.')
1337 call s:merge_dict(l:server_info['workspace_config'], a:workspace_config)
1339 let l:server_info['workspace_config'] = a:workspace_config
1341 let l:server_info['_workspace_config_sent'] = v:false
1342 call s:ensure_conf(bufnr('%'), a:server_name, function('s:Noop'))
1345 function! lsp#server_complete(lead, line, pos) abort
1346 return filter(sort(keys(s:servers)), 'stridx(v:val, a:lead)==0 && has_key(s:servers[v:val], "init_result")')
1349 function! lsp#server_complete_running(lead, line, pos) abort
1350 let l:all_servers = sort(keys(s:servers))
1351 return filter(l:all_servers, {idx, name ->
1352 \ stridx(name, a:lead) == 0 && lsp#is_server_running(name)
1356 function! lsp#_new_command() abort
1357 let s:last_command_id += 1
1358 call lsp#stream(1, { 'command': 1 })
1359 return s:last_command_id
1362 function! lsp#_last_command() abort
1363 return s:last_command_id