initial commit

This commit is contained in:
Prabir Shrestha
2016-11-06 01:06:39 -08:00
commit b49f25c2b3
4 changed files with 417 additions and 0 deletions

21
LICENSE Normal file
View 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
View 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
View 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
View 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
" }}}