Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lua/fff/file_picker/image.lua
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ end
function M.display_image_info(file_path, bufnr, reason)
local info = {}

local stat = vim.loop.fs_stat(file_path)
local stat = vim.uv.fs_stat(file_path)
if stat then
table.insert(info, string.format('📁 File: %s', vim.fn.fnamemodify(file_path, ':t')))
table.insert(info, string.format('📏 Size: %d bytes', stat.size))
Expand Down
4 changes: 2 additions & 2 deletions lua/fff/file_picker/preview.lua
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ end
--- @param file_path string Path to the file
--- @return table File information
function M.get_file_info(file_path)
local stat = vim.loop.fs_stat(file_path)
local stat = vim.uv.fs_stat(file_path)
if not stat then return nil end

local info = {
Expand Down Expand Up @@ -554,7 +554,7 @@ function M.preview(file_path, bufnr, file)
M.state.current_file = file_path
M.state.bufnr = bufnr

local stat = vim.loop.fs_stat(file_path)
local stat = vim.uv.fs_stat(file_path)
if not stat then
M.clear_buffer_completely(bufnr)
safe_set_buffer_lines(bufnr, 0, -1, false, {
Expand Down
1 change: 1 addition & 0 deletions lua/fff/fuzzy.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ M.get_keyword_range = rust_module.get_keyword_range
M.guess_edit_range = rust_module.guess_edit_range
M.get_words = rust_module.get_words
M.init_file_picker = rust_module.init_file_picker
M.restart_index_in_path = rust_module.restart_index_in_path
M.scan_files = rust_module.scan_files
M.get_cached_files = rust_module.get_cached_files
M.fuzzy_search_files = rust_module.fuzzy_search_files
Expand Down
143 changes: 66 additions & 77 deletions lua/fff/main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ function M.setup_global_file_tracking()
if file_path and file_path ~= '' and not vim.startswith(file_path, 'term://') then
-- never block the UI
vim.schedule(function()
local stat = vim.loop.fs_stat(file_path)
local stat = vim.uv.fs_stat(file_path)
if stat and stat.type == 'file' then
local relative_path = vim.fn.fnamemodify(file_path, ':.')
pcall(fuzzy.access_file, relative_path)
Expand All @@ -136,6 +136,26 @@ function M.setup_global_file_tracking()
end,
desc = 'Track file access for FFF frecency',
})

-- make sure that this won't work correctly if autochdir plugins are enabled
-- using a pure :cd command but will work using lua api or :e command
vim.api.nvim_create_autocmd('DirChanged', {
group = group,
callback = function()
local new_cwd = vim.v.event.cwd
if M.is_initialized() and new_cwd and new_cwd ~= M.config.base_path then
vim.schedule(function()
local ok, err = pcall(M.change_indexing_directory, new_cwd)
if not ok then
vim.notify('FFF: Failed to change indexing directory: ' .. tostring(err), vim.log.levels.ERROR)
else
M.config.base_path = new_cwd
end
end)
end
end,
desc = 'Automatically sync FFF directory changes',
})
end

function M.setup_commands()
Expand Down Expand Up @@ -234,12 +254,7 @@ function M.find_in_git_root()
return
end

local picker_ok, picker_ui = pcall(require, 'fff.picker_ui')
if picker_ok then
picker_ui.open({ title = 'Git Files', cwd = git_root })
else
vim.notify('Failed to load picker UI', vim.log.levels.ERROR)
end
M.find_files_in_dir(git_root)
end

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

function M.debug_file_ordering()
print('FFF Debug File Ordering')
print('=======================')

if not M.is_initialized() then
print('File picker not initialized. Run :FFFScan first.')
return
end

local picker = M.picker or M.core
print('Getting top 10 files with debug info...')

-- Enable debug mode temporarily
local old_debug = M.config.debug.show_scores
M.config.debug.show_scores = true

-- Search with empty query to get default ordering
local files = picker.search_files('', 10)

print('🏆 TOP FILES (in order they appear):')
print('=' .. string.rep('=', 70))

for i, file in ipairs(files) do
local frecency_stars = ''
if file.frecency_score > 0 then frecency_stars = ' ⭐' .. file.frecency_score end

-- Extract directory information
local dir = vim.fn.fnamemodify(file.relative_path, ':h')
local filename = vim.fn.fnamemodify(file.relative_path, ':t')
local dir_display = (dir == '.' or dir == '') and 'root' or dir

-- Get score information for this file
local score = picker.get_file_score(i)

print(string.format('%2d. %s%s', i, filename, frecency_stars))
print(string.format(' Path: %s/', dir_display))
print(string.format(' Debug: %s', score and score.match_type or 'no debug info'))
if score then
print(
string.format(
' Total Score: %d (base=%d, name_bonus=%d, special_bonus=%d, frec=%d, dist=%d)',
score.total,
score.base_score,
score.filename_bonus,
score.special_filename_bonus,
score.frecency_boost,
score.distance_penalty
)
)
else
print(' Total Score: N/A (no score data)')
end

local now = os.time()
local age_hours = math.floor((now - file.modified) / 3600)
local age_days = math.floor(age_hours / 24)
print(string.format(' Age: %d hours (%d days) since last modified', age_hours, age_days))
print('')
end

print('💡 EXPLANATION:')
print('• Files are sorted by FRECENCY first (⭐ score), then by modification time')
print('• Frecency combines how often AND how recently you accessed files')
print('• The file at #1 has either:')
print(' - Highest frecency score, OR')
print(' - Same frecency as others but most recent modification')

M.config.debug.show_scores = old_debug
end

function M.health_check()
local health = {
ok = true,
Expand Down Expand Up @@ -460,4 +405,48 @@ end

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

--- Find files in a specific directory
--- @param directory string Directory path to search in
function M.find_files_in_dir(directory)
if not directory then
vim.notify('Directory path required for find_files_in_dir', vim.log.levels.ERROR)
return
end

M.change_indexing_directory(directory)

local picker_ok, picker_ui = pcall(require, 'fff.picker_ui')
if picker_ok then
picker_ui.open({ title = 'Files in ' .. vim.fn.fnamemodify(directory, ':t') })
else
vim.notify('Failed to load picker UI', vim.log.levels.ERROR)
end
end

--- Change the base directory for the file picker
--- @param new_path string New directory path to use as base
--- @return boolean `true` if successful, `false` otherwise
function M.change_indexing_directory(new_path)
if not new_path or new_path == '' then
vim.notify('Directory path is required', vim.log.levels.ERROR)
return false
end

local expanded_path = vim.fn.expand(new_path)

if vim.fn.isdirectory(expanded_path) ~= 1 then
vim.notify('Directory does not exist: ' .. expanded_path, vim.log.levels.ERROR)
return false
end

local ok, result = pcall(fuzzy.restart_index_in_path, expanded_path)
if not ok then
vim.notify('Failed to change directory: ' .. result, vim.log.levels.ERROR)
return false
end

M.config.base_path = expanded_path
return true
end

return M
2 changes: 0 additions & 2 deletions lua/fff/picker_ui.lua
Original file line number Diff line number Diff line change
Expand Up @@ -930,7 +930,6 @@ function M.scroll_preview_down()
preview.scroll(scroll_lines)
end

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

Expand Down Expand Up @@ -960,7 +959,6 @@ function M.select(action)
end
end

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

Expand Down
35 changes: 35 additions & 0 deletions lua/fff/rust/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,37 @@ pub fn init_file_picker(_: &Lua, base_path: String) -> LuaResult<bool> {
Ok(true)
}

fn reinit_file_picker_internal(path: std::path::PathBuf) -> Result<(), Error> {
let mut file_picker = FILE_PICKER.write().map_err(|_| Error::AcquireItemLock)?;

// drop should clean it anyway but just to be extra sure
if let Some(picker) = file_picker.take() {
picker.stop_background_monitor();
}

let new_picker = FilePicker::new(path.to_string_lossy().to_string())?;
Comment thread
dmtrKovalenko marked this conversation as resolved.
*file_picker = Some(new_picker);

Ok(())
}

pub fn restart_index_in_path(_: &Lua, new_path: String) -> LuaResult<bool> {
let path = std::path::PathBuf::from(&new_path);
if !path.exists() {
return Err(LuaError::RuntimeError(format!(
"Path does not exist: {}",
new_path
)));
}

let canonical_path = path.canonicalize().map_err(|e| {
LuaError::RuntimeError(format!("Failed to canonicalize path '{}': {}", new_path, e))
})?;

reinit_file_picker_internal(canonical_path)?;
Ok(true)
}

pub fn scan_files(_: &Lua, _: ()) -> LuaResult<()> {
let file_picker = FILE_PICKER.read().map_err(|_| Error::AcquireItemLock)?;
let picker = file_picker
Expand Down Expand Up @@ -163,6 +194,10 @@ fn create_exports(lua: &Lua) -> LuaResult<LuaTable> {
exports.set("init_db", lua.create_function(init_db)?)?;
exports.set("destroy_db", lua.create_function(destroy_db)?)?;
exports.set("init_file_picker", lua.create_function(init_file_picker)?)?;
exports.set(
"restart_index_in_path",
lua.create_function(restart_index_in_path)?,
)?;
exports.set("scan_files", lua.create_function(scan_files)?)?;
exports.set("get_cached_files", lua.create_function(get_cached_files)?)?;
exports.set(
Expand Down
Loading