feat(#2225): add renderer.hidden_display to show a summary of hidden files below the tree (#2856)

* feat(icon_placement): Allow right_align icon_placemente for decorator using ext_marks nvim api

* feat(icon_placement): Allow right_align icon_placemente for decorator using ext_marks nvim api

feat(icon_placement): Allow right_align icon_placemente for decorator using ext_marks nvim api

* feat(icon_placement): consolidate doc

* fix: extra namespace added to avoid colision between right_align and full_name features

* feat(hidden_display): Allow fine grained rendering of hidden files in
a folder

* feat(hidden_display): update defaults in Builder to allow rendering

* feat(hidden_display): Rename opts function name for the feature

* feat(#2349): add "right_align" option for renderer.icons.*_placement (#2846)

* feat(icon_placement): Allow right_align icon_placemente for decorator using ext_marks nvim api

* feat(icon_placement): Allow right_align icon_placemente for decorator using ext_marks nvim api

feat(icon_placement): Allow right_align icon_placemente for decorator using ext_marks nvim api

* feat(icon_placement): consolidate doc

* fix: extra namespace added to avoid colision between right_align and full_name features

* style: rename namespace_id

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>

* docs: update docs

* feat(hidden_display): Simplification and better performance by not sorting and grouping virtual lines

* Update doc/nvim-tree-lua.txt

Co-authored-by: Alexander Courtis <alex@courtis.org>

* style: hidden_stats is better

* docs: change to hidden_stats

* add separate namespace for virtual lines

* help: add highlight group

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
This commit is contained in:
Everton Jr. 2024-08-09 22:36:30 -03:00 committed by GitHub
parent 48d0e82f94
commit e25eb7fa83
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 258 additions and 10 deletions

View File

@ -423,6 +423,7 @@ Following is the default configuration. See |nvim-tree-opts| for details.
root_folder_label = ":~:s?$?/..?",
indent_width = 2,
special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" },
hidden_display = "none",
symlink_destination = true,
highlight_git = "none",
highlight_diagnostics = "none",
@ -878,6 +879,49 @@ Number of spaces for an each tree nesting level. Minimum 1.
A list of filenames that gets highlighted with `NvimTreeSpecialFile`.
Type: `table`, Default: `{ "Cargo.toml", "Makefile", "README.md", "readme.md", }`
*nvim-tree.renderer.hidden_display*
Show a summary of hidden files below the tree using `NvimTreeHiddenDisplay
Type: `function | string`, Default: `"none"`
Possible string values are:
- `"none"`: Doesn't inform anything about hidden files.
- `"simple"`: Shows how many hidden files are in a folder.
- `"all"`: Shows how many files are hidden and the number of hidden
files per reason why they're hidden.
Example `"all"`:
If a folder has 14 hidden items for various reasons, the display might
show: >
(14 total git: 5, dotfile: 9)
<
If a function is provided, it receives a table `hidden_stats` where keys are
reasons and values are the count of hidden files for that reason.
The `hidden_stats` argument is structured as follows, where <num> is the
number of hidden files related to the field: >
hidden_stats = {
bookmark = <num>,
buf = <num>,
custom = <num>,
dotfile = <num>,
git = <num>,
live_filter = <num>,
}
<
Example of function that can be passed: >
function(hidden_stats)
local total_count = 0
for reason, count in pairs(hidden_stats) do
total_count = total_count + count
end
if total_count > 0 then
return "(" .. tostring(total_count) .. " hidden)"
end
return nil
end
<
*nvim-tree.renderer.symlink_destination*
Whether to show the destination of the symlink.
Type: `boolean`, Default: `true`
@ -2461,6 +2505,9 @@ Hidden: >
NvimTreeModifiedFileHL NvimTreeHiddenIcon
NvimTreeModifiedFolderHL NvimTreeHiddenFileHL
<
Hidden Display: >
NvimTreeHiddenDisplay Conceal
<
Opened: >
NvimTreeOpenedHL Special
<
@ -2872,6 +2919,7 @@ highlight group is not, hard linking as follows: >
|nvim-tree.renderer.add_trailing|
|nvim-tree.renderer.full_name|
|nvim-tree.renderer.group_empty|
|nvim-tree.renderer.hidden_display|
|nvim-tree.renderer.highlight_bookmarks|
|nvim-tree.renderer.highlight_clipboard|
|nvim-tree.renderer.highlight_diagnostics|

View File

@ -398,6 +398,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
root_folder_label = ":~:s?$?/..?",
indent_width = 2,
special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" },
hidden_display = "none",
symlink_destination = true,
highlight_git = "none",
highlight_diagnostics = "none",
@ -647,6 +648,7 @@ local ACCEPTED_TYPES = {
},
},
renderer = {
hidden_display = { "function", "string" },
group_empty = { "boolean", "function" },
root_folder_label = { "function", "string", "boolean" },
},
@ -680,6 +682,7 @@ local ACCEPTED_STRINGS = {
signcolumn = { "yes", "no", "auto" },
},
renderer = {
hidden_display = { "none", "simple", "all" },
highlight_git = { "none", "icon", "name", "all" },
highlight_opened_files = { "none", "icon", "name", "all" },
highlight_modified = { "none", "icon", "name", "all" },

View File

@ -14,6 +14,7 @@ M.HIGHLIGHT_GROUPS = {
-- Standard
{ group = "NvimTreeNormal", link = "Normal" },
{ group = "NvimTreeNormalFloat", link = "NormalFloat" },
{ group = "NvimTreeNormalFloatBorder", link = "FloatBorder" },
{ group = "NvimTreeNormalNC", link = "NvimTreeNormal" },
{ group = "NvimTreeLineNr", link = "LineNr" },
@ -81,6 +82,9 @@ M.HIGHLIGHT_GROUPS = {
{ group = "NvimTreeHiddenFileHL", link = "NvimTreeHiddenIcon" },
{ group = "NvimTreeHiddenFolderHL", link = "NvimTreeHiddenFileHL" },
-- Hidden Display
{ group = "NvimTreeHiddenDisplay", link = "Conceal" },
-- Opened
{ group = "NvimTreeOpenedHL", link = "Special" },

View File

@ -19,4 +19,15 @@ M.ICON_PLACEMENT = {
right_align = 4,
}
---Reason for filter in filter.lua
---@enum FILTER_REASON
M.FILTER_REASON = {
none = 0, -- It's not filtered
git = 1,
buf = 2,
dotfile = 4,
custom = 8,
bookmark = 16,
}
return M

View File

@ -5,6 +5,7 @@ local git = require "nvim-tree.git"
local live_filter = require "nvim-tree.live-filter"
local log = require "nvim-tree.log"
local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON
local Watcher = require "nvim-tree.watcher"
local M = {}
@ -17,7 +18,17 @@ local M = {}
local function populate_children(handle, cwd, node, git_status, parent)
local node_ignored = explorer_node.is_git_ignored(node)
local nodes_by_path = utils.bool_record(node.nodes, "absolute_path")
local filter_status = parent.filters:prepare(git_status)
node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, {
git = 0,
buf = 0,
dotfile = 0,
custom = 0,
bookmark = 0,
})
while true do
local name, t = vim.loop.fs_scandir_next(handle)
if not name then
@ -29,8 +40,8 @@ local function populate_children(handle, cwd, node, git_status, parent)
---@type uv.fs_stat.result|nil
local stat = vim.loop.fs_stat(abs)
if not parent.filters:should_filter(abs, stat, filter_status) and not nodes_by_path[abs] and Watcher.is_fs_event_capable(abs) then
local filter_reason = parent.filters:should_filter_as_reason(abs, stat, filter_status)
if filter_reason == FILTER_REASON.none and not nodes_by_path[abs] and Watcher.is_fs_event_capable(abs) then
local child = nil
if t == "directory" and vim.loop.fs_access(abs, "R") then
child = builders.folder(node, abs, name, stat)
@ -47,6 +58,12 @@ local function populate_children(handle, cwd, node, git_status, parent)
nodes_by_path[child.absolute_path] = true
explorer_node.update_git_status(child, node_ignored, git_status)
end
else
for reason, value in pairs(FILTER_REASON) do
if filter_reason == value then
node.hidden_stats[reason] = node.hidden_stats[reason] + 1
end
end
end
log.profile_end(profile)

View File

@ -1,4 +1,5 @@
local utils = require "nvim-tree.utils"
local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON
---@class Filters to handle all opts.filters and related API
---@field config table hydrated user opts.filters
@ -223,4 +224,33 @@ function Filters:should_filter(path, fs_stat, status)
or bookmark(self, path, fs_stat and fs_stat.type, status.bookmarks)
end
--- Check if the given path should be filtered, and provide the reason why it was
---@param path string Absolute path
---@param fs_stat uv.fs_stat.result|nil fs_stat of file
---@param status table from prepare
---@return FILTER_REASON
function Filters:should_filter_as_reason(path, fs_stat, status)
if not self.config.enable then
return FILTER_REASON.none
end
if is_excluded(self, path) then
return FILTER_REASON.none
end
if git(self, path, status.git_status) then
return FILTER_REASON.git
elseif buf(self, path, status.bufinfo) then
return FILTER_REASON.buf
elseif dotfile(self, path) then
return FILTER_REASON.dotfile
elseif custom(self, path) then
return FILTER_REASON.custom
elseif bookmark(self, path, fs_stat and fs_stat.type, status.bookmarks) then
return FILTER_REASON.bookmark
else
return FILTER_REASON.none
end
end
return Filters

View File

@ -5,6 +5,7 @@ local live_filter = require "nvim-tree.live-filter"
local git = require "nvim-tree.git"
local log = require "nvim-tree.log"
local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON
local NodeIterator = require "nvim-tree.iterators.node-iterator"
local Watcher = require "nvim-tree.watcher"
@ -92,6 +93,16 @@ function M.reload(node, git_status)
local node_ignored = explorer_node.is_git_ignored(node)
---@type table<string, Node>
local nodes_by_path = utils.key_by(node.nodes, "absolute_path")
-- To reset we must 'zero' everything that we use
node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, {
git = 0,
buf = 0,
dotfile = 0,
custom = 0,
bookmark = 0,
})
while true do
local name, t = vim.loop.fs_scandir_next(handle)
if not name then
@ -102,7 +113,8 @@ function M.reload(node, git_status)
---@type uv.fs_stat.result|nil
local stat = vim.loop.fs_stat(abs)
if not explorer.filters:should_filter(abs, stat, filter_status) then
local filter_reason = explorer.filters:should_filter_as_reason(abs, stat, filter_status)
if filter_reason == FILTER_REASON.none then
remain_childs[abs] = true
-- Recreate node if type changes.
@ -139,6 +151,12 @@ function M.reload(node, git_status)
n.fs_stat = stat
end
end
else
for reason, value in pairs(FILTER_REASON) do
if filter_reason == value then
node.hidden_stats[reason] = node.hidden_stats[reason] + 1
end
end
end
end

View File

@ -18,10 +18,17 @@ local function reset_filter(node_)
return
end
node_.hidden_stats = vim.tbl_deep_extend("force", node_.hidden_stats or {}, {
live_filter = 0,
})
Iterator.builder(node_.nodes)
:hidden()
:applier(function(node)
node.hidden = false
node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, {
live_filter = 0,
})
end)
:iterate()
end
@ -79,6 +86,10 @@ function M.apply_filter(node_)
local filtered_nodes = 0
local nodes = node.group_next and { node.group_next } or node.nodes
node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, {
live_filter = 0,
})
if nodes then
for _, n in pairs(nodes) do
iterate(n)
@ -88,6 +99,8 @@ function M.apply_filter(node_)
end
end
node.hidden_stats.live_filter = filtered_nodes
local has_nodes = nodes and (M.always_show_folders or #nodes > filtered_nodes)
local ok, is_match = pcall(matches, node)
node.hidden = not (has_nodes or (ok and is_match))

View File

@ -21,6 +21,7 @@
---@field group_next Node|nil
---@field nodes Node[]
---@field open boolean
---@field hidden_stats table -- Each field of this table is a key for source and value for count
---@class FileNode: BaseNode
---@field extension string

View File

@ -43,6 +43,8 @@ local M = {
---@field lines string[] includes icons etc.
---@field hl_args AddHighlightArgs[] line highlights
---@field signs string[] line signs
---@field extmarks table[] extra marks for right icon placement
---@field virtual_lines table[] virtual lines for hidden count display
---@field private root_cwd string absolute path
---@field private index number
---@field private depth number
@ -62,6 +64,7 @@ function Builder:new()
markers = {},
signs = {},
extmarks = {},
virtual_lines = {},
}
setmetatable(o, self)
self.__index = self
@ -351,7 +354,6 @@ function Builder:build_line(node, idx, num_children)
self.index = self.index + 1
node = require("nvim-tree.lib").get_last_group_node(node)
if node.open then
self.depth = self.depth + 1
self:build_lines(node)
@ -359,6 +361,32 @@ function Builder:build_line(node, idx, num_children)
end
end
---Add virtual lines for rendering hidden count information per node
---@private
function Builder:add_hidden_count_string(node, idx, num_children)
if not node.open then
return
end
local hidden_count_string = M.opts.renderer.hidden_display(node.hidden_stats)
if hidden_count_string and hidden_count_string ~= "" then
local indent_markers = pad.get_indent_markers(self.depth, idx or 0, num_children or 0, node, self.markers, 1)
local indent_width = M.opts.renderer.indent_width
local indent_padding = string.rep(" ", indent_width)
local indent_string = indent_padding .. indent_markers.str
local line_nr = #self.lines - 1
self.virtual_lines[line_nr] = self.virtual_lines[line_nr] or {}
-- NOTE: We are inserting in depth order because of current traversal
-- if we change the traversal, we might need to sort by depth before rendering `self.virtual_lines`
-- to maintain proper ordering of parent and child folder hidden count info.
table.insert(self.virtual_lines[line_nr], {
{ indent_string, indent_markers.hl },
{ string.rep(indent_padding, (node.parent == nil and 0 or 1)) .. hidden_count_string, "NvimTreeHiddenDisplay" },
})
end
end
---@private
function Builder:get_nodes_number(nodes)
if not live_filter.filter then
@ -388,6 +416,7 @@ function Builder:build_lines(node)
idx = idx + 1
end
end
self:add_hidden_count_string(node)
end
---@private
@ -442,7 +471,50 @@ function Builder:build()
return self
end
---@param opts table
local setup_hidden_display_function = function(opts)
local hidden_display = opts.renderer.hidden_display
-- options are already validated, so ´hidden_display´ can ONLY be `string` or `function` if type(hidden_display) == "string" then
if type(hidden_display) == "string" then
if hidden_display == "none" then
opts.renderer.hidden_display = function()
return nil
end
elseif hidden_display == "simple" then
opts.renderer.hidden_display = function(hidden_stats)
return utils.default_format_hidden_count(hidden_stats, true)
end
elseif hidden_display == "all" then
opts.renderer.hidden_display = function(hidden_stats)
return utils.default_format_hidden_count(hidden_stats, false)
end
end
elseif type(hidden_display) == "function" then
local safe_render = function(hidden_stats)
-- In case of missing field such as live_filter we zero it, otherwise keep field as is
hidden_stats = vim.tbl_deep_extend("force", {
live_filter = 0,
git = 0,
buf = 0,
dotfile = 0,
custom = 0,
bookmark = 0,
}, hidden_stats or {})
local ok, result = pcall(hidden_display, hidden_stats)
if not ok then
notify.warn "Problem occurred in the function ``opts.renderer.hidden_display`` see nvim-tree.renderer.hidden_display on :h nvim-tree"
return nil
end
return result
end
opts.renderer.hidden_display = safe_render
end
end
function Builder.setup(opts)
setup_hidden_display_function(opts)
M.opts = opts
-- priority order

View File

@ -19,7 +19,7 @@ local function check_siblings_for_folder(node, with_arrows)
return false
end
local function get_padding_indent_markers(depth, idx, nodes_number, markers, with_arrows, inline_arrows, node)
local function get_padding_indent_markers(depth, idx, nodes_number, markers, with_arrows, inline_arrows, node, early_stop)
local base_padding = with_arrows and (not node.nodes or depth > 0) and " " or ""
local padding = (inline_arrows or depth == 0) and base_padding or ""
@ -27,7 +27,7 @@ local function get_padding_indent_markers(depth, idx, nodes_number, markers, wit
local has_folder_sibling = check_siblings_for_folder(node, with_arrows)
local indent = string.rep(" ", M.config.indent_width - 1)
markers[depth] = idx ~= nodes_number
for i = 1, depth do
for i = 1, depth - early_stop do
local glyph
if idx == nodes_number and i == depth then
local bottom_width = M.config.indent_width - 2 + (with_arrows and not inline_arrows and has_folder_sibling and 2 or 0)
@ -62,7 +62,7 @@ end
---@param node table
---@param markers table
---@return HighlightedString[]
function M.get_indent_markers(depth, idx, nodes_number, node, markers)
function M.get_indent_markers(depth, idx, nodes_number, node, markers, early_stop)
local str = ""
local show_arrows = M.config.icons.show.folder_arrow
@ -71,7 +71,7 @@ function M.get_indent_markers(depth, idx, nodes_number, node, markers)
local indent_width = M.config.indent_width
if show_markers then
str = str .. get_padding_indent_markers(depth, idx, nodes_number, markers, show_arrows, inline_arrows, node)
str = str .. get_padding_indent_markers(depth, idx, nodes_number, markers, show_arrows, inline_arrows, node, early_stop or 0)
else
str = str .. string.rep(" ", depth * indent_width)
end

View File

@ -14,12 +14,13 @@ local SIGN_GROUP = "NvimTreeRendererSigns"
local namespace_highlights_id = vim.api.nvim_create_namespace "NvimTreeHighlights"
local namespace_extmarks_id = vim.api.nvim_create_namespace "NvimTreeExtmarks"
local namespace_virtual_lines_id = vim.api.nvim_create_namespace "NvimTreeVirtualLines"
---@param bufnr number
---@param lines string[]
---@param hl_args AddHighlightArgs[]
---@param signs string[]
local function _draw(bufnr, lines, hl_args, signs, extmarks)
local function _draw(bufnr, lines, hl_args, signs, extmarks, virtual_lines)
if vim.fn.has "nvim-0.10" == 1 then
vim.api.nvim_set_option_value("modifiable", true, { buf = bufnr })
else
@ -50,6 +51,15 @@ local function _draw(bufnr, lines, hl_args, signs, extmarks)
})
end
end
vim.api.nvim_buf_clear_namespace(bufnr, namespace_virtual_lines_id, 0, -1)
for line_nr, vlines in pairs(virtual_lines) do
vim.api.nvim_buf_set_extmark(bufnr, namespace_virtual_lines_id, line_nr, 0, {
virt_lines = vlines,
virt_lines_above = false,
virt_lines_leftcol = true,
})
end
end
function M.render_hl(bufnr, hl)
@ -79,7 +89,7 @@ function M.draw()
local builder = Builder:new():build()
_draw(bufnr, builder.lines, builder.hl_args, builder.signs, builder.extmarks)
_draw(bufnr, builder.lines, builder.hl_args, builder.signs, builder.extmarks, builder.virtual_lines)
if cursor and #builder.lines >= cursor[1] then
vim.api.nvim_win_set_cursor(view.get_winnr() or 0, cursor)

View File

@ -181,6 +181,26 @@ function M.get_parent_of_group(node)
return node
end
M.default_format_hidden_count = function(hidden_count, simple)
local parts = {}
local total_count = 0
for reason, count in pairs(hidden_count) do
total_count = total_count + count
if count > 0 then
table.insert(parts, reason .. ": " .. tostring(count))
end
end
local hidden_count_string = table.concat(parts, ", ") -- if empty then is "" (empty string)
if simple then
hidden_count_string = ""
end
if total_count > 0 then
return "(" .. tostring(total_count) .. (simple and " hidden" or " total ") .. hidden_count_string .. ")"
end
return nil
end
--- Return visible nodes indexed by line
---@param nodes_all Node[]
---@param line_start number

View File

@ -50,6 +50,7 @@ M.View = {
"Normal:NvimTreeNormal",
"NormalNC:NvimTreeNormalNC",
"NormalFloat:NvimTreeNormalFloat",
"FloatBorder:NvimTreeNormalFloatBorder",
}, ","),
},
}