Skip to content

Commit 113fb3b

Browse files
committed
feat: add fuzzy_finder command which improves filter_as_you_type, closes #129
1 parent f963c7b commit 113fb3b

File tree

9 files changed

+146
-34
lines changed

9 files changed

+146
-34
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ use {
8080
["H"] = "toggle_hidden",
8181
["I"] = "toggle_gitignore",
8282
["R"] = "refresh",
83-
["/"] = "filter_as_you_type",
83+
["/"] = "fuzzy_finder",
84+
--["/"] = "filter_as_you_type", -- this was the default until v1.28
8485
--["/"] = "none" -- Assigning a key to "none" will remove the default mapping
8586
["f"] = "filter_on_submit",
8687
["<c-x>"] = "clear_filter",

doc/neo-tree.txt

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,28 +143,40 @@ Note: The "selected" item is the line the cursor is currently on.
143143

144144
<2-LeftMouse> = open: Expand or collapse a folder. If a file is selected,
145145
open it in the window closest to the tree.
146+
146147
<cr> = open: Same as above.
148+
147149
S = open_split: Same as open, but opens in a new horizontal split.
150+
148151
s = open_vsplit: Same as open, but opens in a vertical split
152+
149153
<bs> = navigate_up: Moves the root directory up one level.
154+
150155
. = set_root: Changes the root directory to the currently
151156
selected folder.
152157

153158

154159
FILE ACTIONS *neo-tree-file-actions*
155160
a = add: Create a new file or directory.
161+
156162
d = delete: Delete the selected file or directory.
163+
157164
r = rename: Rename the selected file or directory.
165+
158166
c = copy_to_clipboard: Mark file to be copied.
167+
159168
x = cut_to_clipboard: Mark file to be cut (moved).
169+
160170
p = paste_from_clipboard: Copy/move each marked file in the to the
161171
selected folder.
162172

163173

164174
VIEW CHANGES *neo-tree-view-changes*
165175
H = toggle_hidden: Toggle whether hidden files (.*) are shown or not.
176+
166177
I = toggle_gitignore: Toggle whether the gitignore file is
167178
respected.
179+
168180
R = refresh: Rescan the filesystem and redraw the tree. Changes
169181
made within nvim should be detected automatically, but
170182
this is useful for changes made elsewhere.
@@ -175,15 +187,27 @@ FILTER *neo-tree-filter*
175187
files and folders that contain the specified term as
176188
you type. This will use fd if it is installed, or
177189
find, or which if you are on Windows.
190+
191+
As of v1.28, this acts like a fuzzy finder,
192+
meaning that pressing up/down while the filter
193+
window is open will move the cursor up and down in
194+
the tree, and pressing `<enter>` will open that
195+
item and clear the filter. Any other method of
196+
closing the filter window will also clear the
197+
filter.
198+
178199
f = filter_on_submit: Same as above, but does not search until you hit
179200
enter. Useful if filter_as_you_type is too slow.
201+
Also useful if you want to leave the tree
202+
filtered.
203+
180204
<C-x> = clear_filter: Removes the filter.
181205

182206

183207
CUSTOM COMMANDS *neo-tree-custom-commands*
184208

185209
If you want to define your own command, you have two options:
186-
1. You can define (or override) a command in the `commands` section of the
210+
1. You can define (or oVErride) a command in the `commands` section of the
187211
config for each source, then reference that by name in a mapping.
188212
2. You can map directly to a function and skip defining a command.
189213

lua/neo-tree.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ M.reveal_in_split = function(source_name, toggle_if_open)
191191
return
192192
end
193193
end
194+
--TODO: if we are currently in a sidebar, don't replace it with a split style
194195
manager.reveal_in_split(source_name)
195196
end
196197

@@ -203,6 +204,7 @@ M.show_in_split = function(source_name, toggle_if_open)
203204
return
204205
end
205206
end
207+
--TODO: if we are currently in a sidebar, don't replace it with a split style
206208
manager.show_in_split(source_name)
207209
end
208210

lua/neo-tree/defaults.lua

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,16 @@ local config = {
4949
--},
5050
filesystem = {
5151
follow_current_file = false, -- This will find and focus the file in the active buffer
52-
-- every time the current file is changed while the tree
53-
-- is open.
52+
-- every time the current file is changed while the tree
53+
-- is open.
5454
use_libuv_file_watcher = false, -- This will use the OS level file watchers to detect
55-
-- changes instead of relying on nvim autocmd events.
56-
window = { -- see https://github.com/MunifTanjim/nui.nvim/tree/main/lua/nui/popup
57-
-- for possible options. These can also be functions that return these
58-
-- options.
55+
-- changes instead of relying on nvim autocmd events.
56+
window = { -- see https://github.com/MunifTanjim/nui.nvim/tree/main/lua/nui/popup
57+
-- for possible options. These can also be functions that return these
58+
-- options.
5959
position = "left", -- left, right, float, split
6060
width = 40, -- applies to left and right positions
61-
popup = { -- settings that apply to float position only
61+
popup = { -- settings that apply to float position only
6262
size = {
6363
height = "80%",
6464
width = "50%",
@@ -81,7 +81,8 @@ local config = {
8181
["H"] = "toggle_hidden",
8282
["I"] = "toggle_gitignore",
8383
["R"] = "refresh",
84-
["/"] = "filter_as_you_type",
84+
["/"] = "fuzzy_finder",
85+
--["/"] = "filter_as_you_type", -- this was the default until v1.28
8586
["f"] = "filter_on_submit",
8687
["<C-x>"] = "clear_filter",
8788
["a"] = "add",

lua/neo-tree/sources/example/init.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
local vim = vim
55
local renderer = require("neo-tree.ui.renderer")
66
local manager = require("neo-tree.sources.manager")
7+
local events = require("neo-tree.events")
78

89
local M = { name = "example" }
910

lua/neo-tree/sources/filesystem/commands.lua

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ M.filter_on_submit = function(state)
5353
filter.show_filter(state, false)
5454
end
5555

56+
---Shows the filter input in fuzzy finder mode.
57+
M.fuzzy_finder = function(state)
58+
filter.show_filter(state, true, true)
59+
end
60+
5661
---Navigate up one level.
5762
M.navigate_up = function(state)
5863
local parent_path, _ = utils.split_path(state.path)

lua/neo-tree/sources/filesystem/init.lua

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,22 +170,56 @@ M.navigate = function(state, path, path_to_reveal, callback)
170170
end, utils.debounce_strategy.CALL_FIRST_AND_LAST, 500)
171171
end
172172

173-
M.reset_search = function(state, refresh)
173+
M.reset_search = function(state, refresh, open_current_node)
174174
log.trace("reset_search")
175175
if refresh == nil then
176176
refresh = true
177177
end
178178
if state.open_folders_before_search then
179-
log.trace("reset_search: open_folders_before_search")
180179
state.force_open_folders = utils.table_copy(state.open_folders_before_search)
181180
else
182-
log.trace("reset_search: why are there no open_folders_before_search?")
183181
state.force_open_folders = nil
184182
end
185183
state.search_pattern = nil
186184
state.open_folders_before_search = nil
187185
if refresh then
188-
M.navigate(state)
186+
if open_current_node then
187+
local node = state.tree:get_node()
188+
if node then
189+
local path = node:get_id()
190+
state.position.set(path)
191+
if node.type == "directory" then
192+
log.trace("opening directory from search: ", path)
193+
M.navigate(state, nil, path, function()
194+
-- this callback is to focus the selected node
195+
local event = {
196+
event = events.AFTER_RENDER,
197+
id = "neo-tree-open-from-search:" .. tostring(state),
198+
}
199+
events.unsubscribe(event) -- if there is a prior event waiting, replace it
200+
201+
event.handler = function(arg)
202+
if arg ~= state then
203+
return -- this is not our event
204+
end
205+
pcall(renderer.focus_node, state, path, false)
206+
event.cancelled = true
207+
end
208+
209+
events.subscribe(event)
210+
end)
211+
else
212+
if state.current_position == "split" then
213+
utils.open_file(state, node:get_id())
214+
else
215+
utils.open_file(state, path)
216+
M.navigate(state, nil, path)
217+
end
218+
end
219+
end
220+
else
221+
M.navigate(state)
222+
end
189223
end
190224
end
191225

lua/neo-tree/sources/filesystem/lib/filter.lua

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ local manager = require("neo-tree.sources.manager")
1313

1414
local M = {}
1515

16-
M.show_filter = function(state, search_as_you_type)
16+
M.show_filter = function(state, search_as_you_type, fuzzy_finder_mode)
1717
local width = vim.fn.winwidth(0) - 2
1818
local row = vim.api.nvim_win_get_height(0) - 3
1919
local popup_options = popups.popup_options("Enter Filter Pattern:", width, {
@@ -39,6 +39,12 @@ M.show_filter = function(state, search_as_you_type)
3939
if value == "" then
4040
fs.reset_search(state)
4141
else
42+
if search_as_you_type and fuzzy_finder_mode then
43+
state.search_pattern = nil
44+
fs.reset_search(state, true, true)
45+
require("neo-tree.sources.filesystem.commands").open(state)
46+
return
47+
end
4248
state.search_pattern = value
4349
manager.refresh("filesystem", function()
4450
-- focus first file
@@ -107,11 +113,31 @@ M.show_filter = function(state, search_as_you_type)
107113

108114
input:map("i", "<esc>", function(bufnr)
109115
input:unmount()
116+
if fuzzy_finder_mode and utils.truthy(state.search_pattern) then
117+
fs.reset_search(state, true)
118+
end
110119
end, { noremap = true })
111120

112121
input:on({ event.BufLeave, event.BufDelete }, function()
113122
input:unmount()
123+
if fuzzy_finder_mode and utils.truthy(state.search_pattern) then
124+
fs.reset_search(state, true)
125+
end
114126
end, { once = true })
127+
128+
if fuzzy_finder_mode then
129+
local move_cursor_down = function()
130+
renderer.focus_node(state, nil, true, 1, 3)
131+
end
132+
local move_cursor_up = function()
133+
renderer.focus_node(state, nil, true, -1, 3)
134+
vim.cmd("redraw!")
135+
end
136+
input:map("i", "<down>", move_cursor_down, { noremap = true })
137+
input:map("i", "<C-n>", move_cursor_down, { noremap = true })
138+
input:map("i", "<up>", move_cursor_up, { noremap = true })
139+
input:map("i", "<C-p>", move_cursor_up, { noremap = true })
140+
end
115141
end
116142

117143
return M

lua/neo-tree/ui/renderer.lua

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -173,10 +173,13 @@ end
173173
---@param id string The id of the node to set the cursor at.
174174
---@return boolean boolean True if the node was found and focused, false
175175
---otherwise.
176-
M.focus_node = function(state, id, do_not_focus_window)
177-
if not id then
176+
M.focus_node = function(state, id, do_not_focus_window, relative_movement, bottom_scroll_padding)
177+
if not id and not relative_movement then
178178
return nil
179179
end
180+
relative_movement = relative_movement or 0
181+
bottom_scroll_padding = bottom_scroll_padding or 0
182+
180183
local tree = state.tree
181184
if not tree then
182185
return false
@@ -185,8 +188,7 @@ M.focus_node = function(state, id, do_not_focus_window)
185188
if not node then
186189
return false
187190
end
188-
--expand_to_root(tree, node)
189-
--tree:render()
191+
id = node:get_id() -- in case nil was passed in for id, meaning current node
190192

191193
local bufnr = utils.get_value(state, "bufnr", 0, true)
192194
if bufnr == 0 then
@@ -202,33 +204,49 @@ M.focus_node = function(state, id, do_not_focus_window)
202204
node = tree:get_node(linenr)
203205
if node then
204206
if node:get_id() == id then
207+
if relative_movement ~= 0 then
208+
local success, relative_node = pcall(tree.get_node, tree, linenr)
209+
-- this may fail if the node is at the first or last line
210+
if success and relative_node then
211+
node = relative_node
212+
linenr = linenr + relative_movement
213+
end
214+
end
205215
local col = 0
206216
if node.indent then
207217
col = string.len(node.indent)
208218
end
209219
local focus_window = not do_not_focus_window
210-
if focus_window then
211-
if M.window_exists(state) then
220+
if M.window_exists(state) then
221+
if focus_window then
212222
vim.api.nvim_set_current_win(state.winid)
213-
else
214-
return false
215223
end
216-
end
217-
local success, err = pcall(vim.api.nvim_win_set_cursor, state.winid, { linenr, col })
218-
if success then
219-
-- make sure we are not scrolled down if it can all fit on the screen
220-
local win_height = vim.api.nvim_win_get_height(state.winid)
221-
if vim.api.nvim_get_current_win() == state.winid then
222-
if win_height > linenr then
223-
vim.cmd("normal! zb")
224+
local success, err = pcall(vim.api.nvim_win_set_cursor, state.winid, { linenr, col })
225+
if success then
226+
local execute_win_command = function(cmd)
227+
if vim.api.nvim_get_current_win() == state.winid then
228+
vim.cmd(cmd)
229+
else
230+
vim.cmd("call win_execute(" .. state.winid .. [[, "]] .. cmd .. [[")]])
231+
end
232+
end
233+
234+
-- make sure we are not scrolled down if it can all fit on the screen
235+
local win_height = vim.api.nvim_win_get_height(state.winid)
236+
if linenr > (win_height - bottom_scroll_padding) then
237+
execute_win_command("normal! zz")
238+
elseif win_height > linenr then
239+
execute_win_command("normal! zb")
224240
elseif linenr < (win_height / 2) then
225-
vim.cmd("normal! zz")
241+
execute_win_command("normal! zz")
226242
end
243+
else
244+
log.warn("Failed to set cursor: " .. err)
227245
end
246+
return success
228247
else
229-
log.warn("Failed to set cursor: " .. err)
248+
return false
230249
end
231-
return success
232250
end
233251
else
234252
--must be out of nodes

0 commit comments

Comments
 (0)