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
This commit is contained in:
Prabir Shrestha
2022-12-19 19:44:45 -08:00
committed by GitHub
parent ed2c818b1e
commit 5c1df0cf12
5 changed files with 165 additions and 12 deletions

View File

@@ -449,7 +449,7 @@ function! s:ensure_start(buf, server_name, cb) abort
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(l:msg)
call lsp#log_verbose(l:msg)
call a:cb(l:msg)
return
endif
@@ -647,7 +647,7 @@ function! s:ensure_init(buf, server_name, cb) abort
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(l:msg)
call lsp#log_verbose(l:msg)
call a:cb(l:msg)
return
endif
@@ -721,7 +721,7 @@ function! s:ensure_conf(buf, server_name, cb) abort
\ })
endif
let l:msg = s:new_rpc_success('configuration sent', { 'server_name': a:server_name })
call lsp#log(l:msg)
call lsp#log_verbose(l:msg)
call a:cb(l:msg)
endfunction
@@ -768,7 +768,7 @@ function! s:ensure_changed(buf, server_name, cb) abort
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(l:msg)
call lsp#log_verbose(l:msg)
call a:cb(l:msg)
return
endif
@@ -805,7 +805,7 @@ function! s:ensure_open(buf, server_name, cb) abort
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(l:msg)
call lsp#log_verbose(l:msg)
call a:cb(l:msg)
return
endif

View File

@@ -3,6 +3,9 @@ set cpoptions&vim
let s:clients = {} " { client_id: ctx }
" Vars used by native lsp
let s:jobidseq = 0
function! s:create_context(client_id, opts) abort
if a:client_id <= 0
return {}
@@ -212,9 +215,7 @@ let s:send_type_notification = 2
let s:send_type_response = 3
function! s:lsp_send(id, opts, type) abort " opts = { id?, method?, result?, params?, on_notification }
let l:ctx = get(s:clients, a:id, {})
if empty(l:ctx)
return -1
endif
if empty(l:ctx) | return -1 | endif
let l:request = { 'jsonrpc': '2.0' }
@@ -281,26 +282,156 @@ function! s:is_server_instantiated_notification(notification) abort
return !has_key(a:notification, 'request')
endfunction
function! s:native_out_cb(cbctx, channel, response) abort
if !has_key(a:cbctx, 'ctx') | return | endif
let l:ctx = a:cbctx['ctx']
if has_key(a:response, 'method') && has_key(a:response, 'id')
" it is a request from a server
let l:request = a:response
if has_key(l:ctx['opts'], 'on_request')
call l:ctx['opts']['on_request'](l:ctx['id'], l:request)
endif
elseif !has_key(a:response, 'id') && has_key(l:ctx['opts'], 'on_notification')
" it is a notification
let l:on_notification_data = { 'response': a:response }
try
call l:ctx['opts']['on_notification'](l:ctx['id'], l:on_notification_data, 'on_notification')
catch
call lsp#log('s:native_notification_callback on_notification() error', v:exception, v:throwpoint)
endtry
endif
endfunction
function! s:native_err_cb(cbctx, channel, response) abort
if !has_key(a:cbctx, 'ctx') | return | endif
let l:ctx = a:cbctx['ctx']
if has_key(l:ctx['opts'], 'on_stderr')
try
call l:ctx['opts']['on_stderr'](l:ctx['id'], a:response, 'stderr')
catch
call lsp#log('s:on_stderr exception', v:exception, v:throwpoint)
echom v:exception
endtry
endif
endfunction
" public apis {{{
function! lsp#client#start(opts) abort
if g:lsp_use_native_client && lsp#utils#has_native_lsp_client()
if has_key(a:opts, 'cmd')
let l:cbctx = {}
let l:jobopt = { 'in_mode': 'lsp', 'out_mode': 'lsp', 'noblock': 1,
\ 'out_cb': function('s:native_out_cb', [l:cbctx]),
\ 'err_cb': function('s:native_err_cb', [l:cbctx]),
\ }
if has_key(a:opts, 'cwd') | let l:jobopt['cwd'] = a:opts['cwd'] | endif
if has_key(a:opts, 'env') | let l:jobopt['env'] = a:opts['env'] | endif
let s:jobidseq += 1
let l:jobid = s:jobidseq " jobid == clientid
call lsp#log_verbose('using native lsp client')
let l:job = job_start(a:opts['cmd'], l:jobopt)
if job_status(l:job) !=? 'run' | return -1 | endif
let l:ctx = s:create_context(l:jobid, a:opts)
let l:ctx['id'] = l:jobid
let l:ctx['job'] = l:job
let l:ctx['channel'] = job_getchannel(l:job)
let l:cbctx['ctx'] = l:ctx
return l:jobid
elseif has_key(a:opts, 'tcp')
" add support for tcp
call lsp#log('tcp not supported when using native lsp client')
return -1
endif
endif
return s:lsp_start(a:opts)
endfunction
function! lsp#client#stop(client_id) abort
if g:lsp_use_native_client && lsp#utils#has_native_lsp_client()
let l:ctx = get(s:clients, a:client_id, {})
if empty(l:ctx) | return | endif
call job_stop(l:ctx['job'])
else
return s:lsp_stop(a:client_id)
endif
endfunction
function! lsp#client#send_request(client_id, opts) abort
if g:lsp_use_native_client && lsp#utils#has_native_lsp_client()
let l:ctx = get(s:clients, a:client_id, {})
if empty(l:ctx) | return -1 | endif
let l:request = {}
" id shouldn't be passed to request as vim will overwrite it. refer to :h language-server-protocol
if has_key(a:opts, 'method') | let l:request['method'] = a:opts['method'] | endif
if has_key(a:opts, 'params') | let l:request['params'] = a:opts['params'] | endif
call ch_sendexpr(l:ctx['channel'], l:request, { 'callback': function('s:on_response_native', [l:ctx, l:request]) })
let l:ctx['requests'][l:request['id']] = l:request
if has_key(a:opts, 'on_notification')
let l:ctx['on_notifications'][l:request['id']] = a:opts['on_notification']
endif
let l:ctx['request_sequence'] = l:request['id']
return l:request['id']
else
return s:lsp_send(a:client_id, a:opts, s:send_type_request)
endif
endfunction
function! s:on_response_native(ctx, request, channel, response) abort
" request -> response
let l:on_notification_data = { 'response': a:response, 'request': a:request }
if has_key(a:ctx['opts'], 'on_notification')
" call client's on_notification first
try
call a:ctx['opts']['on_notification'](a:ctx['id'], l:on_notification_data, 'on_notification')
catch
call lsp#log('s:on_response_native client option on_notification() error', v:exception, v:throwpoint)
endtry
endif
if has_key(a:ctx['on_notifications'], a:request['id'])
" call lsp#client#send({ 'on_notification' }) second
try
call a:ctx['on_notifications'][a:request['id']](a:ctx['id'], l:on_notification_data, 'on_notification')
catch
call lsp#log('s:on_response_native client request on_notification() error', v:exception, v:throwpoint, a:request, a:response)
endtry
unlet a:ctx['on_notifications'][a:response['id']]
if has_key(a:ctx['requests'], a:response['id'])
unlet a:ctx['requests'][a:response['id']]
else
call lsp#log('cannot find the request corresponding to response: ', a:response)
endif
endif
endfunction
function! lsp#client#send_notification(client_id, opts) abort
if g:lsp_use_native_client && lsp#utils#has_native_lsp_client()
let l:ctx = get(s:clients, a:client_id, {})
if empty(l:ctx) | return -1 | endif
let l:request = {}
if has_key(a:opts, 'method') | let l:request['method'] = a:opts['method'] | endif
if has_key(a:opts, 'params') | let l:request['params'] = a:opts['params'] | endif
call ch_sendexpr(l:ctx['channel'], l:request)
return 0
else
return s:lsp_send(a:client_id, a:opts, s:send_type_notification)
endif
endfunction
function! lsp#client#send_response(client_id, opts) abort
if g:lsp_use_native_client && lsp#utils#has_native_lsp_client()
let l:ctx = get(s:clients, a:client_id, {})
if empty(l:ctx) | return -1 | endif
let l:request = {}
if has_key(a:opts, 'id') | let l:request['id'] = a:opts['id'] | endif
if has_key(a:opts, 'result') | let l:request['result'] = a:opts['result'] | endif
if has_key(a:opts, 'error') | let l:request['error'] = a:opts['error'] | endif
call ch_sendexpr(l:ctx['channel'], l:request)
return 0
else
return s:lsp_send(a:client_id, a:opts, s:send_type_response)
endif
endfunction
function! lsp#client#get_last_request_id(client_id) abort

View File

@@ -3,6 +3,11 @@ function! lsp#utils#has_lua() abort
return s:has_lua
endfunction
let s:has_native_lsp_client = !has('nvim') && has('patch-8.2.4780')
function! lsp#utils#has_native_lsp_client() abort
return s:has_native_lsp_client
endfunction
let s:has_virtual_text = exists('*nvim_buf_set_virtual_text') && exists('*nvim_create_namespace')
function! lsp#utils#_has_nvim_virtual_text() abort
return s:has_virtual_text

View File

@@ -14,6 +14,7 @@ CONTENTS *vim-lsp-contents*
Health Check |vim-lsp-healthcheck|
Options |vim-lsp-options|
g:lsp_auto_enable |g:lsp_auto_enable|
g:lsp_use_native_client |g:lsp_use_native_client|
g:lsp_preview_keep_focus |g:lsp_preview_keep_focus|
g:lsp_preview_float |g:lsp_preview_float|
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
64bit:
http://downloads.sourceforge.net/luabinaries/lua-5.3.2_Win64_dllw4_lib.zip
If you are using vim set `let g:lsp_use_native_client = 1` and make sure you
are running vim 8.2.4780+.
Set |g:lsp_semantic_enabled| to 0.
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*
let g:lsp_auto_enable = 1
let g:lsp_auto_enable = 0
g:lsp_use_native_client *g:lsp_use_native_client*
Type: |Number|
Default: `0`
Enable native lsp client support for vim 8.2.4780+. No impact for neovim.
TCP language servers are not supported and should be set to 0 if one is
used.
Example: >
let g:lsp_use_native_client = 1
let g:lsp_use_native_client = 0
g:lsp_preview_keep_focus *g:lsp_preview_keep_focus*
Type: |Number|
Default: `1`

View File

@@ -4,6 +4,7 @@ endif
let g:lsp_loaded = 1
let g:lsp_use_lua = get(g:, 'lsp_use_lua', has('nvim-0.4.0') || (has('lua') && has('patch-8.2.0775')))
let g:lsp_use_native_client = get(g:, 'lsp_use_native_client', 0)
let g:lsp_auto_enable = get(g:, 'lsp_auto_enable', 1)
let g:lsp_async_completion = get(g:, 'lsp_async_completion', 0)
let g:lsp_log_file = get(g:, 'lsp_log_file', '')