diff --git a/README.md b/README.md index 90f9d1e..cb6c42e 100644 --- a/README.md +++ b/README.md @@ -139,16 +139,19 @@ require('fff').setup({ preview_scroll_down = '', toggle_debug = '', }, - hl = { - border = 'FloatBorder', - normal = 'Normal', - cursor = 'CursorLine', - matched = 'IncSearch', - title = 'Title', - prompt = 'Question', - active_file = 'Visual', - frecency = 'Number', - debug = 'Comment', + ui = { + picker = 'default', + hl = { + border = 'FloatBorder', + normal = 'Normal', + cursor = 'CursorLine', + matched = 'IncSearch', + title = 'Title', + prompt = 'Question', + active_file = 'Visual', + frecency = 'Number', + debug = 'Comment', + }, }, frecency = { enabled = true, diff --git a/doc/fff.nvim.txt b/doc/fff.nvim.txt index 06f6db6..2d1d49c 100644 --- a/doc/fff.nvim.txt +++ b/doc/fff.nvim.txt @@ -146,17 +146,20 @@ all available options: preview_scroll_down = '', toggle_debug = '', }, - hl = { - border = 'FloatBorder', - normal = 'Normal', - cursor = 'CursorLine', - matched = 'IncSearch', - title = 'Title', - prompt = 'Question', - active_file = 'Visual', - frecency = 'Number', - debug = 'Comment', - }, + ui = { + picker = 'default', -- one of 'default' or 'mini' (requires `mini.pick`) + hl = { + border = 'FloatBorder', + normal = 'Normal', + cursor = 'CursorLine', + matched = 'IncSearch', + title = 'Title', + prompt = 'Question', + active_file = 'Visual', + frecency = 'Number', + debug = 'Comment', + }, + }, frecency = { enabled = true, db_path = vim.fn.stdpath('cache') .. '/fff_nvim', diff --git a/lua/fff/conf.lua b/lua/fff/conf.lua index c11d90f..eb2a10b 100644 --- a/lua/fff/conf.lua +++ b/lua/fff/conf.lua @@ -141,16 +141,19 @@ local function init() preview_scroll_down = '', toggle_debug = '', }, - hl = { - border = 'FloatBorder', - normal = 'Normal', - cursor = 'CursorLine', - matched = 'IncSearch', - title = 'Title', - prompt = 'Question', - active_file = 'Visual', - frecency = 'Number', - debug = 'Comment', + ui = { + picker = 'default', + hl = { + border = 'FloatBorder', + normal = 'Normal', + cursor = 'CursorLine', + matched = 'IncSearch', + title = 'Title', + prompt = 'Question', + active_file = 'Visual', + frecency = 'Number', + debug = 'Comment', + }, }, frecency = { enabled = true, diff --git a/lua/fff/main.lua b/lua/fff/main.lua index bc93e98..91fadf2 100644 --- a/lua/fff/main.lua +++ b/lua/fff/main.lua @@ -11,11 +11,27 @@ function M.setup(config) vim.g.fff = config end --- Find files in current directory function M.find_files() - local picker_ok, picker_ui = pcall(require, 'fff.picker_ui') - if picker_ok then - picker_ui.open() + local config = require('fff.conf').get() + local picker = config.ui.picker + + if picker == 'default' then + local picker_ok, picker_ui = pcall(require, 'fff.ui.default') + if picker_ok then + picker_ui.open() + else + vim.notify('Failed to load picker UI', vim.log.levels.ERROR) + end + elseif picker == 'mini' then + local ok, _ = pcall(require, 'mini.pick') + if not ok then + vim.notify('mini.pick is not installed', vim.log.levels.ERROR) + return + end + local mini_picker = require('fff.ui.mini') + if not mini_picker.is_initialized() then mini_picker.setup() end + MiniPick.registry.fffiles() else - vim.notify('Failed to load picker UI', vim.log.levels.ERROR) + vim.notify('Unknown picker:' .. picker, vim.log.levels.ERROR) end end @@ -144,6 +160,19 @@ function M.health_check() end end + local optional_plugins = { + { plugin = 'mini.pick', desc = 'Use mini.pick UI' }, + } + + for _, dep in ipairs(optional_plugins) do + local ok, _ = pcall(require, dep.plugin) + if not ok then + table.insert(health.messages, string.format('Optional: %s not found (%s)', dep.plugin, dep.desc)) + else + table.insert(health.messages, string.format('✓ %s found', dep.plugin)) + end + end + if health.ok then vim.notify('FFF health check passed ✓', vim.log.levels.INFO) else @@ -170,11 +199,27 @@ function M.find_files_in_dir(directory) 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') }) + local config = require('fff.conf').get() + local picker = config.ui.picker + + if picker == 'default' then + 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 + elseif picker == 'mini' then + local ok, _ = pcall(require, 'mini.pick') + if not ok then + vim.notify('mini.pick is not installed', vim.log.levels.ERROR) + return + end + local mini_picker = require('fff.ui.mini') + if not mini_picker.is_initialized() then mini_picker.setup() end + MiniPick.registry.fffiles() else - vim.notify('Failed to load picker UI', vim.log.levels.ERROR) + vim.notify('Unknown picker:' .. picker, vim.log.levels.ERROR) end end diff --git a/lua/fff/picker_ui.lua b/lua/fff/ui/default.lua similarity index 99% rename from lua/fff/picker_ui.lua rename to lua/fff/ui/default.lua index 014530c..ac3e025 100644 --- a/lua/fff/picker_ui.lua +++ b/lua/fff/ui/default.lua @@ -465,7 +465,7 @@ end --- Setup window options function M.setup_windows() - local hl = M.state.config.hl + local hl = M.state.config.ui.hl local win_hl = string.format('Normal:%s,FloatBorder:%s,FloatTitle:%s', hl.normal, hl.border, hl.title) vim.api.nvim_win_set_option(M.state.input_win, 'wrap', false) vim.api.nvim_win_set_option(M.state.input_win, 'cursorline', false) @@ -853,7 +853,7 @@ function M.render_list() vim.api.nvim_buf_add_highlight( M.state.list_buf, M.state.ns_id, - M.state.config.hl.active_file, + M.state.config.ui.hl.active_file, cursor_line - 1, 0, -1 @@ -866,7 +866,7 @@ function M.render_list() if remaining_width > 0 then vim.api.nvim_buf_set_extmark(M.state.list_buf, M.state.ns_id, cursor_line - 1, -1, { - virt_text = { { string.rep(' ', remaining_width), M.state.config.hl.active_file } }, + virt_text = { { string.rep(' ', remaining_width), M.state.config.ui.hl.active_file } }, virt_text_pos = 'eol', }) end @@ -906,7 +906,7 @@ function M.render_list() vim.api.nvim_buf_add_highlight( M.state.list_buf, M.state.ns_id, - M.state.config.hl.frecency, + M.state.config.ui.hl.frecency, line_idx - 1, star_start - 1, star_end @@ -932,7 +932,7 @@ function M.render_list() vim.api.nvim_buf_add_highlight(M.state.list_buf, M.state.ns_id, 'Comment', line_idx - 1, 0, -1) end - local virt_text_hl = is_cursor_line and M.state.config.hl.active_file or 'Comment' + local virt_text_hl = is_cursor_line and M.state.config.ui.hl.active_file or 'Comment' vim.api.nvim_buf_set_extmark(M.state.list_buf, M.state.ns_id, line_idx - 1, 0, { virt_text = { { ' (current)', virt_text_hl } }, virt_text_pos = 'right_align', @@ -952,7 +952,7 @@ function M.render_list() end local final_border_hl = border_hl ~= '' and border_hl - or (is_cursor_line and M.state.config.hl.active_file or '') + or (is_cursor_line and M.state.config.ui.hl.active_file or '') if final_border_hl ~= '' or is_cursor_line then vim.api.nvim_buf_set_extmark(M.state.list_buf, M.state.ns_id, line_idx - 1, 0, { @@ -967,7 +967,7 @@ function M.render_list() vim.api.nvim_buf_add_highlight( M.state.list_buf, M.state.ns_id, - config.hl.matched or 'IncSearch', + config.ui.hl.matched or 'IncSearch', line_idx - 1, match_start - 1, match_end diff --git a/lua/fff/ui/mini.lua b/lua/fff/ui/mini.lua new file mode 100644 index 0000000..802bb21 --- /dev/null +++ b/lua/fff/ui/mini.lua @@ -0,0 +1,102 @@ +---@class PickerItem +---@field text string +---@field path string + +local M = { + ---@class FFFPickerState + ---@field current_file_cache string + state = {}, + ns_id = vim.api.nvim_create_namespace('MiniPick FFFiles Picker'), +} + +---@param query string|nil +---@return PickerItem[] +local function find(query) + local file_picker = require('fff.file_picker') + + query = query or '' + local fff_result = file_picker.search_files(query, 100, 4, M.state.current_file_cache, false) + + local items = {} + for _, fff_item in ipairs(fff_result) do + local item = { + text = fff_item.relative_path, + path = fff_item.path, + } + table.insert(items, item) + end + + return items +end + +---@param items PickerItem[] +local function show(buf_id, items) + local icon_data = {} + + -- Show items + local items_to_show = {} + for i, item in ipairs(items) do + local icon, hl, _ = MiniIcons.get('file', item.text) + icon_data[i] = { icon = icon, hl = hl } + + items_to_show[i] = string.format('%s %s', icon, item.text) + end + vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, items_to_show) + + vim.api.nvim_buf_clear_namespace(buf_id, M.ns_id, 0, -1) + + local icon_extmark_opts = { hl_mode = 'combine', priority = 200 } + for i, item in ipairs(items) do + -- Highlight Icons + icon_extmark_opts.hl_group = icon_data[i].hl + icon_extmark_opts.end_row, icon_extmark_opts.end_col = i - 1, 1 + vim.api.nvim_buf_set_extmark(buf_id, M.ns_id, i - 1, 0, icon_extmark_opts) + end +end + +local function run() + -- Setup fff.nvim + local file_picker = require('fff.file_picker') + if not file_picker.is_initialized() then + local setup_success = file_picker.setup() + if not setup_success then + vim.notify('Could not setup fff.nvim', vim.log.levels.ERROR) + return + end + end + + -- Cache current file to deprioritize in fff.nvim + if not M.state.current_file_cache then + local current_buf = vim.api.nvim_get_current_buf() + if current_buf and vim.api.nvim_buf_is_valid(current_buf) then + local current_file = vim.api.nvim_buf_get_name(current_buf) + if current_file ~= '' and vim.fn.filereadable(current_file) == 1 then + local relative_path = vim.fs.relpath(vim.uv.cwd(), current_file) + M.state.current_file_cache = relative_path + else + M.state.current_file_cache = nil + end + end + end + + -- Start picker + MiniPick.start({ + source = { + name = 'FFFiles', + items = find, + match = function(_, _, query) + local items = find(table.concat(query)) + MiniPick.set_picker_items(items, { do_match = false }) + end, + show = show, + }, + }) + + M.state.current_file_cache = nil -- Reset cache +end + +function M.setup() MiniPick.registry.fffiles = run end + +function M.is_initialized() return MiniPick.registry.fffiles ~= nil end + +return M