feat(picker): multi source picker. Combine multiple pickers (as opposed to just finders) in one picker

This commit is contained in:
Folke Lemaitre 2025-01-22 23:48:46 +01:00
parent 5e69fb83d5
commit 9434986ff1
No known key found for this signature in database
GPG key ID: 41F8B1FBACAE2040
3 changed files with 64 additions and 2 deletions

View file

@ -15,6 +15,7 @@ local M = {}
---@field buf? boolean|number only show items for the current or given buffer
---@field paths? table<string, boolean> only show items that include or exclude the given paths
---@field filter? fun(item:snacks.picker.finder.Item, filter:snacks.picker.Filter):boolean? custom filter function
---@field transform? fun(picker:snacks.Picker, filter:snacks.picker.Filter):boolean? filter transform. Return `true` to force refresh
--- This is only used when using `opts.preview = "preview"`.
--- It's a previewer that shows a preview based on the item data.
@ -59,6 +60,7 @@ local M = {}
---@field preview? snacks.win.Config|{} preview window config
---@class snacks.picker.Config
---@field multi? (string|snacks.picker.Config)[]
---@field source? string source name and config to use
---@field pattern? string|fun(picker:snacks.Picker):string pattern used to filter items by the matcher
---@field search? string|fun(picker:snacks.Picker):string search string used by finders

View file

@ -76,7 +76,58 @@ function M.get(opts)
end
-- Merge the configs
return Snacks.config.merge(unpack(todo))
opts = Snacks.config.merge(unpack(todo))
M.multi(opts)
return opts
end
---@param opts snacks.picker.Config
function M.multi(opts)
if not opts.multi then
return opts
end
local Finder = require("snacks.picker.core.finder")
local finders = {} ---@type snacks.picker.finder[]
local formats = {} ---@type snacks.picker.format[]
local previews = {} ---@type snacks.picker.preview[]
local confirms = {} ---@type snacks.picker.Action.spec[]
local sources = {} ---@type snacks.picker.Config[]
for _, source in ipairs(opts.multi) do
if type(source) == "string" then
source = { source = source }
end
---@cast source snacks.picker.Config
source = Snacks.config.merge({}, opts.sources[source.source], source) --[[@as snacks.picker.Config]]
source.actions = source.actions or {}
if source.confirm then
source.actions.confirm = source.confirm
end
local finder = M.finder(source.finder)
finders[#finders + 1] = function(fopts, filter, picker)
fopts = Snacks.config.merge({}, vim.deepcopy(source), fopts)
return finder(fopts, filter, picker)
end
confirms[#confirms + 1] = source.actions.confirm or "jump"
previews[#previews + 1] = M.preview(source)
formats[#formats + 1] = M.format(source)
sources[#sources + 1] = source
end
opts.finder = opts.finder or Finder.multi(finders)
opts.format = opts.format or function(item, picker)
return formats[item.fid](item, picker)
end
opts.preview = opts.preview or function(ctx)
return previews[ctx.item.fid](ctx)
end
opts.confirm = opts.confirm
or function(picker, item, action)
return confirms[item.fid](picker, item, action)
end
end
---@param opts snacks.picker.Config
function M.format(opts)
local ret = type(opts.format) == "string" and Snacks.picker.format[opts.format]
@ -86,6 +137,15 @@ function M.format(opts)
return ret
end
---@param opts snacks.picker.Config
function M.transform(opts)
local ret = type(opts.transform) == "string" and require("snacks.picker.transform")[opts.transform]
or opts.transform
or nil
---@cast ret snacks.picker.transform?
return ret
end
---@param opts snacks.picker.Config
function M.preview(opts)
local preview = opts.preview or Snacks.picker.preview.file

View file

@ -63,7 +63,7 @@ function M.pick(source, opts)
opts = opts or {}
opts.source = source or opts.source
-- Show pickers if no source, items or finder is provided
if not (opts.source or opts.items or opts.finder) then
if not (opts.source or opts.items or opts.finder or opts.multi) then
opts.source = "pickers"
return M.pick(opts)
end