diff --git a/crates/fff-c/include/fff.h b/crates/fff-c/include/fff.h index fa655b9b..4ac91410 100644 --- a/crates/fff-c/include/fff.h +++ b/crates/fff-c/include/fff.h @@ -340,12 +340,14 @@ typedef struct FffMixedSearchResult { * * # Parameters * - * * `base_path` – directory to index (required) - * * `frecency_db_path` – path to frecency LMDB database (NULL/empty to skip) - * * `history_db_path` – path to query history LMDB database (NULL/empty to skip) - * * `use_unsafe_no_lock` – use MDB_NOLOCK for LMDB (useful in single-process setups) - * * `warmup_mmap_cache` – pre-populate mmap caches after the initial scan - * * `ai_mode` – enable AI-agent optimizations (auto-track frecency on modifications) + * * `base_path` – directory to index (required) + * * `frecency_db_path` – path to frecency LMDB database (NULL/empty to skip) + * * `history_db_path` – path to query history LMDB database (NULL/empty to skip) + * * `use_unsafe_no_lock` – use MDB_NOLOCK for LMDB (useful in single-process setups) + * * `enable_mmap_cache` – pre-populate mmap caches after the initial scan + * * `enable_content_indexing` – build content index after the initial scan + * * `watch` – start a background file-system watcher for live updates + * * `ai_mode` – enable AI-agent optimizations (auto-track frecency on modifications) * * ## Safety * String parameters must be valid null-terminated UTF-8 or NULL. @@ -354,7 +356,9 @@ struct FffResult *fff_create_instance(const char *base_path, const char *frecency_db_path, const char *history_db_path, bool use_unsafe_no_lock, - bool warmup_mmap_cache, + bool enable_mmap_cache, + bool enable_content_indexing, + bool watch, bool ai_mode); /** diff --git a/crates/fff-c/src/lib.rs b/crates/fff-c/src/lib.rs index 70d296b0..ce545cb3 100644 --- a/crates/fff-c/src/lib.rs +++ b/crates/fff-c/src/lib.rs @@ -110,12 +110,14 @@ fn default_i32(val: i32, default: i32) -> i32 { /// /// # Parameters /// -/// * `base_path` – directory to index (required) -/// * `frecency_db_path` – path to frecency LMDB database (NULL/empty to skip) -/// * `history_db_path` – path to query history LMDB database (NULL/empty to skip) -/// * `use_unsafe_no_lock` – use MDB_NOLOCK for LMDB (useful in single-process setups) -/// * `warmup_mmap_cache` – pre-populate mmap caches after the initial scan -/// * `ai_mode` – enable AI-agent optimizations (auto-track frecency on modifications) +/// * `base_path` – directory to index (required) +/// * `frecency_db_path` – path to frecency LMDB database (NULL/empty to skip) +/// * `history_db_path` – path to query history LMDB database (NULL/empty to skip) +/// * `use_unsafe_no_lock` – use MDB_NOLOCK for LMDB (useful in single-process setups) +/// * `enable_mmap_cache` – pre-populate mmap caches after the initial scan +/// * `enable_content_indexing` – build content index after the initial scan +/// * `watch` – start a background file-system watcher for live updates +/// * `ai_mode` – enable AI-agent optimizations (auto-track frecency on modifications) /// /// ## Safety /// String parameters must be valid null-terminated UTF-8 or NULL. @@ -125,7 +127,9 @@ pub unsafe extern "C" fn fff_create_instance( frecency_db_path: *const c_char, history_db_path: *const c_char, use_unsafe_no_lock: bool, - warmup_mmap_cache: bool, + enable_mmap_cache: bool, + enable_content_indexing: bool, + watch: bool, ai_mode: bool, ) -> *mut FffResult { let base_path_str = match unsafe { cstr_to_str(base_path) } { @@ -186,10 +190,11 @@ pub unsafe extern "C" fn fff_create_instance( shared_frecency.clone(), fff::FilePickerOptions { base_path: base_path_str, - warmup_mmap_cache, + enable_mmap_cache, + enable_content_indexing, + watch, mode, cache_budget: None, - ..Default::default() }, ) { return FffResult::err(&format!("Failed to init file picker: {}", e)); @@ -829,13 +834,15 @@ pub unsafe extern "C" fn fff_restart_index( Err(e) => return FffResult::err(&format!("Failed to acquire file picker lock: {}", e)), }; - let (warmup_caches, mode) = if let Some(mut picker) = guard.take() { - let warmup = picker.need_warmup_mmap_cache(); + let (warmup_caches, content_indexing, watch, mode) = if let Some(mut picker) = guard.take() { + let warmup = picker.need_enable_mmap_cache(); + let ci = picker.need_enable_content_indexing(); + let w = picker.need_watch(); let mode = picker.mode(); picker.stop_background_monitor(); - (warmup, mode) + (warmup, ci, w, mode) } else { - (false, FFFMode::default()) + (false, false, true, FFFMode::default()) }; drop(guard); @@ -845,10 +852,11 @@ pub unsafe extern "C" fn fff_restart_index( inst.frecency.clone(), fff::FilePickerOptions { base_path: canonical_path.to_string_lossy().to_string(), - warmup_mmap_cache: warmup_caches, + enable_mmap_cache: warmup_caches, + enable_content_indexing: content_indexing, + watch, mode, cache_budget: None, - ..Default::default() }, ) { Ok(()) => FffResult::ok_empty(), diff --git a/crates/fff-core/src/background_watcher.rs b/crates/fff-core/src/background_watcher.rs index c419342b..70c067fc 100644 --- a/crates/fff-core/src/background_watcher.rs +++ b/crates/fff-core/src/background_watcher.rs @@ -560,7 +560,9 @@ fn trigger_full_rescan(shared_picker: &SharedPicker, shared_frecency: &SharedFre // Spawn background warmup + bigram rebuild (mirrors the initial scan's // post-scan phase). The write lock is still held here but the spawned // thread re-acquires it later — safe because the guard drops at function end. - picker.spawn_post_rescan_rebuild(shared_picker.clone()); + if shared_picker.need_complex_rebuild() { + picker.spawn_post_rescan_rebuild(shared_picker.clone()); + } } /// After registering a watch on a newly created directory, list its diff --git a/crates/fff-core/src/file_picker.rs b/crates/fff-core/src/file_picker.rs index 430c5bdb..86f81dd3 100644 --- a/crates/fff-core/src/file_picker.rs +++ b/crates/fff-core/src/file_picker.rs @@ -389,13 +389,16 @@ impl FileItem { /// Options for creating a [`FilePicker`]. pub struct FilePickerOptions { pub base_path: String, - pub warmup_mmap_cache: bool, + /// Pre-populate mmap caches for top-frecency files after the initial scan. + pub enable_mmap_cache: bool, + /// Build content index after the initial scan for faster content-aware filtering. + pub enable_content_indexing: bool, + /// Mode of the picker impact the way file watcher events are handled and the scoring logic pub mode: FFFMode, /// Explicit cache budget. When `None`, the budget is auto-computed from /// the repo size after the initial scan completes. pub cache_budget: Option, /// When `false`, `new_with_shared_state` skips the background file watcher. - /// Files are still scanned, warmed up, and bigram-indexed. pub watch: bool, } @@ -403,7 +406,8 @@ impl Default for FilePickerOptions { fn default() -> Self { Self { base_path: ".".into(), - warmup_mmap_cache: false, + enable_mmap_cache: false, + enable_content_indexing: false, mode: FFFMode::default(), cache_budget: None, watch: true, @@ -421,7 +425,8 @@ pub struct FilePicker { watcher_ready: Arc, scanned_files_count: Arc, background_watcher: Option, - warmup_mmap_cache: bool, + enable_mmap_cache: bool, + enable_content_indexing: bool, watch: bool, cancelled: Arc, // This is a soft lock that we use to prevent rescan be triggered while the @@ -479,8 +484,16 @@ impl FilePicker { .and_then(|p| p.to_str()) } - pub fn need_warmup_mmap_cache(&self) -> bool { - self.warmup_mmap_cache + pub fn need_enable_mmap_cache(&self) -> bool { + self.enable_mmap_cache + } + + pub fn need_enable_content_indexing(&self) -> bool { + self.enable_content_indexing + } + + pub fn need_watch(&self) -> bool { + self.watch } pub fn mode(&self) -> FFFMode { @@ -632,7 +645,8 @@ impl FilePicker { post_scan_busy: Arc::new(AtomicBool::new(false)), scanned_files_count: Arc::new(AtomicUsize::new(0)), sync_data: FileSync::new(), - warmup_mmap_cache: options.warmup_mmap_cache, + enable_mmap_cache: options.enable_mmap_cache, + enable_content_indexing: options.enable_content_indexing, watch: options.watch, watcher_ready: Arc::new(AtomicBool::new(false)), }) @@ -648,13 +662,15 @@ impl FilePicker { let picker = Self::new(options)?; info!( - "Spawning background threads: base_path={}, warmup={}, mode={:?}", + "Spawning background threads: base_path={}, warmup={}, content_indexing={}, mode={:?}", picker.base_path.display(), - picker.warmup_mmap_cache, + picker.enable_mmap_cache, + picker.enable_content_indexing, picker.mode, ); - let warmup = picker.warmup_mmap_cache; + let warmup = picker.enable_mmap_cache; + let content_indexing = picker.enable_content_indexing; let watch = picker.watch; let mode = picker.mode; @@ -678,6 +694,7 @@ impl FilePicker { watcher_ready, synced_files_count, warmup, + content_indexing, watch, mode, shared_picker, @@ -1400,13 +1417,15 @@ impl FilePicker { /// Spawn a background thread to rebuild the bigram index after rescan. pub(crate) fn spawn_post_rescan_rebuild(&self, shared_picker: SharedPicker) -> bool { - if !self.warmup_mmap_cache || self.cancelled.load(Ordering::Relaxed) { + if self.cancelled.load(Ordering::Relaxed) { return false; } let post_scan_busy = Arc::clone(&self.post_scan_busy); let cancelled = Arc::clone(&self.cancelled); let auto_budget = !self.has_explicit_cache_budget; + let do_warmup = self.enable_mmap_cache; + let do_content_indexing = self.enable_content_indexing; post_scan_busy.store(true, Ordering::Release); @@ -1450,7 +1469,7 @@ impl FilePicker { if let Some((files, budget, bp, arena)) = files_snapshot { // Warmup mmap caches. - if !cancelled.load(Ordering::Acquire) { + if do_warmup && !cancelled.load(Ordering::Acquire) { let t = std::time::Instant::now(); warmup_mmaps(files, &budget, &bp, arena); info!( @@ -1462,7 +1481,7 @@ impl FilePicker { } // Build bigram index (lock-free). - if !cancelled.load(Ordering::Acquire) { + if do_content_indexing && !cancelled.load(Ordering::Acquire) { let t = std::time::Instant::now(); info!( "Rescan: starting bigram index build for {} files...", @@ -1495,8 +1514,10 @@ impl FilePicker { post_scan_busy.store(false, Ordering::Release); info!( - "Rescan post-scan warmup + bigram total: {:.2}s", + "Rescan post-scan phase total: {:.2}s (warmup={}, content_indexing={})", phase_start.elapsed().as_secs_f64(), + do_warmup, + do_content_indexing, ); }); @@ -1617,7 +1638,8 @@ fn spawn_scan_and_watcher( scan_signal: Arc, watcher_ready: Arc, synced_files_count: Arc, - warmup_mmap_cache: bool, + enable_mmap_cache: bool, + enable_content_indexing: bool, watch: bool, mode: FFFMode, shared_picker: SharedPicker, @@ -1725,7 +1747,10 @@ fn spawn_scan_and_watcher( watcher_ready.store(true, Ordering::Release); - if warmup_mmap_cache && !cancelled.load(Ordering::Acquire) { + let need_post_scan = + (enable_mmap_cache || enable_content_indexing) && !cancelled.load(Ordering::Acquire); + + if need_post_scan { post_scan_busy.store(true, Ordering::Release); let phase_start = std::time::Instant::now(); @@ -1768,9 +1793,11 @@ fn spawn_scan_and_watcher( None }; + // both of this is using a custom soft lock not guaranteed by compiler + // this is required to keep the picker functioning if someone opened a really crazy + // e.g 10m files directory but potentially unsafe if let Some((files, budget, arena)) = files_snapshot { - // Warmup: populate mmap caches for top-frecency files. - if !cancelled.load(Ordering::Acquire) { + if enable_mmap_cache && !cancelled.load(Ordering::Acquire) { let warmup_start = std::time::Instant::now(); warmup_mmaps(files, &budget, &base_path, arena); info!( @@ -1781,16 +1808,9 @@ fn spawn_scan_and_watcher( ); } - // Build bigram index — entirely lock-free. - if !cancelled.load(Ordering::Acquire) { - let bigram_start = std::time::Instant::now(); - info!("Starting bigram index build for {} files...", files.len()); + if enable_content_indexing && !cancelled.load(Ordering::Acquire) { let (index, content_binary) = build_bigram_index(files, &budget, &base_path, arena); - info!( - "Bigram index ready in {:.2}s", - bigram_start.elapsed().as_secs_f64(), - ); if let Ok(mut guard) = shared_picker.write() && let Some(ref mut picker) = *guard @@ -1813,8 +1833,10 @@ fn spawn_scan_and_watcher( post_scan_busy.store(false, Ordering::Release); info!( - "Post-scan warmup + bigram total: {:.2}s", + "Post-scan phase total: {:.2}s (warmup={}, content_indexing={})", phase_start.elapsed().as_secs_f64(), + enable_mmap_cache, + enable_content_indexing, ); } @@ -1897,6 +1919,7 @@ pub(crate) fn warmup_mmaps( /// so reading further adds no new information to the index. pub const BIGRAM_CONTENT_CAP: usize = 64 * 1024; +#[tracing::instrument(skip_all, name = "Building Bigram Index", level = Level::DEBUG)] pub(crate) fn build_bigram_index( files: &[FileItem], budget: &ContentCacheBudget, diff --git a/crates/fff-core/src/shared.rs b/crates/fff-core/src/shared.rs index 4e30729e..26a120a3 100644 --- a/crates/fff-core/src/shared.rs +++ b/crates/fff-core/src/shared.rs @@ -34,6 +34,15 @@ impl SharedPicker { Ok(self.0.write()) } + /// Return `true` if this is an instance of the picker that requires a complicated post-scan + /// indexing/cache warmup job. The indexing is not crazy but it takes time. + pub fn need_complex_rebuild(&self) -> bool { + let guard = self.0.read(); + guard + .as_ref() + .is_some_and(|p| p.need_enable_mmap_cache() || p.need_enable_content_indexing()) + } + /// Block until the background filesystem scan finishes. /// Returns `true` if scan completed, `false` on timeout. pub fn wait_for_scan(&self, timeout: Duration) -> bool { diff --git a/crates/fff-core/tests/bigram_overlay_coherence_test.rs b/crates/fff-core/tests/bigram_overlay_coherence_test.rs index fe5d2d7e..2c8642f1 100644 --- a/crates/fff-core/tests/bigram_overlay_coherence_test.rs +++ b/crates/fff-core/tests/bigram_overlay_coherence_test.rs @@ -1435,7 +1435,8 @@ fn make_picker(base: &Path) -> (SharedPicker, SharedFrecency) { shared_frecency.clone(), FilePickerOptions { base_path: base.to_string_lossy().to_string(), - warmup_mmap_cache: true, + enable_mmap_cache: true, + enable_content_indexing: true, mode: FFFMode::Neovim, watch: false, // we drive events manually ..Default::default() diff --git a/crates/fff-core/tests/bigram_overlay_integration.rs b/crates/fff-core/tests/bigram_overlay_integration.rs index bd5acdfb..d6a734af 100644 --- a/crates/fff-core/tests/bigram_overlay_integration.rs +++ b/crates/fff-core/tests/bigram_overlay_integration.rs @@ -33,7 +33,8 @@ fn modified_file_findable_via_overlay() { shared_frecency.clone(), FilePickerOptions { base_path: base.to_string_lossy().to_string(), - warmup_mmap_cache: true, + enable_mmap_cache: true, + enable_content_indexing: true, mode: FFFMode::Neovim, ..Default::default() }, @@ -182,7 +183,8 @@ fn deleted_file_excluded_via_overlay() { shared_frecency.clone(), FilePickerOptions { base_path: base.to_string_lossy().to_string(), - warmup_mmap_cache: true, + enable_mmap_cache: true, + enable_content_indexing: true, mode: FFFMode::Neovim, ..Default::default() }, @@ -250,7 +252,8 @@ fn new_file_findable_after_add() { shared_frecency.clone(), FilePickerOptions { base_path: base.to_string_lossy().to_string(), - warmup_mmap_cache: true, + enable_mmap_cache: true, + enable_content_indexing: true, mode: FFFMode::Neovim, ..Default::default() }, @@ -319,7 +322,8 @@ fn modified_file_findable_via_regex_overlay() { shared_frecency.clone(), FilePickerOptions { base_path: base.to_string_lossy().to_string(), - warmup_mmap_cache: true, + enable_mmap_cache: true, + enable_content_indexing: true, mode: FFFMode::Neovim, ..Default::default() }, diff --git a/crates/fff-core/tests/fuzz_file_operations.rs b/crates/fff-core/tests/fuzz_file_operations.rs index 7f4a0556..cb41ecfe 100644 --- a/crates/fff-core/tests/fuzz_file_operations.rs +++ b/crates/fff-core/tests/fuzz_file_operations.rs @@ -316,7 +316,8 @@ fn fuzz_file_operations_stress() { FilePickerOptions { watch: false, // we do not need the backgrodun monitor base_path: base.to_string_lossy().to_string(), - warmup_mmap_cache: true, + enable_mmap_cache: true, + enable_content_indexing: true, mode: FFFMode::Neovim, ..Default::default() }, diff --git a/crates/fff-core/tests/grep_integration.rs b/crates/fff-core/tests/grep_integration.rs index d93b37e9..73fb7ae2 100644 --- a/crates/fff-core/tests/grep_integration.rs +++ b/crates/fff-core/tests/grep_integration.rs @@ -17,7 +17,7 @@ fn create_picker(base: &Path, specs: &[(&str, &str)]) -> FilePicker { } let mut picker = FilePicker::new(FilePickerOptions { base_path: base.to_string_lossy().to_string(), - warmup_mmap_cache: false, + enable_mmap_cache: false, watch: false, ..Default::default() }) diff --git a/crates/fff-core/tests/new_directory_watcher_test.rs b/crates/fff-core/tests/new_directory_watcher_test.rs index 23e3419f..4190ec2d 100644 --- a/crates/fff-core/tests/new_directory_watcher_test.rs +++ b/crates/fff-core/tests/new_directory_watcher_test.rs @@ -63,7 +63,7 @@ fn make_watched_picker(base: &Path) -> (SharedPicker, SharedFrecency) { shared_frecency.clone(), FilePickerOptions { base_path: base.to_string_lossy().to_string(), - warmup_mmap_cache: false, + enable_mmap_cache: false, mode: FFFMode::Neovim, watch: true, ..Default::default() diff --git a/crates/fff-mcp/src/main.rs b/crates/fff-mcp/src/main.rs index adb814b0..eab67ce0 100644 --- a/crates/fff-mcp/src/main.rs +++ b/crates/fff-mcp/src/main.rs @@ -136,6 +136,19 @@ pub(crate) struct Args { #[arg(long = "no-warmup")] no_warmup: bool, + /// Disable the content index built after the initial scan. + /// This makes grep calls slower but consumes less RAM (recommended to not turn off) + no_content_indexing: bool, + + /// Explicitly enable content indexing even when `--no-warmup` is set. + #[arg(long = "content-indexing")] + content_indexing: bool, + + /// Disable the background file-system watcher. Files are scanned once + /// at startup but not monitored for changes. + #[arg(long = "no-watch")] + no_watch: bool, + /// Maximum number of files whose content is kept persistently in memory. /// Files beyond this limit are still searchable via temporary mmaps that /// are released after each grep. Defaults to 30 000. @@ -264,18 +277,30 @@ async fn main() -> Result<(), Box> { } } + // Content indexing follows warmup by default (backward compat), unless + // the user explicitly opts in via --content-indexing or out via + // --no-content-indexing. + let enable_content_indexing = if args.content_indexing { + true + } else if args.no_content_indexing { + false + } else { + !args.no_warmup + }; + // Initialize file picker (spawns background scan + watcher) FilePicker::new_with_shared_state( shared_picker.clone(), shared_frecency.clone(), fff::FilePickerOptions { base_path, - warmup_mmap_cache: !args.no_warmup, + enable_mmap_cache: !args.no_warmup, + enable_content_indexing, + watch: !args.no_watch, mode: FFFMode::Ai, cache_budget: args .max_cached_files .map(fff::ContentCacheBudget::new_for_repo), - ..Default::default() }, ) .map_err(|e| format!("Failed to init file picker: {}", e))?; diff --git a/crates/fff-nvim/benches/indexing_and_search.rs b/crates/fff-nvim/benches/indexing_and_search.rs index 219b1a0b..27de61f3 100644 --- a/crates/fff-nvim/benches/indexing_and_search.rs +++ b/crates/fff-nvim/benches/indexing_and_search.rs @@ -33,7 +33,7 @@ fn init_file_picker_internal( shared_frecency.clone(), FilePickerOptions { base_path: path.to_string(), - warmup_mmap_cache: false, + enable_mmap_cache: false, mode: FFFMode::Neovim, ..Default::default() }, diff --git a/crates/fff-nvim/src/bin/bench_grep_query.rs b/crates/fff-nvim/src/bin/bench_grep_query.rs index 2245e8a2..063d0816 100644 --- a/crates/fff-nvim/src/bin/bench_grep_query.rs +++ b/crates/fff-nvim/src/bin/bench_grep_query.rs @@ -114,7 +114,7 @@ fn main() { let t = Instant::now(); let mut picker = FilePicker::new(fff::FilePickerOptions { base_path: canonical.to_string_lossy().to_string(), - warmup_mmap_cache: false, + enable_mmap_cache: false, mode: fff::FFFMode::Neovim, ..Default::default() }) diff --git a/crates/fff-nvim/src/bin/bench_search_only.rs b/crates/fff-nvim/src/bin/bench_search_only.rs index f833682e..3dc76231 100644 --- a/crates/fff-nvim/src/bin/bench_search_only.rs +++ b/crates/fff-nvim/src/bin/bench_search_only.rs @@ -6,7 +6,7 @@ use std::time::Instant; fn load_picker(path: &std::path::Path) -> FilePicker { let mut picker = FilePicker::new(fff::FilePickerOptions { base_path: path.to_string_lossy().to_string(), - warmup_mmap_cache: false, + enable_mmap_cache: false, mode: fff::FFFMode::Neovim, ..Default::default() }) diff --git a/crates/fff-nvim/src/bin/fuzzy_grep_test.rs b/crates/fff-nvim/src/bin/fuzzy_grep_test.rs index 7d0cf8ec..e3bceac8 100644 --- a/crates/fff-nvim/src/bin/fuzzy_grep_test.rs +++ b/crates/fff-nvim/src/bin/fuzzy_grep_test.rs @@ -14,7 +14,7 @@ use std::time::Instant; fn create_picker(path: &Path) -> FilePicker { let mut picker = FilePicker::new(fff::FilePickerOptions { base_path: path.to_string_lossy().to_string(), - warmup_mmap_cache: false, + enable_mmap_cache: false, mode: fff::FFFMode::Neovim, ..Default::default() }) diff --git a/crates/fff-nvim/src/bin/grep_profiler.rs b/crates/fff-nvim/src/bin/grep_profiler.rs index c55ce146..4c0386b2 100644 --- a/crates/fff-nvim/src/bin/grep_profiler.rs +++ b/crates/fff-nvim/src/bin/grep_profiler.rs @@ -16,7 +16,7 @@ use std::time::{Duration, Instant}; fn create_picker(path: &std::path::Path) -> FilePicker { let mut picker = FilePicker::new(fff::FilePickerOptions { base_path: path.to_string_lossy().to_string(), - warmup_mmap_cache: false, + enable_mmap_cache: false, mode: fff::FFFMode::Neovim, ..Default::default() }) diff --git a/crates/fff-nvim/src/bin/grep_vs_rg.rs b/crates/fff-nvim/src/bin/grep_vs_rg.rs index 60c66d8c..f202fc1f 100644 --- a/crates/fff-nvim/src/bin/grep_vs_rg.rs +++ b/crates/fff-nvim/src/bin/grep_vs_rg.rs @@ -31,7 +31,7 @@ const DEFAULT_ITERS: usize = 5; fn create_picker(path: &Path) -> FilePicker { let mut picker = FilePicker::new(fff::FilePickerOptions { base_path: path.to_string_lossy().to_string(), - warmup_mmap_cache: false, + enable_mmap_cache: false, mode: fff::FFFMode::Neovim, ..Default::default() }) diff --git a/crates/fff-nvim/src/bin/jemalloc_profile.rs b/crates/fff-nvim/src/bin/jemalloc_profile.rs index bb839215..cda913be 100644 --- a/crates/fff-nvim/src/bin/jemalloc_profile.rs +++ b/crates/fff-nvim/src/bin/jemalloc_profile.rs @@ -187,7 +187,7 @@ fn main() -> Result<(), Box> { shared_frecency.clone(), fff::FilePickerOptions { base_path: base_path.clone(), - warmup_mmap_cache: false, + enable_mmap_cache: false, mode: FFFMode::Neovim, ..Default::default() }, diff --git a/crates/fff-nvim/src/bin/search_profiler.rs b/crates/fff-nvim/src/bin/search_profiler.rs index 34362320..09ff11fc 100644 --- a/crates/fff-nvim/src/bin/search_profiler.rs +++ b/crates/fff-nvim/src/bin/search_profiler.rs @@ -42,7 +42,7 @@ fn main() { shared_frecency.clone(), fff::FilePickerOptions { base_path: canonical_path.to_string_lossy().to_string(), - warmup_mmap_cache: false, + enable_mmap_cache: false, mode: FFFMode::Neovim, ..Default::default() }, diff --git a/crates/fff-nvim/src/bin/test_memory_leak.rs b/crates/fff-nvim/src/bin/test_memory_leak.rs index 5ec7dff0..9707ddea 100644 --- a/crates/fff-nvim/src/bin/test_memory_leak.rs +++ b/crates/fff-nvim/src/bin/test_memory_leak.rs @@ -88,7 +88,7 @@ fn main() -> Result<(), Box> { shared_frecency.clone(), fff::FilePickerOptions { base_path: base_path.clone(), - warmup_mmap_cache: false, + enable_mmap_cache: false, mode: FFFMode::Neovim, ..Default::default() }, diff --git a/crates/fff-nvim/src/bin/test_watcher.rs b/crates/fff-nvim/src/bin/test_watcher.rs index ee4b6bc8..e4b29de3 100644 --- a/crates/fff-nvim/src/bin/test_watcher.rs +++ b/crates/fff-nvim/src/bin/test_watcher.rs @@ -50,7 +50,7 @@ fn main() -> Result<(), Box> { shared_frecency.clone(), fff::FilePickerOptions { base_path: base_path.clone(), - warmup_mmap_cache: false, + enable_mmap_cache: false, mode: FFFMode::default(), ..Default::default() }, diff --git a/crates/fff-nvim/src/lib.rs b/crates/fff-nvim/src/lib.rs index 4c57c658..33cd163e 100644 --- a/crates/fff-nvim/src/lib.rs +++ b/crates/fff-nvim/src/lib.rs @@ -80,7 +80,8 @@ pub fn init_file_picker(_: &Lua, base_path: String) -> LuaResult { FRECENCY.clone(), fff::FilePickerOptions { base_path, - warmup_mmap_cache: true, + enable_mmap_cache: true, + enable_content_indexing: true, mode: FFFMode::Neovim, ..Default::default() }, @@ -112,7 +113,8 @@ fn reinit_file_picker_internal(path: &Path) -> Result<(), Error> { FRECENCY.clone(), fff::FilePickerOptions { base_path: path.to_string_lossy().to_string(), - warmup_mmap_cache: true, + enable_mmap_cache: true, + enable_content_indexing: true, mode: FFFMode::Neovim, ..Default::default() }, diff --git a/doc/fff.nvim.txt b/doc/fff.nvim.txt index 908569c0..9a38f555 100644 --- a/doc/fff.nvim.txt +++ b/doc/fff.nvim.txt @@ -1,5 +1,5 @@ *fff.nvim.txt* - For Neovim >= 0.10.0 Last change: 2026 April 18 + For Neovim >= 0.10.0 Last change: 2026 April 19 ============================================================================== Table of Contents *fff.nvim-table-of-contents* diff --git a/packages/fff-bun/examples/grep.ts b/packages/fff-bun/examples/grep.ts index a84b38b2..bc53eb38 100755 --- a/packages/fff-bun/examples/grep.ts +++ b/packages/fff-bun/examples/grep.ts @@ -111,7 +111,6 @@ async function main() { console.log(`${DIM}Initializing index for: ${directory}${RESET}`); const createResult = FileFinder.create({ basePath: directory, - warmupMmapCache: true, }); if (!createResult.ok) { diff --git a/packages/fff-bun/src/ffi.ts b/packages/fff-bun/src/ffi.ts index c3520faa..c0757c4b 100644 --- a/packages/fff-bun/src/ffi.ts +++ b/packages/fff-bun/src/ffi.ts @@ -50,7 +50,9 @@ const ffiDefinition = { FFIType.cstring, // frecency_db_path FFIType.cstring, // history_db_path FFIType.bool, // use_unsafe_no_lock - FFIType.bool, // warmup_mmap_cache + FFIType.bool, // enable_mmap_cache + FFIType.bool, // enable_content_indexing + FFIType.bool, // watch FFIType.bool, // ai_mode ], returns: FFIType.ptr, @@ -419,7 +421,9 @@ export function ffiCreate( frecencyDbPath: string, historyDbPath: string, useUnsafeNoLock: boolean, - warmupMmapCache: boolean, + enableMmapCache: boolean, + enableContentIndexing: boolean, + watch: boolean, aiMode: boolean, ): Result { const library = loadLibrary(); @@ -428,7 +432,9 @@ export function ffiCreate( ptr(encodeString(frecencyDbPath)), ptr(encodeString(historyDbPath)), useUnsafeNoLock, - warmupMmapCache, + enableMmapCache, + enableContentIndexing, + watch, aiMode, ); diff --git a/packages/fff-bun/src/finder.ts b/packages/fff-bun/src/finder.ts index 98fbbf51..1452d73b 100644 --- a/packages/fff-bun/src/finder.ts +++ b/packages/fff-bun/src/finder.ts @@ -38,7 +38,7 @@ import type { GrepOptions, GrepResult, HealthCheck, - InitOptions, + InitOptions as FFFInitOptions, MixedSearchResult, MultiGrepOptions, Result, @@ -107,13 +107,15 @@ export class FileFinder { * }); * ``` */ - static create(options: InitOptions): Result { + static create(options: FFFInitOptions): Result { const result = ffiCreate( options.basePath, options.frecencyDbPath ?? "", options.historyDbPath ?? "", options.useUnsafeNoLock ?? false, - options.warmupMmapCache ?? false, + !(options.disableMmapCache ?? false), + !(options.disableContentIndexing ?? options.disableMmapCache ?? false), + !(options.disableWatch ?? false), options.aiMode ?? false, ); diff --git a/packages/fff-bun/src/types.ts b/packages/fff-bun/src/types.ts index b8962eba..f9d80a16 100644 --- a/packages/fff-bun/src/types.ts +++ b/packages/fff-bun/src/types.ts @@ -30,12 +30,24 @@ export interface InitOptions { /** Use unsafe no-lock mode for databases (optional, defaults to false) */ useUnsafeNoLock?: boolean; /** - * Pre-populate mmap caches for all files after the initial scan completes. - * When enabled, the first grep search will be as fast as subsequent ones - * at the cost of a longer scan time and higher initial memory usage. + * Disable mmap cache warmup after the initial scan. When mmap cache is + * enabled (the default), the first grep search is as fast as subsequent + * ones at the cost of background resources spent on awarming up the cache + */ + disableMmapCache?: boolean; + /** + * Disable the content index built after the initial scan. + * Content indexing enables faster content-aware filtering during grep. + * When omitted, follows `disableMmapCache` for backward compatibility. + * (default: follows `disableMmapCache`) + */ + disableContentIndexing?: boolean; + /** + * Disable the background file-system watcher. When the watcher is + * disabled, files are scanned once but not monitored for changes. * (default: false) */ - warmupMmapCache?: boolean; + disableWatch?: boolean; /** enables optimizations for AI agent assistants. Provide as true if running via mcp/agent */ aiMode?: boolean; } diff --git a/packages/fff-node/src/ffi.ts b/packages/fff-node/src/ffi.ts index ddd84771..43e5ff25 100644 --- a/packages/fff-node/src/ffi.ts +++ b/packages/fff-node/src/ffi.ts @@ -330,7 +330,9 @@ export function ffiCreate( frecencyDbPath: string, historyDbPath: string, useUnsafeNoLock: boolean, - warmupMmapCache: boolean, + enableMmapCache: boolean, + enableContentIndexing: boolean, + watch: boolean, aiMode: boolean, ): Result { loadLibrary(); @@ -342,10 +344,21 @@ export function ffiCreate( DataType.String, // frecency_db_path DataType.String, // history_db_path DataType.Boolean, // use_unsafe_no_lock - DataType.Boolean, // warmup_mmap_cache + DataType.Boolean, // enable_mmap_cache + DataType.Boolean, // enable_content_indexing + DataType.Boolean, // watch DataType.Boolean, // ai_mode ], - [basePath, frecencyDbPath, historyDbPath, useUnsafeNoLock, warmupMmapCache, aiMode], + [ + basePath, + frecencyDbPath, + historyDbPath, + useUnsafeNoLock, + enableMmapCache, + enableContentIndexing, + watch, + aiMode, + ], ); const success = structData.success !== 0; diff --git a/packages/fff-node/src/finder.ts b/packages/fff-node/src/finder.ts index 2cb86b22..6486c20b 100644 --- a/packages/fff-node/src/finder.ts +++ b/packages/fff-node/src/finder.ts @@ -112,7 +112,9 @@ export class FileFinder { options.frecencyDbPath ?? "", options.historyDbPath ?? "", options.useUnsafeNoLock ?? false, - options.warmupMmapCache ?? false, + !(options.disableMmapCache ?? false), + !(options.disableContentIndexing ?? options.disableMmapCache ?? false), + !(options.disableWatch ?? false), options.aiMode ?? false, ); diff --git a/packages/fff-node/src/types.ts b/packages/fff-node/src/types.ts index d385520c..43afcec2 100644 --- a/packages/fff-node/src/types.ts +++ b/packages/fff-node/src/types.ts @@ -30,12 +30,25 @@ export interface InitOptions { /** Use unsafe no-lock mode for databases (optional, defaults to false) */ useUnsafeNoLock?: boolean; /** - * Pre-populate mmap caches for all files after the initial scan completes. - * When enabled, the first grep search will be as fast as subsequent ones - * at the cost of a longer scan time and higher initial memory usage. + * Disable mmap cache warmup after the initial scan. When mmap cache is + * enabled (the default), the first grep search is as fast as subsequent + * ones at the cost of a longer scan time and higher initial memory usage. * (default: false) */ - warmupMmapCache?: boolean; + disableMmapCache?: boolean; + /** + * Disable the content index built after the initial scan. + * Content indexing enables faster content-aware filtering during grep. + * When omitted, follows `disableMmapCache` for backward compatibility. + * (default: follows `disableMmapCache`) + */ + disableContentIndexing?: boolean; + /** + * Disable the background file-system watcher. When the watcher is + * disabled, files are scanned once but not monitored for changes. + * (default: false) + */ + disableWatch?: boolean; /** enables optimizations for AI agent assistants. Provide as true if running via mcp/agent */ aiMode?: boolean; }