From 264cab138039412a151b21fdc30d4928f50d79b4 Mon Sep 17 00:00:00 2001 From: Folke Lemaitre Date: Mon, 29 Sep 2025 09:01:19 +0200 Subject: [PATCH] feat(picker): added custom options to `vim.ui.select` that snacks can use for a better select --- lua/snacks/picker/format.lua | 17 +++++++++---- lua/snacks/picker/select.lua | 49 ++++++++++++++++++++++-------------- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/lua/snacks/picker/format.lua b/lua/snacks/picker/format.lua index 4edca05c..569405b1 100644 --- a/lua/snacks/picker/format.lua +++ b/lua/snacks/picker/format.lua @@ -312,18 +312,18 @@ function M.lsp_symbol(item, picker) return ret end ----@param kind? string ----@param count number +---@param opts snacks.picker.ui_select.Opts ---@return snacks.picker.format -function M.ui_select(kind, count) - return function(item) +function M.ui_select(opts) + return function(item, picker) + local count = picker:count() local ret = {} ---@type snacks.picker.Highlight[] local idx = tostring(item.idx) idx = (" "):rep(#tostring(count) - #idx) .. idx ret[#ret + 1] = { idx .. ".", "SnacksPickerIdx" } ret[#ret + 1] = { " " } - if kind == "codeaction" then + if opts.kind == "codeaction" then ---@type lsp.Command|lsp.CodeAction, lsp.HandlerContext local action, ctx = item.item.action, item.item.ctx local client = vim.lsp.get_client_by_id(ctx.client_id) @@ -332,6 +332,13 @@ function M.ui_select(kind, count) ret[#ret + 1] = { " " } ret[#ret + 1] = { ("[%s]"):format(client.name), "SnacksPickerSpecial" } end + elseif opts.kind == "snacks" and opts.format_item then + local t = opts.format_item(item.item, true) + if type(t) == "string" then + ret[#ret + 1] = { t } + elseif type(t) == "table" then + vim.list_extend(ret, t) + end else ret[#ret + 1] = { item.formatted } end diff --git a/lua/snacks/picker/select.lua b/lua/snacks/picker/select.lua index 9e885c10..65a9837a 100644 --- a/lua/snacks/picker/select.lua +++ b/lua/snacks/picker/select.lua @@ -2,37 +2,44 @@ local M = {} ---@alias snacks.picker.ui_select fun(items: any[], opts?: {prompt?: string, format_item?: (fun(item: any): string), kind?: string}, on_choice: fun(item?: any, idx?: number)) +---@class snacks.picker.ui_select.Opts: vim.ui.select.Opts +---@field format_item? fun(item: any, is_snacks: boolean):(string|snacks.picker.Highlight[]) +---@field picker? snacks.picker.Config + ---@generic T ---@param items T[] Arbitrary items ----@param opts? {prompt?: string, format_item?: (fun(item: T): string), kind?: string} +---@param opts? snacks.picker.ui_select.Opts ---@param on_choice fun(item?: T, idx?: number) function M.select(items, opts, on_choice) assert(type(on_choice) == "function", "on_choice must be a function") opts = opts or {} - ---@type snacks.picker.finder.Item[] - local finder_items = {} - for idx, item in ipairs(items) do - local text = (opts.format_item or tostring)(item) - table.insert(finder_items, { - formatted = text, - text = idx .. " " .. text, - item = item, - idx = idx, - }) - end - local title = opts.prompt or "Select" title = title:gsub("^%s*", ""):gsub("[%s:]*$", "") local completed = false + opts.kind = opts.kind or (opts.picker and "snacks") or opts.kind - ---@type snacks.picker.finder.Item[] - return Snacks.picker.pick({ + ---@type snacks.picker.Config + local picker_opts = { source = "select", - items = finder_items, - format = Snacks.picker.format.ui_select(opts.kind, #items), + finder = function() + ---@type snacks.picker.finder.Item[] + local ret = {} + for idx, item in ipairs(items) do + local text = (opts.format_item or tostring)(item) + ---@type snacks.picker.finder.Item + local it = type(item) == "table" and setmetatable({}, { __index = item }) or {} + it.formatted = text + it.text = idx .. " " .. text + it.item = item + it.idx = idx + ret[#ret + 1] = it + end + return ret + end, + format = Snacks.picker.format.ui_select(opts), title = title, - layout = { + layout = opts.picker and opts.picker.layout or { preview = false, layout = { height = math.floor(math.min(vim.o.lines * 0.8 - 10, #items + 2) + 0.5), @@ -57,7 +64,11 @@ function M.select(items, opts, on_choice) completed = true vim.schedule(on_choice) end, - }) + } + if opts.picker then + picker_opts = Snacks.config.merge({}, vim.deepcopy(picker_opts), opts.picker) + end + return Snacks.picker.pick(picker_opts) end return M