Compare commits

...

4 Commits

Author SHA1 Message Date
github-actions[bot]
ad0b95dee5 chore(master): release nvim-tree 1.6.0 (#2845)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-08-10 12:26:42 +10:00
Michael Härtl
466fbed3e4 fix(#2859): make sure window still exists when restoring options (#2863)
Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-08-10 12:21:48 +10:00
Mateusz Russak
15942df2bb refactor(#2827): multi instance nvim-tree.live-filter (#2849)
* feat(#2827): Multi Instance: Refactor: nvim-tree.live-filter

* refactor: all usages going through the explorer

* fix: api and filtration

* fix: style

* Update lua/nvim-tree/api.lua

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

* docs: add missing live filter luadocs

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-08-10 12:02:13 +10:00
Everton Jr.
e25eb7fa83 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>
2024-08-10 11:36:30 +10:00
20 changed files with 362 additions and 75 deletions

View File

@@ -1,3 +1,3 @@
{ {
".": "1.5.0" ".": "1.6.0"
} }

View File

@@ -1,5 +1,20 @@
# Changelog # Changelog
## [1.6.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.5.0...nvim-tree-v1.6.0) (2024-08-10)
### Features
* **#2225:** add renderer.hidden_display to show a summary of hidden files below the tree ([#2856](https://github.com/nvim-tree/nvim-tree.lua/issues/2856)) ([e25eb7f](https://github.com/nvim-tree/nvim-tree.lua/commit/e25eb7fa83f7614bb23d762e91d2de44fcd7103b))
* **#2349:** add "right_align" option for renderer.icons.*_placement ([#2839](https://github.com/nvim-tree/nvim-tree.lua/issues/2839)) ([1d629a5](https://github.com/nvim-tree/nvim-tree.lua/commit/1d629a5d3f7d83d516494c221a2cfc079f43bc47))
* **#2349:** add "right_align" option for renderer.icons.*_placement ([#2846](https://github.com/nvim-tree/nvim-tree.lua/issues/2846)) ([48d0e82](https://github.com/nvim-tree/nvim-tree.lua/commit/48d0e82f9434691cc50d970898142a8c084a49d6))
* add renderer.highlight_hidden, renderer.icons.show.hidden and renderer.icons.hidden_placement for dotfile icons/highlights ([#2840](https://github.com/nvim-tree/nvim-tree.lua/issues/2840)) ([48a9290](https://github.com/nvim-tree/nvim-tree.lua/commit/48a92907575df1dbd7242975a04e98169cb3a115))
### Bug Fixes
* **#2859:** make sure window still exists when restoring options ([#2863](https://github.com/nvim-tree/nvim-tree.lua/issues/2863)) ([466fbed](https://github.com/nvim-tree/nvim-tree.lua/commit/466fbed3e4b61fcc23a48fe99de7bfa264a9fee8))
## [1.5.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.4.0...nvim-tree-v1.5.0) (2024-07-11) ## [1.5.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.4.0...nvim-tree-v1.5.0) (2024-07-11)

View File

@@ -423,6 +423,7 @@ Following is the default configuration. See |nvim-tree-opts| for details.
root_folder_label = ":~:s?$?/..?", root_folder_label = ":~:s?$?/..?",
indent_width = 2, indent_width = 2,
special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" }, special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" },
hidden_display = "none",
symlink_destination = true, symlink_destination = true,
highlight_git = "none", highlight_git = "none",
highlight_diagnostics = "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`. A list of filenames that gets highlighted with `NvimTreeSpecialFile`.
Type: `table`, Default: `{ "Cargo.toml", "Makefile", "README.md", "readme.md", }` 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* *nvim-tree.renderer.symlink_destination*
Whether to show the destination of the symlink. Whether to show the destination of the symlink.
Type: `boolean`, Default: `true` Type: `boolean`, Default: `true`
@@ -2461,6 +2505,9 @@ Hidden: >
NvimTreeModifiedFileHL NvimTreeHiddenIcon NvimTreeModifiedFileHL NvimTreeHiddenIcon
NvimTreeModifiedFolderHL NvimTreeHiddenFileHL NvimTreeModifiedFolderHL NvimTreeHiddenFileHL
< <
Hidden Display: >
NvimTreeHiddenDisplay Conceal
<
Opened: > Opened: >
NvimTreeOpenedHL Special NvimTreeOpenedHL Special
< <
@@ -2872,6 +2919,7 @@ highlight group is not, hard linking as follows: >
|nvim-tree.renderer.add_trailing| |nvim-tree.renderer.add_trailing|
|nvim-tree.renderer.full_name| |nvim-tree.renderer.full_name|
|nvim-tree.renderer.group_empty| |nvim-tree.renderer.group_empty|
|nvim-tree.renderer.hidden_display|
|nvim-tree.renderer.highlight_bookmarks| |nvim-tree.renderer.highlight_bookmarks|
|nvim-tree.renderer.highlight_clipboard| |nvim-tree.renderer.highlight_clipboard|
|nvim-tree.renderer.highlight_diagnostics| |nvim-tree.renderer.highlight_diagnostics|

View File

@@ -398,6 +398,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
root_folder_label = ":~:s?$?/..?", root_folder_label = ":~:s?$?/..?",
indent_width = 2, indent_width = 2,
special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" }, special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" },
hidden_display = "none",
symlink_destination = true, symlink_destination = true,
highlight_git = "none", highlight_git = "none",
highlight_diagnostics = "none", highlight_diagnostics = "none",
@@ -647,6 +648,7 @@ local ACCEPTED_TYPES = {
}, },
}, },
renderer = { renderer = {
hidden_display = { "function", "string" },
group_empty = { "boolean", "function" }, group_empty = { "boolean", "function" },
root_folder_label = { "function", "string", "boolean" }, root_folder_label = { "function", "string", "boolean" },
}, },
@@ -680,6 +682,7 @@ local ACCEPTED_STRINGS = {
signcolumn = { "yes", "no", "auto" }, signcolumn = { "yes", "no", "auto" },
}, },
renderer = { renderer = {
hidden_display = { "none", "simple", "all" },
highlight_git = { "none", "icon", "name", "all" }, highlight_git = { "none", "icon", "name", "all" },
highlight_opened_files = { "none", "icon", "name", "all" }, highlight_opened_files = { "none", "icon", "name", "all" },
highlight_modified = { "none", "icon", "name", "all" }, highlight_modified = { "none", "icon", "name", "all" },
@@ -837,7 +840,6 @@ function M.setup(conf)
require("nvim-tree.view").setup(opts) require("nvim-tree.view").setup(opts)
require("nvim-tree.lib").setup(opts) require("nvim-tree.lib").setup(opts)
require("nvim-tree.renderer").setup(opts) require("nvim-tree.renderer").setup(opts)
require("nvim-tree.live-filter").setup(opts)
require("nvim-tree.marks").setup(opts) require("nvim-tree.marks").setup(opts)
require("nvim-tree.buffers").setup(opts) require("nvim-tree.buffers").setup(opts)
require("nvim-tree.help").setup(opts) require("nvim-tree.help").setup(opts)

View File

@@ -167,6 +167,8 @@ local function pick_win_id()
if laststatus == 3 then if laststatus == 3 then
for _, id in ipairs(not_selectable) do for _, id in ipairs(not_selectable) do
-- Ensure window still exists at this point
if vim.api.nvim_win_is_valid(id) then
for opt, value in pairs(win_opts[id]) do for opt, value in pairs(win_opts[id]) do
if vim.fn.has "nvim-0.10" == 1 then if vim.fn.has "nvim-0.10" == 1 then
vim.api.nvim_set_option_value(opt, value, { win = id }) vim.api.nvim_set_option_value(opt, value, { win = id })
@@ -176,6 +178,7 @@ local function pick_win_id()
end end
end end
end end
end
vim.o.laststatus = laststatus vim.o.laststatus = laststatus

View File

@@ -6,7 +6,6 @@ local actions = require "nvim-tree.actions"
local appearance_diagnostics = require "nvim-tree.appearance.diagnostics" local appearance_diagnostics = require "nvim-tree.appearance.diagnostics"
local events = require "nvim-tree.events" local events = require "nvim-tree.events"
local help = require "nvim-tree.help" local help = require "nvim-tree.help"
local live_filter = require "nvim-tree.live-filter"
local marks_navigation = require "nvim-tree.marks.navigation" local marks_navigation = require "nvim-tree.marks.navigation"
local marks_bulk_delete = require "nvim-tree.marks.bulk-delete" local marks_bulk_delete = require "nvim-tree.marks.bulk-delete"
local marks_bulk_trash = require "nvim-tree.marks.bulk-trash" local marks_bulk_trash = require "nvim-tree.marks.bulk-trash"
@@ -265,8 +264,8 @@ Api.git.reload = wrap(actions.reloaders.reload_git)
Api.events.subscribe = events.subscribe Api.events.subscribe = events.subscribe
Api.events.Event = events.Event Api.events.Event = events.Event
Api.live_filter.start = wrap(live_filter.start_filtering) Api.live_filter.start = wrap_explorer_member("live_filter", "start_filtering")
Api.live_filter.clear = wrap(live_filter.clear_filter) Api.live_filter.clear = wrap_explorer_member("live_filter", "clear_filter")
Api.marks.get = wrap_node(wrap_explorer_member("marks", "get_mark")) Api.marks.get = wrap_node(wrap_explorer_member("marks", "get_mark"))
Api.marks.list = wrap_explorer_member("marks", "get_marks") Api.marks.list = wrap_explorer_member("marks", "get_marks")

View File

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

View File

@@ -1,6 +1,5 @@
local events = require "nvim-tree.events" local events = require "nvim-tree.events"
local explorer = require "nvim-tree.explorer" local explorer = require "nvim-tree.explorer"
local live_filter = require "nvim-tree.live-filter"
local view = require "nvim-tree.view" local view = require "nvim-tree.view"
local log = require "nvim-tree.log" local log = require "nvim-tree.log"
@@ -45,7 +44,7 @@ function M.get_nodes_starting_line()
if view.is_root_folder_visible(M.get_cwd()) then if view.is_root_folder_visible(M.get_cwd()) then
offset = offset + 1 offset = offset + 1
end end
if live_filter.filter then if TreeExplorer and TreeExplorer.live_filter.filter then
return offset + 1 return offset + 1
end end
return offset return offset

View File

@@ -19,4 +19,15 @@ M.ICON_PLACEMENT = {
right_align = 4, 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 return M

View File

@@ -2,9 +2,9 @@ local utils = require "nvim-tree.utils"
local builders = require "nvim-tree.explorer.node-builders" local builders = require "nvim-tree.explorer.node-builders"
local explorer_node = require "nvim-tree.explorer.node" local explorer_node = require "nvim-tree.explorer.node"
local git = require "nvim-tree.git" local git = require "nvim-tree.git"
local live_filter = require "nvim-tree.live-filter"
local log = require "nvim-tree.log" local log = require "nvim-tree.log"
local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON
local Watcher = require "nvim-tree.watcher" local Watcher = require "nvim-tree.watcher"
local M = {} local M = {}
@@ -17,7 +17,17 @@ local M = {}
local function populate_children(handle, cwd, node, git_status, parent) local function populate_children(handle, cwd, node, git_status, parent)
local node_ignored = explorer_node.is_git_ignored(node) local node_ignored = explorer_node.is_git_ignored(node)
local nodes_by_path = utils.bool_record(node.nodes, "absolute_path") local nodes_by_path = utils.bool_record(node.nodes, "absolute_path")
local filter_status = parent.filters:prepare(git_status) 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 while true do
local name, t = vim.loop.fs_scandir_next(handle) local name, t = vim.loop.fs_scandir_next(handle)
if not name then if not name then
@@ -29,8 +39,8 @@ local function populate_children(handle, cwd, node, git_status, parent)
---@type uv.fs_stat.result|nil ---@type uv.fs_stat.result|nil
local stat = vim.loop.fs_stat(abs) local stat = vim.loop.fs_stat(abs)
local filter_reason = parent.filters:should_filter_as_reason(abs, stat, filter_status)
if not parent.filters:should_filter(abs, stat, filter_status) and not nodes_by_path[abs] and Watcher.is_fs_event_capable(abs) then if filter_reason == FILTER_REASON.none and not nodes_by_path[abs] and Watcher.is_fs_event_capable(abs) then
local child = nil local child = nil
if t == "directory" and vim.loop.fs_access(abs, "R") then if t == "directory" and vim.loop.fs_access(abs, "R") then
child = builders.folder(node, abs, name, stat) child = builders.folder(node, abs, name, stat)
@@ -47,6 +57,12 @@ local function populate_children(handle, cwd, node, git_status, parent)
nodes_by_path[child.absolute_path] = true nodes_by_path[child.absolute_path] = true
explorer_node.update_git_status(child, node_ignored, git_status) explorer_node.update_git_status(child, node_ignored, git_status)
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
log.profile_end(profile) log.profile_end(profile)
@@ -82,7 +98,7 @@ function M.explore(node, status, parent)
end end
parent.sorters:sort(node.nodes) parent.sorters:sort(node.nodes)
live_filter.apply_filter(node) parent.live_filter:apply_filter(node)
log.profile_end(profile) log.profile_end(profile)
return node.nodes return node.nodes

View File

@@ -1,4 +1,5 @@
local utils = require "nvim-tree.utils" 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 ---@class Filters to handle all opts.filters and related API
---@field config table hydrated user opts.filters ---@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) or bookmark(self, path, fs_stat and fs_stat.type, status.bookmarks)
end 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 return Filters

View File

@@ -4,6 +4,7 @@ local watch = require "nvim-tree.explorer.watch"
local explorer_node = require "nvim-tree.explorer.node" local explorer_node = require "nvim-tree.explorer.node"
local Filters = require "nvim-tree.explorer.filters" local Filters = require "nvim-tree.explorer.filters"
local Marks = require "nvim-tree.marks" local Marks = require "nvim-tree.marks"
local LiveFilter = require "nvim-tree.explorer.live-filter"
local Sorters = require "nvim-tree.explorer.sorters" local Sorters = require "nvim-tree.explorer.sorters"
local M = {} local M = {}
@@ -15,6 +16,9 @@ M.reload = require("nvim-tree.explorer.reload").reload
---@field absolute_path string ---@field absolute_path string
---@field nodes Node[] ---@field nodes Node[]
---@field open boolean ---@field open boolean
---@field filters Filters
---@field live_filter LiveFilter
---@field sorters Sorter
---@field marks Marks ---@field marks Marks
local Explorer = {} local Explorer = {}
@@ -45,6 +49,7 @@ function Explorer.new(path)
}, Explorer) }, Explorer)
explorer.watcher = watch.create_watcher(explorer) explorer.watcher = watch.create_watcher(explorer)
explorer.filters = Filters:new(M.config, explorer) explorer.filters = Filters:new(M.config, explorer)
explorer.live_filter = LiveFilter:new(M.config, explorer)
explorer:_load(explorer) explorer:_load(explorer)
return explorer return explorer
end end

View File

@@ -2,26 +2,50 @@ local view = require "nvim-tree.view"
local utils = require "nvim-tree.utils" local utils = require "nvim-tree.utils"
local Iterator = require "nvim-tree.iterators.node-iterator" local Iterator = require "nvim-tree.iterators.node-iterator"
local M = { ---@class LiveFilter
---@field explorer Explorer
---@field prefix string
---@field always_show_folders boolean
---@field filter string
local LiveFilter = {}
---@param opts table
---@param explorer Explorer
function LiveFilter:new(opts, explorer)
local o = {
explorer = explorer,
prefix = opts.live_filter.prefix,
always_show_folders = opts.live_filter.always_show_folders,
filter = nil, filter = nil,
} }
setmetatable(o, self)
self.__index = self
return o
end
local function redraw() local function redraw()
require("nvim-tree.renderer").draw() require("nvim-tree.renderer").draw()
end end
---@param node_ Node|nil ---@param node_ Node|nil
local function reset_filter(node_) local function reset_filter(self, node_)
node_ = node_ or require("nvim-tree.core").get_explorer() node_ = node_ or self.explorer
if node_ == nil then if node_ == nil then
return return
end end
node_.hidden_stats = vim.tbl_deep_extend("force", node_.hidden_stats or {}, {
live_filter = 0,
})
Iterator.builder(node_.nodes) Iterator.builder(node_.nodes)
:hidden() :hidden()
:applier(function(node) :applier(function(node)
node.hidden = false node.hidden = false
node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, {
live_filter = 0,
})
end) end)
:iterate() :iterate()
end end
@@ -29,7 +53,7 @@ end
local overlay_bufnr = 0 local overlay_bufnr = 0
local overlay_winnr = 0 local overlay_winnr = 0
local function remove_overlay() local function remove_overlay(self)
if view.View.float.enable and view.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 -- return to normal nvim-tree float behaviour when filter window is closed
vim.api.nvim_create_autocmd("WinLeave", { vim.api.nvim_create_autocmd("WinLeave", {
@@ -48,28 +72,27 @@ local function remove_overlay()
overlay_bufnr = 0 overlay_bufnr = 0
overlay_winnr = 0 overlay_winnr = 0
if M.filter == "" then if self.filter == "" then
M.clear_filter() self:clear_filter()
end end
end end
---@param node Node ---@param node Node
---@return boolean ---@return boolean
local function matches(node) local function matches(self, node)
local explorer = require("nvim-tree.core").get_explorer() if not self.explorer.filters.config.enable then
if not explorer or not explorer.filters.config.enable then
return true return true
end end
local path = node.absolute_path local path = node.absolute_path
local name = vim.fn.fnamemodify(path, ":t") local name = vim.fn.fnamemodify(path, ":t")
return vim.regex(M.filter):match_str(name) ~= nil return vim.regex(self.filter):match_str(name) ~= nil
end end
---@param node_ Node|nil ---@param node_ Node|nil
function M.apply_filter(node_) function LiveFilter:apply_filter(node_)
if not M.filter or M.filter == "" then if not self.filter or self.filter == "" then
reset_filter(node_) reset_filter(self, node_)
return return
end end
@@ -79,6 +102,10 @@ function M.apply_filter(node_)
local filtered_nodes = 0 local filtered_nodes = 0
local nodes = node.group_next and { node.group_next } or node.nodes 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 if nodes then
for _, n in pairs(nodes) do for _, n in pairs(nodes) do
iterate(n) iterate(n)
@@ -88,31 +115,37 @@ function M.apply_filter(node_)
end end
end end
local has_nodes = nodes and (M.always_show_folders or #nodes > filtered_nodes) node.hidden_stats.live_filter = filtered_nodes
local ok, is_match = pcall(matches, node)
local has_nodes = nodes and (self.always_show_folders or #nodes > filtered_nodes)
local ok, is_match = pcall(matches, self, node)
node.hidden = not (has_nodes or (ok and is_match)) node.hidden = not (has_nodes or (ok and is_match))
end end
iterate(node_ or require("nvim-tree.core").get_explorer()) iterate(node_ or self.explorer)
end end
local function record_char() local function record_char(self)
vim.schedule(function() vim.schedule(function()
M.filter = vim.api.nvim_buf_get_lines(overlay_bufnr, 0, -1, false)[1] self.filter = vim.api.nvim_buf_get_lines(overlay_bufnr, 0, -1, false)[1]
M.apply_filter() self:apply_filter()
redraw() redraw()
end) end)
end end
local function configure_buffer_overlay() local function configure_buffer_overlay(self)
overlay_bufnr = vim.api.nvim_create_buf(false, true) overlay_bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_attach(overlay_bufnr, true, { vim.api.nvim_buf_attach(overlay_bufnr, true, {
on_lines = record_char, on_lines = function()
return record_char(self)
end,
}) })
vim.api.nvim_create_autocmd("InsertLeave", { vim.api.nvim_create_autocmd("InsertLeave", {
callback = remove_overlay, callback = function()
return remove_overlay(self)
end,
once = true, once = true,
}) })
@@ -120,17 +153,17 @@ local function configure_buffer_overlay()
end end
---@return integer ---@return integer
local function calculate_overlay_win_width() local function calculate_overlay_win_width(self)
local wininfo = vim.fn.getwininfo(view.get_winnr())[1] local wininfo = vim.fn.getwininfo(view.get_winnr())[1]
if wininfo then if wininfo then
return wininfo.width - wininfo.textoff - #M.prefix return wininfo.width - wininfo.textoff - #self.prefix
end end
return 20 return 20
end end
local function create_overlay() local function create_overlay(self)
if view.View.float.enable then if view.View.float.enable then
-- don't close nvim-tree float when focus is changed to filter window -- don't close nvim-tree float when focus is changed to filter window
vim.api.nvim_clear_autocmds { vim.api.nvim_clear_autocmds {
@@ -140,12 +173,12 @@ local function create_overlay()
} }
end end
configure_buffer_overlay() configure_buffer_overlay(self)
overlay_winnr = vim.api.nvim_open_win(overlay_bufnr, true, { overlay_winnr = vim.api.nvim_open_win(overlay_bufnr, true, {
col = 1, col = 1,
row = 0, row = 0,
relative = "cursor", relative = "cursor",
width = calculate_overlay_win_width(), width = calculate_overlay_win_width(self),
height = 1, height = 1,
border = "none", border = "none",
style = "minimal", style = "minimal",
@@ -157,29 +190,31 @@ local function create_overlay()
vim.api.nvim_buf_set_option(overlay_bufnr, "modifiable", true) ---@diagnostic disable-line: deprecated vim.api.nvim_buf_set_option(overlay_bufnr, "modifiable", true) ---@diagnostic disable-line: deprecated
end end
vim.api.nvim_buf_set_lines(overlay_bufnr, 0, -1, false, { M.filter }) vim.api.nvim_buf_set_lines(overlay_bufnr, 0, -1, false, { self.filter })
vim.cmd "startinsert" vim.cmd "startinsert"
vim.api.nvim_win_set_cursor(overlay_winnr, { 1, #M.filter + 1 }) vim.api.nvim_win_set_cursor(overlay_winnr, { 1, #self.filter + 1 })
end end
function M.start_filtering() function LiveFilter:start_filtering()
view.View.live_filter.prev_focused_node = require("nvim-tree.lib").get_node_at_cursor() view.View.live_filter.prev_focused_node = require("nvim-tree.lib").get_node_at_cursor()
M.filter = M.filter or "" self.filter = self.filter or ""
redraw() redraw()
local row = require("nvim-tree.core").get_nodes_starting_line() - 1 local row = require("nvim-tree.core").get_nodes_starting_line() - 1
local col = #M.prefix > 0 and #M.prefix - 1 or 1 local col = #self.prefix > 0 and #self.prefix - 1 or 1
view.set_cursor { row, col } view.set_cursor { row, col }
-- needs scheduling to let the cursor move before initializing the window -- needs scheduling to let the cursor move before initializing the window
vim.schedule(create_overlay) vim.schedule(function()
return create_overlay(self)
end)
end end
function M.clear_filter() function LiveFilter:clear_filter()
local node = require("nvim-tree.lib").get_node_at_cursor() local node = require("nvim-tree.lib").get_node_at_cursor()
local last_node = view.View.live_filter.prev_focused_node local last_node = view.View.live_filter.prev_focused_node
M.filter = nil self.filter = nil
reset_filter() reset_filter(self)
redraw() redraw()
if node then if node then
@@ -189,9 +224,4 @@ function M.clear_filter()
end end
end end
function M.setup(opts) return LiveFilter
M.prefix = opts.live_filter.prefix
M.always_show_folders = opts.live_filter.always_show_folders
end
return M

View File

@@ -1,10 +1,10 @@
local utils = require "nvim-tree.utils" local utils = require "nvim-tree.utils"
local builders = require "nvim-tree.explorer.node-builders" local builders = require "nvim-tree.explorer.node-builders"
local explorer_node = require "nvim-tree.explorer.node" local explorer_node = require "nvim-tree.explorer.node"
local live_filter = require "nvim-tree.live-filter"
local git = require "nvim-tree.git" local git = require "nvim-tree.git"
local log = require "nvim-tree.log" local log = require "nvim-tree.log"
local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON
local NodeIterator = require "nvim-tree.iterators.node-iterator" local NodeIterator = require "nvim-tree.iterators.node-iterator"
local Watcher = require "nvim-tree.watcher" local Watcher = require "nvim-tree.watcher"
@@ -92,6 +92,16 @@ function M.reload(node, git_status)
local node_ignored = explorer_node.is_git_ignored(node) local node_ignored = explorer_node.is_git_ignored(node)
---@type table<string, Node> ---@type table<string, Node>
local nodes_by_path = utils.key_by(node.nodes, "absolute_path") 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 while true do
local name, t = vim.loop.fs_scandir_next(handle) local name, t = vim.loop.fs_scandir_next(handle)
if not name then if not name then
@@ -102,7 +112,8 @@ function M.reload(node, git_status)
---@type uv.fs_stat.result|nil ---@type uv.fs_stat.result|nil
local stat = vim.loop.fs_stat(abs) 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 remain_childs[abs] = true
-- Recreate node if type changes. -- Recreate node if type changes.
@@ -139,6 +150,12 @@ function M.reload(node, git_status)
n.fs_stat = stat n.fs_stat = stat
end end
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
end end
@@ -165,7 +182,7 @@ function M.reload(node, git_status)
end end
explorer.sorters:sort(node.nodes) explorer.sorters:sort(node.nodes)
live_filter.apply_filter(node) explorer.live_filter:apply_filter(node)
log.profile_end(profile) log.profile_end(profile)
return node.nodes return node.nodes
end end

View File

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

View File

@@ -1,5 +1,4 @@
local core = require "nvim-tree.core" local core = require "nvim-tree.core"
local live_filter = require "nvim-tree.live-filter"
local notify = require "nvim-tree.notify" local notify = require "nvim-tree.notify"
local utils = require "nvim-tree.utils" local utils = require "nvim-tree.utils"
local view = require "nvim-tree.view" local view = require "nvim-tree.view"
@@ -43,6 +42,8 @@ local M = {
---@field lines string[] includes icons etc. ---@field lines string[] includes icons etc.
---@field hl_args AddHighlightArgs[] line highlights ---@field hl_args AddHighlightArgs[] line highlights
---@field signs string[] line signs ---@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 root_cwd string absolute path
---@field private index number ---@field private index number
---@field private depth number ---@field private depth number
@@ -62,6 +63,7 @@ function Builder:new()
markers = {}, markers = {},
signs = {}, signs = {},
extmarks = {}, extmarks = {},
virtual_lines = {},
} }
setmetatable(o, self) setmetatable(o, self)
self.__index = self self.__index = self
@@ -351,7 +353,6 @@ function Builder:build_line(node, idx, num_children)
self.index = self.index + 1 self.index = self.index + 1
node = require("nvim-tree.lib").get_last_group_node(node) node = require("nvim-tree.lib").get_last_group_node(node)
if node.open then if node.open then
self.depth = self.depth + 1 self.depth = self.depth + 1
self:build_lines(node) self:build_lines(node)
@@ -359,9 +360,36 @@ function Builder:build_line(node, idx, num_children)
end end
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 ---@private
function Builder:get_nodes_number(nodes) function Builder:get_nodes_number(nodes)
if not live_filter.filter then local explorer = core.get_explorer()
if not explorer or not explorer.live_filter.filter then
return #nodes return #nodes
end end
@@ -388,6 +416,7 @@ function Builder:build_lines(node)
idx = idx + 1 idx = idx + 1
end end
end end
self:add_hidden_count_string(node)
end end
---@private ---@private
@@ -407,6 +436,7 @@ end
---@private ---@private
function Builder:build_header() function Builder:build_header()
local explorer = core.get_explorer()
if view.is_root_folder_visible(core.get_cwd()) then if view.is_root_folder_visible(core.get_cwd()) then
local root_name = self:format_root_name(M.opts.renderer.root_folder_label) local root_name = self:format_root_name(M.opts.renderer.root_folder_label)
table.insert(self.lines, root_name) table.insert(self.lines, root_name)
@@ -414,8 +444,8 @@ function Builder:build_header()
self.index = 1 self.index = 1
end end
if live_filter.filter then if explorer and explorer.live_filter.filter then
local filter_line = string.format("%s/%s/", M.opts.live_filter.prefix, live_filter.filter) local filter_line = string.format("%s/%s/", M.opts.live_filter.prefix, explorer.live_filter.filter)
table.insert(self.lines, filter_line) table.insert(self.lines, filter_line)
local prefix_length = string.len(M.opts.live_filter.prefix) local prefix_length = string.len(M.opts.live_filter.prefix)
self:insert_highlight({ "NvimTreeLiveFilterPrefix" }, 0, prefix_length) self:insert_highlight({ "NvimTreeLiveFilterPrefix" }, 0, prefix_length)
@@ -442,7 +472,50 @@ function Builder:build()
return self return self
end 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) function Builder.setup(opts)
setup_hidden_display_function(opts)
M.opts = opts M.opts = opts
-- priority order -- priority order

View File

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

View File

@@ -14,12 +14,13 @@ local SIGN_GROUP = "NvimTreeRendererSigns"
local namespace_highlights_id = vim.api.nvim_create_namespace "NvimTreeHighlights" local namespace_highlights_id = vim.api.nvim_create_namespace "NvimTreeHighlights"
local namespace_extmarks_id = vim.api.nvim_create_namespace "NvimTreeExtmarks" 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 bufnr number
---@param lines string[] ---@param lines string[]
---@param hl_args AddHighlightArgs[] ---@param hl_args AddHighlightArgs[]
---@param signs string[] ---@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 if vim.fn.has "nvim-0.10" == 1 then
vim.api.nvim_set_option_value("modifiable", true, { buf = bufnr }) vim.api.nvim_set_option_value("modifiable", true, { buf = bufnr })
else else
@@ -50,6 +51,15 @@ local function _draw(bufnr, lines, hl_args, signs, extmarks)
}) })
end end
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 end
function M.render_hl(bufnr, hl) function M.render_hl(bufnr, hl)
@@ -79,7 +89,7 @@ function M.draw()
local builder = Builder:new():build() 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 if cursor and #builder.lines >= cursor[1] then
vim.api.nvim_win_set_cursor(view.get_winnr() or 0, cursor) vim.api.nvim_win_set_cursor(view.get_winnr() or 0, cursor)

View File

@@ -112,7 +112,10 @@ function M.find_node(nodes, fn)
end) end)
:iterate() :iterate()
i = require("nvim-tree.view").is_root_folder_visible() and i or i - 1 i = require("nvim-tree.view").is_root_folder_visible() and i or i - 1
i = require("nvim-tree.live-filter").filter and i + 1 or i local explorer = require("nvim-tree.core").get_explorer()
if explorer and explorer.live_filter.filter then
i = i + 1
end
return node, i return node, i
end end
@@ -181,6 +184,26 @@ function M.get_parent_of_group(node)
return node return node
end 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 --- Return visible nodes indexed by line
---@param nodes_all Node[] ---@param nodes_all Node[]
---@param line_start number ---@param line_start number

View File

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