From 2eedac5134cff515979d715f004fd4c3f1b035c0 Mon Sep 17 00:00:00 2001 From: Prad Nukala Date: Sat, 11 Jul 2026 18:46:49 -0400 Subject: [PATCH] refactor(plugins): replace neo-tree with yazi as the file manager --- lua/plugins/astrocore.lua | 6 +- lua/plugins/neotree.lua | 355 -------------------------------------- lua/plugins/user.lua | 60 +++---- lua/plugins/yazi.lua | 59 +++++++ 4 files changed, 85 insertions(+), 395 deletions(-) delete mode 100644 lua/plugins/neotree.lua create mode 100644 lua/plugins/yazi.lua diff --git a/lua/plugins/astrocore.lua b/lua/plugins/astrocore.lua index 19ca867..45eabc8 100644 --- a/lua/plugins/astrocore.lua +++ b/lua/plugins/astrocore.lua @@ -3,9 +3,6 @@ --- @module plugins.astrocore local ignore_patterns = require "config.ignore_patterns" -local terminals = require "config.terminals" - -terminals.setup() local file_filter = ignore_patterns.create_picker_filter() @@ -140,7 +137,7 @@ return { mappings = { n = { [""] = { "suspend", desc = "Suspend nvim (return with 'fg')" }, - [""] = { "wqa", desc = "Save all buffers and quit Neovim" }, + ["ZZ"] = { "wqa", desc = "Save all buffers and quit Neovim" }, [""] = { "wqa", desc = "Save all buffers and quit Neovim" }, [""] = { "wabd", desc = "Save and close buffer" }, ["F"] = { "za", desc = "Toggle fold under cursor" }, @@ -298,7 +295,6 @@ return { end, desc = "Paste from system clipboard", }, - [""] = { function() terminals.scratch_toggle() end, desc = "Open Scratch Terminal" }, ["d"] = { function() with_trouble("diagnostics") end, desc = "Search diagnostics ( sends to Trouble)", diff --git a/lua/plugins/neotree.lua b/lua/plugins/neotree.lua deleted file mode 100644 index c9e468a..0000000 --- a/lua/plugins/neotree.lua +++ /dev/null @@ -1,355 +0,0 @@ ---- Neo-tree configuration with dynamic pattern filtering ---- @module plugins.neotree - --- Import shared modules -local ignore_patterns = require "config.ignore_patterns" -local terminals = require "config.terminals" - ----@type LazySpec -return { - { - "neo-tree.nvim", - init = function() - vim.api.nvim_create_autocmd("DirChanged", { - group = vim.api.nvim_create_augroup("NeoTreeRefreshPatterns", { clear = true }), - callback = function() - local ok, manager = pcall(require, "neo-tree.sources.manager") - if ok then pcall(manager.refresh, "filesystem") end - end, - }) - end, - opts = function() - -- Get all hide patterns from shared module - local hide_patterns = ignore_patterns.get_all_patterns() - - return { - - popup_border_style = "rounded", - enable_git_status = true, - enable_diagnostics = true, - sort_case_insensitive = false, - auto_expand_width = true, - git_status_async = true, -- Asynchronous git status. Improves performance. - hide_root_node = true, -- Hide the root node. - retain_hidden_root_indent = true, -- IF the root node is hidden, keep the indentation anyhow. - use_libuv_file_watcher = true, - sort = { - sorter = "name", - }, - window = { - position = "left", - width = 32, - mappings = { - ["H"] = "navigate_up", - ["L"] = function(state) - require("neo-tree.sources.filesystem.commands").set_root(state) - -- Refresh patterns when changing root - vim.defer_fn(function() - local new_patterns = ignore_patterns.get_all_patterns() - require("neo-tree").config.filesystem.filtered_items.hide_by_pattern = new_patterns - require("neo-tree.sources.manager").refresh "filesystem" - end, 50) - end, - [""] = "toggle_hidden", - ["."] = "toggle_hidden", - ["/"] = "fuzzy_finder", - ["f"] = "filter_on_submit", - [""] = "clear_filter", - [""] = "open_and_close_neotree", - [""] = "open", - -- Preview image under cursor via kitty's icat kitten - ["p"] = "image_kitty", - -- Git keybindings from astrocore - ["o"] = function() vim.cmd "!gh repo view --web" end, - ["h"] = function() - require("snacks").terminal("gh dash", { - hidden = true, - auto_close = false, - interactive = true, - }) - end, - ["g"] = function() - require("snacks").terminal("lazygit", { - hidden = true, - auto_close = true, - interactive = true, - }) - end, - ["d"] = function() require("snacks").picker.git_diff() end, - ["b"] = function() require("snacks").picker.git_branches() end, - -- Terminal keybindings from astrocore - ["m"] = terminals.mk_toggle, - ["l"] = function() - require("snacks").terminal("lazydocker", { - hidden = true, - auto_close = true, - start_in_insert = true, - interactive = true, - }) - end, - ["du"] = function() - require("snacks").terminal("devbox services up", { - hidden = true, - auto_close = false, - start_in_insert = true, - interactive = true, - }) - end, - ["dd"] = function() - require("snacks").terminal("devbox services down", { - hidden = true, - auto_close = false, - start_in_insert = true, - interactive = true, - }) - end, - ["da"] = function() - require("snacks").terminal("devbox services attach", { - hidden = true, - auto_close = false, - start_in_insert = true, - interactive = true, - }) - end, - ["."] = terminals.yazi_toggle, - ["t"] = function() require("snacks").terminal() end, - -- Find keybindings from astrocore - -- These Find keybindings are defined in astrocore.lua - -- NeoTree only needs the basic navigation and terminal bindings - -- Mappings for Mason and formatting - ["N"] = function(state) - local node = state.tree:get_node() - local path = node.type == "directory" and node.path or vim.fn.fnamemodify(node.path, ":h") - - -- Check if running on Arch Linux - local is_arch = vim.fn.filereadable "/etc/arch-release" == 1 - - if is_arch then - -- Use uwsm to launch alacritty with yazi on Arch Linux - vim.fn.jobstart( - string.format("uwsm app -- alacritty --working-directory %s -e yazi", vim.fn.shellescape(path)), - { - detach = true, - } - ) - else - -- Fallback to direct alacritty launch with yazi on other systems - vim.fn.jobstart(string.format("alacritty --working-directory %s -e yazi", vim.fn.shellescape(path)), { - detach = true, - }) - end - end, - }, - header = { - highlight = "NeoTreeHeader", - }, - }, - default_component_configs = { - container = { - enable_character_fade = true, - }, - indent = { - indent_size = 2, - padding = 1, -- extra padding on left hand side - -- indent guides - with_markers = true, - indent_marker = "│", - last_indent_marker = "└", - highlight = "NeoTreeIndentMarker", - -- expander config, needed for nesting files - expander_collapsed = "", - expander_expanded = "", - expander_highlight = "NeoTreeExpander", - }, - name = { - trailing_slash = false, -- append a trailing slash to folder names - use_git_status_colors = true, -- use git status colors instead of the default colors - highlight = "NeoTreeFileName", - }, - git_status = { - symbols = { - -- Change type - added = "", -- or "✚", but this is redundant info if you use git_status_colors on the name - modified = "", -- or "", but this is redundant info if you use git_status_colors on the name - deleted = "✖", -- this can only be used in the git_status source - renamed = "󰁕", -- this can only be used in the git_status source - -- Status type - untracked = "", - ignored = "", - unstaged = "󰄱", - staged = "", - conflict = "", - }, - }, - -- If you don't want to use these columns, you can set `enabled = false` for each of them individually - file_size = { - enabled = true, - width = 12, -- width of the column - required_width = 48, -- min width of window required to show this column - }, - type = { - enabled = true, - width = 10, -- width of the column - required_width = 122, -- min width of window required to show this column - }, - last_modified = { - enabled = true, - width = 20, -- width of the column - required_width = 88, -- min width of window required to show this column - }, - created = { - enabled = true, - width = 20, -- width of the column - required_width = 110, -- min width of window required to show this column - }, - symlink_target = { - enabled = false, - }, - }, - - buffers = { - follow_current_file = { - enabled = true, - }, - }, - - -- Filesystem specific settings - filesystem = { - commands = { - -- Refresh patterns when changing directories - refresh_patterns = function(state) - -- Re-read patterns when navigating - local new_patterns = ignore_patterns.get_all_patterns() - state.filtered_items.hide_by_pattern = new_patterns - require("neo-tree.sources.manager").refresh "filesystem" - end, - -- If item is a file it will close neotree after opening it. - open_and_close_neotree = function(state) - require("neo-tree.sources.filesystem.commands").open(state) - local tree = state.tree - local success, node = pcall(tree.get_node, tree) - if not success then return end - if node.type == "file" then require("neo-tree.command").execute { action = "close" } end - end, - - -- Preview an image with kitty's icat kitten in a snacks terminal - image_kitty = function(state) - local node = state.tree:get_node() - if node and node.type == "file" then - require("snacks").terminal("kitten icat --hold " .. vim.fn.shellescape(node.path), { - hidden = true, - auto_close = false, - interactive = true, - }) - else - vim.notify("Not a file", vim.log.levels.WARN, { title = "Neo-tree" }) - end - end, - - -- Custom command to open Mason - mason_open = function() vim.cmd "Mason" end, - - -- Custom command to format a file using the LSP - format_file = function(state) - local node = state.tree:get_node() - if node and node.type == "file" then - -- Format the file in a temporary buffer without opening it in a window - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, node.path) - vim.api.nvim_buf_load(bufnr) - vim.lsp.buf.format { bufnr = bufnr, async = false, timeout_ms = 5000 } - vim.api.nvim_buf_call(bufnr, function() vim.cmd "write" end) - vim.notify("Formatted " .. vim.fs.basename(node.path), vim.log.levels.INFO, { title = "Neo-tree" }) - else - vim.notify("Not a file", vim.log.levels.WARN, { title = "Neo-tree" }) - end - end, - }, - bind_to_cwd = true, - follow_current_file = { - enabled = true, - leave_dirs_open = false, - }, - filtered_items = { - force_visible_in_empty_folder = false, - group_empty = false, - show_hidden_count = false, - hide_dotfiles = false, - hide_gitignored = true, - hide_by_pattern = hide_patterns, - always_show = {}, - always_show_by_pattern = {}, - visible = function(item) - -- Hide all symlinks - if item.type == "link" then return false end - return true - end, - hide_by_name = { - -- "contracts", - -- "crypto", - -- "CONSTITUTION.md", - -- "CONTRIBUTING.md", - -- "api", - -- "turbo.json", - -- "OpenCode.md", - -- "AGENT.md", - -- "AGENTS.md", - -- "svgo.config.mjs", - -- ".prettierrc.cjs", - -- ".eslintrc.cjs", - -- "tsconfig.json", - -- "tsconfig.prod.json", - -- ".prettierignore", - -- "CONVENTIONS.md", - -- ".gitkeep", - -- ".source", - -- "settings.json", - -- ".gitignore", - -- "analysis_options.yaml", - -- "slumber.yml", - -- ".eslintignore", - -- "CONTRIBUTING.md", - -- "CHANGELOG.md", - -- "devbox.d", - -- "env", - -- ".husky", - -- "translations", - -- "LICENSE.md", - -- ".envrc", - -- "CLAUDE.md", - -- "chains", - -- "test", - -- ".golangci.yml", - -- "go.mod", - -- "pnpm-workspace.yaml", - -- "examples", - -- "bridge", - -- "client", - }, - never_show_by_pattern = ignore_patterns.never_show_patterns, - never_show = ignore_patterns.never_show, - }, - }, - diagnostics = { - enable = false, - icons = { - hint = "", - info = "", - warning = "", - error = "", - }, - }, - - source_selector = { - winbar = false, - statusline = false, - }, - - -- Main sources - sources = { - "filesystem", - }, - } - end, - }, -} diff --git a/lua/plugins/user.lua b/lua/plugins/user.lua index a4fdc53..64cfd6a 100644 --- a/lua/plugins/user.lua +++ b/lua/plugins/user.lua @@ -1,53 +1,43 @@ --- User plugins: cursor animation and snacks.nvim (picker + dashboard) customization --- Confirm action for file-opening pickers: --- open in a vertical split by default, reuse the window when the buffer is --- already visible in this tab, plain edit when the main window holds an --- empty scratch buffer. Explicit edit-cmd keybinds ( split, --- vsplit, tab) carry `action.cmd` and are passed through untouched. -local function vsplit_confirm(picker, item, action) +-- Confirm action for file-opening pickers: opens the selection as a +-- tab via `tabdrop` (:tab drop semantics — focus the buffer if it's already +-- visible in any tab, don't spawn a tab from an empty buffer, else open a +-- new tab). Explicit edit-cmd keybinds (/ vsplit, split) +-- carry `action.cmd` and are passed through untouched. +local function tab_confirm(picker, item, action) local actions = require "snacks.picker.actions" if action.cmd then return actions.jump(picker, item, action) end - local path = item and item.file and require("snacks.picker.util").path(item) - if not (item and (item.buf or path)) then return actions.jump(picker, item, action) end - local buf = item.buf or vim.fn.bufadd(path) - local visible = false - for _, w in ipairs(vim.fn.win_findbuf(buf)) do - if - vim.api.nvim_win_get_tabpage(w) == vim.api.nvim_get_current_tabpage() - and vim.api.nvim_win_get_config(w).relative == "" - then - visible = true - break - end - end - local main_buf = picker.main and vim.api.nvim_win_is_valid(picker.main) and vim.api.nvim_win_get_buf(picker.main) - local main_empty = main_buf - and vim.api.nvim_buf_get_name(main_buf) == "" - and vim.bo[main_buf].buftype == "" - and not vim.bo[main_buf].modified - local cmd = (visible or main_empty) and "edit" or "vsplit" - return actions.jump(picker, item, vim.tbl_extend("force", {}, action, { cmd = cmd })) + return actions.jump(picker, item, vim.tbl_extend("force", {}, action, { cmd = "tabdrop" })) +end + +-- Tab-on-enter + vsplit-on-shift-enter for the file-opening pickers only; +-- location pickers (lsp_*, lines, diagnostics, grep) and sources with their +-- own confirm (git_branches, projects) keep their defaults. +local picker_sources = {} +for _, source in ipairs { "files", "git_files", "smart", "recent", "buffers" } do + picker_sources[source] = { + confirm = tab_confirm, + win = { + input = { keys = { [""] = { "edit_vsplit", mode = { "n", "i" } } } }, + list = { keys = { [""] = "edit_vsplit" } }, + }, + } end ---@type LazySpec return { { "sphamba/smear-cursor.nvim", opts = {} }, + -- terminals are handled by kitty now + { "akinsho/toggleterm.nvim", enabled = false }, + { "folke/snacks.nvim", opts = { picker = { jump = { reuse_win = true }, - -- vsplit-by-default only for file-opening pickers; location pickers - -- (lsp_*, lines, diagnostics, grep) keep the default in-window jump - sources = { - files = { confirm = vsplit_confirm }, - git_files = { confirm = vsplit_confirm }, - smart = { confirm = vsplit_confirm }, - recent = { confirm = vsplit_confirm }, - buffers = { confirm = vsplit_confirm }, - }, + sources = picker_sources, }, dashboard = { enabled = true, diff --git a/lua/plugins/yazi.lua b/lua/plugins/yazi.lua new file mode 100644 index 0000000..e840f35 --- /dev/null +++ b/lua/plugins/yazi.lua @@ -0,0 +1,59 @@ +--- Yazi as the default file manager (full neo-tree replacement) +---@type LazySpec +return { + { + "mikavilpas/yazi.nvim", + version = "*", -- latest stable release + event = "VeryLazy", + dependencies = { + { "nvim-lua/plenary.nvim", lazy = true }, + }, + keys = { + { + "-", + mode = { "n", "v" }, + "Yazi", + desc = "Open yazi at the current file", + }, + { + "cw", + "Yazi cwd", + desc = "Open yazi in nvim's working directory", + }, + { + -- replaces AstroNvim's o "Toggle Explorer Focus" (neo-tree) + "o", + "Yazi", + desc = "Open Explorer (yazi at current file)", + }, + }, + ---@type YaziConfig | {} + opts = { + -- yazi replaces netrw/neo-tree when opening directories (nvim .) + open_for_directories = true, + -- open visible splits as yazi tabs for fast navigation + open_multiple_tabs = true, + -- occupy the whole screen instead of a floating popup + floating_window_scaling_factor = 1, + yazi_floating_window_border = "none", + keymaps = { + show_help = "", + -- ~/.config/yazi/keymap.toml binds to quit inside yazi; + -- don't hijack it for the quickfix list (multi-select still goes + -- to the quickfix list via the default yazi_opened_multiple_files) + send_to_quickfix_list = false, + }, + integrations = { + -- copy-relative-path action inside snacks pickers too + picker_add_copy_relative_path_action = "snacks.picker", + }, + }, + init = function() + -- netrw is fully replaced by yazi (open_for_directories) + vim.g.loaded_netrwPlugin = 1 + end, + }, + + -- neo-tree is fully replaced by yazi.nvim + { "nvim-neo-tree/neo-tree.nvim", enabled = false }, +}