Skip to content

Instantly share code, notes, and snippets.

@smjonas
Last active March 1, 2023 14:45
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save smjonas/fd4094c96db02810693909efcb0f9999 to your computer and use it in GitHub Desktop.
Save smjonas/fd4094c96db02810693909efcb0f9999 to your computer and use it in GitHub Desktop.
Incremental LSP rename command based on Neovim's command-preview feature
-- Usage: type :IncrementalRename <new_name> while your cursor is placed above an identifier (LSP must be enabled)
-- Update: this gist will no longer be updated since this command has been turned into a plugin!
-- You can find the plugin here: https://github.com/smjonas/inc-rename.nvim
local state = {
added_lines = false,
orig_lines = {},
lsp_positions = {},
}
local function incremental_rename(opts, preview_ns, preview_buf)
-- Store the lines of the buffer at the first invocation
if not state.added_lines then
-- Get positions of LSP reference symbols
local params = vim.lsp.util.make_position_params()
vim.lsp.buf_request(0, "textDocument/references", params, function(err, result, _, _)
if err then
vim.notify("Error while finding references: " .. err.message, vim.lsp.log_levels.ERROR)
return
end
if not result or vim.tbl_isempty(result) then
vim.notify("No results from textDocument/references", vim.lsp.log_levels.WARN)
return
end
state.lsp_positions = vim.tbl_map(function(x)
return x.range
end, result)
local buf = vim.api.nvim_get_current_buf()
state.orig_lines = {}
for _, position in ipairs(state.lsp_positions) do
local line_nr = position.start.line
local line = vim.api.nvim_buf_get_lines(buf, line_nr, line_nr + 1, 0)[1]
local start_col, end_col = position.start.character, position["end"].character
local line_item = { text = line, start_col = start_col, end_col = end_col }
-- Same line was already seen, this case needs to be handled separately
if state.orig_lines[line_nr] then
table.insert(state.orig_lines[line_nr], line_item)
else
state.orig_lines[line_nr] = { line_item }
end
end
state.added_lines = true
end)
return
end
for line_nr, line_items in pairs(state.orig_lines) do
local offset = 0
local updated_line = line_items[1].text
local highlight_positions = {}
for _, item in ipairs(line_items) do
updated_line = updated_line:sub(1, item.start_col + offset)
.. opts.args
.. updated_line:sub(item.end_col + 1 + offset)
table.insert(highlight_positions, {
start_col = item.start_col + offset,
end_col = item.start_col + #opts.args + offset,
})
-- Offset by the length difference between the old and new names
offset = offset + #opts.args - (item.end_col - item.start_col)
end
if preview_ns then
vim.api.nvim_buf_set_lines(0, line_nr, line_nr + 1, false, { updated_line })
if preview_buf then
vim.api.nvim_buf_set_lines(preview_buf, line_nr, line_nr + 1, false, { updated_line })
end
for _, hl_pos in ipairs(highlight_positions) do
vim.api.nvim_buf_add_highlight(
0,
preview_ns,
"Substitute",
line_nr,
hl_pos.start_col,
hl_pos.end_col
)
if preview_buf then
vim.api.nvim_buf_add_highlight(
preview_buf,
preview_ns,
"Substitute",
line_nr,
hl_pos.start_col,
hl_pos.end_col
)
end
end
end
end
if not preview_ns then
-- Now execute the actual LSP rename command
vim.lsp.buf.rename(opts.args)
state.added_lines = false
end
if preview_ns then
return 2
end
end
-- Create the user command
vim.api.nvim_create_user_command(
"IncrementalRename",
incremental_rename,
{ nargs = 1, range = "%", addr = "lines", preview = incremental_rename }
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment