|
| 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 events = require("nvim-tree.events") |
| 7 | +local help = require("nvim-tree.help") |
| 8 | +local keymap = require("nvim-tree.keymap") |
| 9 | +local notify = require("nvim-tree.notify") |
| 10 | + |
| 11 | +local DirectoryNode = require("nvim-tree.node.directory") |
| 12 | +local FileNode = require("nvim-tree.node.file") |
| 13 | +local FileLinkNode = require("nvim-tree.node.file-link") |
| 14 | +local RootNode = require("nvim-tree.node.root") |
| 15 | +local UserDecorator = require("nvim-tree.renderer.decorator.user") |
| 16 | + |
| 17 | +local Api = { |
| 18 | + tree = {}, |
| 19 | + node = { |
| 20 | + navigate = { |
| 21 | + sibling = {}, |
| 22 | + git = {}, |
| 23 | + diagnostics = {}, |
| 24 | + opened = {}, |
| 25 | + }, |
| 26 | + run = {}, |
| 27 | + open = {}, |
| 28 | + buffer = {}, |
| 29 | + }, |
| 30 | + events = {}, |
| 31 | + marks = { |
| 32 | + bulk = {}, |
| 33 | + navigate = {}, |
| 34 | + }, |
| 35 | + fs = { |
| 36 | + copy = {}, |
| 37 | + }, |
| 38 | + git = {}, |
| 39 | + live_filter = {}, |
| 40 | + config = { |
| 41 | + mappings = {}, |
| 42 | + }, |
| 43 | + commands = {}, |
| 44 | + diagnostics = {}, |
| 45 | + decorator = {}, |
| 46 | +} |
| 47 | + |
| 48 | +---Print error when setup not called. |
| 49 | +---@param fn fun(...): any |
| 50 | +---@return fun(...): any |
| 51 | +local function wrap(fn) |
| 52 | + return function(...) |
| 53 | + if vim.g.NvimTreeSetup == 1 then |
| 54 | + return fn(...) |
| 55 | + else |
| 56 | + notify.error("nvim-tree setup not called") |
| 57 | + end |
| 58 | + end |
| 59 | +end |
| 60 | + |
| 61 | +---Invoke a method on the singleton explorer. |
| 62 | +---Print error when setup not called. |
| 63 | +---@param explorer_method string explorer method name |
| 64 | +---@return fun(...): any |
| 65 | +local function wrap_explorer(explorer_method) |
| 66 | + return wrap(function(...) |
| 67 | + local explorer = core.get_explorer() |
| 68 | + if explorer then |
| 69 | + return explorer[explorer_method](explorer, ...) |
| 70 | + end |
| 71 | + end) |
| 72 | +end |
| 73 | + |
| 74 | +---Inject the node as the first argument if present otherwise do nothing. |
| 75 | +---@param fn fun(node: Node, ...): any |
| 76 | +---@return fun(node: Node?, ...): any |
| 77 | +local function wrap_node(fn) |
| 78 | + return function(node, ...) |
| 79 | + node = node or wrap_explorer("get_node_at_cursor")() |
| 80 | + if node then |
| 81 | + return fn(node, ...) |
| 82 | + end |
| 83 | + end |
| 84 | +end |
| 85 | + |
| 86 | +---Inject the node or nil as the first argument if absent. |
| 87 | +---@param fn fun(node: Node?, ...): any |
| 88 | +---@return fun(node: Node?, ...): any |
| 89 | +local function wrap_node_or_nil(fn) |
| 90 | + return function(node, ...) |
| 91 | + node = node or wrap_explorer("get_node_at_cursor")() |
| 92 | + return fn(node, ...) |
| 93 | + end |
| 94 | +end |
| 95 | + |
| 96 | +---Invoke a member's method on the singleton explorer. |
| 97 | +---Print error when setup not called. |
| 98 | +---@param explorer_member string explorer member name |
| 99 | +---@param member_method string method name to invoke on member |
| 100 | +---@param ... any passed to method |
| 101 | +---@return fun(...): any |
| 102 | +local function wrap_explorer_member_args(explorer_member, member_method, ...) |
| 103 | + local method_args = ... |
| 104 | + return wrap(function(...) |
| 105 | + local explorer = core.get_explorer() |
| 106 | + if explorer then |
| 107 | + return explorer[explorer_member][member_method](explorer[explorer_member], method_args, ...) |
| 108 | + end |
| 109 | + end) |
| 110 | +end |
| 111 | + |
| 112 | +---Invoke a member's method on the singleton explorer. |
| 113 | +---Print error when setup not called. |
| 114 | +---@param explorer_member string explorer member name |
| 115 | +---@param member_method string method name to invoke on member |
| 116 | +---@return fun(...): any |
| 117 | +local function wrap_explorer_member(explorer_member, member_method) |
| 118 | + return wrap(function(...) |
| 119 | + local explorer = core.get_explorer() |
| 120 | + if explorer then |
| 121 | + return explorer[explorer_member][member_method](explorer[explorer_member], ...) |
| 122 | + end |
| 123 | + end) |
| 124 | +end |
| 125 | + |
| 126 | +---@class ApiTreeOpenOpts |
| 127 | +---@field path string|nil path |
| 128 | +---@field current_window boolean|nil default false |
| 129 | +---@field winid number|nil |
| 130 | +---@field find_file boolean|nil default false |
| 131 | +---@field update_root boolean|nil default false |
| 132 | + |
| 133 | +Api.tree.open = wrap(actions.tree.open.fn) |
| 134 | +Api.tree.focus = Api.tree.open |
| 135 | + |
| 136 | +---@class ApiTreeToggleOpts |
| 137 | +---@field path string|nil |
| 138 | +---@field current_window boolean|nil default false |
| 139 | +---@field winid number|nil |
| 140 | +---@field find_file boolean|nil default false |
| 141 | +---@field update_root boolean|nil default false |
| 142 | +---@field focus boolean|nil default true |
| 143 | + |
| 144 | +Api.tree.toggle = wrap(actions.tree.toggle.fn) |
| 145 | +Api.tree.close = wrap(view.close) |
| 146 | +Api.tree.close_in_this_tab = wrap(view.close_this_tab_only) |
| 147 | +Api.tree.close_in_all_tabs = wrap(view.close_all_tabs) |
| 148 | +Api.tree.reload = wrap_explorer("reload_explorer") |
| 149 | + |
| 150 | +---@class ApiTreeResizeOpts |
| 151 | +---@field width string|function|number|table|nil |
| 152 | +---@field absolute number|nil |
| 153 | +---@field relative number|nil |
| 154 | + |
| 155 | +Api.tree.resize = wrap(actions.tree.resize.fn) |
| 156 | + |
| 157 | +Api.tree.change_root = wrap(function(...) |
| 158 | + require("nvim-tree").change_dir(...) |
| 159 | +end) |
| 160 | + |
| 161 | +Api.tree.change_root_to_node = wrap_node(function(node) |
| 162 | + if node.name == ".." or node:is(RootNode) then |
| 163 | + actions.root.change_dir.fn("..") |
| 164 | + return |
| 165 | + end |
| 166 | + |
| 167 | + if node:is(FileNode) and node.parent ~= nil then |
| 168 | + actions.root.change_dir.fn(node.parent:last_group_node().absolute_path) |
| 169 | + return |
| 170 | + end |
| 171 | + |
| 172 | + if node:is(DirectoryNode) then |
| 173 | + actions.root.change_dir.fn(node:last_group_node().absolute_path) |
| 174 | + return |
| 175 | + end |
| 176 | +end) |
| 177 | + |
| 178 | +Api.tree.change_root_to_parent = wrap_node(wrap_explorer("dir_up")) |
| 179 | +Api.tree.get_node_under_cursor = wrap_explorer("get_node_at_cursor") |
| 180 | +Api.tree.get_nodes = wrap_explorer("get_nodes") |
| 181 | + |
| 182 | +---@class ApiTreeFindFileOpts |
| 183 | +---@field buf string|number|nil |
| 184 | +---@field open boolean|nil default false |
| 185 | +---@field current_window boolean|nil default false |
| 186 | +---@field winid number|nil |
| 187 | +---@field update_root boolean|nil default false |
| 188 | +---@field focus boolean|nil default false |
| 189 | + |
| 190 | +Api.tree.find_file = wrap(actions.tree.find_file.fn) |
| 191 | +Api.tree.search_node = wrap(actions.finders.search_node.fn) |
| 192 | + |
| 193 | +---@class ApiCollapseOpts |
| 194 | +---@field keep_buffers boolean|nil default false |
| 195 | + |
| 196 | +Api.tree.collapse_all = wrap(actions.tree.modifiers.collapse.all) |
| 197 | + |
| 198 | +---@class ApiTreeExpandOpts |
| 199 | +---@field expand_until (fun(expansion_count: integer, node: Node): boolean)|nil |
| 200 | + |
| 201 | +Api.tree.expand_all = wrap_node(actions.tree.modifiers.expand.all) |
| 202 | +Api.tree.toggle_enable_filters = wrap_explorer_member("filters", "toggle") |
| 203 | +Api.tree.toggle_gitignore_filter = wrap_explorer_member_args("filters", "toggle", "git_ignored") |
| 204 | +Api.tree.toggle_git_clean_filter = wrap_explorer_member_args("filters", "toggle", "git_clean") |
| 205 | +Api.tree.toggle_no_buffer_filter = wrap_explorer_member_args("filters", "toggle", "no_buffer") |
| 206 | +Api.tree.toggle_custom_filter = wrap_explorer_member_args("filters", "toggle", "custom") |
| 207 | +Api.tree.toggle_hidden_filter = wrap_explorer_member_args("filters", "toggle", "dotfiles") |
| 208 | +Api.tree.toggle_no_bookmark_filter = wrap_explorer_member_args("filters", "toggle", "no_bookmark") |
| 209 | +Api.tree.toggle_help = wrap(help.toggle) |
| 210 | +Api.tree.is_tree_buf = wrap(utils.is_nvim_tree_buf) |
| 211 | + |
| 212 | +---@class ApiTreeIsVisibleOpts |
| 213 | +---@field tabpage number|nil |
| 214 | +---@field any_tabpage boolean|nil default false |
| 215 | + |
| 216 | +Api.tree.is_visible = wrap(view.is_visible) |
| 217 | + |
| 218 | +---@class ApiTreeWinIdOpts |
| 219 | +---@field tabpage number|nil default nil |
| 220 | + |
| 221 | +Api.tree.winid = wrap(view.winid) |
| 222 | + |
| 223 | +Api.fs.create = wrap_node_or_nil(actions.fs.create_file.fn) |
| 224 | +Api.fs.remove = wrap_node(actions.fs.remove_file.fn) |
| 225 | +Api.fs.trash = wrap_node(actions.fs.trash.fn) |
| 226 | +Api.fs.rename_node = wrap_node(actions.fs.rename_file.fn(":t")) |
| 227 | +Api.fs.rename = wrap_node(actions.fs.rename_file.fn(":t")) |
| 228 | +Api.fs.rename_sub = wrap_node(actions.fs.rename_file.fn(":p:h")) |
| 229 | +Api.fs.rename_basename = wrap_node(actions.fs.rename_file.fn(":t:r")) |
| 230 | +Api.fs.rename_full = wrap_node(actions.fs.rename_file.fn(":p")) |
| 231 | +Api.fs.cut = wrap_node(wrap_explorer_member("clipboard", "cut")) |
| 232 | +Api.fs.paste = wrap_node(wrap_explorer_member("clipboard", "paste")) |
| 233 | +Api.fs.clear_clipboard = wrap_explorer_member("clipboard", "clear_clipboard") |
| 234 | +Api.fs.print_clipboard = wrap_explorer_member("clipboard", "print_clipboard") |
| 235 | +Api.fs.copy.node = wrap_node(wrap_explorer_member("clipboard", "copy")) |
| 236 | +Api.fs.copy.absolute_path = wrap_node(wrap_explorer_member("clipboard", "copy_absolute_path")) |
| 237 | +Api.fs.copy.filename = wrap_node(wrap_explorer_member("clipboard", "copy_filename")) |
| 238 | +Api.fs.copy.basename = wrap_node(wrap_explorer_member("clipboard", "copy_basename")) |
| 239 | +Api.fs.copy.relative_path = wrap_node(wrap_explorer_member("clipboard", "copy_path")) |
| 240 | +--- |
| 241 | +---@class NodeEditOpts |
| 242 | +---@field quit_on_open boolean|nil default false |
| 243 | +---@field focus boolean|nil default true |
| 244 | + |
| 245 | +---@param mode string |
| 246 | +---@param node Node |
| 247 | +---@param edit_opts NodeEditOpts? |
| 248 | +local function edit(mode, node, edit_opts) |
| 249 | + local file_link = node:as(FileLinkNode) |
| 250 | + local path = file_link and file_link.link_to or node.absolute_path |
| 251 | + local cur_tabpage = vim.api.nvim_get_current_tabpage() |
| 252 | + |
| 253 | + actions.node.open_file.fn(mode, path) |
| 254 | + |
| 255 | + edit_opts = edit_opts or {} |
| 256 | + |
| 257 | + local mode_unsupported_quit_on_open = mode == "drop" or mode == "tab_drop" or mode == "edit_in_place" |
| 258 | + if not mode_unsupported_quit_on_open and edit_opts.quit_on_open then |
| 259 | + view.close(cur_tabpage) |
| 260 | + end |
| 261 | + |
| 262 | + local mode_unsupported_focus = mode == "drop" or mode == "tab_drop" or mode == "edit_in_place" |
| 263 | + local focus = edit_opts.focus == nil or edit_opts.focus == true |
| 264 | + if not mode_unsupported_focus and not focus then |
| 265 | + -- if mode == "tabnew" a new tab will be opened and we need to focus back to the previous tab |
| 266 | + if mode == "tabnew" then |
| 267 | + vim.cmd(":tabprev") |
| 268 | + end |
| 269 | + view.focus() |
| 270 | + end |
| 271 | +end |
| 272 | + |
| 273 | +---@param mode string |
| 274 | +---@param toggle_group boolean? |
| 275 | +---@return fun(node: Node, edit_opts: NodeEditOpts?) |
| 276 | +local function open_or_expand_or_dir_up(mode, toggle_group) |
| 277 | + ---@param node Node |
| 278 | + ---@param edit_opts NodeEditOpts? |
| 279 | + return function(node, edit_opts) |
| 280 | + local root = node:as(RootNode) |
| 281 | + local dir = node:as(DirectoryNode) |
| 282 | + |
| 283 | + if root or node.name == ".." then |
| 284 | + actions.root.change_dir.fn("..") |
| 285 | + elseif dir then |
| 286 | + dir:expand_or_collapse(toggle_group) |
| 287 | + elseif not toggle_group then |
| 288 | + edit(mode, node, edit_opts) |
| 289 | + end |
| 290 | + end |
| 291 | +end |
| 292 | + |
| 293 | +Api.node.open.edit = wrap_node(open_or_expand_or_dir_up("edit")) |
| 294 | +Api.node.open.drop = wrap_node(open_or_expand_or_dir_up("drop")) |
| 295 | +Api.node.open.tab_drop = wrap_node(open_or_expand_or_dir_up("tab_drop")) |
| 296 | +Api.node.open.replace_tree_buffer = wrap_node(open_or_expand_or_dir_up("edit_in_place")) |
| 297 | +Api.node.open.no_window_picker = wrap_node(open_or_expand_or_dir_up("edit_no_picker")) |
| 298 | +Api.node.open.vertical = wrap_node(open_or_expand_or_dir_up("vsplit")) |
| 299 | +Api.node.open.vertical_no_picker = wrap_node(open_or_expand_or_dir_up("vsplit_no_picker")) |
| 300 | +Api.node.open.horizontal = wrap_node(open_or_expand_or_dir_up("split")) |
| 301 | +Api.node.open.horizontal_no_picker = wrap_node(open_or_expand_or_dir_up("split_no_picker")) |
| 302 | +Api.node.open.tab = wrap_node(open_or_expand_or_dir_up("tabnew")) |
| 303 | +Api.node.open.toggle_group_empty = wrap_node(open_or_expand_or_dir_up("toggle_group_empty", true)) |
| 304 | +Api.node.open.preview = wrap_node(open_or_expand_or_dir_up("preview")) |
| 305 | +Api.node.open.preview_no_picker = wrap_node(open_or_expand_or_dir_up("preview_no_picker")) |
| 306 | + |
| 307 | +Api.node.show_info_popup = wrap_node(actions.node.file_popup.toggle_file_info) |
| 308 | +Api.node.run.cmd = wrap_node(actions.node.run_command.run_file_command) |
| 309 | +Api.node.run.system = wrap_node(actions.node.system_open.fn) |
| 310 | + |
| 311 | +Api.node.navigate.sibling.next = wrap_node(actions.moves.sibling.fn("next")) |
| 312 | +Api.node.navigate.sibling.prev = wrap_node(actions.moves.sibling.fn("prev")) |
| 313 | +Api.node.navigate.sibling.first = wrap_node(actions.moves.sibling.fn("first")) |
| 314 | +Api.node.navigate.sibling.last = wrap_node(actions.moves.sibling.fn("last")) |
| 315 | +Api.node.navigate.parent = wrap_node(actions.moves.parent.fn(false)) |
| 316 | +Api.node.navigate.parent_close = wrap_node(actions.moves.parent.fn(true)) |
| 317 | +Api.node.navigate.git.next = wrap_node(actions.moves.item.fn({ where = "next", what = "git" })) |
| 318 | +Api.node.navigate.git.next_skip_gitignored = wrap_node(actions.moves.item.fn({ where = "next", what = "git", skip_gitignored = true })) |
| 319 | +Api.node.navigate.git.next_recursive = wrap_node(actions.moves.item.fn({ where = "next", what = "git", recurse = true })) |
| 320 | +Api.node.navigate.git.prev = wrap_node(actions.moves.item.fn({ where = "prev", what = "git" })) |
| 321 | +Api.node.navigate.git.prev_skip_gitignored = wrap_node(actions.moves.item.fn({ where = "prev", what = "git", skip_gitignored = true })) |
| 322 | +Api.node.navigate.git.prev_recursive = wrap_node(actions.moves.item.fn({ where = "prev", what = "git", recurse = true })) |
| 323 | +Api.node.navigate.diagnostics.next = wrap_node(actions.moves.item.fn({ where = "next", what = "diag" })) |
| 324 | +Api.node.navigate.diagnostics.next_recursive = wrap_node(actions.moves.item.fn({ where = "next", what = "diag", recurse = true })) |
| 325 | +Api.node.navigate.diagnostics.prev = wrap_node(actions.moves.item.fn({ where = "prev", what = "diag" })) |
| 326 | +Api.node.navigate.diagnostics.prev_recursive = wrap_node(actions.moves.item.fn({ where = "prev", what = "diag", recurse = true })) |
| 327 | +Api.node.navigate.opened.next = wrap_node(actions.moves.item.fn({ where = "next", what = "opened" })) |
| 328 | +Api.node.navigate.opened.prev = wrap_node(actions.moves.item.fn({ where = "prev", what = "opened" })) |
| 329 | + |
| 330 | +Api.node.expand = wrap_node(actions.tree.modifiers.expand.node) |
| 331 | +Api.node.collapse = wrap_node(actions.tree.modifiers.collapse.node) |
| 332 | + |
| 333 | +---@class ApiNodeDeleteWipeBufferOpts |
| 334 | +---@field force boolean|nil default false |
| 335 | + |
| 336 | +Api.node.buffer.delete = wrap_node(function(node, opts) |
| 337 | + actions.node.buffer.delete(node, opts) |
| 338 | +end) |
| 339 | +Api.node.buffer.wipe = wrap_node(function(node, opts) |
| 340 | + actions.node.buffer.wipe(node, opts) |
| 341 | +end) |
| 342 | + |
| 343 | +Api.git.reload = wrap_explorer("reload_git") |
| 344 | + |
| 345 | +Api.events.subscribe = events.subscribe |
| 346 | +Api.events.Event = events.Event |
| 347 | + |
| 348 | +Api.live_filter.start = wrap_explorer_member("live_filter", "start_filtering") |
| 349 | +Api.live_filter.clear = wrap_explorer_member("live_filter", "clear_filter") |
| 350 | + |
| 351 | +Api.marks.get = wrap_node(wrap_explorer_member("marks", "get")) |
| 352 | +Api.marks.list = wrap_explorer_member("marks", "list") |
| 353 | +Api.marks.toggle = wrap_node(wrap_explorer_member("marks", "toggle")) |
| 354 | +Api.marks.clear = wrap_explorer_member("marks", "clear") |
| 355 | +Api.marks.bulk.delete = wrap_explorer_member("marks", "bulk_delete") |
| 356 | +Api.marks.bulk.trash = wrap_explorer_member("marks", "bulk_trash") |
| 357 | +Api.marks.bulk.move = wrap_explorer_member("marks", "bulk_move") |
| 358 | +Api.marks.navigate.next = wrap_explorer_member("marks", "navigate_next") |
| 359 | +Api.marks.navigate.prev = wrap_explorer_member("marks", "navigate_prev") |
| 360 | +Api.marks.navigate.select = wrap_explorer_member("marks", "navigate_select") |
| 361 | + |
| 362 | +Api.config.mappings.get_keymap = wrap(keymap.get_keymap) |
| 363 | +Api.config.mappings.get_keymap_default = wrap(keymap.get_keymap_default) |
| 364 | +Api.config.mappings.default_on_attach = keymap.default_on_attach |
| 365 | + |
| 366 | +Api.diagnostics.hi_test = wrap(appearance_hi_test) |
| 367 | + |
| 368 | +Api.commands.get = wrap(function() |
| 369 | + return require("nvim-tree.commands").get() |
| 370 | +end) |
| 371 | + |
| 372 | +---Create a decorator class by calling :extend() |
| 373 | +---See :help nvim-tree-decorators |
| 374 | +---@type nvim_tree.api.decorator.UserDecorator |
| 375 | +Api.decorator.UserDecorator = UserDecorator --[[@as nvim_tree.api.decorator.UserDecorator]] |
| 376 | + |
| 377 | +return Api |
0 commit comments