Compare commits

...

1 Commits

Author SHA1 Message Date
Dmitriy Kovalenko d135a2bc73 fix: add follow_symlinks option (#375)
Expose follow_symlinks configuration option for following symbolic links during file indexing.

- Add follow_symlinks field to FilePickerOptions (default false)
- Thread option through FilePicker, ScanConfig, walk_filesystem
- Add lua config: vim.g.fff.follow_symlinks (default false)
- Pass config from lua -> rust FFI init_file_picker

Root cause: WalkBuilder.follow_links() hardcoded to false at file_picker.rs:1738

Refs #375

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-20 11:35:01 -07:00
7 changed files with 28 additions and 3 deletions
+2
View File
@@ -270,6 +270,7 @@ pub unsafe extern "C" fn fff_create_instance2(
watch,
mode,
cache_budget,
follow_symlinks: false,
},
) {
return FffResult::err(&format!("Failed to init file picker: {}", e));
@@ -924,6 +925,7 @@ pub unsafe extern "C" fn fff_restart_index(
watch,
mode,
cache_budget: None,
follow_symlinks: false,
},
) {
Ok(()) => FffResult::ok_empty(),
+14 -1
View File
@@ -444,6 +444,8 @@ pub struct FilePickerOptions {
pub cache_budget: Option<ContentCacheBudget>,
/// When `false`, `new_with_shared_state` skips the background file watcher.
pub watch: bool,
/// Follow symbolic links during file indexing.
pub follow_symlinks: bool,
}
impl Default for FilePickerOptions {
@@ -455,6 +457,7 @@ impl Default for FilePickerOptions {
mode: FFFMode::default(),
cache_budget: None,
watch: true,
follow_symlinks: false,
}
}
}
@@ -471,6 +474,7 @@ pub struct FilePicker {
enable_mmap_cache: bool,
enable_content_indexing: bool,
watch: bool,
follow_symlinks: bool,
}
impl std::fmt::Debug for FilePicker {
@@ -524,6 +528,10 @@ impl FilePicker {
self.watch
}
pub fn follows_symlinks(&self) -> bool {
self.follow_symlinks
}
pub fn mode(&self) -> FFFMode {
self.mode
}
@@ -717,6 +725,7 @@ impl FilePicker {
enable_mmap_cache: options.enable_mmap_cache,
enable_content_indexing: options.enable_content_indexing,
watch: options.watch,
follow_symlinks: options.follow_symlinks,
})
}
@@ -741,6 +750,7 @@ impl FilePicker {
let content_indexing = picker.enable_content_indexing;
let watch = picker.watch;
let mode = picker.mode;
let follow_symlinks = picker.follow_symlinks;
let signals = picker.scan_signals();
let scanned_files_counter = picker.scanned_files_counter();
@@ -767,6 +777,7 @@ impl FilePicker {
watch,
auto_cache_budget: true,
install_watcher: true,
follow_symlinks,
},
)
.spawn();
@@ -796,6 +807,7 @@ impl FilePicker {
&self.scanned_files_count,
&empty_frecency,
self.mode,
self.follow_symlinks,
)?;
self.sync_data = sync;
@@ -1711,6 +1723,7 @@ impl FileSync {
synced_files_count: &Arc<AtomicUsize>,
shared_frecency: &SharedFrecency,
mode: FFFMode,
follow_symlinks: bool,
) -> Result<FileSync, Error> {
use ignore::WalkBuilder;
@@ -1729,7 +1742,7 @@ impl FileSync {
.git_exclude(true)
.git_global(true)
.ignore(true)
.follow_links(false)
.follow_links(follow_symlinks)
.threads(bg_threads);
if !is_git_repo && let Some(overrides) = non_git_repo_overrides(base_path) {
+3
View File
@@ -39,6 +39,7 @@ pub(crate) struct ScanConfig {
pub(crate) watch: bool,
pub(crate) auto_cache_budget: bool,
pub(crate) install_watcher: bool,
pub(crate) follow_symlinks: bool,
}
/// A fully-configured scan job ready to run on a background thread.
@@ -87,6 +88,7 @@ impl ScanJob {
watch: picker.has_watcher(),
auto_cache_budget: !picker.has_explicit_cache_budget(),
install_watcher: false, // the watcher is independent of rescan, it is not restarting EVER
follow_symlinks: picker.follows_symlinks(),
};
drop(guard); // just a sanity check
@@ -157,6 +159,7 @@ impl ScanJob {
&scanned_files_counter,
&shared_frecency,
mode,
config.follow_symlinks,
) {
Ok(sync) => sync,
Err(e) => {
+1
View File
@@ -269,6 +269,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
cache_budget: args
.max_cached_files
.map(fff::ContentCacheBudget::new_for_repo),
follow_symlinks: false,
},
)
.map_err(|e| format!("Failed to init file picker: {}", e))?;
+5 -1
View File
@@ -57,7 +57,10 @@ pub fn destroy_query_db(_: &Lua, _: ()) -> LuaResult<bool> {
Ok(QUERY_TRACKER.destroy().into_lua_result()?.is_some())
}
pub fn init_file_picker(_: &Lua, base_path: String) -> LuaResult<bool> {
pub fn init_file_picker(
_: &Lua,
(base_path, follow_symlinks): (String, Option<bool>),
) -> LuaResult<bool> {
{
let guard = FILE_PICKER.read().into_lua_result()?;
if guard.is_some() {
@@ -73,6 +76,7 @@ pub fn init_file_picker(_: &Lua, base_path: String) -> LuaResult<bool> {
enable_mmap_cache: true,
enable_content_indexing: true,
mode: FFFMode::Neovim,
follow_symlinks: follow_symlinks.unwrap_or(false),
..Default::default()
},
)
+2
View File
@@ -65,6 +65,7 @@ local M = {}
--- @field max_threads number
--- @field lazy_sync boolean
--- @field prompt_vim_mode boolean
--- @field follow_symlinks boolean
--- @field layout FffLayoutConfig
--- @field preview FffPreviewConfig
--- @field keymaps FffKeymapsConfig
@@ -201,6 +202,7 @@ local function init()
lazy_sync = true, -- set to false if you want file indexing to start on open
prompt_vim_mode = false, -- set to true to enable vim-mode in the prompt: <Esc> leaves insert for normal mode bindings (also allows <leader>p or <leader>l to jump around) the second <Esc> closes the picker
wrap_around = false, -- set to true to wrap cursor to the opposite end when reaching the first/last item
follow_symlinks = false, -- set to true to follow symbolic links during file indexing
layout = {
height = 0.8,
width = 0.8,
+1 -1
View File
@@ -118,7 +118,7 @@ M.ensure_initialized = function()
local ok, result = pcall(fuzzy.init_db, frecency_db_path, history_db_path, true)
if not ok then vim.notify('Failed to databases: ' .. tostring(result), vim.log.levels.WARN) end
ok, result = pcall(fuzzy.init_file_picker, config.base_path)
ok, result = pcall(fuzzy.init_file_picker, config.base_path, config.follow_symlinks)
if not ok then
vim.notify('Failed to initialize file picker: ' .. tostring(result), vim.log.levels.ERROR)
return fuzzy