├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── LICENSE ├── README.md ├── autoload └── deoplete │ ├── lsp.vim │ └── lsp │ ├── handler.vim │ └── init.vim ├── lua ├── candidates.lua ├── hover.lua └── util.lua ├── plugin └── deoplete │ └── lsp.vim └── rplugin └── python3 └── deoplete └── source └── lsp.py /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Warning: I will close the issue without the minimal init.vim and the reproduction instructions.** 2 | 3 | # Problems summary 4 | 5 | 6 | ## Expected 7 | 8 | 9 | ## Environment Information 10 | 11 | * deoplete and deoplete-lsp version (SHA1): 12 | 13 | * OS: 14 | 15 | * neovim/Vim `:version` output: 16 | 17 | * `:checkhealth` or `:CheckHealth` result(neovim only): 18 | 19 | ## Provide a minimal init.vim/vimrc with less than 50 lines (Required!) 20 | 21 | ```vim 22 | " Your minimal init.vim/vimrc 23 | set runtimepath+=~/path/to/deoplete.nvim/ 24 | set runtimepath+=~/path/to/deoplete-lsp/ 25 | let g:deoplete#enable_at_startup = 1 26 | 27 | " For Vim only 28 | "set runtimepath+=~/path/to/nvim-yarp/ 29 | "set runtimepath+=~/path/to/vim-hug-neovim-rpc/ 30 | ``` 31 | 32 | 33 | ## How to reproduce the problem from neovim/Vim startup (Required!) 34 | 35 | 1. foo 36 | 2. bar 37 | 3. baz 38 | 39 | 40 | ## Generate a logfile if appropriate 41 | 42 | 1. export NVIM_PYTHON_LOG_FILE=/tmp/log 43 | 2. export NVIM_PYTHON_LOG_LEVEL=DEBUG 44 | 3. nvim -u minimal.vimrc 45 | 4. some works 46 | 5. cat /tmp/log_{PID} 47 | 48 | 49 | ## Screenshot (if possible) 50 | 51 | 52 | ## Upload the log file 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | doc/tags 3 | vim-themis 4 | .cache 5 | .mypy_cache 6 | tags 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | License: MIT license 2 | AUTHOR: Shougo Matsushita 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included 13 | in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 16 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 19 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 20 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 21 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LSP Completion source for deoplete 2 | 3 | ## Install 4 | 5 | - Install LSP enabled [neovim](https://github.com/neovim/neovim)(version 0.5.0+) 6 | 7 | - Install the latest [deoplete](https://github.com/Shougo/deoplete.nvim) 8 | 9 | - Install and configure [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig) 10 | 11 | ## Configuration 12 | 13 | - `g:deoplete#lsp#handler_enabled`: If you set it to v:true, you can disable 14 | hover handler. 15 | 16 | - `g:deoplete#lsp#use_icons_for_candidates`: Set to v:true to enable icons for 17 | LSP candidates. Requires patched font: https://www.nerdfonts.com/ 18 | -------------------------------------------------------------------------------- /autoload/deoplete/lsp.vim: -------------------------------------------------------------------------------- 1 | "============================================================================= 2 | " FILE: lsp.vim 3 | " License: MIT license 4 | "============================================================================= 5 | 6 | function! deoplete#lsp#enable() abort 7 | if has('vim_starting') 8 | augroup deoplete#lsp 9 | autocmd! 10 | autocmd VimEnter * call deoplete#lsp#enable() 11 | augroup END 12 | 13 | return 1 14 | endif 15 | 16 | if deoplete#lsp#init#_is_handler_enabled() 17 | return 1 18 | endif 19 | 20 | return deoplete#lsp#init#_enable_handler() 21 | endfunction 22 | -------------------------------------------------------------------------------- /autoload/deoplete/lsp/handler.vim: -------------------------------------------------------------------------------- 1 | "============================================================================= 2 | " FILE: handler.vim 3 | " License: MIT license 4 | "============================================================================= 5 | 6 | function! deoplete#lsp#handler#_init() abort 7 | augroup deoplete#lsp 8 | autocmd! 9 | autocmd InsertEnter * lua require'hover'.insert_enter_handler() 10 | autocmd InsertLeave * lua require'hover'.insert_leave_handler() 11 | augroup END 12 | endfunction 13 | -------------------------------------------------------------------------------- /autoload/deoplete/lsp/init.vim: -------------------------------------------------------------------------------- 1 | "============================================================================= 2 | " FILE: init.vim 3 | " License: MIT license 4 | "============================================================================= 5 | 6 | if !exists('g:deoplete#lsp#handler_enabled') 7 | let g:deoplete#lsp#handler_enabled = v:false 8 | endif 9 | 10 | if !exists('s:is_handler_enabled') 11 | let s:is_handler_enabled = v:false 12 | endif 13 | 14 | function! deoplete#lsp#init#_is_handler_enabled() abort 15 | return s:is_handler_enabled 16 | endfunction 17 | 18 | function! deoplete#lsp#init#_enable_handler() abort 19 | if !g:deoplete#lsp#handler_enabled 20 | return 21 | endif 22 | 23 | call deoplete#lsp#handler#_init() 24 | let s:is_handler_enabled = v:true 25 | endfunction 26 | -------------------------------------------------------------------------------- /lua/candidates.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -------------------------------------------------------------------------------- 3 | -- File: candidates.lua 4 | -------------------------------------------------------------------------------- 5 | -- 6 | 7 | local api = vim.api 8 | 9 | local get_candidates = function(_, arg1, arg2) 10 | -- For neovim 0.5.1/0.6 breaking changes 11 | -- https://github.com/neovim/neovim/pull/15504 12 | local result = ((vim.fn.has('nvim-0.6') == 1 or vim.fn.has('nvim-0.5.1')) 13 | and type(arg1) == 'table' and arg1 or arg2) 14 | if not result or result == 0 then 15 | return 16 | end 17 | 18 | local success = (type(result) == 'table' and not vim.tbl_isempty(result) 19 | ) and true or false 20 | result = result['items'] ~= nil and result['items'] or result 21 | 22 | if #result > 0 then 23 | api.nvim_set_var('deoplete#source#lsp#_results', result) 24 | api.nvim_set_var('deoplete#source#lsp#_success', success) 25 | api.nvim_set_var('deoplete#source#lsp#_requested', true) 26 | api.nvim_call_function('deoplete#auto_complete', {}) 27 | end 28 | end 29 | 30 | local request_candidates = function(arguments) 31 | vim.lsp.buf_request(0, 'textDocument/completion', arguments, get_candidates) 32 | end 33 | 34 | return { 35 | request_candidates = request_candidates 36 | } 37 | -------------------------------------------------------------------------------- /lua/hover.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -------------------------------------------------------------------------------- 3 | -- File: hover.lua 4 | -------------------------------------------------------------------------------- 5 | -- 6 | 7 | local vim = vim 8 | local api = vim.api 9 | local feature = 'textDocument/hover' 10 | local default_response_handler = vim.lsp.handlers[feature] 11 | 12 | local hover_initialise = { 13 | buffer_changes = 0, 14 | complete_item = nil, 15 | complete_item_index = -1, 16 | insert_mode = false, 17 | window = nil 18 | } 19 | 20 | local hover = hover_initialise 21 | local util = require 'util' 22 | 23 | local complete_visible = function() 24 | return vim.fn.pumvisible() ~= 0 25 | end 26 | 27 | local get_markdown_lines = function(result) 28 | local markdown_lines = vim.lsp.util.convert_input_to_markdown_lines(result.contents) 29 | 30 | return vim.lsp.util.trim_empty_lines(markdown_lines) 31 | end 32 | 33 | local get_window_alignment = function(complete_columns, screen_columns) 34 | if complete_columns < screen_columns / 2 then 35 | alignment = 'right' 36 | else 37 | alignment = 'left' 38 | end 39 | 40 | return alignment 41 | end 42 | 43 | local create_window = function(method, result) 44 | return util.focusable_float(method, function() 45 | local markdown_lines = get_markdown_lines(result) 46 | if vim.tbl_isempty(markdown_lines) then return end 47 | 48 | local complete_display_info = vim.fn.pum_getpos() 49 | local alignment = get_window_alignment(complete_display_info['col'], api.nvim_get_option('columns')) 50 | 51 | local hover_buffer, hover_window 52 | 53 | hover_buffer, hover_window = util.fancy_floating_markdown(markdown_lines, { 54 | pad_left = 1; pad_right = 1; 55 | col = complete_display_info['col']; width = complete_display_info['width']; row = vim.fn.winline(); 56 | align = alignment; 57 | }) 58 | 59 | hover.window = hover_window 60 | 61 | if hover_window ~= nil and api.nvim_win_is_valid(hover_window) then 62 | vim.lsp.util.close_preview_autocmd({"CursorMoved", "BufHidden", "InsertCharPre"}, hover_window) 63 | end 64 | 65 | return hover_buffer, hover_window 66 | end) 67 | end 68 | 69 | local handle_response = function(_, method, result) 70 | if complete_visible() == false then return default_response_handler(_, method, result, _) end 71 | if not (result and result.contents) then return end 72 | 73 | return create_window(method, result) 74 | end 75 | 76 | local set_response_handler = function() 77 | for _, client in pairs(vim.lsp.buf_get_clients(0)) do 78 | local handlers = client.config and client.config.handlers 79 | if handlers then 80 | if handlers[feature] == handle_response then 81 | break 82 | end 83 | handlers[feature] = handle_response 84 | end 85 | end 86 | end 87 | 88 | local decode_user_data = function(user_data) 89 | if user_data == nil or (user_data ~= nil and #user_data == 0) then return end 90 | 91 | return vim.fn.json_decode(user_data) 92 | end 93 | 94 | local client_with_hover = function() 95 | for _, client in pairs(vim.lsp.buf_get_clients(0)) do 96 | if client.server_capabilities.hoverProvider == false then return false end 97 | end 98 | 99 | return true 100 | end 101 | 102 | local buffer_changed = function() 103 | buffer_changes = api.nvim_buf_get_changedtick(0) 104 | if hover.buffer_changes == buffer_changes then return false end 105 | 106 | hover.buffer_changes = buffer_changes 107 | 108 | return hover.buffer_changes 109 | end 110 | 111 | local close_window = function() 112 | if hover.window == nil or not api.nvim_win_is_valid(hover.window) then return end 113 | 114 | api.nvim_win_close(hover.window, true) 115 | end 116 | 117 | local get_complete_item = function() 118 | local complete_info = api.nvim_call_function('complete_info', {{ 'eval', 'selected', 'items', 'user_data' }}) 119 | if complete_info['selected'] == -1 or complete_info['selected'] == hover.complete_item_index then return false end 120 | 121 | hover.complete_item_index = complete_info['selected'] 122 | 123 | return complete_info['items'][hover.complete_item_index + 1] 124 | end 125 | 126 | local request_hover = function() 127 | local complete_item = get_complete_item() 128 | if not complete_visible() or not buffer_changed() or not complete_item then return end 129 | 130 | close_window() 131 | 132 | if not client_with_hover() then return end 133 | 134 | local decoded_user_data = decode_user_data(complete_item['user_data']) 135 | if decoded_user_data == nil then return end 136 | 137 | set_response_handler() 138 | 139 | return vim.lsp.buf_request(api.nvim_get_current_buf(), 'textDocument/hover', util.make_position_params()) 140 | end 141 | 142 | local insert_enter_handler = function() 143 | hover.insert_mode = true 144 | local timer = vim.loop.new_timer() 145 | 146 | timer:start(100, 80, vim.schedule_wrap(function() 147 | request_hover() 148 | 149 | if hover.insert_mode == false and timer:is_closing() == false then 150 | timer:stop() 151 | timer:close() 152 | end 153 | end)) 154 | end 155 | 156 | local insert_leave_handler = function() 157 | hover.insert_mode = false 158 | end 159 | 160 | return { 161 | insert_enter_handler = insert_enter_handler, 162 | insert_leave_handler = insert_leave_handler 163 | } 164 | -------------------------------------------------------------------------------- /lua/util.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -------------------------------------------------------------------------------- 3 | -- File: util.lua 4 | -------------------------------------------------------------------------------- 5 | -- 6 | -- https://github.com/neovim/neovim/blob/master/runtime/lua/vim/lsp/util.lua 7 | -- 8 | -- Don't like some conventions here but minimal modifications will make easier to compare with original 9 | 10 | local vim = vim 11 | local validate = vim.validate 12 | local api = vim.api 13 | 14 | local M = {} 15 | 16 | local function ok_or_nil(status, ...) 17 | if not status then return end 18 | return ... 19 | end 20 | local function npcall(fn, ...) 21 | return ok_or_nil(pcall(fn, ...)) 22 | end 23 | 24 | function M.make_floating_popup_options(width, height, opts) 25 | validate { 26 | opts = { opts, 't', true }; 27 | } 28 | opts = opts or {} 29 | validate { 30 | ["opts.offset_x"] = { opts.offset_x, 'n', true }; 31 | ["opts.offset_y"] = { opts.offset_y, 'n', true }; 32 | } 33 | 34 | local lines_above = vim.fn.winline() - 1 35 | local lines_below = vim.fn.winheight(0) - lines_above 36 | 37 | local col 38 | 39 | if lines_above < lines_below then 40 | height = math.min(lines_below, height) 41 | else 42 | height = math.min(lines_above, height) 43 | end 44 | 45 | if opts.align == 'right' then 46 | col = opts.col + opts.width 47 | else 48 | col = opts.col - width - 1 49 | end 50 | 51 | return { 52 | col = col, 53 | height = height, 54 | relative = 'editor', 55 | row = opts.row, 56 | focusable = false, 57 | style = 'minimal', 58 | width = width, 59 | } 60 | end 61 | 62 | local function find_window_by_var(name, value) 63 | for _, win in ipairs(api.nvim_list_wins()) do 64 | if npcall(api.nvim_win_get_var, win, name) == value then 65 | return win 66 | end 67 | end 68 | end 69 | 70 | function M.focusable_float(unique_name, fn) 71 | if npcall(api.nvim_win_get_var, 0, unique_name) then 72 | return api.nvim_command("wincmd p") 73 | end 74 | 75 | local bufnr = api.nvim_get_current_buf() 76 | 77 | do 78 | local win = find_window_by_var(unique_name, bufnr) 79 | if win then 80 | api.nvim_win_close(win, true) 81 | end 82 | end 83 | 84 | local pbufnr, pwinnr = fn() 85 | 86 | if pbufnr then 87 | api.nvim_win_set_var(pwinnr, unique_name, bufnr) 88 | return pbufnr, pwinnr 89 | end 90 | end 91 | 92 | -- Convert markdown into syntax highlighted regions by stripping the code 93 | -- blocks and converting them into highlighted code. 94 | -- This will by default insert a blank line separator after those code block 95 | -- regions to improve readability. 96 | function M.fancy_floating_markdown(contents, opts) 97 | local pad_left = opts and opts.pad_left 98 | local pad_right = opts and opts.pad_right 99 | local stripped = {} 100 | local highlights = {} 101 | 102 | local max_width 103 | if opts.align == 'right' then 104 | local columns = api.nvim_get_option('columns') 105 | max_width = columns - opts.col - opts.width 106 | else 107 | max_width = opts.col - 1 108 | end 109 | 110 | -- Didn't have time to investigate but this fixes popup offset by one display issue 111 | max_width = max_width - pad_left - pad_right 112 | 113 | do 114 | local i = 1 115 | while i <= #contents do 116 | local line = contents[i] 117 | local ft = line:match("^```([a-zA-Z0-9_]*)$") 118 | if ft then 119 | local start = #stripped 120 | i = i + 1 121 | while i <= #contents do 122 | line = contents[i] 123 | if line == "```" then 124 | i = i + 1 125 | break 126 | end 127 | if #line > max_width then 128 | while #line > max_width do 129 | local trimmed_line = string.sub(line, 1, max_width) 130 | local index = trimmed_line:reverse():find(" ") 131 | if index == nil or index > #trimmed_line/2 then 132 | break 133 | else 134 | table.insert(stripped, string.sub(line, 1, max_width-index)) 135 | line = string.sub(line, max_width-index+2, #line) 136 | end 137 | end 138 | table.insert(stripped, line) 139 | else 140 | table.insert(stripped, line) 141 | end 142 | i = i + 1 143 | end 144 | table.insert(highlights, { 145 | ft = ft; 146 | start = start + 1; 147 | finish = #stripped + 1 - 1 148 | }) 149 | else 150 | if #line > max_width then 151 | while #line > max_width do 152 | local trimmed_line = string.sub(line, 1, max_width) 153 | -- local index = math.max(trimmed_line:reverse():find(" "), trimmed_line:reverse():find("/")) 154 | local index = trimmed_line:reverse():find(" ") 155 | if index == nil or index > #trimmed_line/2 then 156 | break 157 | else 158 | table.insert(stripped, string.sub(line, 1, max_width-index)) 159 | line = string.sub(line, max_width-index+2, #line) 160 | end 161 | end 162 | table.insert(stripped, line) 163 | else 164 | table.insert(stripped, line) 165 | end 166 | i = i + 1 167 | end 168 | end 169 | end 170 | 171 | local width = 0 172 | for i, v in ipairs(stripped) do 173 | v = v:gsub("\r", "") 174 | if pad_left then v = (" "):rep(pad_left)..v end 175 | if pad_right then v = v..(" "):rep(pad_right) end 176 | stripped[i] = v 177 | width = math.max(width, #v) 178 | end 179 | 180 | if opts.align == 'right' then 181 | local columns = api.nvim_get_option('columns') 182 | if opts.col + opts.row + width > columns then 183 | width = columns - opts.col - opts.width - 1 184 | end 185 | else 186 | if width > opts.col then 187 | width = opts.col - 1 188 | end 189 | end 190 | 191 | local insert_separator = true 192 | if insert_separator then 193 | for i, h in ipairs(highlights) do 194 | h.start = h.start + i - 1 195 | h.finish = h.finish + i - 1 196 | if h.finish + 1 <= #stripped then 197 | table.insert(stripped, h.finish + 1, string.rep("─", width)) 198 | end 199 | end 200 | end 201 | 202 | 203 | -- Make the floating window. 204 | local height = #stripped 205 | local bufnr = api.nvim_create_buf(false, true) 206 | local winnr 207 | if vim.g.completion_docked_hover == 1 then 208 | if height > vim.g.completion_docked_maximum_size then 209 | height = vim.g.completion_docked_maximum_size 210 | elseif height < vim.g.completion_docked_minimum_size then 211 | height = vim.g.completion_docked_minimum_size 212 | end 213 | local row 214 | if vim.fn.winline() > api.nvim_get_option('lines')/2 then 215 | row = 0 216 | else 217 | row = api.nvim_get_option('lines') - height 218 | end 219 | winnr = api.nvim_open_win(bufnr, false, { 220 | col = 0, 221 | height = height, 222 | relative = 'editor', 223 | row = row, 224 | focusable = true, 225 | style = 'minimal', 226 | width = api.nvim_get_option('columns'), 227 | }) 228 | else 229 | local opt = M.make_floating_popup_options(width, height, opts) 230 | if opt.width <= 0 then return end 231 | winnr = api.nvim_open_win(bufnr, false, opt) 232 | end 233 | vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, stripped) 234 | 235 | local cwin = vim.api.nvim_get_current_win() 236 | vim.api.nvim_set_current_win(winnr) 237 | 238 | vim.cmd("ownsyntax markdown") 239 | local idx = 1 240 | local function highlight_region(ft, start, finish) 241 | if ft == '' then return end 242 | local name = ft..idx 243 | idx = idx + 1 244 | local lang = "@"..ft:upper() 245 | -- TODO(ashkan): better validation before this. 246 | if not pcall(vim.cmd, string.format("syntax include %s syntax/%s.vim", lang, ft)) then 247 | return 248 | end 249 | vim.cmd(string.format("syntax region %s start=+\\%%%dl+ end=+\\%%%dl+ contains=%s", name, start, finish + 1, lang)) 250 | end 251 | for _, h in ipairs(highlights) do 252 | highlight_region(h.ft, h.start, h.finish) 253 | end 254 | 255 | vim.api.nvim_set_current_win(cwin) 256 | return bufnr, winnr 257 | end 258 | 259 | local str_utfindex = vim.str_utfindex 260 | local function make_position_param() 261 | local row, col = unpack(api.nvim_win_get_cursor(0)) 262 | row = row - 1 263 | local line = api.nvim_buf_get_lines(0, row, row+1, true)[1] 264 | col = str_utfindex(line, col) 265 | return { line = row; character = col; } 266 | end 267 | 268 | function M.make_position_params() 269 | return { 270 | textDocument = M.make_text_document_params(); 271 | position = make_position_param() 272 | } 273 | end 274 | 275 | function M.make_text_document_params() 276 | return { uri = vim.uri_from_bufnr(0) } 277 | end 278 | 279 | return M 280 | -------------------------------------------------------------------------------- /plugin/deoplete/lsp.vim: -------------------------------------------------------------------------------- 1 | "============================================================================= 2 | " FILE: lsp.vim 3 | " License: MIT license 4 | "============================================================================= 5 | 6 | if exists('g:loaded_deoplete_lsp') 7 | finish 8 | endif 9 | 10 | let g:loaded_deoplete_lsp = 1 11 | 12 | " Global options definition. 13 | if get(g:, 'deoplete#enable_at_startup', 0) 14 | call deoplete#lsp#enable() 15 | endif 16 | 17 | let g:completion_docked_hover = get( 18 | \ g:, 'completion_docked_hover', 0) 19 | let g:completion_docked_maximum_size = get( 20 | \ g:, 'completion_docked_maximum_size', 10) 21 | let g:completion_docked_minimum_size = get( 22 | \ g:, 'completion_docked_minimum_size', 3) 23 | -------------------------------------------------------------------------------- /rplugin/python3/deoplete/source/lsp.py: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # FILE: lsp.py 3 | # AUTHOR: Shougo Matsushita 4 | # ============================================================================= 5 | 6 | import json 7 | import re 8 | 9 | from deoplete.source.base import Base 10 | 11 | 12 | LSP_KINDS = [ 13 | 'Text', 14 | 'Method', 15 | 'Function', 16 | 'Constructor', 17 | 'Field', 18 | 'Variable', 19 | 'Class', 20 | 'Interface', 21 | 'Module', 22 | 'Property', 23 | 'Unit', 24 | 'Value', 25 | 'Enum', 26 | 'Keyword', 27 | 'Snippet', 28 | 'Color', 29 | 'File', 30 | 'Reference', 31 | 'Folder', 32 | 'EnumMember', 33 | 'Constant', 34 | 'Struct', 35 | 'Event', 36 | 'Operator', 37 | 'TypeParameter', 38 | ] 39 | 40 | LSP_KINDS_WITH_ICONS = [ 41 | ' [text] ', 42 | ' [method] ', 43 | ' [function] ', 44 | ' [constructor]', 45 | 'ﰠ [field] ', 46 | '𝒙 [variable] ', 47 | ' [class] ', 48 | ' [interface]', 49 | ' [module] ', 50 | ' [property] ', 51 | ' [unit] ', 52 | ' [value] ', 53 | ' [enum] ', 54 | ' [key] ', 55 | '﬌ [snippet] ', 56 | ' [color] ', 57 | ' [file] ', 58 | ' [refrence] ', 59 | ' [folder] ', 60 | ' [enumMember]', 61 | ' [constant] ', 62 | ' [struct] ', 63 | ' [event] ', 64 | ' [operator] ', 65 | ' [typeParameter]', 66 | ] 67 | 68 | 69 | class Source(Base): 70 | def __init__(self, vim): 71 | Base.__init__(self, vim) 72 | 73 | self.name = 'lsp' 74 | self.mark = '[lsp]' 75 | self.rank = 500 76 | self.input_pattern = r'(\.|:|->)$' 77 | self.is_volatile = True 78 | self.vars = {} 79 | self.vim.vars['deoplete#source#lsp#_results'] = [] 80 | self.vim.vars['deoplete#source#lsp#_success'] = False 81 | self.vim.vars['deoplete#source#lsp#_requested'] = False 82 | self.vim.vars['deoplete#source#lsp#_prev_input'] = '' 83 | if 'deoplete#lsp#use_icons_for_candidates' not in self.vim.vars: 84 | self.vim.vars['deoplete#lsp#use_icons_for_candidates'] = False 85 | 86 | self.lsp_kinds = LSP_KINDS 87 | 88 | def gather_candidates(self, context): 89 | if not self.vim.call('has', 'nvim-0.5.0'): 90 | return [] 91 | 92 | prev_input = self.vim.vars['deoplete#source#lsp#_prev_input'] 93 | if context['input'] == prev_input and self.vim.vars[ 94 | 'deoplete#source#lsp#_requested']: 95 | return self.process_candidates() 96 | 97 | vars = self.vim.vars 98 | vars['deoplete#source#lsp#_requested'] = False 99 | vars['deoplete#source#lsp#_prev_input'] = context['input'] 100 | vars['deoplete#source#lsp#_complete_position'] = context[ 101 | 'complete_position'] 102 | 103 | # Note: request_candidates() may be failed 104 | try: 105 | params = self.vim.call( 106 | 'luaeval', 107 | 'vim.lsp.util.make_position_params()') 108 | 109 | self.vim.call( 110 | 'luaeval', 'require("candidates").request_candidates(' 111 | '_A.arguments)', 112 | {'arguments': params}) 113 | except Exception: 114 | pass 115 | return [] 116 | 117 | def process_candidates(self): 118 | candidates = [] 119 | vars = self.vim.vars 120 | results = vars['deoplete#source#lsp#_results'] 121 | 122 | if not results: 123 | return 124 | elif isinstance(results, dict): 125 | if 'items' not in results: 126 | self.print_error( 127 | 'LSP results does not have "items" key:{}'.format( 128 | str(results))) 129 | return 130 | items = results['items'] 131 | else: 132 | items = results 133 | 134 | use_icons = vars['deoplete#lsp#use_icons_for_candidates'] 135 | if use_icons: 136 | self.lsp_kinds = LSP_KINDS_WITH_ICONS 137 | 138 | for rec in items: 139 | if 'textEdit' in rec and rec['textEdit'] is not None: 140 | textEdit = rec['textEdit'] 141 | if ('range' in textEdit and textEdit['range']['start'] == 142 | textEdit['range']['end']): 143 | previous_input = vars['deoplete#source#lsp#_prev_input'] 144 | complete_position = vars[ 145 | 'deoplete#source#lsp#_complete_position'] 146 | new_text = textEdit['newText'] 147 | word = f'{previous_input[complete_position:]}{new_text}' 148 | else: 149 | word = textEdit['newText'] 150 | elif rec.get('insertText', ''): 151 | if rec.get('insertTextFormat', 1) != 1: 152 | word = rec.get('entryName', rec.get('label')) 153 | else: 154 | word = rec['insertText'] 155 | else: 156 | word = rec.get('entryName', rec.get('label')) 157 | 158 | # Remove parentheses from word. 159 | # Note: some LSP includes snippet parentheses in word(newText) 160 | word = re.sub(r'[\(|<].*[\)|>](\$\d+)?', '', word) 161 | 162 | item = { 163 | 'word': word, 164 | 'abbr': rec['label'], 165 | 'dup': 0, 166 | 'user_data': json.dumps({ 167 | 'lspitem': rec 168 | }) 169 | } 170 | 171 | if isinstance(rec.get('kind'), int): 172 | item['kind'] = self.lsp_kinds[rec['kind'] - 1] 173 | elif rec.get('insertTextFormat') == 2: 174 | item['kind'] = 'Snippet' 175 | 176 | if rec.get('detail'): 177 | item['menu'] = rec['detail'] 178 | 179 | if isinstance(rec.get('documentation'), str): 180 | item['info'] = rec['documentation'] 181 | elif (isinstance(rec.get('documentation'), dict) and 182 | 'value' in rec['documentation']): 183 | item['info'] = rec['documentation']['value'] 184 | 185 | candidates.append(item) 186 | 187 | return candidates 188 | --------------------------------------------------------------------------------