├── plugin └── nvim-lsputils.vim ├── lua └── lsputil │ ├── symbols.lua │ ├── codeAction.lua │ ├── actions.lua │ ├── util.lua │ └── locations.lua └── README.md /plugin/nvim-lsputils.vim: -------------------------------------------------------------------------------- 1 | " built upon popfix api(https://github.com/RishabhRD/popfix) 2 | " for parameter references see popfix readme. 3 | 4 | if exists('g:loaded_lsputils') | finish | endif 5 | 6 | let s:save_cpo = &cpo 7 | set cpo&vim 8 | 9 | if ! exists('g:lsp_utils_location_opts') 10 | let g:lsp_utils_location_opts = v:null 11 | endif 12 | 13 | if ! exists('g:lsp_utils_symbols_opts') 14 | let g:lsp_utils_symbols_opts = v:null 15 | endif 16 | 17 | if ! exists('g:lsp_utils_codeaction_opts') 18 | let g:lsp_utils_codeaction_opts = v:null 19 | endif 20 | 21 | let &cpo = s:save_cpo 22 | unlet s:save_cpo 23 | 24 | let g:loaded_lsputils = 1 25 | -------------------------------------------------------------------------------- /lua/lsputil/symbols.lua: -------------------------------------------------------------------------------- 1 | -- built upon popfix api(https://github.com/RishabhRD/popfix) 2 | -- for parameter references see popfix readme. 3 | 4 | local util = require'lsputil.util' 5 | local popfix = require'popfix' 6 | local action = require'lsputil.actions' 7 | 8 | 9 | -- default keymaps provided by nvim-lsputils 10 | local keymaps = { 11 | i = { 12 | [''] = action.close_edit, 13 | [''] = action.select_next, 14 | [''] = action.select_prev, 15 | [''] = action.select_next, 16 | [''] = action.select_prev, 17 | [''] = action.close_cancelled, 18 | [''] = action.close_vert_split, 19 | [''] = action.close_split, 20 | [''] = action.close_tab, 21 | }, 22 | n = { 23 | [''] = action.close_edit, 24 | ['j'] = action.select_next, 25 | ['k'] = action.select_prev, 26 | [''] = action.select_next, 27 | [''] = action.select_prev, 28 | [''] = action.close_cancelled, 29 | ['q'] = action.close_cancelled, 30 | [''] = action.close_vert_split, 31 | [''] = action.close_split, 32 | [''] = action.close_tab, 33 | } 34 | } 35 | 36 | -- opts for popfix 37 | local function createOpts() 38 | local opts = { 39 | mode = 'split', 40 | height = 12, 41 | keymaps = keymaps, 42 | close_on_bufleave = true, 43 | callbacks = { 44 | select = action.selection_handler, 45 | close = action.close_cancelled_handler, 46 | }, 47 | list = { 48 | numbering = true 49 | }, 50 | preview = { 51 | type = 'terminal', 52 | border = true, 53 | } 54 | } 55 | util.handleGlobalVariable(vim.g.lsp_utils_symbols_opts, opts) 56 | return opts 57 | end 58 | -- callback for lsp actions that returns symbols 59 | -- (for symbols see :h lsp) 60 | local function symbol_handler(_, result, ctx, _) 61 | if action.popup then 62 | print 'Busy in some other LSP popup' 63 | return 64 | end 65 | local bufnr = ctx.bufnr 66 | if not result or vim.tbl_isempty(result) then return end 67 | local filename = vim.api.nvim_buf_get_name(bufnr) 68 | action.items = vim.lsp.util.symbols_to_items(result, bufnr) 69 | local data = {} 70 | for i, item in pairs(action.items) do 71 | data[i] = item.text 72 | if filename ~= item.filename then 73 | local cwd = vim.fn.getcwd(0)..'/' 74 | local add = util.get_relative_path(cwd, item.filename) 75 | data[i] = data[i]..' - '..add 76 | end 77 | data[i] = data[i]:gsub("\n", "") 78 | item.text = nil 79 | end 80 | local opts = createOpts() 81 | opts.data = data 82 | action.popup = popfix:new(opts) 83 | if not action.popup then 84 | action.items = nil 85 | end 86 | if action.popup.list then 87 | util.setFiletype(action.popup.list.buffer, 'lsputil_symbols_list') 88 | end 89 | if action.popup.preview then 90 | util.setFiletype(action.popup.preview.buffer, 'lsputil_symbols_preview') 91 | end 92 | if action.popup.prompt then 93 | util.setFiletype(action.popup.prompt.buffer, 'lsputil_symbols_prompt') 94 | end 95 | opts.data = nil 96 | end 97 | 98 | return{ 99 | document_handler = symbol_handler, 100 | workspace_handler = symbol_handler, 101 | keymaps = keymaps, 102 | } 103 | -------------------------------------------------------------------------------- /lua/lsputil/codeAction.lua: -------------------------------------------------------------------------------- 1 | local popfix = require'popfix' 2 | local util = require'lsputil.util' 3 | local actionModule = require'lsputil.actions' 4 | 5 | -- default keymaps provided by nvim-lsputils 6 | local function createKeymaps() 7 | return { 8 | i = { 9 | [''] = actionModule.codeaction_next, 10 | [''] = actionModule.codeaction_prev, 11 | [''] = actionModule.codeaction_fix, 12 | [''] = actionModule.select_next, 13 | [''] = actionModule.select_prev, 14 | }, 15 | n = { 16 | [''] = actionModule.codeaction_fix, 17 | [''] = actionModule.codeaction_cancel, 18 | ['q'] = actionModule.codeaction_cancel, 19 | ['j'] = actionModule.codeaction_next, 20 | ['k'] = actionModule.codeaction_prev, 21 | [''] = actionModule.select_next, 22 | [''] = actionModule.select_prev, 23 | } 24 | } 25 | end 26 | 27 | -- opts required for popfix 28 | local function createOpts() 29 | local opts = { 30 | mode = 'cursor', 31 | close_on_bufleave = true, 32 | list = { 33 | numbering = true, 34 | border = true, 35 | }, 36 | callbacks = { 37 | close = actionModule.codeaction_cancel_handler 38 | }, 39 | } 40 | util.handleGlobalVariable(vim.g.lsp_utils_codeaction_opts, opts) 41 | return opts 42 | end 43 | 44 | -- codeAction event callback handler 45 | -- use customSelectionHandler for defining custom way to handle selection 46 | local code_action_handler = function(_,actions, _, _, customSelectionHandler) 47 | if actions == nil or vim.tbl_isempty(actions) then 48 | print("No code actions available") 49 | return 50 | end 51 | if actionModule.popup then 52 | print 'Busy in other LSP popup' 53 | return 54 | end 55 | actionModule.actionBuffer = actions 56 | local data = {} 57 | for i, action in ipairs (actions) do 58 | local title = action.title:gsub('\r\n', '\\r\\n') 59 | title = title:gsub('\n','\\n') 60 | data[i] = title 61 | data[i] = data[i]:gsub("\n", "") 62 | end 63 | local width = 0 64 | for _, str in ipairs(data) do 65 | if #str > width then 66 | width = #str 67 | end 68 | end 69 | local keymaps = createKeymaps() 70 | local opts = createOpts() 71 | if not opts.prompt then 72 | for k,_ in ipairs(data) do 73 | keymaps.n[tostring(k)] = k..'G' 74 | end 75 | end 76 | util.setCustomActionMappings(keymaps, customSelectionHandler) 77 | opts.keymaps = keymaps 78 | opts.width = width + 5 79 | if opts.height == nil then 80 | opts.height = #data 81 | if opts.height > vim.api.nvim_win_get_height(0) - 4 then 82 | opts.height = vim.api.nvim_win_get_height(0) - 4 83 | local currentLine = vim.fn.line('.') 84 | local firstVisibleLine = vim.fn.line('w0') 85 | local heightDiff = currentLine - firstVisibleLine 86 | opts.height = opts.height - heightDiff - 2 87 | end 88 | end 89 | if opts.width >= vim.api.nvim_win_get_width(0) - 6 then 90 | opts.width = vim.api.nvim_win_get_width(0) - 6 91 | end 92 | opts.data = data 93 | actionModule.popup = popfix:new(opts) 94 | if not actionModule.popup then 95 | actionModule.actionBuffer = nil 96 | return 97 | end 98 | util.setFiletype(actionModule.popup.list.buffer, 'lsputil_codeaction_list') 99 | if actionModule.popup.prompt then 100 | util.setFiletype(actionModule.popup.prompt.buffer, 'lsputil_codeaction_prompt') 101 | end 102 | opts.data = nil 103 | end 104 | 105 | return{ 106 | code_action_handler = code_action_handler, 107 | } 108 | -------------------------------------------------------------------------------- /lua/lsputil/actions.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | local api = vim.api 3 | -- close handler 4 | -- jump to location in a new vertical split 5 | -- according to index and result returned by server. 6 | -- Also cleans the data structure(memory mangement) 7 | function M.close_selected_handler(index, command) 8 | M.popup = nil 9 | if index == nil then 10 | M.items = nil 11 | return 12 | end 13 | local item = M.items[index] 14 | local location = { 15 | uri = 'file://'..item.filename, 16 | range = { 17 | start = { 18 | line = item.lnum - 1, 19 | --TODO: robust character column 20 | character = item.col 21 | } 22 | } 23 | } 24 | if command == nil then 25 | elseif command == 'vsp' then 26 | vim.cmd('vsp') 27 | elseif command == 'sp' then 28 | vim.cmd('sp') 29 | elseif command == 'tab' then 30 | local buffer = api.nvim_get_current_buf() 31 | vim.cmd(string.format(":tab sb %d", buffer)) 32 | end 33 | vim.lsp.util.jump_to_location(location) 34 | vim.cmd(':normal! zz') 35 | M.items = nil 36 | end 37 | 38 | -- selection handler 39 | -- returns data to preview for index item 40 | -- according to data returned by server 41 | function M.selection_handler(index) 42 | if index == nil then return end 43 | local item = M.items[index] 44 | local startPoint = item.lnum - 3 45 | if startPoint <= 0 then 46 | startPoint = item.lnum 47 | end 48 | local cmd = string.format('bat "%s" --color=always --paging=always --plain -n --pager="less -RS" -H %s -r %s:', item.filename, item.lnum, startPoint) 49 | return { 50 | cmd = cmd 51 | } 52 | end 53 | 54 | 55 | function M.close_cancelled_handler() 56 | M.popup = nil 57 | M.items = nil 58 | end 59 | 60 | function M.close_cancelled(self) 61 | self:close(M.close_cancelled_handler) 62 | end 63 | 64 | function M.select_next(self) 65 | self:select_next(M.selection_handler) 66 | end 67 | 68 | function M.select_prev(self) 69 | self:select_prev(M.selection_handler) 70 | end 71 | 72 | function M.close_vertical_split_handler(index) 73 | M.close_selected_handler(index, 'vsp') 74 | end 75 | 76 | function M.close_vert_split(self) 77 | self:close(M.close_vertical_split_handler) 78 | end 79 | 80 | function M.close_split_handler(index) 81 | M.close_selected_handler(index, 'sp') 82 | end 83 | 84 | function M.close_split(self) 85 | self:close(M.close_split_handler) 86 | end 87 | 88 | function M.close_tab_handler(index) 89 | M.close_selected_handler(index, 'tab') 90 | end 91 | 92 | function M.close_tab(self) 93 | self:close(M.close_tab_handler) 94 | end 95 | 96 | function M.close_edit(self) 97 | self:close(M.close_selected_handler) 98 | end 99 | 100 | -- for codeactions 101 | function M.codeaction_selection_handler(index) 102 | M.popup = nil 103 | if index == nil then 104 | M.actionBuffer = nil 105 | return 106 | end 107 | local action = M.actionBuffer[index] 108 | if action.edit or type(action.command) == "table" then 109 | if action.edit then 110 | vim.lsp.util.apply_workspace_edit(action.edit) 111 | end 112 | if type(action.command) == "table" then 113 | vim.lsp.buf.execute_command(action.command) 114 | end 115 | else 116 | vim.lsp.buf.execute_command(action) 117 | end 118 | M.actionBuffer = nil 119 | end 120 | 121 | function M.customSelectionAction(customFunction) 122 | return function(popup) 123 | popup:close(function(index) 124 | M.popup = nil 125 | if index == nil then 126 | M.actionBuffer = nil 127 | return 128 | end 129 | local action = M.actionBuffer[index] 130 | customFunction(action) 131 | M.actionBuffer = nil 132 | end) 133 | end 134 | end 135 | 136 | 137 | function M.codeaction_cancel_handler() 138 | M.popup = nil 139 | M.actionBuffer = nil 140 | end 141 | 142 | function M.codeaction_fix(self) 143 | self:close(M.codeaction_selection_handler) 144 | end 145 | 146 | function M.codeaction_cancel(self) 147 | self:close(M.codeaction_cancel_handler) 148 | end 149 | 150 | function M.codeaction_next(self) 151 | self:select_next() 152 | end 153 | 154 | function M.codeaction_prev(self) 155 | self:select_prev() 156 | end 157 | 158 | return M 159 | -------------------------------------------------------------------------------- /lua/lsputil/util.lua: -------------------------------------------------------------------------------- 1 | -- retreives data form file 2 | -- and line to highlight 3 | local function get_data_from_file(filename,startLine) 4 | local displayLine; 5 | if startLine < 3 then 6 | displayLine = startLine 7 | startLine = 0 8 | else 9 | startLine = startLine - 2 10 | displayLine = 2 11 | end 12 | local uri = 'file://'..filename 13 | local bufnr = vim.uri_to_bufnr(uri) 14 | vim.fn.bufload(bufnr) 15 | local data = vim.api.nvim_buf_get_lines(bufnr, startLine, startLine+8, false) 16 | if data == nil or vim.tbl_isempty(data) then 17 | startLine = nil 18 | else 19 | local len = #data 20 | startLine = startLine+1 21 | for i = 1, len, 1 do 22 | data[i] = startLine..' '..data[i] 23 | startLine = startLine + 1 24 | end 25 | end 26 | return{ 27 | data = data, 28 | line = displayLine 29 | } 30 | end 31 | 32 | local function get_base(path) 33 | local len = #path 34 | for i = len , 1, -1 do 35 | if path:sub(i,i) == '/' then 36 | local ret = path:sub(i+1,len) 37 | return ret 38 | end 39 | end 40 | end 41 | 42 | local function getDirectores(path) 43 | local data = {} 44 | local len = #path 45 | if len <= 1 then return nil end 46 | local last_index = 1 47 | for i = 2, len do 48 | local cur_char = path:sub(i,i) 49 | if cur_char == '/' then 50 | local my_data = path:sub(last_index + 1, i - 1) 51 | table.insert(data, my_data) 52 | last_index = i 53 | end 54 | end 55 | return data 56 | end 57 | 58 | local function get_relative_path(base_path, my_path) 59 | local base_data = getDirectores(base_path) 60 | local my_data = getDirectores(my_path) 61 | local base_len = #base_data 62 | local my_len = #my_data 63 | 64 | if base_len > my_len then 65 | return my_path 66 | end 67 | 68 | if base_data[1] ~= my_data[1] then 69 | return my_path 70 | end 71 | 72 | local cur = 0 73 | for i = 1, base_len do 74 | if base_data[i] ~= my_data[i] then 75 | break 76 | end 77 | cur = i 78 | end 79 | local data = '' 80 | for i = cur+1, my_len do 81 | data = data..my_data[i]..'/' 82 | end 83 | data = data..get_base(my_path) 84 | return data 85 | end 86 | 87 | local function handleGlobalVariable(var, opts) 88 | if var == nil or var == vim.NIL then return end 89 | opts.mode = var.mode or opts.mode 90 | opts.height = var.height or opts.height 91 | if opts.height == 0 then 92 | if opts.mode == 'editor' then 93 | opts.height = nil 94 | elseif opts.mode == 'split' then 95 | opts.height = 12 96 | end 97 | end 98 | opts.width = var.width 99 | if var.keymaps then 100 | if not opts.keymaps then 101 | opts.keymaps = {} 102 | end 103 | if var.keymaps.i then 104 | if not opts.keymaps.i then 105 | opts.keymaps.i = {} 106 | end 107 | for k, v in pairs(var.keymaps.i) do 108 | opts.keymaps.i[k] = v 109 | end 110 | end 111 | if var.keymaps.n then 112 | if not opts.keymaps.n then 113 | opts.keymaps.n = {} 114 | end 115 | for k, v in pairs(var.keymaps.n) do 116 | opts.keymaps.n[k] = v 117 | end 118 | end 119 | end 120 | if var.list then 121 | if not (var.list.numbering == nil) then 122 | opts.list.numbering = var.list.numbering 123 | end 124 | if not (var.list.border == nil) then 125 | opts.list.border = var.list.border 126 | end 127 | opts.list.title = var.list.title or opts.list.title 128 | opts.list.border_chars = var.list.border_chars 129 | end 130 | if var.preview and opts.preview then 131 | if not (var.preview.numbering == nil) then 132 | opts.preview.numbering = var.preview.numbering 133 | end 134 | if not (var.preview.border == nil) then 135 | opts.preview.border = var.preview.border 136 | end 137 | opts.preview.title = var.preview.title or opts.preview.title 138 | opts.preview.border_chars = var.preview.border_chars 139 | end 140 | if var.prompt then 141 | opts.prompt = { 142 | border_chars = var.prompt.border_chars, 143 | coloring = var.prompt.coloring, 144 | prompt_text = 'Symbols', 145 | search_type = 'plain', 146 | border = true 147 | } 148 | if var.prompt.border == false or var.prompt.border == true then 149 | opts.prompt.border = var.prompt.border 150 | end 151 | end 152 | end 153 | 154 | local function setFiletype(buffer, type) 155 | vim.api.nvim_buf_set_option(buffer, 'filetype', type) 156 | end 157 | 158 | local function setCustomActionMappings(keymaps, customSelectionHandler) 159 | if customSelectionHandler then 160 | local closeWrapperFunction = require'lsputil.actions'.customSelectionAction(customSelectionHandler) 161 | keymaps.i[''] = closeWrapperFunction 162 | keymaps.n[''] = closeWrapperFunction 163 | end 164 | end 165 | 166 | 167 | return{ 168 | get_data_from_file = get_data_from_file, 169 | get_relative_path = get_relative_path, 170 | handleGlobalVariable = handleGlobalVariable, 171 | setFiletype = setFiletype, 172 | setCustomActionMappings = setCustomActionMappings 173 | } 174 | 175 | -------------------------------------------------------------------------------- /lua/lsputil/locations.lua: -------------------------------------------------------------------------------- 1 | -- built upon popfix api(https://github.com/RishabhRD/popfix) 2 | -- for parameter references see popfix readme. 3 | 4 | local popfix = require'popfix' 5 | local util = require'lsputil.util' 6 | local action = require'lsputil.actions' 7 | 8 | -- default keymaps provided by nvim-lsputils 9 | local keymaps = { 10 | i = { 11 | [''] = action.close_edit, 12 | [''] = action.select_next, 13 | [''] = action.select_prev, 14 | [''] = action.select_next, 15 | [''] = action.select_prev, 16 | [''] = action.close_cancelled, 17 | [''] = action.close_vert_split, 18 | [''] = action.close_split, 19 | [''] = action.close_tab, 20 | }, 21 | n = { 22 | [''] = action.close_edit, 23 | ['j'] = action.select_next, 24 | ['k'] = action.select_prev, 25 | [''] = action.select_next, 26 | [''] = action.select_prev, 27 | [''] = action.close_cancelled, 28 | ['q'] = action.close_cancelled, 29 | [''] = action.close_vert_split, 30 | [''] = action.close_split, 31 | [''] = action.close_tab, 32 | } 33 | } 34 | 35 | -- opts for popfix 36 | local function createOpts() 37 | local opts = { 38 | mode = 'split', 39 | height = 12, 40 | keymaps = keymaps, 41 | close_on_bufleave = true, 42 | callbacks = { 43 | select = action.selection_handler, 44 | close = action.close_cancelled_handler, 45 | }, 46 | list = { 47 | numbering = true 48 | }, 49 | preview = { 50 | type = 'terminal', 51 | border = true, 52 | } 53 | } 54 | util.handleGlobalVariable(vim.g.lsp_utils_location_opts, opts) 55 | return opts 56 | end 57 | -- callback for lsp references handler 58 | local function references_handler(_, locations, ctx, _) 59 | if locations == nil or vim.tbl_isempty(locations) then 60 | print "No references found" 61 | return 62 | end 63 | if action.popup then 64 | print 'Busy with some LSP popup' 65 | return 66 | end 67 | local bufnr = ctx.bufnr 68 | local data = {} 69 | local filename = vim.api.nvim_buf_get_name(bufnr) 70 | action.items = vim.lsp.util.locations_to_items(locations) 71 | for i, item in pairs(action.items) do 72 | data[i] = item.text 73 | if filename ~= item.filename then 74 | local cwd = vim.fn.getcwd(0)..'/' 75 | local add = util.get_relative_path(cwd, item.filename) 76 | data[i] = data[i]..' - '..add 77 | end 78 | data[i] = data[i]:gsub("\n", "") 79 | action.items.text = nil 80 | end 81 | local opts = createOpts(); 82 | opts.data = data 83 | action.popup = popfix:new(opts) 84 | if not action.popup then 85 | action.items = nil 86 | end 87 | if action.popup.list then 88 | util.setFiletype(action.popup.list.buffer, 'lsputil_locations_list') 89 | end 90 | if action.popup.preview then 91 | util.setFiletype(action.popup.preview.buffer, 'lsputil_locations_preview') 92 | end 93 | if action.popup.prompt then 94 | util.setFiletype(action.popup.prompt.buffer, 'lsputil_locations_prompt') 95 | end 96 | opts.data = nil 97 | end 98 | 99 | -- callback for lsp definition, implementation and declaration handler 100 | local definition_handler = function(_,locations, ctx, _) 101 | if locations == nil or vim.tbl_isempty(locations) then 102 | return 103 | end 104 | local bufnr = ctx.bufnr 105 | if vim.tbl_islist(locations) then 106 | if #locations > 1 then 107 | if action.popup then 108 | print 'Busy with some LSP popup' 109 | return 110 | end 111 | local data = {} 112 | local filename = vim.api.nvim_buf_get_name(bufnr) 113 | action.items = vim.lsp.util.locations_to_items(locations) 114 | for i, item in pairs(action.items) do 115 | data[i] = item.text 116 | if filename ~= item.filename then 117 | local cwd = vim.fn.getcwd(0)..'/' 118 | local add = util.get_relative_path(cwd, item.filename) 119 | data[i] = data[i]..' - '..add 120 | end 121 | data[i] = data[i]:gsub("\n", "") 122 | item.text = nil 123 | end 124 | local opts = createOpts(); 125 | opts.data = data 126 | action.popup = popfix:new(opts) 127 | if not action.popup then 128 | action.items = nil 129 | end 130 | if action.popup.list then 131 | util.setFiletype(action.popup.list.buffer, 'lsputil_locations_list') 132 | end 133 | if action.popup.preview then 134 | util.setFiletype(action.popup.preview.buffer, 'lsputil_locations_preview') 135 | end 136 | if action.popup.prompt then 137 | util.setFiletype(action.popup.prompt.buffer, 'lsputil_locations_prompt') 138 | end 139 | opts.data = nil 140 | else 141 | vim.lsp.util.jump_to_location(locations[1]) 142 | end 143 | else 144 | vim.lsp.util.jump_to_location(locations) 145 | end 146 | end 147 | 148 | return{ 149 | references_handler = references_handler, 150 | definition_handler = definition_handler, 151 | declaration_handler = definition_handler, 152 | typeDefinition_handler = definition_handler, 153 | implementation_handler = definition_handler, 154 | keymaps = keymaps, 155 | 156 | } 157 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nvim-lsputils 2 | 3 | Neovim built-in LSP client implementation is so lightweight and awesome. 4 | However, default settings for actions like go-to-definition, code-quickfix, etc 5 | may not seem user friendly for many users. But neovim LSP client is highly 6 | extensible with lua. This plugin focuses on making such LSP actions highly user 7 | friendly. 8 | 9 | ## Features 10 | 11 | - Floating popup for code actions 12 | - Preview window for references 13 | - Preview window for definition, declaration, type-definition, implementation 14 | - Preview window for document symbol and workspace symbol 15 | - Fuzzy finding of symbols, references, defintion and codeActions [optional] 16 | - , , opens tabs, vertical split and horizontal split for 17 | location and symbol actions 18 | 19 | ## Demo 20 | 21 | ### Code Action: 22 | ![](https://user-images.githubusercontent.com/26287448/98861852-f7b7bd00-248b-11eb-8c11-fd37ab5d45d7.gif) 23 | 24 | 25 | ### References: 26 | ![](https://user-images.githubusercontent.com/26287448/98860407-9ee72500-2489-11eb-8b61-4ca7cb3f78ee.gif) 27 | 28 | ### Custom theming (See below for more details) 29 | ![](https://user-images.githubusercontent.com/26287448/98858320-6abe3500-2486-11eb-8795-a8d1bb8dbecc.gif) 30 | 31 | ### Fuzzy search: 32 | ![](https://user-images.githubusercontent.com/26287448/100378731-abe34700-3039-11eb-98e6-151007b49d5a.gif) 33 | 34 | 35 | ## Future goals 36 | 37 | - LspSearch command to search symbol over workspace and list it in a window. 38 | 39 | **Fuzzy finding feature is optional for the time being. This is because 40 | fuzzy engine of popfix is still in developement. However, it usually doesn't 41 | crash and work as expected. For using it read custom options section. 42 | For the code snippet read sample theming section.** 43 | 44 | ## Prerequisite 45 | 46 | - Neovim nightly 47 | 48 | ## Installation 49 | 50 | This plugin utilizes RishabhRD/popfix plugin for managing underlying popups 51 | and previews. 52 | It can be installed with any plugin manager. For example with vim-plug: 53 | 54 | Plug 'RishabhRD/popfix' 55 | Plug 'RishabhRD/nvim-lsputils' 56 | 57 | ## Setup 58 | 59 | Add following to init.vim lua chunk as: 60 | 61 | lua < |Applies the codeaction | 117 | |\ |Quits the menu without applying codeaction | 118 | |q |Quits the menu without applying codeaction | 119 | |j |Selects next item | 120 | |k |Selects previous item | 121 | |\|Selects next item | 122 | |\ |Selects previous item | 123 | 124 | 125 | **In insert mode(Fuzzy search mode):** 126 | 127 | |Key |Action | 128 | |:------:|-------------------------------------------| 129 | |\ |Applies the codeaction | 130 | |\ |Quits the menu without applying codeaction | 131 | |\ |Selects next item | 132 | |\ |Selects previous item | 133 | |\|Selects next item | 134 | |\ |Selects previous item | 135 | 136 | ### For symbols, references, and defintions 137 | 138 | (document symbols + workspace symbols + references + defintions) 139 | 140 | **In normal mode:** 141 | 142 | |Key |Action | 143 | |:------:|-------------------------------------------| 144 | |\ |Jump to location in same window | 145 | |\ |Jump to location in a vertical split | 146 | |\ |Jump to location in a horizontal split | 147 | |\ |Jump to location in a new tab | 148 | |\ |Quits the menu without jumping to location | 149 | |q |Quits the menu without jumping to location | 150 | |j |Selects next item | 151 | |k |Selects previous item | 152 | |\|Selects next item | 153 | |\ |Selects previous item | 154 | 155 | **In insert mode(Fuzzy search mode):** 156 | 157 | |Key |Action | 158 | |:------:|-------------------------------------------| 159 | |\ |Jump to location in same window | 160 | |\ |Jump to location in a vertical split | 161 | |\ |Jump to location in a horizontal split | 162 | |\ |Jump to location in a new tab | 163 | |\ |Quits the menu without jumping to location | 164 | |\ |Selects next item | 165 | |\ |Selects previous item | 166 | |\|Selects next item | 167 | |\ |Selects previous item | 168 | 169 | ## Custom Filetypes 170 | 171 | nvim-lsputils export some custom filetypes for their created buffer. 172 | This enables users to do customization for nvim-lsputil buffers. 173 | 174 | Custom filetypes are: 175 | 176 | - For codeaction: 177 | 178 | - lsputil_codeaction_list (Represents codeaction list) 179 | - lsputil_codeaction_prompt (Represents codeaction prompt) (If enabled) 180 | 181 | - For symbols (workspace and document symbols): 182 | 183 | - lsputil_symbols_list (Represents symbols list) 184 | - lsputil_symbols_preview (Represents symbols preview) 185 | - lsputil_symbols_prompt (Represents symbols prompt) 186 | 187 | - For locations (definition, declaration, references, implementation): 188 | 189 | - lsputil_locations_list (Represents locations list) 190 | - lsputil_locations_preview (Represents locations preview) 191 | - lsputil_locations_prompt (Represents locations prompt) 192 | 193 | ## Custom Options 194 | 195 | **NOTE: EACH attribute of custom opts is optional. If not provided, a suitable 196 | default is used in place of it.** 197 | 198 | nvim-lsputils provides 3 global variables: 199 | 200 | - lsp_utils_location_opts 201 | - lsp_utils_symbols_opts 202 | - lsp_utils_codeaction_opts 203 | 204 | These 3 variables are supposed to have vimscript dictionary values (Lua tables) 205 | 206 | lsp_utils_location_opts defines options for: 207 | 208 | - definition handler 209 | - references handler 210 | - declaration handler 211 | - implementation handler 212 | - type_definition hander 213 | 214 | lsp_utils_symbols_opts defines options for: 215 | 216 | - workspace symbol handler 217 | - files symbols handler 218 | 219 | lsp_utils_codeaction_opts defines options for: 220 | 221 | - code_action handler 222 | 223 | lsp_utils_location_opts and lsp_utils_symbols_opts takes following key-value pairs: 224 | 225 | - height (integer) (Defines height of window) 226 | if value is 0 then a suitable default height is provided. (Specially for 227 | editor mode) 228 | - width (integer) (Defines width of window) 229 | - mode (string) 230 | - split (for split previews (default)) 231 | - editor (for floating previews) 232 | - list (vimscript dictionary / Lua tables) Accepts following key/value pairs: 233 | - border (boolean) (borders in floating mode) 234 | - numbering (boolean) (vim window numbering active or not) 235 | - title (boolean) (title for window) 236 | - border_chars (vimscript dictionary/ Lua table) (border characters for list) 237 | Sample border_chars example: 238 | ``` 239 | border_chars = { 240 | TOP_LEFT = '┌', 241 | TOP_RIGHT = '┐', 242 | MID_HORIZONTAL = '─', 243 | MID_VERTICAL = '│', 244 | BOTTOM_LEFT = '└', 245 | BOTTOM_RIGHT = '┘', 246 | } 247 | ``` 248 | If any of shown key of border_chars is missing then a space character 249 | is used instead of it. 250 | - preview (vimscript dictionary / Lua tables) Accepts following key/value pairs: 251 | - border (boolean) (borders in floating mode) 252 | - numbering (boolean) (vim window numbering active or not) 253 | - title (string) (title for window) 254 | - border_chars (vimscript dictionary/ Lua table) (border characters for preview window) 255 | Sample border_chars example: 256 | ``` 257 | border_chars = { 258 | TOP_LEFT = '┌', 259 | TOP_RIGHT = '┐', 260 | MID_HORIZONTAL = '─', 261 | MID_VERTICAL = '│', 262 | BOTTOM_LEFT = '└', 263 | BOTTOM_RIGHT = '┘', 264 | } 265 | If any of shown key of border_chars is missing then a space character 266 | is used instead of it. 267 | - keymaps (vimscript dictionary / Lua tables) Additional keymaps. 268 | See https://github.com/RishabhRD/popfix to read about keymaps documentation. 269 | 270 | lsp_utils_codeaction_opts takes following key-value pairs: 271 | 272 | - height (integer) (Defines height of window) 273 | if value is 0 then a suitable default height is provided. (Specially for 274 | editor mode) 275 | - width (integer) (Defines width of window) 276 | - mode (string) 277 | - split (for split previews (default)) 278 | - editor (for floating previews) 279 | - list (vimscript dictionary / Lua tables) Accepts following key/value pairs: 280 | - border (boolean) (borders in floating mode) 281 | - numbering (boolean) (vim window numbering active or not) 282 | - title (string) (title for window) 283 | - border_chars (vimscript dictionary/ Lua table) (border characters for list) 284 | Sample border_chars example: 285 | ``` 286 | border_chars = { 287 | TOP_LEFT = '┌', 288 | TOP_RIGHT = '┐', 289 | MID_HORIZONTAL = '─', 290 | MID_VERTICAL = '│', 291 | BOTTOM_LEFT = '└', 292 | BOTTOM_RIGHT = '┘', 293 | } 294 | ``` 295 | If any of shown key of border_chars is missing then a space character 296 | is used instead of it. 297 | - prompt (table) (optional and may break) 298 | - border (boolean) (borders in floating mode) 299 | - numbering (boolean) (vim window numbering active or not) 300 | - border_chars (vimscript dictionary/ Lua table) (border characters for list) 301 | Sample border_chars example: 302 | ``` 303 | border_chars = { 304 | TOP_LEFT = '┌', 305 | TOP_RIGHT = '┐', 306 | MID_HORIZONTAL = '─', 307 | MID_VERTICAL = '│', 308 | BOTTOM_LEFT = '└', 309 | BOTTOM_RIGHT = '┘', 310 | } 311 | ``` 312 | If any of shown key of border_chars is missing then a space character 313 | is used instead of it. 314 | 315 | See https://github.com/RishabhRD/popfix for more documentation of options. 316 | 317 | These options helps to get better theme that suits your need. 318 | 319 | ### Sample themeing with lua 320 | 321 | ``` 322 | local border_chars = { 323 | TOP_LEFT = '┌', 324 | TOP_RIGHT = '┐', 325 | MID_HORIZONTAL = '─', 326 | MID_VERTICAL = '│', 327 | BOTTOM_LEFT = '└', 328 | BOTTOM_RIGHT = '┘', 329 | } 330 | vim.g.lsp_utils_location_opts = { 331 | height = 24, 332 | mode = 'editor', 333 | preview = { 334 | title = 'Location Preview', 335 | border = true, 336 | border_chars = border_chars 337 | }, 338 | keymaps = { 339 | n = { 340 | [''] = 'j', 341 | [''] = 'k', 342 | } 343 | } 344 | } 345 | vim.g.lsp_utils_symbols_opts = { 346 | height = 24, 347 | mode = 'editor', 348 | preview = { 349 | title = 'Symbols Preview', 350 | border = true, 351 | border_chars = border_chars 352 | }, 353 | prompt = {}, 354 | } 355 | ``` 356 | 357 | **Symbols would have fuzzy find features with these configuration** 358 | 359 | ## Advanced configuration 360 | 361 | nvim-lsputils provides some extension in handler function definition so that 362 | it can be integrated with some other lsp plugins easily. 363 | 364 | Currently codeaction supports this extended defintion. Codeaction handler signature 365 | is something like: 366 | 367 | ```lua 368 | code_action_handler(_, _, actions, _, _, _, customSelectionHandler) 369 | ``` 370 | 371 | ``customSelectionHandler`` is not defined by standard docs. However, nvim-lsputils 372 | provide it for easy extension and use with other plugins. ``customSelectionHandler`` is expected to be a function 373 | that accepts the selection action(from all codeactions) as parameter. If provided to the function, 374 | the function executes this customSelectionHandler with selected action instead 375 | of applying codeaction directly. 376 | 377 | A simple ``customSelectionHandler`` can look like: 378 | ```lua 379 | local function customSelectionHandler(selectedAction) 380 | print("Action selected: ", selectedAction) 381 | end 382 | ``` 383 | 384 | 385 | One simple example is integration with [nvim-jdtls](https://github.com/mfussenegger/nvim-jdtls). 386 | 387 | ```lua 388 | local jdtls_ui = require'jdtls.ui' 389 | function jdtls_ui.pick_one_async(items, _, _, cb) 390 | require'lsputil.codeAction'.code_action_handler(nil, nil, items, nil, nil, nil, cb) 391 | end 392 | ``` 393 | 394 | This code snippet modifies the nvim-jdtls UI to make use of nvim-lsputils UI. 395 | With this code snippet, nvim-lsputils would provide the UI but the action would 396 | be decided by the functin parameter cb. 397 | --------------------------------------------------------------------------------