Skip to content

Commit aa897e9

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

File tree

11 files changed

+320
-146
lines changed

11 files changed

+320
-146
lines changed

lua/nvim-tree/actions/change-dir.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function M.force_dirchange(foldername, with_open)
3535
vim.cmd("lcd " .. vim.fn.fnameescape(foldername))
3636
end
3737
end
38-
require("nvim-tree.core").init(foldername)
38+
core.init(foldername)
3939
if with_open then
4040
require("nvim-tree.lib").open()
4141
else

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

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,33 +13,35 @@ function M.fn(fname)
1313
end
1414
running[fname] = true
1515

16-
local i = view.is_root_folder_visible() and 1 or 0
16+
local i = core.get_nodes_starting_line() - 1
1717
local tree_altered = false
1818

1919
local function iterate_nodes(nodes)
2020
for _, node in ipairs(nodes) do
21-
i = i + 1
22-
if node.absolute_path == fname then
23-
return i
24-
end
25-
26-
local path_matches = node.nodes and vim.startswith(fname, node.absolute_path .. utils.path_separator)
27-
if path_matches then
28-
if not node.open then
29-
node.open = true
30-
tree_altered = true
31-
end
32-
33-
if #node.nodes == 0 then
34-
core.get_explorer():expand(node)
21+
if not node.hidden then
22+
i = i + 1
23+
if node.absolute_path == fname then
24+
return i
3525
end
3626

37-
if iterate_nodes(node.nodes) ~= nil then
38-
return i
27+
local path_matches = node.nodes and vim.startswith(fname, node.absolute_path .. utils.path_separator)
28+
if path_matches then
29+
if not node.open then
30+
node.open = true
31+
tree_altered = true
32+
end
33+
34+
if #node.nodes == 0 then
35+
core.get_explorer():expand(node)
36+
end
37+
38+
if iterate_nodes(node.nodes) ~= nil then
39+
return i
40+
end
41+
-- mandatory to iterate i
42+
elseif node.open then
43+
iterate_nodes(node.nodes)
3944
end
40-
-- mandatory to iterate i
41-
elseif node.open then
42-
iterate_nodes(node.nodes)
4345
end
4446
end
4547
end

lua/nvim-tree/actions/init.lua

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ 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" },
4344
{ key = "q", action = "close" },
4445
{ key = "g?", action = "toggle_help" },
4546
{ key = "W", action = "collapse_all" },
@@ -64,6 +65,7 @@ local keypress_funcs = {
6465
first_sibling = require("nvim-tree.actions.movements").sibling(-math.huge),
6566
full_rename = require("nvim-tree.actions.rename-file").fn(true),
6667
last_sibling = require("nvim-tree.actions.movements").sibling(math.huge),
68+
live_filter = require("nvim-tree.live-filter").start_filtering,
6769
next_git_item = require("nvim-tree.actions.movements").find_git_item "next",
6870
next_sibling = require("nvim-tree.actions.movements").sibling(1),
6971
parent_node = require("nvim-tree.actions.movements").parent_node(false),
@@ -90,6 +92,11 @@ function M.on_keypress(action)
9092
if view.is_help_ui() and action ~= "toggle_help" then
9193
return
9294
end
95+
96+
if action == "live_filter" then
97+
return keypress_funcs[action]()
98+
end
99+
93100
local node = lib.get_node_at_cursor()
94101
if not node then
95102
return

lua/nvim-tree/actions/movements.lua

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ local view = require "nvim-tree.view"
33
local diagnostics = require "nvim-tree.diagnostics"
44
local renderer = require "nvim-tree.renderer"
55
local core = require "nvim-tree.core"
6-
76
local lib = function()
87
return require "nvim-tree.lib"
98
end
@@ -58,7 +57,7 @@ function M.parent_node(should_close)
5857
parent.open = false
5958
altered_tree = true
6059
end
61-
if not view.is_root_folder_visible() then
60+
if not view.is_root_folder_visible(core.get_cwd()) then
6261
line = line - 1
6362
end
6463
view.set_cursor { line, 0 }
@@ -91,7 +90,7 @@ function M.sibling(direction)
9190
end
9291

9392
if line > 0 then
94-
parent = core.get_explorer()
93+
parent = core.get_explorer().nodes
9594
else
9695
_, parent = iter(core.get_explorer().nodes, true)
9796
if parent ~= nil and #parent.nodes > 1 then
@@ -111,7 +110,7 @@ function M.sibling(direction)
111110
local target_node = parent.nodes[index]
112111

113112
line, _ = get_line_from_node(target_node)(core.get_explorer().nodes, true)
114-
if not view.is_root_folder_visible() then
113+
if not view.is_root_folder_visible(core.get_cwd()) then
115114
line = line - 1
116115
end
117116
view.set_cursor { line, 0 }
@@ -121,7 +120,7 @@ end
121120
function M.find_git_item(where)
122121
return function()
123122
local node_cur = lib().get_node_at_cursor()
124-
local nodes_by_line = lib().get_nodes_by_line(core.get_explorer().nodes, view.View.hide_root_folder and 1 or 2)
123+
local nodes_by_line = lib().get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line())
125124

126125
local cur, first, prev, nex = nil, nil, nil, nil
127126
for line, node in pairs(nodes_by_line) do

lua/nvim-tree/actions/search-node.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ function M.fn()
7878
end
7979

8080
if found_something and view.is_visible() then
81-
if view.is_root_folder_visible() then
81+
if view.is_root_folder_visible(core.get_cwd()) then
8282
index = index + 1
8383
end
8484

lua/nvim-tree/core.lua

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

46
local M = {}
57

6-
local first_init_done = false
7-
88
TreeExplorer = nil
9+
local first_init_done = false
910

1011
function M.init(foldername)
1112
TreeExplorer = explorer.Explorer.new(foldername)
@@ -15,12 +16,23 @@ function M.init(foldername)
1516
end
1617
end
1718

19+
function M.get_cwd()
20+
return TreeExplorer.cwd
21+
end
22+
1823
function M.get_explorer()
1924
return TreeExplorer
2025
end
2126

22-
function M.get_cwd()
23-
return TreeExplorer.cwd
27+
function M.get_nodes_starting_line()
28+
local offset = 2
29+
if not view.is_root_folder_visible(M.get_cwd()) then
30+
offset = offset - 1
31+
end
32+
if live_filter.filter then
33+
return offset + 1
34+
end
35+
return offset
2436
end
2537

2638
return M

lua/nvim-tree/lib.lua

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ function M.get_nodes_by_line(nodes_all, line_start)
1414
local line = line_start
1515
local function iter(nodes)
1616
for _, node in ipairs(nodes) do
17-
nodes_by_line[line] = node
18-
line = line + 1
19-
if node.open == true then
20-
local child = iter(node.nodes)
21-
if child ~= nil then
22-
return child
17+
if not node.hidden then
18+
nodes_by_line[line] = node
19+
line = line + 1
20+
if node.open == true then
21+
local child = iter(node.nodes)
22+
if child ~= nil then
23+
return child
24+
end
2325
end
2426
end
2527
end
@@ -32,27 +34,25 @@ function M.get_node_at_cursor()
3234
if not core.get_explorer() then
3335
return
3436
end
37+
3538
local winnr = view.get_winnr()
36-
local hide_root_folder = view.View.hide_root_folder
3739
if not winnr then
3840
return
3941
end
42+
4043
local cursor = api.nvim_win_get_cursor(view.get_winnr())
4144
local line = cursor[1]
4245
if view.is_help_ui() then
4346
local help_lines = require("nvim-tree.renderer.help").compute_lines()
4447
local help_text = M.get_nodes_by_line(help_lines, 1)[line]
4548
return { name = help_text }
46-
else
47-
if line == 1 and core.get_explorer().cwd ~= "/" and not hide_root_folder then
48-
return { name = ".." }
49-
end
49+
end
5050

51-
if core.get_explorer().cwd == "/" then
52-
line = line + 1
53-
end
54-
return M.get_nodes_by_line(core.get_explorer().nodes, view.View.hide_root_folder and 1 or 2)[line]
51+
if line == 1 and view.is_root_folder_visible() then
52+
return { name = ".." }
5553
end
54+
55+
return M.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line())[line]
5656
end
5757

5858
-- If node is grouped, return the last node in the group. Otherwise, return the given node.

lua/nvim-tree/live-filter.lua

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
-- TODO
2+
-- fix(movements): first/last siblings (K/J)
3+
-- fix(movements): next/prev siblings (>/<)
4+
-- fix(movements): parent node (P)
5+
-- fix(movements): close_node (backspace)
6+
-- fix(expansion): new expanded nodes are not filtered
7+
-- check(rendering): padding with indent markers
8+
-- improve: matching algorithm
9+
-- improve: folder expansion -> might involve performance issues that could be resolved with max depth search
10+
-- improve: user configuration
11+
-- improve: make prompt prettier
12+
-- finish: documentation
13+
local a = vim.api
14+
15+
local view = require "nvim-tree.view"
16+
17+
local M = {
18+
filter = nil,
19+
prefix = "Filtering on: ",
20+
depth = 3,
21+
}
22+
23+
local function redraw()
24+
require("nvim-tree.renderer").draw()
25+
end
26+
27+
local function reset_filter()
28+
local function iterate(n)
29+
n.hidden = false
30+
if n.nodes then
31+
for _, _n in pairs(n.nodes) do
32+
iterate(_n)
33+
end
34+
end
35+
end
36+
iterate(TreeExplorer)
37+
end
38+
39+
local overlay_bufnr = nil
40+
local overlay_winnr = nil
41+
42+
function M.remove_overlay()
43+
vim.cmd "augroup NvimTreeRecordFilter"
44+
vim.cmd "au!"
45+
vim.cmd "augroup END"
46+
47+
a.nvim_win_close(overlay_winnr, { force = true })
48+
overlay_bufnr = nil
49+
overlay_winnr = nil
50+
51+
if M.filter == "" then
52+
M.filter = nil
53+
reset_filter()
54+
redraw()
55+
end
56+
end
57+
58+
local function matches(node)
59+
local path = node.cwd or node.link_to or node.absolute_path
60+
local name = vim.fn.fnamemodify(path, ":t")
61+
return vim.fn.match(name, M.filter) ~= -1
62+
end
63+
64+
local function apply_filter()
65+
if not M.filter or M.filter == "" then
66+
reset_filter()
67+
return
68+
end
69+
70+
local function iterate(node)
71+
local filtered_nodes = 0
72+
if node.nodes then
73+
for _, n in pairs(node.nodes) do
74+
if not iterate(n) then
75+
filtered_nodes = filtered_nodes + 1
76+
end
77+
end
78+
end
79+
80+
local has_nodes = #(node.nodes or {}) > filtered_nodes
81+
if has_nodes or matches(node) then
82+
node.hidden = false
83+
return true
84+
end
85+
86+
node.hidden = true
87+
return false
88+
end
89+
90+
iterate(TreeExplorer)
91+
end
92+
93+
local function record_char()
94+
vim.schedule(function()
95+
M.filter = a.nvim_buf_get_lines(overlay_bufnr, 0, -1, false)[1]
96+
apply_filter()
97+
redraw()
98+
end)
99+
end
100+
101+
local function configure_buffer_overlay()
102+
overlay_bufnr = a.nvim_create_buf(false, true)
103+
104+
a.nvim_buf_attach(overlay_bufnr, true, {
105+
on_lines = record_char,
106+
})
107+
vim.cmd "augroup NvimTreeRecordFilter"
108+
vim.cmd "au!"
109+
vim.cmd "au InsertLeave * lua require'nvim-tree.live-filter'.remove_overlay()"
110+
vim.cmd "augroup END"
111+
112+
a.nvim_buf_set_keymap(overlay_bufnr, "i", "<CR>", "<cmd>stopinsert<CR>", {})
113+
end
114+
115+
local function create_overlay()
116+
configure_buffer_overlay()
117+
overlay_winnr = a.nvim_open_win(overlay_bufnr, true, {
118+
col = 1,
119+
row = 0,
120+
relative = "cursor",
121+
width = math.max(20, a.nvim_win_get_width(view.get_winnr()) - #M.prefix - 2),
122+
height = 1,
123+
border = "none",
124+
style = "minimal",
125+
})
126+
a.nvim_buf_set_option(overlay_bufnr, "modifiable", true)
127+
a.nvim_buf_set_lines(overlay_bufnr, 0, -1, false, { M.filter })
128+
vim.cmd "startinsert"
129+
a.nvim_win_set_cursor(overlay_winnr, { 1, #M.filter + 1 })
130+
end
131+
132+
function M.start_filtering()
133+
M.filter = M.filter or ""
134+
135+
redraw()
136+
local row = require("nvim-tree.core").get_nodes_starting_line() - 1
137+
view.set_cursor { row, #M.prefix - 1 }
138+
-- needs scheduling to let the cursor move before initializing the window
139+
vim.schedule(create_overlay)
140+
end
141+
142+
return M

0 commit comments

Comments
 (0)