diff options
Diffstat (limited to 'runtime/lua/vim/lsp/util.lua')
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 247 |
1 files changed, 57 insertions, 190 deletions
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 952926b67e..a4b7b9922b 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -151,7 +151,7 @@ end --- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position --- Returns a zero-indexed column, since set_lines() does the conversion to --- 1-indexed -local function get_line_byte_from_position(bufnr, position) +local function get_line_byte_from_position(bufnr, position, offset_encoding) -- LSP's line and characters are 0-indexed -- Vim's line and columns are 1-indexed local col = position.character @@ -165,7 +165,13 @@ local function get_line_byte_from_position(bufnr, position) local line = position.line local lines = api.nvim_buf_get_lines(bufnr, line, line + 1, false) if #lines > 0 then - local ok, result = pcall(vim.str_byteindex, lines[1], col) + local ok, result + + if offset_encoding == "utf-16" or not offset_encoding then + ok, result = pcall(vim.str_byteindex, lines[1], col, true) + elseif offset_encoding == "utf-32" then + ok, result = pcall(vim.str_byteindex, lines[1], col, false) + end if ok then return result @@ -226,9 +232,10 @@ function M.get_progress_messages() table.remove(client.messages, item.idx) end - for _, item in ipairs(progress_remove) do - client.messages.progress[item.token] = nil - end + end + + for _, item in ipairs(progress_remove) do + item.client.messages.progress[item.token] = nil end return new_messages @@ -275,7 +282,8 @@ function M.apply_text_edits(text_edits, bufnr) -- Some LSP servers may return +1 range of the buffer content but nvim_buf_set_text can't accept it so we should fix it here. local has_eol_text_edit = false local max = vim.api.nvim_buf_line_count(bufnr) - local len = vim.str_utfindex(vim.api.nvim_buf_get_lines(bufnr, -2, -1, false)[1] or '') + -- TODO handle offset_encoding + local _, len = vim.str_utfindex(vim.api.nvim_buf_get_lines(bufnr, -2, -1, false)[1] or '') text_edits = vim.tbl_map(function(text_edit) if max <= text_edit.range.start.line then text_edit.range.start.line = max - 1 @@ -359,177 +367,6 @@ end -- function M.glob_to_regex(glob) -- end ----@private ---- Finds the first line and column of the difference between old and new lines ----@param old_lines table list of lines ----@param new_lines table list of lines ----@returns (int, int) start_line_idx and start_col_idx of range -local function first_difference(old_lines, new_lines, start_line_idx) - local line_count = math.min(#old_lines, #new_lines) - if line_count == 0 then return 1, 1 end - if not start_line_idx then - for i = 1, line_count do - start_line_idx = i - if old_lines[start_line_idx] ~= new_lines[start_line_idx] then - break - end - end - end - local old_line = old_lines[start_line_idx] - local new_line = new_lines[start_line_idx] - local length = math.min(#old_line, #new_line) - local start_col_idx = 1 - while start_col_idx <= length do - if string.sub(old_line, start_col_idx, start_col_idx) ~= string.sub(new_line, start_col_idx, start_col_idx) then - break - end - start_col_idx = start_col_idx + 1 - end - return start_line_idx, start_col_idx -end - - ----@private ---- Finds the last line and column of the differences between old and new lines ----@param old_lines table list of lines ----@param new_lines table list of lines ----@param start_char integer First different character idx of range ----@returns (int, int) end_line_idx and end_col_idx of range -local function last_difference(old_lines, new_lines, start_char, end_line_idx) - local line_count = math.min(#old_lines, #new_lines) - if line_count == 0 then return 0,0 end - if not end_line_idx then - end_line_idx = -1 - end - for i = end_line_idx, -line_count, -1 do - if old_lines[#old_lines + i + 1] ~= new_lines[#new_lines + i + 1] then - end_line_idx = i - break - end - end - local old_line - local new_line - if end_line_idx <= -line_count then - end_line_idx = -line_count - old_line = string.sub(old_lines[#old_lines + end_line_idx + 1], start_char) - new_line = string.sub(new_lines[#new_lines + end_line_idx + 1], start_char) - else - old_line = old_lines[#old_lines + end_line_idx + 1] - new_line = new_lines[#new_lines + end_line_idx + 1] - end - local old_line_length = #old_line - local new_line_length = #new_line - local length = math.min(old_line_length, new_line_length) - local end_col_idx = -1 - while end_col_idx >= -length do - local old_char = string.sub(old_line, old_line_length + end_col_idx + 1, old_line_length + end_col_idx + 1) - local new_char = string.sub(new_line, new_line_length + end_col_idx + 1, new_line_length + end_col_idx + 1) - if old_char ~= new_char then - break - end - end_col_idx = end_col_idx - 1 - end - return end_line_idx, end_col_idx - -end - ----@private ---- Get the text of the range defined by start and end line/column ----@param lines table list of lines ----@param start_char integer First different character idx of range ----@param end_char integer Last different character idx of range ----@param start_line integer First different line idx of range ----@param end_line integer Last different line idx of range ----@returns string text extracted from defined region -local function extract_text(lines, start_line, start_char, end_line, end_char) - if start_line == #lines + end_line + 1 then - if end_line == 0 then return '' end - local line = lines[start_line] - local length = #line + end_char - start_char - return string.sub(line, start_char, start_char + length + 1) - end - local result = string.sub(lines[start_line], start_char) .. '\n' - for line_idx = start_line + 1, #lines + end_line do - result = result .. lines[line_idx] .. '\n' - end - if end_line ~= 0 then - local line = lines[#lines + end_line + 1] - local length = #line + end_char + 1 - result = result .. string.sub(line, 1, length) - end - return result -end - ----@private ---- Compute the length of the substituted range ----@param lines table list of lines ----@param start_char integer First different character idx of range ----@param end_char integer Last different character idx of range ----@param start_line integer First different line idx of range ----@param end_line integer Last different line idx of range ----@returns (int, int) end_line_idx and end_col_idx of range -local function compute_length(lines, start_line, start_char, end_line, end_char) - local adj_end_line = #lines + end_line + 1 - local adj_end_char - if adj_end_line > #lines then - adj_end_char = end_char - 1 - else - adj_end_char = #lines[adj_end_line] + end_char - end - if start_line == adj_end_line then - return adj_end_char - start_char + 1 - end - local result = #lines[start_line] - start_char + 1 - for line = start_line + 1, adj_end_line -1 do - result = result + #lines[line] + 1 - end - result = result + adj_end_char + 1 - return result -end - ---- Returns the range table for the difference between old and new lines ----@param old_lines table list of lines ----@param new_lines table list of lines ----@param start_line_idx int line to begin search for first difference ----@param end_line_idx int line to begin search for last difference ----@param offset_encoding string encoding requested by language server ----@returns table start_line_idx and start_col_idx of range -function M.compute_diff(old_lines, new_lines, start_line_idx, end_line_idx, offset_encoding) - local start_line, start_char = first_difference(old_lines, new_lines, start_line_idx) - local end_line, end_char = last_difference(vim.list_slice(old_lines, start_line, #old_lines), - vim.list_slice(new_lines, start_line, #new_lines), start_char, end_line_idx) - local text = extract_text(new_lines, start_line, start_char, end_line, end_char) - local length = compute_length(old_lines, start_line, start_char, end_line, end_char) - - local adj_end_line = #old_lines + end_line - local adj_end_char - if end_line == 0 then - adj_end_char = 0 - else - adj_end_char = #old_lines[#old_lines + end_line + 1] + end_char + 1 - end - - local _ - if offset_encoding == "utf-16" then - _, start_char = vim.str_utfindex(old_lines[start_line], start_char - 1) - _, end_char = vim.str_utfindex(old_lines[#old_lines + end_line + 1], adj_end_char) - else - start_char = start_char - 1 - end_char = adj_end_char - end - - local result = { - range = { - start = { line = start_line - 1, character = start_char}, - ["end"] = { line = adj_end_line, character = end_char} - }, - text = text, - rangeLength = length + 1, - } - - return result -end - --- Can be used to extract the completion items from a --- `textDocument/completion` request, which may return one of --- `CompletionItem[]`, `CompletionList` or null. @@ -712,18 +549,29 @@ end -- ignoreIfExists? bool function M.rename(old_fname, new_fname, opts) opts = opts or {} - local bufnr = vim.fn.bufadd(old_fname) - vim.fn.bufload(bufnr) local target_exists = vim.loop.fs_stat(new_fname) ~= nil if target_exists and not opts.overwrite or opts.ignoreIfExists then vim.notify('Rename target already exists. Skipping rename.') return end + local oldbuf = vim.fn.bufadd(old_fname) + vim.fn.bufload(oldbuf) + + -- The there may be pending changes in the buffer + api.nvim_buf_call(oldbuf, function() + vim.cmd('w!') + end) + local ok, err = os.rename(old_fname, new_fname) assert(ok, err) - api.nvim_buf_call(bufnr, function() - vim.cmd('saveas! ' .. vim.fn.fnameescape(new_fname)) - end) + + local newbuf = vim.fn.bufadd(new_fname) + for _, win in pairs(api.nvim_list_wins()) do + if api.nvim_win_get_buf(win) == oldbuf then + api.nvim_win_set_buf(win, newbuf) + end + end + api.nvim_buf_delete(oldbuf, { force = true }) end @@ -1494,18 +1342,30 @@ do --[[ References ]] ---@param bufnr buffer id ---@param references List of `DocumentHighlight` objects to highlight ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#documentHighlight - function M.buf_highlight_references(bufnr, references) + function M.buf_highlight_references(bufnr, references, client_id) validate { bufnr = {bufnr, 'n', true} } + local client = vim.lsp.get_client_by_id(client_id) + if not client then + return + end for _, reference in ipairs(references) do - local start_pos = {reference["range"]["start"]["line"], reference["range"]["start"]["character"]} - local end_pos = {reference["range"]["end"]["line"], reference["range"]["end"]["character"]} + local start_line, start_char = reference["range"]["start"]["line"], reference["range"]["start"]["character"] + local end_line, end_char = reference["range"]["end"]["line"], reference["range"]["end"]["character"] + + local start_idx = get_line_byte_from_position(bufnr, { line = start_line, character = start_char }, client.offset_encoding) + local end_idx = get_line_byte_from_position(bufnr, { line = start_line, character = end_char }, client.offset_encoding) + local document_highlight_kind = { [protocol.DocumentHighlightKind.Text] = "LspReferenceText"; [protocol.DocumentHighlightKind.Read] = "LspReferenceRead"; [protocol.DocumentHighlightKind.Write] = "LspReferenceWrite"; } local kind = reference["kind"] or protocol.DocumentHighlightKind.Text - highlight.range(bufnr, reference_ns, document_highlight_kind[kind], start_pos, end_pos) + highlight.range(bufnr, + reference_ns, + document_highlight_kind[kind], + { start_line, start_idx }, + { end_line, end_idx }) end end end @@ -1719,7 +1579,9 @@ function M.symbols_to_items(symbols, bufnr) }) if symbol.children then for _, v in ipairs(_symbols_to_items(symbol.children, _items, _bufnr)) do - vim.list_extend(_items, v) + for _, s in ipairs(v) do + table.insert(_items, s) + end end end end @@ -1787,7 +1649,9 @@ local function make_position_param() if not line then return { line = 0; character = 0; } end - col = str_utfindex(line, col) + -- TODO handle offset_encoding + local _ + _, col = str_utfindex(line, col) return { line = row; character = col; } end @@ -1837,11 +1701,14 @@ function M.make_given_range_params(start_pos, end_pos) A[1] = A[1] - 1 B[1] = B[1] - 1 -- account for encoding. + -- TODO handle offset_encoding if A[2] > 0 then - A = {A[1], M.character_offset(0, A[1], A[2])} + local _, char = M.character_offset(0, A[1], A[2]) + A = {A[1], char} end if B[2] > 0 then - B = {B[1], M.character_offset(0, B[1], B[2])} + local _, char = M.character_offset(0, B[1], B[2]) + B = {B[1], char} end -- we need to offset the end character position otherwise we loose the last -- character of the selection, as LSP end position is exclusive |