From f80b18544a771786b418f882d9c239c315625155 Mon Sep 17 00:00:00 2001 From: Prad Nukala Date: Sat, 11 Jul 2026 18:46:47 -0400 Subject: [PATCH] refactor(config): enhance ignore patterns and remove terminals.lua --- lua/config/ignore_patterns.lua | 148 ++++++++++++++++++++++----------- lua/config/terminals.lua | 88 -------------------- 2 files changed, 99 insertions(+), 137 deletions(-) delete mode 100644 lua/config/terminals.lua diff --git a/lua/config/ignore_patterns.lua b/lua/config/ignore_patterns.lua index d420e1c..5c33c9f 100644 --- a/lua/config/ignore_patterns.lua +++ b/lua/config/ignore_patterns.lua @@ -1,12 +1,20 @@ --- Shared ignore patterns for file explorers and pickers --- This module provides a centralized list of patterns to ignore across ---- neotree, snacks.picker, and other file browsing tools. +--- snacks.picker and other file browsing tools. (The never_show lists were +--- consumed by neo-tree, now replaced by yazi.nvim; kept as reference data.) +--- +--- Everything here is fast-event-context safe (pure Lua + libuv only): +--- snacks picker transforms run inside async/uv callbacks where vimL +--- functions (vim.fn.*) raise E5560. --- @module config.ignore_patterns +local uv = vim.uv or vim.loop + local M = {} --- Core ignore patterns used across the configuration ---- These patterns use glob syntax (* for wildcards) +--- These patterns use glob syntax (* and ? wildcards), matched against +--- file basenames and individual path segments. M.patterns = { -- Build artifacts and generated files ".beads", @@ -33,14 +41,11 @@ M.patterns = { "AGENTS.md", "test", "tests", - ".omc", -- Configuration files ".editorconfig", ".gitpod.*", - - -- Lock files and dependencies "*.lock", "*.lockb", @@ -176,7 +181,6 @@ M.never_show_patterns = { "Icon?", "iCloud~", "com~", - "*.icns", ".conform*", ".null-ls_*", } @@ -198,66 +202,112 @@ M.ignored_directories = { ".worktrees", } ---- Read additional ignore patterns from .rgignore file in git root ---- @return table patterns List of patterns from .rgignore -function M.read_rgignore() - local patterns = {} - local current_file = vim.fn.expand "%:p" - local current_dir = vim.fn.fnamemodify(current_file, ":h") +local EMPTY = {} - if current_dir == "" then current_dir = vim.fn.getcwd() end +--- Convert a glob (* and ? wildcards) to an anchored Lua pattern +--- @param glob string +--- @return string +local function glob_to_lua(glob) + return "^" .. glob:gsub("[%%%.%(%)%+%-%^%$%[%]]", "%%%0"):gsub("%*", ".*"):gsub("%?", ".") .. "$" +end - local git_root = - vim.fn.systemlist("cd " .. vim.fn.shellescape(current_dir) .. " && git rev-parse --show-toplevel 2>/dev/null")[1] - - if git_root and git_root ~= "" and not git_root:match "^fatal:" then - local rgignore_path = git_root .. "/.rgignore" - local file = io.open(rgignore_path, "r") - - if file then - for line in file:lines() do - if line ~= "" and not line:match "^#" then - line = line:gsub("^%./", "") - table.insert(patterns, line) - end - end - file:close() - end +--- @param globs string[] +--- @return string[] +local function compile_globs(globs) + local out = {} + for _, g in ipairs(globs) do + out[#out + 1] = glob_to_lua(g) end + return out +end +--- Find the git root for a directory by walking up (pure libuv, cached) +local root_cache = {} ---@type table +--- @param dir string +--- @return string? +local function git_root(dir) + local cached = root_cache[dir] + if cached ~= nil then return cached or nil end + local cur = dir + while cur and cur ~= "" do + if uv.fs_stat(cur .. "/.git") then + root_cache[dir] = cur + return cur + end + local parent = cur:match "(.+)/[^/]+$" + if not parent or parent == cur then break end + cur = parent + end + root_cache[dir] = false + return nil +end + +--- Read additional ignore patterns from the .rgignore file in the git root. +--- Cached per file and invalidated on mtime change. Safe in fast event contexts. +--- @param dir? string directory to resolve the git root from (defaults to cwd) +--- @return string[] patterns List of glob patterns from .rgignore +local rgignore_cache = {} ---@type table +function M.read_rgignore(dir) + local root = git_root(dir or uv.cwd()) + if not root then return EMPTY end + local path = root .. "/.rgignore" + local stat = uv.fs_stat(path) + if not stat then return EMPTY end + local cached = rgignore_cache[path] + if cached and cached.mtime == stat.mtime.sec then return cached.patterns end + local patterns = {} + local file = io.open(path, "r") + if file then + for line in file:lines() do + if line ~= "" and not line:match "^#" then + patterns[#patterns + 1] = (line:gsub("^%./", "")) + end + end + file:close() + end + rgignore_cache[path] = { mtime = stat.mtime.sec, patterns = patterns } return patterns end --- Get all hide patterns including rgignore +--- @param dir? string directory to resolve the git root from (defaults to cwd) --- @return table patterns Combined list of all patterns to hide -function M.get_all_patterns() - local rgignore_patterns = M.read_rgignore() - return vim.list_extend(vim.list_extend({}, M.patterns), rgignore_patterns) +function M.get_all_patterns(dir) + return vim.list_extend(vim.list_extend({}, M.patterns), M.read_rgignore(dir)) end ---- Create a file filter function for pickers ---- @return function filter Function that filters files based on ignore patterns +--- Create a filter for snacks picker `transform` options. +--- Snacks contract: return `false` to DROP an item, `true`/`nil` to keep it +--- (returning nil keeps the item!). Runs per item in a fast event context, +--- so only pure Lua and libuv are used here. +--- @return fun(item: snacks.picker.finder.Item): boolean function M.create_picker_filter() + local static ---@type string[]? compiled static globs (lazy) + local last_raw, last_rg = nil, EMPTY ---@type string[]?, string[] return function(item) - if not item or not item.file then return item end + local file = item and item.file + if not file then return true end - local file = item.file - local basename = vim.fn.fnamemodify(file, ":t") - local dir = vim.fn.fnamemodify(file, ":h:t") - - -- Check against all patterns - local all_patterns = M.get_all_patterns() - for _, pattern in ipairs(all_patterns) do - local lua_pattern = pattern:gsub("%.", "%%."):gsub("%*", ".*"):gsub("%?", ".") - if basename:match("^" .. lua_pattern .. "$") or file:match(lua_pattern) then return nil end + static = static or compile_globs(M.patterns) + local raw = M.read_rgignore(item.cwd) + if raw ~= last_raw then + last_raw, last_rg = raw, compile_globs(raw) end - -- Check ignored directories - for _, ignored in ipairs(M.ignored_directories) do - if dir == ignored or file:match("/" .. ignored .. "/") then return nil end + -- Match every path segment (directories and basename) anchored, + -- so `test` drops `test/foo.lua` but never `latest.md`. + for seg in file:gmatch "[^/\\]+" do + for _, pat in ipairs(static) do + if seg:match(pat) then return false end + end + for _, pat in ipairs(last_rg) do + if seg:match(pat) then return false end + end + for _, dirname in ipairs(M.ignored_directories) do + if seg == dirname then return false end + end end - - return item + return true end end diff --git a/lua/config/terminals.lua b/lua/config/terminals.lua deleted file mode 100644 index 98972e3..0000000 --- a/lua/config/terminals.lua +++ /dev/null @@ -1,88 +0,0 @@ ---- Terminal configurations using toggleterm ---- This module provides terminal toggle functions for various CLI tools ---- @module config.terminals - -local M = {} - ---- Check if toggleterm is available ---- @return boolean available Whether toggleterm is loaded -local function is_toggleterm_available() - local ok, _ = pcall(require, "toggleterm.terminal") - return ok -end - ---- Initialize all terminal configurations ---- Only runs if toggleterm is available -function M.setup() - if not is_toggleterm_available() then - vim.notify("toggleterm not available, skipping terminal setup", vim.log.levels.WARN) - return - end - - local Terminal = require("toggleterm.terminal").Terminal - -- Scooter find and replace terminal - local smartCommit = Terminal:new { - cmd = "smartcommit", - hidden = true, - direction = "vertical", - close_on_exit = true, - on_open = function(term) - vim.cmd "startinsert!" - vim.api.nvim_buf_set_keymap(term.bufnr, "n", "q", "close", { noremap = true, silent = true }) - end, - } - - -- Scooter find and replace terminal - local scooter = Terminal:new { - cmd = "scooter", - hidden = true, - direction = "vertical", - close_on_exit = true, - on_open = function(term) - vim.cmd "startinsert!" - vim.api.nvim_buf_set_keymap(term.bufnr, "n", "q", "close", { noremap = true, silent = true }) - end, - } - - -- Mk (make) terminal - local lazygit = Terminal:new { - cmd = "lazygit", - hidden = true, - direction = "float", - on_open = function(term) - vim.cmd "startinsert!" - vim.api.nvim_buf_set_keymap(term.bufnr, "n", "q", "close", { noremap = true, silent = true }) - end, - } - -- Mk (make) terminal - local lazydocker = Terminal:new { - cmd = "lazydocker", - hidden = true, - direction = "float", - on_open = function(term) - vim.cmd "startinsert!" - vim.api.nvim_buf_set_keymap(term.bufnr, "n", "q", "close", { noremap = true, silent = true }) - end, - } - - -- yazi terminal - local yazi = Terminal:new { - cmd = "yazi", - hidden = true, - direction = "float", - on_open = function(term) - vim.cmd "startinsert!" - vim.api.nvim_buf_set_keymap(term.bufnr, "n", "q", "close", { noremap = true, silent = true }) - end, - } - - -- Export toggle functions to module instead of global namespace - M.scooter_toggle = function() scooter:toggle() end - M.mk_toggle = function() mk:toggle() end - M.lazygit_toggle = function() lazygit:toggle() end - M.lazydocker_toggle = function() lazydocker:toggle() end - M.smartCommit_toggle = function() smartCommit:toggle() end - M.yazi_toggle = function() yazi:toggle() end -end - -return M