Skip to content

Commit d3e34a7

Browse files
committed
feat: Correctly handle change of cwd automatically and via API
closes #20 This allows to use ``` require('fff.main').find_files_in_dir('/Users/neogoose/dev/lightsource') ``` to change cwd and start the picker immediately to choose the file.
1 parent 5607e61 commit d3e34a7

6 files changed

Lines changed: 105 additions & 82 deletions

File tree

lua/fff/file_picker/image.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ end
127127
function M.display_image_info(file_path, bufnr, reason)
128128
local info = {}
129129

130-
local stat = vim.loop.fs_stat(file_path)
130+
local stat = vim.uv.fs_stat(file_path)
131131
if stat then
132132
table.insert(info, string.format('📁 File: %s', vim.fn.fnamemodify(file_path, ':t')))
133133
table.insert(info, string.format('📏 Size: %d bytes', stat.size))

lua/fff/file_picker/preview.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ end
157157
--- @param file_path string Path to the file
158158
--- @return table File information
159159
function M.get_file_info(file_path)
160-
local stat = vim.loop.fs_stat(file_path)
160+
local stat = vim.uv.fs_stat(file_path)
161161
if not stat then return nil end
162162

163163
local info = {
@@ -554,7 +554,7 @@ function M.preview(file_path, bufnr, file)
554554
M.state.current_file = file_path
555555
M.state.bufnr = bufnr
556556

557-
local stat = vim.loop.fs_stat(file_path)
557+
local stat = vim.uv.fs_stat(file_path)
558558
if not stat then
559559
M.clear_buffer_completely(bufnr)
560560
safe_set_buffer_lines(bufnr, 0, -1, false, {

lua/fff/fuzzy.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ M.get_keyword_range = rust_module.get_keyword_range
1515
M.guess_edit_range = rust_module.guess_edit_range
1616
M.get_words = rust_module.get_words
1717
M.init_file_picker = rust_module.init_file_picker
18+
M.restart_index_in_path = rust_module.restart_index_in_path
1819
M.scan_files = rust_module.scan_files
1920
M.get_cached_files = rust_module.get_cached_files
2021
M.fuzzy_search_files = rust_module.fuzzy_search_files

lua/fff/main.lua

Lines changed: 66 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ function M.setup_global_file_tracking()
126126
if file_path and file_path ~= '' and not vim.startswith(file_path, 'term://') then
127127
-- never block the UI
128128
vim.schedule(function()
129-
local stat = vim.loop.fs_stat(file_path)
129+
local stat = vim.uv.fs_stat(file_path)
130130
if stat and stat.type == 'file' then
131131
local relative_path = vim.fn.fnamemodify(file_path, ':.')
132132
pcall(fuzzy.access_file, relative_path)
@@ -136,6 +136,26 @@ function M.setup_global_file_tracking()
136136
end,
137137
desc = 'Track file access for FFF frecency',
138138
})
139+
140+
-- make sure that this won't work correctly if autochdir plugins are enabled
141+
-- using a pure :cd command but will work using lua api or :e command
142+
vim.api.nvim_create_autocmd('DirChanged', {
143+
group = group,
144+
callback = function()
145+
local new_cwd = vim.v.event.cwd
146+
if M.is_initialized() and new_cwd and new_cwd ~= M.config.base_path then
147+
vim.schedule(function()
148+
local ok, err = pcall(M.change_indexing_directory, new_cwd)
149+
if not ok then
150+
vim.notify('FFF: Failed to change indexing directory: ' .. tostring(err), vim.log.levels.ERROR)
151+
else
152+
M.config.base_path = new_cwd
153+
end
154+
end)
155+
end
156+
end,
157+
desc = 'Automatically sync FFF directory changes',
158+
})
139159
end
140160

141161
function M.setup_commands()
@@ -234,12 +254,7 @@ function M.find_in_git_root()
234254
return
235255
end
236256

237-
local picker_ok, picker_ui = pcall(require, 'fff.picker_ui')
238-
if picker_ok then
239-
picker_ui.open({ title = 'Git Files', cwd = git_root })
240-
else
241-
vim.notify('Failed to load picker UI', vim.log.levels.ERROR)
242-
end
257+
M.find_files_in_dir(git_root)
243258
end
244259

245260
--- Trigger rescan of files in the current directory
@@ -331,76 +346,6 @@ function M.get_preview(file_path)
331346
return table.concat(lines, '\n')
332347
end
333348

334-
function M.debug_file_ordering()
335-
print('FFF Debug File Ordering')
336-
print('=======================')
337-
338-
if not M.is_initialized() then
339-
print('File picker not initialized. Run :FFFScan first.')
340-
return
341-
end
342-
343-
local picker = M.picker or M.core
344-
print('Getting top 10 files with debug info...')
345-
346-
-- Enable debug mode temporarily
347-
local old_debug = M.config.debug.show_scores
348-
M.config.debug.show_scores = true
349-
350-
-- Search with empty query to get default ordering
351-
local files = picker.search_files('', 10)
352-
353-
print('🏆 TOP FILES (in order they appear):')
354-
print('=' .. string.rep('=', 70))
355-
356-
for i, file in ipairs(files) do
357-
local frecency_stars = ''
358-
if file.frecency_score > 0 then frecency_stars = '' .. file.frecency_score end
359-
360-
-- Extract directory information
361-
local dir = vim.fn.fnamemodify(file.relative_path, ':h')
362-
local filename = vim.fn.fnamemodify(file.relative_path, ':t')
363-
local dir_display = (dir == '.' or dir == '') and 'root' or dir
364-
365-
-- Get score information for this file
366-
local score = picker.get_file_score(i)
367-
368-
print(string.format('%2d. %s%s', i, filename, frecency_stars))
369-
print(string.format(' Path: %s/', dir_display))
370-
print(string.format(' Debug: %s', score and score.match_type or 'no debug info'))
371-
if score then
372-
print(
373-
string.format(
374-
' Total Score: %d (base=%d, name_bonus=%d, special_bonus=%d, frec=%d, dist=%d)',
375-
score.total,
376-
score.base_score,
377-
score.filename_bonus,
378-
score.special_filename_bonus,
379-
score.frecency_boost,
380-
score.distance_penalty
381-
)
382-
)
383-
else
384-
print(' Total Score: N/A (no score data)')
385-
end
386-
387-
local now = os.time()
388-
local age_hours = math.floor((now - file.modified) / 3600)
389-
local age_days = math.floor(age_hours / 24)
390-
print(string.format(' Age: %d hours (%d days) since last modified', age_hours, age_days))
391-
print('')
392-
end
393-
394-
print('💡 EXPLANATION:')
395-
print('• Files are sorted by FRECENCY first (⭐ score), then by modification time')
396-
print('• Frecency combines how often AND how recently you accessed files')
397-
print('• The file at #1 has either:')
398-
print(' - Highest frecency score, OR')
399-
print(' - Same frecency as others but most recent modification')
400-
401-
M.config.debug.show_scores = old_debug
402-
end
403-
404349
function M.health_check()
405350
local health = {
406351
ok = true,
@@ -460,4 +405,48 @@ end
460405

461406
function M.is_initialized() return M.state and M.state.initialized or false end
462407

408+
--- Find files in a specific directory
409+
--- @param directory string Directory path to search in
410+
function M.find_files_in_dir(directory)
411+
if not directory then
412+
vim.notify('Directory path required for find_files_in_dir', vim.log.levels.ERROR)
413+
return
414+
end
415+
416+
M.change_indexing_directory(directory)
417+
418+
local picker_ok, picker_ui = pcall(require, 'fff.picker_ui')
419+
if picker_ok then
420+
picker_ui.open({ title = 'Files in ' .. vim.fn.fnamemodify(directory, ':t') })
421+
else
422+
vim.notify('Failed to load picker UI', vim.log.levels.ERROR)
423+
end
424+
end
425+
426+
--- Change the base directory for the file picker
427+
--- @param new_path string New directory path to use as base
428+
--- @return boolean `true` if successful, `false` otherwise
429+
function M.change_indexing_directory(new_path)
430+
if not new_path or new_path == '' then
431+
vim.notify('Directory path is required', vim.log.levels.ERROR)
432+
return false
433+
end
434+
435+
local expanded_path = vim.fn.expand(new_path)
436+
437+
if vim.fn.isdirectory(expanded_path) ~= 1 then
438+
vim.notify('Directory does not exist: ' .. expanded_path, vim.log.levels.ERROR)
439+
return false
440+
end
441+
442+
local ok, result = pcall(fuzzy.restart_index_in_path, expanded_path)
443+
if not ok then
444+
vim.notify('Failed to change directory: ' .. result, vim.log.levels.ERROR)
445+
return false
446+
end
447+
448+
M.config.base_path = expanded_path
449+
return true
450+
end
451+
463452
return M

lua/fff/picker_ui.lua

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -930,7 +930,6 @@ function M.scroll_preview_down()
930930
preview.scroll(scroll_lines)
931931
end
932932

933-
--- Select current item
934933
function M.select(action)
935934
if not M.state.active then return end
936935

@@ -960,7 +959,6 @@ function M.select(action)
960959
end
961960
end
962961

963-
--- Close picker
964962
function M.close()
965963
if not M.state.active then return end
966964

lua/fff/rust/lib.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,37 @@ pub fn init_file_picker(_: &Lua, base_path: String) -> LuaResult<bool> {
4646
Ok(true)
4747
}
4848

49+
fn reinit_file_picker_internal(path: std::path::PathBuf) -> Result<(), Error> {
50+
let mut file_picker = FILE_PICKER.write().map_err(|_| Error::AcquireItemLock)?;
51+
52+
// drop should clean it anyway but just to be extra sure
53+
if let Some(picker) = file_picker.take() {
54+
picker.stop_background_monitor();
55+
}
56+
57+
let new_picker = FilePicker::new(path.to_string_lossy().to_string())?;
58+
*file_picker = Some(new_picker);
59+
60+
Ok(())
61+
}
62+
63+
pub fn restart_index_in_path(_: &Lua, new_path: String) -> LuaResult<bool> {
64+
let path = std::path::PathBuf::from(&new_path);
65+
if !path.exists() {
66+
return Err(LuaError::RuntimeError(format!(
67+
"Path does not exist: {}",
68+
new_path
69+
)));
70+
}
71+
72+
let canonical_path = path.canonicalize().map_err(|e| {
73+
LuaError::RuntimeError(format!("Failed to canonicalize path '{}': {}", new_path, e))
74+
})?;
75+
76+
reinit_file_picker_internal(canonical_path)?;
77+
Ok(true)
78+
}
79+
4980
pub fn scan_files(_: &Lua, _: ()) -> LuaResult<()> {
5081
let file_picker = FILE_PICKER.read().map_err(|_| Error::AcquireItemLock)?;
5182
let picker = file_picker
@@ -163,6 +194,10 @@ fn create_exports(lua: &Lua) -> LuaResult<LuaTable> {
163194
exports.set("init_db", lua.create_function(init_db)?)?;
164195
exports.set("destroy_db", lua.create_function(destroy_db)?)?;
165196
exports.set("init_file_picker", lua.create_function(init_file_picker)?)?;
197+
exports.set(
198+
"restart_index_in_path",
199+
lua.create_function(restart_index_in_path)?,
200+
)?;
166201
exports.set("scan_files", lua.create_function(scan_files)?)?;
167202
exports.set("get_cached_files", lua.create_function(get_cached_files)?)?;
168203
exports.set(

0 commit comments

Comments
 (0)