From 181bbe8e6a248a93277be0d121db89eec45eb700 Mon Sep 17 00:00:00 2001 From: uanela Date: Wed, 11 Feb 2026 09:51:19 +0200 Subject: [PATCH 1/6] feat: add .zig-cache in default ignored_dirs --- doc/nvim-tree-lua.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index 725ab2d34f0..a61bff0279c 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -3004,6 +3004,7 @@ Following is the default configuration, see |nvim_tree.config| for details. >lua "/build", "/node_modules", "/target", + "/.zig-cache", }, }, actions = { From 9b0c6a65baf2a5dbd26152eb0d6be7375f63c1fe Mon Sep 17 00:00:00 2001 From: uanela Date: Wed, 11 Feb 2026 10:06:32 +0200 Subject: [PATCH 2/6] feat: correctly add .zig-cache in default ignored_dirs --- lua/nvim-tree.lua | 1 + lua/nvim-tree/explorer/watch.lua | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/lua/nvim-tree.lua b/lua/nvim-tree.lua index b5a75d78db9..13012e0a295 100644 --- a/lua/nvim-tree.lua +++ b/lua/nvim-tree.lua @@ -468,6 +468,7 @@ local DEFAULT_OPTS = { -- default-config-start "/build", "/node_modules", "/target", + "/.zig-cache", }, }, actions = { diff --git a/lua/nvim-tree/explorer/watch.lua b/lua/nvim-tree/explorer/watch.lua index 2ce5051c69a..94f6e30fdd9 100644 --- a/lua/nvim-tree/explorer/watch.lua +++ b/lua/nvim-tree/explorer/watch.lua @@ -44,6 +44,11 @@ local function is_folder_ignored(path) if type(M.config.filesystem_watchers.ignore_dirs) == "table" then for _, ignore_dir in ipairs(M.config.filesystem_watchers.ignore_dirs) do + if utils.is_windows or true then + ignore_dir = utils.escape_special_chars(ignore_dir) or ignore_dir + print(ignore_dir) + end + if vim.fn.match(path, ignore_dir) ~= -1 then return true end From 56fa77f454476b3f3f14f33a5d511a66967c91f3 Mon Sep 17 00:00:00 2001 From: uanela Date: Wed, 11 Feb 2026 10:49:22 +0200 Subject: [PATCH 3/6] fix(#3178): handle windows paths in ignore_dirs --- lua/nvim-tree.lua | 1442 +++++++++++++++--------------- lua/nvim-tree/explorer/watch.lua | 194 ++-- 2 files changed, 821 insertions(+), 815 deletions(-) diff --git a/lua/nvim-tree.lua b/lua/nvim-tree.lua index 13012e0a295..aaf1d35ba05 100644 --- a/lua/nvim-tree.lua +++ b/lua/nvim-tree.lua @@ -8,7 +8,7 @@ local notify = require("nvim-tree.notify") local _config = {} local M = { - init_root = "", + init_root = "", } --- Helper function to execute some explorer method safely @@ -16,802 +16,806 @@ local M = { ---@param ... any|nil ---@return function|nil local function explorer_fn(fn, ...) - local explorer = core.get_explorer() - if explorer then - return explorer[fn](explorer, ...) - end + local explorer = core.get_explorer() + if explorer then + return explorer[fn](explorer, ...) + end end --- Update the tree root to a directory or the directory containing ---@param path string relative or absolute ---@param bufnr number|nil function M.change_root(path, bufnr) - -- skip if current file is in ignore_list - if type(bufnr) == "number" then - local ft - - if vim.fn.has("nvim-0.10") == 1 then - ft = vim.api.nvim_get_option_value("filetype", { buf = bufnr }) or "" - else - ft = vim.api.nvim_buf_get_option(bufnr, "filetype") or "" ---@diagnostic disable-line: deprecated - end - - for _, value in pairs(_config.update_focused_file.update_root.ignore_list) do - if utils.str_find(path, value) or utils.str_find(ft, value) then - return - end - end - end - - -- don't find inexistent - if vim.fn.filereadable(path) == 0 then - return - end - - local cwd = core.get_cwd() - if cwd == nil then - return - end - - local vim_cwd = vim.fn.getcwd() - - -- test if in vim_cwd - if utils.path_relative(path, vim_cwd) ~= path then - if vim_cwd ~= cwd then - explorer_fn("change_dir", vim_cwd) - end - return - end - -- test if in cwd - if utils.path_relative(path, cwd) ~= path then - return - end - - -- otherwise test M.init_root - if _config.prefer_startup_root and utils.path_relative(path, M.init_root) ~= path then - explorer_fn("change_dir", M.init_root) - return - end - -- otherwise root_dirs - for _, dir in pairs(_config.root_dirs) do - dir = vim.fn.fnamemodify(dir, ":p") - if utils.path_relative(path, dir) ~= path then - explorer_fn("change_dir", dir) - return - end - end - -- finally fall back to the folder containing the file - explorer_fn("change_dir", vim.fn.fnamemodify(path, ":p:h")) + -- skip if current file is in ignore_list + if type(bufnr) == "number" then + local ft + + if vim.fn.has("nvim-0.10") == 1 then + ft = vim.api.nvim_get_option_value("filetype", { buf = bufnr }) or "" + else + ft = vim.api.nvim_buf_get_option(bufnr, "filetype") or "" ---@diagnostic disable-line: deprecated + end + + for _, value in pairs(_config.update_focused_file.update_root.ignore_list) do + if utils.str_find(path, value) or utils.str_find(ft, value) then + return + end + end + end + + -- don't find inexistent + if vim.fn.filereadable(path) == 0 then + return + end + + local cwd = core.get_cwd() + if cwd == nil then + return + end + + local vim_cwd = vim.fn.getcwd() + + -- test if in vim_cwd + if utils.path_relative(path, vim_cwd) ~= path then + if vim_cwd ~= cwd then + explorer_fn("change_dir", vim_cwd) + end + return + end + -- test if in cwd + if utils.path_relative(path, cwd) ~= path then + return + end + + -- otherwise test M.init_root + if _config.prefer_startup_root and utils.path_relative(path, M.init_root) ~= path then + explorer_fn("change_dir", M.init_root) + return + end + -- otherwise root_dirs + for _, dir in pairs(_config.root_dirs) do + dir = vim.fn.fnamemodify(dir, ":p") + if utils.path_relative(path, dir) ~= path then + explorer_fn("change_dir", dir) + return + end + end + -- finally fall back to the folder containing the file + explorer_fn("change_dir", vim.fn.fnamemodify(path, ":p:h")) end function M.tab_enter() - if view.is_visible({ any_tabpage = true }) then - local bufname = vim.api.nvim_buf_get_name(0) - - local ft - if vim.fn.has("nvim-0.10") == 1 then - ft = vim.api.nvim_get_option_value("filetype", { buf = 0 }) or "" - else - ft = vim.api.nvim_buf_get_option(0, "ft") ---@diagnostic disable-line: deprecated - end - - for _, filter in ipairs(M.config.tab.sync.ignore) do - if bufname:match(filter) ~= nil or ft:match(filter) ~= nil then - return - end - end - view.open({ focus_tree = false }) - - local explorer = core.get_explorer() - if explorer then - explorer.renderer:draw() - end - end + if view.is_visible({ any_tabpage = true }) then + local bufname = vim.api.nvim_buf_get_name(0) + + local ft + if vim.fn.has("nvim-0.10") == 1 then + ft = vim.api.nvim_get_option_value("filetype", { buf = 0 }) or "" + else + ft = vim.api.nvim_buf_get_option(0, "ft") ---@diagnostic disable-line: deprecated + end + + for _, filter in ipairs(M.config.tab.sync.ignore) do + if bufname:match(filter) ~= nil or ft:match(filter) ~= nil then + return + end + end + view.open({ focus_tree = false }) + + local explorer = core.get_explorer() + if explorer then + explorer.renderer:draw() + end + end end function M.open_on_directory() - local should_proceed = _config.hijack_directories.auto_open or view.is_visible() - if not should_proceed then - return - end - - local buf = vim.api.nvim_get_current_buf() - local bufname = vim.api.nvim_buf_get_name(buf) - if vim.fn.isdirectory(bufname) ~= 1 then - return - end - - - local explorer = core.get_explorer() - if not explorer then - core.init(bufname) - end - - explorer_fn("force_dirchange", bufname, true, false) + local should_proceed = _config.hijack_directories.auto_open or view.is_visible() + if not should_proceed then + return + end + + local buf = vim.api.nvim_get_current_buf() + local bufname = vim.api.nvim_buf_get_name(buf) + if vim.fn.isdirectory(bufname) ~= 1 then + return + end + + local explorer = core.get_explorer() + if not explorer then + core.init(bufname) + end + + explorer_fn("force_dirchange", bufname, true, false) end ---@return table function M.get_config() - return M.config + return M.config end ---@param disable_netrw boolean ---@param hijack_netrw boolean local function manage_netrw(disable_netrw, hijack_netrw) - if hijack_netrw then - vim.cmd("silent! autocmd! FileExplorer *") - vim.cmd("autocmd VimEnter * ++once silent! autocmd! FileExplorer *") - end - if disable_netrw then - vim.g.loaded_netrw = 1 - vim.g.loaded_netrwPlugin = 1 - end + if hijack_netrw then + vim.cmd("silent! autocmd! FileExplorer *") + vim.cmd("autocmd VimEnter * ++once silent! autocmd! FileExplorer *") + end + if disable_netrw then + vim.g.loaded_netrw = 1 + vim.g.loaded_netrwPlugin = 1 + end end ---@param name string|nil function M.change_dir(name) - if name then - explorer_fn("change_dir", name) - end + if name then + explorer_fn("change_dir", name) + end - if _config.update_focused_file.update_root.enable then - actions.tree.find_file.fn() - end + if _config.update_focused_file.update_root.enable then + actions.tree.find_file.fn() + end end ---@param opts table local function setup_autocommands(opts) - local augroup_id = vim.api.nvim_create_augroup("NvimTree", { clear = true }) - local function create_nvim_tree_autocmd(name, custom_opts) - local default_opts = { group = augroup_id } - vim.api.nvim_create_autocmd(name, vim.tbl_extend("force", default_opts, custom_opts)) - end - - -- prevent new opened file from opening in the same window as nvim-tree - create_nvim_tree_autocmd("BufWipeout", { - pattern = "NvimTree_*", - callback = function() - if not utils.is_nvim_tree_buf(0) then - return - end - if opts.actions.open_file.eject then - view._prevent_buffer_override() - else - view.abandon_current_window() - end - end, - }) - - if opts.tab.sync.open then - create_nvim_tree_autocmd("TabEnter", { callback = vim.schedule_wrap(M.tab_enter) }) - end - if opts.sync_root_with_cwd then - create_nvim_tree_autocmd("DirChanged", { - callback = function() - M.change_dir(vim.loop.cwd()) - end, - }) - end - if opts.update_focused_file.enable then - create_nvim_tree_autocmd("BufEnter", { - callback = function(event) - local exclude = opts.update_focused_file.exclude - if type(exclude) == "function" and exclude(event) then - return - end - utils.debounce("BufEnter:find_file", opts.view.debounce_delay, function() - actions.tree.find_file.fn() - end) - end, - }) - end - - if opts.hijack_directories.enable then - create_nvim_tree_autocmd({ "BufEnter", "BufNewFile" }, { callback = M.open_on_directory, nested = true }) - end - - if opts.view.centralize_selection then - create_nvim_tree_autocmd("BufEnter", { - pattern = "NvimTree_*", - callback = function() - vim.schedule(function() - vim.api.nvim_buf_call(0, function() - local is_term_mode = vim.api.nvim_get_mode().mode == "t" - if is_term_mode then - return - end - vim.cmd([[norm! zz]]) - end) - end) - end, - }) - end - - if opts.diagnostics.enable then - create_nvim_tree_autocmd("DiagnosticChanged", { - callback = function(ev) - log.line("diagnostics", "DiagnosticChanged") - require("nvim-tree.diagnostics").update_lsp(ev) - end, - }) - create_nvim_tree_autocmd("User", { - pattern = "CocDiagnosticChange", - callback = function() - log.line("diagnostics", "CocDiagnosticChange") - require("nvim-tree.diagnostics").update_coc() - end, - }) - end - - if opts.view.float.enable and opts.view.float.quit_on_focus_loss then - create_nvim_tree_autocmd("WinLeave", { - pattern = "NvimTree_*", - callback = function() - if utils.is_nvim_tree_buf(0) then - view.close() - end - end, - }) - end - - -- Handles event dispatch when tree is closed by `:q` - create_nvim_tree_autocmd("WinClosed", { - pattern = "*", - ---@param ev vim.api.keyset.create_autocmd.callback_args - callback = function(ev) - if not vim.api.nvim_buf_is_valid(ev.buf) then - return - end - if vim.api.nvim_get_option_value("filetype", { buf = ev.buf }) == "NvimTree" then - require("nvim-tree.events")._dispatch_on_tree_close() - end - end, - }) + local augroup_id = vim.api.nvim_create_augroup("NvimTree", { clear = true }) + local function create_nvim_tree_autocmd(name, custom_opts) + local default_opts = { group = augroup_id } + vim.api.nvim_create_autocmd(name, vim.tbl_extend("force", default_opts, custom_opts)) + end + + -- prevent new opened file from opening in the same window as nvim-tree + create_nvim_tree_autocmd("BufWipeout", { + pattern = "NvimTree_*", + callback = function() + if not utils.is_nvim_tree_buf(0) then + return + end + if opts.actions.open_file.eject then + view._prevent_buffer_override() + else + view.abandon_current_window() + end + end, + }) + + if opts.tab.sync.open then + create_nvim_tree_autocmd("TabEnter", { callback = vim.schedule_wrap(M.tab_enter) }) + end + if opts.sync_root_with_cwd then + create_nvim_tree_autocmd("DirChanged", { + callback = function() + M.change_dir(vim.loop.cwd()) + end, + }) + end + if opts.update_focused_file.enable then + create_nvim_tree_autocmd("BufEnter", { + callback = function(event) + local exclude = opts.update_focused_file.exclude + if type(exclude) == "function" and exclude(event) then + return + end + utils.debounce("BufEnter:find_file", opts.view.debounce_delay, function() + actions.tree.find_file.fn() + end) + end, + }) + end + + if opts.hijack_directories.enable then + create_nvim_tree_autocmd({ "BufEnter", "BufNewFile" }, { callback = M.open_on_directory, nested = true }) + end + + if opts.view.centralize_selection then + create_nvim_tree_autocmd("BufEnter", { + pattern = "NvimTree_*", + callback = function() + vim.schedule(function() + vim.api.nvim_buf_call(0, function() + local is_term_mode = vim.api.nvim_get_mode().mode == "t" + if is_term_mode then + return + end + vim.cmd([[norm! zz]]) + end) + end) + end, + }) + end + + if opts.diagnostics.enable then + create_nvim_tree_autocmd("DiagnosticChanged", { + callback = function(ev) + log.line("diagnostics", "DiagnosticChanged") + require("nvim-tree.diagnostics").update_lsp(ev) + end, + }) + create_nvim_tree_autocmd("User", { + pattern = "CocDiagnosticChange", + callback = function() + log.line("diagnostics", "CocDiagnosticChange") + require("nvim-tree.diagnostics").update_coc() + end, + }) + end + + if opts.view.float.enable and opts.view.float.quit_on_focus_loss then + create_nvim_tree_autocmd("WinLeave", { + pattern = "NvimTree_*", + callback = function() + if utils.is_nvim_tree_buf(0) then + view.close() + end + end, + }) + end + + -- Handles event dispatch when tree is closed by `:q` + create_nvim_tree_autocmd("WinClosed", { + pattern = "*", + ---@param ev vim.api.keyset.create_autocmd.callback_args + callback = function(ev) + if not vim.api.nvim_buf_is_valid(ev.buf) then + return + end + if vim.api.nvim_get_option_value("filetype", { buf = ev.buf }) == "NvimTree" then + require("nvim-tree.events")._dispatch_on_tree_close() + end + end, + }) end ---@type nvim_tree.config local DEFAULT_OPTS = { -- default-config-start - on_attach = "default", - hijack_cursor = false, - auto_reload_on_write = true, - disable_netrw = false, - hijack_netrw = true, - hijack_unnamed_buffer_when_opening = false, - root_dirs = {}, - prefer_startup_root = false, - sync_root_with_cwd = false, - reload_on_bufenter = false, - respect_buf_cwd = false, - select_prompts = false, - sort = { - sorter = "name", - folders_first = true, - files_first = false, - }, - view = { - centralize_selection = false, - cursorline = true, - cursorlineopt = "both", - debounce_delay = 15, - side = "left", - preserve_window_proportions = false, - number = false, - relativenumber = false, - signcolumn = "yes", - width = 30, - float = { - enable = false, - quit_on_focus_loss = true, - open_win_config = { - relative = "editor", - border = "rounded", - width = 30, - height = 30, - row = 1, - col = 1, - }, - }, - }, - renderer = { - add_trailing = false, - group_empty = false, - full_name = false, - root_folder_label = ":~:s?$?/..?", - indent_width = 2, - special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" }, - hidden_display = "none", - symlink_destination = true, - decorators = { "Git", "Open", "Hidden", "Modified", "Bookmark", "Diagnostics", "Copied", "Cut", }, - highlight_git = "none", - highlight_diagnostics = "none", - highlight_opened_files = "none", - highlight_modified = "none", - highlight_hidden = "none", - highlight_bookmarks = "none", - highlight_clipboard = "name", - indent_markers = { - enable = false, - inline_arrows = true, - icons = { - corner = "└", - edge = "│", - item = "│", - bottom = "─", - none = " ", - }, - }, - icons = { - web_devicons = { - file = { - enable = true, - color = true, - }, - folder = { - enable = false, - color = true, - }, - }, - git_placement = "before", - modified_placement = "after", - hidden_placement = "after", - diagnostics_placement = "signcolumn", - bookmarks_placement = "signcolumn", - padding = { - icon = " ", - folder_arrow = " ", - }, - symlink_arrow = " ➛ ", - show = { - file = true, - folder = true, - folder_arrow = true, - git = true, - modified = true, - hidden = false, - diagnostics = true, - bookmarks = true, - }, - glyphs = { - default = "", - symlink = "", - bookmark = "󰆤", - modified = "●", - hidden = "󰜌", - folder = { - arrow_closed = "", - arrow_open = "", - default = "", - open = "", - empty = "", - empty_open = "", - symlink = "", - symlink_open = "", - }, - git = { - unstaged = "✗", - staged = "✓", - unmerged = "", - renamed = "➜", - untracked = "★", - deleted = "", - ignored = "◌", - }, - }, - }, - }, - hijack_directories = { - enable = true, - auto_open = true, - }, - update_focused_file = { - enable = false, - update_root = { - enable = false, - ignore_list = {}, - }, - exclude = false, - }, - system_open = { - cmd = "", - args = {}, - }, - git = { - enable = true, - show_on_dirs = true, - show_on_open_dirs = true, - disable_for_dirs = {}, - timeout = 400, - cygwin_support = false, - }, - diagnostics = { - enable = false, - show_on_dirs = false, - show_on_open_dirs = true, - debounce_delay = 500, - severity = { - min = vim.diagnostic.severity.HINT, - max = vim.diagnostic.severity.ERROR, - }, - icons = { - hint = "", - info = "", - warning = "", - error = "", - }, - diagnostic_opts = false, - }, - modified = { - enable = false, - show_on_dirs = true, - show_on_open_dirs = true, - }, - filters = { - enable = true, - git_ignored = true, - dotfiles = false, - git_clean = false, - no_buffer = false, - no_bookmark = false, - custom = {}, - exclude = {}, - }, - live_filter = { - prefix = "[FILTER]: ", - always_show_folders = true, - }, - filesystem_watchers = { - enable = true, - debounce_delay = 50, - max_events = 100, - ignore_dirs = { - "/.ccls-cache", - "/build", - "/node_modules", - "/target", - "/.zig-cache", - }, - }, - actions = { - use_system_clipboard = true, - change_dir = { - enable = true, - global = false, - restrict_above_cwd = false, - }, - expand_all = { - max_folder_discovery = 300, - exclude = {}, - }, - file_popup = { - open_win_config = { - col = 1, - row = 1, - relative = "cursor", - border = "shadow", - style = "minimal", - }, - }, - open_file = { - quit_on_open = false, - eject = true, - resize_window = true, - relative_path = true, - window_picker = { - enable = true, - picker = "default", - chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", - exclude = { - filetype = { "notify", "packer", "qf", "diff", "fugitive", "fugitiveblame" }, - buftype = { "nofile", "terminal", "help" }, - }, - }, - }, - remove_file = { - close_window = true, - }, - }, - trash = { - cmd = "gio trash", - }, - tab = { - sync = { - open = false, - close = false, - ignore = {}, - }, - }, - notify = { - threshold = vim.log.levels.INFO, - absolute_path = true, - }, - help = { - sort_by = "key", - }, - ui = { - confirm = { - remove = true, - trash = true, - default_yes = false, - }, - }, - bookmarks = { - persist = false, - }, - experimental = { - }, - log = { - enable = false, - truncate = false, - types = { - all = false, - config = false, - copy_paste = false, - dev = false, - diagnostics = false, - git = false, - profile = false, - watcher = false, - }, - }, + on_attach = "default", + hijack_cursor = false, + auto_reload_on_write = true, + disable_netrw = false, + hijack_netrw = true, + hijack_unnamed_buffer_when_opening = false, + root_dirs = {}, + prefer_startup_root = false, + sync_root_with_cwd = false, + reload_on_bufenter = false, + respect_buf_cwd = false, + select_prompts = false, + sort = { + sorter = "name", + folders_first = true, + files_first = false, + }, + view = { + centralize_selection = false, + cursorline = true, + cursorlineopt = "both", + debounce_delay = 15, + side = "left", + preserve_window_proportions = false, + number = false, + relativenumber = false, + signcolumn = "yes", + width = 30, + float = { + enable = false, + quit_on_focus_loss = true, + open_win_config = { + relative = "editor", + border = "rounded", + width = 30, + height = 30, + row = 1, + col = 1, + }, + }, + }, + renderer = { + add_trailing = false, + group_empty = false, + full_name = false, + root_folder_label = ":~:s?$?/..?", + indent_width = 2, + special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" }, + hidden_display = "none", + symlink_destination = true, + decorators = { "Git", "Open", "Hidden", "Modified", "Bookmark", "Diagnostics", "Copied", "Cut" }, + highlight_git = "none", + highlight_diagnostics = "none", + highlight_opened_files = "none", + highlight_modified = "none", + highlight_hidden = "none", + highlight_bookmarks = "none", + highlight_clipboard = "name", + indent_markers = { + enable = false, + inline_arrows = true, + icons = { + corner = "└", + edge = "│", + item = "│", + bottom = "─", + none = " ", + }, + }, + icons = { + web_devicons = { + file = { + enable = true, + color = true, + }, + folder = { + enable = false, + color = true, + }, + }, + git_placement = "before", + modified_placement = "after", + hidden_placement = "after", + diagnostics_placement = "signcolumn", + bookmarks_placement = "signcolumn", + padding = { + icon = " ", + folder_arrow = " ", + }, + symlink_arrow = " ➛ ", + show = { + file = true, + folder = true, + folder_arrow = true, + git = true, + modified = true, + hidden = false, + diagnostics = true, + bookmarks = true, + }, + glyphs = { + default = "", + symlink = "", + bookmark = "󰆤", + modified = "●", + hidden = "󰜌", + folder = { + arrow_closed = "", + arrow_open = "", + default = "", + open = "", + empty = "", + empty_open = "", + symlink = "", + symlink_open = "", + }, + git = { + unstaged = "✗", + staged = "✓", + unmerged = "", + renamed = "➜", + untracked = "★", + deleted = "", + ignored = "◌", + }, + }, + }, + }, + hijack_directories = { + enable = true, + auto_open = true, + }, + update_focused_file = { + enable = false, + update_root = { + enable = false, + ignore_list = {}, + }, + exclude = false, + }, + system_open = { + cmd = "", + args = {}, + }, + git = { + enable = true, + show_on_dirs = true, + show_on_open_dirs = true, + disable_for_dirs = {}, + timeout = 400, + cygwin_support = false, + }, + diagnostics = { + enable = false, + show_on_dirs = false, + show_on_open_dirs = true, + debounce_delay = 500, + severity = { + min = vim.diagnostic.severity.HINT, + max = vim.diagnostic.severity.ERROR, + }, + icons = { + hint = "", + info = "", + warning = "", + error = "", + }, + diagnostic_opts = false, + }, + modified = { + enable = false, + show_on_dirs = true, + show_on_open_dirs = true, + }, + filters = { + enable = true, + git_ignored = true, + dotfiles = false, + git_clean = false, + no_buffer = false, + no_bookmark = false, + custom = {}, + exclude = {}, + }, + live_filter = { + prefix = "[FILTER]: ", + always_show_folders = true, + }, + filesystem_watchers = { + enable = true, + debounce_delay = 50, + max_events = 100, + ignore_dirs = { + "/.ccls-cache", + "/build", + "/node_modules", + "/target", + "/.zig-cache", + }, + }, + actions = { + use_system_clipboard = true, + change_dir = { + enable = true, + global = false, + restrict_above_cwd = false, + }, + expand_all = { + max_folder_discovery = 300, + exclude = {}, + }, + file_popup = { + open_win_config = { + col = 1, + row = 1, + relative = "cursor", + border = "shadow", + style = "minimal", + }, + }, + open_file = { + quit_on_open = false, + eject = true, + resize_window = true, + relative_path = true, + window_picker = { + enable = true, + picker = "default", + chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", + exclude = { + filetype = { "notify", "packer", "qf", "diff", "fugitive", "fugitiveblame" }, + buftype = { "nofile", "terminal", "help" }, + }, + }, + }, + remove_file = { + close_window = true, + }, + }, + trash = { + cmd = "gio trash", + }, + tab = { + sync = { + open = false, + close = false, + ignore = {}, + }, + }, + notify = { + threshold = vim.log.levels.INFO, + absolute_path = true, + }, + help = { + sort_by = "key", + }, + ui = { + confirm = { + remove = true, + trash = true, + default_yes = false, + }, + }, + bookmarks = { + persist = false, + }, + experimental = {}, + log = { + enable = false, + truncate = false, + types = { + all = false, + config = false, + copy_paste = false, + dev = false, + diagnostics = false, + git = false, + profile = false, + watcher = false, + }, + }, } -- default-config-end local function merge_options(conf) - return vim.tbl_deep_extend("force", DEFAULT_OPTS, conf or {}) + return vim.tbl_deep_extend("force", DEFAULT_OPTS, conf or {}) end local FIELD_SKIP_VALIDATE = { - open_win_config = true, + open_win_config = true, } local ACCEPTED_TYPES = { - on_attach = { "function", "string" }, - sort = { - sorter = { "function", "string" }, - }, - view = { - width = { - "string", - "function", - "number", - "table", - min = { "string", "function", "number" }, - max = { "string", "function", "number" }, - lines_excluded = { "table" }, - padding = { "function", "number" }, - }, - }, - renderer = { - hidden_display = { "function", "string" }, - group_empty = { "boolean", "function" }, - root_folder_label = { "function", "string", "boolean" }, - }, - update_focused_file = { - exclude = { "function" }, - }, - git = { - disable_for_dirs = { "function" }, - }, - filters = { - custom = { "function" }, - }, - filesystem_watchers = { - ignore_dirs = { "function" }, - }, - actions = { - open_file = { - window_picker = { - picker = { "function", "string" }, - }, - }, - }, - bookmarks = { - persist = { "boolean", "string" }, - }, + on_attach = { "function", "string" }, + sort = { + sorter = { "function", "string" }, + }, + view = { + width = { + "string", + "function", + "number", + "table", + min = { "string", "function", "number" }, + max = { "string", "function", "number" }, + lines_excluded = { "table" }, + padding = { "function", "number" }, + }, + }, + renderer = { + hidden_display = { "function", "string" }, + group_empty = { "boolean", "function" }, + root_folder_label = { "function", "string", "boolean" }, + }, + update_focused_file = { + exclude = { "function" }, + }, + git = { + disable_for_dirs = { "function" }, + }, + filters = { + custom = { "function" }, + }, + filesystem_watchers = { + ignore_dirs = { "function" }, + }, + actions = { + open_file = { + window_picker = { + picker = { "function", "string" }, + }, + }, + }, + bookmarks = { + persist = { "boolean", "string" }, + }, } local ACCEPTED_STRINGS = { - sort = { - sorter = { "name", "case_sensitive", "modification_time", "extension", "suffix", "filetype" }, - }, - view = { - side = { "left", "right" }, - signcolumn = { "yes", "no", "auto" }, - }, - renderer = { - hidden_display = { "none", "simple", "all" }, - highlight_git = { "none", "icon", "name", "all" }, - highlight_opened_files = { "none", "icon", "name", "all" }, - highlight_modified = { "none", "icon", "name", "all" }, - highlight_hidden = { "none", "icon", "name", "all" }, - highlight_bookmarks = { "none", "icon", "name", "all" }, - highlight_diagnostics = { "none", "icon", "name", "all" }, - highlight_clipboard = { "none", "icon", "name", "all" }, - icons = { - git_placement = { "before", "after", "signcolumn", "right_align" }, - modified_placement = { "before", "after", "signcolumn", "right_align" }, - hidden_placement = { "before", "after", "signcolumn", "right_align" }, - diagnostics_placement = { "before", "after", "signcolumn", "right_align" }, - bookmarks_placement = { "before", "after", "signcolumn", "right_align" }, - }, - }, - help = { - sort_by = { "key", "desc" }, - }, + sort = { + sorter = { "name", "case_sensitive", "modification_time", "extension", "suffix", "filetype" }, + }, + view = { + side = { "left", "right" }, + signcolumn = { "yes", "no", "auto" }, + }, + renderer = { + hidden_display = { "none", "simple", "all" }, + highlight_git = { "none", "icon", "name", "all" }, + highlight_opened_files = { "none", "icon", "name", "all" }, + highlight_modified = { "none", "icon", "name", "all" }, + highlight_hidden = { "none", "icon", "name", "all" }, + highlight_bookmarks = { "none", "icon", "name", "all" }, + highlight_diagnostics = { "none", "icon", "name", "all" }, + highlight_clipboard = { "none", "icon", "name", "all" }, + icons = { + git_placement = { "before", "after", "signcolumn", "right_align" }, + modified_placement = { "before", "after", "signcolumn", "right_align" }, + hidden_placement = { "before", "after", "signcolumn", "right_align" }, + diagnostics_placement = { "before", "after", "signcolumn", "right_align" }, + bookmarks_placement = { "before", "after", "signcolumn", "right_align" }, + }, + }, + help = { + sort_by = { "key", "desc" }, + }, } local ACCEPTED_ENUMS = { - view = { - width = { - lines_excluded = { "root", }, - }, - }, + view = { + width = { + lines_excluded = { "root" }, + }, + }, } ---@param conf? nvim_tree.config local function validate_options(conf) - local msg - - ---@param user any - ---@param def any - ---@param strs table - ---@param types table - ---@param enums table - ---@param prefix string - local function validate(user, def, strs, types, enums, prefix) - -- if user's option is not a table there is nothing to do - if type(user) ~= "table" then - return - end - - -- we have hit a leaf enum to validate against - it's an integer indexed table - local enum_value = type(enums) == "table" and next(enums) and type(next(enums)) == "number" - - -- only compare tables with contents that are not integer indexed nor enums - if not enum_value and (type(def) ~= "table" or not next(def) or type(next(def)) == "number") then - -- unless the field can be a table (and is not a table in default config) - if vim.tbl_contains(types, "table") then - -- use a dummy default to allow all checks - def = {} - else - return - end - end - - for k, v in pairs(user) do - if not FIELD_SKIP_VALIDATE[k] then - local invalid - - if enum_value then - if not vim.tbl_contains(enums, v) then - invalid = string.format("Invalid value for field %s%s: Expected one of enum '%s', got '%s'", prefix, k, - table.concat(enums, "'|'"), tostring(v)) - end - else - if def[k] == nil and types[k] == nil then - -- option does not exist - invalid = string.format("Unknown option: %s%s", prefix, k) - elseif type(v) ~= type(def[k]) then - local expected - - if types[k] and #types[k] > 0 then - if not vim.tbl_contains(types[k], type(v)) then - expected = table.concat(types[k], "|") - end - else - expected = type(def[k]) - end - - if expected then - -- option is of the wrong type - invalid = string.format("Invalid option: %s%s. Expected %s, got %s", prefix, k, expected, type(v)) - end - elseif type(v) == "string" and strs[k] and not vim.tbl_contains(strs[k], v) then - -- option has type `string` but value is not accepted - invalid = string.format("Invalid value for field %s%s: '%s'", prefix, k, v) - end - end - - if invalid then - if msg then - msg = string.format("%s\n%s", msg, invalid) - else - msg = invalid - end - user[k] = nil - elseif not enum_value then - validate(v, def[k], strs[k] or {}, types[k] or {}, enums[k] or {}, prefix .. k .. ".") - end - end - end - end - - validate(conf, DEFAULT_OPTS, ACCEPTED_STRINGS, ACCEPTED_TYPES, ACCEPTED_ENUMS, "") - - if msg then - notify.warn(msg .. "\n\nsee :help nvim-tree-opts for available configuration options") - end + local msg + + ---@param user any + ---@param def any + ---@param strs table + ---@param types table + ---@param enums table + ---@param prefix string + local function validate(user, def, strs, types, enums, prefix) + -- if user's option is not a table there is nothing to do + if type(user) ~= "table" then + return + end + + -- we have hit a leaf enum to validate against - it's an integer indexed table + local enum_value = type(enums) == "table" and next(enums) and type(next(enums)) == "number" + + -- only compare tables with contents that are not integer indexed nor enums + if not enum_value and (type(def) ~= "table" or not next(def) or type(next(def)) == "number") then + -- unless the field can be a table (and is not a table in default config) + if vim.tbl_contains(types, "table") then + -- use a dummy default to allow all checks + def = {} + else + return + end + end + + for k, v in pairs(user) do + if not FIELD_SKIP_VALIDATE[k] then + local invalid + + if enum_value then + if not vim.tbl_contains(enums, v) then + invalid = string.format( + "Invalid value for field %s%s: Expected one of enum '%s', got '%s'", + prefix, + k, + table.concat(enums, "'|'"), + tostring(v) + ) + end + else + if def[k] == nil and types[k] == nil then + -- option does not exist + invalid = string.format("Unknown option: %s%s", prefix, k) + elseif type(v) ~= type(def[k]) then + local expected + + if types[k] and #types[k] > 0 then + if not vim.tbl_contains(types[k], type(v)) then + expected = table.concat(types[k], "|") + end + else + expected = type(def[k]) + end + + if expected then + -- option is of the wrong type + invalid = + string.format("Invalid option: %s%s. Expected %s, got %s", prefix, k, expected, type(v)) + end + elseif type(v) == "string" and strs[k] and not vim.tbl_contains(strs[k], v) then + -- option has type `string` but value is not accepted + invalid = string.format("Invalid value for field %s%s: '%s'", prefix, k, v) + end + end + + if invalid then + if msg then + msg = string.format("%s\n%s", msg, invalid) + else + msg = invalid + end + user[k] = nil + elseif not enum_value then + validate(v, def[k], strs[k] or {}, types[k] or {}, enums[k] or {}, prefix .. k .. ".") + end + end + end + end + + validate(conf, DEFAULT_OPTS, ACCEPTED_STRINGS, ACCEPTED_TYPES, ACCEPTED_ENUMS, "") + + if msg then + notify.warn(msg .. "\n\nsee :help nvim-tree-opts for available configuration options") + end end --- Apply OS specific localisations to DEFAULT_OPTS local function localise_default_opts() - if utils.is_macos or utils.is_windows then - DEFAULT_OPTS.trash.cmd = "trash" - end + if utils.is_macos or utils.is_windows then + DEFAULT_OPTS.trash.cmd = "trash" + end end function M.purge_all_state() - view.close_all_tabs() - view.abandon_all_windows() - local explorer = core.get_explorer() - if explorer then - require("nvim-tree.git").purge_state() - explorer:destroy() - core.reset_explorer() - end - -- purge orphaned that were not destroyed by their nodes - require("nvim-tree.watcher").purge_watchers() + view.close_all_tabs() + view.abandon_all_windows() + local explorer = core.get_explorer() + if explorer then + require("nvim-tree.git").purge_state() + explorer:destroy() + core.reset_explorer() + end + -- purge orphaned that were not destroyed by their nodes + require("nvim-tree.watcher").purge_watchers() end ---@param conf? nvim_tree.config function M.setup(conf) - if vim.fn.has("nvim-0.9") == 0 then - notify.warn("nvim-tree.lua requires Neovim 0.9 or higher") - return - end + if vim.fn.has("nvim-0.9") == 0 then + notify.warn("nvim-tree.lua requires Neovim 0.9 or higher") + return + end - M.init_root = vim.fn.getcwd() + M.init_root = vim.fn.getcwd() - localise_default_opts() + localise_default_opts() - require("nvim-tree.legacy").migrate_legacy_options(conf or {}) + require("nvim-tree.legacy").migrate_legacy_options(conf or {}) - validate_options(conf) + validate_options(conf) - local opts = merge_options(conf) + local opts = merge_options(conf) - local netrw_disabled = opts.disable_netrw or opts.hijack_netrw + local netrw_disabled = opts.disable_netrw or opts.hijack_netrw - _config.root_dirs = opts.root_dirs - _config.prefer_startup_root = opts.prefer_startup_root - _config.update_focused_file = opts.update_focused_file - _config.hijack_directories = opts.hijack_directories - _config.hijack_directories.enable = _config.hijack_directories.enable and netrw_disabled + _config.root_dirs = opts.root_dirs + _config.prefer_startup_root = opts.prefer_startup_root + _config.update_focused_file = opts.update_focused_file + _config.hijack_directories = opts.hijack_directories + _config.hijack_directories.enable = _config.hijack_directories.enable and netrw_disabled - manage_netrw(opts.disable_netrw, opts.hijack_netrw) + manage_netrw(opts.disable_netrw, opts.hijack_netrw) - M.config = opts - require("nvim-tree.notify").setup(opts) - require("nvim-tree.log").setup(opts) + M.config = opts + require("nvim-tree.notify").setup(opts) + require("nvim-tree.log").setup(opts) - if log.enabled("config") then - log.line("config", "default config + user") - log.raw("config", "%s\n", vim.inspect(opts)) - end + if log.enabled("config") then + log.line("config", "default config + user") + log.raw("config", "%s\n", vim.inspect(opts)) + end - require("nvim-tree.actions").setup(opts) - require("nvim-tree.keymap").setup(opts) - require("nvim-tree.appearance").setup() - require("nvim-tree.diagnostics").setup(opts) - require("nvim-tree.explorer"):setup(opts) - require("nvim-tree.explorer.watch").setup(opts) - require("nvim-tree.git").setup(opts) - require("nvim-tree.git.utils").setup(opts) - require("nvim-tree.view").setup(opts) - require("nvim-tree.lib").setup(opts) - require("nvim-tree.renderer.components").setup(opts) - require("nvim-tree.buffers").setup(opts) - require("nvim-tree.help").setup(opts) - require("nvim-tree.watcher").setup(opts) + require("nvim-tree.actions").setup(opts) + require("nvim-tree.keymap").setup(opts) + require("nvim-tree.appearance").setup() + require("nvim-tree.diagnostics").setup(opts) + require("nvim-tree.explorer"):setup(opts) + require("nvim-tree.explorer.watch").setup(opts) + require("nvim-tree.git").setup(opts) + require("nvim-tree.git.utils").setup(opts) + require("nvim-tree.view").setup(opts) + require("nvim-tree.lib").setup(opts) + require("nvim-tree.renderer.components").setup(opts) + require("nvim-tree.buffers").setup(opts) + require("nvim-tree.help").setup(opts) + require("nvim-tree.watcher").setup(opts) - setup_autocommands(opts) + setup_autocommands(opts) - if vim.g.NvimTreeSetup == 1 then - -- subsequent calls to setup - M.purge_all_state() - end + if vim.g.NvimTreeSetup == 1 then + -- subsequent calls to setup + M.purge_all_state() + end - vim.g.NvimTreeSetup = 1 - vim.api.nvim_exec_autocmds("User", { pattern = "NvimTreeSetup" }) + vim.g.NvimTreeSetup = 1 + vim.api.nvim_exec_autocmds("User", { pattern = "NvimTreeSetup" }) end vim.g.NvimTreeRequired = 1 diff --git a/lua/nvim-tree/explorer/watch.lua b/lua/nvim-tree/explorer/watch.lua index 94f6e30fdd9..855821e86d8 100644 --- a/lua/nvim-tree/explorer/watch.lua +++ b/lua/nvim-tree/explorer/watch.lua @@ -5,123 +5,125 @@ local notify = require("nvim-tree.notify") local Watcher = require("nvim-tree.watcher").Watcher local M = { - config = {}, - uid = 0, + config = {}, + uid = 0, } ---@param path string ---@return boolean local function is_git(path) - -- If $GIT_DIR is set, consider its value to be equivalent to '.git'. - -- Expand $GIT_DIR (and `path`) to a full path (see :help filename-modifiers), since - -- it's possible to set it to a relative path. We want to make our best - -- effort to expand that to a valid absolute path. - if vim.fn.fnamemodify(path, ":p") == vim.fn.fnamemodify(vim.env.GIT_DIR, ":p") then - return true - elseif vim.fn.fnamemodify(path, ":t") == ".git" then - return true - else - return false - end + -- If $GIT_DIR is set, consider its value to be equivalent to '.git'. + -- Expand $GIT_DIR (and `path`) to a full path (see :help filename-modifiers), since + -- it's possible to set it to a relative path. We want to make our best + -- effort to expand that to a valid absolute path. + if vim.fn.fnamemodify(path, ":p") == vim.fn.fnamemodify(vim.env.GIT_DIR, ":p") then + return true + elseif vim.fn.fnamemodify(path, ":t") == ".git" then + return true + else + return false + end end local IGNORED_PATHS = { - -- disable watchers on kernel filesystems - -- which have a lot of unwanted events - "/sys", - "/proc", - "/dev", + -- disable watchers on kernel filesystems + -- which have a lot of unwanted events + "/sys", + "/proc", + "/dev", } ---@param path string ---@return boolean local function is_folder_ignored(path) - for _, folder in ipairs(IGNORED_PATHS) do - if vim.startswith(path, folder) then - return true - end - end - - if type(M.config.filesystem_watchers.ignore_dirs) == "table" then - for _, ignore_dir in ipairs(M.config.filesystem_watchers.ignore_dirs) do - if utils.is_windows or true then - ignore_dir = utils.escape_special_chars(ignore_dir) or ignore_dir - print(ignore_dir) - end - - if vim.fn.match(path, ignore_dir) ~= -1 then - return true - end - end - elseif type(M.config.filesystem_watchers.ignore_dirs) == "function" then - return M.config.filesystem_watchers.ignore_dirs(path) - end - - return false + for _, folder in ipairs(IGNORED_PATHS) do + if vim.startswith(path, folder) then + return true + end + end + + if type(M.config.filesystem_watchers.ignore_dirs) == "table" then + print(vim.inspect(M.config.filesystem_watchers.ignore_dirs)) + for _, ignore_dir in ipairs(M.config.filesystem_watchers.ignore_dirs) do + if utils.is_windows or true then + ignore_dir = ignore_dir:gsub("/", "\\") or ignore_dir + end + + if vim.fn.match(path, ignore_dir) ~= -1 then + return true + end + end + elseif type(M.config.filesystem_watchers.ignore_dirs) == "function" then + return M.config.filesystem_watchers.ignore_dirs(path) + end + + return false end ---@param node DirectoryNode ---@return Watcher|nil function M.create_watcher(node) - if not M.config.filesystem_watchers.enable or type(node) ~= "table" then - return nil - end - - local path = node.link_to or node.absolute_path - if is_git(path) or is_folder_ignored(path) then - return nil - end - - ---@param watcher Watcher - local function callback(watcher) - log.line("watcher", "node event scheduled refresh %s", watcher.data.context) - - -- event is awaiting debouncing and handling - watcher.data.outstanding_events = watcher.data.outstanding_events + 1 - - -- disable watcher when outstanding exceeds max - if watcher.data.outstanding_events > M.config.filesystem_watchers.max_events then - notify.error(string.format( - "Observed %d consecutive file system events with interval < %dms, exceeding filesystem_watchers.max_events=%s. Disabling watcher for directory '%s'. Consider adding this directory to filesystem_watchers.ignore_dirs", - watcher.data.outstanding_events, - M.config.filesystem_watchers.max_events, - M.config.filesystem_watchers.debounce_delay, - node.absolute_path - )) - node:destroy_watcher() - end - - utils.debounce(watcher.data.context, M.config.filesystem_watchers.debounce_delay, function() - if watcher.destroyed then - return - end - - -- event has been handled - watcher.data.outstanding_events = 0 - - if node.link_to then - log.line("watcher", "node event executing refresh '%s' -> '%s'", node.link_to, node.absolute_path) - else - log.line("watcher", "node event executing refresh '%s'", node.absolute_path) - end - git.refresh_dir(node) - end) - end - - M.uid = M.uid + 1 - return Watcher:create({ - path = path, - callback = callback, - data = { - context = "explorer:watch:" .. path .. ":" .. M.uid, - outstanding_events = 0, -- unprocessed events that have not been debounced - } - }) + if not M.config.filesystem_watchers.enable or type(node) ~= "table" then + return nil + end + + local path = node.link_to or node.absolute_path + if is_git(path) or is_folder_ignored(path) then + return nil + end + + ---@param watcher Watcher + local function callback(watcher) + log.line("watcher", "node event scheduled refresh %s", watcher.data.context) + + -- event is awaiting debouncing and handling + watcher.data.outstanding_events = watcher.data.outstanding_events + 1 + + -- disable watcher when outstanding exceeds max + if watcher.data.outstanding_events > M.config.filesystem_watchers.max_events then + notify.error( + string.format( + "Observed %d consecutive file system events with interval < %dms, exceeding filesystem_watchers.max_events=%s. Disabling watcher for directory '%s'. Consider adding this directory to filesystem_watchers.ignore_dirs", + watcher.data.outstanding_events, + M.config.filesystem_watchers.max_events, + M.config.filesystem_watchers.debounce_delay, + node.absolute_path + ) + ) + node:destroy_watcher() + end + + utils.debounce(watcher.data.context, M.config.filesystem_watchers.debounce_delay, function() + if watcher.destroyed then + return + end + + -- event has been handled + watcher.data.outstanding_events = 0 + + if node.link_to then + log.line("watcher", "node event executing refresh '%s' -> '%s'", node.link_to, node.absolute_path) + else + log.line("watcher", "node event executing refresh '%s'", node.absolute_path) + end + git.refresh_dir(node) + end) + end + + M.uid = M.uid + 1 + return Watcher:create({ + path = path, + callback = callback, + data = { + context = "explorer:watch:" .. path .. ":" .. M.uid, + outstanding_events = 0, -- unprocessed events that have not been debounced + }, + }) end function M.setup(opts) - M.config.filesystem_watchers = opts.filesystem_watchers - M.uid = 0 + M.config.filesystem_watchers = opts.filesystem_watchers + M.uid = 0 end return M From dc30754efebc8526dda0eb17e1990ca62f4badf5 Mon Sep 17 00:00:00 2001 From: uanela Date: Wed, 11 Feb 2026 11:02:12 +0200 Subject: [PATCH 4/6] fix: remove unsed print statement --- lua/nvim-tree.lua | 9 +- lua/nvim-tree/explorer/watch.lua | 195 +++++++++++++++---------------- 2 files changed, 100 insertions(+), 104 deletions(-) diff --git a/lua/nvim-tree.lua b/lua/nvim-tree.lua index aaf1d35ba05..f34333bd37f 100644 --- a/lua/nvim-tree.lua +++ b/lua/nvim-tree.lua @@ -681,13 +681,11 @@ local function validate_options(conf) if enum_value then if not vim.tbl_contains(enums, v) then - invalid = string.format( - "Invalid value for field %s%s: Expected one of enum '%s', got '%s'", + invalid = string.format("Invalid value for field %s%s: Expected one of enum '%s', got '%s'", prefix, k, table.concat(enums, "'|'"), - tostring(v) - ) + tostring(v)) end else if def[k] == nil and types[k] == nil then @@ -706,8 +704,7 @@ local function validate_options(conf) if expected then -- option is of the wrong type - invalid = - string.format("Invalid option: %s%s. Expected %s, got %s", prefix, k, expected, type(v)) + invalid = string.format("Invalid option: %s%s. Expected %s, got %s", prefix, k, expected, type(v)) end elseif type(v) == "string" and strs[k] and not vim.tbl_contains(strs[k], v) then -- option has type `string` but value is not accepted diff --git a/lua/nvim-tree/explorer/watch.lua b/lua/nvim-tree/explorer/watch.lua index 855821e86d8..bae4f531652 100644 --- a/lua/nvim-tree/explorer/watch.lua +++ b/lua/nvim-tree/explorer/watch.lua @@ -5,125 +5,124 @@ local notify = require("nvim-tree.notify") local Watcher = require("nvim-tree.watcher").Watcher local M = { - config = {}, - uid = 0, + config = {}, + uid = 0, } ---@param path string ---@return boolean local function is_git(path) - -- If $GIT_DIR is set, consider its value to be equivalent to '.git'. - -- Expand $GIT_DIR (and `path`) to a full path (see :help filename-modifiers), since - -- it's possible to set it to a relative path. We want to make our best - -- effort to expand that to a valid absolute path. - if vim.fn.fnamemodify(path, ":p") == vim.fn.fnamemodify(vim.env.GIT_DIR, ":p") then - return true - elseif vim.fn.fnamemodify(path, ":t") == ".git" then - return true - else - return false - end + -- If $GIT_DIR is set, consider its value to be equivalent to '.git'. + -- Expand $GIT_DIR (and `path`) to a full path (see :help filename-modifiers), since + -- it's possible to set it to a relative path. We want to make our best + -- effort to expand that to a valid absolute path. + if vim.fn.fnamemodify(path, ":p") == vim.fn.fnamemodify(vim.env.GIT_DIR, ":p") then + return true + elseif vim.fn.fnamemodify(path, ":t") == ".git" then + return true + else + return false + end end local IGNORED_PATHS = { - -- disable watchers on kernel filesystems - -- which have a lot of unwanted events - "/sys", - "/proc", - "/dev", + -- disable watchers on kernel filesystems + -- which have a lot of unwanted events + "/sys", + "/proc", + "/dev", } ---@param path string ---@return boolean local function is_folder_ignored(path) - for _, folder in ipairs(IGNORED_PATHS) do - if vim.startswith(path, folder) then - return true - end - end - - if type(M.config.filesystem_watchers.ignore_dirs) == "table" then - print(vim.inspect(M.config.filesystem_watchers.ignore_dirs)) - for _, ignore_dir in ipairs(M.config.filesystem_watchers.ignore_dirs) do - if utils.is_windows or true then - ignore_dir = ignore_dir:gsub("/", "\\") or ignore_dir - end - - if vim.fn.match(path, ignore_dir) ~= -1 then - return true - end - end - elseif type(M.config.filesystem_watchers.ignore_dirs) == "function" then - return M.config.filesystem_watchers.ignore_dirs(path) - end - - return false + for _, folder in ipairs(IGNORED_PATHS) do + if vim.startswith(path, folder) then + return true + end + end + + if type(M.config.filesystem_watchers.ignore_dirs) == "table" then + for _, ignore_dir in ipairs(M.config.filesystem_watchers.ignore_dirs) do + if utils.is_windows or true then + ignore_dir = ignore_dir:gsub("/", "\\") or ignore_dir + end + + if vim.fn.match(path, ignore_dir) ~= -1 then + return true + end + end + elseif type(M.config.filesystem_watchers.ignore_dirs) == "function" then + return M.config.filesystem_watchers.ignore_dirs(path) + end + + return false end ---@param node DirectoryNode ---@return Watcher|nil function M.create_watcher(node) - if not M.config.filesystem_watchers.enable or type(node) ~= "table" then - return nil - end - - local path = node.link_to or node.absolute_path - if is_git(path) or is_folder_ignored(path) then - return nil - end - - ---@param watcher Watcher - local function callback(watcher) - log.line("watcher", "node event scheduled refresh %s", watcher.data.context) - - -- event is awaiting debouncing and handling - watcher.data.outstanding_events = watcher.data.outstanding_events + 1 - - -- disable watcher when outstanding exceeds max - if watcher.data.outstanding_events > M.config.filesystem_watchers.max_events then - notify.error( - string.format( - "Observed %d consecutive file system events with interval < %dms, exceeding filesystem_watchers.max_events=%s. Disabling watcher for directory '%s'. Consider adding this directory to filesystem_watchers.ignore_dirs", - watcher.data.outstanding_events, - M.config.filesystem_watchers.max_events, - M.config.filesystem_watchers.debounce_delay, - node.absolute_path - ) - ) - node:destroy_watcher() - end - - utils.debounce(watcher.data.context, M.config.filesystem_watchers.debounce_delay, function() - if watcher.destroyed then - return - end - - -- event has been handled - watcher.data.outstanding_events = 0 - - if node.link_to then - log.line("watcher", "node event executing refresh '%s' -> '%s'", node.link_to, node.absolute_path) - else - log.line("watcher", "node event executing refresh '%s'", node.absolute_path) - end - git.refresh_dir(node) - end) - end - - M.uid = M.uid + 1 - return Watcher:create({ - path = path, - callback = callback, - data = { - context = "explorer:watch:" .. path .. ":" .. M.uid, - outstanding_events = 0, -- unprocessed events that have not been debounced - }, - }) + if not M.config.filesystem_watchers.enable or type(node) ~= "table" then + return nil + end + + local path = node.link_to or node.absolute_path + if is_git(path) or is_folder_ignored(path) then + return nil + end + + ---@param watcher Watcher + local function callback(watcher) + log.line("watcher", "node event scheduled refresh %s", watcher.data.context) + + -- event is awaiting debouncing and handling + watcher.data.outstanding_events = watcher.data.outstanding_events + 1 + + -- disable watcher when outstanding exceeds max + if watcher.data.outstanding_events > M.config.filesystem_watchers.max_events then + notify.error( + string.format( + "Observed %d consecutive file system events with interval < %dms, exceeding filesystem_watchers.max_events=%s. Disabling watcher for directory '%s'. Consider adding this directory to filesystem_watchers.ignore_dirs", + watcher.data.outstanding_events, + M.config.filesystem_watchers.max_events, + M.config.filesystem_watchers.debounce_delay, + node.absolute_path + ) + ) + node:destroy_watcher() + end + + utils.debounce(watcher.data.context, M.config.filesystem_watchers.debounce_delay, function() + if watcher.destroyed then + return + end + + -- event has been handled + watcher.data.outstanding_events = 0 + + if node.link_to then + log.line("watcher", "node event executing refresh '%s' -> '%s'", node.link_to, node.absolute_path) + else + log.line("watcher", "node event executing refresh '%s'", node.absolute_path) + end + git.refresh_dir(node) + end) + end + + M.uid = M.uid + 1 + return Watcher:create({ + path = path, + callback = callback, + data = { + context = "explorer:watch:" .. path .. ":" .. M.uid, + outstanding_events = 0, -- unprocessed events that have not been debounced + }, + }) end function M.setup(opts) - M.config.filesystem_watchers = opts.filesystem_watchers - M.uid = 0 + M.config.filesystem_watchers = opts.filesystem_watchers + M.uid = 0 end return M From 7ac15ccd50968d590f06895dddd8d54f5c15fa36 Mon Sep 17 00:00:00 2001 From: uanela Date: Wed, 11 Feb 2026 11:15:36 +0200 Subject: [PATCH 5/6] fix: tab formatting --- lua/nvim-tree.lua | 1440 ++++++++++++++++++++++----------------------- 1 file changed, 720 insertions(+), 720 deletions(-) diff --git a/lua/nvim-tree.lua b/lua/nvim-tree.lua index f34333bd37f..52ae7cfc655 100644 --- a/lua/nvim-tree.lua +++ b/lua/nvim-tree.lua @@ -8,7 +8,7 @@ local notify = require("nvim-tree.notify") local _config = {} local M = { - init_root = "", + init_root = "", } --- Helper function to execute some explorer method safely @@ -16,803 +16,803 @@ local M = { ---@param ... any|nil ---@return function|nil local function explorer_fn(fn, ...) - local explorer = core.get_explorer() - if explorer then - return explorer[fn](explorer, ...) - end + local explorer = core.get_explorer() + if explorer then + return explorer[fn](explorer, ...) + end end --- Update the tree root to a directory or the directory containing ---@param path string relative or absolute ---@param bufnr number|nil function M.change_root(path, bufnr) - -- skip if current file is in ignore_list - if type(bufnr) == "number" then - local ft - - if vim.fn.has("nvim-0.10") == 1 then - ft = vim.api.nvim_get_option_value("filetype", { buf = bufnr }) or "" - else - ft = vim.api.nvim_buf_get_option(bufnr, "filetype") or "" ---@diagnostic disable-line: deprecated - end - - for _, value in pairs(_config.update_focused_file.update_root.ignore_list) do - if utils.str_find(path, value) or utils.str_find(ft, value) then - return - end - end - end - - -- don't find inexistent - if vim.fn.filereadable(path) == 0 then - return - end - - local cwd = core.get_cwd() - if cwd == nil then - return - end - - local vim_cwd = vim.fn.getcwd() - - -- test if in vim_cwd - if utils.path_relative(path, vim_cwd) ~= path then - if vim_cwd ~= cwd then - explorer_fn("change_dir", vim_cwd) - end - return - end - -- test if in cwd - if utils.path_relative(path, cwd) ~= path then - return - end - - -- otherwise test M.init_root - if _config.prefer_startup_root and utils.path_relative(path, M.init_root) ~= path then - explorer_fn("change_dir", M.init_root) - return - end - -- otherwise root_dirs - for _, dir in pairs(_config.root_dirs) do - dir = vim.fn.fnamemodify(dir, ":p") - if utils.path_relative(path, dir) ~= path then - explorer_fn("change_dir", dir) - return - end - end - -- finally fall back to the folder containing the file - explorer_fn("change_dir", vim.fn.fnamemodify(path, ":p:h")) + -- skip if current file is in ignore_list + if type(bufnr) == "number" then + local ft + + if vim.fn.has("nvim-0.10") == 1 then + ft = vim.api.nvim_get_option_value("filetype", { buf = bufnr }) or "" + else + ft = vim.api.nvim_buf_get_option(bufnr, "filetype") or "" ---@diagnostic disable-line: deprecated + end + + for _, value in pairs(_config.update_focused_file.update_root.ignore_list) do + if utils.str_find(path, value) or utils.str_find(ft, value) then + return + end + end + end + + -- don't find inexistent + if vim.fn.filereadable(path) == 0 then + return + end + + local cwd = core.get_cwd() + if cwd == nil then + return + end + + local vim_cwd = vim.fn.getcwd() + + -- test if in vim_cwd + if utils.path_relative(path, vim_cwd) ~= path then + if vim_cwd ~= cwd then + explorer_fn("change_dir", vim_cwd) + end + return + end + -- test if in cwd + if utils.path_relative(path, cwd) ~= path then + return + end + + -- otherwise test M.init_root + if _config.prefer_startup_root and utils.path_relative(path, M.init_root) ~= path then + explorer_fn("change_dir", M.init_root) + return + end + -- otherwise root_dirs + for _, dir in pairs(_config.root_dirs) do + dir = vim.fn.fnamemodify(dir, ":p") + if utils.path_relative(path, dir) ~= path then + explorer_fn("change_dir", dir) + return + end + end + -- finally fall back to the folder containing the file + explorer_fn("change_dir", vim.fn.fnamemodify(path, ":p:h")) end function M.tab_enter() - if view.is_visible({ any_tabpage = true }) then - local bufname = vim.api.nvim_buf_get_name(0) - - local ft - if vim.fn.has("nvim-0.10") == 1 then - ft = vim.api.nvim_get_option_value("filetype", { buf = 0 }) or "" - else - ft = vim.api.nvim_buf_get_option(0, "ft") ---@diagnostic disable-line: deprecated - end - - for _, filter in ipairs(M.config.tab.sync.ignore) do - if bufname:match(filter) ~= nil or ft:match(filter) ~= nil then - return - end - end - view.open({ focus_tree = false }) - - local explorer = core.get_explorer() - if explorer then - explorer.renderer:draw() - end - end + if view.is_visible({ any_tabpage = true }) then + local bufname = vim.api.nvim_buf_get_name(0) + + local ft + if vim.fn.has("nvim-0.10") == 1 then + ft = vim.api.nvim_get_option_value("filetype", { buf = 0 }) or "" + else + ft = vim.api.nvim_buf_get_option(0, "ft") ---@diagnostic disable-line: deprecated + end + + for _, filter in ipairs(M.config.tab.sync.ignore) do + if bufname:match(filter) ~= nil or ft:match(filter) ~= nil then + return + end + end + view.open({ focus_tree = false }) + + local explorer = core.get_explorer() + if explorer then + explorer.renderer:draw() + end + end end function M.open_on_directory() - local should_proceed = _config.hijack_directories.auto_open or view.is_visible() - if not should_proceed then - return - end - - local buf = vim.api.nvim_get_current_buf() - local bufname = vim.api.nvim_buf_get_name(buf) - if vim.fn.isdirectory(bufname) ~= 1 then - return - end - - local explorer = core.get_explorer() - if not explorer then - core.init(bufname) - end - - explorer_fn("force_dirchange", bufname, true, false) + local should_proceed = _config.hijack_directories.auto_open or view.is_visible() + if not should_proceed then + return + end + + local buf = vim.api.nvim_get_current_buf() + local bufname = vim.api.nvim_buf_get_name(buf) + if vim.fn.isdirectory(bufname) ~= 1 then + return + end + + local explorer = core.get_explorer() + if not explorer then + core.init(bufname) + end + + explorer_fn("force_dirchange", bufname, true, false) end ---@return table function M.get_config() - return M.config + return M.config end ---@param disable_netrw boolean ---@param hijack_netrw boolean local function manage_netrw(disable_netrw, hijack_netrw) - if hijack_netrw then - vim.cmd("silent! autocmd! FileExplorer *") - vim.cmd("autocmd VimEnter * ++once silent! autocmd! FileExplorer *") - end - if disable_netrw then - vim.g.loaded_netrw = 1 - vim.g.loaded_netrwPlugin = 1 - end + if hijack_netrw then + vim.cmd("silent! autocmd! FileExplorer *") + vim.cmd("autocmd VimEnter * ++once silent! autocmd! FileExplorer *") + end + if disable_netrw then + vim.g.loaded_netrw = 1 + vim.g.loaded_netrwPlugin = 1 + end end ---@param name string|nil function M.change_dir(name) - if name then - explorer_fn("change_dir", name) - end + if name then + explorer_fn("change_dir", name) + end - if _config.update_focused_file.update_root.enable then - actions.tree.find_file.fn() - end + if _config.update_focused_file.update_root.enable then + actions.tree.find_file.fn() + end end ---@param opts table local function setup_autocommands(opts) - local augroup_id = vim.api.nvim_create_augroup("NvimTree", { clear = true }) - local function create_nvim_tree_autocmd(name, custom_opts) - local default_opts = { group = augroup_id } - vim.api.nvim_create_autocmd(name, vim.tbl_extend("force", default_opts, custom_opts)) - end - - -- prevent new opened file from opening in the same window as nvim-tree - create_nvim_tree_autocmd("BufWipeout", { - pattern = "NvimTree_*", - callback = function() - if not utils.is_nvim_tree_buf(0) then - return - end - if opts.actions.open_file.eject then - view._prevent_buffer_override() - else - view.abandon_current_window() - end - end, - }) - - if opts.tab.sync.open then - create_nvim_tree_autocmd("TabEnter", { callback = vim.schedule_wrap(M.tab_enter) }) - end - if opts.sync_root_with_cwd then - create_nvim_tree_autocmd("DirChanged", { - callback = function() - M.change_dir(vim.loop.cwd()) - end, - }) - end - if opts.update_focused_file.enable then - create_nvim_tree_autocmd("BufEnter", { - callback = function(event) - local exclude = opts.update_focused_file.exclude - if type(exclude) == "function" and exclude(event) then - return - end - utils.debounce("BufEnter:find_file", opts.view.debounce_delay, function() - actions.tree.find_file.fn() - end) - end, - }) - end - - if opts.hijack_directories.enable then - create_nvim_tree_autocmd({ "BufEnter", "BufNewFile" }, { callback = M.open_on_directory, nested = true }) - end - - if opts.view.centralize_selection then - create_nvim_tree_autocmd("BufEnter", { - pattern = "NvimTree_*", - callback = function() - vim.schedule(function() - vim.api.nvim_buf_call(0, function() - local is_term_mode = vim.api.nvim_get_mode().mode == "t" - if is_term_mode then - return - end - vim.cmd([[norm! zz]]) - end) - end) - end, - }) - end - - if opts.diagnostics.enable then - create_nvim_tree_autocmd("DiagnosticChanged", { - callback = function(ev) - log.line("diagnostics", "DiagnosticChanged") - require("nvim-tree.diagnostics").update_lsp(ev) - end, - }) - create_nvim_tree_autocmd("User", { - pattern = "CocDiagnosticChange", - callback = function() - log.line("diagnostics", "CocDiagnosticChange") - require("nvim-tree.diagnostics").update_coc() - end, - }) - end - - if opts.view.float.enable and opts.view.float.quit_on_focus_loss then - create_nvim_tree_autocmd("WinLeave", { - pattern = "NvimTree_*", - callback = function() - if utils.is_nvim_tree_buf(0) then - view.close() - end - end, - }) - end - - -- Handles event dispatch when tree is closed by `:q` - create_nvim_tree_autocmd("WinClosed", { - pattern = "*", - ---@param ev vim.api.keyset.create_autocmd.callback_args - callback = function(ev) - if not vim.api.nvim_buf_is_valid(ev.buf) then - return - end - if vim.api.nvim_get_option_value("filetype", { buf = ev.buf }) == "NvimTree" then - require("nvim-tree.events")._dispatch_on_tree_close() - end - end, - }) + local augroup_id = vim.api.nvim_create_augroup("NvimTree", { clear = true }) + local function create_nvim_tree_autocmd(name, custom_opts) + local default_opts = { group = augroup_id } + vim.api.nvim_create_autocmd(name, vim.tbl_extend("force", default_opts, custom_opts)) + end + + -- prevent new opened file from opening in the same window as nvim-tree + create_nvim_tree_autocmd("BufWipeout", { + pattern = "NvimTree_*", + callback = function() + if not utils.is_nvim_tree_buf(0) then + return + end + if opts.actions.open_file.eject then + view._prevent_buffer_override() + else + view.abandon_current_window() + end + end, + }) + + if opts.tab.sync.open then + create_nvim_tree_autocmd("TabEnter", { callback = vim.schedule_wrap(M.tab_enter) }) + end + if opts.sync_root_with_cwd then + create_nvim_tree_autocmd("DirChanged", { + callback = function() + M.change_dir(vim.loop.cwd()) + end, + }) + end + if opts.update_focused_file.enable then + create_nvim_tree_autocmd("BufEnter", { + callback = function(event) + local exclude = opts.update_focused_file.exclude + if type(exclude) == "function" and exclude(event) then + return + end + utils.debounce("BufEnter:find_file", opts.view.debounce_delay, function() + actions.tree.find_file.fn() + end) + end, + }) + end + + if opts.hijack_directories.enable then + create_nvim_tree_autocmd({ "BufEnter", "BufNewFile" }, { callback = M.open_on_directory, nested = true }) + end + + if opts.view.centralize_selection then + create_nvim_tree_autocmd("BufEnter", { + pattern = "NvimTree_*", + callback = function() + vim.schedule(function() + vim.api.nvim_buf_call(0, function() + local is_term_mode = vim.api.nvim_get_mode().mode == "t" + if is_term_mode then + return + end + vim.cmd([[norm! zz]]) + end) + end) + end, + }) + end + + if opts.diagnostics.enable then + create_nvim_tree_autocmd("DiagnosticChanged", { + callback = function(ev) + log.line("diagnostics", "DiagnosticChanged") + require("nvim-tree.diagnostics").update_lsp(ev) + end, + }) + create_nvim_tree_autocmd("User", { + pattern = "CocDiagnosticChange", + callback = function() + log.line("diagnostics", "CocDiagnosticChange") + require("nvim-tree.diagnostics").update_coc() + end, + }) + end + + if opts.view.float.enable and opts.view.float.quit_on_focus_loss then + create_nvim_tree_autocmd("WinLeave", { + pattern = "NvimTree_*", + callback = function() + if utils.is_nvim_tree_buf(0) then + view.close() + end + end, + }) + end + + -- Handles event dispatch when tree is closed by `:q` + create_nvim_tree_autocmd("WinClosed", { + pattern = "*", + ---@param ev vim.api.keyset.create_autocmd.callback_args + callback = function(ev) + if not vim.api.nvim_buf_is_valid(ev.buf) then + return + end + if vim.api.nvim_get_option_value("filetype", { buf = ev.buf }) == "NvimTree" then + require("nvim-tree.events")._dispatch_on_tree_close() + end + end, + }) end ---@type nvim_tree.config local DEFAULT_OPTS = { -- default-config-start - on_attach = "default", - hijack_cursor = false, - auto_reload_on_write = true, - disable_netrw = false, - hijack_netrw = true, - hijack_unnamed_buffer_when_opening = false, - root_dirs = {}, - prefer_startup_root = false, - sync_root_with_cwd = false, - reload_on_bufenter = false, - respect_buf_cwd = false, - select_prompts = false, - sort = { - sorter = "name", - folders_first = true, - files_first = false, - }, - view = { - centralize_selection = false, - cursorline = true, - cursorlineopt = "both", - debounce_delay = 15, - side = "left", - preserve_window_proportions = false, - number = false, - relativenumber = false, - signcolumn = "yes", - width = 30, - float = { - enable = false, - quit_on_focus_loss = true, - open_win_config = { - relative = "editor", - border = "rounded", - width = 30, - height = 30, - row = 1, - col = 1, - }, - }, - }, - renderer = { - add_trailing = false, - group_empty = false, - full_name = false, - root_folder_label = ":~:s?$?/..?", - indent_width = 2, - special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" }, - hidden_display = "none", - symlink_destination = true, - decorators = { "Git", "Open", "Hidden", "Modified", "Bookmark", "Diagnostics", "Copied", "Cut" }, - highlight_git = "none", - highlight_diagnostics = "none", - highlight_opened_files = "none", - highlight_modified = "none", - highlight_hidden = "none", - highlight_bookmarks = "none", - highlight_clipboard = "name", - indent_markers = { - enable = false, - inline_arrows = true, - icons = { - corner = "└", - edge = "│", - item = "│", - bottom = "─", - none = " ", - }, - }, - icons = { - web_devicons = { - file = { - enable = true, - color = true, - }, - folder = { - enable = false, - color = true, - }, - }, - git_placement = "before", - modified_placement = "after", - hidden_placement = "after", - diagnostics_placement = "signcolumn", - bookmarks_placement = "signcolumn", - padding = { - icon = " ", - folder_arrow = " ", - }, - symlink_arrow = " ➛ ", - show = { - file = true, - folder = true, - folder_arrow = true, - git = true, - modified = true, - hidden = false, - diagnostics = true, - bookmarks = true, - }, - glyphs = { - default = "", - symlink = "", - bookmark = "󰆤", - modified = "●", - hidden = "󰜌", - folder = { - arrow_closed = "", - arrow_open = "", - default = "", - open = "", - empty = "", - empty_open = "", - symlink = "", - symlink_open = "", - }, - git = { - unstaged = "✗", - staged = "✓", - unmerged = "", - renamed = "➜", - untracked = "★", - deleted = "", - ignored = "◌", - }, - }, - }, - }, - hijack_directories = { - enable = true, - auto_open = true, - }, - update_focused_file = { - enable = false, - update_root = { - enable = false, - ignore_list = {}, - }, - exclude = false, - }, - system_open = { - cmd = "", - args = {}, - }, - git = { - enable = true, - show_on_dirs = true, - show_on_open_dirs = true, - disable_for_dirs = {}, - timeout = 400, - cygwin_support = false, - }, - diagnostics = { - enable = false, - show_on_dirs = false, - show_on_open_dirs = true, - debounce_delay = 500, - severity = { - min = vim.diagnostic.severity.HINT, - max = vim.diagnostic.severity.ERROR, - }, - icons = { - hint = "", - info = "", - warning = "", - error = "", - }, - diagnostic_opts = false, - }, - modified = { - enable = false, - show_on_dirs = true, - show_on_open_dirs = true, - }, - filters = { - enable = true, - git_ignored = true, - dotfiles = false, - git_clean = false, - no_buffer = false, - no_bookmark = false, - custom = {}, - exclude = {}, - }, - live_filter = { - prefix = "[FILTER]: ", - always_show_folders = true, - }, - filesystem_watchers = { - enable = true, - debounce_delay = 50, - max_events = 100, - ignore_dirs = { - "/.ccls-cache", - "/build", - "/node_modules", - "/target", - "/.zig-cache", - }, - }, - actions = { - use_system_clipboard = true, - change_dir = { - enable = true, - global = false, - restrict_above_cwd = false, - }, - expand_all = { - max_folder_discovery = 300, - exclude = {}, - }, - file_popup = { - open_win_config = { - col = 1, - row = 1, - relative = "cursor", - border = "shadow", - style = "minimal", - }, - }, - open_file = { - quit_on_open = false, - eject = true, - resize_window = true, - relative_path = true, - window_picker = { - enable = true, - picker = "default", - chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", - exclude = { - filetype = { "notify", "packer", "qf", "diff", "fugitive", "fugitiveblame" }, - buftype = { "nofile", "terminal", "help" }, - }, - }, - }, - remove_file = { - close_window = true, - }, - }, - trash = { - cmd = "gio trash", - }, - tab = { - sync = { - open = false, - close = false, - ignore = {}, - }, - }, - notify = { - threshold = vim.log.levels.INFO, - absolute_path = true, - }, - help = { - sort_by = "key", - }, - ui = { - confirm = { - remove = true, - trash = true, - default_yes = false, - }, - }, - bookmarks = { - persist = false, - }, - experimental = {}, - log = { - enable = false, - truncate = false, - types = { - all = false, - config = false, - copy_paste = false, - dev = false, - diagnostics = false, - git = false, - profile = false, - watcher = false, - }, - }, + on_attach = "default", + hijack_cursor = false, + auto_reload_on_write = true, + disable_netrw = false, + hijack_netrw = true, + hijack_unnamed_buffer_when_opening = false, + root_dirs = {}, + prefer_startup_root = false, + sync_root_with_cwd = false, + reload_on_bufenter = false, + respect_buf_cwd = false, + select_prompts = false, + sort = { + sorter = "name", + folders_first = true, + files_first = false, + }, + view = { + centralize_selection = false, + cursorline = true, + cursorlineopt = "both", + debounce_delay = 15, + side = "left", + preserve_window_proportions = false, + number = false, + relativenumber = false, + signcolumn = "yes", + width = 30, + float = { + enable = false, + quit_on_focus_loss = true, + open_win_config = { + relative = "editor", + border = "rounded", + width = 30, + height = 30, + row = 1, + col = 1, + }, + }, + }, + renderer = { + add_trailing = false, + group_empty = false, + full_name = false, + root_folder_label = ":~:s?$?/..?", + indent_width = 2, + special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" }, + hidden_display = "none", + symlink_destination = true, + decorators = { "Git", "Open", "Hidden", "Modified", "Bookmark", "Diagnostics", "Copied", "Cut" }, + highlight_git = "none", + highlight_diagnostics = "none", + highlight_opened_files = "none", + highlight_modified = "none", + highlight_hidden = "none", + highlight_bookmarks = "none", + highlight_clipboard = "name", + indent_markers = { + enable = false, + inline_arrows = true, + icons = { + corner = "└", + edge = "│", + item = "│", + bottom = "─", + none = " ", + }, + }, + icons = { + web_devicons = { + file = { + enable = true, + color = true, + }, + folder = { + enable = false, + color = true, + }, + }, + git_placement = "before", + modified_placement = "after", + hidden_placement = "after", + diagnostics_placement = "signcolumn", + bookmarks_placement = "signcolumn", + padding = { + icon = " ", + folder_arrow = " ", + }, + symlink_arrow = " ➛ ", + show = { + file = true, + folder = true, + folder_arrow = true, + git = true, + modified = true, + hidden = false, + diagnostics = true, + bookmarks = true, + }, + glyphs = { + default = "", + symlink = "", + bookmark = "󰆤", + modified = "●", + hidden = "󰜌", + folder = { + arrow_closed = "", + arrow_open = "", + default = "", + open = "", + empty = "", + empty_open = "", + symlink = "", + symlink_open = "", + }, + git = { + unstaged = "✗", + staged = "✓", + unmerged = "", + renamed = "➜", + untracked = "★", + deleted = "", + ignored = "◌", + }, + }, + }, + }, + hijack_directories = { + enable = true, + auto_open = true, + }, + update_focused_file = { + enable = false, + update_root = { + enable = false, + ignore_list = {}, + }, + exclude = false, + }, + system_open = { + cmd = "", + args = {}, + }, + git = { + enable = true, + show_on_dirs = true, + show_on_open_dirs = true, + disable_for_dirs = {}, + timeout = 400, + cygwin_support = false, + }, + diagnostics = { + enable = false, + show_on_dirs = false, + show_on_open_dirs = true, + debounce_delay = 500, + severity = { + min = vim.diagnostic.severity.HINT, + max = vim.diagnostic.severity.ERROR, + }, + icons = { + hint = "", + info = "", + warning = "", + error = "", + }, + diagnostic_opts = false, + }, + modified = { + enable = false, + show_on_dirs = true, + show_on_open_dirs = true, + }, + filters = { + enable = true, + git_ignored = true, + dotfiles = false, + git_clean = false, + no_buffer = false, + no_bookmark = false, + custom = {}, + exclude = {}, + }, + live_filter = { + prefix = "[FILTER]: ", + always_show_folders = true, + }, + filesystem_watchers = { + enable = true, + debounce_delay = 50, + max_events = 100, + ignore_dirs = { + "/.ccls-cache", + "/build", + "/node_modules", + "/target", + "/.zig-cache", + }, + }, + actions = { + use_system_clipboard = true, + change_dir = { + enable = true, + global = false, + restrict_above_cwd = false, + }, + expand_all = { + max_folder_discovery = 300, + exclude = {}, + }, + file_popup = { + open_win_config = { + col = 1, + row = 1, + relative = "cursor", + border = "shadow", + style = "minimal", + }, + }, + open_file = { + quit_on_open = false, + eject = true, + resize_window = true, + relative_path = true, + window_picker = { + enable = true, + picker = "default", + chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", + exclude = { + filetype = { "notify", "packer", "qf", "diff", "fugitive", "fugitiveblame" }, + buftype = { "nofile", "terminal", "help" }, + }, + }, + }, + remove_file = { + close_window = true, + }, + }, + trash = { + cmd = "gio trash", + }, + tab = { + sync = { + open = false, + close = false, + ignore = {}, + }, + }, + notify = { + threshold = vim.log.levels.INFO, + absolute_path = true, + }, + help = { + sort_by = "key", + }, + ui = { + confirm = { + remove = true, + trash = true, + default_yes = false, + }, + }, + bookmarks = { + persist = false, + }, + experimental = {}, + log = { + enable = false, + truncate = false, + types = { + all = false, + config = false, + copy_paste = false, + dev = false, + diagnostics = false, + git = false, + profile = false, + watcher = false, + }, + }, } -- default-config-end local function merge_options(conf) - return vim.tbl_deep_extend("force", DEFAULT_OPTS, conf or {}) + return vim.tbl_deep_extend("force", DEFAULT_OPTS, conf or {}) end local FIELD_SKIP_VALIDATE = { - open_win_config = true, + open_win_config = true, } local ACCEPTED_TYPES = { - on_attach = { "function", "string" }, - sort = { - sorter = { "function", "string" }, - }, - view = { - width = { - "string", - "function", - "number", - "table", - min = { "string", "function", "number" }, - max = { "string", "function", "number" }, - lines_excluded = { "table" }, - padding = { "function", "number" }, - }, - }, - renderer = { - hidden_display = { "function", "string" }, - group_empty = { "boolean", "function" }, - root_folder_label = { "function", "string", "boolean" }, - }, - update_focused_file = { - exclude = { "function" }, - }, - git = { - disable_for_dirs = { "function" }, - }, - filters = { - custom = { "function" }, - }, - filesystem_watchers = { - ignore_dirs = { "function" }, - }, - actions = { - open_file = { - window_picker = { - picker = { "function", "string" }, - }, - }, - }, - bookmarks = { - persist = { "boolean", "string" }, - }, + on_attach = { "function", "string" }, + sort = { + sorter = { "function", "string" }, + }, + view = { + width = { + "string", + "function", + "number", + "table", + min = { "string", "function", "number" }, + max = { "string", "function", "number" }, + lines_excluded = { "table" }, + padding = { "function", "number" }, + }, + }, + renderer = { + hidden_display = { "function", "string" }, + group_empty = { "boolean", "function" }, + root_folder_label = { "function", "string", "boolean" }, + }, + update_focused_file = { + exclude = { "function" }, + }, + git = { + disable_for_dirs = { "function" }, + }, + filters = { + custom = { "function" }, + }, + filesystem_watchers = { + ignore_dirs = { "function" }, + }, + actions = { + open_file = { + window_picker = { + picker = { "function", "string" }, + }, + }, + }, + bookmarks = { + persist = { "boolean", "string" }, + }, } local ACCEPTED_STRINGS = { - sort = { - sorter = { "name", "case_sensitive", "modification_time", "extension", "suffix", "filetype" }, - }, - view = { - side = { "left", "right" }, - signcolumn = { "yes", "no", "auto" }, - }, - renderer = { - hidden_display = { "none", "simple", "all" }, - highlight_git = { "none", "icon", "name", "all" }, - highlight_opened_files = { "none", "icon", "name", "all" }, - highlight_modified = { "none", "icon", "name", "all" }, - highlight_hidden = { "none", "icon", "name", "all" }, - highlight_bookmarks = { "none", "icon", "name", "all" }, - highlight_diagnostics = { "none", "icon", "name", "all" }, - highlight_clipboard = { "none", "icon", "name", "all" }, - icons = { - git_placement = { "before", "after", "signcolumn", "right_align" }, - modified_placement = { "before", "after", "signcolumn", "right_align" }, - hidden_placement = { "before", "after", "signcolumn", "right_align" }, - diagnostics_placement = { "before", "after", "signcolumn", "right_align" }, - bookmarks_placement = { "before", "after", "signcolumn", "right_align" }, - }, - }, - help = { - sort_by = { "key", "desc" }, - }, + sort = { + sorter = { "name", "case_sensitive", "modification_time", "extension", "suffix", "filetype" }, + }, + view = { + side = { "left", "right" }, + signcolumn = { "yes", "no", "auto" }, + }, + renderer = { + hidden_display = { "none", "simple", "all" }, + highlight_git = { "none", "icon", "name", "all" }, + highlight_opened_files = { "none", "icon", "name", "all" }, + highlight_modified = { "none", "icon", "name", "all" }, + highlight_hidden = { "none", "icon", "name", "all" }, + highlight_bookmarks = { "none", "icon", "name", "all" }, + highlight_diagnostics = { "none", "icon", "name", "all" }, + highlight_clipboard = { "none", "icon", "name", "all" }, + icons = { + git_placement = { "before", "after", "signcolumn", "right_align" }, + modified_placement = { "before", "after", "signcolumn", "right_align" }, + hidden_placement = { "before", "after", "signcolumn", "right_align" }, + diagnostics_placement = { "before", "after", "signcolumn", "right_align" }, + bookmarks_placement = { "before", "after", "signcolumn", "right_align" }, + }, + }, + help = { + sort_by = { "key", "desc" }, + }, } local ACCEPTED_ENUMS = { - view = { - width = { - lines_excluded = { "root" }, - }, - }, + view = { + width = { + lines_excluded = { "root" }, + }, + }, } ---@param conf? nvim_tree.config local function validate_options(conf) - local msg - - ---@param user any - ---@param def any - ---@param strs table - ---@param types table - ---@param enums table - ---@param prefix string - local function validate(user, def, strs, types, enums, prefix) - -- if user's option is not a table there is nothing to do - if type(user) ~= "table" then - return - end - - -- we have hit a leaf enum to validate against - it's an integer indexed table - local enum_value = type(enums) == "table" and next(enums) and type(next(enums)) == "number" - - -- only compare tables with contents that are not integer indexed nor enums - if not enum_value and (type(def) ~= "table" or not next(def) or type(next(def)) == "number") then - -- unless the field can be a table (and is not a table in default config) - if vim.tbl_contains(types, "table") then - -- use a dummy default to allow all checks - def = {} - else - return - end - end - - for k, v in pairs(user) do - if not FIELD_SKIP_VALIDATE[k] then - local invalid - - if enum_value then - if not vim.tbl_contains(enums, v) then - invalid = string.format("Invalid value for field %s%s: Expected one of enum '%s', got '%s'", - prefix, - k, - table.concat(enums, "'|'"), - tostring(v)) - end - else - if def[k] == nil and types[k] == nil then - -- option does not exist - invalid = string.format("Unknown option: %s%s", prefix, k) - elseif type(v) ~= type(def[k]) then - local expected - - if types[k] and #types[k] > 0 then - if not vim.tbl_contains(types[k], type(v)) then - expected = table.concat(types[k], "|") - end - else - expected = type(def[k]) - end - - if expected then - -- option is of the wrong type - invalid = string.format("Invalid option: %s%s. Expected %s, got %s", prefix, k, expected, type(v)) - end - elseif type(v) == "string" and strs[k] and not vim.tbl_contains(strs[k], v) then - -- option has type `string` but value is not accepted - invalid = string.format("Invalid value for field %s%s: '%s'", prefix, k, v) - end - end - - if invalid then - if msg then - msg = string.format("%s\n%s", msg, invalid) - else - msg = invalid - end - user[k] = nil - elseif not enum_value then - validate(v, def[k], strs[k] or {}, types[k] or {}, enums[k] or {}, prefix .. k .. ".") - end - end - end - end - - validate(conf, DEFAULT_OPTS, ACCEPTED_STRINGS, ACCEPTED_TYPES, ACCEPTED_ENUMS, "") - - if msg then - notify.warn(msg .. "\n\nsee :help nvim-tree-opts for available configuration options") - end + local msg + + ---@param user any + ---@param def any + ---@param strs table + ---@param types table + ---@param enums table + ---@param prefix string + local function validate(user, def, strs, types, enums, prefix) + -- if user's option is not a table there is nothing to do + if type(user) ~= "table" then + return + end + + -- we have hit a leaf enum to validate against - it's an integer indexed table + local enum_value = type(enums) == "table" and next(enums) and type(next(enums)) == "number" + + -- only compare tables with contents that are not integer indexed nor enums + if not enum_value and (type(def) ~= "table" or not next(def) or type(next(def)) == "number") then + -- unless the field can be a table (and is not a table in default config) + if vim.tbl_contains(types, "table") then + -- use a dummy default to allow all checks + def = {} + else + return + end + end + + for k, v in pairs(user) do + if not FIELD_SKIP_VALIDATE[k] then + local invalid + + if enum_value then + if not vim.tbl_contains(enums, v) then + invalid = string.format("Invalid value for field %s%s: Expected one of enum '%s', got '%s'", + prefix, + k, + table.concat(enums, "'|'"), + tostring(v)) + end + else + if def[k] == nil and types[k] == nil then + -- option does not exist + invalid = string.format("Unknown option: %s%s", prefix, k) + elseif type(v) ~= type(def[k]) then + local expected + + if types[k] and #types[k] > 0 then + if not vim.tbl_contains(types[k], type(v)) then + expected = table.concat(types[k], "|") + end + else + expected = type(def[k]) + end + + if expected then + -- option is of the wrong type + invalid = string.format("Invalid option: %s%s. Expected %s, got %s", prefix, k, expected, type(v)) + end + elseif type(v) == "string" and strs[k] and not vim.tbl_contains(strs[k], v) then + -- option has type `string` but value is not accepted + invalid = string.format("Invalid value for field %s%s: '%s'", prefix, k, v) + end + end + + if invalid then + if msg then + msg = string.format("%s\n%s", msg, invalid) + else + msg = invalid + end + user[k] = nil + elseif not enum_value then + validate(v, def[k], strs[k] or {}, types[k] or {}, enums[k] or {}, prefix .. k .. ".") + end + end + end + end + + validate(conf, DEFAULT_OPTS, ACCEPTED_STRINGS, ACCEPTED_TYPES, ACCEPTED_ENUMS, "") + + if msg then + notify.warn(msg .. "\n\nsee :help nvim-tree-opts for available configuration options") + end end --- Apply OS specific localisations to DEFAULT_OPTS local function localise_default_opts() - if utils.is_macos or utils.is_windows then - DEFAULT_OPTS.trash.cmd = "trash" - end + if utils.is_macos or utils.is_windows then + DEFAULT_OPTS.trash.cmd = "trash" + end end function M.purge_all_state() - view.close_all_tabs() - view.abandon_all_windows() - local explorer = core.get_explorer() - if explorer then - require("nvim-tree.git").purge_state() - explorer:destroy() - core.reset_explorer() - end - -- purge orphaned that were not destroyed by their nodes - require("nvim-tree.watcher").purge_watchers() + view.close_all_tabs() + view.abandon_all_windows() + local explorer = core.get_explorer() + if explorer then + require("nvim-tree.git").purge_state() + explorer:destroy() + core.reset_explorer() + end + -- purge orphaned that were not destroyed by their nodes + require("nvim-tree.watcher").purge_watchers() end ---@param conf? nvim_tree.config function M.setup(conf) - if vim.fn.has("nvim-0.9") == 0 then - notify.warn("nvim-tree.lua requires Neovim 0.9 or higher") - return - end + if vim.fn.has("nvim-0.9") == 0 then + notify.warn("nvim-tree.lua requires Neovim 0.9 or higher") + return + end - M.init_root = vim.fn.getcwd() + M.init_root = vim.fn.getcwd() - localise_default_opts() + localise_default_opts() - require("nvim-tree.legacy").migrate_legacy_options(conf or {}) + require("nvim-tree.legacy").migrate_legacy_options(conf or {}) - validate_options(conf) + validate_options(conf) - local opts = merge_options(conf) + local opts = merge_options(conf) - local netrw_disabled = opts.disable_netrw or opts.hijack_netrw + local netrw_disabled = opts.disable_netrw or opts.hijack_netrw - _config.root_dirs = opts.root_dirs - _config.prefer_startup_root = opts.prefer_startup_root - _config.update_focused_file = opts.update_focused_file - _config.hijack_directories = opts.hijack_directories - _config.hijack_directories.enable = _config.hijack_directories.enable and netrw_disabled + _config.root_dirs = opts.root_dirs + _config.prefer_startup_root = opts.prefer_startup_root + _config.update_focused_file = opts.update_focused_file + _config.hijack_directories = opts.hijack_directories + _config.hijack_directories.enable = _config.hijack_directories.enable and netrw_disabled - manage_netrw(opts.disable_netrw, opts.hijack_netrw) + manage_netrw(opts.disable_netrw, opts.hijack_netrw) - M.config = opts - require("nvim-tree.notify").setup(opts) - require("nvim-tree.log").setup(opts) + M.config = opts + require("nvim-tree.notify").setup(opts) + require("nvim-tree.log").setup(opts) - if log.enabled("config") then - log.line("config", "default config + user") - log.raw("config", "%s\n", vim.inspect(opts)) - end + if log.enabled("config") then + log.line("config", "default config + user") + log.raw("config", "%s\n", vim.inspect(opts)) + end - require("nvim-tree.actions").setup(opts) - require("nvim-tree.keymap").setup(opts) - require("nvim-tree.appearance").setup() - require("nvim-tree.diagnostics").setup(opts) - require("nvim-tree.explorer"):setup(opts) - require("nvim-tree.explorer.watch").setup(opts) - require("nvim-tree.git").setup(opts) - require("nvim-tree.git.utils").setup(opts) - require("nvim-tree.view").setup(opts) - require("nvim-tree.lib").setup(opts) - require("nvim-tree.renderer.components").setup(opts) - require("nvim-tree.buffers").setup(opts) - require("nvim-tree.help").setup(opts) - require("nvim-tree.watcher").setup(opts) + require("nvim-tree.actions").setup(opts) + require("nvim-tree.keymap").setup(opts) + require("nvim-tree.appearance").setup() + require("nvim-tree.diagnostics").setup(opts) + require("nvim-tree.explorer"):setup(opts) + require("nvim-tree.explorer.watch").setup(opts) + require("nvim-tree.git").setup(opts) + require("nvim-tree.git.utils").setup(opts) + require("nvim-tree.view").setup(opts) + require("nvim-tree.lib").setup(opts) + require("nvim-tree.renderer.components").setup(opts) + require("nvim-tree.buffers").setup(opts) + require("nvim-tree.help").setup(opts) + require("nvim-tree.watcher").setup(opts) - setup_autocommands(opts) + setup_autocommands(opts) - if vim.g.NvimTreeSetup == 1 then - -- subsequent calls to setup - M.purge_all_state() - end + if vim.g.NvimTreeSetup == 1 then + -- subsequent calls to setup + M.purge_all_state() + end - vim.g.NvimTreeSetup = 1 - vim.api.nvim_exec_autocmds("User", { pattern = "NvimTreeSetup" }) + vim.g.NvimTreeSetup = 1 + vim.api.nvim_exec_autocmds("User", { pattern = "NvimTreeSetup" }) end vim.g.NvimTreeRequired = 1 From 71b699ed023d4515495ca7a27b682df9c9b5a887 Mon Sep 17 00:00:00 2001 From: uanela Date: Wed, 11 Feb 2026 11:22:02 +0200 Subject: [PATCH 6/6] chore: match with documentation --- lua/nvim-tree.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lua/nvim-tree.lua b/lua/nvim-tree.lua index 52ae7cfc655..c0c9cebf1b2 100644 --- a/lua/nvim-tree.lua +++ b/lua/nvim-tree.lua @@ -320,7 +320,7 @@ local DEFAULT_OPTS = { -- default-config-start special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" }, hidden_display = "none", symlink_destination = true, - decorators = { "Git", "Open", "Hidden", "Modified", "Bookmark", "Diagnostics", "Copied", "Cut" }, + decorators = { "Git", "Open", "Hidden", "Modified", "Bookmark", "Diagnostics", "Copied", "Cut", }, highlight_git = "none", highlight_diagnostics = "none", highlight_opened_files = "none", @@ -536,7 +536,8 @@ local DEFAULT_OPTS = { -- default-config-start bookmarks = { persist = false, }, - experimental = {}, + experimental = { + }, log = { enable = false, truncate = false,