|
| 1 | +local core = require("nvim-tree.core") |
| 2 | +local view = require("nvim-tree.view") |
| 3 | +local utils = require("nvim-tree.utils") |
| 4 | +local actions = require("nvim-tree.actions") |
| 5 | +local appearance_hi_test = require("nvim-tree.appearance.hi-test") |
| 6 | +local help = require("nvim-tree.help") |
| 7 | +local keymap = require("nvim-tree.keymap") |
| 8 | + |
| 9 | +local DirectoryNode = require("nvim-tree.node.directory") |
| 10 | +local FileNode = require("nvim-tree.node.file") |
| 11 | +local FileLinkNode = require("nvim-tree.node.file-link") |
| 12 | +local RootNode = require("nvim-tree.node.root") |
| 13 | + |
| 14 | +---Hydrate all implementations barring those that were called during hydrate_pre |
| 15 | +---@param api table |
| 16 | +local function hydrate_post(api) |
| 17 | + ---Invoke a method on the singleton explorer. |
| 18 | + ---Print error when setup not called. |
| 19 | + ---@param explorer_method string explorer method name |
| 20 | + ---@return fun(...): any |
| 21 | + local function wrap_explorer(explorer_method) |
| 22 | + return function(...) |
| 23 | + local explorer = core.get_explorer() |
| 24 | + if explorer then |
| 25 | + return explorer[explorer_method](explorer, ...) |
| 26 | + end |
| 27 | + end |
| 28 | + end |
| 29 | + |
| 30 | + ---Inject the node as the first argument if present otherwise do nothing. |
| 31 | + ---@param fn fun(node: Node, ...): any |
| 32 | + ---@return fun(node: Node?, ...): any |
| 33 | + local function wrap_node(fn) |
| 34 | + return function(node, ...) |
| 35 | + node = node or wrap_explorer("get_node_at_cursor")() |
| 36 | + if node then |
| 37 | + return fn(node, ...) |
| 38 | + end |
| 39 | + end |
| 40 | + end |
| 41 | + |
| 42 | + ---Inject the node or nil as the first argument if absent. |
| 43 | + ---@param fn fun(node: Node?, ...): any |
| 44 | + ---@return fun(node: Node?, ...): any |
| 45 | + local function wrap_node_or_nil(fn) |
| 46 | + return function(node, ...) |
| 47 | + node = node or wrap_explorer("get_node_at_cursor")() |
| 48 | + return fn(node, ...) |
| 49 | + end |
| 50 | + end |
| 51 | + |
| 52 | + ---Invoke a member's method on the singleton explorer. |
| 53 | + ---Print error when setup not called. |
| 54 | + ---@param explorer_member string explorer member name |
| 55 | + ---@param member_method string method name to invoke on member |
| 56 | + ---@param ... any passed to method |
| 57 | + ---@return fun(...): any |
| 58 | + local function wrap_explorer_member_args(explorer_member, member_method, ...) |
| 59 | + local method_args = ... |
| 60 | + return function(...) |
| 61 | + local explorer = core.get_explorer() |
| 62 | + if explorer then |
| 63 | + return explorer[explorer_member][member_method](explorer[explorer_member], method_args, ...) |
| 64 | + end |
| 65 | + end |
| 66 | + end |
| 67 | + |
| 68 | + ---Invoke a member's method on the singleton explorer. |
| 69 | + ---Print error when setup not called. |
| 70 | + ---@param explorer_member string explorer member name |
| 71 | + ---@param member_method string method name to invoke on member |
| 72 | + ---@return fun(...): any |
| 73 | + local function wrap_explorer_member(explorer_member, member_method) |
| 74 | + return function(...) |
| 75 | + local explorer = core.get_explorer() |
| 76 | + if explorer then |
| 77 | + return explorer[explorer_member][member_method](explorer[explorer_member], ...) |
| 78 | + end |
| 79 | + end |
| 80 | + end |
| 81 | + |
| 82 | + api.tree.open = actions.tree.open.fn |
| 83 | + api.tree.focus = api.tree.open |
| 84 | + |
| 85 | + api.tree.toggle = actions.tree.toggle.fn |
| 86 | + api.tree.close = view.close |
| 87 | + api.tree.close_in_this_tab = view.close_this_tab_only |
| 88 | + api.tree.close_in_all_tabs = view.close_all_tabs |
| 89 | + api.tree.reload = wrap_explorer("reload_explorer") |
| 90 | + |
| 91 | + api.tree.resize = actions.tree.resize.fn |
| 92 | + |
| 93 | + api.tree.change_root = require("nvim-tree").change_dir |
| 94 | + |
| 95 | + api.tree.change_root_to_node = wrap_node(function(node) |
| 96 | + if node.name == ".." or node:is(RootNode) then |
| 97 | + actions.root.change_dir.fn("..") |
| 98 | + return |
| 99 | + end |
| 100 | + |
| 101 | + if node:is(FileNode) and node.parent ~= nil then |
| 102 | + actions.root.change_dir.fn(node.parent:last_group_node().absolute_path) |
| 103 | + return |
| 104 | + end |
| 105 | + |
| 106 | + if node:is(DirectoryNode) then |
| 107 | + actions.root.change_dir.fn(node:last_group_node().absolute_path) |
| 108 | + return |
| 109 | + end |
| 110 | + end) |
| 111 | + |
| 112 | + api.tree.change_root_to_parent = wrap_node(wrap_explorer("dir_up")) |
| 113 | + api.tree.get_node_under_cursor = wrap_explorer("get_node_at_cursor") |
| 114 | + api.tree.get_nodes = wrap_explorer("get_nodes") |
| 115 | + |
| 116 | + api.tree.find_file = actions.tree.find_file.fn |
| 117 | + api.tree.search_node = actions.finders.search_node.fn |
| 118 | + |
| 119 | + api.tree.collapse_all = actions.tree.modifiers.collapse.all |
| 120 | + |
| 121 | + api.tree.expand_all = wrap_node(actions.tree.modifiers.expand.all) |
| 122 | + api.tree.toggle_help = help.toggle |
| 123 | + api.tree.is_tree_buf = utils.is_nvim_tree_buf |
| 124 | + |
| 125 | + api.tree.is_visible = view.is_visible |
| 126 | + |
| 127 | + api.tree.winid = view.winid |
| 128 | + |
| 129 | + api.fs.create = wrap_node_or_nil(actions.fs.create_file.fn) |
| 130 | + api.fs.remove = wrap_node(actions.fs.remove_file.fn) |
| 131 | + api.fs.trash = wrap_node(actions.fs.trash.fn) |
| 132 | + api.fs.rename_node = wrap_node(actions.fs.rename_file.fn(":t")) |
| 133 | + api.fs.rename = wrap_node(actions.fs.rename_file.fn(":t")) |
| 134 | + api.fs.rename_sub = wrap_node(actions.fs.rename_file.fn(":p:h")) |
| 135 | + api.fs.rename_basename = wrap_node(actions.fs.rename_file.fn(":t:r")) |
| 136 | + api.fs.rename_full = wrap_node(actions.fs.rename_file.fn(":p")) |
| 137 | + api.fs.cut = wrap_node(wrap_explorer_member("clipboard", "cut")) |
| 138 | + api.fs.paste = wrap_node(wrap_explorer_member("clipboard", "paste")) |
| 139 | + api.fs.clear_clipboard = wrap_explorer_member("clipboard", "clear_clipboard") |
| 140 | + api.fs.print_clipboard = wrap_explorer_member("clipboard", "print_clipboard") |
| 141 | + api.fs.copy.node = wrap_node(wrap_explorer_member("clipboard", "copy")) |
| 142 | + api.fs.copy.absolute_path = wrap_node(wrap_explorer_member("clipboard", "copy_absolute_path")) |
| 143 | + api.fs.copy.filename = wrap_node(wrap_explorer_member("clipboard", "copy_filename")) |
| 144 | + api.fs.copy.basename = wrap_node(wrap_explorer_member("clipboard", "copy_basename")) |
| 145 | + api.fs.copy.relative_path = wrap_node(wrap_explorer_member("clipboard", "copy_path")) |
| 146 | + --- |
| 147 | + ---@class NodeEditOpts |
| 148 | + ---@field quit_on_open boolean|nil default false |
| 149 | + ---@field focus boolean|nil default true |
| 150 | + |
| 151 | + ---@param mode string |
| 152 | + ---@param node Node |
| 153 | + ---@param edit_opts NodeEditOpts? |
| 154 | + local function edit(mode, node, edit_opts) |
| 155 | + local file_link = node:as(FileLinkNode) |
| 156 | + local path = file_link and file_link.link_to or node.absolute_path |
| 157 | + local cur_tabpage = vim.api.nvim_get_current_tabpage() |
| 158 | + |
| 159 | + actions.node.open_file.fn(mode, path) |
| 160 | + |
| 161 | + edit_opts = edit_opts or {} |
| 162 | + |
| 163 | + local mode_unsupported_quit_on_open = mode == "drop" or mode == "tab_drop" or mode == "edit_in_place" |
| 164 | + if not mode_unsupported_quit_on_open and edit_opts.quit_on_open then |
| 165 | + view.close(cur_tabpage) |
| 166 | + end |
| 167 | + |
| 168 | + local mode_unsupported_focus = mode == "drop" or mode == "tab_drop" or mode == "edit_in_place" |
| 169 | + local focus = edit_opts.focus == nil or edit_opts.focus == true |
| 170 | + if not mode_unsupported_focus and not focus then |
| 171 | + -- if mode == "tabnew" a new tab will be opened and we need to focus back to the previous tab |
| 172 | + if mode == "tabnew" then |
| 173 | + vim.cmd(":tabprev") |
| 174 | + end |
| 175 | + view.focus() |
| 176 | + end |
| 177 | + end |
| 178 | + |
| 179 | + ---@param mode string |
| 180 | + ---@param toggle_group boolean? |
| 181 | + ---@return fun(node: Node, edit_opts: NodeEditOpts?) |
| 182 | + local function open_or_expand_or_dir_up(mode, toggle_group) |
| 183 | + ---@param node Node |
| 184 | + ---@param edit_opts NodeEditOpts? |
| 185 | + return function(node, edit_opts) |
| 186 | + local root = node:as(RootNode) |
| 187 | + local dir = node:as(DirectoryNode) |
| 188 | + |
| 189 | + if root or node.name == ".." then |
| 190 | + actions.root.change_dir.fn("..") |
| 191 | + elseif dir then |
| 192 | + dir:expand_or_collapse(toggle_group) |
| 193 | + elseif not toggle_group then |
| 194 | + edit(mode, node, edit_opts) |
| 195 | + end |
| 196 | + end |
| 197 | + end |
| 198 | + |
| 199 | + api.node.open.edit = wrap_node(open_or_expand_or_dir_up("edit")) |
| 200 | + api.node.open.drop = wrap_node(open_or_expand_or_dir_up("drop")) |
| 201 | + api.node.open.tab_drop = wrap_node(open_or_expand_or_dir_up("tab_drop")) |
| 202 | + api.node.open.replace_tree_buffer = wrap_node(open_or_expand_or_dir_up("edit_in_place")) |
| 203 | + api.node.open.no_window_picker = wrap_node(open_or_expand_or_dir_up("edit_no_picker")) |
| 204 | + api.node.open.vertical = wrap_node(open_or_expand_or_dir_up("vsplit")) |
| 205 | + api.node.open.vertical_no_picker = wrap_node(open_or_expand_or_dir_up("vsplit_no_picker")) |
| 206 | + api.node.open.horizontal = wrap_node(open_or_expand_or_dir_up("split")) |
| 207 | + api.node.open.horizontal_no_picker = wrap_node(open_or_expand_or_dir_up("split_no_picker")) |
| 208 | + api.node.open.tab = wrap_node(open_or_expand_or_dir_up("tabnew")) |
| 209 | + api.node.open.toggle_group_empty = wrap_node(open_or_expand_or_dir_up("toggle_group_empty", true)) |
| 210 | + api.node.open.preview = wrap_node(open_or_expand_or_dir_up("preview")) |
| 211 | + api.node.open.preview_no_picker = wrap_node(open_or_expand_or_dir_up("preview_no_picker")) |
| 212 | + |
| 213 | + api.node.show_info_popup = wrap_node(actions.node.file_popup.toggle_file_info) |
| 214 | + api.node.run.cmd = wrap_node(actions.node.run_command.run_file_command) |
| 215 | + api.node.run.system = wrap_node(actions.node.system_open.fn) |
| 216 | + |
| 217 | + api.node.navigate.sibling.next = wrap_node(actions.moves.sibling.fn("next")) |
| 218 | + api.node.navigate.sibling.prev = wrap_node(actions.moves.sibling.fn("prev")) |
| 219 | + api.node.navigate.sibling.first = wrap_node(actions.moves.sibling.fn("first")) |
| 220 | + api.node.navigate.sibling.last = wrap_node(actions.moves.sibling.fn("last")) |
| 221 | + api.node.navigate.parent = wrap_node(actions.moves.parent.fn(false)) |
| 222 | + api.node.navigate.parent_close = wrap_node(actions.moves.parent.fn(true)) |
| 223 | + api.node.navigate.git.next = wrap_node(actions.moves.item.fn({ where = "next", what = "git" })) |
| 224 | + api.node.navigate.git.next_skip_gitignored = wrap_node(actions.moves.item.fn({ where = "next", what = "git", skip_gitignored = true })) |
| 225 | + api.node.navigate.git.next_recursive = wrap_node(actions.moves.item.fn({ where = "next", what = "git", recurse = true })) |
| 226 | + api.node.navigate.git.prev = wrap_node(actions.moves.item.fn({ where = "prev", what = "git" })) |
| 227 | + api.node.navigate.git.prev_skip_gitignored = wrap_node(actions.moves.item.fn({ where = "prev", what = "git", skip_gitignored = true })) |
| 228 | + api.node.navigate.git.prev_recursive = wrap_node(actions.moves.item.fn({ where = "prev", what = "git", recurse = true })) |
| 229 | + api.node.navigate.diagnostics.next = wrap_node(actions.moves.item.fn({ where = "next", what = "diag" })) |
| 230 | + api.node.navigate.diagnostics.next_recursive = wrap_node(actions.moves.item.fn({ where = "next", what = "diag", recurse = true })) |
| 231 | + api.node.navigate.diagnostics.prev = wrap_node(actions.moves.item.fn({ where = "prev", what = "diag" })) |
| 232 | + api.node.navigate.diagnostics.prev_recursive = wrap_node(actions.moves.item.fn({ where = "prev", what = "diag", recurse = true })) |
| 233 | + api.node.navigate.opened.next = wrap_node(actions.moves.item.fn({ where = "next", what = "opened" })) |
| 234 | + api.node.navigate.opened.prev = wrap_node(actions.moves.item.fn({ where = "prev", what = "opened" })) |
| 235 | + |
| 236 | + api.node.expand = wrap_node(actions.tree.modifiers.expand.node) |
| 237 | + api.node.collapse = wrap_node(actions.tree.modifiers.collapse.node) |
| 238 | + |
| 239 | + api.node.buffer.delete = wrap_node(function(node, opts) |
| 240 | + actions.node.buffer.delete(node, opts) |
| 241 | + end) |
| 242 | + api.node.buffer.wipe = wrap_node(function(node, opts) |
| 243 | + actions.node.buffer.wipe(node, opts) |
| 244 | + end) |
| 245 | + |
| 246 | + api.tree.reload_git = wrap_explorer("reload_git") |
| 247 | + |
| 248 | + api.filter.live.start = wrap_explorer_member("live_filter", "start_filtering") |
| 249 | + api.filter.live.clear = wrap_explorer_member("live_filter", "clear_filter") |
| 250 | + api.filter.toggle = wrap_explorer_member("filters", "toggle") |
| 251 | + api.filter.git.ignored.toggle = wrap_explorer_member_args("filters", "toggle", "git_ignored") |
| 252 | + api.filter.git.clean.toggle = wrap_explorer_member_args("filters", "toggle", "git_clean") |
| 253 | + api.filter.no_buffer.toggle = wrap_explorer_member_args("filters", "toggle", "no_buffer") |
| 254 | + api.filter.custom.toggle = wrap_explorer_member_args("filters", "toggle", "custom") |
| 255 | + api.filter.dotfiles.toggle = wrap_explorer_member_args("filters", "toggle", "dotfiles") |
| 256 | + api.filter.no_bookmark.toggle = wrap_explorer_member_args("filters", "toggle", "no_bookmark") |
| 257 | + |
| 258 | + api.marks.get = wrap_node(wrap_explorer_member("marks", "get")) |
| 259 | + api.marks.list = wrap_explorer_member("marks", "list") |
| 260 | + api.marks.toggle = wrap_node(wrap_explorer_member("marks", "toggle")) |
| 261 | + api.marks.clear = wrap_explorer_member("marks", "clear") |
| 262 | + api.marks.bulk.delete = wrap_explorer_member("marks", "bulk_delete") |
| 263 | + api.marks.bulk.trash = wrap_explorer_member("marks", "bulk_trash") |
| 264 | + api.marks.bulk.move = wrap_explorer_member("marks", "bulk_move") |
| 265 | + api.marks.navigate.next = wrap_explorer_member("marks", "navigate_next") |
| 266 | + api.marks.navigate.prev = wrap_explorer_member("marks", "navigate_prev") |
| 267 | + api.marks.navigate.select = wrap_explorer_member("marks", "navigate_select") |
| 268 | + |
| 269 | + api.map.get_keymap = keymap.get_keymap |
| 270 | + api.map.get_keymap_default = keymap.get_keymap_default |
| 271 | + |
| 272 | + api.health.hi_test = appearance_hi_test |
| 273 | + |
| 274 | + api.commands.get = require("nvim-tree.commands").get |
| 275 | +end |
| 276 | + |
| 277 | +---Hydrates all API functions with concrete implementations. |
| 278 | +---All "nvim-tree setup not called" error functions will be replaced. |
| 279 | +--- |
| 280 | +---Call this after nvim-tree setup |
| 281 | +--- |
| 282 | +---This is expensive as there are many cascading requires and is avoided |
| 283 | +---until after setup has been called, so that the user may require API cheaply. |
| 284 | +---@param api table |
| 285 | +return function(api) |
| 286 | + -- All concrete implementations |
| 287 | + hydrate_post(api) |
| 288 | + |
| 289 | + -- (Re)hydrate any legacy by mapping to function set above |
| 290 | + require("nvim-tree.legacy").map_api(api) |
| 291 | +end |
0 commit comments