From 612cb8618ed567f9d6763d2950a1efa11f204658 Mon Sep 17 00:00:00 2001 From: gustav-fff <286169375+gustav-fff@users.noreply.github.com> Date: Tue, 16 Jun 2026 14:02:07 -0700 Subject: [PATCH 1/2] feat(picker_ui): add opts.on_submit hook for custom selection action Allows callers to override the default :edit action when the user picks an item, enabling integrations like opening the directory in oil.nvim. Refs #605 --- lua/fff/main.lua | 4 +++- lua/fff/picker_ui/picker_ui.lua | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lua/fff/main.lua b/lua/fff/main.lua index f8d48945..1418a2d7 100644 --- a/lua/fff/main.lua +++ b/lua/fff/main.lua @@ -8,7 +8,9 @@ function M.setup(config) vim.g.fff = config end --- Find files in current directory. --- When opts.resume is true, resumes the last find_files picker (or opens a new one if none saved). ---- @param opts? table Optional configuration {renderer = custom_renderer, resume = boolean} +--- When opts.on_submit is set, it replaces the default `:edit` action on user selection. +--- Signature: `fun(item: table, ctx: { action: string, path: string, relative_path: string, location: table|nil, query: string, mode: string|nil })`. +--- @param opts? table Optional configuration {renderer = custom_renderer, resume = boolean, on_submit = function} function M.find_files(opts) local picker_ok, picker_ui = pcall(require, 'fff.picker_ui.picker_ui') if not picker_ok then diff --git a/lua/fff/picker_ui/picker_ui.lua b/lua/fff/picker_ui/picker_ui.lua index bf01dc26..dc4b861f 100644 --- a/lua/fff/picker_ui/picker_ui.lua +++ b/lua/fff/picker_ui/picker_ui.lua @@ -483,9 +483,36 @@ function M.select(action) vim.cmd('stopinsert') M.close() + local on_submit = config and config.on_submit + -- Defer file open past picker float teardown. Without this, foldexpr is not -- recomputed on the new window (folds appear missing) on some platforms. vim.schedule(function() + if type(on_submit) == 'function' then + local ok, err = pcall(on_submit, item, { + action = action, + path = abs_path, + relative_path = relative_path, + location = location, + query = query, + mode = mode, + }) + if not ok then vim.notify('FFF: on_submit error: ' .. tostring(err), vim.log.levels.ERROR) end + + if query and query ~= '' then + local cfg = config or conf.get() + if cfg.history and cfg.history.enabled then + local fff = require('fff.core').ensure_initialized() + if mode == 'grep' then + pcall(fff.track_grep_query, query) + else + pcall(fff.track_query_completion, query, item.relative_path) + end + end + end + return + end + if config and config.select and type(config.select.select_window) == 'function' then local ok, win = pcall(config.select.select_window, vim.api.nvim_get_current_buf(), action) if not ok then From 1b05da93e6a548455e1fd7dbdd863488acb22e47 Mon Sep 17 00:00:00 2001 From: gustav-fff <286169375+gustav-fff@users.noreply.github.com> Date: Tue, 16 Jun 2026 16:31:15 -0700 Subject: [PATCH 2/2] refactor(picker_ui): dedupe on_submit branch, share history tracking --- lua/fff/picker_ui/picker_ui.lua | 57 +++++++++++++-------------------- 1 file changed, 22 insertions(+), 35 deletions(-) diff --git a/lua/fff/picker_ui/picker_ui.lua b/lua/fff/picker_ui/picker_ui.lua index dc4b861f..c8d72831 100644 --- a/lua/fff/picker_ui/picker_ui.lua +++ b/lua/fff/picker_ui/picker_ui.lua @@ -498,50 +498,37 @@ function M.select(action) mode = mode, }) if not ok then vim.notify('FFF: on_submit error: ' .. tostring(err), vim.log.levels.ERROR) end - - if query and query ~= '' then - local cfg = config or conf.get() - if cfg.history and cfg.history.enabled then - local fff = require('fff.core').ensure_initialized() - if mode == 'grep' then - pcall(fff.track_grep_query, query) - else - pcall(fff.track_query_completion, query, item.relative_path) - end + else + if config and config.select and type(config.select.select_window) == 'function' then + local ok, win = pcall(config.select.select_window, vim.api.nvim_get_current_buf(), action) + if not ok then + vim.notify('FFF: select.select_window error: ' .. tostring(win), vim.log.levels.WARN) + elseif type(win) == 'number' and vim.api.nvim_win_is_valid(win) then + vim.api.nvim_set_current_win(win) end end - return - end - if config and config.select and type(config.select.select_window) == 'function' then - local ok, win = pcall(config.select.select_window, vim.api.nvim_get_current_buf(), action) - if not ok then - vim.notify('FFF: select.select_window error: ' .. tostring(win), vim.log.levels.WARN) - elseif type(win) == 'number' and vim.api.nvim_win_is_valid(win) then - vim.api.nvim_set_current_win(win) - end - end + if action == 'edit' then + -- Hard guard against E1513 ("Cannot switch buffer. 'winfixbuf' is enabled"): + -- if the (post-hook) current window is pinned, fall back to :split. + local opened_via_split = false + if window_has_winfixbuf(vim.api.nvim_get_current_win()) then + vim.cmd('split ' .. vim.fn.fnameescape(relative_path)) + opened_via_split = true + end - if action == 'edit' then - -- Hard guard against E1513 ("Cannot switch buffer. 'winfixbuf' is enabled"): - -- if the (post-hook) current window is pinned, fall back to :split. - local opened_via_split = false - if window_has_winfixbuf(vim.api.nvim_get_current_win()) then + if not opened_via_split then vim.cmd('edit ' .. vim.fn.fnameescape(relative_path)) end + elseif action == 'split' then vim.cmd('split ' .. vim.fn.fnameescape(relative_path)) - opened_via_split = true + elseif action == 'vsplit' then + vim.cmd('vsplit ' .. vim.fn.fnameescape(relative_path)) + elseif action == 'tab' then + vim.cmd('tabedit ' .. vim.fn.fnameescape(relative_path)) end - if not opened_via_split then vim.cmd('edit ' .. vim.fn.fnameescape(relative_path)) end - elseif action == 'split' then - vim.cmd('split ' .. vim.fn.fnameescape(relative_path)) - elseif action == 'vsplit' then - vim.cmd('vsplit ' .. vim.fn.fnameescape(relative_path)) - elseif action == 'tab' then - vim.cmd('tabedit ' .. vim.fn.fnameescape(relative_path)) + if location then location_utils.jump_to_location(location) end end - if location then location_utils.jump_to_location(location) end - if query and query ~= '' then local cfg = config or conf.get() if cfg.history and cfg.history.enabled then