mirror of
https://github.com/prabirshrestha/vim-lsp.git
synced 2025-12-14 20:35:59 +01:00
initial commit
This commit is contained in:
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Prabir Shrestha
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
45
README.md
Normal file
45
README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
vim-lsp (experimental)
|
||||
======================
|
||||
|
||||
Async [Language Server Protocol](https://github.com/Microsoft/language-server-protocol) plugin for vim8 and neovim.
|
||||
Internally vim-lsp uses [async.vim](https://github.com/prabirshrestha/async.vim).
|
||||
|
||||
Language Server Protocol VIM Client usage
|
||||
=========================================
|
||||
|
||||
Sample usage talking with `langserver-go`
|
||||
|
||||
```vim
|
||||
function! s:on_stderr(id, data, event)
|
||||
echom 'lsp('.a:id.'):stderr:'.join(a:data, "\r\n")
|
||||
endfunction
|
||||
|
||||
function! s:on_exit(id, status, event)
|
||||
echom 'lsp('.a:id.'):exit:'.a:status
|
||||
endfunction
|
||||
|
||||
function! s:on_res1(id, data, event)
|
||||
echom 'res1'. json_encode(a:data)
|
||||
endfunction
|
||||
|
||||
" go get github.com/sourcegraph/go-langserver/langserver/cmd/langserver-go
|
||||
let s:lsp_id = lsp#lspClient#start({
|
||||
\ 'cmd': ['langserver-go', '-trace', '-logfile', expand('~/Desktop/langserver-go.log')],
|
||||
\ 'on_stderr': function('s:on_stderr'),
|
||||
\ 'on_exit': function('s:on_exit')
|
||||
\ })
|
||||
|
||||
if s:lsp_id > 0
|
||||
echom 'lsp server running'
|
||||
call lsp#lspClient#send(s:lsp_id, {
|
||||
\ 'method': 'initialize',
|
||||
\ 'params': {
|
||||
\ 'capabilities': {},
|
||||
\ 'rootPath': 'file:///D:/go/src/github.com/nsf/gocode'
|
||||
\ },
|
||||
\ 'on_response': function('s:on_res1')
|
||||
\ })
|
||||
else
|
||||
echom 'failed to start lsp server'
|
||||
endif
|
||||
```
|
||||
176
autoload/lsp/lspClient.vim
Normal file
176
autoload/lsp/lspClient.vim
Normal file
@@ -0,0 +1,176 @@
|
||||
let s:save_cpo = &cpo
|
||||
set cpo&vim
|
||||
|
||||
let s:lsp_clients = {} " { id, opts, req_seq, on_responses, stdout: { max_buffer_size, buffer, next_token, current_content_length, current_content_type } }
|
||||
let s:lsp_token_type_contentlength = 'content-length'
|
||||
let s:lsp_token_type_contenttype = 'content-type'
|
||||
let s:lsp_token_type_message = 'message'
|
||||
let s:lsp_default_max_buffer = -1
|
||||
|
||||
function! s:_on_lsp_stdout(id, data, event)
|
||||
if has_key(s:lsp_clients, a:id)
|
||||
let l:client = s:lsp_clients[a:id]
|
||||
|
||||
let l:client.stdout.buffer .= join(a:data, "\n")
|
||||
|
||||
if l:client.stdout.max_buffer_size != -1 && len(l:client.stdout.buffer) > l:client.stdout.max_buffer_size
|
||||
echom 'lsp: reached max buffer size'
|
||||
call lsp#utils#job#stop(a:id)
|
||||
endif
|
||||
|
||||
while 1
|
||||
if l:client.stdout.next_token == s:lsp_token_type_contentlength
|
||||
let l:new_line_index = stridx(l:client.stdout.buffer, "\r\n")
|
||||
if l:new_line_index >= 0
|
||||
let l:content_length_str = l:client.stdout.buffer[:l:new_line_index - 1]
|
||||
let l:client.stdout.buffer = l:client.stdout.buffer[l:new_line_index + 2:]
|
||||
let l:client.stdout.current_content_length = str2nr(split(l:content_length_str, ':')[1], 10)
|
||||
let l:client.stdout.next_token = s:lsp_token_type_contenttype
|
||||
continue
|
||||
else
|
||||
" wait for next buffer to arrive
|
||||
break
|
||||
endif
|
||||
elseif l:client.stdout.next_token == s:lsp_token_type_contenttype
|
||||
let l:new_line_index = stridx(l:client.stdout.buffer, "\r\n")
|
||||
if l:new_line_index >= 0
|
||||
let l:content_type_str = l:client.stdout.buffer[:l:new_line_index - 1]
|
||||
let l:client.stdout.buffer = l:client.stdout.buffer[l:new_line_index + 4:]
|
||||
let l:client.stdout.current_content_type = l:content_type_str
|
||||
let l:client.stdout.next_token = s:lsp_token_type_message
|
||||
continue
|
||||
else
|
||||
" wait for next buffer to arrive
|
||||
break
|
||||
endif
|
||||
else " we are reading a message
|
||||
if len(l:client.stdout.buffer) >= l:client.stdout.current_content_length
|
||||
" we have complete message
|
||||
let l:response_str = l:client.stdout.buffer[:l:client.stdout.current_content_length - 1]
|
||||
let l:client.stdout.buffer = l:client.stdout.buffer[l:client.stdout.current_content_length:]
|
||||
let l:client.stdout.next_token = s:lsp_token_type_contentlength
|
||||
let l:response_msg = json_decode(l:response_str)
|
||||
if has_key(l:response_msg, 'id') && has_key(l:client.on_responses, l:response_msg.id)
|
||||
call l:client.on_responses[l:response_msg.id](a:id, {
|
||||
\ 'response': l:response_msg,
|
||||
\ },
|
||||
\ 'on_response')
|
||||
call remove(l:client.on_responses, l:response_msg.id)
|
||||
endif
|
||||
continue
|
||||
else
|
||||
" wait for next buffer to arrive since we have incomplete message
|
||||
break
|
||||
endif
|
||||
endif
|
||||
endwhile
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:_on_lsp_stderr(id, data, event)
|
||||
if has_key(s:lsp_clients, a:id)
|
||||
let l:client = s:lsp_clients[a:id]
|
||||
if has_key(l:client.opts, 'on_stderr')
|
||||
call l:client.opts.on_stderr(a:id, a:data, a:event)
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:_on_lsp_exit(id, status, event)
|
||||
if has_key(s:lsp_clients, a:id)
|
||||
let l:client = s:lsp_clients[a:id]
|
||||
if has_key(l:client.opts, 'on_exit')
|
||||
call l:client.opts.on_exit(a:id, a:status, a:event)
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:lsp_start(opts)
|
||||
if !has_key(a:opts, 'cmd')
|
||||
return -1
|
||||
endif
|
||||
|
||||
let l:lsp_client_id = lsp#utils#job#start(a:opts.cmd, {
|
||||
\ 'on_stdout': function('s:_on_lsp_stdout'),
|
||||
\ 'on_stderr': function('s:_on_lsp_stderr'),
|
||||
\ 'on_exit': function('s:_on_lsp_exit'),
|
||||
\ })
|
||||
|
||||
let l:max_buffer_size = s:lsp_default_max_buffer
|
||||
if has_key(a:opts, 'max_buffer_size')
|
||||
let l:max_buffer_size = a:opts.max_buffer_size
|
||||
endif
|
||||
|
||||
let s:lsp_clients[l:lsp_client_id] = {
|
||||
\ 'id': l:lsp_client_id,
|
||||
\ 'opts': a:opts,
|
||||
\ 'req_seq': 0,
|
||||
\ 'on_responses': {},
|
||||
\ 'stdout': {
|
||||
\ 'max_buffer_size': l:max_buffer_size,
|
||||
\ 'buffer': '',
|
||||
\ 'next_token': s:lsp_token_type_contentlength,
|
||||
\ },
|
||||
\ }
|
||||
|
||||
return l:lsp_client_id
|
||||
endfunction
|
||||
|
||||
function! s:lsp_stop(id)
|
||||
call lsp#utils#job#stop(id)
|
||||
endfunction
|
||||
|
||||
function! s:lsp_send_request(id, opts) " opts = { method, params?, on_response }
|
||||
if has_key(s:lsp_clients, a:id)
|
||||
let l:client = s:lsp_clients[a:id]
|
||||
|
||||
let l:client.req_seq = l:client.req_seq + 1
|
||||
let l:req_seq = l:client.req_seq
|
||||
|
||||
let l:msg = { 'jsonrpc': '2.0', 'id': l:req_seq, 'method': a:opts.method }
|
||||
if has_key(a:opts, 'params')
|
||||
let l:msg.params = a:opts.params
|
||||
endif
|
||||
|
||||
let l:json = json_encode(l:msg)
|
||||
let l:req_data = 'Content-Length: ' . len(l:json) . "\r\n\r\n" . l:json
|
||||
|
||||
if has_key(a:opts, 'on_response')
|
||||
let l:client.on_responses[l:req_seq] = a:opts.on_response
|
||||
endif
|
||||
|
||||
call lsp#utils#job#send(l:client.id, l:req_data)
|
||||
|
||||
return l:req_seq
|
||||
else
|
||||
return -1
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:lsp_get_last_request_id(id)
|
||||
return s:lsp_clients[a:id].req_seq
|
||||
endfunction
|
||||
|
||||
" public apis {{{
|
||||
|
||||
function! lsp#lspClient#start(opts)
|
||||
return s:lsp_start(a:opts)
|
||||
endfunction
|
||||
|
||||
function! lsp#lspClient#stop(client_id)
|
||||
return s:lsp_stop(a:client_id)
|
||||
endfunction
|
||||
|
||||
function! lsp#lspClient#send(client_id, opts)
|
||||
return s:lsp_send_request(a:client_id, a:opts)
|
||||
endfunction
|
||||
|
||||
function! lsp#lspClient#get_last_request_id(client_id)
|
||||
return s:lsp_get_last_request_id(a:client_id)
|
||||
endfunction
|
||||
|
||||
" }}}
|
||||
|
||||
let &cpo = s:save_cpo
|
||||
unlet s:save_cpo
|
||||
" vim sw=4 ts=4 et
|
||||
175
autoload/lsp/utils/job.vim
Normal file
175
autoload/lsp/utils/job.vim
Normal file
@@ -0,0 +1,175 @@
|
||||
" Author: Prabir Shrestha <mail at prabir dot me>
|
||||
" License: The MIT License
|
||||
" Website: https://github.com/prabirshrestha/async.vim
|
||||
|
||||
let s:save_cpo = &cpo
|
||||
set cpo&vim
|
||||
|
||||
let s:jobidseq = 0
|
||||
let s:jobs = {} " { job, opts, type: 'vimjob|nvimjob'}
|
||||
let s:job_type_nvimjob = 'nvimjob'
|
||||
let s:job_type_vimjob = 'vimjob'
|
||||
let s:job_error_unsupported_job_type = -2 " unsupported job type
|
||||
|
||||
function! s:job_supported_types()
|
||||
let l:supported_types = []
|
||||
if has('nvim')
|
||||
let l:supported_types += [s:job_type_nvimjob]
|
||||
endif
|
||||
if !has('nvim') && has('job') && has('channel') && has('lambda')
|
||||
let l:supported_types += [s:job_type_vimjob]
|
||||
endif
|
||||
return l:supported_types
|
||||
endfunction
|
||||
|
||||
function! s:job_supports_type(type)
|
||||
return index(s:job_supported_types(), a:type) >= 0
|
||||
endfunction
|
||||
|
||||
function! s:out_cb(job, data, jobid, opts)
|
||||
if has_key(a:opts, 'on_stdout')
|
||||
call a:opts.on_stdout(a:jobid, split(a:data, "\n"), 'stdout')
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:err_cb(job, data, jobid, opts)
|
||||
if has_key(a:opts, 'on_stderr')
|
||||
call a:opts.on_stderr(a:jobid, split(a:data, "\n"), 'stderr')
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:exit_cb(job, status, jobid, opts)
|
||||
if has_key(a:opts, 'on_exit')
|
||||
call a:opts.on_exit(a:jobid, a:status, 'exit')
|
||||
endif
|
||||
if has_key(s:jobs, a:jobid)
|
||||
call remove(s:jobs, a:jobid)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:on_stdout(jobid, data, event)
|
||||
if has_key(s:jobs, a:jobid)
|
||||
let l:jobinfo = s:jobs[a:jobid]
|
||||
if has_key(l:jobinfo.opts, 'on_stdout')
|
||||
call l:jobinfo.opts.on_stdout(a:jobid, a:data, a:event)
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:on_stderr(jobid, data, event)
|
||||
if has_key(s:jobs, a:jobid)
|
||||
let l:jobinfo = s:jobs[a:jobid]
|
||||
if has_key(l:jobinfo.opts, 'on_stderr')
|
||||
call l:jobinfo.opts.on_stderr(a:jobid, a:data, a:event)
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:on_exit(jobid, status, event)
|
||||
if has_key(s:jobs, a:jobid)
|
||||
let l:jobinfo = s:jobs[a:jobid]
|
||||
if has_key(l:jobinfo.opts, 'on_exit')
|
||||
call l:jobinfo.opts.on_exit(a:jobid, a:status, a:event)
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:job_start(cmd, opts)
|
||||
let l:jobtypes = s:job_supported_types()
|
||||
let l:jobtype = ''
|
||||
|
||||
if has_key(a:opts, 'type')
|
||||
if type(a:opts.type, v:t_string)
|
||||
if !s:job_supports_type(a:opts.type)
|
||||
return s:job_error_unsupported_job_type
|
||||
endif
|
||||
let l:jobtype = a:opts.type
|
||||
else
|
||||
let l:jobtypes = a:opts.type
|
||||
endif
|
||||
endif
|
||||
|
||||
if empty(l:jobtype)
|
||||
" find the best jobtype
|
||||
for jobtype in l:jobtypes
|
||||
if s:job_supports_type(jobtype)
|
||||
let l:jobtype = jobtype
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
|
||||
if l:jobtype == ''
|
||||
return s:job_error_unsupported_job_type
|
||||
endif
|
||||
|
||||
|
||||
if l:jobtype == s:job_type_nvimjob
|
||||
let l:job = jobstart(a:cmd, {
|
||||
\ 'on_stdout': function('s:on_stdout'),
|
||||
\ 'on_stderr': function('s:on_stderr'),
|
||||
\ 'on_exit': function('s:on_exit'),
|
||||
\})
|
||||
let l:jobid = l:job " nvimjobid and internal jobid is same
|
||||
let s:jobs[l:jobid] = {
|
||||
\ 'type': s:job_type_nvimjob,
|
||||
\ 'opts': a:opts,
|
||||
\ }
|
||||
let s:jobs[l:jobid].job = l:job
|
||||
elseif l:jobtype == s:job_type_vimjob
|
||||
let s:jobidseq = s:jobidseq + 1
|
||||
let l:jobid = s:jobidseq
|
||||
let l:job = job_start(a:cmd, {
|
||||
\ 'out_cb': {job,data->s:out_cb(job, data, l:jobid, a:opts)},
|
||||
\ 'err_cb': {job,data->s:err_cb(job, data, l:jobid, a:opts)},
|
||||
\ 'exit_cb': {job,data->s:exit_cb(job, data, l:jobid, a:opts)},
|
||||
\ 'mode': 'raw',
|
||||
\})
|
||||
let s:jobs[l:jobid] = {
|
||||
\ 'type': s:job_type_vimjob,
|
||||
\ 'opts': a:opts,
|
||||
\ 'job': l:job,
|
||||
\ 'channel': job_getchannel(l:job)
|
||||
\ }
|
||||
else
|
||||
return s:job_error_unsupported_job_type
|
||||
endif
|
||||
|
||||
return l:jobid
|
||||
endfunction
|
||||
|
||||
function! s:job_stop(jobid)
|
||||
if has_key(s:jobs, a:jobid)
|
||||
let l:jobinfo = s:jobs[a:jobid]
|
||||
if l:jobinfo.type == s:job_type_nvimjob
|
||||
call jobstop(a:jobid)
|
||||
elseif l:jobinfo.type == s:job_type_vimjob
|
||||
call job_stop(s:jobs[a:jobid].job)
|
||||
endif
|
||||
if has_key(s:jobs, a:jobid)
|
||||
call remove(s:jobs, a:jobid)
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:job_send(jobid, data)
|
||||
let l:jobinfo = s:jobs[a:jobid]
|
||||
if l:jobinfo.type == s:job_type_nvimjob
|
||||
call jobsend(a:jobid, a:data)
|
||||
elseif l:jobinfo.type == s:job_type_vimjob
|
||||
call ch_sendraw(l:jobinfo.channel, a:data)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" public apis {{{
|
||||
function lsp#utils#job#start(cmd, opts) abort
|
||||
return s:job_start(a:cmd, a:opts)
|
||||
endfunction
|
||||
|
||||
function lsp#utils#job#stop(jobid) abort
|
||||
call s:job_stop(a:jobid)
|
||||
endfunction
|
||||
|
||||
function lsp#utils#job#send(jobid, data) abort
|
||||
call s:job_send(a:jobid, a:data)
|
||||
endfunction
|
||||
" }}}
|
||||
Reference in New Issue
Block a user