Skip to content

Commit adc54b2

Browse files
committed
feat: function to re-open the most recent search
Replicates the "resume" functionality of telescope and Snacks.nvim.picker (see #159)
1 parent 8cf03fd commit adc54b2

2 files changed

Lines changed: 61 additions & 49 deletions

File tree

lua/fff/main.lua

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ function M.live_grep(opts)
3939
picker_ui.open(picker_opts)
4040
end
4141

42+
--- Resume the last search.
43+
--- @param opts? table Optional configuration overrides.
44+
function M.resume(opts)
45+
require('fff.picker_ui').open(opts, true)
46+
end
47+
4248
--- Changes the directory indexed by the file picker to the git root and opens the file picker
4349
--- @deprecated Use `find_files` instead
4450
function M.find_in_git_root()

lua/fff/picker_ui.lua

Lines changed: 55 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -185,16 +185,16 @@ local function build_window_configs(layout, config)
185185
-- List border: when prompt at bottom, list has top+sides (no bottom); when top, T-junctions at top
186186
local list_border = prompt_position == 'bottom'
187187
and { border_chars[1], border_chars[2], border_chars[3], border_chars[4], '', '', '', border_chars[8] }
188-
or {
189-
t_junctions[1],
190-
border_chars[2],
191-
t_junctions[2],
192-
border_chars[4],
193-
border_chars[5],
194-
border_chars[6],
195-
border_chars[7],
196-
border_chars[8],
197-
}
188+
or {
189+
t_junctions[1],
190+
border_chars[2],
191+
t_junctions[2],
192+
border_chars[4],
193+
border_chars[5],
194+
border_chars[6],
195+
border_chars[7],
196+
border_chars[8],
197+
}
198198

199199
local list_cfg = {
200200
relative = 'editor',
@@ -222,7 +222,7 @@ local function build_window_configs(layout, config)
222222
border_chars[7],
223223
border_chars[8],
224224
}
225-
or { border_chars[1], border_chars[2], border_chars[3], border_chars[4], '', '', '', border_chars[8] }
225+
or { border_chars[1], border_chars[2], border_chars[3], border_chars[4], '', '', '', border_chars[8] }
226226

227227
local input_cfg = {
228228
relative = 'editor',
@@ -473,18 +473,18 @@ M.state = {
473473
location = nil, -- Current location from search results
474474

475475
-- History cycling state
476-
history_offset = nil, -- Current offset in history (nil = not cycling, 0 = first query)
476+
history_offset = nil, -- Current offset in history (nil = not cycling, 0 = first query)
477477
next_search_force_combo_boost = false, -- Force combo boost on next search (for history recall)
478478

479479
-- Combo state
480-
combo_visible = true, -- Whether to show combo indicator (hidden after significant navigation)
480+
combo_visible = true, -- Whether to show combo indicator (hidden after significant navigation)
481481
combo_initial_cursor = nil, -- Initial cursor position when combo was shown
482482

483483
-- Pagination state
484484
pagination = {
485-
page_index = 0, -- Current page index (0-based)
486-
page_size = 20, -- Items per page (updated dynamically)
487-
total_matched = 0, -- Total results from last search
485+
page_index = 0, -- Current page index (0-based)
486+
page_size = 20, -- Items per page (updated dynamically)
487+
total_matched = 0, -- Total results from last search
488488
prefetch_margin = 5, -- Trigger refetch when within N items of edge
489489
-- Grep file-based pagination: stores the file_offset for each visited page
490490
-- so we can go backwards. grep_file_offsets[1] = 0 (page 0 starts at file 0),
@@ -592,7 +592,6 @@ function M.create_ui()
592592
preview.set_preview_window(M.state.preview_win)
593593

594594
M.update_results_sync()
595-
M.clear_preview()
596595
M.update_status()
597596

598597
return true
@@ -959,8 +958,8 @@ function M.cycle_grep_modes()
959958
-- Use grep_config.modes if provided, otherwise fall back to global config
960959
---@diagnostic disable-next-line: undefined-field
961960
local modes = (M.state.grep_config and M.state.grep_config.modes)
962-
or config.grep.modes
963-
or { 'plain', 'regex', 'fuzzy' }
961+
or config.grep.modes
962+
or { 'plain', 'regex', 'fuzzy' }
964963

965964
-- If only one mode configured, no cycling needed
966965
if #modes <= 1 then return end
@@ -1032,7 +1031,7 @@ function M.update_results_sync()
10321031
if current_buf and vim.api.nvim_buf_is_valid(current_buf) then
10331032
local current_file = vim.api.nvim_buf_get_name(current_buf)
10341033
M.state.current_file_cache = (current_file ~= '' and vim.fn.filereadable(current_file) == 1) and current_file
1035-
or nil
1034+
or nil
10361035
end
10371036
end
10381037
local page_size
@@ -1458,7 +1457,7 @@ local function render_grep_empty_state(ctx)
14581457
for i = 0, #content - 1 do
14591458
local line = content[i + 1]
14601459
if
1461-
line and (line:match('^%s+Start typing') or line:match('^%s+Tips') or line:match('^%s+"') or line:match('^%s+!'))
1460+
line and (line:match('^%s+Start typing') or line:match('^%s+Tips') or line:match('^%s+"') or line:match('^%s+!'))
14621461
then
14631462
pcall(
14641463
vim.api.nvim_buf_set_extmark,
@@ -1561,11 +1560,11 @@ local function build_render_context()
15611560
iter_end = iter_end,
15621561
iter_step = iter_step,
15631562
renderer = M.state.suggestion_source and M.get_suggestion_renderer() or M.state.renderer,
1564-
query = M.state.query, -- Current search query (used by grep renderer for empty-state detection)
1565-
selected_files = M.state.selected_files, -- Selected files set (used by file renderer for selection markers)
1566-
selected_items = M.state.selected_items, -- Selected items map (used by grep renderer for per-occurrence markers)
1567-
mode = M.state.mode, -- Current mode (nil or 'grep')
1568-
format_file_display = format_file_display, -- Helper for renderers to format filename + dir path
1563+
query = M.state.query, -- Current search query (used by grep renderer for empty-state detection)
1564+
selected_files = M.state.selected_files, -- Selected files set (used by file renderer for selection markers)
1565+
selected_items = M.state.selected_items, -- Selected items map (used by grep renderer for per-occurrence markers)
1566+
mode = M.state.mode, -- Current mode (nil or 'grep')
1567+
format_file_display = format_file_display, -- Helper for renderers to format filename + dir path
15691568
suggestion_source = M.state.suggestion_source, -- Cross-mode suggestion source ('grep' or 'files')
15701569
}
15711570
end
@@ -1824,8 +1823,8 @@ function M.update_status(progress)
18241823
-- Use grep_config.modes if provided, otherwise fall back to global config
18251824
---@diagnostic disable-next-line: undefined-field
18261825
local modes = (M.state.grep_config and M.state.grep_config.modes)
1827-
or config.grep.modes
1828-
or { 'plain', 'regex', 'fuzzy' }
1826+
or config.grep.modes
1827+
or { 'plain', 'regex', 'fuzzy' }
18291828

18301829
-- When regex compilation failed and we fell back to literal search, show a warning
18311830
local fallback_label = nil
@@ -1882,7 +1881,7 @@ function M.update_status(progress)
18821881
vim.api.nvim_buf_set_extmark(M.state.input_buf, M.state.ns_id, 0, 0, {
18831882
virt_text = {
18841883
{ keybind .. ' ', hl },
1885-
{ mode_label, hl },
1884+
{ mode_label, hl },
18861885
},
18871886
virt_text_win_col = col_position,
18881887
})
@@ -2139,10 +2138,10 @@ local function find_suitable_window()
21392138
)
21402139

21412140
if
2142-
(buftype == '' or buftype == 'acwrite')
2143-
and modifiable
2144-
and not is_picker_window
2145-
and filetype ~= 'undotree'
2141+
(buftype == '' or buftype == 'acwrite')
2142+
and modifiable
2143+
and not is_picker_window
2144+
and filetype ~= 'undotree'
21462145
then
21472146
return win
21482147
end
@@ -2313,9 +2312,9 @@ function M.select(action)
23132312
if vim.startswith(path, '\\\\?\\') then path = path:sub(5) end
23142313

23152314
local relative_path = vim.fn.fnamemodify(path, ':.')
2316-
local location = M.state.location -- Capture location before closing
2317-
local query = M.state.query -- Capture query before closing for tracking
2318-
local mode = M.state.mode -- Capture mode before closing for tracking
2315+
local location = M.state.location -- Capture location before closing
2316+
local query = M.state.query -- Capture query before closing for tracking
2317+
local mode = M.state.mode -- Capture mode before closing for tracking
23192318
local suggestion_source = M.state.suggestion_source -- Capture suggestion context
23202319

23212320
-- In grep mode (or when selecting a grep suggestion), derive location from the match item
@@ -2463,6 +2462,12 @@ function M.close()
24632462
end
24642463
end
24652464

2465+
-- Clean up picker focus autocmds
2466+
pcall(vim.api.nvim_del_augroup_by_name, 'fff_picker_focus')
2467+
end
2468+
2469+
--- Reset picker state
2470+
function M.reset_state()
24662471
if M.state.preview_timer then
24672472
M.state.preview_timer:stop()
24682473
M.state.preview_timer:close()
@@ -2498,8 +2503,6 @@ function M.close()
24982503
M.state.combo_visible = true
24992504
M.state.combo_initial_cursor = nil
25002505
M.reset_history_state()
2501-
-- Clean up picker focus autocmds
2502-
pcall(vim.api.nvim_del_augroup_by_name, 'fff_picker_focus')
25032506
end
25042507

25052508
--- Helper function to determine current file cache for deprioritization
@@ -2555,7 +2558,9 @@ end
25552558
--- @param location table|nil Pre-fetched location data
25562559
--- @param merged_config table Merged configuration
25572560
--- @param current_file_cache string|nil Current file cache
2558-
local function open_ui_with_state(query, results, location, merged_config, current_file_cache)
2561+
--- @param resume? boolean Resume previous search
2562+
local function open_ui_with_state(query, results, location, merged_config, current_file_cache, resume)
2563+
if not resume then M.reset_state() end
25592564
M.state.config = merged_config
25602565

25612566
if not M.create_ui() then
@@ -2569,10 +2574,9 @@ local function open_ui_with_state(query, results, location, merged_config, curre
25692574
-- Set up initial state
25702575
if query then
25712576
M.state.query = query
2572-
vim.api.nvim_buf_set_lines(M.state.input_buf, 0, -1, false, { M.state.config.prompt .. query })
2573-
else
2574-
M.state.query = ''
2577+
resume = false
25752578
end
2579+
vim.api.nvim_buf_set_lines(M.state.input_buf, 0, -1, false, { M.state.config.prompt .. M.state.query })
25762580

25772581
if results then
25782582
-- Use prefetched results
@@ -2584,7 +2588,7 @@ local function open_ui_with_state(query, results, location, merged_config, curre
25842588
M.render_list()
25852589
M.update_preview()
25862590
M.update_status()
2587-
else
2591+
elseif not resume then
25882592
M.update_results()
25892593
M.clear_preview()
25902594
M.update_status()
@@ -2612,8 +2616,9 @@ end
26122616
--- @param query string The search query to execute
26132617
--- @param callback function Function called with results: function(results, metadata, location, get_file_score) -> boolean
26142618
--- @param opts? table Optional configuration to override defaults (same as M.open)
2619+
--- @param resume? boolean Resume previous search
26152620
--- @return boolean true if callback handled results, false if UI was opened
2616-
function M.open_with_callback(query, callback, opts)
2621+
function M.open_with_callback(query, callback, opts, resume)
26172622
if M.state.active then return false end
26182623

26192624
local merged_config, base_path = initialize_picker(opts)
@@ -2636,14 +2641,15 @@ function M.open_with_callback(query, callback, opts)
26362641
end
26372642

26382643
if callback_handled then return true end
2639-
open_ui_with_state(query, results, location, merged_config, current_file_cache)
2644+
open_ui_with_state(query, results, location, merged_config, current_file_cache, resume)
26402645

26412646
return false
26422647
end
26432648

26442649
--- Open the file picker UI
26452650
--- @param opts? {cwd?: string, title?: string, prompt?: string, max_results?: number, max_threads?: number, layout?: {width?: number|function, height?: number|function, prompt_position?: string|function, preview_position?: string|function, preview_size?: number|function}, renderer?: table, mode?: string, grep_config?: table, query?: string} Optional configuration to override defaults
2646-
function M.open(opts)
2651+
--- @param resume? boolean Resume previous search
2652+
function M.open(opts, resume)
26472653
if M.state.active then return end
26482654

26492655
M.state.selected_files = {}
@@ -2662,14 +2668,14 @@ function M.open(opts)
26622668
-- Use grep_config.modes if provided, otherwise fall back to global config
26632669
---@diagnostic disable-next-line: undefined-field
26642670
local modes = (M.state.grep_config and M.state.grep_config.modes)
2665-
or merged_config.grep.modes
2666-
or { 'plain', 'regex', 'fuzzy' }
2671+
or merged_config.grep.modes
2672+
or { 'plain', 'regex', 'fuzzy' }
26672673
M.state.grep_mode = modes[1] or 'plain'
26682674
end
26692675

26702676
local current_file_cache = get_current_file_cache(base_path)
26712677
local query = opts and opts.query or nil ---@type string|nil
2672-
return open_ui_with_state(query, nil, nil, merged_config, current_file_cache)
2678+
return open_ui_with_state(query, nil, nil, merged_config, current_file_cache, resume)
26732679
end
26742680

26752681
--- Change the base directory for the file picker

0 commit comments

Comments
 (0)