From 68992ee24486b830afc759e524d99a31c0730c09 Mon Sep 17 00:00:00 2001 From: uanela Date: Tue, 16 Jun 2026 23:49:46 +0200 Subject: [PATCH] feat: add rename with substitue command --- lua/nvim-tree/actions/fs/rename-file.lua | 29 +++++++++++++-- lua/nvim-tree/autocmd.lua | 46 ++++++++++++++++++++++++ lua/nvim-tree/utils.lua | 14 +++++--- 3 files changed, 83 insertions(+), 6 deletions(-) diff --git a/lua/nvim-tree/actions/fs/rename-file.lua b/lua/nvim-tree/actions/fs/rename-file.lua index 3bf50fd149d..e10dea5f75b 100644 --- a/lua/nvim-tree/actions/fs/rename-file.lua +++ b/lua/nvim-tree/actions/fs/rename-file.lua @@ -3,6 +3,7 @@ local utils = require("nvim-tree.utils") local events = require("nvim-tree.events") local notify = require("nvim-tree.notify") local config = require("nvim-tree.config") +local lib = require("nvim-tree.lib") local find_file = require("nvim-tree.actions.finders.find-file").fn @@ -47,7 +48,9 @@ end ---@param node Node ---@param to string -function M.rename(node, to) +---@param opts? { notify?: boolean } +function M.rename(node, to, opts) + opts = opts and opts or {} local notify_from = notify.render_path(node.absolute_path) local notify_to = notify.render_path(to) @@ -92,7 +95,9 @@ function M.rename(node, to) end if not is_error then - notify.info(string.format("%s -> %s", notify_from, notify_to)) + if opts.notify ~= false then + notify.info(string.format("%s -> %s", notify_from, notify_to)) + end utils.rename_loaded_buffers(node.absolute_path, to) events._dispatch_node_renamed(node.absolute_path, to) end @@ -192,4 +197,24 @@ function M.rename_full(node) prompt_to_rename(node, ":p") end +---@param nodes Node[] +---@param old_part string +---@param new_part string +function M.bulk_rename(nodes, old_part, new_part) + local prompt_select = string.format("Substitute '%s' for '%s' in %s files?", old_part, new_part, #nodes) + + lib.prompt(prompt_select .. " Y/n: ", prompt_select, ({ "", "y" }), { "No", "Yes" }, "nvimtree_bulk_rename", function(answer) + utils.clear_prompt() + if answer == "n" then return end + + for _, node in ipairs(nodes) do + local new_name = node.name:gsub(old_part, new_part) + local new_full_path = vim.fn.fnamemodify(node.absolute_path, ":h") .. "/" .. new_name + M.rename(node, new_full_path, { notify = false }) + end + + notify.info(string.format("%s nodes substituted from %s -> %s", #nodes, old_part, new_part)) + end) +end + return M diff --git a/lua/nvim-tree/autocmd.lua b/lua/nvim-tree/autocmd.lua index 2056927ffc0..f7647cb3b79 100644 --- a/lua/nvim-tree/autocmd.lua +++ b/lua/nvim-tree/autocmd.lua @@ -140,6 +140,52 @@ function M.global() end, }) end + + vim.api.nvim_create_autocmd("CmdlineLeave", { + group = augroup_id, + pattern = ":", + callback = function(ev) + if require("nvim-tree.view").get_bufnr() == ev.buf then + local cmd = vim.fn.getcmdline() + + if cmd:match("s([^%w])") then + local delimiter = cmd:match("s([^%w])") + local escaped_delim = delimiter:gsub("[%%%.%+%-%*%?%^%$%(%)%[%]]", "%%%1") + + -- Looks for: s + local pattern = "s" .. escaped_delim .. "([^" .. escaped_delim .. "]*)" .. escaped_delim .. "([^" .. escaped_delim .. "]*)" + + local old_part, new_part = cmd:match(pattern) + + local core = require("nvim-tree.core") + local explorer = core.get_explorer() + local utils = require("nvim-tree.utils") + local bulk_rename = require("nvim-tree.actions.fs.rename-file").bulk_rename + + local visual_marker = "'<,'>" + local nodes = cmd:sub(1, #visual_marker) == visual_marker and + utils.get_visual_nodes({ use_native = true, should_exit = false }) or + explorer and explorer:get_nodes_by_line(core.get_nodes_starting_line()) or {} + local matching_nodes = {} + + for i = #nodes, 1, -1 do + local node = nodes[i] + if node and node.name:find(old_part) ~= nil then + table.insert(matching_nodes, node) + end + end + + if #matching_nodes > 0 then + bulk_rename(matching_nodes, old_part, new_part) + else + require("nvim-tree.notify").notify(string.format("Not matching nodes with '%s'", old_part)) + end + + vim.fn.setcmdline("") + end + end + end + }) end return M diff --git a/lua/nvim-tree/utils.lua b/lua/nvim-tree/utils.lua index c1941dba87d..9232bc23f1e 100644 --- a/lua/nvim-tree/utils.lua +++ b/lua/nvim-tree/utils.lua @@ -527,19 +527,25 @@ function M.exit_visual_mode() end ---Get the visual selection range nodes, exiting visual mode. +---@param opts? { should_exit?: boolean, use_native?: boolean } ---@return Node[]? -function M.get_visual_nodes() +function M.get_visual_nodes(opts) + opts = opts and opts or {} + local explorer = require("nvim-tree.core").get_explorer() if not explorer then return nil end - local start_line = vim.fn.line("v") - local end_line = vim.fn.line(".") + local start_line = vim.fn.line(opts.use_native == true and "'<" or "v") + local end_line = vim.fn.line(opts.use_native == true and "'>" or ".") if start_line > end_line then start_line, end_line = end_line, start_line end local nodes = explorer:get_nodes_in_range(start_line, end_line) - M.exit_visual_mode() + + if opts.should_exit ~= false then + M.exit_visual_mode() + end return nodes end