Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f495542
perf(#3257): remove devicons setup
alex-courtis Mar 28, 2026
0042f7d
perf(#3257): remove padding setup
alex-courtis Mar 28, 2026
f917e9d
perf(#3257): remove appearance and log setup
alex-courtis Mar 28, 2026
2036eec
perf(#3257): remove appearance setup
alex-courtis Mar 28, 2026
6818250
perf(#3257): remove rename-file setup
alex-courtis Mar 28, 2026
77ed15f
perf(#3257): remove devicons setup
alex-courtis Mar 30, 2026
50e7e4c
perf(#3257): remove appearance setup
alex-courtis Mar 30, 2026
b7b1b98
perf(#3257): inline require legacy notify
alex-courtis Mar 30, 2026
bb845a6
perf(#3257): inline require events notify
alex-courtis Mar 30, 2026
cc6bcda
perf(#3257): inline require log notify
alex-courtis Mar 30, 2026
38916ac
perf(#3253): extract change-root
alex-courtis Mar 28, 2026
b7b8e79
perf(#3253): extract open_on_directory
alex-courtis Mar 28, 2026
dc0fc5e
perf(#3253): extract tab_enter
alex-courtis Mar 28, 2026
4c096cd
perf(#3253): move requires inline
alex-courtis Mar 28, 2026
f972194
perf(#3253): extract au find-file, view
alex-courtis Mar 28, 2026
d50a205
perf(#3253): extract manage_netrw
alex-courtis Mar 28, 2026
9184bb4
perf(#3253): extract purge_all_state and document
alex-courtis Mar 28, 2026
430514f
perf(#3253): move full-name setup_autocommands to main
alex-courtis Mar 30, 2026
d38839d
perf(#3253): unextract au find-file for laziness
alex-courtis Mar 30, 2026
c59fc31
perf(#3253): short-circuit open_on_directory au
alex-courtis Mar 30, 2026
400839b
perf(#3253): extract autocmd
alex-courtis Mar 30, 2026
181dab8
perf(#3253): move all setup requires inside the function
alex-courtis Mar 30, 2026
0967322
Merge branch 'master' into 3253.2
alex-courtis Mar 31, 2026
a6ae1cd
Merge branch 'master' into 3253.2
alex-courtis Apr 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
316 changes: 33 additions & 283 deletions lua/nvim-tree.lua
Original file line number Diff line number Diff line change
@@ -1,308 +1,58 @@
local api = require("nvim-tree.api")
local log = require("nvim-tree.log")
local view = require("nvim-tree.view")
local utils = require("nvim-tree.utils")
local find_file = require("nvim-tree.actions.tree.find-file")
local change_dir = require("nvim-tree.actions.tree.change-dir")
local full_name = require("nvim-tree.renderer.components.full-name")
local core = require("nvim-tree.core")
local notify = require("nvim-tree.notify")
local config = require("nvim-tree.config")

local M = {
init_root = "",
}

--- Helper function to execute some explorer method safely
---@param fn string # key of explorer
---@param ... any|nil
---@return function|nil
local function explorer_fn(fn, ...)
local explorer = core.get_explorer()
if explorer then
return explorer[fn](explorer, ...)
end
end

--- Update the tree root to a directory or the directory containing
---@param path string relative or absolute
---@param bufnr number|nil
function M.change_root(path, bufnr)
-- skip if current file is in ignore_list
if type(bufnr) == "number" then
local ft

if vim.fn.has("nvim-0.10") == 1 then
ft = vim.api.nvim_get_option_value("filetype", { buf = bufnr }) or ""
else
ft = vim.api.nvim_buf_get_option(bufnr, "filetype") or "" ---@diagnostic disable-line: deprecated
end

for _, value in pairs(config.g.update_focused_file.update_root.ignore_list) do
if utils.str_find(path, value) or utils.str_find(ft, value) then
return
end
end
end

-- don't find inexistent
if vim.fn.filereadable(path) == 0 then
return
end

local cwd = core.get_cwd()
if cwd == nil then
return
end

local vim_cwd = vim.fn.getcwd()

-- test if in vim_cwd
if utils.path_relative(path, vim_cwd) ~= path then
if vim_cwd ~= cwd then
explorer_fn("change_dir", vim_cwd)
end
return
end
-- test if in cwd
if utils.path_relative(path, cwd) ~= path then
return
end

-- otherwise test M.init_root
if config.g.prefer_startup_root and utils.path_relative(path, M.init_root) ~= path then
explorer_fn("change_dir", M.init_root)
return
end
-- otherwise root_dirs
for _, dir in pairs(config.g.root_dirs) do
dir = vim.fn.fnamemodify(dir, ":p")
if utils.path_relative(path, dir) ~= path then
explorer_fn("change_dir", dir)
return
end
end
-- finally fall back to the folder containing the file
explorer_fn("change_dir", vim.fn.fnamemodify(path, ":p:h"))
end

function M.tab_enter()
if view.is_visible({ any_tabpage = true }) then
local bufname = vim.api.nvim_buf_get_name(0)

local ft
if vim.fn.has("nvim-0.10") == 1 then
ft = vim.api.nvim_get_option_value("filetype", { buf = 0 }) or ""
else
ft = vim.api.nvim_buf_get_option(0, "ft") ---@diagnostic disable-line: deprecated
end

for _, filter in ipairs(config.g.tab.sync.ignore) do
if bufname:match(filter) ~= nil or ft:match(filter) ~= nil then
return
end
end
view.open({ focus_tree = false })

local explorer = core.get_explorer()
if explorer then
explorer.renderer:draw()
end
end
end

function M.open_on_directory()
local should_proceed = config.g.hijack_directories.auto_open or view.is_visible()
if not should_proceed then
return
end

local buf = vim.api.nvim_get_current_buf()
local bufname = vim.api.nvim_buf_get_name(buf)
if vim.fn.isdirectory(bufname) ~= 1 then
return
end


local explorer = core.get_explorer()
if not explorer then
core.init(bufname)
end

explorer_fn("force_dirchange", bufname, true, false)
end

local function manage_netrw()
if config.g.hijack_netrw then
vim.cmd("silent! autocmd! FileExplorer *")
vim.cmd("autocmd VimEnter * ++once silent! autocmd! FileExplorer *")
end
if config.g.disable_netrw then
vim.g.loaded_netrw = 1
vim.g.loaded_netrwPlugin = 1
end
end

local function setup_autocommands()
local augroup_id = vim.api.nvim_create_augroup("NvimTree", { clear = true })
local function create_nvim_tree_autocmd(name, custom_opts)
local default_opts = { group = augroup_id }
vim.api.nvim_create_autocmd(name, vim.tbl_extend("force", default_opts, custom_opts))
end

-- prevent new opened file from opening in the same window as nvim-tree
create_nvim_tree_autocmd("BufWipeout", {
pattern = "NvimTree_*",
callback = function()
if not utils.is_nvim_tree_buf(0) then
return
end
if config.g.actions.open_file.eject then
view._prevent_buffer_override()
else
view.abandon_current_window()
end
end,
})

if config.g.tab.sync.open then
create_nvim_tree_autocmd("TabEnter", { callback = vim.schedule_wrap(M.tab_enter) })
end
if config.g.sync_root_with_cwd then
create_nvim_tree_autocmd("DirChanged", {
callback = function()
change_dir.fn(vim.loop.cwd())
end,
})
end
if config.g.update_focused_file.enable then
create_nvim_tree_autocmd("BufEnter", {
callback = function(event)
local exclude = config.g.update_focused_file.exclude
if type(exclude) == "function" and exclude(event) then
return
end
utils.debounce("BufEnter:find_file", config.g.view.debounce_delay, function()
find_file.fn()
end)
end,
})
end

if config.g.hijack_directories.enable and (config.g.disable_netrw or config.g.hijack_netrw) then
create_nvim_tree_autocmd({ "BufEnter", "BufNewFile" }, { callback = M.open_on_directory, nested = true })
end

if config.g.view.centralize_selection then
create_nvim_tree_autocmd("BufEnter", {
pattern = "NvimTree_*",
callback = function()
vim.schedule(function()
vim.api.nvim_buf_call(0, function()
local is_term_mode = vim.api.nvim_get_mode().mode == "t"
if is_term_mode then
return
end
vim.cmd([[norm! zz]])
end)
end)
end,
})
end

if config.g.diagnostics.enable then
create_nvim_tree_autocmd("DiagnosticChanged", {
callback = function(ev)
log.line("diagnostics", "DiagnosticChanged")
require("nvim-tree.diagnostics").update_lsp(ev)
end,
})
create_nvim_tree_autocmd("User", {
pattern = "CocDiagnosticChange",
callback = function()
log.line("diagnostics", "CocDiagnosticChange")
require("nvim-tree.diagnostics").update_coc()
end,
})
end

if config.g.view.float.enable and config.g.view.float.quit_on_focus_loss then
create_nvim_tree_autocmd("WinLeave", {
pattern = "NvimTree_*",
callback = function()
if utils.is_nvim_tree_buf(0) then
view.close()
end
end,
})
end

-- Handles event dispatch when tree is closed by `:q`
create_nvim_tree_autocmd("WinClosed", {
pattern = "*",
---@param ev vim.api.keyset.create_autocmd.callback_args
callback = function(ev)
if not vim.api.nvim_buf_is_valid(ev.buf) then
return
end
if vim.api.nvim_get_option_value("filetype", { buf = ev.buf }) == "NvimTree" then
require("nvim-tree.events")._dispatch_on_tree_close()
end
end,
})

-- renderer.full name
full_name.setup_autocommands()
end

function M.purge_all_state()
view.close_all_tabs()
view.abandon_all_windows()
local explorer = core.get_explorer()
if explorer then
require("nvim-tree.git").purge_state()
explorer:destroy()
core.reset_explorer()
end
-- purge orphaned that were not destroyed by their nodes
require("nvim-tree.watcher").purge_watchers()
end

---@param config_user? nvim_tree.config user supplied subset of config
local M = {}

---`require("nvim-tree").setup` must be called once to initialise nvim-tree.
---
---Call again to apply a change in configuration without restarting Nvim.
---
---See :help nvim-tree-setup
---
---@param config_user? nvim_tree.config subset, uses defaults when nil
function M.setup(config_user)
local api = require("nvim-tree.api")
local api_impl = require("nvim-tree.api.impl")
local appearance = require("nvim-tree.appearance")
local autocmd = require("nvim-tree.autocmd")
local config = require("nvim-tree.config")
local log = require("nvim-tree.log")
local view_state = require("nvim-tree.view-state")

-- Nvim version check
if vim.fn.has("nvim-0.9") == 0 then
notify.warn("nvim-tree.lua requires Neovim 0.9 or higher")
require("nvim-tree.notify").warn("nvim-tree.lua requires Neovim 0.9 or higher")
return
end

M.init_root = vim.fn.getcwd()

-- validate and merge with defaults as config.g
config.setup(config_user)

manage_netrw()

-- optionally create the log file
log.start()

-- optionally log the configuration
if log.enabled("config") then
log.line("config", "default config + user")
log.raw("config", "%s\n", vim.inspect(config.g))
end

require("nvim-tree.appearance").highlight()
-- idempotent highlight definition
appearance.highlight()

require("nvim-tree.view-state").initialize()
-- set the initial view state based on config
view_state.initialize()

setup_autocommands()
-- idempotent au (re)definition
autocmd.global()

-- subsequent calls to setup clear all state
if vim.g.NvimTreeSetup == 1 then
-- subsequent calls to setup
M.purge_all_state()
require("nvim-tree.core").purge_all_state()
end

-- hydrate post setup API
api_impl.hydrate_post_setup(api)

vim.g.NvimTreeSetup = 1
vim.api.nvim_exec_autocmds("User", { pattern = "NvimTreeSetup" })

require("nvim-tree.api.impl").hydrate_post_setup(api)
end

vim.g.NvimTreeRequired = 1
Expand Down
Loading
Loading