From ee854b4c555739c1838764097bb02cb607a5838d Mon Sep 17 00:00:00 2001 From: mattn Date: Fri, 1 Jan 2021 01:28:06 +0900 Subject: [PATCH] Add tcp feature (#985) * Add tcp feature * Update doc * Update async.vim embed * Update doc --- autoload/lsp.vim | 55 +++++++++++++++++++++++--------------- autoload/lsp/client.vim | 20 +++++++++----- autoload/lsp/utils/job.vim | 50 ++++++++++++++++++++++++++++++++-- doc/vim-lsp.txt | 9 +++---- 4 files changed, 98 insertions(+), 36 deletions(-) diff --git a/autoload/lsp.vim b/autoload/lsp.vim index 16bf3cee..f9b74b7f 100644 --- a/autoload/lsp.vim +++ b/autoload/lsp.vim @@ -412,30 +412,41 @@ function! s:ensure_start(buf, server_name, cb) abort return endif - 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) + 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:lsp_id = lsp#client#start({ + \ '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]), + \ }) 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:lsp_id = lsp#client#start({ - \ '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 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 }) diff --git a/autoload/lsp/client.vim b/autoload/lsp/client.vim index 797bdf76..0a51b097 100644 --- a/autoload/lsp/client.vim +++ b/autoload/lsp/client.vim @@ -183,16 +183,22 @@ function! s:on_exit(id, status, event) abort endfunction function! s:lsp_start(opts) abort - if !has_key(a:opts, 'cmd') + if has_key(a:opts, 'cmd') + let l:client_id = lsp#utils#job#start(a:opts.cmd, { + \ 'on_stdout': function('s:on_stdout'), + \ 'on_stderr': function('s:on_stderr'), + \ 'on_exit': function('s:on_exit'), + \ }) + elseif has_key(a:opts, 'tcp') + let l:client_id = lsp#utils#job#connect(a:opts.tcp, { + \ 'on_stdout': function('s:on_stdout'), + \ 'on_stderr': function('s:on_stderr'), + \ 'on_exit': function('s:on_exit'), + \ }) + else return -1 endif - let l:client_id = lsp#utils#job#start(a:opts.cmd, { - \ 'on_stdout': function('s:on_stdout'), - \ 'on_stderr': function('s:on_stderr'), - \ 'on_exit': function('s:on_exit'), - \ }) - let l:ctx = s:create_context(l:client_id, a:opts) let l:ctx['id'] = l:client_id diff --git a/autoload/lsp/utils/job.vim b/autoload/lsp/utils/job.vim index b7605ca5..2d427613 100644 --- a/autoload/lsp/utils/job.vim +++ b/autoload/lsp/utils/job.vim @@ -1,4 +1,4 @@ -" https://github.com/prabirshrestha/async.vim#d15123af3483350e235397116b554cb37e705130 (dirty) +" https://github.com/prabirshrestha/async.vim#236debf1a68d69a74f1f6647c273b0477e1ec1bf (dirty) " :AsyncEmbed path=./autoload/lsp/utils/job.vim namespace=lsp#utils#job " Author: Prabir Shrestha @@ -195,7 +195,11 @@ function! s:job_stop(jobid) abort " silently for 'E900: Invalid job id' exception endtry elseif l:jobinfo.type == s:job_type_vimjob - call job_stop(s:jobs[a:jobid].job) + if type(s:jobs[a:jobid].job) == v:t_job + call job_stop(s:jobs[a:jobid].job) + elseif type(s:jobs[a:jobid].job) == v:t_channel + call ch_close(s:jobs[a:jobid].job) + endif endif endif endfunction @@ -306,6 +310,21 @@ function! s:job_pid(jobid) abort return 0 endfunction +function! s:callback_cb(jobid, opts, ch, data) abort + if has_key(a:opts, 'on_stdout') + call a:opts.on_stdout(a:jobid, split(a:data, "\n", 1), 'stdout') + endif +endfunction + +function! s:close_cb(jobid, opts, ch) abort + if has_key(a:opts, 'on_exit') + call a:opts.on_exit(a:jobid, 'closed', 'exit') + endif + if has_key(s:jobs, a:jobid) + call remove(s:jobs, a:jobid) + endif +endfunction + " public apis {{{ function! lsp#utils#job#start(cmd, opts) abort return s:job_start(a:cmd, a:opts) @@ -328,6 +347,33 @@ endfunction function! lsp#utils#job#pid(jobid) abort return s:job_pid(a:jobid) endfunction + +function! lsp#utils#job#connect(addr, opts) abort + let s:jobidseq = s:jobidseq + 1 + let l:jobid = s:jobidseq + let l:retry = 0 + while l:retry < 5 + let l:ch = ch_open(a:addr, {'waittime': 1000}) + call ch_setoptions(l:ch, { + \ 'callback': function('s:callback_cb', [l:jobid, a:opts]), + \ 'close_cb': function('s:close_cb', [l:jobid, a:opts]), + \ 'mode': 'raw', + \}) + if ch_status(l:ch) ==# 'open' + break + endif + sleep 100m + let l:retry += 1 + endwhile + let s:jobs[l:jobid] = { + \ 'type': s:job_type_vimjob, + \ 'opts': a:opts, + \ 'job': l:ch, + \ 'channel': l:ch, + \ 'buffer': '' + \} + return l:jobid +endfunction " }}} let &cpo = s:save_cpo diff --git a/doc/vim-lsp.txt b/doc/vim-lsp.txt index 062b6c90..ef98f53d 100644 --- a/doc/vim-lsp.txt +++ b/doc/vim-lsp.txt @@ -220,15 +220,14 @@ on pyls (https://github.com/palantir/python-language-server) augroup END < TCP SERVERS *vim-lsp-tcp* -You can use netcat to connect to LSP servers that don't support stdio, but do -support TCP. Set your cmd to use your netcat executable (`nc` on unix-likes), -localhost, and your server's port. The Godot game engine uses 6008 as its LSP -port and godot ftplugins define gdscript or gdscript3 filetype: > +You can use tcp to connect to LSP servers that don't support stdio. Set host +and port to tcp. The Godot game engine uses 6008 as its LSP port and godot +ftplugins define gdscript or gdscript3 filetype: > au User lsp_setup \ call lsp#register_server({ \ 'name': 'godot', - \ 'cmd': ["nc", "localhost", "6008"], + \ 'tcp': "localhost:6008", \ 'allowlist': ['gdscript3', 'gdscript'] \ }) >