Skip to content
Open
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
57 changes: 57 additions & 0 deletions lua/fff/conf.lua
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ local M = {}
--- @field modes string[]
--- @field trim_whitespace boolean

--- @alias FffSelectAction 'edit' | 'split' | 'vsplit' | 'tabedit'

--- @class FffSelectConfig
--- @field pre_select_hook fun(current_buf: integer, action: FffSelectAction)

--- @class FffConfig
--- @field base_path string
--- @field prompt string
Expand All @@ -69,6 +74,7 @@ local M = {}
--- @field hl table<string, string>
--- @field frecency FffFrecencyConfig
--- @field history FffHistoryConfig
--- @field select FffSelectConfig
--- @field git table
--- @field debug table
--- @field logging table
Expand Down Expand Up @@ -187,6 +193,42 @@ local function fallback_hl(name)
return resolved_hl or name[#name]
end

--- Find the first visible window with a normal file buffer
--- @return number|nil Window ID of the first suitable window, or nil if none found
local function find_suitable_window()
local current_tabpage = vim.api.nvim_get_current_tabpage()
local windows = vim.api.nvim_tabpage_list_wins(current_tabpage)

for _, win in ipairs(windows) do
if vim.api.nvim_win_is_valid(win) then
local buf = vim.api.nvim_win_get_buf(win)
if vim.api.nvim_buf_is_valid(buf) then
local buftype = vim.api.nvim_get_option_value('buftype', { buf = buf })
local modifiable = vim.api.nvim_get_option_value('modifiable', { buf = buf })
local filetype = vim.api.nvim_get_option_value('filetype', { buf = buf })

local is_picker_window = (
win == M.state.input_win
or win == M.state.list_win
or win == M.state.preview_win
or win == M.state.file_info_win
)

if
(buftype == '' or buftype == 'acwrite')
and modifiable
and not is_picker_window
and filetype ~= 'undotree'
then
return win
end
end
end
end

return nil
end

local function init()
local config = vim.g.fff or {}
local default_config = {
Expand Down Expand Up @@ -309,6 +351,21 @@ local function init()
min_combo_count = 3, -- Minimum selections before combo boost applies (3 = boost starts on 3rd selection)
combo_boost_score_multiplier = 100, -- Score multiplier for combo matches (files repeatedly opened with same query)
},
select = {
--- @param current_buf integer
--- @param action FffSelectAction
pre_select_hook = function(current_buf, action)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this is actually a hook

Copy link
Copy Markdown
Contributor Author

@mrcjkb mrcjkb Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we call it pre_select_callback or on_pre_select?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean it's unclear that this hook should actually change the window, maybe we should just override the whole open_file callback that is doing all the window picking and :e call

this would be more flixble probably

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or the other option is to make this callback return the window_id and we then opening the file in this window

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I like the idea of returning a window ID (which I guess would make it a select_window_hook).

if action ~= 'edit' then return end
local current_buftype = vim.api.nvim_get_option_value('buftype', { buf = current_buf })
local current_buf_modifiable = vim.api.nvim_get_option_value('modifiable', { buf = current_buf })

-- If current active buffer is not a normal buffer we find a suitable window with a tab otherwise opening a new split
if current_buftype ~= '' or not current_buf_modifiable then
local suitable_win = find_suitable_window()
if suitable_win then vim.api.nvim_set_current_win(suitable_win) end
end
end,
},
-- Git integration
git = {
status_text_color = false, -- Apply git status colors to filename text (default: false, only sign column)
Expand Down
49 changes: 3 additions & 46 deletions lua/fff/picker_ui.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2117,42 +2117,6 @@ function M.recall_query_from_history()
end)
end

--- Find the first visible window with a normal file buffer
--- @return number|nil Window ID of the first suitable window, or nil if none found
local function find_suitable_window()
local current_tabpage = vim.api.nvim_get_current_tabpage()
local windows = vim.api.nvim_tabpage_list_wins(current_tabpage)

for _, win in ipairs(windows) do
if vim.api.nvim_win_is_valid(win) then
local buf = vim.api.nvim_win_get_buf(win)
if vim.api.nvim_buf_is_valid(buf) then
local buftype = vim.api.nvim_get_option_value('buftype', { buf = buf })
local modifiable = vim.api.nvim_get_option_value('modifiable', { buf = buf })
local filetype = vim.api.nvim_get_option_value('filetype', { buf = buf })

local is_picker_window = (
win == M.state.input_win
or win == M.state.list_win
or win == M.state.preview_win
or win == M.state.file_info_win
)

if
(buftype == '' or buftype == 'acwrite')
and modifiable
and not is_picker_window
and filetype ~= 'undotree'
then
return win
end
end
end
end

return nil
end

--- Build a unique key for a grep match occurrence.
--- Format: "path:line:col" — uniquely identifies one match entry.
---@param item table Grep match item with path, line_number, col
Expand Down Expand Up @@ -2347,17 +2311,10 @@ function M.select(action)
vim.cmd('stopinsert')
M.close()

if action == 'edit' then
local current_buf = vim.api.nvim_get_current_buf()
local current_buftype = vim.api.nvim_get_option_value('buftype', { buf = current_buf })
local current_buf_modifiable = vim.api.nvim_get_option_value('modifiable', { buf = current_buf })

-- If current active buffer is not a normal buffer we find a suitable window with a tab otherwise opening a new split
if current_buftype ~= '' or not current_buf_modifiable then
local suitable_win = find_suitable_window()
if suitable_win then vim.api.nvim_set_current_win(suitable_win) end
end
local current_buf = vim.api.nvim_get_current_buf()
M.state.config.pre_select_hook(current_buf, action)

if action == 'edit' then
vim.cmd('edit ' .. vim.fn.fnameescape(relative_path))
elseif action == 'split' then
vim.cmd('split ' .. vim.fn.fnameescape(relative_path))
Expand Down
Loading