From 3ae9b3781e0b66f5874971665b38314d61668627 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Wed, 20 Nov 2019 11:34:10 -0800 Subject: Bugfix for floating_preview Don't modify your inputs. --- runtime/lua/vim/lsp/util.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index f96e0f01a8..707e2d0d3b 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -262,6 +262,9 @@ function M.open_floating_preview(contents, filetype, opts) opts = { opts, 't', true }; } + -- Don't modify our inputs. + contents = vim.deepcopy(contents) + -- Trim empty lines from the end. for i = #contents, 1, -1 do if #contents[i] == 0 then -- cgit From 2d580756ca707945c01703d404e10f4bf412a72c Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Wed, 20 Nov 2019 15:35:18 -0800 Subject: Add everything to lsp.buf and get rid of autoload. --- runtime/lua/vim/lsp/util.lua | 88 +++++++++++++++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 21 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 707e2d0d3b..13c83fefd6 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -261,32 +261,27 @@ function M.open_floating_preview(contents, filetype, opts) filetype = { filetype, 's', true }; opts = { opts, 't', true }; } - - -- Don't modify our inputs. - contents = vim.deepcopy(contents) + opts = opts or {} -- Trim empty lines from the end. - for i = #contents, 1, -1 do - if #contents[i] == 0 then - table.remove(contents) - else - break + contents = M.trim_empty_lines(contents) + + local width = opts.width + local height = opts.height or #contents + if not width then + width = 0 + for i, line in ipairs(contents) do + -- Clean up the input and add left pad. + line = " "..line:gsub("\r", "") + -- TODO(ashkan) use nvim_strdisplaywidth if/when that is introduced. + local line_width = vim.fn.strdisplaywidth(line) + width = math.max(line_width, width) + contents[i] = line end + -- Add right padding of 1 each. + width = width + 1 end - local width = 0 - local height = #contents - for i, line in ipairs(contents) do - -- Clean up the input and add left pad. - line = " "..line:gsub("\r", "") - -- TODO(ashkan) use nvim_strdisplaywidth if/when that is introduced. - local line_width = vim.fn.strdisplaywidth(line) - width = math.max(line_width, width) - contents[i] = line - end - -- Add right padding of 1 each. - width = width + 1 - local floating_bufnr = api.nvim_create_buf(false, true) if filetype then api.nvim_buf_set_option(floating_bufnr, 'filetype', filetype) @@ -556,5 +551,56 @@ function M.buf_loclist(bufnr, locations) vim.fn.setloclist(targetwin, items, ' ', 'Language Server') end +-- Remove empty lines from the beginning and end. +function M.trim_empty_lines(lines) + local result = {} + local start = 1 + for i = 1, #lines do + if #lines[i] > 0 then + start = i + break + end + end + local finish = 1 + for i = #lines, 1, -1 do + if #lines[i] > 0 then + finish = i + break + end + end + -- TODO(ashkan) use tbl_slice. + for i = start, finish do + table.insert(result, lines[i]) + end + return result +end + +-- Accepts markdown lines and tries to reduce it to a filetype if it is +-- just a single code block. +-- Note: This modifies the input. +-- +-- Returns: filetype or 'markdown' if it was unchanged. +function M.try_trim_markdown_code_blocks(lines) + local language_id = lines[1]:match("^```(.*)") + if language_id then + local has_inner_code_fence = false + for i = 2, (#lines - 1) do + local line = lines[i] + if line:sub(1,3) == '```' then + has_inner_code_fence = true + break + end + end + -- No inner code fences + starting with code fence = hooray. + if not has_inner_code_fence then + table.remove(lines, 1) + table.remove(lines) + return language_id + end + end + return 'markdown' +end + + return M -- vim:sw=2 ts=2 et -- cgit From 93beae4f31d42dc70c874020011220444d7f979c Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Wed, 20 Nov 2019 16:35:11 -0800 Subject: Fix rename support. --- runtime/lua/vim/lsp/util.lua | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 13c83fefd6..ab2c02ffcd 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -52,16 +52,12 @@ function M.text_document_apply_text_edit(text_edit, bufnr) api.nvim_buf_set_lines(bufnr, start.line, finish.line, false, new_lines) return end - api.nvim_err_writeln('apply_text_edit currently only supports character ranges starting at 0') - error('apply_text_edit currently only supports character ranges starting at 0') - return - -- TODO test and finish this support for character ranges. --- local lines = api.nvim_buf_get_lines(0, start.line, finish.line + 1, false) --- local suffix = lines[#lines]:sub(finish.character+2) --- local prefix = lines[1]:sub(start.character+2) --- new_lines[#new_lines] = new_lines[#new_lines]..suffix --- new_lines[1] = prefix..new_lines[1] --- api.nvim_buf_set_lines(0, start.line, finish.line, false, new_lines) + local lines = api.nvim_buf_get_lines(bufnr, start.line, finish.line + 1, false) + local suffix = lines[#lines]:sub(finish.character+1) + local prefix = lines[1]:sub(1, start.character) + new_lines[#new_lines] = new_lines[#new_lines]..suffix + new_lines[1] = prefix..new_lines[1] + api.nvim_buf_set_lines(bufnr, start.line, finish.line + 1, false, new_lines) end -- textDocument/completion response returns one of CompletionItem[], CompletionList or null. @@ -158,18 +154,15 @@ function M.workspace_apply_workspace_edit(workspace_edit) return end - if workspace_edit.changes == nil or #workspace_edit.changes == 0 then + local all_changes = workspace_edit.changes + if not (all_changes and not vim.tbl_isempty(all_changes)) then return end - for uri, changes in pairs(workspace_edit.changes) do - local fname = vim.uri_to_fname(uri) - -- TODO improve this approach. Try to edit open buffers without switching. - -- Not sure how to handle files which aren't open. This is deprecated - -- anyway, so I guess it could be left as is. - api.nvim_command('edit '..fname) + for uri, changes in pairs(all_changes) do + local bufnr = vim.uri_to_bufnr(uri) for _, change in ipairs(changes) do - M.text_document_apply_text_edit(change) + M.text_document_apply_text_edit(change, bufnr) end end end -- cgit From b984f613c1e8dadbe59bf0d7093a6ed12af61b37 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Wed, 20 Nov 2019 17:09:21 -0800 Subject: Extend list_extend to take start/finish. --- runtime/lua/vim/lsp/util.lua | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index ab2c02ffcd..3a2142a478 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -546,7 +546,6 @@ end -- Remove empty lines from the beginning and end. function M.trim_empty_lines(lines) - local result = {} local start = 1 for i = 1, #lines do if #lines[i] > 0 then @@ -561,11 +560,7 @@ function M.trim_empty_lines(lines) break end end - -- TODO(ashkan) use tbl_slice. - for i = start, finish do - table.insert(result, lines[i]) - end - return result + return vim.list_extend({}, lines, start, finish) end -- Accepts markdown lines and tries to reduce it to a filetype if it is -- cgit From 6fc409d5939500b038ba281ac1929fd053f35310 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Wed, 20 Nov 2019 20:51:44 -0800 Subject: Add full text_edit implementation. - Implements textDocument/formatting, textDocument/rangeFormatting, workspace/applyEdit. TODO: - still has edge cases around replacement probably. Only tested with inserts on the same position. --- runtime/lua/vim/lsp/util.lua | 134 +++++++++++++++++++++++++++++++------------ 1 file changed, 98 insertions(+), 36 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 3a2142a478..004fb81cba 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -33,6 +33,93 @@ local function resolve_bufnr(bufnr) return bufnr end +function M.apply_edit_to_lines(lines, start_pos, end_pos, new_lines) + -- 0-indexing to 1-indexing makes things look a bit worse. + local i_0 = start_pos[1] + 1 + local i_n = end_pos[1] + 1 + local n = i_n - i_0 + 1 + if not lines[i_0] or not lines[i_n] then + error(vim.inspect{#lines, i_0, i_n, n, start_pos, end_pos, new_lines}) + end + local prefix = "" + local suffix = lines[i_n]:sub(end_pos[2]+1) + lines[i_n] = lines[i_n]:sub(1, end_pos[2]+1) + if start_pos[2] > 0 then + prefix = lines[i_0]:sub(1, start_pos[2]) + -- lines[i_0] = lines[i_0]:sub(start.character+1) + end + -- TODO(ashkan) figure out how to avoid copy here. likely by changing algo. + new_lines = vim.list_extend({}, new_lines) + if #suffix > 0 then + new_lines[#new_lines] = new_lines[#new_lines]..suffix + end + if #prefix > 0 then + new_lines[1] = prefix..new_lines[1] + end + if #new_lines >= n then + for i = 1, n do + lines[i + i_0 - 1] = new_lines[i] + end + for i = n+1,#new_lines do + table.insert(lines, i_n + 1, new_lines[i]) + end + else + for i = 1, #new_lines do + lines[i + i_0 - 1] = new_lines[i] + end + for _ = #new_lines+1, n do + table.remove(lines, i_0 + #new_lines + 1) + end + end +end + +function M.apply_text_edits(text_edits, bufnr) + if not next(text_edits) then return end + -- nvim.print("Start", #text_edits) + local start_line, finish_line = math.huge, -1 + local cleaned = {} + for _, e in ipairs(text_edits) do + start_line = math.min(e.range.start.line, start_line) + finish_line = math.max(e.range["end"].line, finish_line) + table.insert(cleaned, { + A = {e.range.start.line; e.range.start.character}; + B = {e.range["end"].line; e.range["end"].character}; + lines = vim.split(e.newText, '\n', true); + }) + end + local lines = api.nvim_buf_get_lines(bufnr, start_line, finish_line + 1, false) + for i, e in ipairs(cleaned) do + -- nvim.print(i, "e", e.A, e.B, #e.lines[#e.lines], e.lines) + local y = 0 + local x = 0 + -- TODO(ashkan) this could be done in O(n) with dynamic programming + for j = 1, i-1 do + local o = cleaned[j] + -- nvim.print(i, "o", o.A, o.B, x, y, #o.lines[#o.lines], o.lines) + if o.A[1] <= e.A[1] and o.A[2] <= e.A[2] then + y = y - (o.B[1] - o.A[1] + 1) + #o.lines + -- Same line + if #o.lines > 1 then + x = -e.A[2] + #o.lines[#o.lines] + else + if o.A[1] == e.A[1] then + -- Try to account for insertions. + -- TODO how to account for deletions? + x = x - (o.B[2] - o.A[2]) + #o.lines[#o.lines] + end + end + end + end + local A = {e.A[1] + y - start_line, e.A[2] + x} + local B = {e.B[1] + y - start_line, e.B[2] + x} + -- if x ~= 0 or y ~= 0 then + -- nvim.print(i, "_", e.A, e.B, y, x, A, B, e.lines) + -- end + M.apply_edit_to_lines(lines, A, B, e.lines) + end + api.nvim_buf_set_lines(bufnr, start_line, finish_line + 1, false, lines) +end + -- local valid_windows_path_characters = "[^<>:\"/\\|?*]" -- local valid_unix_path_characters = "[^/]" -- https://github.com/davidm/lua-glob-pattern @@ -40,26 +127,6 @@ end -- function M.glob_to_regex(glob) -- end ---- Apply the TextEdit response. --- @params TextEdit [table] see https://microsoft.github.io/language-server-protocol/specification -function M.text_document_apply_text_edit(text_edit, bufnr) - bufnr = resolve_bufnr(bufnr) - local range = text_edit.range - local start = range.start - local finish = range['end'] - local new_lines = split_lines(text_edit.newText) - if start.character == 0 and finish.character == 0 then - api.nvim_buf_set_lines(bufnr, start.line, finish.line, false, new_lines) - return - end - local lines = api.nvim_buf_get_lines(bufnr, start.line, finish.line + 1, false) - local suffix = lines[#lines]:sub(finish.character+1) - local prefix = lines[1]:sub(1, start.character) - new_lines[#new_lines] = new_lines[#new_lines]..suffix - new_lines[1] = prefix..new_lines[1] - api.nvim_buf_set_lines(bufnr, start.line, finish.line + 1, false, new_lines) -end - -- textDocument/completion response returns one of CompletionItem[], CompletionList or null. -- https://microsoft.github.io/language-server-protocol/specification#textDocument_completion function M.extract_completion_items(result) @@ -74,18 +141,15 @@ end --- Apply the TextDocumentEdit response. -- @params TextDocumentEdit [table] see https://microsoft.github.io/language-server-protocol/specification -function M.text_document_apply_text_document_edit(text_document_edit, bufnr) - -- local text_document = text_document_edit.textDocument - -- TODO use text_document_version? - -- local text_document_version = text_document.version - - -- TODO technically, you could do this without doing multiple buf_get/set - -- by getting the full region (smallest line and largest line) and doing - -- the edits on the buffer, and then applying the buffer at the end. - -- I'm not sure if that's better. - for _, text_edit in ipairs(text_document_edit.edits) do - M.text_document_apply_text_edit(text_edit, bufnr) +function M.apply_text_document_edit(text_document_edit) + local text_document = text_document_edit.textDocument + local bufnr = vim.uri_to_bufnr(text_document.uri) + -- TODO(ashkan) check this is correct. + if api.nvim_buf_get_changedtick(bufnr) > text_document.version then + print("Buffer ", text_document.uri, " newer than edits.") + return end + M.apply_text_edits(text_document_edit.edits, bufnr) end function M.get_current_line_to_cursor() @@ -141,14 +205,14 @@ function M.text_document_completion_list_to_complete_items(result, line_prefix) end -- @params WorkspaceEdit [table] see https://microsoft.github.io/language-server-protocol/specification -function M.workspace_apply_workspace_edit(workspace_edit) +function M.apply_workspace_edit(workspace_edit) if workspace_edit.documentChanges then for _, change in ipairs(workspace_edit.documentChanges) do if change.kind then -- TODO(ashkan) handle CreateFile/RenameFile/DeleteFile error(string.format("Unsupported change: %q", vim.inspect(change))) else - M.text_document_apply_text_document_edit(change) + M.apply_text_document_edit(change) end end return @@ -161,9 +225,7 @@ function M.workspace_apply_workspace_edit(workspace_edit) for uri, changes in pairs(all_changes) do local bufnr = vim.uri_to_bufnr(uri) - for _, change in ipairs(changes) do - M.text_document_apply_text_edit(change, bufnr) - end + M.apply_text_edits(changes, bufnr) end end -- cgit From b7170f2d722cee24a62eb74ac836d6192e5475dc Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Wed, 20 Nov 2019 20:59:52 -0800 Subject: Remove resolve_bufnr/lualint --- runtime/lua/vim/lsp/util.lua | 7 ------- 1 file changed, 7 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 004fb81cba..dcf2c17df4 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -26,13 +26,6 @@ local function remove_prefix(prefix, word) return word:sub(prefix_length + 1) end -local function resolve_bufnr(bufnr) - if bufnr == nil or bufnr == 0 then - return api.nvim_get_current_buf() - end - return bufnr -end - function M.apply_edit_to_lines(lines, start_pos, end_pos, new_lines) -- 0-indexing to 1-indexing makes things look a bit worse. local i_0 = start_pos[1] + 1 -- cgit From bcae04f6c62b23104e85bb08c54a16d0cdc33852 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Thu, 21 Nov 2019 15:19:06 -0800 Subject: Updates - Use correct implementation of text_edits. - Send indent options to rangeFormatting and formatting. - Remove references to vim bindings and filetype from lsp.txt - Add more examples to docs. - Add before_init to allow changing initialize_params. --- runtime/lua/vim/lsp/util.lua | 110 ++++++++++++++++++++----------------------- 1 file changed, 52 insertions(+), 58 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index dcf2c17df4..66430fffc7 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -26,89 +26,83 @@ local function remove_prefix(prefix, word) return word:sub(prefix_length + 1) end -function M.apply_edit_to_lines(lines, start_pos, end_pos, new_lines) - -- 0-indexing to 1-indexing makes things look a bit worse. - local i_0 = start_pos[1] + 1 - local i_n = end_pos[1] + 1 - local n = i_n - i_0 + 1 - if not lines[i_0] or not lines[i_n] then - error(vim.inspect{#lines, i_0, i_n, n, start_pos, end_pos, new_lines}) +-- TODO(ashkan) @performance this could do less copying. +function M.set_lines(lines, A, B, new_lines) + -- 0-indexing to 1-indexing + local i_0 = A[1] + 1 + local i_n = B[1] + 1 + if not (i_0 >= 1 and i_0 <= #lines and i_n >= 1 and i_n <= #lines) then + error("Invalid range: "..vim.inspect{A = A; B = B; #lines, new_lines}) end local prefix = "" - local suffix = lines[i_n]:sub(end_pos[2]+1) - lines[i_n] = lines[i_n]:sub(1, end_pos[2]+1) - if start_pos[2] > 0 then - prefix = lines[i_0]:sub(1, start_pos[2]) - -- lines[i_0] = lines[i_0]:sub(start.character+1) - end - -- TODO(ashkan) figure out how to avoid copy here. likely by changing algo. - new_lines = vim.list_extend({}, new_lines) + local suffix = lines[i_n]:sub(B[2]+1) + if A[2] > 0 then + prefix = lines[i_0]:sub(1, A[2]) + end + new_lines = list_extend({}, new_lines) if #suffix > 0 then new_lines[#new_lines] = new_lines[#new_lines]..suffix end if #prefix > 0 then new_lines[1] = prefix..new_lines[1] end - if #new_lines >= n then - for i = 1, n do - lines[i + i_0 - 1] = new_lines[i] - end - for i = n+1,#new_lines do - table.insert(lines, i_n + 1, new_lines[i]) - end - else - for i = 1, #new_lines do - lines[i + i_0 - 1] = new_lines[i] - end - for _ = #new_lines+1, n do - table.remove(lines, i_0 + #new_lines + 1) + local result = list_extend({}, lines, 1, i_0 - 1) + list_extend(result, new_lines) + list_extend(result, lines, i_n + 1) + return result +end + +local function sort_by_key(fn) + return function(a,b) + local ka, kb = fn(a), fn(b) + assert(#ka == #kb) + for i = 1, #ka do + if ka[i] ~= kb[i] then + return ka[i] < kb[i] + end end + -- every value must have been equal here, which means it's not less than. + return false end end +local edit_sort_key = sort_by_key(function(e) + return {e.A[1], e.A[2], e.i} +end) function M.apply_text_edits(text_edits, bufnr) if not next(text_edits) then return end - -- nvim.print("Start", #text_edits) local start_line, finish_line = math.huge, -1 local cleaned = {} - for _, e in ipairs(text_edits) do + for i, e in ipairs(text_edits) do start_line = math.min(e.range.start.line, start_line) finish_line = math.max(e.range["end"].line, finish_line) + -- TODO(ashkan) sanity check ranges for overlap. table.insert(cleaned, { + i = i; A = {e.range.start.line; e.range.start.character}; B = {e.range["end"].line; e.range["end"].character}; lines = vim.split(e.newText, '\n', true); }) end + + -- Reverse sort the orders so we can apply them without interfering with + -- eachother. Also add i as a sort key to mimic a stable sort. + table.sort(cleaned, edit_sort_key) local lines = api.nvim_buf_get_lines(bufnr, start_line, finish_line + 1, false) - for i, e in ipairs(cleaned) do - -- nvim.print(i, "e", e.A, e.B, #e.lines[#e.lines], e.lines) - local y = 0 - local x = 0 - -- TODO(ashkan) this could be done in O(n) with dynamic programming - for j = 1, i-1 do - local o = cleaned[j] - -- nvim.print(i, "o", o.A, o.B, x, y, #o.lines[#o.lines], o.lines) - if o.A[1] <= e.A[1] and o.A[2] <= e.A[2] then - y = y - (o.B[1] - o.A[1] + 1) + #o.lines - -- Same line - if #o.lines > 1 then - x = -e.A[2] + #o.lines[#o.lines] - else - if o.A[1] == e.A[1] then - -- Try to account for insertions. - -- TODO how to account for deletions? - x = x - (o.B[2] - o.A[2]) + #o.lines[#o.lines] - end - end - end - end - local A = {e.A[1] + y - start_line, e.A[2] + x} - local B = {e.B[1] + y - start_line, e.B[2] + x} - -- if x ~= 0 or y ~= 0 then - -- nvim.print(i, "_", e.A, e.B, y, x, A, B, e.lines) - -- end - M.apply_edit_to_lines(lines, A, B, e.lines) + local fix_eol = api.nvim_buf_get_option(bufnr, 'fixeol') + local set_eol = fix_eol and api.nvim_buf_line_count(bufnr) == finish_line + 1 + if set_eol and #lines[#lines] ~= 0 then + table.insert(lines, '') + end + + for i = #cleaned, 1, -1 do + local e = cleaned[i] + local A = {e.A[1] - start_line, e.A[2]} + local B = {e.B[1] - start_line, e.B[2]} + lines = M.set_lines(lines, A, B, e.lines) + end + if set_eol and #lines[#lines] == 0 then + table.remove(lines) end api.nvim_buf_set_lines(bufnr, start_line, finish_line + 1, false, lines) end -- cgit From b27965538dcc3faa8e1c5b3d64a98108c18981ec Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Thu, 21 Nov 2019 15:20:32 -0800 Subject: Fix hovers staying on bufhidden --- runtime/lua/vim/lsp/util.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 66430fffc7..77f2ae6fbc 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -335,7 +335,7 @@ function M.open_floating_preview(contents, filetype, opts) end api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents) api.nvim_buf_set_option(floating_bufnr, 'modifiable', false) - api.nvim_command("autocmd CursorMoved ++once lua pcall(vim.api.nvim_win_close, "..floating_winnr..", true)") + api.nvim_command("autocmd CursorMoved,BufHidden ++once lua pcall(vim.api.nvim_win_close, "..floating_winnr..", true)") return floating_bufnr, floating_winnr end -- cgit From 43d73ee884f97861c44adab4797a8fc185af1436 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Thu, 21 Nov 2019 15:41:32 -0800 Subject: Fix position params for encoding. --- runtime/lua/vim/lsp/util.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 77f2ae6fbc..76681920bd 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -638,6 +638,17 @@ function M.try_trim_markdown_code_blocks(lines) return 'markdown' end +function M.make_position_params() + local row, col = unpack(api.nvim_win_get_cursor(0)) + row = row - 1 + local line = api.nvim_buf_get_lines(0, row, row+1, true)[1] + col = vim.str_utfindex(line, col) + return { + textDocument = { uri = vim.uri_from_bufnr(0) }; + position = { line = row; character = col; } + } +end + return M -- vim:sw=2 ts=2 et -- cgit From a3d67dac5f9de6099890ae4b9fab2b70b2279a29 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Thu, 21 Nov 2019 16:23:12 -0800 Subject: Fix encoding translation in other places. --- runtime/lua/vim/lsp/util.lua | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 76681920bd..570c4df1dd 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -649,6 +649,13 @@ function M.make_position_params() } end +-- @param buf buffer handle or 0 for current. +-- @param row 0-indexed line +-- @param col 0-indexed byte offset in line +function M.character_offset(buf, row, col) + local line = api.nvim_buf_get_lines(buf, row, row+1, true)[1] + return vim.str_utfindex(line, col) +end return M -- vim:sw=2 ts=2 et -- cgit From 78991ffbf4357ba1ad477a13991078bb4a0bdc58 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Thu, 21 Nov 2019 23:58:32 -0800 Subject: Improve performance of util.set_lines + bugfix Also permit character_offset for col past the end of line (useful in range formatting). --- runtime/lua/vim/lsp/util.lua | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 570c4df1dd..94b4223a20 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -11,6 +11,14 @@ end local list_extend = vim.list_extend +local function ok_or_nil(status, ...) + if not status then return end + return ... +end +local function npcall(fn, ...) + return ok_or_nil(pcall(fn, ...)) +end + --- Find the longest shared prefix between prefix and word. -- e.g. remove_prefix("123tes", "testing") == "ting" local function remove_prefix(prefix, word) @@ -39,17 +47,22 @@ function M.set_lines(lines, A, B, new_lines) if A[2] > 0 then prefix = lines[i_0]:sub(1, A[2]) end - new_lines = list_extend({}, new_lines) + local n = i_n - i_0 + 1 + if n ~= #new_lines then + for _ = 1, n - #new_lines do table.remove(lines, i_0) end + for _ = 1, #new_lines - n do table.insert(lines, i_0, '') end + end + for i = 1, #new_lines do + lines[i - 1 + i_0] = new_lines[i] + end if #suffix > 0 then - new_lines[#new_lines] = new_lines[#new_lines]..suffix + local i = i_0 + #new_lines - 1 + lines[i] = lines[i]..suffix end if #prefix > 0 then - new_lines[1] = prefix..new_lines[1] + lines[i_0] = prefix..lines[i_0] end - local result = list_extend({}, lines, 1, i_0 - 1) - list_extend(result, new_lines) - list_extend(result, lines, i_n + 1) - return result + return lines end local function sort_by_key(fn) @@ -99,7 +112,7 @@ function M.apply_text_edits(text_edits, bufnr) local e = cleaned[i] local A = {e.A[1] - start_line, e.A[2]} local B = {e.B[1] - start_line, e.B[2]} - lines = M.set_lines(lines, A, B, e.lines) + M.set_lines(lines, A, B, e.lines) end if set_eol and #lines[#lines] == 0 then table.remove(lines) @@ -638,11 +651,12 @@ function M.try_trim_markdown_code_blocks(lines) return 'markdown' end +local str_utfindex = vim.str_utfindex function M.make_position_params() local row, col = unpack(api.nvim_win_get_cursor(0)) row = row - 1 local line = api.nvim_buf_get_lines(0, row, row+1, true)[1] - col = vim.str_utfindex(line, col) + col = str_utfindex(line, col) return { textDocument = { uri = vim.uri_from_bufnr(0) }; position = { line = row; character = col; } @@ -654,7 +668,9 @@ end -- @param col 0-indexed byte offset in line function M.character_offset(buf, row, col) local line = api.nvim_buf_get_lines(buf, row, row+1, true)[1] - return vim.str_utfindex(line, col) + -- TODO(ashkan) is there a better way to handle col being past line length? + -- If the col is past the EOL, use the line length. + return npcall(str_utfindex, line, col) or str_utfindex(line) end return M -- cgit From 73487f4130581da72c9e838189aab39c79c177c5 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Fri, 22 Nov 2019 00:31:10 -0800 Subject: Improve the character_offset code. --- runtime/lua/vim/lsp/util.lua | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 94b4223a20..e2a8748a20 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -11,14 +11,6 @@ end local list_extend = vim.list_extend -local function ok_or_nil(status, ...) - if not status then return end - return ... -end -local function npcall(fn, ...) - return ok_or_nil(pcall(fn, ...)) -end - --- Find the longest shared prefix between prefix and word. -- e.g. remove_prefix("123tes", "testing") == "ting" local function remove_prefix(prefix, word) @@ -668,9 +660,11 @@ end -- @param col 0-indexed byte offset in line function M.character_offset(buf, row, col) local line = api.nvim_buf_get_lines(buf, row, row+1, true)[1] - -- TODO(ashkan) is there a better way to handle col being past line length? -- If the col is past the EOL, use the line length. - return npcall(str_utfindex, line, col) or str_utfindex(line) + if col > #line then + return str_utfindex(line) + end + return str_utfindex(line, col) end return M -- cgit From d410812311f7b462420690455914ea1316953b3a Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Sat, 23 Nov 2019 16:14:24 -0800 Subject: UI tweaks. - Hide diagnostics on client exit - Stop insert on popup focus. - Hide popup on insertchar (for signature_help) --- runtime/lua/vim/lsp/util.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index e2a8748a20..0c53494f02 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -104,7 +104,7 @@ function M.apply_text_edits(text_edits, bufnr) local e = cleaned[i] local A = {e.A[1] - start_line, e.A[2]} local B = {e.B[1] - start_line, e.B[2]} - M.set_lines(lines, A, B, e.lines) + lines = M.set_lines(lines, A, B, e.lines) end if set_eol and #lines[#lines] == 0 then table.remove(lines) @@ -340,7 +340,8 @@ function M.open_floating_preview(contents, filetype, opts) end api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents) api.nvim_buf_set_option(floating_bufnr, 'modifiable', false) - api.nvim_command("autocmd CursorMoved,BufHidden ++once lua pcall(vim.api.nvim_win_close, "..floating_winnr..", true)") + -- TODO make InsertCharPre disappearing optional? + api.nvim_command("autocmd CursorMoved,BufHidden,InsertCharPre ++once lua pcall(vim.api.nvim_win_close, "..floating_winnr..", true)") return floating_bufnr, floating_winnr end -- cgit From b35f6aa9dd686d082405132e3455cd0aff334361 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Sun, 24 Nov 2019 03:01:18 -0800 Subject: Add support for textDocument/references. Add set_qflist and set_loclist. - Also add locations_to_items, which calculates byte offsets for character positions in files and avoids unnecessary operations. --- runtime/lua/vim/lsp/util.lua | 80 +++++++++++++++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 19 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 0c53494f02..2dfcdfc70c 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1,4 +1,5 @@ local protocol = require 'vim.lsp.protocol' +local vim = vim local validate = vim.validate local api = vim.api @@ -573,30 +574,71 @@ do end end -function M.buf_loclist(bufnr, locations) - local targetwin - for _, winnr in ipairs(api.nvim_list_wins()) do - local winbuf = api.nvim_win_get_buf(winnr) - if winbuf == bufnr then - targetwin = winnr - break - end - end - if not targetwin then return end +local position_sort = sort_by_key(function(v) + return {v.line, v.character} +end) +-- Returns the items with the byte position calculated correctly and in sorted +-- order. +function M.locations_to_items(locations) local items = {} - local path = api.nvim_buf_get_name(bufnr) + local grouped = setmetatable({}, { + __index = function(t, k) + local v = {} + rawset(t, k, v) + return v + end; + }) for _, d in ipairs(locations) do - -- TODO: URL parsing here? local start = d.range.start - table.insert(items, { - filename = path, - lnum = start.line + 1, - col = start.character + 1, - text = d.message, - }) + local fname = assert(vim.uri_to_fname(d.uri)) + table.insert(grouped[fname], start) + end + local keys = vim.tbl_keys(grouped) + table.sort(keys) + -- TODO(ashkan) I wish we could do this lazily. + for _, fname in ipairs(keys) do + local rows = grouped[fname] + table.sort(rows, position_sort) + local i = 0 + for line in io.lines(fname) do + for _, pos in ipairs(rows) do + local row = pos.line + if i == row then + local col + if pos.character > #line then + col = #line + else + col = vim.str_byteindex(line, pos.character) + end + table.insert(items, { + filename = fname, + lnum = row + 1, + col = col + 1; + }) + end + end + i = i + 1 + end end - vim.fn.setloclist(targetwin, items, ' ', 'Language Server') + return items +end + +-- locations is Location[] +-- Only sets for the current window. +function M.set_loclist(locations) + vim.fn.setloclist(0, {}, ' ', { + title = 'Language Server'; + items = M.locations_to_items(locations); + }) +end + +-- locations is Location[] +function M.set_qflist(locations) + vim.fn.setqflist({}, ' ', { + title = 'Language Server'; + items = M.locations_to_items(locations); + }) end -- Remove empty lines from the beginning and end. -- cgit