Skip to content

Commit

Permalink
refactor(actions): common code for rename-file
Browse files Browse the repository at this point in the history
* feat: `Input_path_editor` - new class
* Deprecate private `fn()` in case it's used elsewhere
* Replace `fn()` by `rename_*()` based on `Input_path_editor` new class
* feat: new `rename_relative`, unbound by default
  • Loading branch information
hinell committed Nov 22, 2023
1 parent 8c53482 commit 386e11c
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 63 deletions.
114 changes: 56 additions & 58 deletions lua/nvim-tree/actions/fs/rename-file.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,22 @@ local lib = require "nvim-tree.lib"
local utils = require "nvim-tree.utils"
local events = require "nvim-tree.events"
local notify = require "nvim-tree.notify"
local vim_ui_utils = require "nvim-tree.nvim.ui"

local find_file = require("nvim-tree.actions.finders.find-file").fn

local M = {
config = {},
}

local ALLOWED_MODIFIERS = {
[":p"] = true,
[":p:h"] = true,
[":t"] = true,
[":t:r"] = true,
}

local function err_fmt(from, to, reason)
return string.format("Cannot rename %s -> %s: %s", from, to, reason)
end

function M.rename(node, to)
--- note: this function is used elsewhere
--- @param node table
--- @param to string path destination
function M.rename_node_to(node, to)
local notify_from = notify.render_path(node.absolute_path)
local notify_to = notify.render_path(to)

Expand All @@ -39,65 +36,66 @@ function M.rename(node, to)
events._dispatch_node_renamed(node.absolute_path, to)
end

function M.fn(default_modifier)
default_modifier = default_modifier or ":t"
--- @class fsPromptForRenameOpts: InputPathEditorOpts

return function(node, modifier)
if type(node) ~= "table" then
node = lib.get_node_at_cursor()
end
--- @param opts? fsPromptForRenameOpts
function M.prompt_for_rename(node, opts)
if type(node) ~= "table" then
node = lib.get_node_at_cursor()
end

if type(modifier) ~= "string" then
modifier = default_modifier
end
local opts_default = { absolute = true }
if type(opts) ~= "table" then
opts = opts_default
end

-- support for only specific modifiers have been implemented
if not ALLOWED_MODIFIERS[modifier] then
return notify.warn("Modifier " .. vim.inspect(modifier) .. " is not in allowed list : " .. table.concat(ALLOWED_MODIFIERS, ","))
end
node = lib.get_last_group_node(node)
if node.name == ".." then
return
end

local default_path = vim_ui_utils.Input_path_editor:new(node.absolute_path, opts)

local input_opts = {
prompt = "Rename to ",
default = default_path:prepare(),
completion = "file",
}

node = lib.get_last_group_node(node)
if node.name == ".." then
vim.ui.input(input_opts, function(new_file_path)
utils.clear_prompt()
if not new_file_path then
return
end

local namelen = node.name:len()
local directory = node.absolute_path:sub(0, namelen * -1 - 1)
local default_path
local prepend = ""
local append = ""
default_path = vim.fn.fnamemodify(node.absolute_path, modifier)
if modifier:sub(0, 2) == ":t" then
prepend = directory
end
if modifier == ":t:r" then
local extension = vim.fn.fnamemodify(node.name, ":e")
append = extension:len() == 0 and "" or "." .. extension
end
if modifier == ":p:h" then
default_path = default_path .. "/"
M.rename_node_to(node, default_path:restore(new_file_path))
if not M.config.filesystem_watchers.enable then
require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
end

local input_opts = {
prompt = "Rename to ",
default = default_path,
completion = "file",
}

vim.ui.input(input_opts, function(new_file_path)
utils.clear_prompt()
if not new_file_path then
return
end

M.rename(node, prepend .. new_file_path .. append)
if not M.config.filesystem_watchers.enable then
require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
end

find_file(utils.path_remove_trailing(new_file_path))
end)
end
find_file(utils.path_remove_trailing(new_file_path))
end)
end -- M.prompt_for_rename

function M.rename_basename(node)
return M.prompt_for_rename(node, { basename = true })
end
function M.rename_absolute(node)
return M.prompt_for_rename(node, { absolute = true })
end
function M.rename(node)
return M.prompt_for_rename(node, { filename = true })
end
function M.rename_sub(node)
return M.prompt_for_rename(node, { dirname = true })
end
function M.rename_relative(node)
return M.prompt_for_rename(node, { relative = true })
end

--- @deprecated
M.fn = function()
error("nvim-tree: method is deprecated, use rename_* instead; see nvim-tree.lua/lua/nvim-tree/actions/fs/rename-file.lua", 2)
end

function M.setup(opts)
Expand Down
11 changes: 6 additions & 5 deletions lua/nvim-tree/api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,12 @@ Api.tree.winid = wrap(require("nvim-tree.view").winid)
Api.fs.create = wrap_node_or_nil(require("nvim-tree.actions.fs.create-file").fn)
Api.fs.remove = wrap_node(require("nvim-tree.actions.fs.remove-file").fn)
Api.fs.trash = wrap_node(require("nvim-tree.actions.fs.trash").fn)
Api.fs.rename_node = wrap_node(require("nvim-tree.actions.fs.rename-file").fn ":t")
Api.fs.rename = wrap_node(require("nvim-tree.actions.fs.rename-file").fn ":t")
Api.fs.rename_sub = wrap_node(require("nvim-tree.actions.fs.rename-file").fn ":p:h")
Api.fs.rename_basename = wrap_node(require("nvim-tree.actions.fs.rename-file").fn ":t:r")
Api.fs.rename_full = wrap_node(require("nvim-tree.actions.fs.rename-file").fn ":p")
Api.fs.rename_node = wrap_node(require("nvim-tree.actions.fs.rename-file").rename)
Api.fs.rename = wrap_node(require("nvim-tree.actions.fs.rename-file").rename)
Api.fs.rename_sub = wrap_node(require("nvim-tree.actions.fs.rename-file").rename_sub)
Api.fs.rename_basename = wrap_node(require("nvim-tree.actions.fs.rename-file").rename_basename)
Api.fs.rename_relative = wrap_node(require("nvim-tree.actions.fs.rename-file").rename_relative)
Api.fs.rename_full = wrap_node(require("nvim-tree.actions.fs.rename-file").rename_absolute)
Api.fs.cut = wrap_node(require("nvim-tree.actions.fs.copy-paste").cut)
Api.fs.paste = wrap_node(require("nvim-tree.actions.fs.copy-paste").paste)
Api.fs.clear_clipboard = wrap(require("nvim-tree.actions.fs.copy-paste").clear_clipboard)
Expand Down
148 changes: 148 additions & 0 deletions lua/nvim-tree/nvim/ui.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
--- Various utility classes and functions for vim.ui.input

local M = {}

--- Options affect what part of the path_base the :prepare() returns
--- At least one field must be specified
--- @class InputPathEditorOpts
--- @field basename boolean|nil - basename of the path_base e.g. foo in foo.lua
--- @field absolute boolean|nil - absolute path: the path_base
--- @field filename boolean|nil - filename of the path_base: foo.lua
--- @field dirname boolean|nil - parent dir of the path_base
--- @field relative boolean|nil - cwd relative path

--- @class InputPathEditorInstance
--- @field constructor InputPathEditor
--- @field opts InputPathEditorOpts
--- @field prepare fun(self):string
--- @field restore fun(self, path_modified: string):string

--- Class to modify parts of the path_base and restore it later.
--- path_base is expected to be absolute
--- The :prepare() method returns a piece of original path_base; it's intended to be modified by user via `vim.ui.input({ default = prepared_path })` prompt.
--- The opts determines what part the path_base :prepare() will return.
--- The :restore(path_modified) to restores absolute :path_base with user applied modifications.
--- Usage example (uncomment, put at the end, and run :luafile %):
--- local Input_path_editor = require("nvim-tree.utils.vim-ui").Input_path_editor
--- local INPUT = vim.fn.expand "%:p"
--- local i = Input_path_editor:new(INPUT, { dirname = true })
--- local prompt = i:prepare()
--- print(prompt)
---
--- vim.ui.input({
--- prompt = "Rename path to: ",
--- default = prompt,
--- }, function(default_modified)
--- default_modified = default_modified and i:restore(default_modified) or i:restore(prompt)
--- vim.cmd "normal! :" -- clear prompt
--- local OUTPUT = default_modified
--- print(OUTPUT)
--- end)
--- @class InputPathEditor
--- @field new fun(self: InputPathEditor, path_base: string, opts?: InputPathEditorOpts): InputPathEditorInstance
--- @field prototype InputPathEditorInstance
--- @diagnostic disable-next-line: missing-fields
M.Input_path_editor = { prototype = { constructor = M.Input_path_editor } }
M.Input_path_editor._mt = {
__index = function(table, key)
if key == "constructor" then
return M.Input_path_editor
end
return table.constructor.prototype[key] or table.constructor.super and table.constructor.super.prototype[key]
end
}
M.Input_path_editor.fnamemodify = vim.fn.fnamemodify
--- Create new vim.ui.input
--- @param path string path to prepare for prompt
function M.Input_path_editor:new(path, opts)
local instance = {}
instance.constructor = self
setmetatable(instance, self._mt)

instance.path = path

local opts_default = { absolute = true }
if opts then
-- at least one opt should be set
local opts_set = false
--- @diagnostic disable-next-line: unused-local
-- luacheck: no unused args
for _, value in pairs(opts) do
if value then
opts_set = true
break
end
end
instance.opts = opts_set and opts or opts_default
else
instance.opts = opts_default
end

local fnamemodify = self.fnamemodify

-- optimizing
if instance.opts.filename or instance.opts.basename or instance.opts.dirname then
instance.path_dirname = fnamemodify(path, ":p:h") .. "/"
end

if instance.opts.basename then
instance.path_ext = fnamemodify(path, ":e")
end

if instance.opts.relative then
instance.path_relative = fnamemodify(path, ":.")
instance.path_relative_dir = path:sub(0, #path - #instance.path_relative)
end

return instance
end

--- Extract a piece of path to be modified by ui.input()
--- Put return value into ui.input({ default = <return> })
--- @return string path_prepared
function M.Input_path_editor.prototype:prepare()
local opts = self.opts
local path = self.path
local fnamemodify = self.constructor.fnamemodify
local path_prepared = path

if opts.absolute then
path_prepared = path
elseif opts.filename then
path_prepared = fnamemodify(path, ":t")
elseif opts.basename then
path_prepared = fnamemodify(path, ":t:r")
elseif opts.dirname then
path_prepared = self.path_dirname
elseif opts.relative then
path_prepared = self.path_relative
end

return path_prepared
end

--- Restore prepared path by using path_modified
--- @return string path_modified
function M.Input_path_editor.prototype:restore(path_modified)
if type(self.opts) ~= "table" then
error("you have to call :prepare(...) first", 2)
end

local opts = self.opts
local path_restored = self.path
if opts.absolute then
path_restored = path_modified
elseif opts.filename then
path_restored = self.path_dirname .. path_modified
elseif opts.basename then
path_restored = self.path_dirname .. path_modified .. "." .. self.path_ext
elseif opts.dirname then
path_restored = path_modified
elseif opts.relative then
path_restored = self.path_relative_dir .. path_modified
end

return path_restored
end

return M

0 comments on commit 386e11c

Please sign in to comment.