snacks-file-browser
unknown
lua
19 days ago
7.7 kB
10
Indexable
---@class catdaddy.util.snacks local M = {} local uv = vim.uv or vim.loop --- Global variable to store the current working directory ---@type string M.cwd = "" --- Global variable to store the current buffer file --- This is used to track the current buffer file when opening the file browser ---@type string M.current_buf_file = "" ---Get the current working directory of the buffer ---@return string local function get_cwd_path() if package.loaded["oil"] then local oil = require("oil") if oil.get_current_dir then local oil_dir = oil.get_current_dir() if oil_dir then return vim.fn.fnamemodify(oil_dir, ":h") end end end local buf_path = vim.api.nvim_buf_get_name(0) if buf_path == "" then return uv.cwd() end return vim.fn.isdirectory(buf_path) == 1 and buf_path or vim.fn.fnamemodify(buf_path, ":h") end ---Locate a target file in the picker list and scroll to it ---@param picker snacks.Picker ---@param target_file string local function locate_file(picker, target_file) vim.schedule(function() local items_lookup = {} for idx, item in ipairs(picker.list.items) do items_lookup[item.file] = idx end local target_idx = items_lookup[target_file] if target_idx then picker.list:view(target_idx) end end) end --- Set the picker current working directory (cwd) and reload the picker --- This function is used to set the new working directory and refresh the picker. ---@param picker snacks.Picker ---@param new_cwd string function M.set_cwd(picker, new_cwd) local resolved_cwd = uv.fs_realpath(new_cwd) or new_cwd if resolved_cwd and vim.fn.isdirectory(resolved_cwd) == 1 then M.cwd = resolved_cwd picker:set_cwd(M.cwd) picker.opts.title = " File Browser ( " .. M.cwd .. " )" picker.input:set("", "") picker:find() end end ---Wait for finder and matcher tasks to complete before locating target file ---@param picker snacks.Picker ---@param target_file string local function wait_for_tasks_and_locate(picker, target_file) local finder_done = false local matcher_done = false local function check() if finder_done and matcher_done then locate_file(picker, target_file) end end if picker.finder and picker.finder.task then picker.finder.task:on("done", function() finder_done = true check() end) end if picker.matcher and picker.matcher.task then picker.matcher.task:on("done", function() matcher_done = true check() end) end end --- Open the file browser picker with specified actions and keys ---@param opts? table -- Optional configuration table function M.file_browser(opts) opts = opts or {} M.cwd = opts.cwd or get_cwd_path() M.current_buf_file = vim.fs.basename(vim.api.nvim_buf_get_name(0)) -- Configure the picker to use the actions and keys from options or defaults local picker = Snacks.picker.files({ on_show = function(self) if M.current_buf_file ~= "" then wait_for_tasks_and_locate(self, M.current_buf_file) end end, cwd = M.cwd, cmd = "fd", args = opts.args or { "--hidden", "--follow", "--max-depth=1", "--type=d", "--color=never", "-E", ".git", }, title = " File Browser ( " .. M.cwd .. " )", actions = opts.actions or { confirm = { action = function(self, selected) if not selected or selected.score == 0 then local new_path = vim.fs.joinpath(M.cwd, self:filter().pattern) self:close() vim.schedule(function() vim.cmd.edit(new_path) end) return end local file = vim.fs.joinpath(M.cwd, selected.file) if vim.fn.isdirectory(file) == 1 then M.set_cwd(self, file) else self:close() vim.schedule(function() vim.cmd.edit(file) end) end end, }, navigate_parent = { action = function(self) local parent = vim.fs.dirname(M.cwd) if parent and parent ~= M.cwd then M.set_cwd(self, parent) end end, }, change_directory = { action = function(self, selected) if selected then M.set_cwd(self, vim.fs.joinpath(M.cwd, selected.file)) vim.cmd("tcd " .. vim.fn.fnameescape(M.cwd)) end end, }, copy_path = { action = function(_, selected) if selected then local file_name = vim.fn.fnamemodify(vim.fs.joinpath(M.cwd, selected.file), ":p") -- Copies to system clipboard vim.fn.setreg("+", file_name) Snacks.notify("Copied: " .. file_name, { level = "info" }) end end, }, delete = { action = function(self, selected) if selected then local file = vim.fs.joinpath(M.cwd, selected.file) vim.ui.input({ prompt = "Delete " .. selected.file .. "? [y/N] " }, function(confirm) if confirm == "y" then local success = vim.fn.delete(file, "rf") == 0 if success then Snacks.notify("Deleted: " .. file, { level = "info" }) self:find() -- Refresh the picker else Snacks.notify("Delete failed: " .. vim.v.exception, { level = "error" }) end end end) end end, }, rename = { action = function(self, selected) if selected then local old_path = vim.fs.joinpath(M.cwd, selected.file) vim.ui.input( { prompt = "Rename " .. selected.file .. " to: ", completion = "file" }, function(new_name) if new_name and new_name ~= "" and new_name ~= selected.file then local new_path = vim.fs.joinpath(M.cwd, new_name) local success, err = os.rename(old_path, new_path) if success then Snacks.notify("Renamed to: " .. new_name, { level = "info" }) self:find() -- Refresh the picker else Snacks.notify("Rename failed: " .. err, { level = "error" }) end end end ) end end, }, new = { action = function(self) vim.ui.input({ prompt = "New file/folder: ", completion = "file" }, function(new_name) if new_name and new_name ~= "" then local new_path = vim.fs.joinpath(M.cwd, new_name) if new_name:sub(-1) == "/" then vim.fn.mkdir(new_path, "p") else local parent_dir = vim.fn.fnamemodify(new_path, ":h") vim.fn.mkdir(parent_dir, "p") local file = io.open(new_path, "w") if not file then Snacks.notify("Failed to create file: " .. new_path, { level = "error" }) return end file:close() Snacks.notify("Created: " .. new_path, { level = "info" }) end self:find() local target_file = vim.fn.fnamemodify(new_name, ":t") Snacks.notify("New file/folder: " .. target_file, { level = "info" }) wait_for_tasks_and_locate(self, target_file) end end) end, }, }, win = { input = { keys = opts.keys or { ["<BS>"] = { "navigate_parent", mode = { "n" } }, ["<M-BS>"] = { "navigate_parent", mode = { "i" } }, ["<C-d>"] = { "change_directory", mode = { "i", "n" } }, ["<M-y>"] = { "copy_path", mode = { "i" } }, ["<M-r>"] = { "rename", mode = { "i" } }, ["<M-d>"] = { "delete", mode = { "i" } }, ["<M-n>"] = { "new", mode = { "i" } }, ["y"] = { "copy_path", mode = { "n" } }, ["r"] = { "rename", mode = { "n" } }, ["d"] = { "delete", mode = { "n" } }, ["N"] = { "new", mode = { "n" } }, }, }, }, layout = opts.layout or { preview = "main", preset = "vscode", }, }) -- If the initial directory is invalid, close the picker and show an error message uv.fs_stat(M.cwd, function(err, stat) if err or not stat then vim.schedule(function() picker:close() Snacks.notify(("Invalid initial directory: %s"):format(M.cwd), { level = "error" }) end) end end) end return M
Editor is loading...
Leave a Comment