Skip to content

Commit ecca2bc

Browse files
committed
feat(live-filter): add ability to live filter out nodes in the tree
1 parent 9d26594 commit ecca2bc

File tree

16 files changed

+310
-100
lines changed

16 files changed

+310
-100
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ require'nvim-tree'.setup { -- BEGIN_DEFAULT_OPTS
156156
icons = {
157157
webdev_colors = true,
158158
git_placement = "before",
159-
}
159+
},
160160
},
161161
hijack_directories = {
162162
enable = true,
@@ -216,6 +216,10 @@ require'nvim-tree'.setup { -- BEGIN_DEFAULT_OPTS
216216
cmd = "trash",
217217
require_confirm = true,
218218
},
219+
live_filter = {
220+
prefix = "[FILTER]: ",
221+
always_show_folders = true,
222+
},
219223
log = {
220224
enable = false,
221225
truncate = false,
@@ -269,6 +273,7 @@ require'nvim-tree'.setup { -- BEGIN_DEFAULT_OPTS
269273
- `S` will prompt the user to enter a path and then expands the tree to match the path
270274
- `.` will enter vim command mode with the file the cursor is on
271275
- `C-k` will toggle a popup with file infos about the file under the cursor
276+
- `f` will allow you to filter nodes dynamically based on regex matching.
272277

273278
### Settings
274279

@@ -330,6 +335,8 @@ local list = {
330335
{ key = "]c", action = "next_git_item" },
331336
{ key = "-", action = "dir_up" },
332337
{ key = "s", action = "system_open" },
338+
{ key = "f", action = "live_filter" },
339+
{ key = "F", action = "clear_live_filter" },
333340
{ key = "q", action = "close" },
334341
{ key = "g?", action = "toggle_help" },
335342
{ key = "W", action = "collapse_all" },

doc/nvim-tree-lua.txt

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ Values may be functions. Warning: this may result in unexpected behaviour.
124124
icons = {
125125
webdev_colors = true,
126126
git_placement = "before",
127-
}
127+
},
128128
},
129129
hijack_directories = {
130130
enable = true,
@@ -184,6 +184,10 @@ Values may be functions. Warning: this may result in unexpected behaviour.
184184
cmd = "trash",
185185
require_confirm = true,
186186
},
187+
live_filter = {
188+
prefix = "[FILTER]: ",
189+
always_show_folders = true,
190+
},
187191
log = {
188192
enable = false,
189193
truncate = false,
@@ -519,6 +523,20 @@ Configuration for various actions.
519523
'+' (system), otherwise, it will be stored in '1' and '"'.
520524
Type: `boolean`, Default: `true`
521525

526+
*nvim-tree.live_filter*
527+
Configurations for the live_filtering feature.
528+
The live filter allows you to filter the tree nodes dynamically, based on
529+
regex matching (see |vim.regex|).
530+
This feature is bound to the `f` key by default.
531+
The filter can be cleared with the `F` key by default.
532+
533+
*nvim-tree.live_filter.prefix*
534+
Prefix of the filter displayed in the buffer.
535+
Type: `string`, Default: `"[FILTER]: "`
536+
537+
*nvim-tree.live_filter.always_show_folders*
538+
Wether to filter folders or not.
539+
Type: `boolean`, Default: `true`
522540

523541
*nvim-tree.log*
524542
Configuration for diagnostic logging.
@@ -764,6 +782,8 @@ Defaults to:
764782
{ key = "]c", action = "next_git_item" },
765783
{ key = "-", action = "dir_up" },
766784
{ key = "s", action = "system_open" },
785+
{ key = "f", action = "live_filter" },
786+
{ key = "F", action = "clear_live_filter" },
767787
{ key = "q", action = "close" },
768788
{ key = "g?", action = "toggle_help" },
769789
{ key = 'W', action = "collapse_all" },
@@ -773,6 +793,7 @@ Defaults to:
773793
{ key = "U", action = "toggle_custom" },
774794
}
775795
<
796+
776797
The `list` option in `view.mappings.list` is a table of
777798

778799
- key can be either a string or a table of string (lhs)
@@ -879,6 +900,11 @@ NvimTreeFileRenamed
879900
NvimTreeFileNew
880901
NvimTreeFileDeleted
881902

903+
There are 2 highlight groups for the live filter feature
904+
905+
NvimTreeLiveFilterPrefix
906+
NvimTreeLiveFilterValue
907+
882908
==============================================================================
883909
vinegar style *nvim-tree-vinegar*
884910

lua/nvim-tree.lua

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,10 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
438438
cmd = "trash",
439439
require_confirm = true,
440440
},
441+
live_filter = {
442+
prefix = "[FILTER]: ",
443+
always_show_folders = true,
444+
},
441445
log = {
442446
enable = false,
443447
truncate = false,
@@ -538,6 +542,7 @@ function M.setup(conf)
538542
require("nvim-tree.view").setup(opts)
539543
require("nvim-tree.lib").setup(opts)
540544
require("nvim-tree.renderer").setup(opts)
545+
require("nvim-tree.live-filter").setup(opts)
541546

542547
setup_vim_commands()
543548
setup_autocommands(opts)

lua/nvim-tree/actions/find-file.lua

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,35 +29,37 @@ function M.fn(fname)
2929

3030
local function iterate_nodes(nodes)
3131
for _, node in ipairs(nodes) do
32-
i = i + 1
32+
if not node.hidden then
33+
i = i + 1
3334

34-
if not node.absolute_path or not uv.fs_stat(node.absolute_path) then
35-
break
36-
end
37-
38-
-- match against node absolute and link, as symlinks themselves will differ
39-
if node.absolute_path == fname_real or node.link_to == fname_real then
40-
return i
41-
end
42-
local abs_match = vim.startswith(fname_real, node.absolute_path .. utils.path_separator)
43-
local link_match = node.link_to and vim.startswith(fname_real, node.link_to .. utils.path_separator)
44-
local path_matches = node.nodes and (abs_match or link_match)
45-
if path_matches then
46-
if not node.open then
47-
node.open = true
48-
tree_altered = true
35+
if not node.absolute_path or not uv.fs_stat(node.absolute_path) then
36+
break
4937
end
5038

51-
if #node.nodes == 0 then
52-
core.get_explorer():expand(node)
39+
-- match against node absolute and link, as symlinks themselves will differ
40+
if node.absolute_path == fname_real or node.link_to == fname_real then
41+
return i
5342
end
43+
local abs_match = vim.startswith(fname_real, node.absolute_path .. utils.path_separator)
44+
local link_match = node.link_to and vim.startswith(fname_real, node.link_to .. utils.path_separator)
45+
local path_matches = node.nodes and (abs_match or link_match)
46+
if path_matches then
47+
if not node.open then
48+
node.open = true
49+
tree_altered = true
50+
end
5451

55-
if iterate_nodes(node.nodes) ~= nil then
56-
return i
52+
if #node.nodes == 0 then
53+
core.get_explorer():expand(node)
54+
end
55+
56+
if iterate_nodes(node.nodes) ~= nil then
57+
return i
58+
end
59+
-- mandatory to iterate i
60+
elseif node.open then
61+
iterate_nodes(node.nodes)
5762
end
58-
-- mandatory to iterate i
59-
elseif node.open then
60-
iterate_nodes(node.nodes)
6163
end
6264
end
6365
end

lua/nvim-tree/actions/init.lua

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ local M = {
4040
{ key = "]c", action = "next_git_item" },
4141
{ key = "-", action = "dir_up" },
4242
{ key = "s", action = "system_open" },
43+
{ key = "f", action = "live_filter" },
44+
{ key = "F", action = "clear_live_filter" },
4345
{ key = "q", action = "close" },
4446
{ key = "g?", action = "toggle_help" },
4547
{ key = "W", action = "collapse_all" },
@@ -65,6 +67,8 @@ local keypress_funcs = {
6567
first_sibling = require("nvim-tree.actions.movements").sibling(-math.huge),
6668
full_rename = require("nvim-tree.actions.rename-file").fn(true),
6769
last_sibling = require("nvim-tree.actions.movements").sibling(math.huge),
70+
live_filter = require("nvim-tree.live-filter").start_filtering,
71+
clear_live_filter = require("nvim-tree.live-filter").clear_filter,
6872
next_git_item = require("nvim-tree.actions.movements").find_git_item "next",
6973
next_sibling = require("nvim-tree.actions.movements").sibling(1),
7074
parent_node = require("nvim-tree.actions.movements").parent_node(false),
@@ -92,6 +96,11 @@ function M.on_keypress(action)
9296
if view.is_help_ui() and action ~= "toggle_help" then
9397
return
9498
end
99+
100+
if action == "live_filter" or action == "clear_live_filter" then
101+
return keypress_funcs[action]()
102+
end
103+
95104
local node = lib.get_node_at_cursor()
96105
if not node then
97106
return

lua/nvim-tree/actions/movements.lua

Lines changed: 22 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,25 @@ local lib = require "nvim-tree.lib"
66

77
local M = {}
88

9-
local function get_line_from_node(node, find_parent)
9+
local function get_index_of(node, nodes)
1010
local node_path = node.absolute_path
11+
local line = 1
1112

12-
if find_parent then
13-
node_path = node.absolute_path:match("(.*)" .. utils.path_separator)
14-
end
15-
16-
local line = core.get_nodes_starting_line()
17-
local function iter(nodes, recursive)
18-
for _, _node in ipairs(nodes) do
13+
for _, _node in ipairs(nodes) do
14+
if not _node.hidden then
1915
local n = lib.get_last_group_node(_node)
2016
if node_path == n.absolute_path then
21-
return line, _node
17+
return line
2218
end
2319

2420
line = line + 1
25-
if _node.open == true and recursive then
26-
local _, child = iter(_node.nodes, recursive)
27-
if child ~= nil then
28-
return line, child
29-
end
30-
end
3121
end
3222
end
33-
return iter
3423
end
3524

3625
function M.parent_node(should_close)
26+
should_close = should_close or false
27+
3728
return function(node)
3829
if should_close and node.open then
3930
node.open = false
@@ -64,41 +55,26 @@ function M.sibling(direction)
6455
return
6556
end
6657

67-
local iter = get_line_from_node(node, true)
68-
local node_path = node.absolute_path
58+
local parent = node.parent or core.get_explorer()
59+
local parent_nodes = vim.tbl_filter(function(n)
60+
return not n.hidden
61+
end, parent.nodes)
6962

70-
local line = 0
71-
local parent, _
63+
local node_index = get_index_of(node, parent_nodes)
7264

73-
-- Check if current node is already at root nodes
74-
for index, _node in ipairs(core.get_explorer().nodes) do
75-
if node_path == _node.absolute_path then
76-
line = index
77-
end
65+
local target_idx = node_index + direction
66+
if target_idx < 1 then
67+
target_idx = 1
68+
elseif target_idx > #parent_nodes then
69+
target_idx = #parent_nodes
7870
end
7971

80-
if line > 0 then
81-
parent = core.get_explorer()
82-
else
83-
_, parent = iter(core.get_explorer().nodes, true)
84-
if parent ~= nil and #parent.nodes > 1 then
85-
line, _ = get_line_from_node(node)(parent.nodes)
86-
end
87-
88-
-- Ignore parent line count
89-
line = line - 1
90-
end
91-
92-
local index = line + direction
93-
if index < 1 then
94-
index = 1
95-
elseif index > #parent.nodes then
96-
index = #parent.nodes
97-
end
98-
local target_node = parent.nodes[index]
72+
local target_node = parent_nodes[target_idx]
73+
local _, line = utils.find_node(core.get_explorer().nodes, function(n)
74+
return n.absolute_path == target_node.absolute_path
75+
end)
9976

100-
line, _ = get_line_from_node(target_node)(core.get_explorer().nodes, true)
101-
view.set_cursor { line, 0 }
77+
view.set_cursor { line + 1, 0 }
10278
end
10379
end
10480

lua/nvim-tree/colors.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ local function get_hl_groups()
5252
GitNew = { fg = colors.yellow },
5353

5454
WindowPicker = { gui = "bold", fg = "#ededed", bg = "#4493c8" },
55+
LiveFilterPrefix = { gui = "bold", fg = colors.purple },
56+
LiveFilterValue = { gui = "bold", fg = "#fff" },
5557
}
5658
end
5759

lua/nvim-tree/core.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
local events = require "nvim-tree.events"
22
local explorer = require "nvim-tree.explorer"
3+
local live_filter = require "nvim-tree.live-filter"
34
local view = require "nvim-tree.view"
45

56
local M = {}
@@ -28,6 +29,9 @@ function M.get_nodes_starting_line()
2829
if view.is_root_folder_visible(M.get_cwd()) then
2930
offset = offset + 1
3031
end
32+
if live_filter.filter then
33+
return offset + 1
34+
end
3135
return offset
3236
end
3337

lua/nvim-tree/explorer/explore.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ local builders = require "nvim-tree.explorer.node-builders"
66
local common = require "nvim-tree.explorer.common"
77
local sorters = require "nvim-tree.explorer.sorters"
88
local filters = require "nvim-tree.explorer.filters"
9+
local live_filter = require "nvim-tree.live-filter"
910

1011
local M = {}
1112

@@ -76,6 +77,7 @@ function M.explore(node, status)
7677
end
7778

7879
sorters.merge_sort(node.nodes, sorters.node_comparator)
80+
live_filter.apply_filter(node)
7981
return node.nodes
8082
end
8183

lua/nvim-tree/explorer/reload.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ local builders = require "nvim-tree.explorer.node-builders"
66
local common = require "nvim-tree.explorer.common"
77
local filters = require "nvim-tree.explorer.filters"
88
local sorters = require "nvim-tree.explorer.sorters"
9+
local live_filter = require "nvim-tree.live-filter"
910

1011
local M = {}
1112

@@ -77,6 +78,7 @@ function M.reload(node, status)
7778
end
7879

7980
sorters.merge_sort(node.nodes, sorters.node_comparator)
81+
live_filter.apply_filter(node)
8082
return node.nodes
8183
end
8284

0 commit comments

Comments
 (0)