From 273f7f77fd0ccc5b7ffedec5ff6eaa5a85ba7dfc Mon Sep 17 00:00:00 2001 From: Dmitriy Kovalenko Date: Sun, 20 Sep 2026 15:40:18 -0700 Subject: [PATCH] fix: Do not start fallback grep if index is not ready --- README.md | 4 ++++ crates/fff-nvim/src/lib.rs | 5 +++++ lua/fff/conf.lua | 10 ++++++++++ lua/fff/file_picker/init.lua | 14 +++++++++++--- lua/fff/picker_ui/search_manager.lua | 23 ++++++++++++++++++++--- 5 files changed, 50 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 088306be8..624c64e6d 100644 --- a/README.md +++ b/README.md @@ -438,6 +438,10 @@ require('fff').setup({ enable_filename_constraint = false, -- treat filename-like tokens (e.g. `score.rs`) in a grep query as a file-path filter scoping the search; off = searched as literal text location_format = ':%d:%d', -- printf format for line:col prefix in grep results, e.g. ':%d' for line-only }, + suggestions = { + enabled = true, -- when a query has no results, look them up in the other mode (files <-> grep) and show as a hint + grep_time_budget_ms = 50, -- hard cap for the grep hint in file mode; it is skipped until content indexing finishes + }, debug = { enabled = false, -- show the file info panel next to the preview show_scores = false, -- inline scores in the file list diff --git a/crates/fff-nvim/src/lib.rs b/crates/fff-nvim/src/lib.rs index 310d618b2..ff92016e6 100644 --- a/crates/fff-nvim/src/lib.rs +++ b/crates/fff-nvim/src/lib.rs @@ -612,6 +612,11 @@ pub fn get_scan_progress(lua: &Lua, _: ()) -> LuaResult { let table = lua.create_table()?; table.set("scanned_files_count", progress.scanned_files_count)?; table.set("is_scanning", progress.is_scanning)?; + table.set("is_warmup_complete", progress.is_warmup_complete)?; + let is_index_ready = + picker.has_content_indexing() && picker.bigram_index().is_some() && !progress.is_scanning; + + table.set("is_index_ready", is_index_ready)?; Ok(LuaValue::Table(table)) } diff --git a/lua/fff/conf.lua b/lua/fff/conf.lua index 7231e7c2e..89f699bca 100644 --- a/lua/fff/conf.lua +++ b/lua/fff/conf.lua @@ -68,6 +68,10 @@ local M = {} --- @field trim_whitespace boolean --- @field location_format string +--- @class FffSuggestionsConfig +--- @field enabled boolean +--- @field grep_time_budget_ms number + --- @alias FffSelectAction 'edit' | 'split' | 'vsplit' | 'tab' --- @class FffSelectConfig @@ -98,6 +102,7 @@ local M = {} --- @field wrap_around boolean --- @field file_picker table --- @field grep FffGrepConfig +--- @field suggestions FffSuggestionsConfig ---@class fff.conf.State local state = { @@ -458,6 +463,11 @@ local function init() current_file_label = '(current)', fuzzy_query_highlighting = false, }, + -- Cross-mode suggestions shown when a query has no results + suggestions = { + enabled = true, + grep_time_budget_ms = 50, -- Hard cap for the grep hint in file mode (it never runs before content indexing finishes) + }, -- grep settings grep = { max_file_size = 10 * 1024 * 1024, -- Skip files larger than 10MB diff --git a/lua/fff/file_picker/init.lua b/lua/fff/file_picker/init.lua index 480b0d40a..e26aedc35 100644 --- a/lua/fff/file_picker/init.lua +++ b/lua/fff/file_picker/init.lua @@ -150,14 +150,22 @@ end function M.is_initialized() return M.state.initialized end --- Get scan progress information ---- @return table Progress information with scanned_files_count, is_scanning +--- @return table Progress information with scanned_files_count, is_scanning, is_warmup_complete, is_index_ready function M.get_scan_progress() - if not M.state.initialized then return { total_files = 0, scanned_files_count = 0, is_scanning = false } end + if not M.state.initialized then + return { + total_files = 0, + scanned_files_count = 0, + is_scanning = false, + is_warmup_complete = false, + is_index_ready = false, + } + end local ok, result = pcall(fuzzy.get_scan_progress) if not ok then vim.notify('Failed to get scan progress: ' .. tostring(result), vim.log.levels.WARN) - return { scanned_files_count = 0, is_scanning = false } + return { scanned_files_count = 0, is_scanning = false, is_warmup_complete = false, is_index_ready = false } end return result diff --git a/lua/fff/picker_ui/search_manager.lua b/lua/fff/picker_ui/search_manager.lua index 005cb0322..06a34843f 100644 --- a/lua/fff/picker_ui/search_manager.lua +++ b/lua/fff/picker_ui/search_manager.lua @@ -81,7 +81,8 @@ function M.update_results_sync() S.suggestion_items = nil S.suggestion_source = nil - if #results == 0 and S.query ~= '' then + local suggestions_enabled = (S.config.suggestions or {}).enabled ~= false + if #results == 0 and S.query ~= '' and suggestions_enabled then if S.mode == 'grep' then local suggestion_results = file_picker.search_files_paginated(S.query, S.current_file_cache, S.config.max_threads, nil, 0, page_size) @@ -90,8 +91,7 @@ function M.update_results_sync() S.suggestion_source = 'files' end else - local grep_result = grep.search(S.query, 0, page_size, S.grep_config, 'plain') - local grep_items = grep_result and grep_result.items or {} + local grep_items = M.suggestion_grep(page_size) if #grep_items > 0 then S.suggestion_items = grep_items S.suggestion_source = 'grep' @@ -113,6 +113,23 @@ function M.update_results_sync() P.render_debounced() end +-- Hint only: hard time budget (a zero-hit query would otherwise scan every file) +-- and skipped unless the bigram index is built, since without it grep is a cold disk scan. +---@param page_size number +---@return table[] +function M.suggestion_grep(page_size) + if not file_picker.get_scan_progress().is_index_ready then return {} end + local suggest_config = S.config.suggestions or {} + + local grep_config = vim.tbl_extend('force', S.grep_config or S.config.grep or {}, { + enforce_time_budget = true, + time_budget_ms = suggest_config.grep_time_budget_ms or 50, + }) + local ok, grep_result = pcall(grep.search, S.query, 0, page_size, grep_config, 'plain') + if not ok or not grep_result then return {} end + return grep_result.items or {} +end + function M.load_page_at_index(new_page_index, adjust_cursor_fn) local ok, err, results local page_size = S.pagination.page_size