aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/lsp/util.lua
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim/lsp/util.lua')
-rw-r--r--runtime/lua/vim/lsp/util.lua78
1 files changed, 50 insertions, 28 deletions
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 60d0f0cc83..f8e5b6a90d 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -675,12 +675,23 @@ local function get_bufs_with_prefix(prefix)
return buffers
end
+local function escape_gsub_repl(s)
+ return (s:gsub('%%', '%%%%'))
+end
+
--- @class vim.lsp.util.rename.Opts
--- @inlinedoc
--- @field overwrite? boolean
--- @field ignoreIfExists? boolean
--- Rename old_fname to new_fname
+---
+--- Existing buffers are renamed as well, while maintaining their bufnr.
+---
+--- It deletes existing buffers that conflict with the renamed file name only when
+--- * `opts` requests overwriting; or
+--- * the conflicting buffers are not loaded, so that deleting thme does not result in data loss.
+---
--- @param old_fname string
--- @param new_fname string
--- @param opts? vim.lsp.util.rename.Opts Options:
@@ -700,24 +711,36 @@ function M.rename(old_fname, new_fname, opts)
return
end
- local oldbufs = {}
- local win = nil
-
- if vim.fn.isdirectory(old_fname_full) == 1 then
- oldbufs = get_bufs_with_prefix(old_fname_full)
- else
- local oldbuf = vim.fn.bufadd(old_fname_full)
- table.insert(oldbufs, oldbuf)
- win = vim.fn.win_findbuf(oldbuf)[1]
- end
-
- for _, b in ipairs(oldbufs) do
- -- There may be pending changes in the buffer
- if api.nvim_buf_is_loaded(b) then
- api.nvim_buf_call(b, function()
- vim.cmd('update!')
- end)
+ local buf_rename = {} ---@type table<integer, {from: string, to: string}>
+ local old_fname_pat = '^' .. vim.pesc(old_fname_full)
+ for b in
+ vim.iter(get_bufs_with_prefix(old_fname_full)):filter(function(b)
+ -- No need to care about unloaded or nofile buffers. Also :saveas won't work for them.
+ return api.nvim_buf_is_loaded(b)
+ and not vim.list_contains({ 'nofile', 'nowrite' }, vim.bo[b].buftype)
+ end)
+ do
+ -- Renaming a buffer may conflict with another buffer that happens to have the same name. In
+ -- most cases, this would have been already detected by the file conflict check above, but the
+ -- conflicting buffer may not be associated with a file. For example, 'buftype' can be "nofile"
+ -- or "nowrite", or the buffer can be a normal buffer but has not been written to the file yet.
+ -- Renaming should fail in such cases to avoid losing the contents of the conflicting buffer.
+ local old_bname = vim.api.nvim_buf_get_name(b)
+ local new_bname = old_bname:gsub(old_fname_pat, escape_gsub_repl(new_fname))
+ if vim.fn.bufexists(new_bname) == 1 then
+ local existing_buf = vim.fn.bufnr(new_bname)
+ if api.nvim_buf_is_loaded(existing_buf) and skip then
+ vim.notify(
+ new_bname .. ' already exists in the buffer list. Skipping rename.',
+ vim.log.levels.ERROR
+ )
+ return
+ end
+ -- no need to preserve if such a buffer is empty
+ api.nvim_buf_delete(existing_buf, {})
end
+
+ buf_rename[b] = { from = old_bname, to = new_bname }
end
local newdir = assert(vim.fs.dirname(new_fname))
@@ -733,17 +756,16 @@ function M.rename(old_fname, new_fname, opts)
os.rename(old_undofile, new_undofile)
end
- if vim.fn.isdirectory(new_fname) == 0 then
- local newbuf = vim.fn.bufadd(new_fname)
- if win then
- vim.fn.bufload(newbuf)
- vim.bo[newbuf].buflisted = true
- api.nvim_win_set_buf(win, newbuf)
- end
- end
-
- for _, b in ipairs(oldbufs) do
- api.nvim_buf_delete(b, {})
+ for b, rename in pairs(buf_rename) do
+ -- Rename with :saveas. This does two things:
+ -- * Unset BF_WRITE_MASK, so that users don't get E13 when they do :write.
+ -- * Send didClose and didOpen via textDocument/didSave handler.
+ api.nvim_buf_call(b, function()
+ vim.cmd('keepalt saveas! ' .. vim.fn.fnameescape(rename.to))
+ end)
+ -- Delete the new buffer with the old name created by :saveas. nvim_buf_delete and
+ -- :bwipeout are futile because the buffer will be added again somewhere else.
+ vim.cmd('bdelete! ' .. vim.fn.bufnr(rename.from))
end
end