From dc56926121c4240d109e6ca1765b0f881c975318 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Sun, 10 Aug 2025 14:11:26 +1000 Subject: [PATCH 1/3] Revert "fix(#3172): live filter exception (#3173)" This reverts commit 0a7fcdf3f8ba208f4260988a198c77ec11748339. --- lua/nvim-tree/explorer/live-filter.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/nvim-tree/explorer/live-filter.lua b/lua/nvim-tree/explorer/live-filter.lua index aa710340136..9bd24406964 100644 --- a/lua/nvim-tree/explorer/live-filter.lua +++ b/lua/nvim-tree/explorer/live-filter.lua @@ -161,7 +161,7 @@ end ---@return integer local function calculate_overlay_win_width(self) - local wininfo = vim.fn.getwininfo(self.explorer.view:get_winid())[1] + local wininfo = vim.fn.getwininfo(self.explorer.view:get_winnr())[1] if wininfo then return wininfo.width - wininfo.textoff - #self.prefix From 5332f3c23879b8bfd30fcb4dc733614ba8a6978f Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Sun, 10 Aug 2025 14:28:34 +1000 Subject: [PATCH 2/3] Revert "refactor(#2826): move view to instanced window class (#3153)" This reverts commit 0a06f65bf06157972f20ca1dee03c97a0efcb188. --- doc/nvim-tree-lua.txt | 1 - lua/nvim-tree.lua | 47 +- lua/nvim-tree/actions/finders/find-file.lua | 7 +- lua/nvim-tree/actions/fs/clipboard.lua | 6 - lua/nvim-tree/actions/fs/remove-file.lua | 10 +- lua/nvim-tree/actions/moves/item.lua | 9 +- lua/nvim-tree/actions/moves/parent.lua | 5 +- lua/nvim-tree/actions/node/open-file.lua | 52 +- lua/nvim-tree/actions/root/change-dir.lua | 2 +- lua/nvim-tree/actions/tree/find-file.lua | 6 +- lua/nvim-tree/actions/tree/open.lua | 8 +- lua/nvim-tree/actions/tree/resize.lua | 21 +- lua/nvim-tree/actions/tree/toggle.lua | 8 +- lua/nvim-tree/api.lua | 21 +- lua/nvim-tree/appearance/init.lua | 23 - lua/nvim-tree/commands.lua | 7 +- lua/nvim-tree/core.lua | 8 +- lua/nvim-tree/diagnostics.lua | 10 +- lua/nvim-tree/explorer/filters.lua | 6 - lua/nvim-tree/explorer/init.lua | 86 +- lua/nvim-tree/explorer/live-filter.lua | 21 +- lua/nvim-tree/explorer/sorter.lua | 6 - lua/nvim-tree/explorer/view.lua | 799 ------------------ lua/nvim-tree/globals.lua | 10 - lua/nvim-tree/help.lua | 9 +- lua/nvim-tree/iterators/node-iterator.lua | 2 +- lua/nvim-tree/lib.lua | 33 +- lua/nvim-tree/log.lua | 2 +- lua/nvim-tree/marks/init.lua | 6 - lua/nvim-tree/multi-instance-debug.lua | 112 --- lua/nvim-tree/renderer/builder.lua | 3 +- .../renderer/components/full-name.lua | 8 +- lua/nvim-tree/renderer/init.lua | 17 +- lua/nvim-tree/utils.lua | 60 +- lua/nvim-tree/view.lua | 639 ++++++++++++++ 35 files changed, 821 insertions(+), 1249 deletions(-) delete mode 100644 lua/nvim-tree/explorer/view.lua delete mode 100644 lua/nvim-tree/globals.lua delete mode 100644 lua/nvim-tree/multi-instance-debug.lua create mode 100644 lua/nvim-tree/view.lua diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index 98eb8abfc46..adf54f1c2b3 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -639,7 +639,6 @@ Following is the default configuration. See |nvim-tree-opts| for details. >lua }, }, experimental = { - multi_instance = false, }, log = { enable = false, diff --git a/lua/nvim-tree.lua b/lua/nvim-tree.lua index e4b12181c73..8befe53ed05 100644 --- a/lua/nvim-tree.lua +++ b/lua/nvim-tree.lua @@ -1,4 +1,5 @@ local log = require("nvim-tree.log") +local view = require("nvim-tree.view") local utils = require("nvim-tree.utils") local actions = require("nvim-tree.actions") local core = require("nvim-tree.core") @@ -73,8 +74,7 @@ function M.change_root(path, bufnr) end function M.tab_enter() - local explorer = core.get_explorer() - if explorer and explorer.view:is_visible({ any_tabpage = true }) then + if view.is_visible({ any_tabpage = true }) then local bufname = vim.api.nvim_buf_get_name(0) local ft @@ -89,15 +89,17 @@ function M.tab_enter() return end end - explorer.view:open({ focus_tree = false }) + view.open({ focus_tree = false }) - explorer.renderer:draw() + local explorer = core.get_explorer() + if explorer then + explorer.renderer:draw() + end end end function M.open_on_directory() - local explorer = core.get_explorer() - local should_proceed = _config.hijack_directories.auto_open or explorer and explorer.view:is_visible() + local should_proceed = _config.hijack_directories.auto_open or view.is_visible() if not should_proceed then return end @@ -148,6 +150,21 @@ local function setup_autocommands(opts) vim.api.nvim_create_autocmd(name, vim.tbl_extend("force", default_opts, custom_opts)) end + -- prevent new opened file from opening in the same window as nvim-tree + create_nvim_tree_autocmd("BufWipeout", { + pattern = "NvimTree_*", + callback = function() + if not utils.is_nvim_tree_buf(0) then + return + end + if opts.actions.open_file.eject then + view._prevent_buffer_override() + else + view.abandon_current_window() + end + end, + }) + if opts.tab.sync.open then create_nvim_tree_autocmd("TabEnter", { callback = vim.schedule_wrap(M.tab_enter) }) end @@ -209,6 +226,17 @@ local function setup_autocommands(opts) }) end + if opts.view.float.enable and opts.view.float.quit_on_focus_loss then + create_nvim_tree_autocmd("WinLeave", { + pattern = "NvimTree_*", + callback = function() + if utils.is_nvim_tree_buf(0) then + view.close() + end + end, + }) + end + -- Handles event dispatch when tree is closed by `:q` create_nvim_tree_autocmd("WinClosed", { pattern = "*", @@ -486,7 +514,6 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS }, }, experimental = { - multi_instance = false, }, log = { enable = false, @@ -666,10 +693,10 @@ local function localise_default_opts() end function M.purge_all_state() + view.close_all_tabs() + view.abandon_all_windows() local explorer = core.get_explorer() if explorer then - explorer.view:close_all_tabs() - explorer.view:abandon_all_windows("purge_all_state") require("nvim-tree.git").purge_state() explorer:destroy() core.reset_explorer() @@ -722,12 +749,12 @@ function M.setup(conf) require("nvim-tree.explorer.watch").setup(opts) require("nvim-tree.git").setup(opts) require("nvim-tree.git.utils").setup(opts) + require("nvim-tree.view").setup(opts) require("nvim-tree.lib").setup(opts) require("nvim-tree.renderer.components").setup(opts) require("nvim-tree.buffers").setup(opts) require("nvim-tree.help").setup(opts) require("nvim-tree.watcher").setup(opts) - require("nvim-tree.multi-instance-debug").setup(opts) setup_autocommands(opts) diff --git a/lua/nvim-tree/actions/finders/find-file.lua b/lua/nvim-tree/actions/finders/find-file.lua index 2ea494fd85c..55e2f23d337 100644 --- a/lua/nvim-tree/actions/finders/find-file.lua +++ b/lua/nvim-tree/actions/finders/find-file.lua @@ -1,4 +1,5 @@ local log = require("nvim-tree.log") +local view = require("nvim-tree.view") local utils = require("nvim-tree.utils") local core = require("nvim-tree.core") @@ -13,7 +14,7 @@ local running = {} ---@param path string relative or absolute function M.fn(path) local explorer = core.get_explorer() - if not explorer or not explorer.view:is_visible() then + if not explorer or not view.is_visible() then return end @@ -83,9 +84,9 @@ function M.fn(path) end) :iterate() - if found and explorer.view:is_visible() then + if found and view.is_visible() then explorer.renderer:draw() - explorer.view:set_cursor({ line, 0 }) + view.set_cursor({ line, 0 }) end running[path_real] = false diff --git a/lua/nvim-tree/actions/fs/clipboard.lua b/lua/nvim-tree/actions/fs/clipboard.lua index aa9ae2c8664..3f3ed37c94d 100644 --- a/lua/nvim-tree/actions/fs/clipboard.lua +++ b/lua/nvim-tree/actions/fs/clipboard.lua @@ -31,8 +31,6 @@ local Clipboard = Class:extend() ---@protected ---@param args ClipboardArgs function Clipboard:new(args) - args.explorer:log_new("Clipboard") - self.explorer = args.explorer self.data = { @@ -44,10 +42,6 @@ function Clipboard:new(args) self.reg = self.explorer.opts.actions.use_system_clipboard and "+" or "1" end -function Clipboard:destroy() - self.explorer:log_destroy("Clipboard") -end - ---@param source string ---@param destination string ---@return boolean diff --git a/lua/nvim-tree/actions/fs/remove-file.lua b/lua/nvim-tree/actions/fs/remove-file.lua index b475506380a..a22aad3a8ed 100644 --- a/lua/nvim-tree/actions/fs/remove-file.lua +++ b/lua/nvim-tree/actions/fs/remove-file.lua @@ -1,6 +1,7 @@ local core = require("nvim-tree.core") local utils = require("nvim-tree.utils") local events = require("nvim-tree.events") +local view = require("nvim-tree.view") local lib = require("nvim-tree.lib") local notify = require("nvim-tree.notify") @@ -13,12 +14,10 @@ local M = { ---@param windows integer[] local function close_windows(windows) - local explorer = core.get_explorer() - -- Prevent from closing when the win count equals 1 or 2, -- where the win to remove could be the last opened. -- For details see #2503. - if explorer and explorer.view.float.enable and #vim.api.nvim_list_wins() < 3 then + if view.View.float.enable and #vim.api.nvim_list_wins() < 3 then return end @@ -31,17 +30,16 @@ end ---@param absolute_path string local function clear_buffer(absolute_path) - local explorer = core.get_explorer() local bufs = vim.fn.getbufinfo({ bufloaded = 1, buflisted = 1 }) for _, buf in pairs(bufs) do if buf.name == absolute_path then local tree_winnr = vim.api.nvim_get_current_win() - if buf.hidden == 0 and (#bufs > 1 or explorer and explorer.view.float.enable) then + if buf.hidden == 0 and (#bufs > 1 or view.View.float.enable) then vim.api.nvim_set_current_win(buf.windows[1]) vim.cmd(":bn") end vim.api.nvim_buf_delete(buf.bufnr, { force = true }) - if explorer and not explorer.view.float.quit_on_focus_loss then + if not view.View.float.quit_on_focus_loss then vim.api.nvim_set_current_win(tree_winnr) end if M.config.actions.remove_file.close_window then diff --git a/lua/nvim-tree/actions/moves/item.lua b/lua/nvim-tree/actions/moves/item.lua index 599a8053027..8aacaae3b56 100644 --- a/lua/nvim-tree/actions/moves/item.lua +++ b/lua/nvim-tree/actions/moves/item.lua @@ -1,4 +1,5 @@ local utils = require("nvim-tree.utils") +local view = require("nvim-tree.view") local core = require("nvim-tree.core") local diagnostics = require("nvim-tree.diagnostics") @@ -66,9 +67,9 @@ local function move(explorer, where, what, skip_gitignored) end if nex then - explorer.view:set_cursor({ nex, 0 }) + view.set_cursor({ nex, 0 }) elseif vim.o.wrapscan and first then - explorer.view:set_cursor({ first, 0 }) + view.set_cursor({ first, 0 }) end end @@ -188,13 +189,13 @@ local function move_prev_recursive(explorer, what, skip_gitignored) -- 4.3) if node_init.name == ".." then -- root node - explorer.view:set_cursor({ 1, 0 }) -- move to root node (position 1) + view.set_cursor({ 1, 0 }) -- move to root node (position 1) else local node_init_line = utils.find_node_line(node_init) if node_init_line < 0 then return end - explorer.view:set_cursor({ node_init_line, 0 }) + view.set_cursor({ node_init_line, 0 }) end -- 4.4) diff --git a/lua/nvim-tree/actions/moves/parent.lua b/lua/nvim-tree/actions/moves/parent.lua index fab1a440a2f..32ca8398116 100644 --- a/lua/nvim-tree/actions/moves/parent.lua +++ b/lua/nvim-tree/actions/moves/parent.lua @@ -1,3 +1,4 @@ +local view = require("nvim-tree.view") local utils = require("nvim-tree.utils") local DirectoryNode = require("nvim-tree.node.directory") @@ -24,7 +25,7 @@ function M.fn(should_close) local parent = (node:get_parent_of_group() or node).parent if not parent or not parent.parent then - node.explorer.view:set_cursor({ 1, 0 }) + view.set_cursor({ 1, 0 }) return end @@ -32,7 +33,7 @@ function M.fn(should_close) return n.absolute_path == parent.absolute_path end) - node.explorer.view:set_cursor({ line + 1, 0 }) + view.set_cursor({ line + 1, 0 }) if should_close then parent.open = false parent.explorer.renderer:draw() diff --git a/lua/nvim-tree/actions/node/open-file.lua b/lua/nvim-tree/actions/node/open-file.lua index f59ceb6f249..de43416eac1 100644 --- a/lua/nvim-tree/actions/node/open-file.lua +++ b/lua/nvim-tree/actions/node/open-file.lua @@ -2,8 +2,8 @@ local lib = require("nvim-tree.lib") local notify = require("nvim-tree.notify") local utils = require("nvim-tree.utils") -local core = require("nvim-tree.core") local full_name = require("nvim-tree.renderer.components.full-name") +local view = require("nvim-tree.view") local M = {} @@ -20,10 +20,9 @@ end ---Get all windows in the current tabpage that aren't NvimTree. ---@return table with valid win_ids local function usable_win_ids() - local explorer = core.get_explorer() local tabpage = vim.api.nvim_get_current_tabpage() local win_ids = vim.api.nvim_tabpage_list_wins(tabpage) - local tree_winid = explorer and explorer.view:get_winnr(tabpage, "open-file.usable_win_ids") + local tree_winid = view.get_winnr(tabpage) return vim.tbl_filter(function(id) local bufid = vim.api.nvim_win_get_buf(id) @@ -194,10 +193,7 @@ end local function open_file_in_tab(filename) if M.quit_on_open then - local explorer = core.get_explorer() - if explorer then - explorer.view:close(nil, "open-file.open_file_in_tab") - end + view.close() end if M.relative_path then filename = utils.path_relative(filename, vim.fn.getcwd()) @@ -207,10 +203,7 @@ end local function drop(filename) if M.quit_on_open then - local explorer = core.get_explorer() - if explorer then - explorer.view:close(nil, "open-file.drop") - end + view.close() end if M.relative_path then filename = utils.path_relative(filename, vim.fn.getcwd()) @@ -220,10 +213,7 @@ end local function tab_drop(filename) if M.quit_on_open then - local explorer = core.get_explorer() - if explorer then - explorer.view:close(nil, "open-file.tab_drop") - end + view.close() end if M.relative_path then filename = utils.path_relative(filename, vim.fn.getcwd()) @@ -244,10 +234,7 @@ local function on_preview(buf_loaded) once = true, }) end - local explorer = core.get_explorer() - if explorer then - explorer.view:focus() - end + view.focus() end local function get_target_winid(mode) @@ -292,8 +279,6 @@ local function set_current_win_no_autocmd(winid, autocmd) end local function open_in_new_window(filename, mode) - local explorer = core.get_explorer() - if type(mode) ~= "string" then mode = "" end @@ -316,11 +301,7 @@ local function open_in_new_window(filename, mode) end, vim.api.nvim_list_wins()) local create_new_window = #win_ids == 1 -- This implies that the nvim-tree window is the only one - - local new_window_side = "belowright" - if explorer and (explorer.view.side == "right") then - new_window_side = "aboveleft" - end + local new_window_side = (view.View.side == "right") and "aboveleft" or "belowright" -- Target is invalid: create new window if not vim.tbl_contains(win_ids, target_winid) then @@ -352,7 +333,7 @@ local function open_in_new_window(filename, mode) end end - if (mode == "preview" or mode == "preview_no_picker") and explorer and explorer.view.float.enable then + if (mode == "preview" or mode == "preview_no_picker") and view.View.float.enable then -- ignore "WinLeave" autocmd on preview -- because the registered "WinLeave" -- will kill the floating window immediately @@ -392,12 +373,7 @@ local function is_already_loaded(filename) end local function edit_in_current_buf(filename) - local explorer = core.get_explorer() - - if explorer then - explorer.view:abandon_current_window() - end - + require("nvim-tree.view").abandon_current_window() if M.relative_path then filename = utils.path_relative(filename, vim.fn.getcwd()) end @@ -408,8 +384,6 @@ end ---@param filename string ---@return nil function M.fn(mode, filename) - local explorer = core.get_explorer() - if type(mode) ~= "string" then mode = "" end @@ -444,16 +418,16 @@ function M.fn(mode, filename) vim.bo.bufhidden = "" end - if M.resize_window and explorer then - explorer.view:resize() + if M.resize_window then + view.resize() end if mode == "preview" or mode == "preview_no_picker" then return on_preview(buf_loaded) end - if M.quit_on_open and explorer then - explorer.view:close(nil, "open-file.fn") + if M.quit_on_open then + view.close() end end diff --git a/lua/nvim-tree/actions/root/change-dir.lua b/lua/nvim-tree/actions/root/change-dir.lua index f98d51d33e2..f23f3c12086 100644 --- a/lua/nvim-tree/actions/root/change-dir.lua +++ b/lua/nvim-tree/actions/root/change-dir.lua @@ -85,7 +85,7 @@ M.force_dirchange = add_profiling_to(function(foldername, should_open_view) if should_change_dir() then cd(M.options.global, foldername) end - core.init(foldername, "change-dir") + core.init(foldername) end if should_open_view then diff --git a/lua/nvim-tree/actions/tree/find-file.lua b/lua/nvim-tree/actions/tree/find-file.lua index 73d1df3c75e..8a05bf6db45 100644 --- a/lua/nvim-tree/actions/tree/find-file.lua +++ b/lua/nvim-tree/actions/tree/find-file.lua @@ -1,5 +1,6 @@ local core = require("nvim-tree.core") local lib = require("nvim-tree.lib") +local view = require("nvim-tree.view") local finders_find_file = require("nvim-tree.actions.finders.find-file") local M = {} @@ -40,12 +41,11 @@ function M.fn(opts) return end - local explorer = core.get_explorer() - if explorer and explorer.view:is_visible() then + if view.is_visible() then -- focus if opts.focus then lib.set_target_win() - explorer.view:focus() + view.focus() end elseif opts.open then -- open diff --git a/lua/nvim-tree/actions/tree/open.lua b/lua/nvim-tree/actions/tree/open.lua index 17e2c581a7b..ff2da837b87 100644 --- a/lua/nvim-tree/actions/tree/open.lua +++ b/lua/nvim-tree/actions/tree/open.lua @@ -1,5 +1,5 @@ -local core = require("nvim-tree.core") local lib = require("nvim-tree.lib") +local view = require("nvim-tree.view") local finders_find_file = require("nvim-tree.actions.finders.find-file") local M = {} @@ -23,12 +23,10 @@ function M.fn(opts) opts.path = nil end - local explorer = core.get_explorer() - - if explorer and explorer.view:is_visible() then + if view.is_visible() then -- focus lib.set_target_win() - explorer.view:focus() + view.focus() else -- open lib.open({ diff --git a/lua/nvim-tree/actions/tree/resize.lua b/lua/nvim-tree/actions/tree/resize.lua index 48f090887f7..e8d4e950729 100644 --- a/lua/nvim-tree/actions/tree/resize.lua +++ b/lua/nvim-tree/actions/tree/resize.lua @@ -1,19 +1,14 @@ -local core = require("nvim-tree.core") +local view = require("nvim-tree.view") local M = {} ---Resize the tree, persisting the new size. ---@param opts ApiTreeResizeOpts|nil function M.fn(opts) - local explorer = core.get_explorer() - if not explorer then - return - end - if opts == nil then -- reset to config values - explorer.view:configure_width() - explorer.view:resize() + view.configure_width() + view.resize() return end @@ -21,19 +16,19 @@ function M.fn(opts) local width_cfg = options.width if width_cfg ~= nil then - explorer.view:configure_width(width_cfg) - explorer.view:resize() + view.configure_width(width_cfg) + view.resize() return end - if not explorer.view:is_width_determined() then + if not view.is_width_determined() then -- {absolute} and {relative} do nothing when {width} is a function. return end local absolute = options.absolute if type(absolute) == "number" then - explorer.view:resize(absolute) + view.resize(absolute) return end @@ -44,7 +39,7 @@ function M.fn(opts) relative_size = "+" .. relative_size end - explorer.view:resize(relative_size) + view.resize(relative_size) return end end diff --git a/lua/nvim-tree/actions/tree/toggle.lua b/lua/nvim-tree/actions/tree/toggle.lua index d2ba0262a30..10aa978467e 100644 --- a/lua/nvim-tree/actions/tree/toggle.lua +++ b/lua/nvim-tree/actions/tree/toggle.lua @@ -1,5 +1,5 @@ -local core = require("nvim-tree.core") local lib = require("nvim-tree.lib") +local view = require("nvim-tree.view") local finders_find_file = require("nvim-tree.actions.finders.find-file") local M = {} @@ -10,8 +10,6 @@ local M = {} ---@param cwd boolean|nil legacy -> opts.path ---@param bang boolean|nil legacy -> opts.update_root function M.fn(opts, no_focus, cwd, bang) - local explorer = core.get_explorer() - -- legacy arguments if type(opts) == "boolean" then opts = { @@ -42,9 +40,9 @@ function M.fn(opts, no_focus, cwd, bang) opts.path = nil end - if explorer and explorer.view:is_visible() then + if view.is_visible() then -- close - explorer.view:close(nil, "toggle.fn") + view.close() else -- open lib.open({ diff --git a/lua/nvim-tree/api.lua b/lua/nvim-tree/api.lua index c75115c6c62..39fba07d52d 100644 --- a/lua/nvim-tree/api.lua +++ b/lua/nvim-tree/api.lua @@ -1,4 +1,5 @@ local core = require("nvim-tree.core") +local view = require("nvim-tree.view") local utils = require("nvim-tree.utils") local actions = require("nvim-tree.actions") local appearance_hi_test = require("nvim-tree.appearance.hi-test") @@ -140,9 +141,9 @@ Api.tree.focus = Api.tree.open ---@field focus boolean|nil default true Api.tree.toggle = wrap(actions.tree.toggle.fn) -Api.tree.close = wrap_explorer_member("view", "close") -Api.tree.close_in_this_tab = wrap_explorer_member("view", "close_this_tab_only") -Api.tree.close_in_all_tabs = wrap_explorer_member("view", "close_all_tabs") +Api.tree.close = wrap(view.close) +Api.tree.close_in_this_tab = wrap(view.close_this_tab_only) +Api.tree.close_in_all_tabs = wrap(view.close_all_tabs) Api.tree.reload = wrap_explorer("reload_explorer") ---@class ApiTreeResizeOpts @@ -201,12 +202,12 @@ Api.tree.is_tree_buf = wrap(utils.is_nvim_tree_buf) ---@field tabpage number|nil ---@field any_tabpage boolean|nil default false -Api.tree.is_visible = wrap_explorer_member("view", "is_visible") +Api.tree.is_visible = wrap(view.is_visible) ---@class ApiTreeWinIdOpts ---@field tabpage number|nil default nil -Api.tree.winid = wrap_explorer_member("view", "api_winid") +Api.tree.winid = wrap(view.winid) Api.fs.create = wrap_node_or_nil(actions.fs.create_file.fn) Api.fs.remove = wrap_node(actions.fs.remove_file.fn) @@ -238,17 +239,13 @@ local function edit(mode, node, edit_opts) local path = file_link and file_link.link_to or node.absolute_path local cur_tabpage = vim.api.nvim_get_current_tabpage() - local explorer = core.get_explorer() - actions.node.open_file.fn(mode, path) edit_opts = edit_opts or {} local mode_unsupported_quit_on_open = mode == "drop" or mode == "tab_drop" or mode == "edit_in_place" if not mode_unsupported_quit_on_open and edit_opts.quit_on_open then - if explorer then - explorer.view:close(cur_tabpage, "api.edit " .. mode) - end + view.close(cur_tabpage) end local mode_unsupported_focus = mode == "drop" or mode == "tab_drop" or mode == "edit_in_place" @@ -258,9 +255,7 @@ local function edit(mode, node, edit_opts) if mode == "tabnew" then vim.cmd(":tabprev") end - if explorer then - explorer.view:focus() - end + view.focus() end end diff --git a/lua/nvim-tree/appearance/init.lua b/lua/nvim-tree/appearance/init.lua index e3e3ab26690..61714af644e 100644 --- a/lua/nvim-tree/appearance/init.lua +++ b/lua/nvim-tree/appearance/init.lua @@ -134,29 +134,6 @@ M.HIGHLIGHT_GROUPS = { { group = "NvimTreeDiagnosticHintFolderHL", link = "NvimTreeDiagnosticHintFileHL" }, } --- winhighlight for most cases -M.WIN_HL = table.concat({ - "EndOfBuffer:NvimTreeEndOfBuffer", - "CursorLine:NvimTreeCursorLine", - "CursorLineNr:NvimTreeCursorLineNr", - "LineNr:NvimTreeLineNr", - "WinSeparator:NvimTreeWinSeparator", - "StatusLine:NvimTreeStatusLine", - "StatusLineNC:NvimTreeStatuslineNC", - "SignColumn:NvimTreeSignColumn", - "Normal:NvimTreeNormal", - "NormalNC:NvimTreeNormalNC", - "NormalFloat:NvimTreeNormalFloat", - "FloatBorder:NvimTreeNormalFloatBorder", -}, ",") - --- winhighlight for help -M.WIN_HL_HELP = table.concat({ - "NormalFloat:NvimTreeNormalFloat", - "WinSeparator:NvimTreeWinSeparator", - "CursorLine:NvimTreeCursorLine", -}, ",") - -- nvim-tree highlight groups to legacy M.LEGACY_LINKS = { NvimTreeModifiedIcon = "NvimTreeModifiedFile", diff --git a/lua/nvim-tree/commands.lua b/lua/nvim-tree/commands.lua index 88ac3e1e61e..bff880c14fd 100644 --- a/lua/nvim-tree/commands.lua +++ b/lua/nvim-tree/commands.lua @@ -1,5 +1,5 @@ local api = require("nvim-tree.api") -local core = require("nvim-tree.core") +local view = require("nvim-tree.view") local M = {} @@ -111,10 +111,7 @@ local CMDS = { bar = true, }, command = function(c) - local explorer = core.get_explorer() - if explorer then - explorer.view:resize(c.args) - end + view.resize(c.args) end, }, { diff --git a/lua/nvim-tree/core.lua b/lua/nvim-tree/core.lua index 46f6ef698ca..60d4a0a6f6a 100644 --- a/lua/nvim-tree/core.lua +++ b/lua/nvim-tree/core.lua @@ -1,5 +1,6 @@ local events = require("nvim-tree.events") local notify = require("nvim-tree.notify") +local view = require("nvim-tree.view") local log = require("nvim-tree.log") local M = {} @@ -9,12 +10,9 @@ local TreeExplorer = nil local first_init_done = false ---@param foldername string ----@param callsite string -function M.init(foldername, callsite) +function M.init(foldername) local profile = log.profile_start("core init %s", foldername) - log.line("dev", "core.init(%s, %s)", foldername, callsite) - if TreeExplorer then TreeExplorer:destroy() end @@ -57,7 +55,7 @@ end ---@return integer function M.get_nodes_starting_line() local offset = 1 - if TreeExplorer and TreeExplorer.view:is_root_folder_visible(M.get_cwd()) then + if view.is_root_folder_visible(M.get_cwd()) then offset = offset + 1 end if TreeExplorer and TreeExplorer.live_filter.filter then diff --git a/lua/nvim-tree/diagnostics.lua b/lua/nvim-tree/diagnostics.lua index 0ed81c74682..76810d4a796 100644 --- a/lua/nvim-tree/diagnostics.lua +++ b/lua/nvim-tree/diagnostics.lua @@ -1,5 +1,6 @@ local core = require("nvim-tree.core") local utils = require("nvim-tree.utils") +local view = require("nvim-tree.view") local log = require("nvim-tree.log") local DirectoryNode = require("nvim-tree.node.directory") @@ -181,15 +182,10 @@ function M.update_coc() end log.profile_end(profile) - local explorer = core.get_explorer() - - local bufnr - if explorer then - bufnr = explorer.view:get_bufnr("diagnostics.update_coc") - end - + local bufnr = view.get_bufnr() local should_draw = bufnr and vim.api.nvim_buf_is_valid(bufnr) and vim.api.nvim_buf_is_loaded(bufnr) if should_draw then + local explorer = core.get_explorer() if explorer then explorer.renderer:draw() end diff --git a/lua/nvim-tree/explorer/filters.lua b/lua/nvim-tree/explorer/filters.lua index 6066e8a32bc..62230687e40 100644 --- a/lua/nvim-tree/explorer/filters.lua +++ b/lua/nvim-tree/explorer/filters.lua @@ -23,8 +23,6 @@ local Filters = Class:extend() ---@protected ---@param args FiltersArgs function Filters:new(args) - args.explorer:log_new("Filters") - self.explorer = args.explorer self.ignore_list = {} self.exclude_list = self.explorer.opts.filters.exclude @@ -52,10 +50,6 @@ function Filters:new(args) end end -function Filters:destroy() - self.explorer:log_destroy("Filters") -end - ---@private ---@param path string ---@return boolean diff --git a/lua/nvim-tree/explorer/init.lua b/lua/nvim-tree/explorer/init.lua index ca8c6d39103..58972164afe 100644 --- a/lua/nvim-tree/explorer/init.lua +++ b/lua/nvim-tree/explorer/init.lua @@ -4,6 +4,7 @@ local core = require("nvim-tree.core") local git = require("nvim-tree.git") local log = require("nvim-tree.log") local utils = require("nvim-tree.utils") +local view = require("nvim-tree.view") local node_factory = require("nvim-tree.node.factory") local DirectoryNode = require("nvim-tree.node.directory") @@ -19,7 +20,6 @@ local LiveFilter = require("nvim-tree.explorer.live-filter") local Sorter = require("nvim-tree.explorer.sorter") local Clipboard = require("nvim-tree.actions.fs.clipboard") local Renderer = require("nvim-tree.renderer") -local View = require("nvim-tree.explorer.view") local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON @@ -35,7 +35,6 @@ local config ---@field sorters Sorter ---@field marks Marks ---@field clipboard Clipboard ----@field view View local Explorer = RootNode:extend() ---@class Explorer @@ -56,18 +55,15 @@ function Explorer:new(args) self.uid_explorer = vim.loop.hrtime() self.augroup_id = vim.api.nvim_create_augroup("NvimTree_Explorer_" .. self.uid_explorer, {}) - self:log_new("Explorer") + self.open = true + self.opts = config - self.open = true - self.opts = config - - self.sorters = Sorter({ explorer = self }) - self.renderer = Renderer({ explorer = self }) - self.filters = Filters({ explorer = self }) - self.live_filter = LiveFilter({ explorer = self }) - self.marks = Marks({ explorer = self }) - self.clipboard = Clipboard({ explorer = self }) - self.view = View({ explorer = self }) + self.sorters = Sorter({ explorer = self }) + self.renderer = Renderer({ explorer = self }) + self.filters = Filters({ explorer = self }) + self.live_filter = LiveFilter({ explorer = self }) + self.marks = Marks({ explorer = self }) + self.clipboard = Clipboard({ explorer = self }) self:create_autocmds() @@ -75,15 +71,7 @@ function Explorer:new(args) end function Explorer:destroy() - self.explorer:log_destroy("Explorer") - - self.clipboard:destroy() - self.filters:destroy() - self.live_filter:destroy() - self.marks:destroy() - self.renderer:destroy() - self.sorters:destroy() - self.view:destroy() + log.line("dev", "Explorer:destroy") vim.api.nvim_del_augroup_by_id(self.augroup_id) @@ -96,26 +84,11 @@ function Explorer:create_autocmds() group = self.augroup_id, callback = function() appearance.setup() - self.view:reset_winhl() + view.reset_winhl() self.renderer:draw() end, }) - if self.opts.view.float.enable and self.opts.view.float.quit_on_focus_loss then - vim.api.nvim_create_autocmd("WinLeave", { - group = self.augroup_id, - pattern = "NvimTree_*", - callback = function(data) - if self.opts.experimental.multi_instance then - log.line("dev", "WinLeave %s", vim.inspect(data, { newline = "" })) - end - if utils.is_nvim_tree_buf(0) then - self.view:close(nil, "WinLeave") - end - end, - }) - end - vim.api.nvim_create_autocmd("BufWritePost", { group = self.augroup_id, callback = function() @@ -168,25 +141,6 @@ function Explorer:create_autocmds() end, }) - -- prevent new opened file from opening in the same window as nvim-tree - vim.api.nvim_create_autocmd("BufWipeout", { - group = self.augroup_id, - pattern = "NvimTree_*", - callback = function(data) - if self.opts.experimental.multi_instance then - log.line("dev", "BufWipeout %s", vim.inspect(data, { newline = "" })) - end - if not utils.is_nvim_tree_buf(0) then - return - end - if self.opts.actions.open_file.eject then - self.view:prevent_buffer_override() - else - self.view:abandon_current_window() - end - end, - }) - vim.api.nvim_create_autocmd("BufEnter", { group = self.augroup_id, pattern = "NvimTree_*", @@ -532,7 +486,7 @@ function Explorer:reload_explorer() local projects = git.reload_all_projects() self:refresh_nodes(projects) - if self.view:is_visible() then + if view.is_visible() then self.renderer:draw() end event_running = false @@ -554,7 +508,7 @@ end ---nil on no explorer or invalid view win ---@return integer[]|nil function Explorer:get_cursor_position() - local winnr = self.view:get_winnr(nil, "Explorer:get_cursor_position") + local winnr = view.get_winnr() if not winnr or not vim.api.nvim_win_is_valid(winnr) then return end @@ -569,7 +523,7 @@ function Explorer:get_node_at_cursor() return end - if cursor[1] == 1 and self.view:is_root_folder_visible(core.get_cwd()) then + if cursor[1] == 1 and view.is_root_folder_visible(core.get_cwd()) then return self end @@ -603,18 +557,6 @@ function Explorer:get_nodes() return self:clone() end ----Log a lifecycle message with uid_explorer and absolute_path ----@param msg string? -function Explorer:log_new(msg) - log.line("dev", "+ %-15s %d %s", msg, self.uid_explorer, self.absolute_path) -end - ----Log a lifecycle message with uid_explorer and absolute_path ----@param msg string? -function Explorer:log_destroy(msg) - log.line("dev", "- %-15s %d %s", msg, self.uid_explorer, self.absolute_path) -end - function Explorer:setup(opts) config = opts end diff --git a/lua/nvim-tree/explorer/live-filter.lua b/lua/nvim-tree/explorer/live-filter.lua index 9bd24406964..62a7dd9ef64 100644 --- a/lua/nvim-tree/explorer/live-filter.lua +++ b/lua/nvim-tree/explorer/live-filter.lua @@ -1,3 +1,4 @@ +local view = require("nvim-tree.view") local utils = require("nvim-tree.utils") local Class = require("nvim-tree.classic") @@ -20,18 +21,12 @@ local LiveFilter = Class:extend() ---@protected ---@param args LiveFilterArgs function LiveFilter:new(args) - args.explorer:log_new("LiveFilter") - self.explorer = args.explorer self.prefix = self.explorer.opts.live_filter.prefix self.always_show_folders = self.explorer.opts.live_filter.always_show_folders self.filter = nil end -function LiveFilter:destroy() - self.explorer:log_destroy("LiveFilter") -end - ---@param node_ Node? local function reset_filter(self, node_) node_ = node_ or self.explorer @@ -61,14 +56,14 @@ local overlay_bufnr = 0 local overlay_winnr = 0 local function remove_overlay(self) - if self.explorer.view.float.enable and self.explorer.view.float.quit_on_focus_loss then + if view.View.float.enable and view.View.float.quit_on_focus_loss then -- return to normal nvim-tree float behaviour when filter window is closed vim.api.nvim_create_autocmd("WinLeave", { pattern = "NvimTree_*", group = vim.api.nvim_create_augroup("NvimTree", { clear = false }), callback = function() if utils.is_nvim_tree_buf(0) then - self.explorer.view:close() + view.close() end end, }) @@ -161,7 +156,7 @@ end ---@return integer local function calculate_overlay_win_width(self) - local wininfo = vim.fn.getwininfo(self.explorer.view:get_winnr())[1] + local wininfo = vim.fn.getwininfo(view.get_winnr())[1] if wininfo then return wininfo.width - wininfo.textoff - #self.prefix @@ -171,7 +166,7 @@ local function calculate_overlay_win_width(self) end local function create_overlay(self) - if self.explorer.view.float.enable then + if view.View.float.enable then -- don't close nvim-tree float when focus is changed to filter window vim.api.nvim_clear_autocmds({ event = "WinLeave", @@ -203,13 +198,13 @@ local function create_overlay(self) end function LiveFilter:start_filtering() - self.explorer.view.live_filter.prev_focused_node = self.explorer:get_node_at_cursor() + view.View.live_filter.prev_focused_node = self.explorer:get_node_at_cursor() self.filter = self.filter or "" self.explorer.renderer:draw() local row = require("nvim-tree.core").get_nodes_starting_line() - 1 local col = #self.prefix > 0 and #self.prefix - 1 or 1 - self.explorer.view:set_cursor({ row, col }) + view.set_cursor({ row, col }) -- needs scheduling to let the cursor move before initializing the window vim.schedule(function() return create_overlay(self) @@ -218,7 +213,7 @@ end function LiveFilter:clear_filter() local node = self.explorer:get_node_at_cursor() - local last_node = self.explorer.view.live_filter.prev_focused_node + local last_node = view.View.live_filter.prev_focused_node self.filter = nil reset_filter(self) diff --git a/lua/nvim-tree/explorer/sorter.lua b/lua/nvim-tree/explorer/sorter.lua index 4e69d36eac6..799cfa481b1 100644 --- a/lua/nvim-tree/explorer/sorter.lua +++ b/lua/nvim-tree/explorer/sorter.lua @@ -19,15 +19,9 @@ local Sorter = Class:extend() ---@protected ---@param args SorterArgs function Sorter:new(args) - args.explorer:log_new("Sorter") - self.explorer = args.explorer end -function Sorter:destroy() - self.explorer:log_destroy("Sorter") -end - ---Create a shallow copy of a portion of a list. ---@param t table ---@param first integer First index, inclusive diff --git a/lua/nvim-tree/explorer/view.lua b/lua/nvim-tree/explorer/view.lua deleted file mode 100644 index 787db038701..00000000000 --- a/lua/nvim-tree/explorer/view.lua +++ /dev/null @@ -1,799 +0,0 @@ -local appearance = require("nvim-tree.appearance") -local events = require("nvim-tree.events") -local utils = require("nvim-tree.utils") -local log = require("nvim-tree.log") -local notify = require("nvim-tree.notify") -local globals = require("nvim-tree.globals") - -local Class = require("nvim-tree.classic") - ----@class OpenInWinOpts ----@field hijack_current_buf boolean|nil default true ----@field resize boolean|nil default true ----@field winid number|nil 0 or nil for current - -local DEFAULT_MIN_WIDTH = 30 -local DEFAULT_MAX_WIDTH = -1 -local DEFAULT_PADDING = 1 - ----@class (exact) View: Class ----@field live_filter table ----@field side string ----@field float table ----@field private explorer Explorer ----@field private adaptive_size boolean ----@field private centralize_selection boolean ----@field private hide_root_folder boolean ----@field private winopts table ----@field private height integer ----@field private preserve_window_proportions boolean ----@field private initial_width integer ----@field private width (fun():integer)|integer|string ----@field private max_width integer ----@field private padding integer ----@field private bufnr_by_tab table stored per tab until multi-instance is complete -local View = Class:extend() - ----@class View ----@overload fun(args: ViewArgs): View - ----@class (exact) ViewArgs ----@field explorer Explorer - ----@protected ----@param args ViewArgs -function View:new(args) - args.explorer:log_new("View") - - self.explorer = args.explorer - self.adaptive_size = false - self.centralize_selection = self.explorer.opts.view.centralize_selection - self.float = self.explorer.opts.view.float - self.height = self.explorer.opts.view.height - self.hide_root_folder = self.explorer.opts.renderer.root_folder_label == false - self.preserve_window_proportions = self.explorer.opts.view.preserve_window_proportions - self.side = (self.explorer.opts.view.side == "right") and "right" or "left" - self.live_filter = { prev_focused_node = nil, } - self.bufnr_by_tab = {} - - self.winopts = { - relativenumber = self.explorer.opts.view.relativenumber, - number = self.explorer.opts.view.number, - list = false, - foldenable = false, - winfixwidth = true, - winfixheight = true, - spell = false, - signcolumn = self.explorer.opts.view.signcolumn, - foldmethod = "manual", - foldcolumn = "0", - cursorcolumn = false, - cursorline = self.explorer.opts.view.cursorline, - cursorlineopt = self.explorer.opts.view.cursorlineopt, - colorcolumn = "0", - wrap = false, - winhl = appearance.WIN_HL, - } - - self:configure_width(self.explorer.opts.view.width) - self.initial_width = self:get_width() -end - -function View:destroy() - self.explorer:log_destroy("View") -end - --- The initial state of a tab -local tabinitial = { - -- The position of the cursor { line, column } - cursor = { 0, 0 }, - -- The NvimTree window number - winnr = nil, -} - ----@type { name: string, value: any }[] -local BUFFER_OPTIONS = { - { name = "bufhidden", value = "wipe" }, - { name = "buflisted", value = false }, - { name = "buftype", value = "nofile" }, - { name = "filetype", value = "NvimTree" }, - { name = "modifiable", value = false }, - { name = "swapfile", value = false }, -} - ----@private ----@param bufnr integer ----@return boolean -function View:matches_bufnr(bufnr) - for _, b in pairs(globals.BUFNR_PER_TAB) do - if b == bufnr then - return true - end - end - return false -end - ----@private -function View:wipe_rogue_buffer() - for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do - if not self:matches_bufnr(bufnr) and utils.is_nvim_tree_buf(bufnr) then - pcall(vim.api.nvim_buf_delete, bufnr, { force = true }) - end - end -end - ----@private ----@param bufnr integer|false|nil -function View:create_buffer(bufnr) - self:wipe_rogue_buffer() - - local tab = vim.api.nvim_get_current_tabpage() - globals.BUFNR_PER_TAB[tab] = bufnr or vim.api.nvim_create_buf(false, false) - - if self.explorer.opts.experimental.multi_instance then - self.bufnr_by_tab[tab] = globals.BUFNR_PER_TAB[tab] - end - - vim.api.nvim_buf_set_name(self:get_bufnr("View:create_buffer1"), "NvimTree_" .. tab) - - bufnr = self:get_bufnr("View:create_buffer2") - for _, option in ipairs(BUFFER_OPTIONS) do - vim.api.nvim_set_option_value(option.name, option.value, { buf = bufnr }) - end - - require("nvim-tree.keymap").on_attach(self:get_bufnr("View:create_buffer3")) - - events._dispatch_tree_attached_post(self:get_bufnr("View:create_buffer4")) -end - ----@private ----@param size (fun():integer)|integer|string ----@return integer -function View:get_size(size) - if type(size) == "number" then - return size - elseif type(size) == "function" then - return self:get_size(size()) - end - local size_as_number = tonumber(size:sub(0, -2)) - local percent_as_decimal = size_as_number / 100 - return math.floor(vim.o.columns * percent_as_decimal) -end - ----@param size (fun():integer)|integer|nil ----@return integer -function View:get_width(size) - if size then - return self:get_size(size) - else - return self:get_size(self.width) - end -end - -local move_tbl = { - left = "H", - right = "L", -} - --- setup_tabpage sets up the initial state of a tab ----@private ----@param tabpage integer ----@param callsite string -function View:setup_tabpage(tabpage, callsite) - local winnr = vim.api.nvim_get_current_win() - - if self.explorer.opts.experimental.multi_instance then - log.line("dev", "View:setup_tabpage(%3s, %-20.20s) w%d %s", - tabpage, - callsite, - winnr, - globals.TABPAGES[tabpage] and vim.inspect(globals.TABPAGES[tabpage], { newline = "" }) or "tabinitial") - end - - globals.TABPAGES[tabpage] = vim.tbl_extend("force", globals.TABPAGES[tabpage] or tabinitial, { winnr = winnr }) -end - ----@private -function View:set_window_options_and_buffer() - pcall(vim.api.nvim_command, "buffer " .. self:get_bufnr("View:set_window_options_and_buffer")) - - if vim.fn.has("nvim-0.10") == 1 then - local eventignore = vim.api.nvim_get_option_value("eventignore", {}) - vim.api.nvim_set_option_value("eventignore", "all", {}) - - for k, v in pairs(self.winopts) do - vim.api.nvim_set_option_value(k, v, { scope = "local" }) - end - - vim.api.nvim_set_option_value("eventignore", eventignore, {}) - else - local eventignore = vim.api.nvim_get_option("eventignore") ---@diagnostic disable-line: deprecated - vim.api.nvim_set_option("eventignore", "all") ---@diagnostic disable-line: deprecated - - -- #3009 vim.api.nvim_win_set_option does not set local scope without explicit winid. - -- Revert to opt_local instead of propagating it through for just the 0.10 path. - for k, v in pairs(self.winopts) do - vim.opt_local[k] = v - end - - vim.api.nvim_set_option("eventignore", eventignore) ---@diagnostic disable-line: deprecated - end -end - ----@private ----@return table -function View:open_win_config() - if type(self.float.open_win_config) == "function" then - return self.float.open_win_config() - else - return self.float.open_win_config - end -end - ----@private -function View:open_window() - if self.float.enable then - vim.api.nvim_open_win(0, true, self:open_win_config()) - else - vim.api.nvim_command("vsp") - self:reposition_window() - end - self:setup_tabpage(vim.api.nvim_get_current_tabpage(), "View:open_window") - self:set_window_options_and_buffer() -end - ----@param buf integer ----@return boolean -local function is_buf_displayed(buf) - return vim.api.nvim_buf_is_valid(buf) and vim.fn.buflisted(buf) == 1 -end - ----@return number|nil -local function get_alt_or_next_buf() - local alt_buf = vim.fn.bufnr("#") - if is_buf_displayed(alt_buf) then - return alt_buf - end - - for _, buf in ipairs(vim.api.nvim_list_bufs()) do - if is_buf_displayed(buf) then - return buf - end - end -end - -local function switch_buf_if_last_buf() - if #vim.api.nvim_list_wins() == 1 then - local buf = get_alt_or_next_buf() - if buf then - vim.cmd("sb" .. buf) - else - vim.cmd("new") - end - end -end - ----save_tab_state saves any state that should be preserved across redraws. ----@private ----@param tabnr integer -function View:save_tab_state(tabnr) - local tabpage = tabnr or vim.api.nvim_get_current_tabpage() - globals.CURSORS[tabpage] = vim.api.nvim_win_get_cursor(self:get_winnr(tabpage, "View:save_tab_state") or 0) -end - ----@private ----@param tabpage integer -function View:close_internal(tabpage) - if self.explorer.opts.experimental.multi_instance then - log.line("dev", "View:close_internal(t%s)", tabpage) - end - if not self:is_visible({ tabpage = tabpage }) then - return - end - self:save_tab_state(tabpage) - switch_buf_if_last_buf() - local tree_win = self:get_winnr(tabpage, "View:close_internal") - local current_win = vim.api.nvim_get_current_win() - for _, win in pairs(vim.api.nvim_tabpage_list_wins(tabpage)) do - if vim.api.nvim_win_get_config(win).relative == "" then - local prev_win = vim.fn.winnr("#") -- this tab only - if tree_win == current_win and prev_win > 0 then - vim.api.nvim_set_current_win(vim.fn.win_getid(prev_win)) - end - if vim.api.nvim_win_is_valid(tree_win or 0) then - if self.explorer.opts.experimental.multi_instance then - log.line("dev", "View:close_internal(t%s) w%s", tabpage, tree_win) - end - local success, error = pcall(vim.api.nvim_win_close, tree_win or 0, true) - if not success then - notify.debug("Failed to close window: " .. error) - return - end - end - return - end - end -end - -function View:close_this_tab_only() - self:close_internal(vim.api.nvim_get_current_tabpage()) -end - -function View:close_all_tabs() - for tabpage, _ in pairs(globals.TABPAGES) do - self:close_internal(tabpage) - end -end - ----@param tabpage integer|nil ----@param callsite string -function View:close(tabpage, callsite) - if self.explorer.opts.experimental.multi_instance then - log.line("dev", "View:close(t%s, %s)", tabpage, callsite) - end - - if self.explorer.opts.tab.sync.close then - self:close_all_tabs() - elseif tabpage then - self:close_internal(tabpage) - else - self:close_this_tab_only() - end -end - ----@param options table|nil -function View:open(options) - if self:is_visible() then - return - end - - local profile = log.profile_start("view open") - - events._dispatch_on_tree_pre_open() - self:create_buffer() - self:open_window() - self:resize() - - local opts = options or { focus_tree = true } - if not opts.focus_tree then - vim.cmd("wincmd p") - end - events._dispatch_on_tree_open() - - log.profile_end(profile) -end - ----@private -function View:grow() - local starts_at = self:is_root_folder_visible(require("nvim-tree.core").get_cwd()) and 1 or 0 - local lines = vim.api.nvim_buf_get_lines(self:get_bufnr("View:grow1"), starts_at, -1, false) - -- number of columns of right-padding to indicate end of path - local padding = self:get_size(self.padding) - - -- account for sign/number columns etc. - local wininfo = vim.fn.getwininfo(self:get_winnr(nil, "View:grow")) - if type(wininfo) == "table" and type(wininfo[1]) == "table" then - padding = padding + wininfo[1].textoff - end - - local resizing_width = self.initial_width - padding - local max_width - - -- maybe bound max - if self.max_width == -1 then - max_width = -1 - else - max_width = self:get_width(self.max_width) - padding - end - - local ns_id = vim.api.nvim_get_namespaces()["NvimTreeExtmarks"] - for line_nr, l in pairs(lines) do - local count = vim.fn.strchars(l) - -- also add space for right-aligned icons - local extmarks = vim.api.nvim_buf_get_extmarks(self:get_bufnr("View:grow2"), ns_id, { line_nr, 0 }, { line_nr, -1 }, { details = true }) - count = count + utils.extmarks_length(extmarks) - if resizing_width < count then - resizing_width = count - end - if self.adaptive_size and max_width >= 0 and resizing_width >= max_width then - resizing_width = max_width - break - end - end - self:resize(resizing_width + padding) -end - -function View:grow_from_content() - if self.adaptive_size then - self:grow() - end -end - ----@param size string|number|nil -function View:resize(size) - if self.float.enable and not self.adaptive_size then - -- if the floating windows's adaptive size is not desired, then the - -- float size should be defined in view.float.open_win_config - return - end - - if type(size) == "string" then - size = vim.trim(size) - local first_char = size:sub(1, 1) - size = tonumber(size) - - if first_char == "+" or first_char == "-" then - size = self.width + size - end - end - - if type(size) == "number" and size <= 0 then - return - end - - if size then - self.width = size - self.height = size - end - - if not self:is_visible() then - return - end - - local winnr = self:get_winnr(nil, "View:resize") or 0 - - local new_size = self:get_width() - - if new_size ~= vim.api.nvim_win_get_width(winnr) then - vim.api.nvim_win_set_width(winnr, new_size) - if not self.preserve_window_proportions then - vim.cmd(":wincmd =") - end - end - - events._dispatch_on_tree_resize(new_size) -end - ----@private -function View:reposition_window() - local move_to = move_tbl[self.side] - vim.api.nvim_command("wincmd " .. move_to) - self:resize() -end - ----@private ----@param callsite string -function View:set_current_win(callsite) - local current_tab = vim.api.nvim_get_current_tabpage() - local current_win = vim.api.nvim_get_current_win() - - if self.explorer.opts.experimental.multi_instance then - log.line("dev", "View:set_current_win(%-20.20s) t%d w%3s->w%3s %s", - callsite, - current_tab, - globals.TABPAGES[current_tab].winnr, - current_win, - (globals.TABPAGES[current_tab].winnr == current_win) and "" or "MISMATCH" - ) - end - - globals.TABPAGES[current_tab].winnr = current_win -end - ----Open the tree in the a window ----@param opts OpenInWinOpts|nil -function View:open_in_win(opts) - opts = opts or { hijack_current_buf = true, resize = true } - events._dispatch_on_tree_pre_open() - if opts.winid and vim.api.nvim_win_is_valid(opts.winid) then - vim.api.nvim_set_current_win(opts.winid) - end - self:create_buffer(opts.hijack_current_buf and vim.api.nvim_get_current_buf()) - self:setup_tabpage(vim.api.nvim_get_current_tabpage(), "View:open_in_win") - self:set_current_win("View:open_in_win") - self:set_window_options_and_buffer() - if opts.resize then - self:reposition_window() - self:resize() - end - events._dispatch_on_tree_open() -end - -function View:abandon_current_window() - local tab = vim.api.nvim_get_current_tabpage() - - if self.explorer.opts.experimental.multi_instance then - log.line("dev", "View:abandon_current_window() t%d w%s b%s member b%s %s", - tab, - globals.TABPAGES[tab] and globals.TABPAGES[tab].winnr or nil, - globals.BUFNR_PER_TAB[tab], - self.bufnr_by_tab[tab], - (globals.BUFNR_PER_TAB[tab] == self.bufnr_by_tab[tab]) and "" or "MISMATCH") - - self.bufnr_by_tab[tab] = nil - end - - -- TODO multi-instance kill the buffer instead of retaining - - globals.BUFNR_PER_TAB[tab] = nil - if globals.TABPAGES[tab] then - globals.TABPAGES[tab].winnr = nil - end -end - ----@param callsite string -function View:abandon_all_windows(callsite) - for tab, _ in pairs(vim.api.nvim_list_tabpages()) do - if self.explorer.opts.experimental.multi_instance then - log.line("dev", "View:abandon_all_windows(%-20.20s) t%d w%s b%s member b%s %s", - callsite, - tab, - globals.TABPAGES and globals.TABPAGES.winnr or nil, - globals.BUFNR_PER_TAB[tab], - self.bufnr_by_tab[tab], - (globals.BUFNR_PER_TAB[tab] == self.bufnr_by_tab[tab]) and "" or "MISMATCH") - end - - -- TODO multi-instance kill the buffer instead of retaining - - globals.BUFNR_PER_TAB[tab] = nil - if globals.TABPAGES[tab] then - globals.TABPAGES[tab].winnr = nil - end - end -end - ----@param opts table|nil ----@return boolean -function View:is_visible(opts) - if opts and opts.tabpage then - if globals.TABPAGES[opts.tabpage] == nil then - return false - end - local winnr = globals.TABPAGES[opts.tabpage].winnr - return winnr and vim.api.nvim_win_is_valid(winnr) - end - - if opts and opts.any_tabpage then - for _, v in pairs(globals.TABPAGES) do - if v.winnr and vim.api.nvim_win_is_valid(v.winnr) then - return true - end - end - return false - end - - return self:get_winnr(nil, "View:is_visible1") ~= nil and vim.api.nvim_win_is_valid(self:get_winnr(nil, "View:is_visible2") or 0) -end - ----@param opts table|nil -function View:set_cursor(opts) - if self:is_visible() then - pcall(vim.api.nvim_win_set_cursor, self:get_winnr(nil, "View:set_cursor"), opts) - end -end - ----@param winnr number|nil ----@param open_if_closed boolean|nil -function View:focus(winnr, open_if_closed) - local wnr = winnr or self:get_winnr(nil, "View:focus1") - - if vim.api.nvim_win_get_tabpage(wnr or 0) ~= vim.api.nvim_win_get_tabpage(0) then - self:close(nil, "View:focus") - self:open() - wnr = self:get_winnr(nil, "View:focus2") - elseif open_if_closed and not self:is_visible() then - self:open() - end - - if wnr then - vim.api.nvim_set_current_win(wnr) - end -end - ---- Retrieve the winid of the open tree. ----@param opts ApiTreeWinIdOpts|nil ----@return number|nil winid unlike get_winnr(), this returns nil if the nvim-tree window is not visible -function View:api_winid(opts) - local tabpage = opts and opts.tabpage - if tabpage == 0 then - tabpage = vim.api.nvim_get_current_tabpage() - end - if self:is_visible({ tabpage = tabpage }) then - return self:get_winnr(tabpage, "View:winid") - else - return nil - end -end - ---- Restores the state of a NvimTree window if it was initialized before. -function View:restore_tab_state() - local tabpage = vim.api.nvim_get_current_tabpage() - self:set_cursor(globals.CURSORS[tabpage]) -end - ---- winid containing the buffer ----@param tabpage number|nil (optional) the number of the chosen tabpage. Defaults to current tabpage. ----@param callsite string ----@return integer? winid -function View:winid(tabpage, callsite) - local bufnr = self.bufnr_by_tab[tabpage] - - local msg = string.format("View:winid(%3s, %-20.20s)", tabpage, callsite) - - if bufnr then - for _, w in pairs(vim.api.nvim_tabpage_list_wins(tabpage or 0)) do - if vim.api.nvim_win_get_buf(w) == bufnr then - log.line("dev", "%s b%d : w%s", msg, bufnr, w) - return w - end - end - else - log.line("dev", "%s no bufnr", msg) - end -end - ---- Returns the window number for nvim-tree within the tabpage specified ----@param tabpage number|nil (optional) the number of the chosen tabpage. Defaults to current tabpage. ----@param callsite string ----@return number|nil -function View:get_winnr(tabpage, callsite) - if self.explorer.opts.experimental.multi_instance then - local msg = string.format("View:get_winnr(%3s, %-20.20s)", tabpage, callsite) - - tabpage = tabpage or vim.api.nvim_get_current_tabpage() - local tabinfo = globals.TABPAGES[tabpage] - - local ret = nil - - if not tabinfo then - msg = string.format("%s t%d no tabinfo", msg, tabpage) - elseif not tabinfo.winnr then - msg = string.format("%s t%d no tabinfo.winnr", msg, tabpage) - elseif not vim.api.nvim_win_is_valid(tabinfo.winnr) then - msg = string.format("%s t%d invalid tabinfo.winnr %d", msg, tabpage, tabinfo.winnr) - else - msg = string.format("%s t%d w%d", msg, tabpage, tabinfo.winnr) - ret = tabinfo.winnr - end - - local winid = self:winid(tabpage, "View:get_winnr") - if ret ~= winid then - if ret then - msg = string.format("%s winid_from_bufnr w%s MISMATCH", msg, winid) - else - msg = string.format("%s winid_from_bufnr w%s STALE", msg, winid) - end - notify.error(string.format("View:get_winnr w%s View:winnr w%s MISMATCH", ret, winid)) - end - - log.line("dev", "%s", msg) - - return ret - else - tabpage = tabpage or vim.api.nvim_get_current_tabpage() - local tabinfo = globals.TABPAGES[tabpage] - if tabinfo and tabinfo.winnr and vim.api.nvim_win_is_valid(tabinfo.winnr) then - return tabinfo.winnr - end - end -end - ---- Returns the current nvim tree bufnr ----@param callsite string ----@return number -function View:get_bufnr(callsite) - local tab = vim.api.nvim_get_current_tabpage() - if self.explorer.opts.experimental.multi_instance then - log.line("dev", "View:get_bufnr(%-20.20s) t%d global b%s member b%s %s", - callsite, - tab, - globals.BUFNR_PER_TAB[tab], - self.bufnr_by_tab[tab], - (globals.BUFNR_PER_TAB[tab] == self.bufnr_by_tab[tab]) and "" or "MISMATCH") - - if globals.BUFNR_PER_TAB[tab] ~= self.bufnr_by_tab[tab] then - notify.error(string.format("View:get_bufnr globals.BUFNR_PER_TAB[%s] b%s view.bufnr_by_tab[%s] b%s MISMATCH", - tab, globals.BUFNR_PER_TAB[tab], - tab, self.bufnr_by_tab[tab] - )) - end - end - return globals.BUFNR_PER_TAB[tab] -end - -function View:prevent_buffer_override() - local view_winnr = self:get_winnr(nil, "View:prevent_buffer_override") - local view_bufnr = self:get_bufnr("View:prevent_buffer_override") - - -- need to schedule to let the new buffer populate the window - -- because this event needs to be run on bufWipeout. - -- Otherwise the curwin/curbuf would match the view buffer and the view window. - vim.schedule(function() - local curwin = vim.api.nvim_get_current_win() - local curwinconfig = vim.api.nvim_win_get_config(curwin) - local curbuf = vim.api.nvim_win_get_buf(curwin) - local bufname = vim.api.nvim_buf_get_name(curbuf) - - if not bufname:match("NvimTree") then - for i, tabpage in ipairs(globals.TABPAGES) do - if tabpage.winnr == view_winnr then - if self.explorer.opts.experimental.multi_instance then - log.line("dev", "View:prevent_buffer_override() t%d w%d clearing", i, view_winnr) - end - - globals.TABPAGES[i] = nil - break - end - end - end - if curwin ~= view_winnr or bufname == "" or curbuf == view_bufnr then - return - end - - -- patch to avoid the overriding window to be fixed in size - -- might need a better patch - vim.cmd("setlocal nowinfixwidth") - vim.cmd("setlocal nowinfixheight") - self:open({ focus_tree = false }) - - local explorer = require("nvim-tree.core").get_explorer() - if explorer then - explorer.renderer:draw() - end - - pcall(vim.api.nvim_win_close, curwin, { force = true }) - - -- to handle opening a file using :e when nvim-tree is on floating mode - -- falling back to the current window instead of creating a new one - if curwinconfig.relative ~= "" then - require("nvim-tree.actions.node.open-file").fn("edit_in_place", bufname) - else - require("nvim-tree.actions.node.open-file").fn("edit", bufname) - end - end) -end - ----@param cwd string|nil ----@return boolean -function View:is_root_folder_visible(cwd) - return cwd ~= "/" and not self.hide_root_folder -end - --- used on ColorScheme event -function View:reset_winhl() - local winnr = self:get_winnr(nil, "View:reset_winhl1") - if winnr and vim.api.nvim_win_is_valid(winnr) then - vim.wo[self:get_winnr(nil, "View:reset_winhl2")].winhl = appearance.WIN_HL - end -end - ----Check if width determined or calculated on-fly ----@return boolean -function View:is_width_determined() - return type(self.width) ~= "function" -end - ----Configure width-related config ----@param width string|function|number|table|nil -function View:configure_width(width) - if type(width) == "table" then - self.adaptive_size = true - self.width = width.min or DEFAULT_MIN_WIDTH - self.max_width = width.max or DEFAULT_MAX_WIDTH - self.padding = width.padding or DEFAULT_PADDING - elseif width == nil then - if self.explorer.opts.view.width ~= nil then - -- if we had input config - fallback to it - self:configure_width(self.explorer.opts.view.width) - else - -- otherwise - restore initial width - self.width = self.initial_width - end - else - self.adaptive_size = false - self.width = width - end -end - -return View diff --git a/lua/nvim-tree/globals.lua b/lua/nvim-tree/globals.lua deleted file mode 100644 index 58e3452f7ee..00000000000 --- a/lua/nvim-tree/globals.lua +++ /dev/null @@ -1,10 +0,0 @@ --- global state, to be refactored away during multi-instance - -local M = { - -- from View - TABPAGES = {}, - BUFNR_PER_TAB = {}, - CURSORS = {}, -} - -return M diff --git a/lua/nvim-tree/help.lua b/lua/nvim-tree/help.lua index 34f6db2d836..a5a0dbd6b4a 100644 --- a/lua/nvim-tree/help.lua +++ b/lua/nvim-tree/help.lua @@ -1,4 +1,3 @@ -local appearance = require("nvim-tree.appearance") local keymap = require("nvim-tree.keymap") local api = {} -- circular dependency @@ -6,6 +5,12 @@ local PAT_MOUSE = "^<.*Mouse" local PAT_CTRL = "^ local types = {} diff --git a/lua/nvim-tree/marks/init.lua b/lua/nvim-tree/marks/init.lua index e0fe9a3086e..c940f999983 100644 --- a/lua/nvim-tree/marks/init.lua +++ b/lua/nvim-tree/marks/init.lua @@ -25,17 +25,11 @@ local Marks = Class:extend() ---@protected ---@param args MarksArgs function Marks:new(args) - args.explorer:log_new("Marks") - self.explorer = args.explorer self.marks = {} end -function Marks:destroy() - self.explorer:log_destroy("Marks") -end - ---Clear all marks and reload if watchers disabled ---@private function Marks:clear_reload() diff --git a/lua/nvim-tree/multi-instance-debug.lua b/lua/nvim-tree/multi-instance-debug.lua deleted file mode 100644 index 5b4604837cd..00000000000 --- a/lua/nvim-tree/multi-instance-debug.lua +++ /dev/null @@ -1,112 +0,0 @@ -local globals = require("nvim-tree.globals") - -local M = {} - ---- Debugging only. ---- Tabs show TABPAGES winnr and BUFNR_PER_TAB bufnr for the tab. ---- Orphans for inexistent tab_ids are shown at the right. ---- lib.target_winid is always shown at the right next to a close button. ---- Enable with: ---- vim.opt.tabline = "%!v:lua.require('nvim-tree.explorer.view').tab_line()" ---- vim.opt.showtabline = 2 ----@return string -function M.tab_line() - local tab_ids = vim.api.nvim_list_tabpages() - local cur_tab_id = vim.api.nvim_get_current_tabpage() - - local bufnr_per_tab = vim.deepcopy(globals.BUFNR_PER_TAB) - local tabpages = vim.deepcopy(globals.TABPAGES) - - local tl = "%#TabLine#" - - for i, tab_id in ipairs(tab_ids) do - -- click to select - tl = tl .. "%" .. i .. "T" - - -- style - if tab_id == cur_tab_id then - tl = tl .. "%#StatusLine#|" - else - tl = tl .. "|%#TabLine#" - end - - -- tab_id itself - tl = tl .. " t" .. tab_id - - -- winnr, if present - local tp = globals.TABPAGES[tab_id] - if tp then - tl = tl .. " w" .. (tp.winnr or "nil") - else - tl = tl .. " " - end - - -- bufnr, if present - local bpt = globals.BUFNR_PER_TAB[tab_id] - if bpt then - tl = tl .. " b" .. bpt - else - tl = tl .. " " - end - - tl = tl .. " " - - -- remove actively mapped - bufnr_per_tab[tab_id] = nil - tabpages[tab_id] = nil - end - - -- close last and reset - tl = tl .. "|%#CursorLine#%T" - - -- collect orphans - local orphans = {} - for tab_id, bufnr in pairs(bufnr_per_tab) do - orphans[tab_id] = orphans[tab_id] or {} - orphans[tab_id].bufnr = bufnr - end - for tab_id, tp in pairs(tabpages) do - orphans[tab_id] = orphans[tab_id] or {} - orphans[tab_id].winnr = tp.winnr - end - - -- right-align - tl = tl .. "%=%#TabLine#" - - -- print orphans - for tab_id, orphan in pairs(orphans) do - -- inexistent tab - tl = tl .. "%#error#| t" .. tab_id - - -- maybe winnr - if orphan.winnr then - tl = tl .. " w" .. (orphan.winnr or "nil") - else - tl = tl .. " " - end - - -- maybe bufnr - if orphan.bufnr then - tl = tl .. " b" .. orphan.bufnr - else - tl = tl .. " " - end - tl = tl .. " " - end - - -- target win id and close button - tl = tl .. "|%#TabLine# twi" .. (require("nvim-tree.lib").target_winid or "?") .. " %999X| X |" - - return tl -end - -function M.setup(opts) - if not opts.experimental.multi_instance then - return - end - - vim.opt.tabline = "%!v:lua.require('nvim-tree.multi-instance-debug').tab_line()" - vim.opt.showtabline = 2 -end - -return M diff --git a/lua/nvim-tree/renderer/builder.lua b/lua/nvim-tree/renderer/builder.lua index d2727f3f4ac..819660e5306 100644 --- a/lua/nvim-tree/renderer/builder.lua +++ b/lua/nvim-tree/renderer/builder.lua @@ -1,5 +1,6 @@ local notify = require("nvim-tree.notify") local utils = require("nvim-tree.utils") +local view = require("nvim-tree.view") local Class = require("nvim-tree.classic") @@ -378,7 +379,7 @@ end ---@private function Builder:build_header() - if self.explorer.view:is_root_folder_visible(self.explorer.absolute_path) then + if view.is_root_folder_visible(self.explorer.absolute_path) then local root_name = self:format_root_name(self.explorer.opts.renderer.root_folder_label) table.insert(self.lines, root_name) self:insert_highlight({ "NvimTreeRootFolder" }, 0, string.len(root_name)) diff --git a/lua/nvim-tree/renderer/components/full-name.lua b/lua/nvim-tree/renderer/components/full-name.lua index 0dc18f47d30..6c41f3909d9 100644 --- a/lua/nvim-tree/renderer/components/full-name.lua +++ b/lua/nvim-tree/renderer/components/full-name.lua @@ -1,8 +1,8 @@ -local appearance = require("nvim-tree.appearance") -local utils = require("nvim-tree.utils") - local M = {} +local utils = require("nvim-tree.utils") +local view = require("nvim-tree.view") + local function hide(win) if win then if vim.api.nvim_win_is_valid(win) then @@ -72,7 +72,7 @@ local function show(opts) style = "minimal", border = "none" }) - vim.wo[M.popup_win].winhl = appearance.WIN_HL + vim.wo[M.popup_win].winhl = view.View.winopts.winhl local ns_id = vim.api.nvim_get_namespaces()["NvimTreeHighlights"] local extmarks = vim.api.nvim_buf_get_extmarks(0, ns_id, { line_nr - 1, 0 }, { line_nr - 1, -1 }, { details = true }) diff --git a/lua/nvim-tree/renderer/init.lua b/lua/nvim-tree/renderer/init.lua index f782973726a..30af27e81b9 100644 --- a/lua/nvim-tree/renderer/init.lua +++ b/lua/nvim-tree/renderer/init.lua @@ -1,4 +1,5 @@ local log = require("nvim-tree.log") +local view = require("nvim-tree.view") local events = require("nvim-tree.events") local Class = require("nvim-tree.classic") @@ -25,15 +26,9 @@ local Renderer = Class:extend() ---@protected ---@param args RendererArgs function Renderer:new(args) - args.explorer:log_new("Renderer") - self.explorer = args.explorer end -function Renderer:destroy() - self.explorer:log_destroy("Renderer") -end - ---@private ---@param bufnr number ---@param lines string[] @@ -101,28 +96,28 @@ function Renderer:render_hl(bufnr, hl_range_args) end function Renderer:draw() - local bufnr = self.explorer.view:get_bufnr("Renderer:draw") + local bufnr = view.get_bufnr() if not bufnr or not vim.api.nvim_buf_is_loaded(bufnr) then return end local profile = log.profile_start("draw") - local cursor = vim.api.nvim_win_get_cursor(self.explorer.view:get_winnr(nil, "Renderer:draw1") or 0) + local cursor = vim.api.nvim_win_get_cursor(view.get_winnr() or 0) local builder = Builder(self.explorer):build() self:_draw(bufnr, builder.lines, builder.hl_range_args, builder.signs, builder.extmarks, builder.virtual_lines) if cursor and #builder.lines >= cursor[1] then - vim.api.nvim_win_set_cursor(self.explorer.view:get_winnr(nil, "Renderer:draw2") or 0, cursor) + vim.api.nvim_win_set_cursor(view.get_winnr() or 0, cursor) end - self.explorer.view:grow_from_content() + view.grow_from_content() log.profile_end(profile) - events._dispatch_on_tree_rendered(bufnr, self.explorer.view:get_winnr(nil, "Renderer:draw3")) + events._dispatch_on_tree_rendered(bufnr, view.get_winnr()) end return Renderer diff --git a/lua/nvim-tree/utils.lua b/lua/nvim-tree/utils.lua index 77c39e6d4ca..9396ba9d52f 100644 --- a/lua/nvim-tree/utils.lua +++ b/lua/nvim-tree/utils.lua @@ -143,16 +143,10 @@ function M.find_node(nodes, fn) return node.group_next and { node.group_next } or (node.open and #node.nodes > 0 and node.nodes) end) :iterate() - - if node then - if not node.explorer.view:is_root_folder_visible() then - i = i - 1 - end - if node.explorer.live_filter.filter then - i = i + 1 - end + i = require("nvim-tree.view").is_root_folder_visible() and i or i - 1 + if node and node.explorer.live_filter.filter then + i = i + 1 end - return node, i end @@ -543,10 +537,7 @@ function M.focus_file(path) local _, i = M.find_node(require("nvim-tree.core").get_explorer().nodes, function(node) return node.absolute_path == path end) - local explorer = require("nvim-tree.core").get_explorer() - if explorer then - explorer.view:set_cursor({ i + 1, 1 }) - end + require("nvim-tree.view").set_cursor({ i + 1, 1 }) end ---Focus node passed as parameter if visible, otherwise focus first visible parent. @@ -566,7 +557,7 @@ function M.focus_node_or_parent(node) end) if found_node or node.parent == nil then - explorer.view:set_cursor({ i + 1, 1 }) + require("nvim-tree.view").set_cursor({ i + 1, 1 }) break end @@ -661,29 +652,32 @@ function M.is_executable(absolute_path) end end ----@class UtilEnumerateOptionsOpts ----@field keyset_opts vim.api.keyset.option ----@field was_set boolean? as per vim.api.keyset.get_option_info +---List of all option info/values +---@param opts vim.api.keyset.option passed directly to vim.api.nvim_get_option_info2 and vim.api.nvim_get_option_value +---@param was_set boolean filter was_set +---@return { info: vim.api.keyset.get_option_info, val: any }[] +function M.enumerate_options(opts, was_set) + local res = {} ----Option name/values ----@param opts UtilEnumerateOptionsOpts ----@return table -function M.enumerate_options(opts) - -- enumerate all options, limiting buf and win scopes - return vim.tbl_map(function(info) - if opts.keyset_opts.buf and info.scope ~= "buf" then - return nil - elseif opts.keyset_opts.win and info.scope ~= "win" then - return nil + local infos = vim.tbl_filter(function(info) + if opts.buf and info.scope ~= "buf" then + return false + elseif opts.win and info.scope ~= "win" then + return false else - -- optional, lazy was_set check - if not opts.was_set or vim.api.nvim_get_option_info2(info.name, opts.keyset_opts).was_set then - return vim.api.nvim_get_option_value(info.name, opts.keyset_opts) - else - return nil - end + return true end end, vim.api.nvim_get_all_options_info()) + + for _, info in vim.spairs(infos) do + local _, info2 = pcall(vim.api.nvim_get_option_info2, info.name, opts) + if not was_set or info2.was_set then + local val = pcall(vim.api.nvim_get_option_value, info.name, opts) + table.insert(res, { info = info2, val = val }) + end + end + + return res end return M diff --git a/lua/nvim-tree/view.lua b/lua/nvim-tree/view.lua new file mode 100644 index 00000000000..4ce95bfb6f4 --- /dev/null +++ b/lua/nvim-tree/view.lua @@ -0,0 +1,639 @@ +local events = require("nvim-tree.events") +local utils = require("nvim-tree.utils") +local log = require("nvim-tree.log") +local notify = require("nvim-tree.notify") + +---@class OpenInWinOpts +---@field hijack_current_buf boolean|nil default true +---@field resize boolean|nil default true +---@field winid number|nil 0 or nil for current + +local M = {} + +local DEFAULT_MIN_WIDTH = 30 +local DEFAULT_MAX_WIDTH = -1 +local DEFAULT_PADDING = 1 + +M.View = { + adaptive_size = false, + centralize_selection = false, + tabpages = {}, + cursors = {}, + hide_root_folder = false, + live_filter = { + prev_focused_node = nil, + }, + winopts = { + relativenumber = false, + number = false, + list = false, + foldenable = false, + winfixwidth = true, + winfixheight = true, + spell = false, + signcolumn = "yes", + foldmethod = "manual", + foldcolumn = "0", + cursorcolumn = false, + cursorline = true, + cursorlineopt = "both", + colorcolumn = "0", + wrap = false, + winhl = table.concat({ + "EndOfBuffer:NvimTreeEndOfBuffer", + "CursorLine:NvimTreeCursorLine", + "CursorLineNr:NvimTreeCursorLineNr", + "LineNr:NvimTreeLineNr", + "WinSeparator:NvimTreeWinSeparator", + "StatusLine:NvimTreeStatusLine", + "StatusLineNC:NvimTreeStatuslineNC", + "SignColumn:NvimTreeSignColumn", + "Normal:NvimTreeNormal", + "NormalNC:NvimTreeNormalNC", + "NormalFloat:NvimTreeNormalFloat", + "FloatBorder:NvimTreeNormalFloatBorder", + }, ","), + }, +} + +-- The initial state of a tab +local tabinitial = { + -- The position of the cursor { line, column } + cursor = { 0, 0 }, + -- The NvimTree window number + winnr = nil, +} + +local BUFNR_PER_TAB = {} + +---@type { name: string, value: any }[] +local BUFFER_OPTIONS = { + { name = "bufhidden", value = "wipe" }, + { name = "buflisted", value = false }, + { name = "buftype", value = "nofile" }, + { name = "filetype", value = "NvimTree" }, + { name = "modifiable", value = false }, + { name = "swapfile", value = false }, +} + +---@param bufnr integer +---@return boolean +local function matches_bufnr(bufnr) + for _, b in pairs(BUFNR_PER_TAB) do + if b == bufnr then + return true + end + end + return false +end + +local function wipe_rogue_buffer() + for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do + if not matches_bufnr(bufnr) and utils.is_nvim_tree_buf(bufnr) then + pcall(vim.api.nvim_buf_delete, bufnr, { force = true }) + end + end +end + +---@param bufnr integer|boolean|nil +local function create_buffer(bufnr) + wipe_rogue_buffer() + + local tab = vim.api.nvim_get_current_tabpage() + BUFNR_PER_TAB[tab] = bufnr or vim.api.nvim_create_buf(false, false) + vim.api.nvim_buf_set_name(M.get_bufnr(), "NvimTree_" .. tab) + + bufnr = M.get_bufnr() + for _, option in ipairs(BUFFER_OPTIONS) do + vim.api.nvim_set_option_value(option.name, option.value, { buf = bufnr }) + end + + require("nvim-tree.keymap").on_attach(M.get_bufnr()) + + events._dispatch_tree_attached_post(M.get_bufnr()) +end + +---@param size (fun():integer)|integer|string +---@return integer +local function get_size(size) + if type(size) == "number" then + return size + elseif type(size) == "function" then + return get_size(size()) + end + local size_as_number = tonumber(size:sub(0, -2)) + local percent_as_decimal = size_as_number / 100 + return math.floor(vim.o.columns * percent_as_decimal) +end + +---@param size (fun():integer)|integer|nil +---@return integer +local function get_width(size) + if size then + return get_size(size) + else + return get_size(M.View.width) + end +end + +local move_tbl = { + left = "H", + right = "L", +} + +-- setup_tabpage sets up the initial state of a tab +---@param tabpage integer +local function setup_tabpage(tabpage) + local winnr = vim.api.nvim_get_current_win() + M.View.tabpages[tabpage] = vim.tbl_extend("force", M.View.tabpages[tabpage] or tabinitial, { winnr = winnr }) +end + +local function set_window_options_and_buffer() + pcall(vim.api.nvim_command, "buffer " .. M.get_bufnr()) + + if vim.fn.has("nvim-0.10") == 1 then + local eventignore = vim.api.nvim_get_option_value("eventignore", {}) + vim.api.nvim_set_option_value("eventignore", "all", {}) + + for k, v in pairs(M.View.winopts) do + vim.api.nvim_set_option_value(k, v, { scope = "local" }) + end + + vim.api.nvim_set_option_value("eventignore", eventignore, {}) + else + local eventignore = vim.api.nvim_get_option("eventignore") ---@diagnostic disable-line: deprecated + vim.api.nvim_set_option("eventignore", "all") ---@diagnostic disable-line: deprecated + + -- #3009 vim.api.nvim_win_set_option does not set local scope without explicit winid. + -- Revert to opt_local instead of propagating it through for just the 0.10 path. + for k, v in pairs(M.View.winopts) do + vim.opt_local[k] = v + end + + vim.api.nvim_set_option("eventignore", eventignore) ---@diagnostic disable-line: deprecated + end +end + +---@return table +local function open_win_config() + if type(M.View.float.open_win_config) == "function" then + return M.View.float.open_win_config() + else + return M.View.float.open_win_config + end +end + +local function open_window() + if M.View.float.enable then + vim.api.nvim_open_win(0, true, open_win_config()) + else + vim.api.nvim_command("vsp") + M.reposition_window() + end + setup_tabpage(vim.api.nvim_get_current_tabpage()) + set_window_options_and_buffer() +end + +---@param buf integer +---@return boolean +local function is_buf_displayed(buf) + return vim.api.nvim_buf_is_valid(buf) and vim.fn.buflisted(buf) == 1 +end + +---@return number|nil +local function get_alt_or_next_buf() + local alt_buf = vim.fn.bufnr("#") + if is_buf_displayed(alt_buf) then + return alt_buf + end + + for _, buf in ipairs(vim.api.nvim_list_bufs()) do + if is_buf_displayed(buf) then + return buf + end + end +end + +local function switch_buf_if_last_buf() + if #vim.api.nvim_list_wins() == 1 then + local buf = get_alt_or_next_buf() + if buf then + vim.cmd("sb" .. buf) + else + vim.cmd("new") + end + end +end + +-- save_tab_state saves any state that should be preserved across redraws. +---@param tabnr integer +local function save_tab_state(tabnr) + local tabpage = tabnr or vim.api.nvim_get_current_tabpage() + M.View.cursors[tabpage] = vim.api.nvim_win_get_cursor(M.get_winnr(tabpage) or 0) +end + +---@param tabpage integer +local function close(tabpage) + if not M.is_visible({ tabpage = tabpage }) then + return + end + save_tab_state(tabpage) + switch_buf_if_last_buf() + local tree_win = M.get_winnr(tabpage) + local current_win = vim.api.nvim_get_current_win() + for _, win in pairs(vim.api.nvim_tabpage_list_wins(tabpage)) do + if vim.api.nvim_win_get_config(win).relative == "" then + local prev_win = vim.fn.winnr("#") -- this tab only + if tree_win == current_win and prev_win > 0 then + vim.api.nvim_set_current_win(vim.fn.win_getid(prev_win)) + end + if vim.api.nvim_win_is_valid(tree_win or 0) then + local success, error = pcall(vim.api.nvim_win_close, tree_win or 0, true) + if not success then + notify.debug("Failed to close window: " .. error) + return + end + end + return + end + end +end + +function M.close_this_tab_only() + close(vim.api.nvim_get_current_tabpage()) +end + +function M.close_all_tabs() + for tabpage, _ in pairs(M.View.tabpages) do + close(tabpage) + end +end + +---@param tabpage integer|nil +function M.close(tabpage) + if M.View.tab.sync.close then + M.close_all_tabs() + elseif tabpage then + close(tabpage) + else + M.close_this_tab_only() + end +end + +---@param options table|nil +function M.open(options) + if M.is_visible() then + return + end + + local profile = log.profile_start("view open") + + events._dispatch_on_tree_pre_open() + create_buffer() + open_window() + M.resize() + + local opts = options or { focus_tree = true } + if not opts.focus_tree then + vim.cmd("wincmd p") + end + events._dispatch_on_tree_open() + + log.profile_end(profile) +end + +local function grow() + local starts_at = M.is_root_folder_visible(require("nvim-tree.core").get_cwd()) and 1 or 0 + local lines = vim.api.nvim_buf_get_lines(M.get_bufnr(), starts_at, -1, false) + -- number of columns of right-padding to indicate end of path + local padding = get_size(M.View.padding) + + -- account for sign/number columns etc. + local wininfo = vim.fn.getwininfo(M.get_winnr()) + if type(wininfo) == "table" and type(wininfo[1]) == "table" then + padding = padding + wininfo[1].textoff + end + + local resizing_width = M.View.initial_width - padding + local max_width + + -- maybe bound max + if M.View.max_width == -1 then + max_width = -1 + else + max_width = get_width(M.View.max_width) - padding + end + + local ns_id = vim.api.nvim_get_namespaces()["NvimTreeExtmarks"] + for line_nr, l in pairs(lines) do + local count = vim.fn.strchars(l) + -- also add space for right-aligned icons + local extmarks = vim.api.nvim_buf_get_extmarks(M.get_bufnr(), ns_id, { line_nr, 0 }, { line_nr, -1 }, { details = true }) + count = count + utils.extmarks_length(extmarks) + if resizing_width < count then + resizing_width = count + end + if M.View.adaptive_size and max_width >= 0 and resizing_width >= max_width then + resizing_width = max_width + break + end + end + M.resize(resizing_width + padding) +end + +function M.grow_from_content() + if M.View.adaptive_size then + grow() + end +end + +---@param size string|number|nil +function M.resize(size) + if M.View.float.enable and not M.View.adaptive_size then + -- if the floating windows's adaptive size is not desired, then the + -- float size should be defined in view.float.open_win_config + return + end + + if type(size) == "string" then + size = vim.trim(size) + local first_char = size:sub(1, 1) + size = tonumber(size) + + if first_char == "+" or first_char == "-" then + size = M.View.width + size + end + end + + if type(size) == "number" and size <= 0 then + return + end + + if size then + M.View.width = size + M.View.height = size + end + + if not M.is_visible() then + return + end + + local winnr = M.get_winnr() or 0 + + local new_size = get_width() + + if new_size ~= vim.api.nvim_win_get_width(winnr) then + vim.api.nvim_win_set_width(winnr, new_size) + if not M.View.preserve_window_proportions then + vim.cmd(":wincmd =") + end + end + + events._dispatch_on_tree_resize(new_size) +end + +function M.reposition_window() + local move_to = move_tbl[M.View.side] + vim.api.nvim_command("wincmd " .. move_to) + M.resize() +end + +local function set_current_win() + local current_tab = vim.api.nvim_get_current_tabpage() + M.View.tabpages[current_tab].winnr = vim.api.nvim_get_current_win() +end + +---Open the tree in the a window +---@param opts OpenInWinOpts|nil +function M.open_in_win(opts) + opts = opts or { hijack_current_buf = true, resize = true } + events._dispatch_on_tree_pre_open() + if opts.winid and vim.api.nvim_win_is_valid(opts.winid) then + vim.api.nvim_set_current_win(opts.winid) + end + create_buffer(opts.hijack_current_buf and vim.api.nvim_get_current_buf()) + setup_tabpage(vim.api.nvim_get_current_tabpage()) + set_current_win() + set_window_options_and_buffer() + if opts.resize then + M.reposition_window() + M.resize() + end + events._dispatch_on_tree_open() +end + +function M.abandon_current_window() + local tab = vim.api.nvim_get_current_tabpage() + BUFNR_PER_TAB[tab] = nil + if M.View.tabpages[tab] then + M.View.tabpages[tab].winnr = nil + end +end + +function M.abandon_all_windows() + for tab, _ in pairs(vim.api.nvim_list_tabpages()) do + BUFNR_PER_TAB[tab] = nil + if M.View.tabpages[tab] then + M.View.tabpages[tab].winnr = nil + end + end +end + +---@param opts table|nil +---@return boolean +function M.is_visible(opts) + if opts and opts.tabpage then + if M.View.tabpages[opts.tabpage] == nil then + return false + end + local winnr = M.View.tabpages[opts.tabpage].winnr + return winnr and vim.api.nvim_win_is_valid(winnr) + end + + if opts and opts.any_tabpage then + for _, v in pairs(M.View.tabpages) do + if v.winnr and vim.api.nvim_win_is_valid(v.winnr) then + return true + end + end + return false + end + + return M.get_winnr() ~= nil and vim.api.nvim_win_is_valid(M.get_winnr() or 0) +end + +---@param opts table|nil +function M.set_cursor(opts) + if M.is_visible() then + pcall(vim.api.nvim_win_set_cursor, M.get_winnr(), opts) + end +end + +---@param winnr number|nil +---@param open_if_closed boolean|nil +function M.focus(winnr, open_if_closed) + local wnr = winnr or M.get_winnr() + + if vim.api.nvim_win_get_tabpage(wnr or 0) ~= vim.api.nvim_win_get_tabpage(0) then + M.close() + M.open() + wnr = M.get_winnr() + elseif open_if_closed and not M.is_visible() then + M.open() + end + + if wnr then + vim.api.nvim_set_current_win(wnr) + end +end + +--- Retrieve the winid of the open tree. +---@param opts ApiTreeWinIdOpts|nil +---@return number|nil winid unlike get_winnr(), this returns nil if the nvim-tree window is not visible +function M.winid(opts) + local tabpage = opts and opts.tabpage + if tabpage == 0 then + tabpage = vim.api.nvim_get_current_tabpage() + end + if M.is_visible({ tabpage = tabpage }) then + return M.get_winnr(tabpage) + else + return nil + end +end + +--- Restores the state of a NvimTree window if it was initialized before. +function M.restore_tab_state() + local tabpage = vim.api.nvim_get_current_tabpage() + M.set_cursor(M.View.cursors[tabpage]) +end + +--- Returns the window number for nvim-tree within the tabpage specified +---@param tabpage number|nil (optional) the number of the chosen tabpage. Defaults to current tabpage. +---@return number|nil +function M.get_winnr(tabpage) + tabpage = tabpage or vim.api.nvim_get_current_tabpage() + local tabinfo = M.View.tabpages[tabpage] + if tabinfo and tabinfo.winnr and vim.api.nvim_win_is_valid(tabinfo.winnr) then + return tabinfo.winnr + end +end + +--- Returns the current nvim tree bufnr +---@return number +function M.get_bufnr() + return BUFNR_PER_TAB[vim.api.nvim_get_current_tabpage()] +end + +function M._prevent_buffer_override() + local view_winnr = M.get_winnr() + local view_bufnr = M.get_bufnr() + + -- need to schedule to let the new buffer populate the window + -- because this event needs to be run on bufWipeout. + -- Otherwise the curwin/curbuf would match the view buffer and the view window. + vim.schedule(function() + local curwin = vim.api.nvim_get_current_win() + local curwinconfig = vim.api.nvim_win_get_config(curwin) + local curbuf = vim.api.nvim_win_get_buf(curwin) + local bufname = vim.api.nvim_buf_get_name(curbuf) + + if not bufname:match("NvimTree") then + for i, tabpage in ipairs(M.View.tabpages) do + if tabpage.winnr == view_winnr then + M.View.tabpages[i] = nil + break + end + end + end + if curwin ~= view_winnr or bufname == "" or curbuf == view_bufnr then + return + end + + -- patch to avoid the overriding window to be fixed in size + -- might need a better patch + vim.cmd("setlocal nowinfixwidth") + vim.cmd("setlocal nowinfixheight") + M.open({ focus_tree = false }) + + local explorer = require("nvim-tree.core").get_explorer() + if explorer then + explorer.renderer:draw() + end + + pcall(vim.api.nvim_win_close, curwin, { force = true }) + + -- to handle opening a file using :e when nvim-tree is on floating mode + -- falling back to the current window instead of creating a new one + if curwinconfig.relative ~= "" then + require("nvim-tree.actions.node.open-file").fn("edit_in_place", bufname) + else + require("nvim-tree.actions.node.open-file").fn("edit", bufname) + end + end) +end + +---@param cwd string|nil +---@return boolean +function M.is_root_folder_visible(cwd) + return cwd ~= "/" and not M.View.hide_root_folder +end + +-- used on ColorScheme event +function M.reset_winhl() + local winnr = M.get_winnr() + if winnr and vim.api.nvim_win_is_valid(winnr) then + vim.wo[M.get_winnr()].winhl = M.View.winopts.winhl + end +end + +---Check if width determined or calculated on-fly +---@return boolean +function M.is_width_determined() + return type(M.View.width) ~= "function" +end + +---Configure width-related config +---@param width string|function|number|table|nil +function M.configure_width(width) + if type(width) == "table" then + M.View.adaptive_size = true + M.View.width = width.min or DEFAULT_MIN_WIDTH + M.View.max_width = width.max or DEFAULT_MAX_WIDTH + M.View.padding = width.padding or DEFAULT_PADDING + elseif width == nil then + if M.config.width ~= nil then + -- if we had input config - fallback to it + M.configure_width(M.config.width) + else + -- otherwise - restore initial width + M.View.width = M.View.initial_width + end + else + M.View.adaptive_size = false + M.View.width = width + end +end + +function M.setup(opts) + local options = opts.view or {} + M.View.centralize_selection = options.centralize_selection + M.View.side = (options.side == "right") and "right" or "left" + M.View.height = options.height + M.View.hide_root_folder = opts.renderer.root_folder_label == false + M.View.tab = opts.tab + M.View.preserve_window_proportions = options.preserve_window_proportions + M.View.winopts.cursorline = options.cursorline + M.View.winopts.number = options.number + M.View.winopts.relativenumber = options.relativenumber + M.View.winopts.signcolumn = options.signcolumn + M.View.float = options.float + M.on_attach = opts.on_attach + + M.config = options + M.configure_width(options.width) + + M.View.initial_width = get_width() +end + +return M From 550c59563b4494e5da0b2467376f670a10d1d3e2 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Sun, 10 Aug 2025 14:31:28 +1000 Subject: [PATCH 3/3] feat(#3157): add view.cursorlineopt --- lua/nvim-tree/view.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/nvim-tree/view.lua b/lua/nvim-tree/view.lua index 4ce95bfb6f4..90f2b033f3e 100644 --- a/lua/nvim-tree/view.lua +++ b/lua/nvim-tree/view.lua @@ -624,6 +624,7 @@ function M.setup(opts) M.View.tab = opts.tab M.View.preserve_window_proportions = options.preserve_window_proportions M.View.winopts.cursorline = options.cursorline + M.View.winopts.cursorlineopt = options.cursorlineopt M.View.winopts.number = options.number M.View.winopts.relativenumber = options.relativenumber M.View.winopts.signcolumn = options.signcolumn