Merge remote-tracking branch 'origin/master' into 2826-multi-instance-view-amc

This commit is contained in:
Alexander Courtis
2025-06-16 12:59:49 +10:00
21 changed files with 242 additions and 89 deletions

View File

@@ -40,21 +40,10 @@ local function usable_win_ids()
end
local win_config = vim.api.nvim_win_get_config(id)
return id ~= tree_winid and win_config.focusable and not win_config.external or false
return id ~= tree_winid and win_config.focusable and not win_config.hide and not win_config.external or false
end, win_ids)
end
---Find the first window in the tab that is not NvimTree.
---@return integer -1 if none available
local function first_win_id()
local selectable = usable_win_ids()
if #selectable > 0 then
return selectable[1]
else
return -1
end
end
---Get user to pick a window in the tab that is not NvimTree.
---@return integer|nil -- If a valid window was picked, return its id. If an
--- invalid window was picked / user canceled, return nil. If there are
@@ -259,9 +248,14 @@ local function get_target_winid(mode)
local target_winid
if not M.window_picker.enable or string.find(mode, "no_picker") then
target_winid = lib.target_winid
-- first available window
if not vim.tbl_contains(vim.api.nvim_tabpage_list_wins(0), target_winid) then
target_winid = first_win_id()
local usable_wins = usable_win_ids()
-- first available usable window
if not vim.tbl_contains(usable_wins, target_winid) then
if #usable_wins > 0 then
target_winid = usable_wins[1]
else
target_winid = -1
end
end
else
-- pick a window

View File

@@ -2,6 +2,7 @@ local utils = require("nvim-tree.utils")
local core = require("nvim-tree.core")
local Iterator = require("nvim-tree.iterators.node-iterator")
local FileNode = require("nvim-tree.node.file")
local DirectoryNode = require("nvim-tree.node.directory")
local M = {}
@@ -23,26 +24,30 @@ local function buf_match()
end
end
---@param keep_buffers boolean
function M.fn(keep_buffers)
---Collapse a node, root if nil
---@param node Node?
---@param opts ApiCollapseOpts
local function collapse(node, opts)
local explorer = core.get_explorer()
if not explorer then
return
end
local node = explorer:get_node_at_cursor()
if not node then
node = node or explorer
local node_at_cursor = explorer:get_node_at_cursor()
if not node_at_cursor then
return
end
local matches = buf_match()
Iterator.builder(explorer.nodes)
Iterator.builder({ node:is(FileNode) and node.parent or node:as(DirectoryNode) })
:hidden()
:applier(function(n)
local dir = n:as(DirectoryNode)
if dir then
dir.open = keep_buffers and matches(dir.absolute_path)
dir.open = opts.keep_buffers == true and matches(dir.absolute_path)
end
end)
:recursor(function(n)
@@ -51,7 +56,26 @@ function M.fn(keep_buffers)
:iterate()
explorer.renderer:draw()
utils.focus_node_or_parent(node)
utils.focus_node_or_parent(node_at_cursor)
end
---@param opts ApiCollapseOpts|boolean|nil legacy -> opts.keep_buffers
function M.all(opts)
-- legacy arguments
if type(opts) == "boolean" then
opts = {
keep_buffers = opts,
}
end
collapse(nil, opts or {})
end
---@param node Node
---@param opts ApiCollapseOpts?
function M.node(node, opts)
collapse(node, opts or {})
end
return M

View File

@@ -2,6 +2,7 @@ local core = require("nvim-tree.core")
local Iterator = require("nvim-tree.iterators.node-iterator")
local notify = require("nvim-tree.notify")
local FileNode = require("nvim-tree.node.file")
local DirectoryNode = require("nvim-tree.node.directory")
local M = {}
@@ -70,23 +71,38 @@ local function gen_iterator()
end
end
---Expand the directory node or the root
---@param node Node
function M.fn(node)
local explorer = core.get_explorer()
local parent = node:as(DirectoryNode) or explorer
if not parent then
---@param node Node?
local function expand_node(node)
if not node then
return
end
if gen_iterator()(parent) then
if gen_iterator()(node) then
notify.warn("expansion iteration was halted after " .. M.MAX_FOLDER_DISCOVERY .. " discovered folders")
end
local explorer = core.get_explorer()
if explorer then
explorer.renderer:draw()
end
end
---Expand the directory node or the root
---@param node Node
function M.all(node)
expand_node(node and node:as(DirectoryNode) or core.get_explorer())
end
---Expand the directory node or parent node
---@param node Node
function M.node(node)
if not node then
return
end
expand_node(node:is(FileNode) and node.parent or node:as(DirectoryNode))
end
function M.setup(opts)
M.MAX_FOLDER_DISCOVERY = opts.actions.expand_all.max_folder_discovery
M.EXCLUDE = to_lookup_table(opts.actions.expand_all.exclude)

View File

@@ -1,10 +1,10 @@
local M = {}
M.collapse_all = require("nvim-tree.actions.tree.modifiers.collapse-all")
M.expand_all = require("nvim-tree.actions.tree.modifiers.expand-all")
M.collapse = require("nvim-tree.actions.tree.modifiers.collapse")
M.expand = require("nvim-tree.actions.tree.modifiers.expand")
function M.setup(opts)
M.expand_all.setup(opts)
M.expand.setup(opts)
end
return M

View File

@@ -181,8 +181,12 @@ Api.tree.get_nodes = wrap_explorer("get_nodes")
Api.tree.find_file = wrap(actions.tree.find_file.fn)
Api.tree.search_node = wrap(actions.finders.search_node.fn)
Api.tree.collapse_all = wrap(actions.tree.modifiers.collapse_all.fn)
Api.tree.expand_all = wrap_node(actions.tree.modifiers.expand_all.fn)
---@class ApiCollapseOpts
---@field keep_buffers boolean|nil default false
Api.tree.collapse_all = wrap(actions.tree.modifiers.collapse.all)
Api.tree.expand_all = wrap_node(actions.tree.modifiers.expand.all)
Api.tree.toggle_enable_filters = wrap_explorer_member("filters", "toggle")
Api.tree.toggle_gitignore_filter = wrap_explorer_member_args("filters", "toggle", "git_ignored")
Api.tree.toggle_git_clean_filter = wrap_explorer_member_args("filters", "toggle", "git_clean")
@@ -317,6 +321,9 @@ Api.node.navigate.diagnostics.prev_recursive = wrap_node(actions.moves.item.fn({
Api.node.navigate.opened.next = wrap_node(actions.moves.item.fn({ where = "next", what = "opened" }))
Api.node.navigate.opened.prev = wrap_node(actions.moves.item.fn({ where = "prev", what = "opened" }))
Api.node.expand = wrap_node(actions.tree.modifiers.expand.node)
Api.node.collapse = wrap_node(actions.tree.modifiers.collapse.node)
---@class ApiNodeDeleteWipeBufferOpts
---@field force boolean|nil default false

View File

@@ -384,14 +384,7 @@ function View:grow()
local count = vim.fn.strchars(l)
-- also add space for right-aligned icons
local extmarks = vim.api.nvim_buf_get_extmarks(self:get_bufnr(), ns_id, { line_nr, 0 }, { line_nr, -1 }, { details = true })
for _, extmark in ipairs(extmarks) do
local virt_texts = extmark[4].virt_text
if virt_texts then
for _, virt_text in ipairs(virt_texts) do
count = count + vim.fn.strchars(virt_text[1])
end
end
end
count = count + utils.extmarks_length(extmarks)
if resizing_width < count then
resizing_width = count
end

View File

@@ -96,7 +96,7 @@ function M.default_on_attach(bufnr)
vim.keymap.set("n", "S", api.tree.search_node, opts("Search"))
vim.keymap.set("n", "u", api.fs.rename_full, opts("Rename: Full Path"))
vim.keymap.set("n", "U", api.tree.toggle_custom_filter, opts("Toggle Filter: Hidden"))
vim.keymap.set("n", "W", api.tree.collapse_all, opts("Collapse"))
vim.keymap.set("n", "W", api.tree.collapse_all, opts("Collapse All"))
vim.keymap.set("n", "x", api.fs.cut, opts("Cut"))
vim.keymap.set("n", "y", api.fs.copy.filename, opts("Copy Name"))
vim.keymap.set("n", "Y", api.fs.copy.relative_path, opts("Copy Relative Path"))

View File

@@ -60,6 +60,13 @@ local function refactored(opts)
end
end
utils.move_missing_val(opts, "update_focused_file", "ignore_list", opts, "update_focused_file.update_root", "ignore_list", true)
-- 2025/04/30
if opts.renderer and opts.renderer.icons and type(opts.renderer.icons.padding) == "string" then
local icons_padding = opts.renderer.icons.padding
opts.renderer.icons.padding = {}
opts.renderer.icons.padding.icon = icons_padding
end
end
local function deprecated(opts)

View File

@@ -134,12 +134,12 @@ end
function Builder:format_line(indent_markers, arrows, icon, name, node)
local added_len = 0
local function add_to_end(t1, t2)
if not t2 then
if not t2 or vim.tbl_isempty(t2) then
return
end
for _, v in ipairs(t2) do
if added_len > 0 then
table.insert(t1, { str = self.explorer.opts.renderer.icons.padding })
table.insert(t1, { str = self.explorer.opts.renderer.icons.padding.icon })
end
table.insert(t1, v)
end

View File

@@ -1,6 +1,7 @@
local M = {}
local utils = require("nvim-tree.utils")
local view = require("nvim-tree.view")
local function hide(win)
if win then
@@ -32,7 +33,7 @@ local function effective_win_width()
return win_width - win_info[1].textoff
end
local function show()
local function show(opts)
local line_nr = vim.api.nvim_win_get_cursor(0)[1]
if vim.wo.wrap then
return
@@ -52,6 +53,11 @@ local function show()
local text_width = vim.fn.strdisplaywidth(vim.fn.substitute(line, "[^[:print:]]*$", "", "g"))
local win_width = effective_win_width()
-- windows width reduced by right aligned icons
local icon_ns_id = vim.api.nvim_get_namespaces()["NvimTreeExtmarks"]
local icon_extmarks = vim.api.nvim_buf_get_extmarks(0, icon_ns_id, { line_nr - 1, 0 }, { line_nr - 1, -1 }, { details = true })
win_width = win_width - utils.extmarks_length(icon_extmarks)
if text_width < win_width then
return
end
@@ -66,6 +72,7 @@ local function show()
style = "minimal",
border = "none"
})
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 })
@@ -88,7 +95,10 @@ local function show()
end
end
end
vim.cmd([[ setlocal nowrap cursorline noswapfile nobuflisted buftype=nofile bufhidden=wipe ]])
vim.cmd([[ setlocal nowrap noswapfile nobuflisted buftype=nofile bufhidden=wipe ]])
if opts.view.cursorline then
vim.cmd([[ setlocal cursorline cursorlineopt=both ]])
end
end)
end
@@ -114,7 +124,7 @@ M.setup = function(opts)
pattern = { "NvimTree_*" },
callback = function()
if utils.is_nvim_tree_buf(0) then
show()
show(opts)
end
end,
})

View File

@@ -95,15 +95,15 @@ function M.get_arrows(node)
local dir = node:as(DirectoryNode)
if dir then
if dir.open then
str = M.config.icons.glyphs.folder["arrow_open"] .. " "
str = M.config.icons.glyphs.folder["arrow_open"] .. M.config.icons.padding.folder_arrow
hl = "NvimTreeFolderArrowOpen"
else
str = M.config.icons.glyphs.folder["arrow_closed"] .. " "
str = M.config.icons.glyphs.folder["arrow_closed"] .. M.config.icons.padding.folder_arrow
end
elseif M.config.indent_markers.enable then
str = ""
else
str = " "
str = " " .. string.rep(" ", #M.config.icons.padding.folder_arrow)
end
return { str = str, hl = { hl } }

View File

@@ -112,9 +112,8 @@ function Decorator:define_sign(icon)
vim.fn.sign_undefine(name)
end
-- don't use sign if not defined
-- don't render sign if empty
if #icon.str < 1 then
self.icon_placement = "none"
return
end

View File

@@ -1,5 +1,4 @@
local Iterator = require("nvim-tree.iterators.node-iterator")
local notify = require("nvim-tree.notify")
local M = {
debouncers = {},
@@ -179,6 +178,21 @@ function M.find_node_line(node)
return -1
end
---@param extmarks vim.api.keyset.get_extmark_item[] as per vim.api.nvim_buf_get_extmarks
---@return number
function M.extmarks_length(extmarks)
local length = 0
for _, extmark in ipairs(extmarks) do
local details = extmark[4]
if details and details.virt_text then
for _, text in ipairs(details.virt_text) do
length = length + vim.fn.strchars(text[1])
end
end
end
return length
end
-- get the node in the tree state depending on the absolute path of the node
-- (grouped or hidden too)
---@param path string
@@ -295,11 +309,51 @@ function M.rename_loaded_buffers(old_path, new_path)
end
end
local is_windows_drive = function(path)
return (M.is_windows) and (path:match("^%a:\\$") ~= nil)
end
---@param path string path to file or directory
---@return boolean
function M.file_exists(path)
local _, error = vim.loop.fs_stat(path)
return error == nil
if not (M.is_windows or M.is_wsl) then
local _, error = vim.loop.fs_stat(path)
return error == nil
end
-- Windows is case-insensetive, but case-preserving
-- If a file's name is being changed into itself
-- with different casing, windows will falsely
-- report that file is already existing, so a hand-rolled
-- implementation of checking for existance is needed.
-- Same holds for WSL, since it can sometimes
-- access Windows files directly.
-- For more details see (#3117).
if is_windows_drive(path) then
return vim.fn.isdirectory(path) == 1
end
local parent = vim.fn.fnamemodify(path, ":h")
local filename = vim.fn.fnamemodify(path, ":t")
local handle = vim.loop.fs_scandir(parent)
if not handle then
-- File can not exist if its parent directory does not exist
return false
end
while true do
local name, _ = vim.loop.fs_scandir_next(handle)
if not name then
break
end
if name == filename then
return true
end
end
return false
end
---@param path string
@@ -355,20 +409,6 @@ end
---@param dst_pos string value pos
---@param remove boolean
function M.move_missing_val(src, src_path, src_pos, dst, dst_path, dst_pos, remove)
local ok, err = pcall(vim.validate, {
src = { src, "table" },
src_path = { src_path, "string" },
src_pos = { src_pos, "string" },
dst = { dst, "table" },
dst_path = { dst_path, "string" },
dst_pos = { dst_pos, "string" },
remove = { remove, "boolean" },
})
if not ok then
notify.warn("move_missing_val: " .. (err or "invalid arguments"))
return
end
for pos in string.gmatch(src_path, "([^%.]+)%.*") do
if src[pos] and type(src[pos]) == "table" then
src = src[pos]