d5f207424b
e2e Tests / e2e (macos-latest) (push) Waiting to run
Nix CI / check (push) Waiting to run
Python CI / Python bindings (macos-latest) (push) Waiting to run
e2e Tests / e2e (windows-latest) (push) Waiting to run
Python CI / Python bindings (windows-latest) (push) Waiting to run
Build & Publish / Build Neovim aarch64-apple-darwin (push) Waiting to run
Build & Publish / Build Neovim aarch64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build Neovim x86_64-apple-darwin (push) Waiting to run
Build & Publish / Build Neovim x86_64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build C FFI aarch64-apple-darwin (push) Waiting to run
Build & Publish / Build C FFI aarch64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build C FFI x86_64-apple-darwin (push) Waiting to run
Build & Publish / Build C FFI x86_64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build MCP aarch64-apple-darwin (push) Waiting to run
Build & Publish / Build MCP aarch64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build MCP x86_64-apple-darwin (push) Waiting to run
Build & Publish / Build MCP x86_64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build Python wheels aarch64 (macos-latest) (push) Waiting to run
Build & Publish / Build Python wheels x86_64 (macos-latest) (push) Waiting to run
Build & Publish / Build Python wheels x86_64 (windows-latest) (push) Waiting to run
Build & Publish / Release (push) Blocked by required conditions
Build & Publish / Publish Python wheels to PyPI (push) Blocked by required conditions
Build & Publish / Publish Rust crates (push) Blocked by required conditions
Build & Publish / Publish npm packages (push) Blocked by required conditions
Rust CI / Fuzz Tests (windows-latest) (push) Waiting to run
Rust CI / Test (macos-latest) (push) Waiting to run
Rust CI / Test (windows-latest) (push) Waiting to run
Rust CI / Fuzz Tests (macos-latest) (push) Waiting to run
Python CI / Python bindings (ubuntu-latest) (push) Failing after 0s
Build & Publish / Build Neovim aarch64-linux-android (push) Failing after 0s
Build & Publish / Build Neovim aarch64-unknown-linux-gnu (push) Failing after 0s
Build & Publish / Build MCP x86_64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build Python wheels aarch64 (ubuntu-latest) (push) Failing after 0s
Build & Publish / Build Python wheels x86_64 (ubuntu-latest) (push) Failing after 1s
Build & Publish / Build Python sdist (push) Failing after 1s
Rust CI / Fuzz Tests (ubuntu-latest) (push) Failing after 1s
Rust CI / Build i686-unknown-linux-gnu (push) Failing after 0s
Rust CI / cargo clippy (push) Failing after 1s
e2e Tests / e2e (ubuntu-latest) (push) Failing after 1s
Lua CI / luacheck lint (push) Failing after 1s
Build & Publish / Build Neovim aarch64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build Neovim x86_64-unknown-linux-gnu (push) Failing after 0s
Build & Publish / Build Neovim x86_64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build C FFI aarch64-linux-android (push) Failing after 0s
Build & Publish / Build C FFI aarch64-unknown-linux-gnu (push) Failing after 0s
Build & Publish / Build C FFI aarch64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build C FFI x86_64-unknown-linux-gnu (push) Failing after 0s
Build & Publish / Build C FFI x86_64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build MCP aarch64-unknown-linux-gnu (push) Failing after 1s
Build & Publish / Build MCP aarch64-unknown-linux-musl (push) Failing after 0s
Build & Publish / Build MCP x86_64-unknown-linux-gnu (push) Failing after 1s
Spelling / Spell Check with Typos (push) Failing after 0s
Rust CI / Test (ubuntu-latest) (push) Failing after 0s
Rust CI / cargo fmt (push) Failing after 0s
Stylua / Check lua files using Stylua (push) Failing after 1s
Lua CI / lua-language-server type check (push) Failing after 12m29s
e2e Tests / e2e (alpine-musl) (push) Successful in 57m31s
177 lines
6.5 KiB
Lua
177 lines
6.5 KiB
Lua
local fuzzy = require('fff.fuzzy')
|
|
if not fuzzy then error('Failed to load fff.fuzzy module. Ensure the Rust backend is compiled and available.') end
|
|
|
|
local M = {}
|
|
|
|
---@class fff.core.State
|
|
local state = {
|
|
---@type boolean
|
|
initialized = false,
|
|
---@type boolean
|
|
file_picker_initialized = false,
|
|
}
|
|
|
|
---@param config table
|
|
local function setup_global_autocmds(config)
|
|
local group = vim.api.nvim_create_augroup('fff_file_tracking', { clear = true })
|
|
|
|
if config.frecency.enabled then
|
|
vim.api.nvim_create_autocmd({ 'BufEnter' }, {
|
|
group = group,
|
|
desc = 'Track file access for FFF frecency',
|
|
callback = function(args)
|
|
local file_path = args.file
|
|
if not (file_path and file_path ~= '' and not vim.startswith(file_path, 'term://')) then return end
|
|
|
|
vim.uv.fs_stat(file_path, function(err, stat)
|
|
if err or not stat then return end
|
|
|
|
vim.uv.fs_realpath(file_path, function(rp_err, real_path)
|
|
if rp_err or not real_path then return end
|
|
local ok, track_err = pcall(fuzzy.track_access, real_path)
|
|
|
|
if not ok then
|
|
vim.schedule(
|
|
function() vim.notify('FFF: Failed to track file access: ' .. tostring(track_err), vim.log.levels.ERROR) end
|
|
)
|
|
end
|
|
end)
|
|
end)
|
|
end,
|
|
})
|
|
end
|
|
|
|
-- 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()
|
|
-- Window-local `:lcd` / `:tcd` are per-window — they don't change the
|
|
-- effective project root for the picker, so bail before touching
|
|
-- anything else.
|
|
if vim.v.event.scope == 'window' then return end
|
|
if not state.initialized then return end
|
|
|
|
local new_cwd = vim.v.event.cwd
|
|
if not new_cwd or new_cwd == '' then return end
|
|
|
|
-- Canonicalize both sides before comparing. `vim.v.event.cwd` is
|
|
-- whatever the caller passed to `:cd` (often unexpanded, sometimes
|
|
-- containing `~` or symlinks), while `config.base_path` is the form
|
|
-- the picker was last re-indexed against (post-`expand`). Without
|
|
-- resolving symlinks + ensuring an absolute path, trivially
|
|
-- equivalent paths compare as different (`/private/var/x` vs
|
|
-- `/var/x` on macOS, resolved-vs-unresolved symlinks from LSP root
|
|
-- detection, etc.) and every such mismatch schedules a 450k-file
|
|
-- reindex through the Rust side.
|
|
local function canonicalize(p)
|
|
if not p or p == '' then return p end
|
|
local abs = vim.fn.fnamemodify(vim.fn.expand(p), ':p')
|
|
-- `:p` leaves a trailing slash on directories — strip for
|
|
-- comparison stability.
|
|
abs = abs:gsub('/+$', '')
|
|
local ok, resolved = pcall(vim.fn.resolve, abs)
|
|
return (ok and resolved ~= '') and resolved or abs
|
|
end
|
|
|
|
local new_canonical = canonicalize(new_cwd)
|
|
local base_canonical = canonicalize(config.base_path)
|
|
if new_canonical == base_canonical then return end
|
|
|
|
vim.schedule(function()
|
|
local change_ok, err = pcall(M.change_indexing_directory, new_canonical)
|
|
if not change_ok then
|
|
vim.notify('FFF: Failed to change indexing directory: ' .. tostring(err), vim.log.levels.ERROR)
|
|
end
|
|
end)
|
|
end,
|
|
desc = 'Automatically sync FFF directory changes',
|
|
})
|
|
end
|
|
|
|
--- @return boolean
|
|
M.is_file_picker_initialized = function() return state.file_picker_initialized end
|
|
|
|
--- Change the base directory for the file picker. Triggers a reindex on the
|
|
--- Rust side and updates `config.base_path` so subsequent `:cd` events compare
|
|
--- against the new root.
|
|
--- @param new_path string New directory path to use as base
|
|
--- @return boolean ok `true` if the reindex was scheduled, `false` otherwise
|
|
M.change_indexing_directory = function(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 fff_rust = M.ensure_initialized()
|
|
local config = require('fff.conf').get()
|
|
local ok, err = pcall(fff_rust.restart_index_in_path, expanded_path, {
|
|
follow_symlinks = config.follow_symlinks,
|
|
enable_fs_root_scanning = config.enable_fs_root_scanning,
|
|
enable_home_dir_scanning = config.enable_home_dir_scanning,
|
|
enable_filename_constraint = config.grep and config.grep.enable_filename_constraint,
|
|
})
|
|
if not ok then
|
|
vim.notify('Failed to change directory: ' .. err, vim.log.levels.ERROR)
|
|
return false
|
|
end
|
|
|
|
require('fff.conf').get().base_path = expanded_path
|
|
return true
|
|
end
|
|
|
|
M.ensure_initialized = function()
|
|
if state.initialized then return fuzzy end
|
|
state.initialized = true
|
|
|
|
local config = require('fff.conf').get()
|
|
if config.logging.enabled then
|
|
local log_success, log_error =
|
|
pcall(fuzzy.init_tracing, config.logging.log_file, config.logging.log_level, config.logging.retain_runs)
|
|
if log_success then
|
|
M.log_file_path = log_error
|
|
else
|
|
vim.notify('Failed to initialize logging: ' .. (tostring(log_error) or 'unknown error'), vim.log.levels.WARN)
|
|
end
|
|
end
|
|
|
|
local frecency_db_path = config.frecency.db_path or (vim.fn.stdpath('cache') .. '/fff_frecency')
|
|
local history_db_path = config.history.db_path or (vim.fn.stdpath('data') .. '/fff_history')
|
|
|
|
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, {
|
|
follow_symlinks = config.follow_symlinks,
|
|
enable_fs_root_scanning = config.enable_fs_root_scanning,
|
|
enable_home_dir_scanning = config.enable_home_dir_scanning,
|
|
enable_filename_constraint = config.grep and config.grep.enable_filename_constraint,
|
|
})
|
|
if not ok then
|
|
vim.notify('Failed to initialize file picker: ' .. tostring(result), vim.log.levels.ERROR)
|
|
return fuzzy
|
|
end
|
|
|
|
state.file_picker_initialized = true
|
|
setup_global_autocmds(config)
|
|
|
|
local highlights = require('fff.highlights')
|
|
highlights.setup()
|
|
|
|
vim.api.nvim_create_autocmd('ColorScheme', {
|
|
group = vim.api.nvim_create_augroup('fff_highlights', { clear = true }),
|
|
callback = function() highlights.setup() end,
|
|
desc = 'Re-apply FFF highlights on colorscheme change',
|
|
})
|
|
|
|
return fuzzy
|
|
end
|
|
|
|
return M
|