From f410996996189fb384ebf9641a1477b5115d9e6e Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Wed, 21 Dec 2016 12:21:36 -0500 Subject: [PATCH 1/6] Add a lock util Realized that messages could be sent over the same stream while we're waiting for a response for the previous message. This is not good. Made a lock util to help with that. I see this happening a lot once we start trying to do completion type items, so that's where I came up with this part. --- autoload/langserver/lock.vim | 16 ++++++++++++++++ tests/test_lock.vader | 10 ++++++++++ 2 files changed, 26 insertions(+) create mode 100644 autoload/langserver/lock.vim create mode 100644 tests/test_lock.vader diff --git a/autoload/langserver/lock.vim b/autoload/langserver/lock.vim new file mode 100644 index 0000000..83c6cdb --- /dev/null +++ b/autoload/langserver/lock.vim @@ -0,0 +1,16 @@ + +function! s:dict_lock() dict + let self.locked = v:true +endfunction + +function! s:dict_unlock() dict + let self.locked = v:false +endfunction + +function! langserver#lock#semaphore() abort + let l:ret = {} + let l:ret.locked = v:false + let l:ret.lock = function('s:dict_lock') + let l:ret.unlock = function('s:dict_unlock') + return l:ret +endfunction diff --git a/tests/test_lock.vader b/tests/test_lock.vader new file mode 100644 index 0000000..b6cdad1 --- /dev/null +++ b/tests/test_lock.vader @@ -0,0 +1,10 @@ +Execute (Test Lock): + let my_lock = langserver#lock#semaphore() + + AssertEqual v:false, my_lock.locked + + call my_lock.lock() + AssertEqual v:true, my_lock.locked + + call my_lock.unlock() + AssertEqual v:false, my_lock.locked From c37e5b0a957fa66077f3adce5619d476caa13102 Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Wed, 21 Dec 2016 12:23:23 -0500 Subject: [PATCH 2/6] Add some stubs for completion --- autoload/langserver/completion.vim | 38 ++++++++++++++++++++++++++ rplugin/python3/deoplete/langserver.py | 24 ++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 autoload/langserver/completion.vim create mode 100644 rplugin/python3/deoplete/langserver.py diff --git a/autoload/langserver/completion.vim b/autoload/langserver/completion.vim new file mode 100644 index 0000000..81b0efa --- /dev/null +++ b/autoload/langserver/completion.vim @@ -0,0 +1,38 @@ +function! langserver#completion#callback(id, data, event) abort + let l:parsed_data = langserver#callbacks#data(a:id, a:data, a:event) + if l:parsed_data == {} + return + endif + + " {'isIncomplete': bool, 'items': [CompletionItems]} + let l:completion_items = l:parsed_data['items'] + +endfunction + +function! langserver#completion#request(...) abort + return langserver#client#send(langserver#util#get_lsp_id(), { + \ 'method': 'textDocument/completion', + \ 'params': langserver#util#get_text_document_position_params(), + \ }) +endfunction + +let s:CompletionItemKind = { + \ 'Text': 1, + \ 'Method': 2, + \ 'Function': 3, + \ 'Constructor': 4, + \ 'Field': 5, + \ 'Variable': 6, + \ 'Class': 7, + \ 'Interface': 8, + \ 'Module': 9, + \ 'Property': 10, + \ 'Unit': 11, + \ 'Value': 12, + \ 'Enum': 13, + \ 'Keyword': 14, + \ 'Snippet': 15, + \ 'Color': 16, + \ 'File': 17, + \ 'Reference': 18 + \ } diff --git a/rplugin/python3/deoplete/langserver.py b/rplugin/python3/deoplete/langserver.py new file mode 100644 index 0000000..118b88c --- /dev/null +++ b/rplugin/python3/deoplete/langserver.py @@ -0,0 +1,24 @@ +from .base import Base + + +class Source(Base): + def __init__(self, nvim): + super(Source, self).__init__(nvim) + + self.nvim = nvim + self.name = langserver + self.mark = '[LSP]' + + def on_event(self, context, filename=''): + pass + + def gather_candidates(self, context): + user_input = context['input'] + filetype = context.get('filetype', '') + complete_str context['complete_str'] + + return [ + { + 'word': user_input + '_hello', + } + ] From 055b35cd561a67d02ae52c9c0932bfc04b83175f Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Wed, 21 Dec 2016 12:31:04 -0500 Subject: [PATCH 3/6] Small improvement to lock --- autoload/langserver/lock.vim | 16 ++++++++++++++-- tests/test_lock.vader | 5 +++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/autoload/langserver/lock.vim b/autoload/langserver/lock.vim index 83c6cdb..81a9826 100644 --- a/autoload/langserver/lock.vim +++ b/autoload/langserver/lock.vim @@ -7,10 +7,22 @@ function! s:dict_unlock() dict let self.locked = v:false endfunction +function! s:set_id(job_id) dict + let self.id = a:job_id +endfunction + +function! s:get_id() dict + return self.id +endfunction + function! langserver#lock#semaphore() abort - let l:ret = {} - let l:ret.locked = v:false + let l:ret = { + \ 'id': -1, + \ 'locked': v:false, + \ } let l:ret.lock = function('s:dict_lock') let l:ret.unlock = function('s:dict_unlock') + let l:ret.set_id = function('s:set_id') + let l:ret.get_id = function('s:get_id') return l:ret endfunction diff --git a/tests/test_lock.vader b/tests/test_lock.vader index b6cdad1..f00580c 100644 --- a/tests/test_lock.vader +++ b/tests/test_lock.vader @@ -8,3 +8,8 @@ Execute (Test Lock): call my_lock.unlock() AssertEqual v:false, my_lock.locked + + AssertEqual -1, my_lock.get_id() + + call my_lock.set_id(1) + AssertEqual 1, my_lock.get_id() From 93c3bc691ed5e41e34727fd225905d4d3e1bdfcd Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Wed, 21 Dec 2016 13:13:34 -0500 Subject: [PATCH 4/6] Update README --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 960889f..906815b 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,21 @@ let g:langserver_executables = { \ } ``` +To start the language server, run the command: + +```vim +:LSPStart +``` + +After starting the language server, you should be able to run commands like: + +```vim +:LSPGoto +:LSPHover +``` + +and some more to come. + More configuration to come... ## Plans From 4b1f7ef2a879bce599a3aae3a7d1aec628f65924 Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Wed, 21 Dec 2016 13:57:13 -0500 Subject: [PATCH 5/6] Update rplugin source --- rplugin/python3/deoplete/langserver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rplugin/python3/deoplete/langserver.py b/rplugin/python3/deoplete/langserver.py index 118b88c..5a282b4 100644 --- a/rplugin/python3/deoplete/langserver.py +++ b/rplugin/python3/deoplete/langserver.py @@ -6,7 +6,7 @@ def __init__(self, nvim): super(Source, self).__init__(nvim) self.nvim = nvim - self.name = langserver + self.name = 'langserver' self.mark = '[LSP]' def on_event(self, context, filename=''): @@ -15,7 +15,7 @@ def on_event(self, context, filename=''): def gather_candidates(self, context): user_input = context['input'] filetype = context.get('filetype', '') - complete_str context['complete_str'] + complete_str = context['complete_str'] return [ { From 7413c1d9b46acf31d593317b2002cfec319ed6ab Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Wed, 21 Dec 2016 14:12:02 -0500 Subject: [PATCH 6/6] Working on improving callback setup --- autoload/langserver/api/textDocument.vim | 4 +++ autoload/langserver/client.vim | 8 ++++-- autoload/langserver/hover.vim | 5 +++- autoload/langserver/lock.vim | 34 ++++++++++++++++++------ tests/test_lock.vader | 11 ++++++-- 5 files changed, 49 insertions(+), 13 deletions(-) diff --git a/autoload/langserver/api/textDocument.vim b/autoload/langserver/api/textDocument.vim index 25f5e02..6cf3f09 100644 --- a/autoload/langserver/api/textDocument.vim +++ b/autoload/langserver/api/textDocument.vim @@ -7,3 +7,7 @@ function! langserver#api#textDocument#definition(request) abort \ {}, \ ) endfunction + +function! langserver#api#textDocument#completion(request) abort + +endfunction diff --git a/autoload/langserver/client.vim b/autoload/langserver/client.vim index 2e64e54..b2315af 100644 --- a/autoload/langserver/client.vim +++ b/autoload/langserver/client.vim @@ -1,4 +1,4 @@ -let s:lsp_clients = {} " { id, opts, req_seq, on_notifications: { request, on_notification }, stdout: { max_buffer_size, buffer, next_token, current_content_length, current_content_type } } +let s:lsp_clients = {} let s:lsp_token_type_contentlength = 'content-length' let s:lsp_token_type_contenttype = 'content-type' let s:lsp_token_type_message = 'message' @@ -14,11 +14,14 @@ function! s:_on_lsp_stdout(id, data, event) abort " \ 'id': l:lsp_client_id, " \ 'opts': a:opts, " \ 'req_seq': 0, - " \ 'on_notifications': {}, + " \ 'lock': langserver#lock#semaphore(), + " \ 'request_notifications': {}, " \ 'stdout': { " \ 'max_buffer_size': l:max_buffer_size, " \ 'buffer': '', " \ 'next_token': s:lsp_token_type_contentlength, + " \ 'current_content_length': , + " \ 'current_content_type': , " \ }, " \ } let l:client = s:lsp_clients[a:id] @@ -126,6 +129,7 @@ function! s:lsp_start(opts) abort \ 'id': l:lsp_client_id, \ 'opts': a:opts, \ 'req_seq': 0, + \ 'lock': langserver#lock#semaphore(), \ 'on_notifications': {}, \ 'stdout': { \ 'max_buffer_size': l:max_buffer_size, diff --git a/autoload/langserver/hover.vim b/autoload/langserver/hover.vim index 0d357bb..1d1f8d6 100644 --- a/autoload/langserver/hover.vim +++ b/autoload/langserver/hover.vim @@ -20,6 +20,9 @@ function! langserver#hover#request() abort return langserver#client#send(langserver#util#get_lsp_id(), { \ 'method': 'textDocument/hover', \ 'params': langserver#util#get_text_document_position_params(), + \ 'on_notification': { + \ 'callback': function('langserver#hover#callback'), + \ }, \ }) endfunction @@ -38,7 +41,7 @@ function! langserver#hover#display(range, data) abort echo l:hover_string - return timer_start(5000, function('s:delete_highlight')) + return timer_start(3000, function('s:delete_highlight')) endfunction function! s:delete_highlight() abort diff --git a/autoload/langserver/lock.vim b/autoload/langserver/lock.vim index 81a9826..3b99682 100644 --- a/autoload/langserver/lock.vim +++ b/autoload/langserver/lock.vim @@ -1,28 +1,46 @@ +let s:unlocked_id = -1 -function! s:dict_lock() dict - let self.locked = v:true +function! s:dict_lock(job_id) dict + if self.locked == v:false + let self.locked = v:true + let self.id = a:job_id + return v:true + else + return v:false + endif endfunction function! s:dict_unlock() dict + let self.id = s:unlocked_id let self.locked = v:false endfunction -function! s:set_id(job_id) dict - let self.id = a:job_id -endfunction - function! s:get_id() dict return self.id endfunction +function! s:take_lock(job_id) dict + " If we're locked, kill the other job + " And set ourselves to be the current job. + if self.locked + " TODO: Don't actually want to stop the whole server... + " I just want to stop the current request. Maybe a send cancel request + " would be good enough. We will see. + " call langserver#job#stop(self.id) + call self.unlock() + endif + + call self.lock(a:job_id) +endfunction + function! langserver#lock#semaphore() abort let l:ret = { - \ 'id': -1, + \ 'id': s:unlocked_id, \ 'locked': v:false, \ } let l:ret.lock = function('s:dict_lock') let l:ret.unlock = function('s:dict_unlock') - let l:ret.set_id = function('s:set_id') let l:ret.get_id = function('s:get_id') + let l:ret.take_lock = function('s:take_lock') return l:ret endfunction diff --git a/tests/test_lock.vader b/tests/test_lock.vader index f00580c..403c256 100644 --- a/tests/test_lock.vader +++ b/tests/test_lock.vader @@ -3,7 +3,7 @@ Execute (Test Lock): AssertEqual v:false, my_lock.locked - call my_lock.lock() + call my_lock.lock(1) AssertEqual v:true, my_lock.locked call my_lock.unlock() @@ -11,5 +11,12 @@ Execute (Test Lock): AssertEqual -1, my_lock.get_id() - call my_lock.set_id(1) + call my_lock.lock(1) AssertEqual 1, my_lock.get_id() + + let set_result = my_lock.lock(2) + AssertEqual v:false, set_result + AssertEqual 1, my_lock.get_id() + + call my_lock.take_lock(2) + AssertEqual 2, my_lock.get_id()