Skip to content

Commit 5c1df0c

Browse files
Prefer native lsp api when in vim. (#1362)
* add g:lsp_experimental_native_lsp flag to enable native vim lsp client * send request and correctly call on_notification * add support for native client in lsp#client#send_notification * Add native lsp client support for lsp#client#send_response * add native notification support * add support for native stop job * handle error response for document_formatting.vim and code_action.vim * add missing return for code action * fix indent * undo code action error * check for a:response to be of type dict as stderr will send string * use out_cb and err_cb instead of channel callback * bump has_native_client version check to 8.2.4780 which fixes LSP parsing messages when partial data is received * rename g:lsp_experimental_native_lsp to g:lsp_use_native_client * fix s:native_err_cb * pass env * disable lsp_use_native_client * add lsp#utils#has_native_lsp_client() * clean up log * handle requests from server * fix lint issues * update docs
1 parent ed2c818 commit 5c1df0c

File tree

5 files changed

+165
-12
lines changed

5 files changed

+165
-12
lines changed

autoload/lsp.vim

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ function! s:ensure_start(buf, server_name, cb) abort
449449
let l:server_info = l:server['server_info']
450450
if l:server['lsp_id'] > 0
451451
let l:msg = s:new_rpc_success('server already started', { 'server_name': a:server_name })
452-
call lsp#log(l:msg)
452+
call lsp#log_verbose(l:msg)
453453
call a:cb(l:msg)
454454
return
455455
endif
@@ -647,7 +647,7 @@ function! s:ensure_init(buf, server_name, cb) abort
647647

648648
if has_key(l:server, 'init_result')
649649
let l:msg = s:new_rpc_success('lsp server already initialized', { 'server_name': a:server_name, 'init_result': l:server['init_result'] })
650-
call lsp#log(l:msg)
650+
call lsp#log_verbose(l:msg)
651651
call a:cb(l:msg)
652652
return
653653
endif
@@ -721,7 +721,7 @@ function! s:ensure_conf(buf, server_name, cb) abort
721721
\ })
722722
endif
723723
let l:msg = s:new_rpc_success('configuration sent', { 'server_name': a:server_name })
724-
call lsp#log(l:msg)
724+
call lsp#log_verbose(l:msg)
725725
call a:cb(l:msg)
726726
endfunction
727727

@@ -768,7 +768,7 @@ function! s:ensure_changed(buf, server_name, cb) abort
768768

769769
if l:buffer_info['changed_tick'] == l:changed_tick
770770
let l:msg = s:new_rpc_success('not dirty', { 'server_name': a:server_name, 'path': l:path })
771-
call lsp#log(l:msg)
771+
call lsp#log_verbose(l:msg)
772772
call a:cb(l:msg)
773773
return
774774
endif
@@ -805,7 +805,7 @@ function! s:ensure_open(buf, server_name, cb) abort
805805

806806
if has_key(l:buffers, l:path)
807807
let l:msg = s:new_rpc_success('already opened', { 'server_name': a:server_name, 'path': l:path })
808-
call lsp#log(l:msg)
808+
call lsp#log_verbose(l:msg)
809809
call a:cb(l:msg)
810810
return
811811
endif

autoload/lsp/client.vim

Lines changed: 138 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ set cpoptions&vim
33

44
let s:clients = {} " { client_id: ctx }
55

6+
" Vars used by native lsp
7+
let s:jobidseq = 0
8+
69
function! s:create_context(client_id, opts) abort
710
if a:client_id <= 0
811
return {}
@@ -212,9 +215,7 @@ let s:send_type_notification = 2
212215
let s:send_type_response = 3
213216
function! s:lsp_send(id, opts, type) abort " opts = { id?, method?, result?, params?, on_notification }
214217
let l:ctx = get(s:clients, a:id, {})
215-
if empty(l:ctx)
216-
return -1
217-
endif
218+
if empty(l:ctx) | return -1 | endif
218219

219220
let l:request = { 'jsonrpc': '2.0' }
220221

@@ -281,26 +282,156 @@ function! s:is_server_instantiated_notification(notification) abort
281282
return !has_key(a:notification, 'request')
282283
endfunction
283284

285+
function! s:native_out_cb(cbctx, channel, response) abort
286+
if !has_key(a:cbctx, 'ctx') | return | endif
287+
let l:ctx = a:cbctx['ctx']
288+
if has_key(a:response, 'method') && has_key(a:response, 'id')
289+
" it is a request from a server
290+
let l:request = a:response
291+
if has_key(l:ctx['opts'], 'on_request')
292+
call l:ctx['opts']['on_request'](l:ctx['id'], l:request)
293+
endif
294+
elseif !has_key(a:response, 'id') && has_key(l:ctx['opts'], 'on_notification')
295+
" it is a notification
296+
let l:on_notification_data = { 'response': a:response }
297+
try
298+
call l:ctx['opts']['on_notification'](l:ctx['id'], l:on_notification_data, 'on_notification')
299+
catch
300+
call lsp#log('s:native_notification_callback on_notification() error', v:exception, v:throwpoint)
301+
endtry
302+
endif
303+
endfunction
304+
305+
function! s:native_err_cb(cbctx, channel, response) abort
306+
if !has_key(a:cbctx, 'ctx') | return | endif
307+
let l:ctx = a:cbctx['ctx']
308+
if has_key(l:ctx['opts'], 'on_stderr')
309+
try
310+
call l:ctx['opts']['on_stderr'](l:ctx['id'], a:response, 'stderr')
311+
catch
312+
call lsp#log('s:on_stderr exception', v:exception, v:throwpoint)
313+
echom v:exception
314+
endtry
315+
endif
316+
endfunction
317+
284318
" public apis {{{
285319

286320
function! lsp#client#start(opts) abort
321+
if g:lsp_use_native_client && lsp#utils#has_native_lsp_client()
322+
if has_key(a:opts, 'cmd')
323+
let l:cbctx = {}
324+
let l:jobopt = { 'in_mode': 'lsp', 'out_mode': 'lsp', 'noblock': 1,
325+
\ 'out_cb': function('s:native_out_cb', [l:cbctx]),
326+
\ 'err_cb': function('s:native_err_cb', [l:cbctx]),
327+
\ }
328+
if has_key(a:opts, 'cwd') | let l:jobopt['cwd'] = a:opts['cwd'] | endif
329+
if has_key(a:opts, 'env') | let l:jobopt['env'] = a:opts['env'] | endif
330+
let s:jobidseq += 1
331+
let l:jobid = s:jobidseq " jobid == clientid
332+
call lsp#log_verbose('using native lsp client')
333+
let l:job = job_start(a:opts['cmd'], l:jobopt)
334+
if job_status(l:job) !=? 'run' | return -1 | endif
335+
let l:ctx = s:create_context(l:jobid, a:opts)
336+
let l:ctx['id'] = l:jobid
337+
let l:ctx['job'] = l:job
338+
let l:ctx['channel'] = job_getchannel(l:job)
339+
let l:cbctx['ctx'] = l:ctx
340+
return l:jobid
341+
elseif has_key(a:opts, 'tcp')
342+
" add support for tcp
343+
call lsp#log('tcp not supported when using native lsp client')
344+
return -1
345+
endif
346+
endif
287347
return s:lsp_start(a:opts)
288348
endfunction
289349

290350
function! lsp#client#stop(client_id) abort
291-
return s:lsp_stop(a:client_id)
351+
if g:lsp_use_native_client && lsp#utils#has_native_lsp_client()
352+
let l:ctx = get(s:clients, a:client_id, {})
353+
if empty(l:ctx) | return | endif
354+
call job_stop(l:ctx['job'])
355+
else
356+
return s:lsp_stop(a:client_id)
357+
endif
292358
endfunction
293359

294360
function! lsp#client#send_request(client_id, opts) abort
295-
return s:lsp_send(a:client_id, a:opts, s:send_type_request)
361+
if g:lsp_use_native_client && lsp#utils#has_native_lsp_client()
362+
let l:ctx = get(s:clients, a:client_id, {})
363+
if empty(l:ctx) | return -1 | endif
364+
let l:request = {}
365+
" id shouldn't be passed to request as vim will overwrite it. refer to :h language-server-protocol
366+
if has_key(a:opts, 'method') | let l:request['method'] = a:opts['method'] | endif
367+
if has_key(a:opts, 'params') | let l:request['params'] = a:opts['params'] | endif
368+
369+
call ch_sendexpr(l:ctx['channel'], l:request, { 'callback': function('s:on_response_native', [l:ctx, l:request]) })
370+
let l:ctx['requests'][l:request['id']] = l:request
371+
if has_key(a:opts, 'on_notification')
372+
let l:ctx['on_notifications'][l:request['id']] = a:opts['on_notification']
373+
endif
374+
let l:ctx['request_sequence'] = l:request['id']
375+
return l:request['id']
376+
else
377+
return s:lsp_send(a:client_id, a:opts, s:send_type_request)
378+
endif
379+
endfunction
380+
381+
function! s:on_response_native(ctx, request, channel, response) abort
382+
" request -> response
383+
let l:on_notification_data = { 'response': a:response, 'request': a:request }
384+
if has_key(a:ctx['opts'], 'on_notification')
385+
" call client's on_notification first
386+
try
387+
call a:ctx['opts']['on_notification'](a:ctx['id'], l:on_notification_data, 'on_notification')
388+
catch
389+
call lsp#log('s:on_response_native client option on_notification() error', v:exception, v:throwpoint)
390+
endtry
391+
endif
392+
if has_key(a:ctx['on_notifications'], a:request['id'])
393+
" call lsp#client#send({ 'on_notification' }) second
394+
try
395+
call a:ctx['on_notifications'][a:request['id']](a:ctx['id'], l:on_notification_data, 'on_notification')
396+
catch
397+
call lsp#log('s:on_response_native client request on_notification() error', v:exception, v:throwpoint, a:request, a:response)
398+
endtry
399+
unlet a:ctx['on_notifications'][a:response['id']]
400+
if has_key(a:ctx['requests'], a:response['id'])
401+
unlet a:ctx['requests'][a:response['id']]
402+
else
403+
call lsp#log('cannot find the request corresponding to response: ', a:response)
404+
endif
405+
endif
296406
endfunction
297407

298408
function! lsp#client#send_notification(client_id, opts) abort
299-
return s:lsp_send(a:client_id, a:opts, s:send_type_notification)
409+
if g:lsp_use_native_client && lsp#utils#has_native_lsp_client()
410+
let l:ctx = get(s:clients, a:client_id, {})
411+
if empty(l:ctx) | return -1 | endif
412+
let l:request = {}
413+
if has_key(a:opts, 'method') | let l:request['method'] = a:opts['method'] | endif
414+
if has_key(a:opts, 'params') | let l:request['params'] = a:opts['params'] | endif
415+
call ch_sendexpr(l:ctx['channel'], l:request)
416+
return 0
417+
else
418+
return s:lsp_send(a:client_id, a:opts, s:send_type_notification)
419+
endif
300420
endfunction
301421

302422
function! lsp#client#send_response(client_id, opts) abort
303-
return s:lsp_send(a:client_id, a:opts, s:send_type_response)
423+
if g:lsp_use_native_client && lsp#utils#has_native_lsp_client()
424+
let l:ctx = get(s:clients, a:client_id, {})
425+
if empty(l:ctx) | return -1 | endif
426+
let l:request = {}
427+
if has_key(a:opts, 'id') | let l:request['id'] = a:opts['id'] | endif
428+
if has_key(a:opts, 'result') | let l:request['result'] = a:opts['result'] | endif
429+
if has_key(a:opts, 'error') | let l:request['error'] = a:opts['error'] | endif
430+
call ch_sendexpr(l:ctx['channel'], l:request)
431+
return 0
432+
else
433+
return s:lsp_send(a:client_id, a:opts, s:send_type_response)
434+
endif
304435
endfunction
305436

306437
function! lsp#client#get_last_request_id(client_id) abort

autoload/lsp/utils.vim

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ function! lsp#utils#has_lua() abort
33
return s:has_lua
44
endfunction
55

6+
let s:has_native_lsp_client = !has('nvim') && has('patch-8.2.4780')
7+
function! lsp#utils#has_native_lsp_client() abort
8+
return s:has_native_lsp_client
9+
endfunction
10+
611
let s:has_virtual_text = exists('*nvim_buf_set_virtual_text') && exists('*nvim_create_namespace')
712
function! lsp#utils#_has_nvim_virtual_text() abort
813
return s:has_virtual_text

doc/vim-lsp.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ CONTENTS *vim-lsp-contents*
1414
Health Check |vim-lsp-healthcheck|
1515
Options |vim-lsp-options|
1616
g:lsp_auto_enable |g:lsp_auto_enable|
17+
g:lsp_use_native_client |g:lsp_use_native_client|
1718
g:lsp_preview_keep_focus |g:lsp_preview_keep_focus|
1819
g:lsp_preview_float |g:lsp_preview_float|
1920
g:lsp_preview_autoclose |g:lsp_preview_autoclose|
@@ -207,6 +208,9 @@ http://downloads.sourceforge.net/luabinaries/lua-5.3.2_Win32_dllw4_lib.zip
207208
64bit:
208209
http://downloads.sourceforge.net/luabinaries/lua-5.3.2_Win64_dllw4_lib.zip
209210

211+
If you are using vim set `let g:lsp_use_native_client = 1` and make sure you
212+
are running vim 8.2.4780+.
213+
210214
Set |g:lsp_semantic_enabled| to 0.
211215

212216
Set |g:lsp_format_sync_timeout| to a reasonable value such as `1000`.
@@ -297,6 +301,18 @@ g:lsp_auto_enable *g:lsp_auto_enable*
297301
let g:lsp_auto_enable = 1
298302
let g:lsp_auto_enable = 0
299303
304+
g:lsp_use_native_client *g:lsp_use_native_client*
305+
Type: |Number|
306+
Default: `0`
307+
308+
Enable native lsp client support for vim 8.2.4780+. No impact for neovim.
309+
TCP language servers are not supported and should be set to 0 if one is
310+
used.
311+
312+
Example: >
313+
let g:lsp_use_native_client = 1
314+
let g:lsp_use_native_client = 0
315+
300316
g:lsp_preview_keep_focus *g:lsp_preview_keep_focus*
301317
Type: |Number|
302318
Default: `1`

plugin/lsp.vim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ endif
44
let g:lsp_loaded = 1
55

66
let g:lsp_use_lua = get(g:, 'lsp_use_lua', has('nvim-0.4.0') || (has('lua') && has('patch-8.2.0775')))
7+
let g:lsp_use_native_client = get(g:, 'lsp_use_native_client', 0)
78
let g:lsp_auto_enable = get(g:, 'lsp_auto_enable', 1)
89
let g:lsp_async_completion = get(g:, 'lsp_async_completion', 0)
910
let g:lsp_log_file = get(g:, 'lsp_log_file', '')

0 commit comments

Comments
 (0)