├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── autoload ├── langserver.vim └── langserver │ ├── api │ ├── textDocument.vim │ └── window.vim │ ├── callbacks.vim │ ├── capabilities.vim │ ├── client.vim │ ├── completion.vim │ ├── default.vim │ ├── documents.vim │ ├── extension │ ├── command.vim │ └── fs.vim │ ├── goto.vim │ ├── highlight.vim │ ├── hover.vim │ ├── initialize.vim │ ├── job.vim │ ├── lock.vim │ ├── log.vim │ ├── mappings.vim │ ├── message.vim │ ├── references.vim │ ├── request.vim │ ├── symbol │ ├── util.vim │ └── workspace.vim │ ├── util.vim │ ├── version.vim │ └── window.vim ├── doc └── langserver.txt ├── docs ├── basic_sequence.mscgen ├── basic_sequence.png ├── communication.mscgen ├── communication.png ├── idea.mscgen └── idea.png ├── plugin └── langserver.vim ├── preconfigured ├── freebroccolo │ └── ocaml-language-server │ │ ├── callbacks.vim │ │ └── settings.json └── test │ └── test │ └── callbacks.vim ├── rplugin └── python3 │ └── deoplete │ └── langserver.py ├── testing └── vim_connection.vim └── tests ├── run.sh ├── setup.vim ├── test.go ├── test.py ├── test_defaults.vader ├── test_goto.vader ├── test_lock.vader ├── test_message.vader ├── test_message_parse.vader └── test_startup.vader /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | *tags* 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 TJ DeVries 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | all: 4 | mscgen -T png docs/basic_sequence.mscgen 5 | mscgen -T png docs/communication.mscgen 6 | mscgen -T png docs/idea.mscgen 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nvim-langserver-shim 2 | 3 | # NOTICE 4 | 5 | This repo is now deprecated. Everything you could have wanted to do here, you can now do in neovim natively (if you download nightly at the time of writing). 6 | 7 | Any neovim of 0.5 or greater, just check out `:help lsp` 8 | 9 | :grin: 10 | -------------------------------------------------------------------------------- /autoload/langserver.vim: -------------------------------------------------------------------------------- 1 | let g:lsp_id_map = get(g:, 'lsp_id_map', {}) 2 | 3 | "" 4 | " Start the langauage server and return the ID 5 | " 6 | " @param options (dict): {cmd, on_stderr?, on_exit?, on_notification?} 7 | function! langserver#start(options) abort 8 | if has_key(a:options, 'cmd') 9 | let l:cmd = a:options['cmd'] 10 | else 11 | let l:cmd = langserver#default#cmd() 12 | if l:cmd == [-1] 13 | call langserver#log#log('error', 'No valid langserver for ' . &filetype . '. Please modify `g:langserver_executables`', v:true) 14 | return 15 | endif 16 | endif 17 | 18 | let l:lsp_id = langserver#client#start({ 19 | \ 'cmd': l:cmd, 20 | \ 'on_stdout': function('langserver#callbacks#on_stdout'), 21 | \ 'on_stderr': function('langserver#callbacks#on_stderr'), 22 | \ 'on_exit': function('langserver#callbacks#on_exit'), 23 | \ 'on_notification': function('langserver#callbacks#on_notification') 24 | \ }) 25 | 26 | if has_key(a:options, 'root_path') 27 | let l:root_path = a:options['root_path'] 28 | else 29 | let l:root_path = langserver#util#get_root_path(l:lsp_id) 30 | endif 31 | 32 | if l:lsp_id > 0 33 | call langserver#log#log('info', 'lsp server running') 34 | call langserver#client#send(l:lsp_id, { 35 | \ 'method': 'initialize', 36 | \ 'params': { 37 | \ 'capabilities': {}, 38 | \ 'rootPath': l:root_path, 39 | \ }, 40 | \ }) 41 | else 42 | call langserver#log#log('error', 'failed to start lsp server', v:true) 43 | endif 44 | 45 | let g:lsp_id_map[&filetype] = l:lsp_id 46 | 47 | return l:lsp_id 48 | endfunction 49 | -------------------------------------------------------------------------------- /autoload/langserver/api/textDocument.vim: -------------------------------------------------------------------------------- 1 | "" 2 | function! langserver#api#textDocument#definition(request) abort 3 | return langserver#goto#goto_defintion( 4 | \ langserver#util#get_lsp_id(), 5 | \ a:request.result.uri, 6 | \ a:request.result.range, 7 | \ {}, 8 | \ ) 9 | endfunction 10 | 11 | function! langserver#api#textDocument#completion(request) abort 12 | 13 | endfunction 14 | -------------------------------------------------------------------------------- /autoload/langserver/api/window.vim: -------------------------------------------------------------------------------- 1 | function! langserver#api#window#showMessage() 2 | endfunction 3 | -------------------------------------------------------------------------------- /autoload/langserver/callbacks.vim: -------------------------------------------------------------------------------- 1 | function! s:check_extra_callbacks(last_topic) abort 2 | echom 'Checking custom callbacks' 3 | let l:custom_callbacks = langserver#default#extension_callbacks() 4 | echom 'Custom callbacks are: ' . string(l:custom_callbacks) 5 | if has_key(l:custom_callbacks, a:last_topic) 6 | call langserver#log#log('info', 'Calling custom callback for: ' . a:last_topic, v:true) 7 | return l:custom_callbacks[a:last_topic] 8 | else 9 | call langserver#log#log('warning', 'No callback registered for: ' . a:last_topic, v:true) 10 | return v:false 11 | endif 12 | endfunction 13 | 14 | function! langserver#callbacks#on_stdout(id, data, event) abort 15 | echom 'LSP STDOUT(' . a:id . '): ' . string(a:data) 16 | endfunction 17 | 18 | function! langserver#callbacks#on_stderr(id, data, event) abort 19 | call langserver#log#response(a:id, a:data, a:event) 20 | endfunction 21 | 22 | function! langserver#callbacks#on_exit(id, status, event) abort 23 | echom 'lsp('.a:id.'):exit:'.a:status 24 | endfunction 25 | 26 | function! langserver#callbacks#on_notification(id, data, event) abort 27 | if a:event ==? 'on_request' 28 | let l:last_topic = a:data['request']['method'] 29 | 30 | let l:ExtraCallbacks = s:check_extra_callbacks(l:last_topic) 31 | 32 | if type(l:ExtraCallbacks) == type(function('tr')) 33 | let l:result = call(l:ExtraCallbacks, [a:id, a:data, a:event]) 34 | else 35 | call langserver#extension#command#callback(a:id, a:data, a:event) 36 | endif 37 | elseif a:event ==? 'on_notification' 38 | if has_key(a:data, 'response') 39 | call langserver#log#response(a:id, a:data, a:event) 40 | 41 | let l:last_topic = a:data['request']['method'] 42 | 43 | if l:last_topic ==? 'textDocument/references' 44 | call langserver#references#callback(a:id, a:data, a:event) 45 | elseif l:last_topic ==? 'textDocument/definition' 46 | call langserver#goto#callback(a:id, a:data, a:event) 47 | elseif l:last_topic ==? 'textDocument/hover' 48 | call langserver#hover#callback(a:id, a:data, a:event) 49 | elseif l:last_topic ==? 'textDocument/didOpen' 50 | call langserver#documents#callback_did_open(a:id, a:data, a:event) 51 | elseif l:last_topic ==? 'initialize' 52 | call langserver#initialize#callback(a:id, a:data, a:event) 53 | elseif l:last_topic ==? 'workspace/symbol' 54 | call langserver#symbol#workspace#callback(a:id, a:data, a:event) 55 | else 56 | " Check if any extra callbacks exist. 57 | let l:ExtraCallbacks = s:check_extra_callbacks(l:last_topic) 58 | 59 | if type(l:ExtraCallbacks) == type(function('tr')) 60 | let l:result = call(l:ExtraCallbacks, [a:id, a:data, a:event]) 61 | endif 62 | endif 63 | elseif has_key(a:data, 'request') 64 | echom 'notification...' 65 | echom string(a:data) 66 | echom '...notification' 67 | endif 68 | 69 | if langserver#client#is_error(a:data.response) 70 | call langserver#log#log('debug', 71 | \ 'lsp('.a:id.'):notification:notification error receieved for '.a:data.request.method . 72 | \ ': ' . string(a:data), 73 | \ v:true, 74 | \ ) 75 | else 76 | call langserver#log#log('debug', 77 | \ 'lsp('.a:id.'):notification:notification success receieved for '.a:data.request.method, 78 | \ langserver#util#debug(), 79 | \ ) 80 | endif 81 | endif 82 | endfunction 83 | 84 | function! langserver#callbacks#data(id, data, event) abort 85 | call langserver#log#callback(a:id, a:data, a:event) 86 | let g:last_response = a:data 87 | 88 | if type(a:data) != type({}) 89 | return {} 90 | endif 91 | 92 | if has_key(a:data, 'response') 93 | let l:parsed_data = a:data['response']['result'] 94 | else 95 | return {} 96 | endif 97 | 98 | return l:parsed_data 99 | endfunction 100 | -------------------------------------------------------------------------------- /autoload/langserver/capabilities.vim: -------------------------------------------------------------------------------- 1 | let s:TextDocumentSyncKind = { 2 | \ 'name': { 3 | \ 'None': 0, 4 | \ 'Full': 1, 5 | \ 'Incremental': 2 6 | \ }, 7 | \ 'number': { 8 | \ 0: 'None', 9 | \ 1: 'Full', 10 | \ 2: 'Incremental' 11 | \ }, 12 | \ } 13 | 14 | " In case this has been set somehow. Not sure if I want to do it like this, 15 | " but it should guard against anything overwriting this 16 | let g:langserver_capabilities = get(g:, 'langserver_capabilities', {}) 17 | 18 | function! langserver#capabilities#check(name, key_1, ...) 19 | if !has_key(g:langserver_capabilities, a:name) 20 | echom 'Language server {' . a:name . '} has not been initialized' 21 | return v:false 22 | endif 23 | 24 | if !has_key(g:langserver_capabilities[a:name], a:key_1) 25 | echom 'Language server {' a:name . '} does not have key {' . a:key_1 . '}' 26 | return v:false 27 | endif 28 | 29 | if a:0 > 0 30 | " TODO: 31 | else 32 | return g:langserver_capabilities[a:name][a:key_1] 33 | endif 34 | endfunction 35 | 36 | "" 37 | " Makes sure that the dictionary is prepared to record the capabilities of 38 | " server {name} 39 | " 40 | " @param name (str): The name of the server 41 | " @returns dict: The dictionary that is in the configuration dictionary 42 | function! s:prepare_capabiilties(name, ...) abort 43 | let l:optional_key = v:false 44 | if a:0 > 0 45 | let l:optional_key = v:true 46 | let l:dict_key = a:1 47 | endif 48 | 49 | if !has_key(g:langserver_capabilities, a:name) 50 | let g:langserver_capabilities[a:name] = {} 51 | endif 52 | 53 | 54 | if l:optional_key 55 | " Get an exisiting language server, if necessary. 56 | let g:langserver_capabilities[a:name][l:dict_key] = 57 | \ get(g:langserver_capabilities[a:name], l:dict_key, {}) 58 | 59 | return g:langserver_capabilities[a:name][l:dict_key] 60 | else 61 | return g:langserver_capabilities[a:name] 62 | endif 63 | 64 | endfunction 65 | 66 | "------------------- Various setters ------------------------------------------ 67 | 68 | "" 69 | " @param name (str): The name of the server 70 | " @param kind (int): The corresponding document sync kind 71 | function! langserver#capabilities#set_test_document_sync(name, kind) abort 72 | let l:config_dict = s:prepare_capabiilties(a:name) 73 | 74 | " Set the value to a human readable value 75 | let l:config_dict['textDocumentSync'] = s:TextDocumentSyncKind['number'][a:kind] 76 | endfunction 77 | 78 | "" 79 | function! langserver#capabilities#set_hover_provider(name, hover_provider) abort 80 | let l:config_dict = s:prepare_capabiilties(a:name) 81 | 82 | let l:config_dict['hoverProvider'] = a:hover_provider 83 | endfunction 84 | 85 | "" 86 | " @param resolve_provider (bool): Provide support for resolves 87 | " @param trigger_characters (list[str]): Characters that trigger completion automatically 88 | " TODO: Set these to autocommands? maps? how to set it exactly. 89 | function! langserver#capabilities#set_completion_provider(name, resolve_provider, trigger_characters) abort 90 | let l:config_dict = s:prepare_capabiilties(a:name, 'completionProvider') 91 | 92 | 93 | let l:config_dict['resolve_provider'] = a:resolve_provider 94 | 95 | " TODO: Might do the mappings here? 96 | let l:config_dict['trigger_characters'] = a:trigger_characters 97 | endfunction 98 | 99 | "" 100 | function! langserver#capabilities#set_signature_help_provider(name, resolve_provider) abort 101 | let l:config_dict = s:prepare_capabiilties(a:name) 102 | 103 | let l:config_dict['resolve_provider'] = a:resolve_provider 104 | endfunction 105 | 106 | "" 107 | " 108 | function! langserver#capabilities#set_definition_provider(name, definition_provider) abort 109 | let l:config_dict = s:prepare_capabiilties(a:name) 110 | 111 | let l:config_dict['definition_provider'] = a:definition_provider 112 | endfunction 113 | 114 | "" 115 | " 116 | function! langserver#capabilities#set_references_provider(name, references_provider) abort 117 | let l:config_dict = s:prepare_capabiilties(a:name) 118 | 119 | let l:config_dict['references_provider'] = a:references_provider 120 | endfunction 121 | 122 | "" 123 | " 124 | function! langserver#capabilities#set_document_highlight_provider(name, document_highlight_provider) abort 125 | let l:config_dict = s:prepare_capabiilties(a:name) 126 | 127 | let l:config_dict['document_highlight_provider'] = a:document_highlight_provider 128 | endfunction 129 | 130 | "" 131 | " 132 | function! langserver#capabilities#set_document_symbol_provider(name, document_symbol_provider) abort 133 | let l:config_dict = s:prepare_capabiilties(a:name) 134 | 135 | let l:config_dict['document_symbol_provider'] = a:document_symbol_provider 136 | endfunction 137 | 138 | "" 139 | " 140 | function! langserver#capabilities#set_workspace_symbol_provider(name, workspace_symbol_provider) abort 141 | let l:config_dict = s:prepare_capabiilties(a:name) 142 | 143 | let l:config_dict['workspace_symbol_provider'] = a:workspace_symbol_provider 144 | endfunction 145 | 146 | "" 147 | " 148 | function! langserver#capabilities#set_code_action_provider(name, code_action_provider) abort 149 | let l:config_dict = s:prepare_capabiilties(a:name) 150 | 151 | let l:config_dict['code_action_provider'] = a:code_action_provider 152 | endfunction 153 | 154 | "" 155 | " 156 | function! langserver#capabilities#set_code_lens_provider(name, resolve_provider) abort 157 | let l:config_dict = s:prepare_capabiilties(a:name, 'codeLensProvider') 158 | 159 | let l:config_dict['resolve_provider'] = a:resolve_provider 160 | endfunction 161 | 162 | "" 163 | " 164 | function! langserver#capabilities#set_document_formatting_provider(name, document_formatting_provider) abort 165 | let l:config_dict = s:prepare_capabiilties(a:name) 166 | 167 | let l:config_dict['document_formatting_provider'] = a:document_formatting_provider 168 | endfunction 169 | 170 | "" 171 | " 172 | function! langserver#capabilities#set_document_range_formatting_provider(name, document_range_formatting_provider) abort 173 | let l:config_dict = s:prepare_capabiilties(a:name) 174 | 175 | let l:config_dict['document_range_formatting_provider'] = a:document_range_formatting_provider 176 | endfunction 177 | 178 | "" 179 | " 180 | function! langserver#capabilities#set_document_on_type_formatting_provider(name, 181 | \ first_trigger_character, 182 | \ more_trigger_character) abort 183 | let l:config_dict = s:prepare_capabiilties(a:name, 'documentOnTypeFormatting') 184 | 185 | let l:config_dict['first_trigger_character'] = a:trigger_characters 186 | let l:config_dict['more_trigger_character'] = a:more_trigger_character 187 | endfunction 188 | 189 | "" 190 | " 191 | function! langserver#capabilities#set_rename_provider(name, rename_provider) abort 192 | let l:config_dict = s:prepare_capabiilties(a:name) 193 | 194 | let l:config_dict['rename_provider'] = a:rename_provider 195 | endfunction 196 | -------------------------------------------------------------------------------- /autoload/langserver/client.vim: -------------------------------------------------------------------------------- 1 | let s:lsp_clients = {} 2 | let s:lsp_token_type_contentlength = 'content-length' 3 | let s:lsp_token_type_contenttype = 'content-type' 4 | let s:lsp_token_type_message = 'message' 5 | let s:lsp_default_max_buffer = -1 6 | 7 | let s:lsp_text_document_sync_kind_none = 0 8 | let s:lsp_text_document_sync_kind_full = 1 9 | let s:lsp_text_document_sync_kind_incremental = 2 10 | 11 | function! s:_on_lsp_stdout(id, data, event) abort 12 | if has_key(s:lsp_clients, a:id) 13 | " let s:lsp_clients[l:lsp_client_id] = { 14 | " \ 'id': l:lsp_client_id, 15 | " \ 'opts': a:opts, 16 | " \ 'req_seq': 0, 17 | " \ 'lock': langserver#lock#semaphore(), 18 | " \ 'request_notifications': {}, 19 | " \ 'stdout': { 20 | " \ 'max_buffer_size': l:max_buffer_size, 21 | " \ 'buffer': '', 22 | " \ 'next_token': s:lsp_token_type_contentlength, 23 | " \ 'current_content_length': , 24 | " \ 'current_content_type': , 25 | " \ }, 26 | " \ } 27 | let l:client = s:lsp_clients[a:id] 28 | 29 | let l:client.stdout.buffer .= join(a:data, "\n") 30 | 31 | if l:client.stdout.max_buffer_size != -1 && len(l:client.stdout.buffer) > l:client.stdout.max_buffer_size 32 | echom 'lsp: reached max buffer size' 33 | call langserver#job#stop(a:id) 34 | endif 35 | 36 | while 1 37 | if l:client.stdout.next_token == s:lsp_token_type_contentlength 38 | let l:new_line_index = stridx(l:client.stdout.buffer, "\r\n") 39 | if l:new_line_index >= 0 40 | let l:content_length_str = l:client.stdout.buffer[:l:new_line_index - 1] 41 | let l:client.stdout.buffer = l:client.stdout.buffer[l:new_line_index + 2:] 42 | let l:client.stdout.current_content_length = str2nr(split(l:content_length_str, ':')[1], 10) 43 | let l:client.stdout.next_token = s:lsp_token_type_contenttype 44 | continue 45 | else 46 | " wait for next buffer to arrive 47 | break 48 | endif 49 | elseif l:client.stdout.next_token == s:lsp_token_type_contenttype 50 | let l:new_line_index = stridx(l:client.stdout.buffer, "\r\n") 51 | if l:new_line_index >= 0 52 | let l:content_type_str = l:client.stdout.buffer[:l:new_line_index - 1] 53 | let l:client.stdout.buffer = l:client.stdout.buffer[l:new_line_index + 4:] 54 | let l:client.stdout.current_content_type = l:content_type_str 55 | let l:client.stdout.next_token = s:lsp_token_type_message 56 | continue 57 | else 58 | " wait for next buffer to arrive 59 | break 60 | endif 61 | else " we are reading a message 62 | if len(l:client.stdout.buffer) >= l:client.stdout.current_content_length 63 | " we have complete message 64 | let l:response_str = l:client.stdout.buffer[:l:client.stdout.current_content_length - 1] 65 | let l:client.stdout.buffer = l:client.stdout.buffer[l:client.stdout.current_content_length :] 66 | let l:client.stdout.next_token = s:lsp_token_type_contentlength 67 | let l:response_msg = json_decode(l:response_str) 68 | if has_key(l:response_msg, 'id') && has_key(l:client.on_notifications, l:response_msg.id) 69 | let l:on_notification_data = { 'request': l:client.on_notifications[l:response_msg.id].request, 'response': l:response_msg } 70 | if has_key(l:client.opts, 'on_notification') 71 | call l:client.opts.on_notification(a:id, l:on_notification_data, 'on_notification') 72 | endif 73 | if has_key(l:client.on_notifications, 'on_notification') 74 | call l:client.on_notifications[l:response_msg.id](a:id, l:on_notification_data, 'on_notification') 75 | endif 76 | call remove(l:client.on_notifications, l:response_msg.id) 77 | else 78 | echom string(l:client) 79 | let l:on_notification_data = { 80 | \ 'request': l:response_msg, 81 | \ } 82 | call l:client.opts.on_notification(a:id, l:on_notification_data, 'on_request') 83 | endif 84 | continue 85 | else 86 | " wait for next buffer to arrive since we have incomplete message 87 | break 88 | endif 89 | endif 90 | endwhile 91 | endif 92 | endfunction 93 | 94 | function! s:_on_lsp_stderr(id, data, event) abort 95 | if has_key(s:lsp_clients, a:id) 96 | let l:client = s:lsp_clients[a:id] 97 | if has_key(l:client.opts, 'on_stderr') 98 | call l:client.opts.on_stderr(a:id, a:data, a:event) 99 | endif 100 | endif 101 | endfunction 102 | 103 | function! s:_on_lsp_exit(id, status, event) abort 104 | if has_key(s:lsp_clients, a:id) 105 | let l:client = s:lsp_clients[a:id] 106 | if has_key(l:client.opts, 'on_exit') 107 | call l:client.opts.on_exit(a:id, a:status, a:event) 108 | endif 109 | endif 110 | endfunction 111 | 112 | function! s:lsp_start(opts) abort 113 | if !has_key(a:opts, 'cmd') 114 | return -1 115 | endif 116 | 117 | let l:lsp_client_id = langserver#job#start(a:opts.cmd, { 118 | \ 'on_stdout': function('s:_on_lsp_stdout'), 119 | \ 'on_stderr': function('s:_on_lsp_stderr'), 120 | \ 'on_exit': function('s:_on_lsp_exit'), 121 | \ }) 122 | 123 | let l:max_buffer_size = s:lsp_default_max_buffer 124 | if has_key(a:opts, 'max_buffer_size') 125 | let l:max_buffer_size = a:opts.max_buffer_size 126 | endif 127 | 128 | let s:lsp_clients[l:lsp_client_id] = { 129 | \ 'id': l:lsp_client_id, 130 | \ 'opts': a:opts, 131 | \ 'req_seq': 0, 132 | \ 'lock': langserver#lock#semaphore(), 133 | \ 'on_notifications': {}, 134 | \ 'stdout': { 135 | \ 'max_buffer_size': l:max_buffer_size, 136 | \ 'buffer': '', 137 | \ 'next_token': s:lsp_token_type_contentlength, 138 | \ }, 139 | \ } 140 | 141 | return l:lsp_client_id 142 | endfunction 143 | 144 | function! s:lsp_stop(id) abort 145 | call langserver#job#stop(a:id) 146 | endfunction 147 | 148 | function! s:lsp_send_request(id, opts) abort " opts = { method, params?, on_notification } 149 | if has_key(s:lsp_clients, a:id) 150 | let l:client = s:lsp_clients[a:id] 151 | let l:type_of_msg = 'Request' 152 | 153 | if has_key(a:opts, 'req_id') 154 | let l:req_seq = a:opts.req_id 155 | else 156 | let l:client.req_seq = l:client.req_seq + 1 157 | let l:req_seq = l:client.req_seq 158 | endif 159 | 160 | let l:msg = { 'jsonrpc': '2.0', 'id': l:req_seq, 'method': a:opts.method } 161 | if has_key(a:opts, 'params') 162 | let l:msg.params = a:opts.params 163 | endif 164 | 165 | " I don't think you should be able to do these at the same time... 166 | " Not going to do anything about that yet though 167 | if has_key(a:opts, 'result') 168 | let l:type_of_msg = 'Response (result)' 169 | let l:msg.result = a:opts.result 170 | endif 171 | 172 | if has_key (a:opts, 'response') 173 | let l:type_of_msg = 'Reponse (response)' 174 | let l:msg.response = a:opts.response 175 | endif 176 | 177 | let l:json = json_encode(l:msg) 178 | let l:req_data = 'Content-Length: ' . len(l:json) . "\r\n\r\n" . l:json 179 | 180 | let l:client.on_notifications[l:req_seq] = { 'request': l:msg } 181 | if has_key(a:opts, 'on_notification') 182 | let l:client.on_notifications[l:req_seq].on_notification = a:opts.on_notification 183 | endif 184 | 185 | if has_key(a:opts, 'on_stderr') 186 | let l:client.opts.on_stderr = a:opts.on_stderr 187 | endif 188 | 189 | call langserver#log#log('debug', printf('Sending %s: %s, %s', 190 | \ l:type_of_msg, 191 | \ a:id, 192 | \ string(l:msg) 193 | \ )) 194 | call langserver#job#send(l:client.id, l:req_data) 195 | 196 | return l:req_seq 197 | else 198 | return -1 199 | endif 200 | endfunction 201 | 202 | function! s:lsp_get_last_request_id(id) abort 203 | return s:lsp_clients[a:id].req_seq 204 | endfunction 205 | 206 | function! s:lsp_is_error(notification) abort 207 | " if type(a:notification) != type({}) 208 | " return v:true 209 | " endif 210 | 211 | " if !has_key(a:notification, 'response') || !has_key(a:notification.response, 'result') 212 | " return v:true 213 | " endif 214 | 215 | return has_key(a:notification, 'error') 216 | endfunction 217 | 218 | " public apis {{{ 219 | 220 | let g:langserver#client#text_document_sync_kind_none = s:lsp_text_document_sync_kind_none 221 | let g:langserver#client#text_document_sync_kind_full = s:lsp_text_document_sync_kind_full 222 | let g:langserver#client#text_document_sync_kind_incremental = s:lsp_text_document_sync_kind_incremental 223 | 224 | function! langserver#client#start(opts) abort 225 | return s:lsp_start(a:opts) 226 | endfunction 227 | 228 | function! langserver#client#stop(client_id) abort 229 | return s:lsp_stop(a:client_id) 230 | endfunction 231 | 232 | function! langserver#client#send(client_id, opts) abort 233 | return s:lsp_send_request(a:client_id, a:opts) 234 | endfunction 235 | 236 | function! langserver#client#get_last_request_id(client_id) abort 237 | return s:lsp_get_last_request_id(a:client_id) 238 | endfunction 239 | 240 | function! langserver#client#is_error(notification) abort 241 | return s:lsp_is_error(a:notification) 242 | endfunction 243 | 244 | " }}} 245 | " vim sw=4 ts=4 et 246 | -------------------------------------------------------------------------------- /autoload/langserver/completion.vim: -------------------------------------------------------------------------------- 1 | function! langserver#completion#callback(id, data, event) abort 2 | let l:parsed_data = langserver#callbacks#data(a:id, a:data, a:event) 3 | if l:parsed_data == {} 4 | return 5 | endif 6 | 7 | " {'isIncomplete': bool, 'items': [CompletionItems]} 8 | let l:completion_items = l:parsed_data['items'] 9 | 10 | endfunction 11 | 12 | function! langserver#completion#request(...) abort 13 | return langserver#client#send(langserver#util#get_lsp_id(), { 14 | \ 'method': 'textDocument/completion', 15 | \ 'params': langserver#util#get_text_document_position_params(), 16 | \ }) 17 | endfunction 18 | 19 | let s:CompletionItemKind = { 20 | \ 'Text': 1, 21 | \ 'Method': 2, 22 | \ 'Function': 3, 23 | \ 'Constructor': 4, 24 | \ 'Field': 5, 25 | \ 'Variable': 6, 26 | \ 'Class': 7, 27 | \ 'Interface': 8, 28 | \ 'Module': 9, 29 | \ 'Property': 10, 30 | \ 'Unit': 11, 31 | \ 'Value': 12, 32 | \ 'Enum': 13, 33 | \ 'Keyword': 14, 34 | \ 'Snippet': 15, 35 | \ 'Color': 16, 36 | \ 'File': 17, 37 | \ 'Reference': 18 38 | \ } 39 | -------------------------------------------------------------------------------- /autoload/langserver/default.vim: -------------------------------------------------------------------------------- 1 | let s:preconfigured_location = expand(':h') . '/../../preconfigured/' 2 | let s:callbacks_name = '/callbacks.vim' 3 | 4 | 5 | "" 6 | " Get the default command for starting the server 7 | function! langserver#default#cmd(...) abort 8 | if a:0 > 0 9 | let l:filetype_key = langserver#util#get_executable_key(a:1) 10 | else 11 | let l:filetype_key = langserver#util#get_executable_key(&filetype) 12 | endif 13 | 14 | let l:bad_cmd = [-1] 15 | 16 | if has_key(g:langserver_executables, l:filetype_key) 17 | " Has to be uppercase because of function naming 18 | " Sorry for mixed case :/ 19 | let l:TmpCmd = g:langserver_executables[l:filetype_key]['cmd'] 20 | 21 | if type(l:TmpCmd) == type([]) 22 | return l:TmpCmd 23 | elseif type(l:TmpCmd) == type(function('tr')) 24 | let l:result = l:TmpCmd() 25 | if type(l:result) == type([]) 26 | return l:result 27 | endif 28 | endif 29 | endif 30 | 31 | " If we didn't return anything, there was an error. 32 | echoerr 'Please consult the documentation for how to configure the langserver' 33 | return l:bad_cmd 34 | endfunction 35 | 36 | function! langserver#default#extension_callbacks(...) abort 37 | try 38 | if a:0 > 0 39 | let l:filetype_key = langserver#util#get_executable_key(a:1) 40 | else 41 | let l:filetype_key = langserver#util#get_executable_key(&filetype) 42 | endif 43 | catch /.*Unsupported filetype.*/ 44 | return {} 45 | endtry 46 | 47 | if has_key(g:langserver_executables, l:filetype_key) 48 | let l:location = s:preconfigured_location . g:langserver_executables[l:filetype_key]['name'] 49 | let l:file_to_source = l:location . s:callbacks_name 50 | if isdirectory(l:location) && filereadable(l:file_to_source) 51 | execute('source ' . l:file_to_source) 52 | let l:subbed = substitute(g:langserver_executables[l:filetype_key]['name'], '/', '_', 'g') 53 | let l:subbed = substitute(l:subbed, '-', '_', 'g') 54 | return Preconfigured_{l:subbed}() 55 | else 56 | return {} 57 | endif 58 | endif 59 | endfunction 60 | -------------------------------------------------------------------------------- /autoload/langserver/documents.vim: -------------------------------------------------------------------------------- 1 | function! langserver#documents#callback_did_open(id, data, event) abort 2 | call langserver#log#callback(a:id, a:data, a:event) 3 | 4 | if type(a:data) != type({}) 5 | return 6 | endif 7 | 8 | let g:last_response = a:data 9 | call langserver#log#log('info', string(a:data)) 10 | endfunction 11 | 12 | "" 13 | " Corresponds to: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#didopentextdocument-notification 14 | function! langserver#documents#did_open() abort 15 | " Open the new file 16 | " execute(':edit ' . a:filename) 17 | 18 | " TODO: Just assume that the server is the filetype for now. 19 | let l:server_name = langserver#util#get_lsp_id() 20 | let l:filename_uri = langserver#util#get_uri(l:server_name, expand('%')) 21 | 22 | return langserver#client#send(langserver#util#get_lsp_id(), { 23 | \ 'method': 'textDocument/didOpen', 24 | \ 'params': langserver#util#get_text_document_identifier(l:server_name), 25 | \ }) 26 | endfunction 27 | 28 | 29 | "" 30 | " Corresponds to: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#didchangetextdocument-notification 31 | function! langserver#documents#did_change() abort 32 | 33 | endfunction 34 | 35 | "" 36 | " Corresponds to: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#didclosetextdocument-notification 37 | function! langserver#documents#did_close() abort 38 | 39 | endfunction 40 | 41 | "" 42 | " Corresponds to: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#didsavetextdocument-notification 43 | function! langserver#documents#did_save() abort 44 | 45 | endfunction 46 | 47 | "" 48 | " Corresponds to: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#didchangewatchedfiles-notification 49 | function! langserver#documents#did_change_watched_files() abort 50 | 51 | endfunction 52 | -------------------------------------------------------------------------------- /autoload/langserver/extension/command.vim: -------------------------------------------------------------------------------- 1 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 2 | " Command handler 3 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 4 | 5 | function! langserver#extension#command#callback(id, data, event) abort 6 | call langserver#log#server_request(a:id, a:data, a:event) 7 | 8 | if type(a:data) != type({}) 9 | return 10 | endif 11 | 12 | let l:method = a:data.request.method 13 | 14 | if l:method ==? 'fs/readFile' 15 | let l:response = langserver#extension#fs#readFille(a:data.request.params) 16 | else 17 | call langserver#log#log('warning', 'No matching callback found for: ' . l:method) 18 | return v:false 19 | endif 20 | 21 | call langserver#client#send(a:id, { 22 | \ 'req_id': a:data.request.id, 23 | \ 'method': l:method, 24 | \ 'params': a:data.request.params, 25 | \ 'result': l:response, 26 | \ }) 27 | return v:true 28 | endfunction 29 | -------------------------------------------------------------------------------- /autoload/langserver/extension/fs.vim: -------------------------------------------------------------------------------- 1 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 2 | " Extension to handle "fl/#" commands 3 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 4 | 5 | "" 6 | " Respond with the full file reading 7 | function! langserver#extension#fs#readFille(filename) abort 8 | echom a:filename 9 | 10 | " let l:text = join(readfile(a:filename), "\n") 11 | " echo system(['base64', join(readfile('/home/tj/test/lsp.py'), "\n")]) 12 | if filereadable(a:filename) 13 | return system(['base64', a:filename]) 14 | else 15 | call langserver#log#log('error', 'Unable to read file: ' . a:filename) 16 | return '' 17 | endif 18 | endfunction 19 | -------------------------------------------------------------------------------- /autoload/langserver/goto.vim: -------------------------------------------------------------------------------- 1 | function! langserver#goto#callback(id, data, event) abort 2 | call langserver#log#callback(a:id, a:data, a:event) 3 | 4 | if type(a:data) != type({}) 5 | return 6 | endif 7 | 8 | if has_key(a:data, 'response') 9 | if empty(a:data['response']['result']) 10 | call langserver#log#log('warning', 'No definition found for: ' . string(a:data['request']), v:true) 11 | return 12 | endif 13 | 14 | if type(a:data['response']['result']) == type([]) 15 | let l:parsed_data = a:data['response']['result'][0] 16 | elseif type(a:data['response']['result']) == type({}) 17 | let l:parsed_data = a:data['response']['result'] 18 | endif 19 | else 20 | return 21 | endif 22 | 23 | " {'data': 24 | " {'textDocument/definition': 25 | " [ 26 | " {'uri': 'file:///home/tj/go/src/github.com/sourcegraph/go-langserver/langserver/util.go', 27 | " 'range': { 28 | " 'end': {'character': 29, 'line': 15}, 29 | " 'start': {'character': 23, 'line': 15}} 30 | " } 31 | " ] 32 | " }, 33 | " 'type': 'result'} 34 | call langserver#goto#goto_defintion(a:id, l:parsed_data['uri'], l:parsed_data['range'], {}) 35 | endfunction 36 | 37 | function! langserver#goto#request(...) abort 38 | if a:0 > 0 39 | let l:sever_name = a:1 40 | else 41 | let l:server_name = langserver#util#get_lsp_id() 42 | endif 43 | 44 | call langserver#client#send(l:server_name, { 45 | \ 'method': 'textDocument/definition', 46 | \ 'params': { 47 | \ 'textDocument': langserver#util#get_text_document_identifier(l:server_name), 48 | \ 'position': langserver#util#get_position(), 49 | \ }, 50 | \ }) 51 | endfunction 52 | 53 | function! langserver#goto#goto_defintion(name, uri, range_dict, options) abort 54 | " TODO: Case sensitivity? 55 | if a:uri !=? langserver#util#get_uri(a:name, expand('%')) 56 | let l:file_name = langserver#util#get_filename(a:name, a:uri) 57 | let l:file_bufnr = bufnr(l:file_name) 58 | 59 | if l:file_bufnr > 0 60 | execute(':silent buffer ' . bufnr(l:file_name)) 61 | else 62 | execute('silent edit ' . l:file_name) 63 | endif 64 | 65 | " Print an informative message 66 | file! 67 | endif 68 | 69 | 70 | " Functions to Handle moving around: 71 | " Saving: 72 | " winsaveview / winrestview 73 | " Moving: 74 | " cursor 75 | " call cursor( 76 | " \ range['start']['line'], 77 | " \ range['start']['character'], 78 | " \ ) 79 | " normal G| 80 | " This keeps the jumplist intact. 81 | " echom printf('norm! %sG%s|', 82 | " \ a:range_dict['start']['line'] + 1, 83 | " \ a:range_dict['start']['character'] + 1, 84 | " \ ) 85 | let l:action = printf('norm! %sG%s|', 86 | \ a:range_dict['start']['line'] + 1, 87 | \ a:range_dict['start']['character'] + 1, 88 | \ ) 89 | execute(l:action) 90 | 91 | return l:action 92 | endfunction 93 | -------------------------------------------------------------------------------- /autoload/langserver/highlight.vim: -------------------------------------------------------------------------------- 1 | 2 | function! s:translate_range(range) abort 3 | return [ 4 | \ a:range['start']['line'] + 1, 5 | \ a:range['start']['character'] + 1, 6 | \ a:range['end']['character'] - a:range['start']['character'] 7 | \ ] 8 | endfunction 9 | 10 | "" 11 | " Convert a range object into a matchaddposd compatible list 12 | function! langserver#highlight#matchaddpos_range(range) abort 13 | let ret_list = [] 14 | if type(a:range) == type([]) 15 | for r in a:range 16 | call add(ret_list, s:translate_range(r)) 17 | endfor 18 | elseif type(a:range) == type({}) 19 | call add(ret_list, s:translate_range(a:range)) 20 | endif 21 | 22 | return ret_list 23 | endfunction 24 | -------------------------------------------------------------------------------- /autoload/langserver/hover.vim: -------------------------------------------------------------------------------- 1 | function! langserver#hover#callback(id, data, event) abort 2 | let l:parsed_data = langserver#callbacks#data(a:id, a:data, a:event) 3 | 4 | if langserver#util#null_check(l:parsed_data) 5 | call langserver#hover#display([], [ 6 | \ { 7 | \ 'language': 'LSP', 8 | \ 'value': 'Unable to retrieve hover information' 9 | \ }, 10 | \ ], 11 | \ ) 12 | return 13 | endif 14 | 15 | if l:parsed_data == {} 16 | return 17 | endif 18 | 19 | " {'data': {'textDocument/hover': {'range': {'end': {'character': 20, 'line': 44}, 'start': {'character': 9, 'line': 44}}, 'contents': [{'language': 'go', 'value': 'type LangHandler struct'}, {'language': 'markdown', 'value': 'LangHandler is a Go language server LSP/JSON-RPC handler.'}]}}, 'type': 'result'} 20 | if has_key(l:parsed_data, 'range') 21 | let l:range = l:parsed_data['range'] 22 | else 23 | let l:range = {} 24 | endif 25 | 26 | let l:data = l:parsed_data['contents'] 27 | 28 | call langserver#hover#display(l:range, l:data) 29 | endfunction 30 | 31 | function! langserver#hover#request() abort 32 | return langserver#client#send(langserver#util#get_lsp_id(), { 33 | \ 'method': 'textDocument/hover', 34 | \ 'params': langserver#util#get_text_document_position_params(), 35 | \ 'on_notification': { 36 | \ 'callback': function('langserver#hover#callback'), 37 | \ }, 38 | \ }) 39 | endfunction 40 | 41 | function! langserver#hover#display(range, data) abort 42 | if !empty(a:range) 43 | let s:my_last_highlight = matchaddpos('WarningMsg', langserver#highlight#matchaddpos_range(a:range)) 44 | endif 45 | 46 | let l:hover_string = '' 47 | for l:explanation in a:data 48 | let l:hover_string .= printf("%s: %s\n", 49 | \ l:explanation['language'], 50 | \ l:explanation['value'], 51 | \ ) 52 | endfor 53 | 54 | echo l:hover_string 55 | 56 | if !empty(a:range) 57 | return timer_start(3000, function('s:delete_highlight')) 58 | endif 59 | endfunction 60 | 61 | function! s:delete_highlight(timer_id) abort 62 | if exists('s:my_last_highlight') 63 | silent! call matchdelete(s:my_last_highlight) 64 | endif 65 | endfunction 66 | -------------------------------------------------------------------------------- /autoload/langserver/initialize.vim: -------------------------------------------------------------------------------- 1 | "" 2 | " Initialize request. The first request from the client to the server 3 | " 4 | " Corresponds to: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#initialize-request 5 | function! langserver#initialize#request() abort 6 | let l:return_dict = {} 7 | 8 | " TODO: Determine what to do about starting vs connecting 9 | " TODO: Add to dictionary if present 10 | let l:process_id = 0 11 | 12 | " TODO: Setting a project root 13 | " TODO: Add to dictionary if present 14 | let l:root_path = '' 15 | 16 | " TODO: Add to dictionary if present 17 | let l:initialization_options = '?' 18 | 19 | let l:return_dict['capabilities'] = langserver#initialize#get_client_capabilities() 20 | 21 | 22 | return l:return_dict 23 | endfunction 24 | 25 | "" 26 | " Return the capabilities of the client 27 | " 28 | " Seems to just be empty according to the spec 29 | function! langserver#initialize#get_client_capabilities() abort 30 | return {} 31 | endfunction 32 | 33 | function! langserver#initialize#callback(id, data, event) abort 34 | if langserver#client#is_error(a:data.response) 35 | call langserver#log#log('error', 36 | \ 'Could not connect to: ' . string(a:id) . "\n" . 37 | \ 'Message was: ' . string(a:data), 38 | \ v:true, 39 | \ ) 40 | else 41 | call langserver#initialize#response(a:id, a:data.response.result) 42 | call langserver#log#log('info', 'Succesfully connected to: ' . string(a:id), v:true) 43 | endif 44 | endfunction 45 | 46 | "" 47 | " Handle the response of the server. 48 | " This message details the capabilities of the language server. 49 | " 50 | " @param name (string): The name of the server 51 | " @param response (dict): The dictionary detailing the capabilities of the server 52 | function! langserver#initialize#response(name, response) abort 53 | if has_key(a:response, 'textDocumentSync') 54 | call langserver#capabilities#set_test_document_sync(a:name, a:response['textDocumentSync']) 55 | endif 56 | 57 | if has_key(a:response, 'hoverProvider') 58 | call langserver#capabilities#set_hover_provider(a:name, a:response['hoverProvider']) 59 | endif 60 | 61 | if has_key(a:response, 'completionProvider') 62 | let l:complete_opt_resolve = get(a:response['completionProvider'], 'resolveProvider', v:false) 63 | let l:complete_opt_trigger = get(a:response['completionProvider'], 'triggerCharacters', []) 64 | call langserver#capabilities#set_completion_provider(a:name, l:complete_opt_resolve, l:complete_opt_trigger) 65 | endif 66 | 67 | if has_key(a:response, 'signatureHelpProvider') 68 | let l:signature_help_resolve = get(a:response['signatureHelpProvider'], 'resolveProvider', v:false) 69 | call langserver#capabilities#set_signature_help_provider(a:name, l:signature_help_resolve) 70 | endif 71 | 72 | if has_key(a:response, 'definitionProvider') 73 | let l:definitionProviderValue = get(a:response, 'definitionProvider', v:false) 74 | 75 | call langserver#capabilities#set_definition_provider(a:name, l:definitionProviderValue) 76 | endif 77 | 78 | if has_key(a:response, 'referencesProvider') 79 | let l:referencesProviderValue = get(a:response, 'referencesProvider', v:false) 80 | 81 | call langserver#capabilities#set_references_provider(a:name, l:referencesProviderValue) 82 | endif 83 | 84 | if has_key(a:response, 'documentHighlightProvider') 85 | let l:documentHighlightProviderValue = get(a:response, 'documentHighlightProvider', v:false) 86 | 87 | call langserver#capabilities#set_document_highlight_provider(a:name, l:documentHighlightProviderValue) 88 | endif 89 | 90 | if has_key(a:response, 'documentSymbolProvider') 91 | let l:documentSymbolProviderValue = get(a:response, 'documentSymbolProvider', v:false) 92 | 93 | call langserver#capabilities#set_document_symbol_provider(a:name, l:documentSymbolProviderValue) 94 | endif 95 | 96 | if has_key(a:response, 'workspaceSymbolProvider') 97 | let l:workspaceSymbolProviderValue = get(a:response, 'workspaceSymbolProvider', v:false) 98 | 99 | call langserver#capabilities#set_workspace_symbol_provider(a:name, l:workspaceSymbolProviderValue) 100 | endif 101 | 102 | if has_key(a:response, 'codeActionProvider') 103 | let l:codeActionProviderValue = get(a:response, 'codeActionProvider', v:false) 104 | 105 | call langserver#capabilities#set_code_action_provider(a:name, l:codeActionProviderValue) 106 | endif 107 | 108 | " TODO: Code lens provider 109 | if has_key(a:response, 'codeLensProvider') 110 | let l:codeLensProviderValue = get(a:response['codeLensProvider'], 'resolveProvider', v:false) 111 | 112 | call langserver#capabilities#set_code_lens_provider(a:name, l:codeLensProviderValue) 113 | endif 114 | 115 | if has_key(a:response, 'documentFormattingProvider') 116 | let l:documentFormattingProviderValue = get(a:response, 'documentFormattingProvider', v:false) 117 | 118 | call langserver#capabilities#set_document_formatting_provider(a:name, l:documentFormattingProviderValue) 119 | endif 120 | 121 | if has_key(a:response, 'documentRangeFormattingProvider') 122 | let l:documentRangeFormattingProviderValue = get(a:response, 'documentRangeFormattingProvider', v:false) 123 | 124 | call langserver#capabilities#set_document_range_formatting_provider(a:name, l:documentRangeFormattingProviderValue) 125 | endif 126 | 127 | if has_key(a:response, 'documentOnTypeFormattingProvider') 128 | let l:document_type_formatting_first = get(a:response['documentOnTypeFormattingProvider'], 'firstTriggerCharacter') 129 | let l:document_type_formatting_more = get(a:response['documentOnTypeFormattingProvider'], 'moreTriggerCharacter') 130 | 131 | call langserver#capabilities#set_document_on_type_formatting_provider(a:name, 132 | \ l:document_type_formatting_first, 133 | \ l:document_type_formatting_more) 134 | endif 135 | 136 | if has_key(a:response, 'renameProvider') 137 | let l:renameProviderValue = get(a:response, 'renameProvider', v:false) 138 | 139 | call langserver#capabilities#set_rename_provider(a:name, l:renameProviderValue) 140 | endif 141 | endfunction 142 | -------------------------------------------------------------------------------- /autoload/langserver/job.vim: -------------------------------------------------------------------------------- 1 | " Author: Prabir Shrestha 2 | " License: The MIT License 3 | " Website: https://github.com/prabirshrestha/async.vim 4 | 5 | if !has('nvim') 6 | " vint: -ProhibitAbbreviationOption 7 | let s:save_cpo = &cpo 8 | set cpo&vim 9 | " vint: +ProhibitAbbreviationOption 10 | endif 11 | 12 | let s:jobidseq = 0 13 | let s:jobs = {} " { job, opts, type: 'vimjob|nvimjob'} 14 | let s:job_type_nvimjob = 'nvimjob' 15 | let s:job_type_vimjob = 'vimjob' 16 | let s:job_error_unsupported_job_type = -2 " unsupported job type 17 | 18 | function! s:job_supported_types() abort 19 | let l:supported_types = [] 20 | if has('nvim') 21 | let l:supported_types += [s:job_type_nvimjob] 22 | endif 23 | if !has('nvim') && has('job') && has('channel') && has('lambda') 24 | let l:supported_types += [s:job_type_vimjob] 25 | endif 26 | return l:supported_types 27 | endfunction 28 | 29 | function! s:job_supports_type(type) abort 30 | return index(s:job_supported_types(), a:type) >= 0 31 | endfunction 32 | 33 | function! s:out_cb(job, data, jobid, opts) abort 34 | if has_key(a:opts, 'on_stdout') 35 | call a:opts.on_stdout(a:jobid, split(a:data, "\n"), 'stdout') 36 | endif 37 | endfunction 38 | 39 | function! s:err_cb(job, data, jobid, opts) abort 40 | if has_key(a:opts, 'on_stderr') 41 | call a:opts.on_stderr(a:jobid, split(a:data, "\n"), 'stderr') 42 | endif 43 | endfunction 44 | 45 | function! s:exit_cb(job, status, jobid, opts) abort 46 | if has_key(a:opts, 'on_exit') 47 | call a:opts.on_exit(a:jobid, a:status, 'exit') 48 | endif 49 | if has_key(s:jobs, a:jobid) 50 | call remove(s:jobs, a:jobid) 51 | endif 52 | endfunction 53 | 54 | function! s:on_stdout(jobid, data, event) abort 55 | if has_key(s:jobs, a:jobid) 56 | let l:jobinfo = s:jobs[a:jobid] 57 | if has_key(l:jobinfo.opts, 'on_stdout') 58 | call l:jobinfo.opts.on_stdout(a:jobid, a:data, a:event) 59 | endif 60 | endif 61 | endfunction 62 | 63 | function! s:on_stderr(jobid, data, event) abort 64 | if has_key(s:jobs, a:jobid) 65 | let l:jobinfo = s:jobs[a:jobid] 66 | if has_key(l:jobinfo.opts, 'on_stderr') 67 | call l:jobinfo.opts.on_stderr(a:jobid, a:data, a:event) 68 | endif 69 | endif 70 | endfunction 71 | 72 | function! s:on_exit(jobid, status, event) abort 73 | if has_key(s:jobs, a:jobid) 74 | let l:jobinfo = s:jobs[a:jobid] 75 | if has_key(l:jobinfo.opts, 'on_exit') 76 | call l:jobinfo.opts.on_exit(a:jobid, a:status, a:event) 77 | endif 78 | endif 79 | endfunction 80 | 81 | function! s:job_start(cmd, opts) abort 82 | let l:jobtypes = s:job_supported_types() 83 | let l:jobtype = '' 84 | 85 | if has_key(a:opts, 'type') 86 | if type(a:opts.type, v:t_string) 87 | if !s:job_supports_type(a:opts.type) 88 | return s:job_error_unsupported_job_type 89 | endif 90 | let l:jobtype = a:opts.type 91 | else 92 | let l:jobtypes = a:opts.type 93 | endif 94 | endif 95 | 96 | if empty(l:jobtype) 97 | " find the best jobtype 98 | for l:jobtype in l:jobtypes 99 | if s:job_supports_type(l:jobtype) 100 | let l:jobtype = l:jobtype 101 | endif 102 | endfor 103 | endif 104 | 105 | if l:jobtype ==? '' 106 | return s:job_error_unsupported_job_type 107 | endif 108 | 109 | 110 | if l:jobtype == s:job_type_nvimjob 111 | let l:job = jobstart(a:cmd, { 112 | \ 'on_stdout': function('s:on_stdout'), 113 | \ 'on_stderr': function('s:on_stderr'), 114 | \ 'on_exit': function('s:on_exit'), 115 | \}) 116 | let l:jobid = l:job " nvimjobid and internal jobid is same 117 | let s:jobs[l:jobid] = { 118 | \ 'type': s:job_type_nvimjob, 119 | \ 'opts': a:opts, 120 | \ } 121 | let s:jobs[l:jobid].job = l:job 122 | elseif l:jobtype == s:job_type_vimjob 123 | let s:jobidseq = s:jobidseq + 1 124 | let l:jobid = s:jobidseq 125 | let l:job = job_start(a:cmd, { 126 | \ 'out_cb': {job,data->s:out_cb(job, data, l:jobid, a:opts)}, 127 | \ 'err_cb': {job,data->s:err_cb(job, data, l:jobid, a:opts)}, 128 | \ 'exit_cb': {job,data->s:exit_cb(job, data, l:jobid, a:opts)}, 129 | \ 'mode': 'raw', 130 | \ }) 131 | let s:jobs[l:jobid] = { 132 | \ 'type': s:job_type_vimjob, 133 | \ 'opts': a:opts, 134 | \ 'job': l:job, 135 | \ 'channel': job_getchannel(l:job) 136 | \ } 137 | else 138 | return s:job_error_unsupported_job_type 139 | endif 140 | 141 | return l:jobid 142 | endfunction 143 | 144 | function! s:job_stop(jobid) abort 145 | if has_key(s:jobs, a:jobid) 146 | let l:jobinfo = s:jobs[a:jobid] 147 | if l:jobinfo.type == s:job_type_nvimjob 148 | call jobstop(a:jobid) 149 | elseif l:jobinfo.type == s:job_type_vimjob 150 | call job_stop(s:jobs[a:jobid].job) 151 | endif 152 | if has_key(s:jobs, a:jobid) 153 | call remove(s:jobs, a:jobid) 154 | endif 155 | endif 156 | endfunction 157 | 158 | function! s:job_send(jobid, data) abort 159 | let l:jobinfo = s:jobs[a:jobid] 160 | if l:jobinfo.type == s:job_type_nvimjob 161 | call jobsend(a:jobid, a:data) 162 | elseif l:jobinfo.type == s:job_type_vimjob 163 | call ch_sendraw(l:jobinfo.channel, a:data) 164 | endif 165 | endfunction 166 | 167 | " public apis {{{ 168 | function! langserver#job#start(cmd, opts) abort 169 | return s:job_start(a:cmd, a:opts) 170 | endfunction 171 | 172 | function! langserver#job#stop(jobid) abort 173 | call s:job_stop(a:jobid) 174 | endfunction 175 | 176 | function! langserver#job#send(jobid, data) abort 177 | call s:job_send(a:jobid, a:data) 178 | endfunction 179 | " }}} 180 | -------------------------------------------------------------------------------- /autoload/langserver/lock.vim: -------------------------------------------------------------------------------- 1 | let s:unlocked_id = -1 2 | 3 | function! s:dict_lock(job_id) dict 4 | if self.locked == v:false 5 | let self.locked = v:true 6 | let self.id = a:job_id 7 | return v:true 8 | else 9 | return v:false 10 | endif 11 | endfunction 12 | 13 | function! s:dict_unlock() dict 14 | let self.id = s:unlocked_id 15 | let self.locked = v:false 16 | endfunction 17 | 18 | function! s:get_id() dict 19 | return self.id 20 | endfunction 21 | 22 | function! s:take_lock(job_id) dict 23 | " If we're locked, kill the other job 24 | " And set ourselves to be the current job. 25 | if self.locked 26 | " TODO: Don't actually want to stop the whole server... 27 | " I just want to stop the current request. Maybe a send cancel request 28 | " would be good enough. We will see. 29 | " call langserver#job#stop(self.id) 30 | call self.unlock() 31 | endif 32 | 33 | call self.lock(a:job_id) 34 | endfunction 35 | 36 | function! langserver#lock#semaphore() abort 37 | let l:ret = { 38 | \ 'id': s:unlocked_id, 39 | \ 'locked': v:false, 40 | \ } 41 | let l:ret.lock = function('s:dict_lock') 42 | let l:ret.unlock = function('s:dict_unlock') 43 | let l:ret.get_id = function('s:get_id') 44 | let l:ret.take_lock = function('s:take_lock') 45 | return l:ret 46 | endfunction 47 | -------------------------------------------------------------------------------- /autoload/langserver/log.vim: -------------------------------------------------------------------------------- 1 | let s:log_file = expand('~/langserver-vim.log') 2 | 3 | let s:current_level = 4 4 | let s:log_level_map = { 5 | \ 'error': 0, 6 | \ 'warning': 1, 7 | \ 'info': 2, 8 | \ 'debug': 3, 9 | \ 'micro': 4, 10 | \ } 11 | 12 | let s:clear_log = v:true 13 | 14 | "" 15 | " Logging helper 16 | function! langserver#log#log(level, message, ...) abort 17 | if s:clear_log 18 | call writefile([], s:log_file, '') 19 | let s:clear_log = v:false 20 | endif 21 | 22 | if a:0 > 0 23 | let l:echo_choice = a:1 24 | else 25 | let l:echo_choice = v:false 26 | endif 27 | 28 | let l:numeric_level = s:log_level_map[a:level] 29 | 30 | if type(a:message) == type('') 31 | let l:msg = [a:message] 32 | elseif type(a:message) == type({}) 33 | let l:msg = [string(a:message)] 34 | elseif type(a:message) != type([]) 35 | " TODO: Handle other types of messages? 36 | else 37 | let l:msg = a:message 38 | endif 39 | 40 | let l:final_msg = [] 41 | for l:item in l:msg 42 | call add(l:final_msg, printf('%5s: %s', 43 | \ a:level, 44 | \ l:item, 45 | \ )) 46 | endfor 47 | 48 | 49 | if l:numeric_level < s:current_level 50 | if l:echo_choice 51 | echom string(l:final_msg) 52 | endif 53 | 54 | call writefile(l:final_msg, s:log_file, 'a') 55 | endif 56 | endfunction 57 | 58 | "" 59 | " Log response helper 60 | function! langserver#log#response(id, data, event) abort 61 | let g:last_response = a:data 62 | 63 | if type(a:data) != type({}) 64 | call langserver#log#log('debug', 65 | \ printf('(%3s:%15s): %s', 66 | \ a:id, 67 | \ a:event, 68 | \ string(a:data) 69 | \ ), 70 | \ langserver#util#debug(), 71 | \ ) 72 | return 73 | endif 74 | 75 | if has_key(a:data, 'response') && has_key(a:data, 'request') 76 | \ && has_key(a:data.response, 'result') && has_key(a:data.request, 'method') 77 | call langserver#log#log('debug', 78 | \ printf('(%3s:%15s): Response -> M(%20s), D(%s)', 79 | \ a:id, 80 | \ a:event, 81 | \ string(a:data.request.method), 82 | \ string(a:data.response.result), 83 | \ ), 84 | \ langserver#util#debug(), 85 | \ ) 86 | elseif has_key(a:data, 'request') && type(a:data.request) == type({}) 87 | call langserver#log#log('debug', 88 | \ printf('(%3s:%15s): Request -> M(%20s), D(%s)', 89 | \ a:id, 90 | \ a:event, 91 | \ string(a:data.request.method), 92 | \ string(a:data.request.params), 93 | \ ), 94 | \ langserver#util#debug(), 95 | \ ) 96 | else 97 | call langserver#log#log('debug', 98 | \ printf('(%3s:%15s): Unknown -> D(%s)', 99 | \ a:id, 100 | \ a:event, 101 | \ string(a:data), 102 | \ ), 103 | \ langserver#util#debug(), 104 | \ ) 105 | endif 106 | endfunction 107 | 108 | "" 109 | " Log only at debug level 110 | function! langserver#log#callback(id, data, event) abort 111 | call langserver#log#log('debug', 112 | \ printf('(%3s:%15s): %s', 113 | \ a:id, 114 | \ a:event, 115 | \ string(a:data) 116 | \ ), 117 | \ v:false 118 | \ ) 119 | endfunction 120 | 121 | "" 122 | " Log a request from the server 123 | function! langserver#log#server_request(id, data, event) abort 124 | call langserver#log#log('info', 125 | \ printf('(%3s:%15s): %s', 126 | \ a:id, 127 | \ a:event, 128 | \ string(a:data) 129 | \ )) 130 | endfunction 131 | 132 | function! langserver#log#pretty_print(json_dict) abort 133 | " TODO: Get pretty printing of json dictionaries if possible 134 | let g:my_var = system([ 135 | \ 'echo', 136 | \ shellescape(g:last_response[3]), 137 | \ '|', 138 | \ 'python', '-m', 'json.tool', 139 | \ ]) 140 | endfunction 141 | -------------------------------------------------------------------------------- /autoload/langserver/mappings.vim: -------------------------------------------------------------------------------- 1 | 2 | 3 | function! langserver#mappings#default(opts) abort 4 | " Goto mappings 5 | nnoremap (langserver_goto_request) :call langserver#goto#request() 6 | 7 | " Hover mappings 8 | nnoremap (langserver_hover_request) :call langserver#hover#request() 9 | 10 | " Reference mappings 11 | nnoremap (langserver_textdocument_references_request) :call langserver#references#request() 12 | endfunction 13 | 14 | -------------------------------------------------------------------------------- /autoload/langserver/message.vim: -------------------------------------------------------------------------------- 1 | 2 | 3 | "" 4 | " Content helper 5 | " 6 | " @param id (int): The id is for TODO 7 | " @param method (str): A string defining the method name 8 | " @param params (dict): The parameters to be loaded into the message 9 | " 10 | " @returns (dict): message content 11 | function! langserver#message#content(id, method, params) abort 12 | return { 13 | \ 'jsonrpc': g:langserver_configuration['json_rpc_version'], 14 | \ 'id': a:id, 15 | \ 'method': a:method, 16 | \ 'params': a:params, 17 | \ } 18 | endfunction 19 | 20 | "" 21 | " Message sender 22 | " 23 | function! langserver#message#send(name, header, content) abort 24 | " Call the remote plugin to send this 25 | return langserver#message#content(0, a:header, a:content) 26 | endfunction 27 | -------------------------------------------------------------------------------- /autoload/langserver/references.vim: -------------------------------------------------------------------------------- 1 | let s:method = 'textDocument/references' 2 | 3 | function! langserver#references#transform_reply(message) abort 4 | " {'bufnr': bufnr('%'), 'lnum': 2, 'col': 8, 'text': 'Testing...', 'type': 'W'}, 5 | 6 | let l:location_list = [] 7 | for l:location in a:message 8 | let l:loc_bufnr = bufnr(langserver#util#get_filename(langserver#util#get_lsp_id(), l:location['uri'])) 9 | let l:loc_filename = langserver#util#get_filename(langserver#util#get_lsp_id(), l:location['uri']) 10 | let l:loc_line = l:location['range']['start']['line'] + 1 11 | let l:loc_text = langserver#util#get_line(l:loc_bufnr, l:loc_filename, l:loc_line) 12 | 13 | let l:location_dict = { 14 | \ 'filename': l:loc_filename, 15 | \ 'lnum': l:loc_line, 16 | \ 'col': l:location['range']['start']['character'] + 1, 17 | \ 'text': l:loc_text, 18 | \ 'type': 'x' 19 | \ } 20 | call add(l:location_list, l:location_dict) 21 | endfor 22 | 23 | return l:location_list 24 | endfunction 25 | 26 | function! langserver#references#callback(id, data, event) abort 27 | call langserver#log#callback(a:id, a:data, a:event) 28 | 29 | if type(a:data) != type({}) 30 | return 31 | endif 32 | 33 | if has_key(a:data, 'response') 34 | let l:parsed_data = a:data['response']['result'] 35 | else 36 | return 37 | endif 38 | 39 | let g:last_response = l:parsed_data 40 | 41 | let l:loc_list = langserver#references#transform_reply(l:parsed_data) 42 | 43 | call langserver#references#display(l:loc_list) 44 | endfunction 45 | 46 | function! langserver#references#request() abort 47 | let l:params = langserver#util#get_text_document_position_params() 48 | call extend(l:params, {'context': {'includeDeclaration': v:true}}) 49 | 50 | return langserver#client#send(langserver#util#get_lsp_id(), { 51 | \ 'method': s:method, 52 | \ 'params': l:params, 53 | \ }) 54 | endfunction 55 | 56 | function! langserver#references#display(loc_list) abort 57 | call langserver#log#log('debug', "Displaying references...\n" . string(a:loc_list)) 58 | 59 | " TODO: Highlight the references, and turn them off somehow 60 | call setloclist(0, 61 | \ a:loc_list, 62 | \ 'r', 63 | \ '[References]', 64 | \ ) 65 | 66 | if !empty(a:loc_list) 67 | lwindow 68 | endif 69 | endfunction 70 | -------------------------------------------------------------------------------- /autoload/langserver/request.vim: -------------------------------------------------------------------------------- 1 | 2 | "" 3 | " Corresponds to: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#goto-definition-request 4 | " 5 | " @param id (int | string): The ID of the request 6 | " @param position (Optional[dict]): {'line': int, 'character': int} 7 | function! langserver#request#goToDefinition(id, ...) abort 8 | if a:0 > 0 9 | let l:position = a:1 10 | else 11 | let l:position = langserver#util#get_position() 12 | endif 13 | 14 | return langserver#message#content( 15 | \ a:id, 16 | \ 'textDocument/definition', 17 | \ { 18 | \ 'textDocument': "TODO", 19 | \ 'position': l:position, 20 | \ }, 21 | \ ) 22 | endfunction 23 | -------------------------------------------------------------------------------- /autoload/langserver/symbol/util.vim: -------------------------------------------------------------------------------- 1 | let s:symbol_kind = { 2 | \ 'name': { 3 | \ 'File': 1, 4 | \ 'Module': 2, 5 | \ 'Namespace': 3, 6 | \ 'Package': 4, 7 | \ 'Class': 5, 8 | \ 'Method': 6, 9 | \ 'Property': 7, 10 | \ 'Field': 8, 11 | \ 'Constructor': 9, 12 | \ 'Enum': 10, 13 | \ 'Interface': 11, 14 | \ 'Function': 12, 15 | \ 'Variable': 13, 16 | \ 'Constant': 14, 17 | \ 'String': 15, 18 | \ 'Number': 16, 19 | \ 'Boolean': 17, 20 | \ 'Array': 18, 21 | \ }, 22 | \ 'number': { 23 | \ 1: 'File', 24 | \ 2: 'Module', 25 | \ 3: 'Namespace', 26 | \ 4: 'Package', 27 | \ 5: 'Class', 28 | \ 6: 'Method', 29 | \ 7: 'Property', 30 | \ 8: 'Field', 31 | \ 9: 'Constructor', 32 | \ 10: 'Enum', 33 | \ 11: 'Interface', 34 | \ 12: 'Function', 35 | \ 13: 'Variable', 36 | \ 14: 'Constant', 37 | \ 15: 'String', 38 | \ 16: 'Number', 39 | \ 17: 'Boolean', 40 | \ 18: 'Array', 41 | \ }, 42 | \ } 43 | 44 | 45 | function! langserver#symbol#util#transform_reply(message) abort 46 | let l:server_name = langserver#util#get_lsp_id() 47 | let l:qf_list = [] 48 | 49 | for l:msg in a:message 50 | let l:loc_filename = langserver#util#get_filename(l:server_name, l:msg['location']['uri']) 51 | let l:loc_line = l:msg['location']['range']['start']['line'] + 1 52 | let l:loc_col = l:msg['location']['range']['start']['character'] + 1 53 | 54 | let l:loc_kind = s:symbol_kind['number'][l:msg['kind']] 55 | 56 | let l:msg_dict = { 57 | \ 'filename': l:loc_filename, 58 | \ 'lnum': l:loc_line, 59 | \ 'col': l:loc_col, 60 | \ 'pattern': l:msg['name'], 61 | \ 'text': printf('%10s: %s', 62 | \ l:loc_kind, 63 | \ l:msg['name'], 64 | \ ), 65 | \ } 66 | 67 | call add(l:qf_list, l:msg_dict) 68 | endfor 69 | 70 | return l:qf_list 71 | endfunction 72 | 73 | -------------------------------------------------------------------------------- /autoload/langserver/symbol/workspace.vim: -------------------------------------------------------------------------------- 1 | let s:method = 'workspace/symbol' 2 | 3 | function! langserver#symbol#workspace#callback(id, data, event) abort 4 | let l:parsed_data = langserver#callbacks#data(a:id, a:data, a:event) 5 | if type(l:parsed_data) == type('') && l:parsed_data ==? '' 6 | return 7 | endif 8 | 9 | let l:transformed = langserver#symbol#util#transform_reply(l:parsed_data) 10 | call langserver#symbol#workspace#display(l:transformed) 11 | endfunction 12 | 13 | function!langserver#symbol#workspace#request(...) abort 14 | if a:0 > 0 15 | let l:query = a:1 16 | else 17 | let l:query = expand('') 18 | endif 19 | 20 | return langserver#client#send(langserver#util#get_lsp_id(), { 21 | \ 'method': s:method, 22 | \ 'params': {'query': l:query} 23 | \ }) 24 | endfunction 25 | 26 | function! langserver#symbol#workspace#display(loc_list) abort 27 | call langserver#log#log('debug', string(a:loc_list)) 28 | 29 | call setqflist(a:loc_list) 30 | endfunction 31 | -------------------------------------------------------------------------------- /autoload/langserver/util.vim: -------------------------------------------------------------------------------- 1 | 2 | let s:diagnostic_severity = { 3 | \ 'Error': 1, 4 | \ 'Warning': 2, 5 | \ 'Informatin': 3, 6 | \ 'Hint': 4, 7 | \ } 8 | 9 | 10 | " Basic Json Structures 11 | " 12 | 13 | "" 14 | " Get a uri from a filename 15 | function! langserver#util#get_uri(name, filename) abort 16 | if has('win32') || has('win64') 17 | return 'file:///' . substitute(a:filename, '\', '/', 'g') 18 | else 19 | return 'file://' . a:filename 20 | endif 21 | endfunction 22 | 23 | "" 24 | " Get a filename from a uri 25 | function! langserver#util#get_filename(name, uri) abort 26 | return substitute(a:uri, 'file://', '', 'g') 27 | endfunction 28 | 29 | "" 30 | " Get the root path 31 | " TODO: Not sure how to do this one well 32 | function! langserver#util#get_root_path(name) abort 33 | return langserver#util#get_uri(a:name, getcwd()) 34 | endfunction 35 | 36 | "" 37 | " Get a position dictinoary like the position structure 38 | " 39 | " Follows spec: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#position 40 | function! langserver#util#get_position() abort 41 | return {'line': line('.') - 1, 'character': col('.') - 1} 42 | endfunction 43 | 44 | "" 45 | " A range in a text document expressed as (zero-based) start and end positions. 46 | " A range is comparable to a selection in an editor. Therefore the end position is exclusive. 47 | " 48 | " Follows spec: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#range 49 | function! langserver#util#get_range(start, end) abort 50 | " TODO: Make sure that these are the correct relativeness, 51 | return {'start': a:start, 'end': a:end} 52 | endfunction 53 | 54 | "" 55 | " Represents a location inside a resource, such as a line inside a text file. 56 | " 57 | " Follows spec: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#location 58 | function! langserver#util#get_location(start, end) abort 59 | return { 60 | \ 'uri': expand('%'), 61 | \ 'range': langserver#util#get_range(a:start, a:end) 62 | \ } 63 | endfunction 64 | 65 | "" 66 | " Represents a diagnostic, such as a compiler error or warning. Diagnostic objects are only valid in the scope of a resource. 67 | " 68 | " Follows spec: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#diagnostic 69 | " 70 | " @param start (int) 71 | " @param end (int) 72 | " @param message (str) 73 | " @param options (dict): Contiainings items like: 74 | " @key severity (string): Matching key to s:diagnostic_severity 75 | " @key code TODO 76 | " @key source TODO 77 | function! langserver#util#get_diagnostic(start, end, message, options) abort 78 | let l:return_dict = { 79 | \ 'range': langserver#util#get_range(a:start, a:end), 80 | \ 'messsage': a:message 81 | \ } 82 | 83 | if has_key(a:options, 'severity') 84 | let l:return_dict['severity'] = a:options['severity'] 85 | endif 86 | 87 | if has_key(a:options, 'code') 88 | let l:return_dict['code'] = a:options['code'] 89 | endif 90 | 91 | if has_key(a:options, 'source') 92 | let l:return_dict['source'] = a:options['source'] 93 | endif 94 | 95 | return l:return_dict 96 | endfunction 97 | 98 | "" 99 | " Represents a reference to a command. 100 | " Provides a title which will be used to represent a command in the UI. 101 | " Commands are identitifed using a string identifier and the protocol currently doesn't specify a set of well known commands. 102 | " So executing a command requires some tool extension code. 103 | " 104 | " Corresponds to: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#command 105 | " 106 | " @param title (str): Title of the command 107 | " @param command (str): The identifier of the actual command handler 108 | " @param arguments (Optional[list]): Optional list of arguments passed to the command 109 | function! langserver#util#get_command(title, command, ...) abort 110 | let l:return_dict = { 111 | \ 'title': a:title, 112 | \ 'command': a:command, 113 | \ } 114 | 115 | if a:0 > 1 116 | let l:return_dict['arguments'] = a:1 117 | endif 118 | endfunction 119 | 120 | "" 121 | " A textual edit applicable to a text document. 122 | " 123 | " Corresponds to: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#textedit 124 | " 125 | " @param start (int) 126 | " @param end (int) 127 | " @param new_text (string): The string to be inserted. Use an empty string for 128 | " delete 129 | function! langserver#util#get_text_edit(start, end, new_text) abort 130 | return { 131 | \ 'range': langserver#util#get_range(a:start, a:end), 132 | \ 'newText': a:new_text 133 | \ } 134 | endfunction 135 | 136 | "" 137 | " A workspace edit represents changes to many resources managed in the workspace. 138 | " 139 | " Corresponds to: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#workspaceedit 140 | " 141 | " TODO: Figure out a better way to do this. 142 | " @param 143 | function! langserver#util#get_workspace_edit(uri, edit) abort 144 | let l:my_dict = { 145 | \ 'uri': 'edit', 146 | \ } 147 | endfunction 148 | 149 | "" 150 | " Text documents are identified using a URI. On the protocol level, URIs are passed as strings. The corresponding JSON structure looks like this: 151 | " 152 | " Corresponds to: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#textdocumentidentifier 153 | function! langserver#util#get_text_document_identifier(name) abort 154 | " TODO: I'm not sure if I'll be looking to get other items or what from this 155 | " function 156 | return {'uri': langserver#util#get_uri(a:name, expand('%:p'))} 157 | " return {'uri': langserver#util#get_uri(a:name, expand('%'))} 158 | endfunction 159 | 160 | "" 161 | " New: An identifier to denote a specific version of a text document. 162 | " 163 | " @param version (Optional[int]): If specified, refer to this version 164 | function! langserver#util#get_versioned_text_document_identifier(...) abort 165 | let l:return_dict = langserver#util#get_text_document_identifier() 166 | 167 | if a:0 > 0 168 | let l:version = a:1 169 | else 170 | " Add the version number to the text document identifier 171 | " And don't increment the count for the version 172 | let l:version = langserver#version#get_version(l:return_dict['uri'], v:false) 173 | endif 174 | 175 | call extend(l:return_dict, {'version': l:version}) 176 | 177 | return l:return_dict 178 | endfunction 179 | 180 | "" 181 | " An item to transfer a text document from the client to the server. 182 | " 183 | " Corresponds to: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#textdocumentitem 184 | function! langserver#util#get_text_document_item(name, filename) abort 185 | let l:temp_uri = langserver#util#get_uri(a:name, a:filename) 186 | 187 | return { 188 | \ 'uri': l:temp_uri, 189 | \ 'languageId': &filetype, 190 | \ 'version': langserver#version#get_version(l:temp_uri), 191 | \ 'text': langserver#util#get_file_contents(a:filename), 192 | \ } 193 | endfunction 194 | 195 | "" 196 | " A parameter literal used in requests to pass a text document and a position inside that document. 197 | " 198 | function! langserver#util#get_text_document_position_params() abort 199 | return { 200 | \ 'textDocument': langserver#util#get_text_document_identifier(langserver#util#get_lsp_id()), 201 | \ 'position': langserver#util#get_position(), 202 | \ } 203 | endfunction 204 | 205 | "" 206 | " Read the contents into a variable 207 | function! langserver#util#get_file_contents(filename) abort 208 | return join(readfile(a:filename), "\n") 209 | endfunction 210 | 211 | 212 | "" 213 | " Parse the stdin of a server 214 | function! langserver#util#parse_message(message) abort 215 | if type(a:message) ==# type([]) 216 | let l:data = join(a:message, '') 217 | elseif type(a:message) ==# type('') 218 | let l:data = a:message 219 | else 220 | endif 221 | 222 | let l:parsed = {} 223 | if l:data =~? '--> request' 224 | let l:parsed['type'] = 'request' 225 | elseif l:data =~? '<-- result' 226 | let l:parsed['type'] = 'result' 227 | else 228 | let l:parsed['type'] = 'info' 229 | endif 230 | 231 | let l:data = substitute(l:data, '--> request #\w*: ', '', 'g') 232 | let l:data = substitute(l:data, '<-- result #\w*: ', '', 'g') 233 | 234 | if l:parsed['type'] ==# 'request' || l:parsed['type'] ==# 'result' 235 | let l:data = substitute(l:data, '^\(\S*\):', '"\1":', 'g') 236 | let l:data = '{' . l:data . '}' 237 | let l:data = json_decode(l:data) 238 | endif 239 | 240 | let l:parsed['data'] = l:data 241 | return l:parsed 242 | endfunction 243 | 244 | function! langserver#util#debug() abort 245 | return v:false 246 | endfunction 247 | 248 | function! langserver#util#get_executable_key(...) abort 249 | if a:0 > 0 250 | let l:file_type = a:1 251 | else 252 | let l:file_type = &filetype 253 | endif 254 | 255 | if !exists('g:langserver_executables') 256 | echoerr '`g:langserver_executables` was not defined' 257 | return '' 258 | endif 259 | 260 | for l:k in keys(g:langserver_executables) 261 | if index(split(l:k, ','), l:file_type) >= 0 262 | return l:k 263 | endif 264 | endfor 265 | 266 | echoerr 'Unsupported filetype: ' . l:file_type 267 | return '' 268 | endfunction 269 | 270 | function! langserver#util#get_lsp_id() abort 271 | let g:lsp_id_map = get(g:, 'lsp_id_map', {}) 272 | 273 | if has_key(g:lsp_id_map, &filetype) 274 | return g:lsp_id_map[&filetype] 275 | else 276 | return -1 277 | endif 278 | endfunction 279 | 280 | function! langserver#util#get_line(loc_bufnr, loc_filename, loc_line) abort 281 | if bufnr('%') == a:loc_bufnr 282 | let l:loc_text = getline(a:loc_line) 283 | else 284 | let l:loc_text = readfile(a:loc_filename, '', a:loc_line)[a:loc_line - 1] 285 | endif 286 | 287 | return l:loc_text 288 | endfunction 289 | 290 | function! langserver#util#null_check(item) 291 | if type(a:item) == type(v:null) 292 | return v:true 293 | else 294 | return v:false 295 | endif 296 | endfunction 297 | -------------------------------------------------------------------------------- /autoload/langserver/version.vim: -------------------------------------------------------------------------------- 1 | let s:uri_version = {} 2 | let s:id_number = 0 3 | 4 | "" 5 | " Get the current version number and then increment it by one. 6 | " 7 | " @param uri (string): The uri of the file 8 | " @param increment (Optional[bool]): If true, increment the version 9 | function! langserver#version#get_version(uri, ...) abort 10 | if a:0 > 0 11 | let l:increment = a:1 12 | else 13 | let l:increment = v:true 14 | endif 15 | 16 | if !has_key(s:uri_version, a:uri) 17 | let s:uri_version[a:uri] = 0 18 | endif 19 | 20 | " Store the value that we're going to return 21 | " before we increment it 22 | let l:return_version = s:uri_version[a:uri] 23 | 24 | if l:increment 25 | let s:uri_version[a:uri] = s:uri_version[a:uri] + 1 26 | endif 27 | 28 | return l:return_version 29 | endfunction 30 | 31 | function! langserver#version#get_id() abort 32 | let s:id_number = s:id_number + 1 33 | return s:id_number 34 | endfunction 35 | -------------------------------------------------------------------------------- /autoload/langserver/window.vim: -------------------------------------------------------------------------------- 1 | let s:message_type = { 2 | \ 'name': { 3 | \ 'error': 1, 4 | \ 'warning': 2, 5 | \ 'info': 3, 6 | \ 'log': 4, 7 | \ }, 8 | \ 'number': { 9 | \ 1: 'error', 10 | \ 2: 'warning', 11 | \ 3: 'info', 12 | \ 4: 'log', 13 | \ }, 14 | \ } 15 | 16 | function! langserver#window#handle#showMessage(message) abort 17 | let l:type = a:message['type'] 18 | let l:message = a:message['message'] 19 | 20 | echo l:type | echo l:message 21 | endfunction 22 | 23 | function! langserver#window#handle#logMessage(message) abort 24 | let l:type = a:message['type'] 25 | let l:message = a:message['message'] 26 | 27 | " Not sure if this will work exactly. 28 | " Might have to do another mapping here. 29 | call langserver#log#log(l:type, l:message) 30 | endfunction 31 | -------------------------------------------------------------------------------- /doc/langserver.txt: -------------------------------------------------------------------------------- 1 | *langserver.txt* Langserver shim for Vim/Neovim 2 | 3 | Version: 0.1 4 | Author: TJ 5 | License: MIT License 6 | 7 | CONTENTS *langserver-contents* 8 | 9 | Introduction 10 | Configuration 11 | 12 | ================================================================================ 13 | INTRODUCTION *langserver-introduction* 14 | 15 | Microsoft Language Server Protocol shim for Vim and Neovim. 16 | 17 | ================================================================================ 18 | CONFIGURATION *langserver-configuration* 19 | 20 | Define a dictionary like this in your vimrc. > 21 | 22 | function! GetMyJSCommand() abort 23 | return ['executable', 'options'] 24 | endfunction 25 | 26 | let g:langserver_executables = { 27 | \ 'go': { 28 | \ 'name': 'sourcegraph/langserver-go', 29 | \ 'cmd': ['langserver-go', '-trace', '-logfile', expand('~/Desktop/langserver-go.log')], 30 | \ }, 31 | \ 'javascript,typescript': { 32 | \ 'name': 'myjsserver', 33 | \ 'cmd': function('GetMyJSCommand'), 34 | \ }, 35 | \ } 36 | 37 | 38 | ===== IN PROGRESS ===== ~ 39 | 40 | In general, it is recommended that you name the server with the convention of 41 | {repository_host}/{repository_name}. There will be configuration defaults set 42 | for known language servers to try and ease the startup difficulty for users. 43 | 44 | ==========~ 45 | 46 | TODO: Mappings 47 | 48 | Plug Mappings: 49 | 50 | (langserver_goto_request) 51 | Send a textdocument/definition request 52 | Sends you to the result 53 | 54 | (langserver_hover_request) 55 | Send a textdocument/hover request 56 | Displays the result in the echo window 57 | 58 | (langserver_textdocument_references_request) 59 | Send a textdocument/references request 60 | Displays the results in the loclist 61 | 62 | ================================================================================ 63 | USAGE *langserver-usage* 64 | 65 | Begin with calling: > 66 | 67 | call langserver#start({}) 68 | < 69 | 70 | or: > 71 | 72 | :LSPStart 73 | > 74 | 75 | TODO: Opening a file 76 | TODO: Goto definition 77 | TODO: Hover 78 | 79 | 80 | vim:tw=78:ts=8:ft=help:norl:noet:fen:noet: 81 | -------------------------------------------------------------------------------- /docs/basic_sequence.mscgen: -------------------------------------------------------------------------------- 1 | msc { 2 | width = "1200"; 3 | 4 | N [label="Neovim"], 5 | C [label="Neovim client"], 6 | S [label="Server (external)"]; 7 | 8 | 9 | N->C [label=":call StartLangServer"]; 10 | N abox N [label="Open a text document", textbgcolor="white", textcolor="black"]; 11 | N->C [label=":call langserver#didOpenTextDocument"]; 12 | C->S [label="send textDocument/didOpen"]; 13 | ...; 14 | 15 | N abox N [label="Edit a text document", textbgcolor="white", textcolor="black"]; 16 | N->C [label=":call langserver#didChangeTextDocument"]; 17 | C->S [label="send textDocument/didChange"]; 18 | S abox S [label="Analyze new document", textbgcolor="white", textcolor="black"]; 19 | S->C [label="send textDocument/publishDiagnostics"]; 20 | C abox C [label="Convert diagnostics to nvim commands", textbgcolor="white", textcolor="black"]; 21 | C->N [label=":call langserver#updateDiagnostics"]; 22 | ...; 23 | } 24 | -------------------------------------------------------------------------------- /docs/basic_sequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjdevries/nvim-langserver-shim/05f55a534e325c4d04b1876ba87270b07713e045/docs/basic_sequence.png -------------------------------------------------------------------------------- /docs/communication.mscgen: -------------------------------------------------------------------------------- 1 | msc { 2 | width = "1200"; 3 | 4 | N [label="Neovim"], 5 | P [label="Neovim Plugin"], 6 | R [label="Neovim Remote Plugin"], 7 | S [label="Server (external)"]; 8 | 9 | N note S [label="Current implementation of remote plugin is in Node. Could be other languages in the future.", textbgcolor="white", textcolor="black"]; 10 | 11 | 12 | N->P [label=":call StartLangServer "]; 13 | P abox P [label="Starts / Connects langserver process", textbgcolor="white", textcolor="black"]; 14 | ...; 15 | 16 | N abox N [label="Open a text document", textbgcolor="white", textcolor="black"]; 17 | N->P [label=":call langserver#didOpenTextDocument"]; 18 | P->R [label="Construct textDocument/didOpen from call"]; 19 | R abox R [label="Use vscode-jsonrpc to construct the correct message for the server", textbgcolor="white", textcolor="black"]; 20 | R->S [label="Send: textDocument/didOpen"]; 21 | ...; 22 | 23 | // TODO: Detail the process for going to a definition. 24 | 25 | } 26 | 27 | -------------------------------------------------------------------------------- /docs/communication.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjdevries/nvim-langserver-shim/05f55a534e325c4d04b1876ba87270b07713e045/docs/communication.png -------------------------------------------------------------------------------- /docs/idea.mscgen: -------------------------------------------------------------------------------- 1 | msc { 2 | width = "1200"; 3 | 4 | N [label="Neovim"], 5 | P [label="Neovim Plugin"], 6 | R [label="Neovim Remote Plugin"], 7 | S [label="Server (external)"]; 8 | 9 | 10 | P box R [label="Startup and connection of server already complete", textbgcolor="black", textcolor="white"]; 11 | N abox N [label="User initiates action / commands", textbgcolor="white", textcolor="black"]; 12 | N->P [label="Trigger command / autocommand"]; 13 | P abox P [label="Create message dictionary", textbgcolor="white", textcolor="black"]; 14 | P->R [label="Send message dictionary"]; 15 | R abox R [label="Turn message dictionary into LSP message", textbgcolor="white", textcolor="black"]; 16 | R->S [label="Send LSP message to server"]; 17 | S abox S [label="Process message / Create response", textbgcolor="white", textcolor="black"]; 18 | S->R [label="Send LSP response"]; 19 | R abox R [label="Convert response into neovim dictionary", textbgcolor="white", textcolor="black"]; 20 | R->P [label="Send vim dictionary"]; 21 | P abox P [label="Convert dictionary into neovim actions", textbgcolor="white", textcolor="black"]; 22 | P->N [label="Send actions"]; 23 | N abox N [label="User experiences happiness", textbgcolor="white", textcolor="black"]; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /docs/idea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjdevries/nvim-langserver-shim/05f55a534e325c4d04b1876ba87270b07713e045/docs/idea.png -------------------------------------------------------------------------------- /plugin/langserver.vim: -------------------------------------------------------------------------------- 1 | 2 | 3 | let g:langserver_configuration = { 4 | \ 'json_rpc_version': '2.0', 5 | \ } 6 | 7 | 8 | " Start the language server 9 | command! LSPStart call langserver#start({}) 10 | 11 | " Open a text document, and alert the language server 12 | command! LSPOpen call langserver#didOpenTextDocument() 13 | 14 | " Request a goto 15 | command! LSPGoto call langserver#goto#request() 16 | 17 | " Request a hover 18 | command! LSPHover call langserver#hover#request() 19 | 20 | let s:mapping_options = get(g:, 'langserver_mapping_options', {}) 21 | call langserver#mappings#default(s:mapping_options) 22 | nmap gd (langserver_goto_request) 23 | nmap gh (langserver_hover_request) 24 | -------------------------------------------------------------------------------- /preconfigured/freebroccolo/ocaml-language-server/callbacks.vim: -------------------------------------------------------------------------------- 1 | function! s:give_word_at_position(id, data, event) abort 2 | call langserver#log#server_request(a:id, a:data, a:event) 3 | " 'params': 4 | " {'uri': 'file:///home/tj/Downloads/example-ocaml-merlin/src/main.ml', 5 | " 'position': {'character': 9, 'line': 6} 6 | " } 7 | if !has_key(a:data.request.params, 'uri') || !has_key(a:data.request.params, 'position') 8 | call langserver#log#log('error', 'Unable to handle callback for: ' . string(a:data), v:true) 9 | return 10 | endif 11 | 12 | if v:false 13 | let l:loc_bufnr = bufnr(langserver#util#get_filename(langserver#util#get_lsp_id(), a:data.request.params.uri)) 14 | let l:loc_line = langserver#util#get_line(l:loc_bufnr, a:data.request.params.uri, a:data.request.params.position.line - 1) 15 | endif 16 | 17 | call langserver#client#send(a:id, { 18 | \ 'req_id': a:data.request.id, 19 | \ 'method': a:data.request.method, 20 | \ 'request': a:data.request, 21 | \ 'result': expand(''), 22 | \ 'response': { 23 | \ 'result': expand(''), 24 | \ }, 25 | \ }) 26 | return v:true 27 | endfunction 28 | 29 | 30 | function! Preconfigured_freebroccolo_ocaml_language_server() abort 31 | return { 32 | \ 'reason.client.giveWordAtPosition': function('s:give_word_at_position'), 33 | \ } 34 | endfunction 35 | -------------------------------------------------------------------------------- /preconfigured/freebroccolo/ocaml-language-server/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "reason.codelens.unicode": { 4 | "type": "boolean", 5 | "default": true, 6 | "description": "Enable the use of unicode symbols in codelens." 7 | }, 8 | "reason.server.languages": { 9 | "type": "array", 10 | "items": { 11 | "enum": [ 12 | "reason" 13 | ] 14 | }, 15 | "default": [ 16 | "reason" 17 | ], 18 | "maxItems": 1, 19 | "uniqueItems": true, 20 | "description": "The list of languages enable support for in the language server." 21 | }, 22 | "reason.debounce.linter": { 23 | "type": "integer", 24 | "default": 500, 25 | "description": "How long to idle (in milliseconds) after keypresses before refreshing linter diagnostics. Smaller values refresh diagnostics more quickly." 26 | }, 27 | "reason.path.ocamlfind": { 28 | "type": "string", 29 | "default": "ocamlfind", 30 | "description": "The path to the `ocamlfind` binary." 31 | }, 32 | "reason.path.ocamlmerlin": { 33 | "type": "string", 34 | "default": "ocamlmerlin", 35 | "description": "The path to the `ocamlmerlin` binary." 36 | }, 37 | "reason.path.opam": { 38 | "type": "string", 39 | "default": "opam", 40 | "description": "The path to the `opam` binary." 41 | }, 42 | "reason.path.rebuild": { 43 | "type": "string", 44 | "default": "rebuild", 45 | "description": "The path to the `rebuild` binary." 46 | }, 47 | "reason.path.refmt": { 48 | "type": "string", 49 | "default": "refmt", 50 | "description": "The path to the `refmt` binary." 51 | }, 52 | "reason.path.refmterr": { 53 | "type": "string", 54 | "default": "refmterr", 55 | "description": "The path to the `refmterr` binary." 56 | }, 57 | "reason.path.rtop": { 58 | "type": "string", 59 | "default": "rtop", 60 | "description": "The path to the `rtop` binary." 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /preconfigured/test/test/callbacks.vim: -------------------------------------------------------------------------------- 1 | function! s:test_function(context) abort 2 | return 'tested ' . a:context['var'] . '!' 3 | endfunction 4 | 5 | function! Preconfigured_test_test() abort 6 | return { 7 | \ 'method_name': function('s:test_function'), 8 | \ } 9 | endfunction 10 | -------------------------------------------------------------------------------- /rplugin/python3/deoplete/langserver.py: -------------------------------------------------------------------------------- 1 | from .base import Base 2 | 3 | 4 | class Source(Base): 5 | def __init__(self, nvim): 6 | super(Source, self).__init__(nvim) 7 | 8 | self.nvim = nvim 9 | self.name = 'langserver' 10 | self.mark = '[LSP]' 11 | 12 | def on_event(self, context, filename=''): 13 | pass 14 | 15 | def gather_candidates(self, context): 16 | user_input = context['input'] 17 | filetype = context.get('filetype', '') 18 | complete_str = context['complete_str'] 19 | 20 | return [ 21 | { 22 | 'word': user_input + '_hello', 23 | } 24 | ] 25 | -------------------------------------------------------------------------------- /testing/vim_connection.vim: -------------------------------------------------------------------------------- 1 | let s:langserver_executabe = 'langserver-go' 2 | let s:langserver_name = 'go' 3 | 4 | let s:debug = v:false 5 | 6 | function! s:on_stderr(id, data, event) 7 | " TODO: some function that parses this easily. 8 | " let split_data = split(a:data[0], ':') 9 | " echom '{' . join(split_data[1:], ':') . '}' 10 | " return 11 | 12 | " if v:true || has_key(a:data, 'textDocument/definition') 13 | " echom 'lsp has found definition' 14 | " else 15 | echom 'lsp('.a:id.'):stderr: Event:' . a:event . ' ==> ' . join(a:data, "\r\n") 16 | 17 | let parsed = langserver#util#parse_message(a:data) 18 | echom 'Resulting data is: ' . string(parsed) 19 | endfunction 20 | 21 | function! s:on_exit(id, status, event) 22 | echom 'lsp('.a:id.'):exit:'.a:status 23 | endfunction 24 | 25 | function! s:on_notification(id, data, event) 26 | if langserver#client#is_error(a:data.response) 27 | echom 'lsp('.a:id.'):notification:notification error receieved for '.a:data.request.method 28 | else 29 | echom 'lsp('.a:id.'):notification:notification success receieved for '.a:data.request.method 30 | endif 31 | endfunction 32 | 33 | function! s:on_notification1(id, data, event) 34 | echom 'lsp('.a:id.'):notification1:'json_encode(a:data) 35 | endfunction 36 | 37 | " go get github.com/sourcegraph/go-langserver/langserver/cmd/langserver-go 38 | let g:lsp_id = langserver#client#start({ 39 | \ 'cmd': [s:langserver_executabe, '-trace', '-logfile', expand('~/Desktop/langserver-go.log')], 40 | \ 'on_stderr': function('s:on_stderr'), 41 | \ 'on_exit': function('s:on_exit'), 42 | \ 'on_notification': function('s:on_notification') 43 | \ }) 44 | 45 | if g:lsp_id > 0 46 | echom 'lsp server running' 47 | call langserver#client#send(g:lsp_id, { 48 | \ 'method': 'initialize', 49 | \ 'params': { 50 | \ 'capabilities': {}, 51 | \ 'rootPath': 'file:///home/tj/go/src/github.com/sourcegraph/go-langserver', 52 | \ }, 53 | \ 'on_notification': function('s:on_notification1') 54 | \ }) 55 | else 56 | echom 'failed to start lsp server' 57 | endif 58 | 59 | sleep 1 60 | 61 | function! s:on_definition_request(id, data, event) 62 | let parsed = langserver#util#parse_message(a:data) 63 | let g:last_response = parsed 64 | 65 | if parsed['type'] ==# 'result' 66 | let data = parsed['data']['textDocument/definition'][0] 67 | else 68 | return 69 | endif 70 | 71 | if s:debug 72 | echom string(a:data) 73 | echom 'Definition data is: ' . string(parsed) 74 | else 75 | " {'data': 76 | " {'textDocument/definition': 77 | " [ 78 | " {'uri': 'file:///home/tj/go/src/github.com/sourcegraph/go-langserver/langserver/util.go', 79 | " 'range': { 80 | " 'end': {'character': 29, 'line': 15}, 81 | " 'start': {'character': 23, 'line': 15}} 82 | " } 83 | " ] 84 | " }, 85 | " 'type': 'result'} 86 | call langserver#goto#goto_defintion(g:lsp_id, data['uri'], data['range'], {}) 87 | endif 88 | endfunction 89 | 90 | function! SendDefinitionRequest() abort 91 | call langserver#client#send(g:lsp_id, { 92 | \ 'method': 'textDocument/definition', 93 | \ 'params': { 94 | \ 'textDocument': langserver#util#get_text_document_identifier(s:langserver_name), 95 | \ 'position': langserver#util#get_position(), 96 | \ }, 97 | \ 'on_notification': function('s:on_definition_request'), 98 | \ 'on_stderr': function('s:on_definition_request'), 99 | \ }) 100 | endfunction 101 | 102 | " call langserver#client#stop(g:lsp_id) 103 | -------------------------------------------------------------------------------- /tests/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Call any associated setup for the server 4 | nvim -u ./tests/setup.vim ./tests/test.go 5 | -------------------------------------------------------------------------------- /tests/setup.vim: -------------------------------------------------------------------------------- 1 | " Set the rtp to be here 2 | 3 | " Add this plugin 4 | call plug#begin('~/.vim/plugged') 5 | 6 | Plug '~/.vim/plugged/nvim-langserver-shim' 7 | 8 | call plug#end() 9 | -------------------------------------------------------------------------------- /tests/test.go: -------------------------------------------------------------------------------- 1 | // In Go, _variables_ are explicitly declared and used by 2 | // the compiler to e.g. check type-correctness of function 3 | // calls. 4 | 5 | package main 6 | 7 | import "fmt" 8 | 9 | func main() { 10 | 11 | // `var` declares 1 or more variables. 12 | var a string = "initial" 13 | fmt.Println(a) 14 | 15 | // You can declare multiple variables at once. 16 | var b, c int = 1, 2 17 | fmt.Println(b, c) 18 | 19 | // Go will infer the type of initialized variables. 20 | var d = true 21 | fmt.Println(d) 22 | 23 | // Variables declared without a corresponding 24 | // initialization are _zero-valued_. For example, the 25 | // zero value for an `int` is `0`. 26 | var e int 27 | fmt.Println(e) 28 | 29 | // The `:=` syntax is shorthand for declaring and 30 | // initializing a variable, e.g. for 31 | // `var f string = "short"` in this case. 32 | f := "short" 33 | fmt.Println(f) 34 | } 35 | -------------------------------------------------------------------------------- /tests/test.py: -------------------------------------------------------------------------------- 1 | hello = 'world' 2 | 3 | print(hello) 4 | -------------------------------------------------------------------------------- /tests/test_defaults.vader: -------------------------------------------------------------------------------- 1 | Execute (Testing executable configuration): 2 | let g:__temp_exec = get(g:, 'langserver_executables', {}) 3 | let g:langserver_executables = { 4 | \ 'go': { 5 | \ 'name': 'sourcegraph/langserver-go', 6 | \ 'cmd': ['langserver-go', '-trace', '-logfile', expand('~/Desktop/langserver-go.log')], 7 | \ }, 8 | \ 'python': { 9 | \ 'name': 'sourcegraph/python-langserver', 10 | \ 'cmd': [expand('~/bin/python-langserver/python-langserver.py')], 11 | \ }, 12 | \ 'javascript,typescript,jsx,tsx': { 13 | \ 'name': 'javascript-typescript', 14 | \ 'cmd': [], 15 | \ }, 16 | \ } 17 | 18 | AssertEqual 'javascript,typescript,jsx,tsx', langserver#util#get_executable_key('jsx') 19 | 20 | let g:langserver_executables = g:__temp_exec 21 | 22 | 23 | Execute (Testing executable function as cmd): 24 | let g:__temp_exec = get(g:, 'langserver_executables', {}) 25 | 26 | let g:__vader_test__global_var = 'not updated' 27 | function! s:set_global_var() abort 28 | let g:__vader_test__global_var = 'updated' 29 | return ['executable', 'options'] 30 | endfunction 31 | 32 | let g:langserver_executables = { 33 | \ 'example': { 34 | \ 'name': 'sourcegraph/langserver-go', 35 | \ 'cmd': function('s:set_global_var'), 36 | \ }, 37 | \ } 38 | 39 | AssertEqual ['executable', 'options'], langserver#default#cmd('example') 40 | AssertEqual g:__vader_test__global_var, 'updated' 41 | 42 | let g:langserver_executables = g:__temp_exec 43 | 44 | 45 | Execute (Testing executable function as cmd with cs list): 46 | let g:__temp_exec = get(g:, 'langserver_executables', {}) 47 | 48 | let g:__vader_test__global_var = 'not updated' 49 | function! s:set_global_var() abort 50 | let g:__vader_test__global_var = 'updated' 51 | return ['executable', 'options'] 52 | endfunction 53 | 54 | let g:langserver_executables = { 55 | \ 'example,this,that,the_other': { 56 | \ 'name': 'this/langserver', 57 | \ 'cmd': function('s:set_global_var'), 58 | \ }, 59 | \ } 60 | 61 | AssertEqual ['executable', 'options'], langserver#default#cmd('example') 62 | AssertEqual g:__vader_test__global_var, 'updated' 63 | 64 | let g:langserver_executables = g:__temp_exec 65 | 66 | Execute (Getting predefined settings): 67 | " Pass 68 | 69 | Execute (Getting predefined functions and passing args): 70 | let g:__temp_exec = get(g:, 'langserver_executables', {}) 71 | 72 | let g:langserver_executables = { 73 | \ 'testing': { 74 | \ 'name': 'test/test', 75 | \ 'cmd': ['echo', '"hello"'], 76 | \ }, 77 | \ } 78 | 79 | let test_functions = langserver#default#extension_callbacks('testing') 80 | AssertEqual 'tested foo!', call(test_functions['method_name'], [{'var': 'foo'}]) 81 | 82 | let g:langserver_executables = g:__temp_exec 83 | 84 | Execute (Returns empty dictionary if unknown filetype): 85 | let g:__temp_exec = get(g:, 'langserver_executables', {}) 86 | 87 | let g:langserver_executables = { 88 | \ 'testing': { 89 | \ 'name': 'test/test', 90 | \ 'cmd': ['echo', '"hello"'], 91 | \ }, 92 | \ } 93 | 94 | let test_functions = langserver#default#extension_callbacks('foobar') 95 | AssertEqual {}, test_functions 96 | 97 | let g:langserver_executables = g:__temp_exec 98 | -------------------------------------------------------------------------------- /tests/test_goto.vader: -------------------------------------------------------------------------------- 1 | 2 | Given vim (Goto position): 3 | let this = 'that' 4 | 5 | " A comment here 6 | " A comment there 7 | " Here a comment, there a comment 8 | 9 | " Everywhere a comment 10 | 11 | Do (Move): 12 | 2j 13 | 14 | Execute (Call goto): 15 | let g:test_request = { 16 | \ 'result': { 17 | \ 'uri': 'file://' . expand('%'), 18 | \ 'range': { 19 | \ 'start': {'line': 0, 'character': 4}, 20 | \ 'end': {'line': 0, 'character': 8}, 21 | \ }, 22 | \ }} 23 | 24 | Log g:test_request.result.uri 25 | 26 | let cmd = langserver#api#textDocument#definition(g:test_request) 27 | AssertEqual cmd, 'norm! 1G5|' 28 | 29 | " I Would like to use these but it doesn't seem to work. 30 | " Then (delete character): 31 | " x 32 | 33 | " Expect (Line to be deleted): 34 | " let his = 'that' 35 | 36 | " " A comment here 37 | " " A comment there 38 | " " Here a comment, there a comment 39 | 40 | " " Everywhere a comment 41 | 42 | 43 | -------------------------------------------------------------------------------- /tests/test_lock.vader: -------------------------------------------------------------------------------- 1 | Execute (Test Lock): 2 | let my_lock = langserver#lock#semaphore() 3 | 4 | AssertEqual v:false, my_lock.locked 5 | 6 | call my_lock.lock(1) 7 | AssertEqual v:true, my_lock.locked 8 | 9 | call my_lock.unlock() 10 | AssertEqual v:false, my_lock.locked 11 | 12 | AssertEqual -1, my_lock.get_id() 13 | 14 | call my_lock.lock(1) 15 | AssertEqual 1, my_lock.get_id() 16 | 17 | let set_result = my_lock.lock(2) 18 | AssertEqual v:false, set_result 19 | AssertEqual 1, my_lock.get_id() 20 | 21 | call my_lock.take_lock(2) 22 | AssertEqual 2, my_lock.get_id() 23 | -------------------------------------------------------------------------------- /tests/test_message.vader: -------------------------------------------------------------------------------- 1 | # nvim -N -u NONE -c "set rtp+=~/.vim/plugged/langserver,~/.vim/plugged/vader.vim/" -c "runtime! plugin/langserver.vim" -c "Vader ~/.vim/plugged/langserver/tests/test_message.vader" 2 | -------------------------------------------------------------------------------- /tests/test_message_parse.vader: -------------------------------------------------------------------------------- 1 | 2 | Execute (Test Parsing): 3 | let test_message = 'lsp server running' 4 | let parsed = langserver#util#parse_message(test_message) 5 | AssertEqual parsed, {'type': 'info', 'data': 'lsp server running'} 6 | 7 | let test_message = 'langserver-go: reading on stdin, writing on stdout' 8 | let parsed = langserver#util#parse_message(test_message) 9 | AssertEqual parsed, {'type': 'info', 'data': 'langserver-go: reading on stdin, writing on stdout'} 10 | 11 | let test_message = '--> request #1: initialize: {"capabilities":{},"rootPath":"file:///home/tj/go/src/github.com/sourcegraph/go-langserver"}' 12 | let parsed = langserver#util#parse_message(test_message) 13 | let expected = {'type': 'request', 'data': {'initialize': {"capabilities":{}, "rootPath": "file:///home/tj/go/src/github.com/sourcegraph/go-langserver"}}} 14 | AssertEqual parsed['type'], expected['type'] 15 | AssertEqual parsed['data'], expected['data'] 16 | 17 | let test_message = '<-- result #1: initialize: {"capabilities":{"textDocumentSync":1,"hoverProvider":true,"definitionProvider":true,"referencesProvider":true,"workspaceSymbolProvider":true}}' 18 | let parsed = langserver#util#parse_message(test_message) 19 | AssertEqual parsed, {'type': 'result', 'data': {"initialize": {"capabilities":{"textDocumentSync":1,"hoverProvider": v:true,"definitionProvider": v:true,"referencesProvider": v:true,"workspaceSymbolProvider": v:true}}}} 20 | 21 | let test_message = '<-- result #2: textDocument/definition: {"textDocument":{"uri":"file:///home/tj/go/src/github.com/sourcegraph/go-langserver/langserver/util.go"},"position":{"character":10,"line":17}}' 22 | let parsed = langserver#util#parse_message(test_message) 23 | AssertEqual parsed, {'type': 'result', 'data': {'textDocument/definition': {"textDocument": {"uri": "file:///home/tj/go/src/github.com/sourcegraph/go-langserver/langserver/util.go"}, "position": {"character":10, "line":17}}}} 24 | 25 | Execute (Test message results greater than 10): 26 | let test_message = '--> request #11: textDocument/hover: {"textDocument":{"uri":"file:///home/tj/go/src/github.com/sourcegraph/go-langserver/langserver/handler.go"},"position":{"character":9,"line":44}}' 27 | let parsed = langserver#util#parse_message(test_message) 28 | AssertEqual parsed, {'type': 'request', 'data': {'textDocument/hover': {"textDocument":{"uri":"file:///home/tj/go/src/github.com/sourcegraph/go-langserver/langserver/handler.go"},"position":{"character":9,"line":44}}}} 29 | 30 | let test_message = '<-- result #11: textDocument/hover: {"contents":[{"language":"go","value":"type LangHandler struct"},{"language":"markdown","value":"LangHandler is a Go language server LSP/JSON-RPC handler. \n\n"}],"range":{"start":{"line":44,"character":9},"end":{"line":44,"character":20}}}' 31 | let parsed = langserver#util#parse_message(test_message) 32 | 33 | Execute (Test parse of references): 34 | let test_message = '<-- result #25: textDocument/references: [{"uri":"file:///home/tj/go/src/github.com/sourcegraph/go-langserver/langserver/util.go","range":{"start":{"line":7,"character":22},"end":{"line":7,"character":28}}},{"uri":"file:///home/tj/go/src/github.com/sourcegraph/go-langserver/langserver/util.go","range":{"start":{"line":9,"character":4},"end":{"line":9,"character":10}}},{"uri":"file:///home/tj/go/src/github.com/sourcegraph/go-langserver/langserver/util.go","range":{"start":{"line":9,"character":39},"end":{"line":9,"character":45}}},{"uri":"file:///home/tj/go/src/github.com/sourcegraph/go-langserver/langserver/util.go","range":{"start":{"line":10,"character":16},"end":{"line":10,"character":22}}},{"uri":"file:///home/tj/go/src/github.com/sourcegraph/go-langserver/langserver/util.go","range":{"start":{"line":12,"character":13},"end":{"line":12,"character":19}}}]' 35 | 36 | let parsed = langserver#util#parse_message(test_message) 37 | " let transformed = langserver#references#transform_reply(parsed) 38 | " Log transformed 39 | 40 | Execute (Test parse of symbols): 41 | let test_message = {'response': {'id': 6, 'result': [{'location': {'uri': 'file:///usr/local/go/src/os/path_plan9.go', 'range': {'end': {'character': 4, 'line': 7}, 'start': {'character': 0, 'line': 6}}}, 'name': 'PathSeparator', 'kind': 14, 'containerName': 'os'}, {'location': {'uri': 'file:///usr/local/go/src/os/path_unix.go', 'range': {'end': {'character': 4, 'line': 9}, 'start': {'character': 0, 'line': 8}}}, 'name': 'PathSeparator', 'kind': 14, 'containerName': 'os'}, {'location': {'uri': 'file:///usr/local/go/src/os/path_windows.go', 'range': {'end': {'character': 4, 'line': 7}, 'start': {'character': 0, 'line': 6}}}, 'name': 'PathSeparator', 'kind': 14, 'containerName': 'os'}], 'jsonrpc': '2.0'}, 'request': {'id': 6, 'jsonrpc': '2.0', 'method': 'workspace/symbol', 'params': {'query': 'PathSeparator'}}} 42 | 43 | let transformed = langserver#symbol#util#transform_reply(test_message['response']['result']) 44 | Log transformed 45 | -------------------------------------------------------------------------------- /tests/test_startup.vader: -------------------------------------------------------------------------------- 1 | " call LangSeverStart() 2 | " call langserver#initialize#request() 3 | call langserver#initialize#response( 4 | \ 'vader-test', 5 | \ { 6 | \ 'textDocumentSync': 1, 7 | \ 'definitionProvider': v:true, 8 | \ } 9 | \ ) 10 | 11 | Log "Capbilities..." 12 | Log string(g:langserver_capabilities) 13 | 14 | 15 | ================================================================================ 16 | 17 | 18 | Given vim (Set up go to): 19 | " Not the first line 20 | let g:goto_test_var = 'hello' 21 | 22 | " This is just a comment 23 | " This is another comment 24 | 25 | call string(g:goto_test_var) 26 | 27 | Do (move to the variable): 28 | /g:goto_test_var\ 29 | n 30 | 31 | Execute (Test GoTo): 32 | " This is just a test to see what we should do when getting 33 | " a response from a server 34 | 35 | " We would send something like this... 36 | " call langserver#goto#request(doc, pos) 37 | AssertEqual 7, line('.'), 'Original line' 38 | call langserver#goto#response('vader-test', { 39 | \ 'uri': 'file://' . expand('%'), 40 | \ 'range': { 41 | \ 'start': { 42 | \ 'line': 1, 43 | \ 'character': 0, 44 | \ }, 45 | \ 'end': { 46 | \ 'line': 1, 47 | \ 'character': 10, 48 | \ }, 49 | \ } 50 | \ }) 51 | norm! dd 52 | 53 | Expect (The original line to be deleted): 54 | " Not the first line 55 | 56 | " This is just a comment 57 | " This is another comment 58 | 59 | call string(g:goto_test_var) 60 | --------------------------------------------------------------------------------