From 09b7d472fa93dfc5856492d57aeeae876d878f7b Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Fri, 6 Mar 2026 11:44:28 +1100 Subject: [PATCH 01/11] refactor(#3255): lazy post requires, split into formatted groups --- lua/nvim-tree/api/impl/post.lua | 266 ++++++++++++++++---------------- 1 file changed, 137 insertions(+), 129 deletions(-) diff --git a/lua/nvim-tree/api/impl/post.lua b/lua/nvim-tree/api/impl/post.lua index 90574c3eae3..8745440776d 100644 --- a/lua/nvim-tree/api/impl/post.lua +++ b/lua/nvim-tree/api/impl/post.lua @@ -3,27 +3,30 @@ --- ---Call this after nvim-tree setup --- ----This is expensive as there are many cascading requires and is avoided ----until after setup has been called, so that the user may require API cheaply. +---All requires must be done lazily so that requiring api post setup is cheap. local legacy = require("nvim-tree.legacy") -local actions = require("nvim-tree.actions") -local config = require("nvim-tree.config") -local help = require("nvim-tree.help") -local keymap = require("nvim-tree.keymap") -local utils = require("nvim-tree.utils") -local view = require("nvim-tree.view") - local M = {} +--- convenience wrappers for lazy module requires +local function actions() return require("nvim-tree.actions") end +local function core() return require("nvim-tree.core") end +local function config() return require("nvim-tree.config") end +local function help() return require("nvim-tree.help") end +local function keymap() return require("nvim-tree.keymap") end +local function utils() return require("nvim-tree.utils") end +local function view() return require("nvim-tree.view") end + +-- TODO 3255 wrap* must be able to take a function. May be best to have that function accept (node, ...) + ---Invoke a method on the singleton explorer. ---Print error when setup not called. ---@param explorer_method string explorer method name ---@return fun(...): any local function wrap_explorer(explorer_method) return function(...) - local explorer = require("nvim-tree.core").get_explorer() + local explorer = core().get_explorer() if explorer then return explorer[explorer_method](explorer, ...) end @@ -61,7 +64,7 @@ end local function wrap_explorer_member_args(explorer_member, member_method, ...) local method_args = ... return function(...) - local explorer = require("nvim-tree.core").get_explorer() + local explorer = core().get_explorer() if explorer then return explorer[explorer_member][member_method](explorer[explorer_member], method_args, ...) end @@ -75,7 +78,7 @@ end ---@return fun(...): any local function wrap_explorer_member(explorer_member, member_method) return function(...) - local explorer = require("nvim-tree.core").get_explorer() + local explorer = core().get_explorer() if explorer then return explorer[explorer_member][member_method](explorer[explorer_member], ...) end @@ -101,132 +104,137 @@ local function wrap_node_or_visual(fn) end end ----Re-Hydrate api functions and classes post-setup ----@param api table not properly typed to prevent LSP from referencing implementations -function M.hydrate(api) - api.tree.open = actions.tree.open.fn - api.tree.focus = api.tree.open - - api.tree.toggle = actions.tree.toggle.fn - api.tree.close = view.close - api.tree.close_in_this_tab = view.close_this_tab_only - api.tree.close_in_all_tabs = view.close_all_tabs - api.tree.reload = wrap_explorer("reload_explorer") - - api.tree.resize = actions.tree.resize.fn +local function hydrate_config(api) + api.config.global = function() return config().g_clone() end + api.config.user = function() return config().u_clone() end +end - api.tree.change_root = actions.tree.change_dir.fn +local function hydrate_filter(api) + api.filter.custom.toggle = wrap_explorer_member_args("filters", "toggle", "custom") + api.filter.dotfiles.toggle = wrap_explorer_member_args("filters", "toggle", "dotfiles") + api.filter.git.clean.toggle = wrap_explorer_member_args("filters", "toggle", "git_clean") + api.filter.git.ignored.toggle = wrap_explorer_member_args("filters", "toggle", "git_ignored") + api.filter.live.clear = wrap_explorer_member("live_filter", "clear_filter") + api.filter.live.start = wrap_explorer_member("live_filter", "start_filtering") + api.filter.no_bookmark.toggle = wrap_explorer_member_args("filters", "toggle", "no_bookmark") + api.filter.no_buffer.toggle = wrap_explorer_member_args("filters", "toggle", "no_buffer") + api.filter.toggle = wrap_explorer_member("filters", "toggle") +end - api.tree.change_root_to_node = wrap_node(wrap_explorer("change_dir_to_node")) - api.tree.change_root_to_parent = wrap_node(wrap_explorer("dir_up")) - api.tree.get_node_under_cursor = wrap_explorer("get_node_at_cursor") - api.tree.get_nodes = wrap_explorer("get_nodes") - - api.tree.find_file = actions.tree.find_file.fn - api.tree.search_node = actions.finders.search_node.fn - - api.tree.collapse_all = actions.tree.collapse.all - - api.tree.expand_all = wrap_node(wrap_explorer("expand_all")) - api.tree.toggle_help = help.toggle - api.tree.is_tree_buf = utils.is_nvim_tree_buf - - api.tree.is_visible = view.is_visible - - api.tree.winid = view.winid - - api.fs.create = wrap_node_or_nil(actions.fs.create_file.fn) - api.fs.remove = wrap_node_or_visual(actions.fs.remove_file.fn) - api.fs.trash = wrap_node_or_visual(actions.fs.trash.fn) - api.fs.rename_node = wrap_node(actions.fs.rename_file.rename_node) - api.fs.rename = wrap_node(actions.fs.rename_file.rename_node) - api.fs.rename_sub = wrap_node(actions.fs.rename_file.rename_sub) - api.fs.rename_basename = wrap_node(actions.fs.rename_file.rename_basename) - api.fs.rename_full = wrap_node(actions.fs.rename_file.rename_full) - api.fs.cut = wrap_node_or_visual(wrap_explorer_member("clipboard", "cut")) - api.fs.paste = wrap_node(wrap_explorer_member("clipboard", "paste")) - api.fs.clear_clipboard = wrap_explorer_member("clipboard", "clear_clipboard") - api.fs.print_clipboard = wrap_explorer_member("clipboard", "print_clipboard") - api.fs.copy.node = wrap_node_or_visual(wrap_explorer_member("clipboard", "copy")) +local function hydrate_fs(api) + api.fs.clear_clipboard = wrap_explorer_member("clipboard", "clear_clipboard") api.fs.copy.absolute_path = wrap_node(wrap_explorer_member("clipboard", "copy_absolute_path")) - api.fs.copy.filename = wrap_node(wrap_explorer_member("clipboard", "copy_filename")) - api.fs.copy.basename = wrap_node(wrap_explorer_member("clipboard", "copy_basename")) + api.fs.copy.basename = wrap_node(wrap_explorer_member("clipboard", "copy_basename")) + api.fs.copy.filename = wrap_node(wrap_explorer_member("clipboard", "copy_filename")) + api.fs.copy.node = wrap_node_or_visual(wrap_explorer_member("clipboard", "copy")) api.fs.copy.relative_path = wrap_node(wrap_explorer_member("clipboard", "copy_path")) + api.fs.create = wrap_node_or_nil(actions().fs.create_file.fn) + api.fs.cut = wrap_node_or_visual(wrap_explorer_member("clipboard", "cut")) + api.fs.paste = wrap_node(wrap_explorer_member("clipboard", "paste")) + api.fs.print_clipboard = wrap_explorer_member("clipboard", "print_clipboard") + api.fs.remove = wrap_node_or_visual(actions().fs.remove_file.fn) + api.fs.rename = wrap_node(actions().fs.rename_file.rename_node) + api.fs.rename_basename = wrap_node(actions().fs.rename_file.rename_basename) + api.fs.rename_full = wrap_node(actions().fs.rename_file.rename_full) + api.fs.rename_node = wrap_node(actions().fs.rename_file.rename_node) + api.fs.rename_sub = wrap_node(actions().fs.rename_file.rename_sub) + api.fs.trash = wrap_node_or_visual(actions().fs.trash.fn) +end - api.node.open.edit = wrap_node(actions.node.open_file.edit) - api.node.open.drop = wrap_node(actions.node.open_file.drop) - api.node.open.tab_drop = wrap_node(actions.node.open_file.tab_drop) - api.node.open.replace_tree_buffer = wrap_node(actions.node.open_file.replace_tree_buffer) - api.node.open.no_window_picker = wrap_node(actions.node.open_file.no_window_picker) - api.node.open.vertical = wrap_node(actions.node.open_file.vertical) - api.node.open.vertical_no_picker = wrap_node(actions.node.open_file.vertical_no_picker) - api.node.open.horizontal = wrap_node(actions.node.open_file.horizontal) - api.node.open.horizontal_no_picker = wrap_node(actions.node.open_file.horizontal_no_picker) - api.node.open.tab = wrap_node(actions.node.open_file.tab) - api.node.open.toggle_group_empty = wrap_node(actions.node.open_file.toggle_group_empty) - api.node.open.preview = wrap_node(actions.node.open_file.preview) - api.node.open.preview_no_picker = wrap_node(actions.node.open_file.preview_no_picker) - - api.node.show_info_popup = wrap_node(actions.node.file_popup.toggle_file_info) - api.node.run.cmd = wrap_node(actions.node.run_command.run_file_command) - api.node.run.system = wrap_node(actions.node.system_open.fn) - - api.node.navigate.sibling.next = wrap_node(actions.moves.sibling.next) - api.node.navigate.sibling.prev = wrap_node(actions.moves.sibling.prev) - api.node.navigate.sibling.first = wrap_node(actions.moves.sibling.first) - api.node.navigate.sibling.last = wrap_node(actions.moves.sibling.last) - - api.node.navigate.parent = wrap_node(actions.moves.parent.move) - api.node.navigate.parent_close = wrap_node(actions.moves.parent.move_close) - - api.node.navigate.git.next = actions.moves.item.git_next - api.node.navigate.git.next_skip_gitignored = actions.moves.item.git_next_skip_gitignored - api.node.navigate.git.next_recursive = actions.moves.item.git_next_recursive - api.node.navigate.git.prev = actions.moves.item.git_prev - api.node.navigate.git.prev_skip_gitignored = actions.moves.item.git_prev_skip_gitignored - api.node.navigate.git.prev_recursive = actions.moves.item.git_prev_recursive - - api.node.navigate.diagnostics.next = actions.moves.item.diagnostics_next - api.node.navigate.diagnostics.next_recursive = actions.moves.item.diagnostics_next_recursive - api.node.navigate.diagnostics.prev = actions.moves.item.diagnostics_prev - api.node.navigate.diagnostics.prev_recursive = actions.moves.item.diagnostics_prev_recursive - - api.node.navigate.opened.next = actions.moves.item.opened_next - api.node.navigate.opened.prev = actions.moves.item.opened_prev - - api.node.expand = wrap_node(wrap_explorer("expand_node")) - api.node.collapse = wrap_node(actions.tree.collapse.node) - - api.node.buffer.delete = wrap_node(function(node, opts) actions.node.buffer.delete(node, opts) end) - api.node.buffer.wipe = wrap_node(function(node, opts) actions.node.buffer.wipe(node, opts) end) - - api.tree.reload_git = wrap_explorer("reload_git") - - api.filter.live.start = wrap_explorer_member("live_filter", "start_filtering") - api.filter.live.clear = wrap_explorer_member("live_filter", "clear_filter") - api.filter.toggle = wrap_explorer_member("filters", "toggle") - api.filter.git.ignored.toggle = wrap_explorer_member_args("filters", "toggle", "git_ignored") - api.filter.git.clean.toggle = wrap_explorer_member_args("filters", "toggle", "git_clean") - api.filter.no_buffer.toggle = wrap_explorer_member_args("filters", "toggle", "no_buffer") - api.filter.custom.toggle = wrap_explorer_member_args("filters", "toggle", "custom") - api.filter.dotfiles.toggle = wrap_explorer_member_args("filters", "toggle", "dotfiles") - api.filter.no_bookmark.toggle = wrap_explorer_member_args("filters", "toggle", "no_bookmark") +local function hydrate_map(api) + api.map.keymap.current = function() return keymap().get_keymap() end +end - api.marks.get = wrap_node(wrap_explorer_member("marks", "get")) - api.marks.list = wrap_explorer_member("marks", "list") - api.marks.toggle = wrap_node_or_visual(wrap_explorer_member("marks", "toggle")) - api.marks.clear = wrap_explorer_member("marks", "clear") - api.marks.bulk.delete = wrap_explorer_member("marks", "bulk_delete") - api.marks.bulk.trash = wrap_explorer_member("marks", "bulk_trash") - api.marks.bulk.move = wrap_explorer_member("marks", "bulk_move") - api.marks.navigate.next = wrap_explorer_member("marks", "navigate_next") - api.marks.navigate.prev = wrap_explorer_member("marks", "navigate_prev") +local function hydrate_marks(api) + api.marks.bulk.delete = wrap_explorer_member("marks", "bulk_delete") + api.marks.bulk.move = wrap_explorer_member("marks", "bulk_move") + api.marks.bulk.trash = wrap_explorer_member("marks", "bulk_trash") + api.marks.clear = wrap_explorer_member("marks", "clear") + api.marks.get = wrap_node(wrap_explorer_member("marks", "get")) + api.marks.list = wrap_explorer_member("marks", "list") + api.marks.navigate.next = wrap_explorer_member("marks", "navigate_next") + api.marks.navigate.prev = wrap_explorer_member("marks", "navigate_prev") api.marks.navigate.select = wrap_explorer_member("marks", "navigate_select") + api.marks.toggle = wrap_node_or_visual(wrap_explorer_member("marks", "toggle")) +end - api.map.keymap.current = keymap.get_keymap +local function hydrate_node(api) + api.node.buffer.delete = wrap_node(function(node, opts) actions().node.buffer.delete(node, opts) end) + api.node.buffer.wipe = wrap_node(function(node, opts) actions().node.buffer.wipe(node, opts) end) + api.node.collapse = wrap_node(actions().tree.collapse.node) + api.node.expand = wrap_node(wrap_explorer("expand_node")) + api.node.navigate.diagnostics.next = function() return actions().moves.item.diagnostics_next() end + api.node.navigate.diagnostics.next_recursive = function() return actions().moves.item.diagnostics_next_recursive() end + api.node.navigate.diagnostics.prev = function() return actions().moves.item.diagnostics_prev() end + api.node.navigate.diagnostics.prev_recursive = function() return actions().moves.item.diagnostics_prev_recursive() end + api.node.navigate.git.next = function() return actions().moves.item.git_next() end + api.node.navigate.git.next_recursive = function() return actions().moves.item.git_next_recursive() end + api.node.navigate.git.next_skip_gitignored = function() return actions().moves.item.git_next_skip_gitignored() end + api.node.navigate.git.prev = function() return actions().moves.item.git_prev() end + api.node.navigate.git.prev_recursive = function() return actions().moves.item.git_prev_recursive() end + api.node.navigate.git.prev_skip_gitignored = function() return actions().moves.item.git_prev_skip_gitignored() end + api.node.navigate.opened.next = function() return actions().moves.item.opened_next() end + api.node.navigate.opened.prev = function() return actions().moves.item.opened_prev() end + api.node.navigate.parent = wrap_node(actions().moves.parent.move) + api.node.navigate.parent_close = wrap_node(actions().moves.parent.move_close) + api.node.navigate.sibling.first = wrap_node(actions().moves.sibling.first) + api.node.navigate.sibling.last = wrap_node(actions().moves.sibling.last) + api.node.navigate.sibling.next = wrap_node(actions().moves.sibling.next) + api.node.navigate.sibling.prev = wrap_node(actions().moves.sibling.prev) + api.node.open.drop = wrap_node(actions().node.open_file.drop) + api.node.open.edit = wrap_node(actions().node.open_file.edit) + api.node.open.horizontal = wrap_node(actions().node.open_file.horizontal) + api.node.open.horizontal_no_picker = wrap_node(actions().node.open_file.horizontal_no_picker) + api.node.open.no_window_picker = wrap_node(actions().node.open_file.no_window_picker) + api.node.open.preview = wrap_node(actions().node.open_file.preview) + api.node.open.preview_no_picker = wrap_node(actions().node.open_file.preview_no_picker) + api.node.open.replace_tree_buffer = wrap_node(actions().node.open_file.replace_tree_buffer) + api.node.open.tab = wrap_node(actions().node.open_file.tab) + api.node.open.tab_drop = wrap_node(actions().node.open_file.tab_drop) + api.node.open.toggle_group_empty = wrap_node(actions().node.open_file.toggle_group_empty) + api.node.open.vertical = wrap_node(actions().node.open_file.vertical) + api.node.open.vertical_no_picker = wrap_node(actions().node.open_file.vertical_no_picker) + api.node.run.cmd = wrap_node(actions().node.run_command.run_file_command) + api.node.run.system = wrap_node(actions().node.system_open.fn) + api.node.show_info_popup = wrap_node(actions().node.file_popup.toggle_file_info) +end - api.config.global = config.g_clone - api.config.user = config.u_clone +local function hydrate_tree(api) + api.tree.change_root = function() return actions().tree.change_dir.fn() end + api.tree.change_root_to_node = wrap_node(wrap_explorer("change_dir_to_node")) + api.tree.change_root_to_parent = wrap_node(wrap_explorer("dir_up")) + api.tree.close = function() return view().close() end + api.tree.close_in_all_tabs = function() return view().close_all_tabs() end + api.tree.close_in_this_tab = function() return view().close_this_tab_only() end + api.tree.collapse_all = function() return actions().tree.collapse.all() end + api.tree.expand_all = wrap_node(wrap_explorer("expand_all")) + api.tree.find_file = function() return actions().tree.find_file.fn() end + api.tree.focus = api.tree.open + api.tree.get_node_under_cursor = wrap_explorer("get_node_at_cursor") + api.tree.get_nodes = wrap_explorer("get_nodes") + api.tree.is_tree_buf = function() return utils().is_nvim_tree_buf() end + api.tree.is_visible = function() return view().is_visible() end + api.tree.open = function() return actions().tree.open.fn() end + api.tree.reload = wrap_explorer("reload_explorer") + api.tree.reload_git = wrap_explorer("reload_git") + api.tree.resize = function() return actions().tree.resize.fn() end + api.tree.search_node = function() return actions().finders.search_node.fn() end + api.tree.toggle = function() return actions().tree.toggle.fn() end + api.tree.toggle_help = function() return help().toggle() end + api.tree.winid = function() return view().winid() end +end + +---Re-Hydrate api functions and classes post-setup +---@param api table not properly typed to prevent LSP from referencing implementations +function M.hydrate(api) + -- hydration has been split into functions for readability and formatting + hydrate_config(api) + hydrate_filter(api) + hydrate_fs(api) + hydrate_map(api) + hydrate_marks(api) + hydrate_node(api) + hydrate_tree(api) -- (Re)hydrate any legacy by mapping to concrete set above legacy.map_api(api) From 23ac60372d9291ea66d42d2e867b3594a731ed4e Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Fri, 6 Mar 2026 12:20:00 +1100 Subject: [PATCH 02/11] refactor(#3255): all post are lazy --- lua/nvim-tree/api/impl/post.lua | 62 ++++++++++++++++----------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/lua/nvim-tree/api/impl/post.lua b/lua/nvim-tree/api/impl/post.lua index 8745440776d..81d384be0b8 100644 --- a/lua/nvim-tree/api/impl/post.lua +++ b/lua/nvim-tree/api/impl/post.lua @@ -128,17 +128,17 @@ local function hydrate_fs(api) api.fs.copy.filename = wrap_node(wrap_explorer_member("clipboard", "copy_filename")) api.fs.copy.node = wrap_node_or_visual(wrap_explorer_member("clipboard", "copy")) api.fs.copy.relative_path = wrap_node(wrap_explorer_member("clipboard", "copy_path")) - api.fs.create = wrap_node_or_nil(actions().fs.create_file.fn) + api.fs.create = wrap_node_or_nil(function(node) actions().fs.create_file.fn(node) end) api.fs.cut = wrap_node_or_visual(wrap_explorer_member("clipboard", "cut")) api.fs.paste = wrap_node(wrap_explorer_member("clipboard", "paste")) api.fs.print_clipboard = wrap_explorer_member("clipboard", "print_clipboard") - api.fs.remove = wrap_node_or_visual(actions().fs.remove_file.fn) - api.fs.rename = wrap_node(actions().fs.rename_file.rename_node) - api.fs.rename_basename = wrap_node(actions().fs.rename_file.rename_basename) - api.fs.rename_full = wrap_node(actions().fs.rename_file.rename_full) - api.fs.rename_node = wrap_node(actions().fs.rename_file.rename_node) - api.fs.rename_sub = wrap_node(actions().fs.rename_file.rename_sub) - api.fs.trash = wrap_node_or_visual(actions().fs.trash.fn) + api.fs.remove = wrap_node_or_visual(function(node) actions().fs.remove_file.fn(node) end) + api.fs.rename = wrap_node(function(node) actions().fs.rename_file.rename_node(node) end) + api.fs.rename_basename = wrap_node(function(node) actions().fs.rename_file.rename_basename(node) end) + api.fs.rename_full = wrap_node(function(node) actions().fs.rename_file.rename_full(node) end) + api.fs.rename_node = wrap_node(function(node) actions().fs.rename_file.rename_node(node) end) + api.fs.rename_sub = wrap_node(function(node) actions().fs.rename_file.rename_sub(node) end) + api.fs.trash = wrap_node_or_visual(function(node) actions().fs.trash.fn(node) end) end local function hydrate_map(api) @@ -161,7 +161,7 @@ end local function hydrate_node(api) api.node.buffer.delete = wrap_node(function(node, opts) actions().node.buffer.delete(node, opts) end) api.node.buffer.wipe = wrap_node(function(node, opts) actions().node.buffer.wipe(node, opts) end) - api.node.collapse = wrap_node(actions().tree.collapse.node) + api.node.collapse = wrap_node(function(node) actions().tree.collapse.node(node) end) api.node.expand = wrap_node(wrap_explorer("expand_node")) api.node.navigate.diagnostics.next = function() return actions().moves.item.diagnostics_next() end api.node.navigate.diagnostics.next_recursive = function() return actions().moves.item.diagnostics_next_recursive() end @@ -175,28 +175,28 @@ local function hydrate_node(api) api.node.navigate.git.prev_skip_gitignored = function() return actions().moves.item.git_prev_skip_gitignored() end api.node.navigate.opened.next = function() return actions().moves.item.opened_next() end api.node.navigate.opened.prev = function() return actions().moves.item.opened_prev() end - api.node.navigate.parent = wrap_node(actions().moves.parent.move) - api.node.navigate.parent_close = wrap_node(actions().moves.parent.move_close) - api.node.navigate.sibling.first = wrap_node(actions().moves.sibling.first) - api.node.navigate.sibling.last = wrap_node(actions().moves.sibling.last) - api.node.navigate.sibling.next = wrap_node(actions().moves.sibling.next) - api.node.navigate.sibling.prev = wrap_node(actions().moves.sibling.prev) - api.node.open.drop = wrap_node(actions().node.open_file.drop) - api.node.open.edit = wrap_node(actions().node.open_file.edit) - api.node.open.horizontal = wrap_node(actions().node.open_file.horizontal) - api.node.open.horizontal_no_picker = wrap_node(actions().node.open_file.horizontal_no_picker) - api.node.open.no_window_picker = wrap_node(actions().node.open_file.no_window_picker) - api.node.open.preview = wrap_node(actions().node.open_file.preview) - api.node.open.preview_no_picker = wrap_node(actions().node.open_file.preview_no_picker) - api.node.open.replace_tree_buffer = wrap_node(actions().node.open_file.replace_tree_buffer) - api.node.open.tab = wrap_node(actions().node.open_file.tab) - api.node.open.tab_drop = wrap_node(actions().node.open_file.tab_drop) - api.node.open.toggle_group_empty = wrap_node(actions().node.open_file.toggle_group_empty) - api.node.open.vertical = wrap_node(actions().node.open_file.vertical) - api.node.open.vertical_no_picker = wrap_node(actions().node.open_file.vertical_no_picker) - api.node.run.cmd = wrap_node(actions().node.run_command.run_file_command) - api.node.run.system = wrap_node(actions().node.system_open.fn) - api.node.show_info_popup = wrap_node(actions().node.file_popup.toggle_file_info) + api.node.navigate.parent = wrap_node(function(node) actions().moves.parent.move(node) end) + api.node.navigate.parent_close = wrap_node(function(node) actions().moves.parent.move_close(node) end) + api.node.navigate.sibling.first = wrap_node(function(node) actions().moves.sibling.first(node) end) + api.node.navigate.sibling.last = wrap_node(function(node) actions().moves.sibling.last(node) end) + api.node.navigate.sibling.next = wrap_node(function(node) actions().moves.sibling.next(node) end) + api.node.navigate.sibling.prev = wrap_node(function(node) actions().moves.sibling.prev(node) end) + api.node.open.drop = wrap_node(function(node) actions().node.open_file.drop(node) end) + api.node.open.edit = wrap_node(function(node) actions().node.open_file.edit(node) end) + api.node.open.horizontal = wrap_node(function(node) actions().node.open_file.horizontal(node) end) + api.node.open.horizontal_no_picker = wrap_node(function(node) actions().node.open_file.horizontal_no_picker(node) end) + api.node.open.no_window_picker = wrap_node(function(node) actions().node.open_file.no_window_picker(node) end) + api.node.open.preview = wrap_node(function(node) actions().node.open_file.preview(node) end) + api.node.open.preview_no_picker = wrap_node(function(node) actions().node.open_file.preview_no_picker(node) end) + api.node.open.replace_tree_buffer = wrap_node(function(node) actions().node.open_file.replace_tree_buffer(node) end) + api.node.open.tab = wrap_node(function(node) actions().node.open_file.tab(node) end) + api.node.open.tab_drop = wrap_node(function(node) actions().node.open_file.tab_drop(node) end) + api.node.open.toggle_group_empty = wrap_node(function(node) actions().node.open_file.toggle_group_empty(node) end) + api.node.open.vertical = wrap_node(function(node) actions().node.open_file.vertical(node) end) + api.node.open.vertical_no_picker = wrap_node(function(node) actions().node.open_file.vertical_no_picker(node) end) + api.node.run.cmd = wrap_node(function(node) actions().node.run_command.run_file_command(node) end) + api.node.run.system = wrap_node(function(node) actions().node.system_open.fn(node) end) + api.node.show_info_popup = wrap_node(function(node) actions().node.file_popup.toggle_file_info(node) end) end local function hydrate_tree(api) From 509947100b9aa8640ad61073605c14021ed73472 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Fri, 6 Mar 2026 15:12:16 +1100 Subject: [PATCH 03/11] refactor(#3255): all post are wrapped consistently --- lua/nvim-tree/api/impl/post.lua | 348 +++++++++++++---------------- lua/nvim-tree/explorer/filters.lua | 1 - 2 files changed, 152 insertions(+), 197 deletions(-) diff --git a/lua/nvim-tree/api/impl/post.lua b/lua/nvim-tree/api/impl/post.lua index 81d384be0b8..6c6cea72a7f 100644 --- a/lua/nvim-tree/api/impl/post.lua +++ b/lua/nvim-tree/api/impl/post.lua @@ -1,5 +1,5 @@ ---Hydrates all API functions with concrete implementations. ----All "nvim-tree setup not called" error functions from pre.lua will be replaced. +---Replace all "nvim-tree setup not called" error functions from pre.lua with their implementations. --- ---Call this after nvim-tree setup --- @@ -18,223 +18,179 @@ local function keymap() return require("nvim-tree.keymap") end local function utils() return require("nvim-tree.utils") end local function view() return require("nvim-tree.view") end --- TODO 3255 wrap* must be able to take a function. May be best to have that function accept (node, ...) - ----Invoke a method on the singleton explorer. ----Print error when setup not called. ----@param explorer_method string explorer method name ----@return fun(...): any -local function wrap_explorer(explorer_method) - return function(...) - local explorer = core().get_explorer() - if explorer then - return explorer[explorer_method](explorer, ...) - end - end -end - ----Inject the node as the first argument if present otherwise do nothing. ----@param fn fun(node: Node, ...): any ----@return fun(node: Node?, ...): any -local function wrap_node(fn) - return function(node, ...) - node = node or wrap_explorer("get_node_at_cursor")() - if node then - return fn(node, ...) - end - end -end - ----Inject the node or nil as the first argument if absent. ----@param fn fun(node: Node?, ...): any ----@return fun(node: Node?, ...): any -local function wrap_node_or_nil(fn) - return function(node, ...) - node = node or wrap_explorer("get_node_at_cursor")() - return fn(node, ...) - end -end - ----Invoke a member's method on the singleton explorer. ----Print error when setup not called. ----@param explorer_member string explorer member name ----@param member_method string method name to invoke on member ----@param ... any passed to method ----@return fun(...): any -local function wrap_explorer_member_args(explorer_member, member_method, ...) - local method_args = ... - return function(...) - local explorer = core().get_explorer() - if explorer then - return explorer[explorer_member][member_method](explorer[explorer_member], method_args, ...) +---Return a function wrapper that calls fn. +---Injects node or node at cursor as first argument. +---Passes other arguments verbatim. +---@param fn fun(n?: Node, ...): any +---@return fun(n?: Node, ...): any +local function _n(fn) + return function(n, ...) + if not n then + local e = core().get_explorer() + n = e and e:get_node_at_cursor() or nil end + return fn(n, ...) end end ----Invoke a member's method on the singleton explorer. ----Print error when setup not called. ----@param explorer_member string explorer member name ----@param member_method string method name to invoke on member ----@return fun(...): any -local function wrap_explorer_member(explorer_member, member_method) +---Return a function wrapper that calls fn. +---Does nothing when no explorer instance. +---Injects an explorer instance as first arg. +---Passes other arguments verbatim. +---@param fn fun(e: Explorer, ...): any +---@return fun(e: Explorer, ...): any +local function e_(fn) return function(...) - local explorer = core().get_explorer() - if explorer then - return explorer[explorer_member][member_method](explorer[explorer_member], ...) + local e = core().get_explorer() + if e then + return fn(e, ...) end end end ----Wrap a function to be mode-dependent: in visual mode, pass all nodes in the ----visual range; in normal mode, pass a single node. The implementation decides ----how to handle each case. ----@param fn fun(node_or_nodes: Node|Node[], ...): any ----@return fun(node: Node?, ...): any -local function wrap_node_or_visual(fn) - local wrapped = wrap_node(fn) - return function(node, ...) - if utils.is_visual_mode() then - local nodes = utils.get_visual_nodes() - if nodes then - fn(nodes, ...) - end - else - return wrapped(node, ...) +---Return a function wrapper that calls fn. +---Does nothing when no explorer instance. +---Injects an explorer instance as first arg. +---Injects node or node at cursor as second argument. +---Passes other arguments verbatim. +---@param fn fun(e: Explorer, n?: Node, ...): any +---@return fun(e: Explorer, n?: Node, ...): any +local function en(fn) + return function(n, ...) + local e = core().get_explorer() + if e then + return fn(e, n or e:get_node_at_cursor(), ...) end end end -local function hydrate_config(api) - api.config.global = function() return config().g_clone() end - api.config.user = function() return config().u_clone() end -end - -local function hydrate_filter(api) - api.filter.custom.toggle = wrap_explorer_member_args("filters", "toggle", "custom") - api.filter.dotfiles.toggle = wrap_explorer_member_args("filters", "toggle", "dotfiles") - api.filter.git.clean.toggle = wrap_explorer_member_args("filters", "toggle", "git_clean") - api.filter.git.ignored.toggle = wrap_explorer_member_args("filters", "toggle", "git_ignored") - api.filter.live.clear = wrap_explorer_member("live_filter", "clear_filter") - api.filter.live.start = wrap_explorer_member("live_filter", "start_filtering") - api.filter.no_bookmark.toggle = wrap_explorer_member_args("filters", "toggle", "no_bookmark") - api.filter.no_buffer.toggle = wrap_explorer_member_args("filters", "toggle", "no_buffer") - api.filter.toggle = wrap_explorer_member("filters", "toggle") -end - -local function hydrate_fs(api) - api.fs.clear_clipboard = wrap_explorer_member("clipboard", "clear_clipboard") - api.fs.copy.absolute_path = wrap_node(wrap_explorer_member("clipboard", "copy_absolute_path")) - api.fs.copy.basename = wrap_node(wrap_explorer_member("clipboard", "copy_basename")) - api.fs.copy.filename = wrap_node(wrap_explorer_member("clipboard", "copy_filename")) - api.fs.copy.node = wrap_node_or_visual(wrap_explorer_member("clipboard", "copy")) - api.fs.copy.relative_path = wrap_node(wrap_explorer_member("clipboard", "copy_path")) - api.fs.create = wrap_node_or_nil(function(node) actions().fs.create_file.fn(node) end) - api.fs.cut = wrap_node_or_visual(wrap_explorer_member("clipboard", "cut")) - api.fs.paste = wrap_node(wrap_explorer_member("clipboard", "paste")) - api.fs.print_clipboard = wrap_explorer_member("clipboard", "print_clipboard") - api.fs.remove = wrap_node_or_visual(function(node) actions().fs.remove_file.fn(node) end) - api.fs.rename = wrap_node(function(node) actions().fs.rename_file.rename_node(node) end) - api.fs.rename_basename = wrap_node(function(node) actions().fs.rename_file.rename_basename(node) end) - api.fs.rename_full = wrap_node(function(node) actions().fs.rename_file.rename_full(node) end) - api.fs.rename_node = wrap_node(function(node) actions().fs.rename_file.rename_node(node) end) - api.fs.rename_sub = wrap_node(function(node) actions().fs.rename_file.rename_sub(node) end) - api.fs.trash = wrap_node_or_visual(function(node) actions().fs.trash.fn(node) end) +local function ev(fn) + -- TODO following rebase end -local function hydrate_map(api) - api.map.keymap.current = function() return keymap().get_keymap() end +local function _v(fn) + -- TODO following rebase end -local function hydrate_marks(api) - api.marks.bulk.delete = wrap_explorer_member("marks", "bulk_delete") - api.marks.bulk.move = wrap_explorer_member("marks", "bulk_move") - api.marks.bulk.trash = wrap_explorer_member("marks", "bulk_trash") - api.marks.clear = wrap_explorer_member("marks", "clear") - api.marks.get = wrap_node(wrap_explorer_member("marks", "get")) - api.marks.list = wrap_explorer_member("marks", "list") - api.marks.navigate.next = wrap_explorer_member("marks", "navigate_next") - api.marks.navigate.prev = wrap_explorer_member("marks", "navigate_prev") - api.marks.navigate.select = wrap_explorer_member("marks", "navigate_select") - api.marks.toggle = wrap_node_or_visual(wrap_explorer_member("marks", "toggle")) -end - -local function hydrate_node(api) - api.node.buffer.delete = wrap_node(function(node, opts) actions().node.buffer.delete(node, opts) end) - api.node.buffer.wipe = wrap_node(function(node, opts) actions().node.buffer.wipe(node, opts) end) - api.node.collapse = wrap_node(function(node) actions().tree.collapse.node(node) end) - api.node.expand = wrap_node(wrap_explorer("expand_node")) - api.node.navigate.diagnostics.next = function() return actions().moves.item.diagnostics_next() end - api.node.navigate.diagnostics.next_recursive = function() return actions().moves.item.diagnostics_next_recursive() end - api.node.navigate.diagnostics.prev = function() return actions().moves.item.diagnostics_prev() end - api.node.navigate.diagnostics.prev_recursive = function() return actions().moves.item.diagnostics_prev_recursive() end - api.node.navigate.git.next = function() return actions().moves.item.git_next() end - api.node.navigate.git.next_recursive = function() return actions().moves.item.git_next_recursive() end - api.node.navigate.git.next_skip_gitignored = function() return actions().moves.item.git_next_skip_gitignored() end - api.node.navigate.git.prev = function() return actions().moves.item.git_prev() end - api.node.navigate.git.prev_recursive = function() return actions().moves.item.git_prev_recursive() end - api.node.navigate.git.prev_skip_gitignored = function() return actions().moves.item.git_prev_skip_gitignored() end - api.node.navigate.opened.next = function() return actions().moves.item.opened_next() end - api.node.navigate.opened.prev = function() return actions().moves.item.opened_prev() end - api.node.navigate.parent = wrap_node(function(node) actions().moves.parent.move(node) end) - api.node.navigate.parent_close = wrap_node(function(node) actions().moves.parent.move_close(node) end) - api.node.navigate.sibling.first = wrap_node(function(node) actions().moves.sibling.first(node) end) - api.node.navigate.sibling.last = wrap_node(function(node) actions().moves.sibling.last(node) end) - api.node.navigate.sibling.next = wrap_node(function(node) actions().moves.sibling.next(node) end) - api.node.navigate.sibling.prev = wrap_node(function(node) actions().moves.sibling.prev(node) end) - api.node.open.drop = wrap_node(function(node) actions().node.open_file.drop(node) end) - api.node.open.edit = wrap_node(function(node) actions().node.open_file.edit(node) end) - api.node.open.horizontal = wrap_node(function(node) actions().node.open_file.horizontal(node) end) - api.node.open.horizontal_no_picker = wrap_node(function(node) actions().node.open_file.horizontal_no_picker(node) end) - api.node.open.no_window_picker = wrap_node(function(node) actions().node.open_file.no_window_picker(node) end) - api.node.open.preview = wrap_node(function(node) actions().node.open_file.preview(node) end) - api.node.open.preview_no_picker = wrap_node(function(node) actions().node.open_file.preview_no_picker(node) end) - api.node.open.replace_tree_buffer = wrap_node(function(node) actions().node.open_file.replace_tree_buffer(node) end) - api.node.open.tab = wrap_node(function(node) actions().node.open_file.tab(node) end) - api.node.open.tab_drop = wrap_node(function(node) actions().node.open_file.tab_drop(node) end) - api.node.open.toggle_group_empty = wrap_node(function(node) actions().node.open_file.toggle_group_empty(node) end) - api.node.open.vertical = wrap_node(function(node) actions().node.open_file.vertical(node) end) - api.node.open.vertical_no_picker = wrap_node(function(node) actions().node.open_file.vertical_no_picker(node) end) - api.node.run.cmd = wrap_node(function(node) actions().node.run_command.run_file_command(node) end) - api.node.run.system = wrap_node(function(node) actions().node.system_open.fn(node) end) - api.node.show_info_popup = wrap_node(function(node) actions().node.file_popup.toggle_file_info(node) end) -end - -local function hydrate_tree(api) - api.tree.change_root = function() return actions().tree.change_dir.fn() end - api.tree.change_root_to_node = wrap_node(wrap_explorer("change_dir_to_node")) - api.tree.change_root_to_parent = wrap_node(wrap_explorer("dir_up")) - api.tree.close = function() return view().close() end - api.tree.close_in_all_tabs = function() return view().close_all_tabs() end - api.tree.close_in_this_tab = function() return view().close_this_tab_only() end - api.tree.collapse_all = function() return actions().tree.collapse.all() end - api.tree.expand_all = wrap_node(wrap_explorer("expand_all")) - api.tree.find_file = function() return actions().tree.find_file.fn() end - api.tree.focus = api.tree.open - api.tree.get_node_under_cursor = wrap_explorer("get_node_at_cursor") - api.tree.get_nodes = wrap_explorer("get_nodes") - api.tree.is_tree_buf = function() return utils().is_nvim_tree_buf() end - api.tree.is_visible = function() return view().is_visible() end - api.tree.open = function() return actions().tree.open.fn() end - api.tree.reload = wrap_explorer("reload_explorer") - api.tree.reload_git = wrap_explorer("reload_git") - api.tree.resize = function() return actions().tree.resize.fn() end - api.tree.search_node = function() return actions().finders.search_node.fn() end - api.tree.toggle = function() return actions().tree.toggle.fn() end - api.tree.toggle_help = function() return help().toggle() end - api.tree.winid = function() return view().winid() end +---Return a function wrapper that calls fn. +---Passes arguments verbatim. +---Exists for formatting purposes only. +---@param fn fun(...): any +---@return fun(...): any +local function __(fn) + return function(...) + return fn(...) + end end ---Re-Hydrate api functions and classes post-setup ---@param api table not properly typed to prevent LSP from referencing implementations function M.hydrate(api) - -- hydration has been split into functions for readability and formatting - hydrate_config(api) - hydrate_filter(api) - hydrate_fs(api) - hydrate_map(api) - hydrate_marks(api) - hydrate_node(api) - hydrate_tree(api) + api.config.global = __(function() return config().g_clone() end) + api.config.user = __(function() return config().u_clone() end) + + api.filter.custom.toggle = e_(function(e) e.filters:toggle("custom") end) + api.filter.dotfiles.toggle = e_(function(e) e.filters:toggle("dotfiles") end) + api.filter.git.clean.toggle = e_(function(e) e.filters:toggle("git_clean") end) + api.filter.git.ignored.toggle = e_(function(e) e.filters:toggle("git_ignored") end) + api.filter.live.clear = e_(function(e) e.live_filter:clear_filter() end) + api.filter.live.start = e_(function(e) e.live_filter:start_filtering() end) + api.filter.no_bookmark.toggle = e_(function(e) e.filters:toggle("no_bookmark") end) + api.filter.no_buffer.toggle = e_(function(e) e.filters:toggle("no_buffer") end) + api.filter.toggle = e_(function(e) e.filters:toggle() end) + + api.fs.clear_clipboard = e_(function(e) e.clipboard:clear_clipboard() end) + api.fs.copy.absolute_path = en(function(e, n) e.clipboard:copy_absolute_path(n) end) + api.fs.copy.basename = en(function(e, n) e.clipboard:copy_basename(n) end) + api.fs.copy.filename = en(function(e, n) e.clipboard:copy_filename(n) end) + api.fs.copy.node = ev(function(e, n) e.clipboard:copy(n) end) + api.fs.copy.relative_path = en(function(e, n) e.clipboard:copy_path(n) end) + api.fs.create = _n(function(n) actions().fs.create_file.fn(n) end) + api.fs.cut = ev(function(e, n) e.clipboard:cut(n) end) + api.fs.paste = en(function(e, n) e.clipboard:paste(n) end) + api.fs.print_clipboard = e_(function(e) e.clipboard:print_clipboard() end) + api.fs.remove = _v(function(n) actions().fs.remove_file.fn(n) end) + api.fs.rename = _n(function(n) actions().fs.rename_file.rename_node(n) end) + api.fs.rename_basename = _n(function(n) actions().fs.rename_file.rename_basename(n) end) + api.fs.rename_full = _n(function(n) actions().fs.rename_file.rename_full(n) end) + api.fs.rename_node = _n(function(n) actions().fs.rename_file.rename_node(n) end) + api.fs.rename_sub = _n(function(n) actions().fs.rename_file.rename_sub(n) end) + api.fs.trash = _v(function(n) actions().fs.trash.fn(n) end) + + api.map.keymap.current = __(function() return keymap().get_keymap() end) + + api.marks.bulk.delete = e_(function(e) e.marks:bulk_delete() end) + api.marks.bulk.move = e_(function(e) e.marks:bulk_move() end) + api.marks.bulk.trash = e_(function(e) e.marks:bulk_trash() end) + api.marks.clear = e_(function(e) e.marks:clear() end) + api.marks.get = en(function(e, n) return e.marks:get(n) end) + api.marks.list = e_(function(e) return e.marks:list() end) + api.marks.navigate.next = e_(function(e) e.marks:navigate_next() end) + api.marks.navigate.prev = e_(function(e) e.marks:navigate_prev() end) + api.marks.navigate.select = e_(function(e) e.marks:navigate_select() end) + api.marks.toggle = ev(function(e, n) e.marks:toggle(n) end) + + api.node.buffer.delete = _n(function(n, opts) actions().node.buffer.delete(n, opts) end) + api.node.buffer.wipe = _n(function(n, opts) actions().node.buffer.wipe(n, opts) end) + api.node.collapse = _n(function(n) actions().tree.collapse.node(n) end) + api.node.expand = en(function(e, n) e:expand_node(n) end) + api.node.navigate.diagnostics.next = __(function() actions().moves.item.diagnostics_next() end) + api.node.navigate.diagnostics.next_recursive = __(function() actions().moves.item.diagnostics_next_recursive() end) + api.node.navigate.diagnostics.prev = __(function() actions().moves.item.diagnostics_prev() end) + api.node.navigate.diagnostics.prev_recursive = __(function() actions().moves.item.diagnostics_prev_recursive() end) + api.node.navigate.git.next = __(function() actions().moves.item.git_next() end) + api.node.navigate.git.next_recursive = __(function() actions().moves.item.git_next_recursive() end) + api.node.navigate.git.next_skip_gitignored = __(function() actions().moves.item.git_next_skip_gitignored() end) + api.node.navigate.git.prev = __(function() actions().moves.item.git_prev() end) + api.node.navigate.git.prev_recursive = __(function() actions().moves.item.git_prev_recursive() end) + api.node.navigate.git.prev_skip_gitignored = __(function() actions().moves.item.git_prev_skip_gitignored() end) + api.node.navigate.opened.next = __(function() actions().moves.item.opened_next() end) + api.node.navigate.opened.prev = __(function() actions().moves.item.opened_prev() end) + api.node.navigate.parent = _n(function(n) actions().moves.parent.move(n) end) + api.node.navigate.parent_close = _n(function(n) actions().moves.parent.move_close(n) end) + api.node.navigate.sibling.first = _n(function(n) actions().moves.sibling.first(n) end) + api.node.navigate.sibling.last = _n(function(n) actions().moves.sibling.last(n) end) + api.node.navigate.sibling.next = _n(function(n) actions().moves.sibling.next(n) end) + api.node.navigate.sibling.prev = _n(function(n) actions().moves.sibling.prev(n) end) + api.node.open.drop = _n(function(n) actions().node.open_file.drop(n) end) + api.node.open.edit = _n(function(n) actions().node.open_file.edit(n) end) + api.node.open.horizontal = _n(function(n) actions().node.open_file.horizontal(n) end) + api.node.open.horizontal_no_picker = _n(function(n) actions().node.open_file.horizontal_no_picker(n) end) + api.node.open.no_window_picker = _n(function(n) actions().node.open_file.no_window_picker(n) end) + api.node.open.preview = _n(function(n) actions().node.open_file.preview(n) end) + api.node.open.preview_no_picker = _n(function(n) actions().node.open_file.preview_no_picker(n) end) + api.node.open.replace_tree_buffer = _n(function(n) actions().node.open_file.replace_tree_buffer(n) end) + api.node.open.tab = _n(function(n) actions().node.open_file.tab(n) end) + api.node.open.tab_drop = _n(function(n) actions().node.open_file.tab_drop(n) end) + api.node.open.toggle_group_empty = _n(function(n) actions().node.open_file.toggle_group_empty(n) end) + api.node.open.vertical = _n(function(n) actions().node.open_file.vertical(n) end) + api.node.open.vertical_no_picker = _n(function(n) actions().node.open_file.vertical_no_picker(n) end) + api.node.run.cmd = _n(function(n) actions().node.run_command.run_file_command(n) end) + api.node.run.system = _n(function(n) actions().node.system_open.fn(n) end) + api.node.show_info_popup = _n(function(n) actions().node.file_popup.toggle_file_info(n) end) + + api.tree.change_root = __(function(path) actions().tree.change_dir.fn(path) end) + api.tree.change_root_to_node = en(function(e, n) e:change_dir_to_node(n) end) + api.tree.change_root_to_parent = en(function(e, n) e:dir_up(n) end) + api.tree.close = __(function() view().close() end) + api.tree.close_in_all_tabs = __(function() view().close_all_tabs() end) + api.tree.close_in_this_tab = __(function() view().close_this_tab_only() end) + api.tree.collapse_all = __(function() actions().tree.collapse.all() end) + api.tree.expand_all = en(function(e, n, opts) e:expand_all(n, opts) end) + api.tree.find_file = __(function() actions().tree.find_file.fn() end) + api.tree.focus = __(function() actions().tree.open.fn() end) + api.tree.get_node_under_cursor = en(function(e) return e:get_node_at_cursor() end) + api.tree.get_nodes = en(function(e) return e:get_nodes() end) + api.tree.is_tree_buf = __(function() return utils().is_nvim_tree_buf() end) + api.tree.is_visible = __(function() return view().is_visible() end) + api.tree.open = __(function() actions().tree.open.fn() end) + api.tree.reload = e_(function(e) e:reload_explorer() end) + api.tree.reload_git = e_(function(e) e:reload_git() end) + api.tree.resize = __(function() actions().tree.resize.fn() end) + api.tree.search_node = __(function() actions().finders.search_node.fn() end) + api.tree.toggle = __(function() actions().tree.toggle.fn() end) + api.tree.toggle_help = __(function() help().toggle() end) + api.tree.winid = __(function() return view().winid() end) -- (Re)hydrate any legacy by mapping to concrete set above legacy.map_api(api) diff --git a/lua/nvim-tree/explorer/filters.lua b/lua/nvim-tree/explorer/filters.lua index 71be462e4c2..a55298d61ee 100644 --- a/lua/nvim-tree/explorer/filters.lua +++ b/lua/nvim-tree/explorer/filters.lua @@ -273,7 +273,6 @@ function Filters:should_filter_as_reason(path, fs_stat, status) end ---Toggle a type and refresh ----@private ---@param type FilterType? nil to disable all function Filters:toggle(type) if not type or self.state[type] == nil then From 36f4794becbdf7d7c047b0ecef8df8a32f6e3e9c Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Fri, 6 Mar 2026 15:27:13 +1100 Subject: [PATCH 04/11] refactor(#3255): all pre are lazy --- lua/nvim-tree/api/impl/post.lua | 154 +++++++++++++++----------------- lua/nvim-tree/api/impl/pre.lua | 40 +++------ 2 files changed, 85 insertions(+), 109 deletions(-) diff --git a/lua/nvim-tree/api/impl/post.lua b/lua/nvim-tree/api/impl/post.lua index 6c6cea72a7f..676407df226 100644 --- a/lua/nvim-tree/api/impl/post.lua +++ b/lua/nvim-tree/api/impl/post.lua @@ -1,23 +1,10 @@ ---Hydrates all API functions with concrete implementations. ---Replace all "nvim-tree setup not called" error functions from pre.lua with their implementations. --- ----Call this after nvim-tree setup ---- ----All requires must be done lazily so that requiring api post setup is cheap. - -local legacy = require("nvim-tree.legacy") +---Called after nvim-tree setup local M = {} ---- convenience wrappers for lazy module requires -local function actions() return require("nvim-tree.actions") end -local function core() return require("nvim-tree.core") end -local function config() return require("nvim-tree.config") end -local function help() return require("nvim-tree.help") end -local function keymap() return require("nvim-tree.keymap") end -local function utils() return require("nvim-tree.utils") end -local function view() return require("nvim-tree.view") end - ---Return a function wrapper that calls fn. ---Injects node or node at cursor as first argument. ---Passes other arguments verbatim. @@ -26,7 +13,7 @@ local function view() return require("nvim-tree.view") end local function _n(fn) return function(n, ...) if not n then - local e = core().get_explorer() + local e = require("nvim-tree.core").get_explorer() n = e and e:get_node_at_cursor() or nil end return fn(n, ...) @@ -41,7 +28,7 @@ end ---@return fun(e: Explorer, ...): any local function e_(fn) return function(...) - local e = core().get_explorer() + local e = require("nvim-tree.core").get_explorer() if e then return fn(e, ...) end @@ -57,9 +44,10 @@ end ---@return fun(e: Explorer, n?: Node, ...): any local function en(fn) return function(n, ...) - local e = core().get_explorer() + local e = require("nvim-tree.core").get_explorer() if e then - return fn(e, n or e:get_node_at_cursor(), ...) + n = e and e:get_node_at_cursor() or nil + return fn(e, n, ...) end end end @@ -86,8 +74,8 @@ end ---Re-Hydrate api functions and classes post-setup ---@param api table not properly typed to prevent LSP from referencing implementations function M.hydrate(api) - api.config.global = __(function() return config().g_clone() end) - api.config.user = __(function() return config().u_clone() end) + api.config.global = __(function() return require("nvim-tree.config").g_clone() end) + api.config.user = __(function() return require("nvim-tree.config").u_clone() end) api.filter.custom.toggle = e_(function(e) e.filters:toggle("custom") end) api.filter.dotfiles.toggle = e_(function(e) e.filters:toggle("dotfiles") end) @@ -105,19 +93,19 @@ function M.hydrate(api) api.fs.copy.filename = en(function(e, n) e.clipboard:copy_filename(n) end) api.fs.copy.node = ev(function(e, n) e.clipboard:copy(n) end) api.fs.copy.relative_path = en(function(e, n) e.clipboard:copy_path(n) end) - api.fs.create = _n(function(n) actions().fs.create_file.fn(n) end) + api.fs.create = _n(function(n) require("nvim-tree.actions").fs.create_file.fn(n) end) api.fs.cut = ev(function(e, n) e.clipboard:cut(n) end) api.fs.paste = en(function(e, n) e.clipboard:paste(n) end) api.fs.print_clipboard = e_(function(e) e.clipboard:print_clipboard() end) - api.fs.remove = _v(function(n) actions().fs.remove_file.fn(n) end) - api.fs.rename = _n(function(n) actions().fs.rename_file.rename_node(n) end) - api.fs.rename_basename = _n(function(n) actions().fs.rename_file.rename_basename(n) end) - api.fs.rename_full = _n(function(n) actions().fs.rename_file.rename_full(n) end) - api.fs.rename_node = _n(function(n) actions().fs.rename_file.rename_node(n) end) - api.fs.rename_sub = _n(function(n) actions().fs.rename_file.rename_sub(n) end) - api.fs.trash = _v(function(n) actions().fs.trash.fn(n) end) + api.fs.remove = _v(function(n) require("nvim-tree.actions").fs.remove_file.fn(n) end) + api.fs.rename = _n(function(n) require("nvim-tree.actions").fs.rename_file.rename_node(n) end) + api.fs.rename_basename = _n(function(n) require("nvim-tree.actions").fs.rename_file.rename_basename(n) end) + api.fs.rename_full = _n(function(n) require("nvim-tree.actions").fs.rename_file.rename_full(n) end) + api.fs.rename_node = _n(function(n) require("nvim-tree.actions").fs.rename_file.rename_node(n) end) + api.fs.rename_sub = _n(function(n) require("nvim-tree.actions").fs.rename_file.rename_sub(n) end) + api.fs.trash = _v(function(n) require("nvim-tree.actions").fs.trash.fn(n) end) - api.map.keymap.current = __(function() return keymap().get_keymap() end) + api.map.keymap.current = __(function() return require("nvim-tree.keymap").get_keymap() end) api.marks.bulk.delete = e_(function(e) e.marks:bulk_delete() end) api.marks.bulk.move = e_(function(e) e.marks:bulk_move() end) @@ -130,70 +118,70 @@ function M.hydrate(api) api.marks.navigate.select = e_(function(e) e.marks:navigate_select() end) api.marks.toggle = ev(function(e, n) e.marks:toggle(n) end) - api.node.buffer.delete = _n(function(n, opts) actions().node.buffer.delete(n, opts) end) - api.node.buffer.wipe = _n(function(n, opts) actions().node.buffer.wipe(n, opts) end) - api.node.collapse = _n(function(n) actions().tree.collapse.node(n) end) + api.node.buffer.delete = _n(function(n, opts) require("nvim-tree.actions").node.buffer.delete(n, opts) end) + api.node.buffer.wipe = _n(function(n, opts) require("nvim-tree.actions").node.buffer.wipe(n, opts) end) + api.node.collapse = _n(function(n) require("nvim-tree.actions").tree.collapse.node(n) end) api.node.expand = en(function(e, n) e:expand_node(n) end) - api.node.navigate.diagnostics.next = __(function() actions().moves.item.diagnostics_next() end) - api.node.navigate.diagnostics.next_recursive = __(function() actions().moves.item.diagnostics_next_recursive() end) - api.node.navigate.diagnostics.prev = __(function() actions().moves.item.diagnostics_prev() end) - api.node.navigate.diagnostics.prev_recursive = __(function() actions().moves.item.diagnostics_prev_recursive() end) - api.node.navigate.git.next = __(function() actions().moves.item.git_next() end) - api.node.navigate.git.next_recursive = __(function() actions().moves.item.git_next_recursive() end) - api.node.navigate.git.next_skip_gitignored = __(function() actions().moves.item.git_next_skip_gitignored() end) - api.node.navigate.git.prev = __(function() actions().moves.item.git_prev() end) - api.node.navigate.git.prev_recursive = __(function() actions().moves.item.git_prev_recursive() end) - api.node.navigate.git.prev_skip_gitignored = __(function() actions().moves.item.git_prev_skip_gitignored() end) - api.node.navigate.opened.next = __(function() actions().moves.item.opened_next() end) - api.node.navigate.opened.prev = __(function() actions().moves.item.opened_prev() end) - api.node.navigate.parent = _n(function(n) actions().moves.parent.move(n) end) - api.node.navigate.parent_close = _n(function(n) actions().moves.parent.move_close(n) end) - api.node.navigate.sibling.first = _n(function(n) actions().moves.sibling.first(n) end) - api.node.navigate.sibling.last = _n(function(n) actions().moves.sibling.last(n) end) - api.node.navigate.sibling.next = _n(function(n) actions().moves.sibling.next(n) end) - api.node.navigate.sibling.prev = _n(function(n) actions().moves.sibling.prev(n) end) - api.node.open.drop = _n(function(n) actions().node.open_file.drop(n) end) - api.node.open.edit = _n(function(n) actions().node.open_file.edit(n) end) - api.node.open.horizontal = _n(function(n) actions().node.open_file.horizontal(n) end) - api.node.open.horizontal_no_picker = _n(function(n) actions().node.open_file.horizontal_no_picker(n) end) - api.node.open.no_window_picker = _n(function(n) actions().node.open_file.no_window_picker(n) end) - api.node.open.preview = _n(function(n) actions().node.open_file.preview(n) end) - api.node.open.preview_no_picker = _n(function(n) actions().node.open_file.preview_no_picker(n) end) - api.node.open.replace_tree_buffer = _n(function(n) actions().node.open_file.replace_tree_buffer(n) end) - api.node.open.tab = _n(function(n) actions().node.open_file.tab(n) end) - api.node.open.tab_drop = _n(function(n) actions().node.open_file.tab_drop(n) end) - api.node.open.toggle_group_empty = _n(function(n) actions().node.open_file.toggle_group_empty(n) end) - api.node.open.vertical = _n(function(n) actions().node.open_file.vertical(n) end) - api.node.open.vertical_no_picker = _n(function(n) actions().node.open_file.vertical_no_picker(n) end) - api.node.run.cmd = _n(function(n) actions().node.run_command.run_file_command(n) end) - api.node.run.system = _n(function(n) actions().node.system_open.fn(n) end) - api.node.show_info_popup = _n(function(n) actions().node.file_popup.toggle_file_info(n) end) - - api.tree.change_root = __(function(path) actions().tree.change_dir.fn(path) end) + api.node.navigate.diagnostics.next = __(function() require("nvim-tree.actions").moves.item.diagnostics_next() end) + api.node.navigate.diagnostics.next_recursive = __(function() require("nvim-tree.actions").moves.item.diagnostics_next_recursive() end) + api.node.navigate.diagnostics.prev = __(function() require("nvim-tree.actions").moves.item.diagnostics_prev() end) + api.node.navigate.diagnostics.prev_recursive = __(function() require("nvim-tree.actions").moves.item.diagnostics_prev_recursive() end) + api.node.navigate.git.next = __(function() require("nvim-tree.actions").moves.item.git_next() end) + api.node.navigate.git.next_recursive = __(function() require("nvim-tree.actions").moves.item.git_next_recursive() end) + api.node.navigate.git.next_skip_gitignored = __(function() require("nvim-tree.actions").moves.item.git_next_skip_gitignored() end) + api.node.navigate.git.prev = __(function() require("nvim-tree.actions").moves.item.git_prev() end) + api.node.navigate.git.prev_recursive = __(function() require("nvim-tree.actions").moves.item.git_prev_recursive() end) + api.node.navigate.git.prev_skip_gitignored = __(function() require("nvim-tree.actions").moves.item.git_prev_skip_gitignored() end) + api.node.navigate.opened.next = __(function() require("nvim-tree.actions").moves.item.opened_next() end) + api.node.navigate.opened.prev = __(function() require("nvim-tree.actions").moves.item.opened_prev() end) + api.node.navigate.parent = _n(function(n) require("nvim-tree.actions").moves.parent.move(n) end) + api.node.navigate.parent_close = _n(function(n) require("nvim-tree.actions").moves.parent.move_close(n) end) + api.node.navigate.sibling.first = _n(function(n) require("nvim-tree.actions").moves.sibling.first(n) end) + api.node.navigate.sibling.last = _n(function(n) require("nvim-tree.actions").moves.sibling.last(n) end) + api.node.navigate.sibling.next = _n(function(n) require("nvim-tree.actions").moves.sibling.next(n) end) + api.node.navigate.sibling.prev = _n(function(n) require("nvim-tree.actions").moves.sibling.prev(n) end) + api.node.open.drop = _n(function(n) require("nvim-tree.actions").node.open_file.drop(n) end) + api.node.open.edit = _n(function(n) require("nvim-tree.actions").node.open_file.edit(n) end) + api.node.open.horizontal = _n(function(n) require("nvim-tree.actions").node.open_file.horizontal(n) end) + api.node.open.horizontal_no_picker = _n(function(n) require("nvim-tree.actions").node.open_file.horizontal_no_picker(n) end) + api.node.open.no_window_picker = _n(function(n) require("nvim-tree.actions").node.open_file.no_window_picker(n) end) + api.node.open.preview = _n(function(n) require("nvim-tree.actions").node.open_file.preview(n) end) + api.node.open.preview_no_picker = _n(function(n) require("nvim-tree.actions").node.open_file.preview_no_picker(n) end) + api.node.open.replace_tree_buffer = _n(function(n) require("nvim-tree.actions").node.open_file.replace_tree_buffer(n) end) + api.node.open.tab = _n(function(n) require("nvim-tree.actions").node.open_file.tab(n) end) + api.node.open.tab_drop = _n(function(n) require("nvim-tree.actions").node.open_file.tab_drop(n) end) + api.node.open.toggle_group_empty = _n(function(n) require("nvim-tree.actions").node.open_file.toggle_group_empty(n) end) + api.node.open.vertical = _n(function(n) require("nvim-tree.actions").node.open_file.vertical(n) end) + api.node.open.vertical_no_picker = _n(function(n) require("nvim-tree.actions").node.open_file.vertical_no_picker(n) end) + api.node.run.cmd = _n(function(n) require("nvim-tree.actions").node.run_command.run_file_command(n) end) + api.node.run.system = _n(function(n) require("nvim-tree.actions").node.system_open.fn(n) end) + api.node.show_info_popup = _n(function(n) require("nvim-tree.actions").node.file_popup.toggle_file_info(n) end) + + api.tree.change_root = __(function(path) require("nvim-tree.actions").tree.change_dir.fn(path) end) api.tree.change_root_to_node = en(function(e, n) e:change_dir_to_node(n) end) api.tree.change_root_to_parent = en(function(e, n) e:dir_up(n) end) - api.tree.close = __(function() view().close() end) - api.tree.close_in_all_tabs = __(function() view().close_all_tabs() end) - api.tree.close_in_this_tab = __(function() view().close_this_tab_only() end) - api.tree.collapse_all = __(function() actions().tree.collapse.all() end) + api.tree.close = __(function() require("nvim-tree.view").close() end) + api.tree.close_in_all_tabs = __(function() require("nvim-tree.view").close_all_tabs() end) + api.tree.close_in_this_tab = __(function() require("nvim-tree.view").close_this_tab_only() end) + api.tree.collapse_all = __(function() require("nvim-tree.actions").tree.collapse.all() end) api.tree.expand_all = en(function(e, n, opts) e:expand_all(n, opts) end) - api.tree.find_file = __(function() actions().tree.find_file.fn() end) - api.tree.focus = __(function() actions().tree.open.fn() end) + api.tree.find_file = __(function() require("nvim-tree.actions").tree.find_file.fn() end) + api.tree.focus = __(function() require("nvim-tree.actions").tree.open.fn() end) api.tree.get_node_under_cursor = en(function(e) return e:get_node_at_cursor() end) api.tree.get_nodes = en(function(e) return e:get_nodes() end) - api.tree.is_tree_buf = __(function() return utils().is_nvim_tree_buf() end) - api.tree.is_visible = __(function() return view().is_visible() end) - api.tree.open = __(function() actions().tree.open.fn() end) + api.tree.is_tree_buf = __(function() return require("nvim-tree.utils").is_nvim_tree_buf() end) + api.tree.is_visible = __(function() return require("nvim-tree.view").is_visible() end) + api.tree.open = __(function() require("nvim-tree.actions").tree.open.fn() end) api.tree.reload = e_(function(e) e:reload_explorer() end) api.tree.reload_git = e_(function(e) e:reload_git() end) - api.tree.resize = __(function() actions().tree.resize.fn() end) - api.tree.search_node = __(function() actions().finders.search_node.fn() end) - api.tree.toggle = __(function() actions().tree.toggle.fn() end) - api.tree.toggle_help = __(function() help().toggle() end) - api.tree.winid = __(function() return view().winid() end) + api.tree.resize = __(function() require("nvim-tree.actions").tree.resize.fn() end) + api.tree.search_node = __(function() require("nvim-tree.actions").finders.search_node.fn() end) + api.tree.toggle = __(function() require("nvim-tree.actions").tree.toggle.fn() end) + api.tree.toggle_help = __(function() require("nvim-tree.help").toggle() end) + api.tree.winid = __(function() return require("nvim-tree.view").winid() end) -- (Re)hydrate any legacy by mapping to concrete set above - legacy.map_api(api) + require("nvim-tree.legacy").map_api(api) end return M diff --git a/lua/nvim-tree/api/impl/pre.lua b/lua/nvim-tree/api/impl/pre.lua index 93c5849b62a..4e65395af5e 100644 --- a/lua/nvim-tree/api/impl/pre.lua +++ b/lua/nvim-tree/api/impl/pre.lua @@ -3,20 +3,7 @@ -- - Post-setup functions will notify error: "nvim-tree setup not called" -- - All classes will be hydrated with their implementations. -- ---Call it once when api is first required --- ---This file should have minimal requires that are cheap and have no dependencies or are already required. --- ---Everything must be as lazily loaded as possible: the user must be able to require api cheaply. - -local legacy = require("nvim-tree.legacy") - -local commands = require("nvim-tree.commands") -- already required by plugin.lua -local events = require("nvim-tree.events") -- needed for event registration pre-setup -local keymap = require("nvim-tree.keymap") -- needed for default on attach -local notify = require("nvim-tree.notify") -- already required by events and others - -local Decorator = require("nvim-tree.renderer.decorator") +--Called once when api is first required local M = {} @@ -27,7 +14,7 @@ local function hydrate_error(t) for k, v in pairs(t) do if type(v) == "function" then t[k] = function() - notify.error("nvim-tree setup not called") + require("nvim-tree.notify").error("nvim-tree setup not called") end elseif type(v) == "table" and not getmetatable(v) then hydrate_error(v) @@ -41,22 +28,23 @@ function M.hydrate(api) -- default to the error message hydrate_error(api) - -- eager functions - api.events.subscribe = events.subscribe - api.map.on_attach.default = keymap.on_attach_default - api.commands.get = commands.get - api.map.keymap.default = keymap.get_keymap_default + api.appearance.hi_test = function() require("nvim-tree.appearance.hi-test")() end + + api.commands.get = function() return require("nvim-tree.commands").get() end + + api.config.default = function() return require("nvim-tree.config").d_clone() end + + api.events.subscribe = function(event_name, handler) require("nvim-tree.events").subscribe(event_name, handler) end - -- lazy functions - api.appearance.hi_test = function() require("nvim-tree.appearance.hi-test")() end - api.config.default = function() return require("nvim-tree.config").d_clone() end + api.map.keymap.default = function() return require("nvim-tree.keymap").get_keymap_default() end + api.map.on_attach.default = function(bufnr) require("nvim-tree.keymap").on_attach_default(bufnr) end -- classes - api.Decorator = Decorator:extend() - api.events.Event = events.Event + api.Decorator = function() return require("nvim-tree.renderer.decorator"):extend() end + api.events.Event = require("nvim-tree.events").Event -- TODO 3255 move this to meta -- Hydrate any legacy by mapping to concrete set above - legacy.map_api(api) + require("nvim-tree.legacy").map_api(api) end return M From a47264393eef08dd19167f76763505cdc0983fb2 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Fri, 6 Mar 2026 15:47:13 +1100 Subject: [PATCH 05/11] refactor(#3255): move Event to meta --- lua/nvim-tree/_meta/api/events.lua | 18 +++++++++++ lua/nvim-tree/api/impl/pre.lua | 8 ++--- lua/nvim-tree/events.lua | 49 ++++++++++-------------------- 3 files changed, 37 insertions(+), 38 deletions(-) diff --git a/lua/nvim-tree/_meta/api/events.lua b/lua/nvim-tree/_meta/api/events.lua index 7e79e1c2c65..978ae8f2aa2 100644 --- a/lua/nvim-tree/_meta/api/events.lua +++ b/lua/nvim-tree/_meta/api/events.lua @@ -1,6 +1,24 @@ ---@meta local nvim_tree = { api = { events = {} } } +nvim_tree.api.events.Event = { + Ready = "Ready", + WillRenameNode = "WillRenameNode", + NodeRenamed = "NodeRenamed", + TreePreOpen = "TreePreOpen", + TreeOpen = "TreeOpen", + TreeClose = "TreeClose", + WillCreateFile = "WillCreateFile", + FileCreated = "FileCreated", + WillRemoveFile = "WillRemoveFile", + FileRemoved = "FileRemoved", + FolderCreated = "FolderCreated", + FolderRemoved = "FolderRemoved", + Resize = "Resize", + TreeAttachedPost = "TreeAttachedPost", + TreeRendered = "TreeRendered", +} + --- ---Register a handler for an event, see [nvim-tree-events]. --- diff --git a/lua/nvim-tree/api/impl/pre.lua b/lua/nvim-tree/api/impl/pre.lua index 4e65395af5e..269b88cc0fa 100644 --- a/lua/nvim-tree/api/impl/pre.lua +++ b/lua/nvim-tree/api/impl/pre.lua @@ -25,9 +25,11 @@ end ---Hydrate api functions and classes pre-setup ---@param api table not properly typed to prevent LSP from referencing implementations function M.hydrate(api) - -- default to the error message + -- default everything to the error message hydrate_error(api) + api.Decorator = require("nvim-tree.renderer.decorator") + api.appearance.hi_test = function() require("nvim-tree.appearance.hi-test")() end api.commands.get = function() return require("nvim-tree.commands").get() end @@ -39,10 +41,6 @@ function M.hydrate(api) api.map.keymap.default = function() return require("nvim-tree.keymap").get_keymap_default() end api.map.on_attach.default = function(bufnr) require("nvim-tree.keymap").on_attach_default(bufnr) end - -- classes - api.Decorator = function() return require("nvim-tree.renderer.decorator"):extend() end - api.events.Event = require("nvim-tree.events").Event -- TODO 3255 move this to meta - -- Hydrate any legacy by mapping to concrete set above require("nvim-tree.legacy").map_api(api) end diff --git a/lua/nvim-tree/events.lua b/lua/nvim-tree/events.lua index e9dde833ba4..85e455ece52 100644 --- a/lua/nvim-tree/events.lua +++ b/lua/nvim-tree/events.lua @@ -1,27 +1,10 @@ local notify = require("nvim-tree.notify") +local Event = require("nvim-tree._meta.api.events").Event local M = {} local global_handlers = {} -M.Event = { - Ready = "Ready", - WillRenameNode = "WillRenameNode", - NodeRenamed = "NodeRenamed", - TreePreOpen = "TreePreOpen", - TreeOpen = "TreeOpen", - TreeClose = "TreeClose", - WillCreateFile = "WillCreateFile", - FileCreated = "FileCreated", - WillRemoveFile = "WillRemoveFile", - FileRemoved = "FileRemoved", - FolderCreated = "FolderCreated", - FolderRemoved = "FolderRemoved", - Resize = "Resize", - TreeAttachedPost = "TreeAttachedPost", - TreeRendered = "TreeRendered", -} - ---@param event_name string ---@return table local function get_handlers(event_name) @@ -49,77 +32,77 @@ end --@private function M._dispatch_ready() - dispatch(M.Event.Ready) + dispatch(Event.Ready) end --@private function M._dispatch_will_rename_node(old_name, new_name) - dispatch(M.Event.WillRenameNode, { old_name = old_name, new_name = new_name }) + dispatch(Event.WillRenameNode, { old_name = old_name, new_name = new_name }) end --@private function M._dispatch_node_renamed(old_name, new_name) - dispatch(M.Event.NodeRenamed, { old_name = old_name, new_name = new_name }) + dispatch(Event.NodeRenamed, { old_name = old_name, new_name = new_name }) end --@private function M._dispatch_will_remove_file(fname) - dispatch(M.Event.WillRemoveFile, { fname = fname }) + dispatch(Event.WillRemoveFile, { fname = fname }) end --@private function M._dispatch_file_removed(fname) - dispatch(M.Event.FileRemoved, { fname = fname }) + dispatch(Event.FileRemoved, { fname = fname }) end --@private function M._dispatch_will_create_file(fname) - dispatch(M.Event.WillCreateFile, { fname = fname }) + dispatch(Event.WillCreateFile, { fname = fname }) end --@private function M._dispatch_file_created(fname) - dispatch(M.Event.FileCreated, { fname = fname }) + dispatch(Event.FileCreated, { fname = fname }) end --@private function M._dispatch_folder_created(folder_name) - dispatch(M.Event.FolderCreated, { folder_name = folder_name }) + dispatch(Event.FolderCreated, { folder_name = folder_name }) end --@private function M._dispatch_folder_removed(folder_name) - dispatch(M.Event.FolderRemoved, { folder_name = folder_name }) + dispatch(Event.FolderRemoved, { folder_name = folder_name }) end --@private function M._dispatch_on_tree_pre_open() - dispatch(M.Event.TreePreOpen, nil) + dispatch(Event.TreePreOpen, nil) end --@private function M._dispatch_on_tree_open() - dispatch(M.Event.TreeOpen, nil) + dispatch(Event.TreeOpen, nil) end --@private function M._dispatch_on_tree_close() - dispatch(M.Event.TreeClose, nil) + dispatch(Event.TreeClose, nil) end --@private function M._dispatch_on_tree_resize(size) - dispatch(M.Event.Resize, size) + dispatch(Event.Resize, size) end --@private function M._dispatch_tree_attached_post(buf) - dispatch(M.Event.TreeAttachedPost, buf) + dispatch(Event.TreeAttachedPost, buf) end --@private function M._dispatch_on_tree_rendered(bufnr, winnr) - dispatch(M.Event.TreeRendered, { bufnr = bufnr, winnr = winnr }) + dispatch(Event.TreeRendered, { bufnr = bufnr, winnr = winnr }) end return M From 29efd62274d7cb84b92ed43901026fb3f0caa317 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Fri, 6 Mar 2026 16:28:26 +1100 Subject: [PATCH 06/11] refactor(#3255): ensure all args passed to api --- lua/nvim-tree/api/impl/post.lua | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lua/nvim-tree/api/impl/post.lua b/lua/nvim-tree/api/impl/post.lua index 676407df226..178d039ae84 100644 --- a/lua/nvim-tree/api/impl/post.lua +++ b/lua/nvim-tree/api/impl/post.lua @@ -105,6 +105,8 @@ function M.hydrate(api) api.fs.rename_sub = _n(function(n) require("nvim-tree.actions").fs.rename_file.rename_sub(n) end) api.fs.trash = _v(function(n) require("nvim-tree.actions").fs.trash.fn(n) end) + api.git.reload = e_(function(e) e:reload_git() end) + api.map.keymap.current = __(function() return require("nvim-tree.keymap").get_keymap() end) api.marks.bulk.delete = e_(function(e) e.marks:bulk_delete() end) @@ -163,22 +165,22 @@ function M.hydrate(api) api.tree.close = __(function() require("nvim-tree.view").close() end) api.tree.close_in_all_tabs = __(function() require("nvim-tree.view").close_all_tabs() end) api.tree.close_in_this_tab = __(function() require("nvim-tree.view").close_this_tab_only() end) - api.tree.collapse_all = __(function() require("nvim-tree.actions").tree.collapse.all() end) + api.tree.collapse_all = __(function(opts) require("nvim-tree.actions").tree.collapse.all(opts) end) api.tree.expand_all = en(function(e, n, opts) e:expand_all(n, opts) end) - api.tree.find_file = __(function() require("nvim-tree.actions").tree.find_file.fn() end) - api.tree.focus = __(function() require("nvim-tree.actions").tree.open.fn() end) + api.tree.find_file = __(function(opts) require("nvim-tree.actions").tree.find_file.fn(opts) end) + api.tree.focus = __(function(opts) require("nvim-tree.actions").tree.open.fn(opts) end) api.tree.get_node_under_cursor = en(function(e) return e:get_node_at_cursor() end) api.tree.get_nodes = en(function(e) return e:get_nodes() end) - api.tree.is_tree_buf = __(function() return require("nvim-tree.utils").is_nvim_tree_buf() end) - api.tree.is_visible = __(function() return require("nvim-tree.view").is_visible() end) - api.tree.open = __(function() require("nvim-tree.actions").tree.open.fn() end) + api.tree.is_tree_buf = __(function(bufnr) return require("nvim-tree.utils").is_nvim_tree_buf(bufnr) end) + api.tree.is_visible = __(function(opts) return require("nvim-tree.view").is_visible(opts) end) + api.tree.open = __(function(opts) require("nvim-tree.actions").tree.open.fn(opts) end) api.tree.reload = e_(function(e) e:reload_explorer() end) api.tree.reload_git = e_(function(e) e:reload_git() end) - api.tree.resize = __(function() require("nvim-tree.actions").tree.resize.fn() end) + api.tree.resize = __(function(opts) require("nvim-tree.actions").tree.resize.fn(opts) end) api.tree.search_node = __(function() require("nvim-tree.actions").finders.search_node.fn() end) - api.tree.toggle = __(function() require("nvim-tree.actions").tree.toggle.fn() end) + api.tree.toggle = __(function(opts) require("nvim-tree.actions").tree.toggle.fn(opts) end) api.tree.toggle_help = __(function() require("nvim-tree.help").toggle() end) - api.tree.winid = __(function() return require("nvim-tree.view").winid() end) + api.tree.winid = __(function(opts) return require("nvim-tree.view").winid(opts) end) -- (Re)hydrate any legacy by mapping to concrete set above require("nvim-tree.legacy").map_api(api) From 2c0860b783b845d967dfde43ba96a547152c607a Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Fri, 6 Mar 2026 16:37:28 +1100 Subject: [PATCH 07/11] refactor(#3255): ensure all args passed to api, fix incorrectly documented api interface --- lua/nvim-tree/_meta/api/marks.lua | 3 ++- lua/nvim-tree/_meta/api/node.lua | 27 +++++++++------------------ lua/nvim-tree/api/impl/post.lua | 4 ++-- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/lua/nvim-tree/_meta/api/marks.lua b/lua/nvim-tree/_meta/api/marks.lua index 07244db7789..5ade6745d79 100644 --- a/lua/nvim-tree/_meta/api/marks.lua +++ b/lua/nvim-tree/_meta/api/marks.lua @@ -4,8 +4,9 @@ local nvim_tree = { api = { marks = {} } } --- ---Return the node if it is marked. --- +---@param node? nvim_tree.api.Node file or directory ---@return nvim_tree.api.Node? -function nvim_tree.api.marks.get() end +function nvim_tree.api.marks.get(node) end --- ---Retrieve all marked nodes. diff --git a/lua/nvim-tree/_meta/api/node.lua b/lua/nvim-tree/_meta/api/node.lua index e6652bebc1b..1c6fd4e5225 100644 --- a/lua/nvim-tree/_meta/api/node.lua +++ b/lua/nvim-tree/_meta/api/node.lua @@ -29,36 +29,31 @@ function nvim_tree.api.node.open.edit(node, opts) end ---Open file in a new horizontal split. --- ---@param node? nvim_tree.api.Node file ----@param opts? nvim_tree.api.node.open.Opts optional -function nvim_tree.api.node.open.horizontal(node, opts) end +function nvim_tree.api.node.open.horizontal(node) end --- ---Open file in a new horizontal split without using the window picker. --- ---@param node? nvim_tree.api.Node file ----@param opts? nvim_tree.api.node.open.Opts optional -function nvim_tree.api.node.open.horizontal_no_picker(node, opts) end +function nvim_tree.api.node.open.horizontal_no_picker(node) end --- ---Open file without using the window picker. --- ---@param node? nvim_tree.api.Node file ----@param opts? nvim_tree.api.node.open.Opts optional -function nvim_tree.api.node.open.no_window_picker(node, opts) end +function nvim_tree.api.node.open.no_window_picker(node) end --- ---Open file with ['bufhidden'] set to `delete`. --- ---@param node? nvim_tree.api.Node directory or file ----@param opts? nvim_tree.api.node.open.Opts optional -function nvim_tree.api.node.open.preview(node, opts) end +function nvim_tree.api.node.open.preview(node) end --- ---Open file with ['bufhidden'] set to `delete` without using the window picker. --- ---@param node? nvim_tree.api.Node directory or file ----@param opts? nvim_tree.api.node.open.Opts optional -function nvim_tree.api.node.open.preview_no_picker(node, opts) end +function nvim_tree.api.node.open.preview_no_picker(node) end --- ---Open file in place: in the nvim-tree window. @@ -70,8 +65,7 @@ function nvim_tree.api.node.open.replace_tree_buffer(node) end ---Open file in a new tab. --- ---@param node? nvim_tree.api.Node directory or file ----@param opts? nvim_tree.api.node.open.Opts optional -function nvim_tree.api.node.open.tab(node, opts) end +function nvim_tree.api.node.open.tab(node) end --- ---Switch to tab containing window with selected file if it exists. Open file in new tab otherwise. @@ -83,22 +77,19 @@ function nvim_tree.api.node.open.tab_drop(node) end ---Toggle [nvim_tree.config.renderer] {group_empty} for a directory. Needs {group_empty} set. --- ---@param node? nvim_tree.api.Node directory ----@param opts? nvim_tree.api.node.open.Opts optional -function nvim_tree.api.node.open.toggle_group_empty(node, opts) end +function nvim_tree.api.node.open.toggle_group_empty(node) end --- ---Open file in a new vertical split. --- ---@param node? nvim_tree.api.Node file ----@param opts? nvim_tree.api.node.open.Opts optional -function nvim_tree.api.node.open.vertical(node, opts) end +function nvim_tree.api.node.open.vertical(node) end --- ---Open file in a new vertical split without using the window picker. --- ---@param node? nvim_tree.api.Node file ----@param opts? nvim_tree.api.node.open.Opts optional -function nvim_tree.api.node.open.vertical_no_picker(node, opts) end +function nvim_tree.api.node.open.vertical_no_picker(node) end --- ---@class nvim_tree.api.node.buffer.RemoveOpts diff --git a/lua/nvim-tree/api/impl/post.lua b/lua/nvim-tree/api/impl/post.lua index 178d039ae84..fb96c942a0a 100644 --- a/lua/nvim-tree/api/impl/post.lua +++ b/lua/nvim-tree/api/impl/post.lua @@ -122,8 +122,8 @@ function M.hydrate(api) api.node.buffer.delete = _n(function(n, opts) require("nvim-tree.actions").node.buffer.delete(n, opts) end) api.node.buffer.wipe = _n(function(n, opts) require("nvim-tree.actions").node.buffer.wipe(n, opts) end) - api.node.collapse = _n(function(n) require("nvim-tree.actions").tree.collapse.node(n) end) - api.node.expand = en(function(e, n) e:expand_node(n) end) + api.node.collapse = _n(function(n, opts) require("nvim-tree.actions").tree.collapse.node(n, opts) end) + api.node.expand = en(function(e, n, opts) e:expand_node(n, opts) end) api.node.navigate.diagnostics.next = __(function() require("nvim-tree.actions").moves.item.diagnostics_next() end) api.node.navigate.diagnostics.next_recursive = __(function() require("nvim-tree.actions").moves.item.diagnostics_next_recursive() end) api.node.navigate.diagnostics.prev = __(function() require("nvim-tree.actions").moves.item.diagnostics_prev() end) From b8f7e2bfe22b58d358dcc5b3e815d95a8de68c73 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Fri, 6 Mar 2026 16:39:39 +1100 Subject: [PATCH 08/11] refactor(#3255): ensure all args passed to api, fix incorrectly documented api interface --- doc/nvim-tree-lua.txt | 68 +++++++++---------------------------------- 1 file changed, 13 insertions(+), 55 deletions(-) diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index ad2dc970366..52e5adf6f02 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -2616,9 +2616,12 @@ bulk.trash() *nvim_tree.api.marks.bulk.trash()* clear() *nvim_tree.api.marks.clear()* Clear all marks. -get() *nvim_tree.api.marks.get()* +get({node}) *nvim_tree.api.marks.get()* Return the node if it is marked. + Parameters: ~ + • {node} (`nvim_tree.api.Node?`) file or directory + Return: ~ (`nvim_tree.api.Node?`) @@ -2828,64 +2831,39 @@ open.edit({node}, {opts}) *nvim_tree.api.node.open.edit()* • {focus}? (`boolean`, default: false) Keep focus in the tree when opening the file. -open.horizontal({node}, {opts}) *nvim_tree.api.node.open.horizontal()* +open.horizontal({node}) *nvim_tree.api.node.open.horizontal()* Open file in a new horizontal split. Parameters: ~ • {node} (`nvim_tree.api.Node?`) file - • {opts} (`table?`) optional - • {quit_on_open}? (`boolean`, default: false) Quits the tree - when opening the file. - • {focus}? (`boolean`, default: false) Keep focus in the tree - when opening the file. *nvim_tree.api.node.open.horizontal_no_picker()* -open.horizontal_no_picker({node}, {opts}) +open.horizontal_no_picker({node}) Open file in a new horizontal split without using the window picker. Parameters: ~ • {node} (`nvim_tree.api.Node?`) file - • {opts} (`table?`) optional - • {quit_on_open}? (`boolean`, default: false) Quits the tree - when opening the file. - • {focus}? (`boolean`, default: false) Keep focus in the tree - when opening the file. *nvim_tree.api.node.open.no_window_picker()* -open.no_window_picker({node}, {opts}) +open.no_window_picker({node}) Open file without using the window picker. Parameters: ~ • {node} (`nvim_tree.api.Node?`) file - • {opts} (`table?`) optional - • {quit_on_open}? (`boolean`, default: false) Quits the tree - when opening the file. - • {focus}? (`boolean`, default: false) Keep focus in the tree - when opening the file. -open.preview({node}, {opts}) *nvim_tree.api.node.open.preview()* +open.preview({node}) *nvim_tree.api.node.open.preview()* Open file with |'bufhidden'| set to `delete`. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file - • {opts} (`table?`) optional - • {quit_on_open}? (`boolean`, default: false) Quits the tree - when opening the file. - • {focus}? (`boolean`, default: false) Keep focus in the tree - when opening the file. *nvim_tree.api.node.open.preview_no_picker()* -open.preview_no_picker({node}, {opts}) +open.preview_no_picker({node}) Open file with |'bufhidden'| set to `delete` without using the window picker. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file - • {opts} (`table?`) optional - • {quit_on_open}? (`boolean`, default: false) Quits the tree - when opening the file. - • {focus}? (`boolean`, default: false) Keep focus in the tree - when opening the file. *nvim_tree.api.node.open.replace_tree_buffer()* open.replace_tree_buffer({node}) @@ -2894,16 +2872,11 @@ open.replace_tree_buffer({node}) Parameters: ~ • {node} (`nvim_tree.api.Node?`) file -open.tab({node}, {opts}) *nvim_tree.api.node.open.tab()* +open.tab({node}) *nvim_tree.api.node.open.tab()* Open file in a new tab. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file - • {opts} (`table?`) optional - • {quit_on_open}? (`boolean`, default: false) Quits the tree - when opening the file. - • {focus}? (`boolean`, default: false) Keep focus in the tree - when opening the file. open.tab_drop({node}) *nvim_tree.api.node.open.tab_drop()* Switch to tab containing window with selected file if it exists. Open file @@ -2913,40 +2886,25 @@ open.tab_drop({node}) *nvim_tree.api.node.open.tab_drop()* • {node} (`nvim_tree.api.Node?`) directory or file *nvim_tree.api.node.open.toggle_group_empty()* -open.toggle_group_empty({node}, {opts}) +open.toggle_group_empty({node}) Toggle |nvim_tree.config.renderer| {group_empty} for a directory. Needs {group_empty} set. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory - • {opts} (`table?`) optional - • {quit_on_open}? (`boolean`, default: false) Quits the tree - when opening the file. - • {focus}? (`boolean`, default: false) Keep focus in the tree - when opening the file. -open.vertical({node}, {opts}) *nvim_tree.api.node.open.vertical()* +open.vertical({node}) *nvim_tree.api.node.open.vertical()* Open file in a new vertical split. Parameters: ~ • {node} (`nvim_tree.api.Node?`) file - • {opts} (`table?`) optional - • {quit_on_open}? (`boolean`, default: false) Quits the tree - when opening the file. - • {focus}? (`boolean`, default: false) Keep focus in the tree - when opening the file. *nvim_tree.api.node.open.vertical_no_picker()* -open.vertical_no_picker({node}, {opts}) +open.vertical_no_picker({node}) Open file in a new vertical split without using the window picker. Parameters: ~ • {node} (`nvim_tree.api.Node?`) file - • {opts} (`table?`) optional - • {quit_on_open}? (`boolean`, default: false) Quits the tree - when opening the file. - • {focus}? (`boolean`, default: false) Keep focus in the tree - when opening the file. run.cmd({node}) *nvim_tree.api.node.run.cmd()* Enter |cmdline| with the full path of the node and the cursor at the start From edee744d621eb7efd69793b581e4b5708a540918 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Thu, 12 Mar 2026 10:18:22 +1100 Subject: [PATCH 09/11] refactor(#3255): ensure all args passed to api --- lua/nvim-tree/api/impl/post.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/nvim-tree/api/impl/post.lua b/lua/nvim-tree/api/impl/post.lua index fb96c942a0a..f8e1a328975 100644 --- a/lua/nvim-tree/api/impl/post.lua +++ b/lua/nvim-tree/api/impl/post.lua @@ -46,7 +46,9 @@ local function en(fn) return function(n, ...) local e = require("nvim-tree.core").get_explorer() if e then - n = e and e:get_node_at_cursor() or nil + if not n then + n = e:get_node_at_cursor() or nil + end return fn(e, n, ...) end end From 144994b5e7f87e9892d94a9b4f5cee40031d8027 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Fri, 13 Mar 2026 15:58:34 +1100 Subject: [PATCH 10/11] refactor(#3255): add visual mode wrappers --- lua/nvim-tree/api/impl/post.lua | 73 +++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/lua/nvim-tree/api/impl/post.lua b/lua/nvim-tree/api/impl/post.lua index f8e1a328975..4611420bd2e 100644 --- a/lua/nvim-tree/api/impl/post.lua +++ b/lua/nvim-tree/api/impl/post.lua @@ -5,25 +5,43 @@ local M = {} ----Return a function wrapper that calls fn. ----Injects node or node at cursor as first argument. ----Passes other arguments verbatim. +---Returns the node under the cursor. +---@return Node? +local function node_at_cursor() + local e = require("nvim-tree.core").get_explorer() + return e and e:get_node_at_cursor() or nil +end + +---Returns the visually selected nodes, if we are in visual mode. +---@return Node[]? +local function visual_nodes() + local utils = require("nvim-tree.utils") + return utils.is_visual_mode() and utils.get_visual_nodes() or nil +end + +---Injects: +---- n: n or node at cursor ---@param fn fun(n?: Node, ...): any ---@return fun(n?: Node, ...): any local function _n(fn) return function(n, ...) - if not n then - local e = require("nvim-tree.core").get_explorer() - n = e and e:get_node_at_cursor() or nil - end - return fn(n, ...) + return fn(n or node_at_cursor(), ...) + end +end + +---Injects: +---- n: visual nodes or n or node at cursor +---@param fn fun(n?: Node|Node[], ...): any +---@return fun(n?: Node|Node[], ...): any +local function _v(fn) + return function(n, ...) + return fn(visual_nodes() or n or node_at_cursor(), ...) end end ----Return a function wrapper that calls fn. +---Injects: +---- e: Explorer instance ---Does nothing when no explorer instance. ----Injects an explorer instance as first arg. ----Passes other arguments verbatim. ---@param fn fun(e: Explorer, ...): any ---@return fun(e: Explorer, ...): any local function e_(fn) @@ -35,36 +53,37 @@ local function e_(fn) end end ----Return a function wrapper that calls fn. +---Injects: +---- e: Explorer instance +---- n: n or node at cursor ---Does nothing when no explorer instance. ----Injects an explorer instance as first arg. ----Injects node or node at cursor as second argument. ----Passes other arguments verbatim. ---@param fn fun(e: Explorer, n?: Node, ...): any ---@return fun(e: Explorer, n?: Node, ...): any local function en(fn) return function(n, ...) local e = require("nvim-tree.core").get_explorer() if e then - if not n then - n = e:get_node_at_cursor() or nil - end - return fn(e, n, ...) + return fn(e, n or node_at_cursor(), ...) end end end +---Injects: +---- e: Explorer instance +---- n: visual nodes or n or node at cursor +---Does nothing when no explorer instance. +---@param fn fun(e: Explorer, n?: Node|Node[], ...): any +---@return fun(e: Explorer, n?: Node|Node[], ...): any local function ev(fn) - -- TODO following rebase -end - -local function _v(fn) - -- TODO following rebase + return function(n, ...) + local e = require("nvim-tree.core").get_explorer() + if e then + return fn(e, visual_nodes() or n or node_at_cursor(), ...) + end + end end ----Return a function wrapper that calls fn. ----Passes arguments verbatim. ----Exists for formatting purposes only. +---NOP function wrapper, exists for formatting purposes only. ---@param fn fun(...): any ---@return fun(...): any local function __(fn) From e5920a0868ffe031cf10dd2f41ff8c6a36facf48 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Fri, 13 Mar 2026 16:11:45 +1100 Subject: [PATCH 11/11] refactor(#3255): define @enum nvim_tree.api.events.Event --- doc/nvim-tree-lua.txt | 2 +- lua/nvim-tree/_meta/api/events.lua | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index 52e5adf6f02..528cbf73f1b 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -2420,7 +2420,7 @@ subscribe({event_type}, {callback}) *nvim_tree.api.events.subscribe()* Register a handler for an event, see |nvim-tree-events|. Parameters: ~ - • {event_type} (`string`) |nvim_tree_events_kind| + • {event_type} (`nvim_tree.api.events.Event`) |nvim_tree_events_kind| • {callback} (`fun(payload: table?)`) diff --git a/lua/nvim-tree/_meta/api/events.lua b/lua/nvim-tree/_meta/api/events.lua index 978ae8f2aa2..a13d7f3a37c 100644 --- a/lua/nvim-tree/_meta/api/events.lua +++ b/lua/nvim-tree/_meta/api/events.lua @@ -1,6 +1,7 @@ ---@meta local nvim_tree = { api = { events = {} } } +---@enum nvim_tree.api.events.Event nvim_tree.api.events.Event = { Ready = "Ready", WillRenameNode = "WillRenameNode", @@ -22,7 +23,7 @@ nvim_tree.api.events.Event = { --- ---Register a handler for an event, see [nvim-tree-events]. --- ----@param event_type string [nvim_tree_events_kind] +---@param event_type nvim_tree.api.events.Event [nvim_tree_events_kind] ---@param callback fun(payload: table?) function nvim_tree.api.events.subscribe(event_type, callback) end