---@class snacks.picker.config local M = {} --- Source aliases M.alias = { live_grep = "grep", find_files = "files", git_commits = "git_log", git_bcommits = "git_log_file", oldfiles = "recent", } local defaults ---@type snacks.picker.Config? --- Fixes keys before merging configs for correctly resolving keymaps. --- For example: -> ---@param opts? snacks.picker.Config function M.fix_keys(opts) opts = opts or {} -- fix keys in sources for _, source in pairs(opts.sources or {}) do M.fix_keys(source) end if not opts.win then return opts end -- fix keys in wins for _, win in pairs(opts.win) do ---@cast win snacks.win.Config if win.keys then local keys = vim.tbl_keys(win.keys) ---@type string[] for _, key in ipairs(keys) do local norm = Snacks.util.normkey(key) if key ~= norm then win.keys[norm], win.keys[key] = win.keys[key], nil end end end end return opts end ---@param opts? snacks.picker.Config function M.get(opts) M.setup() opts = M.fix_keys(opts) -- Setup defaults if not defaults then defaults = require("snacks.picker.config.defaults").defaults defaults.sources = require("snacks.picker.config.sources") defaults.layouts = require("snacks.picker.config.layouts") M.fix_keys(defaults) end local user = M.fix_keys(Snacks.config.picker or {}) opts.source = M.alias[opts.source] or opts.source -- Prepare config local global = Snacks.config.get("picker", defaults, opts) -- defaults + global user config local source = opts.source and global.sources[opts.source] or {} ---@type snacks.picker.Config[] local todo = { vim.deepcopy(defaults), vim.deepcopy(user), vim.deepcopy(source), opts, } -- Merge the confirm action into the actions table for _, t in ipairs(todo) do if t.confirm then t.actions = t.actions or {} t.actions.confirm = t.confirm end end -- Merge the configs return Snacks.config.merge(unpack(todo)) ---@param opts snacks.picker.Config function M.format(opts) local ret = type(opts.format) == "string" and Snacks.picker.format[opts.format] or opts.format or Snacks.picker.format.file ---@cast ret snacks.picker.format return ret end ---@param opts snacks.picker.Config function M.preview(opts) local preview = opts.preview or Snacks.picker.preview.file preview = type(preview) == "string" and Snacks.picker.preview[preview] or preview ---@cast preview snacks.picker.preview return preview end --- Resolve the layout configuration ---@param opts snacks.picker.Config|string function M.layout(opts) if type(opts) == "string" then opts = M.get({ layout = { preset = opts } }) end -- Resolve the layout configuration local layout = M.resolve(opts.layout or {}, opts.source) layout = type(layout) == "string" and { preset = layout } or layout ---@cast layout snacks.picker.layout.Config if layout.layout and layout.layout[1] then return layout end -- Resolve the preset local preset = M.resolve(layout.preset or "custom", opts.source) ---@type snacks.picker.layout.Config local ret = vim.deepcopy(opts.layouts and opts.layouts[preset] or {}) -- Merge and return the layout return Snacks.config.merge(ret, layout) end ---@generic T ---@generic A ---@param v (fun(...:A):T)|unknown ---@param ... A ---@return T function M.resolve(v, ...) return type(v) == "function" and v(...) or v end --- Get the finder ---@param finder string|snacks.picker.finder|snacks.picker.finder.multi ---@return snacks.picker.finder function M.finder(finder) local nop = function() Snacks.notify.error("Finder not found:\n```lua\n" .. vim.inspect(finder) .. "\n```", { title = "Snacks Picker" }) end if not finder or type(finder) == "function" then return finder end if type(finder) == "table" then ---@cast finder snacks.picker.finder.multi ---@type snacks.picker.finder[] local finders = vim.tbl_map(function(f) return M.finder(f) end, finder) return require("snacks.picker.core.finder").multi(finders) end ---@cast finder string local mod, fn = finder:match("^(.-)_(.+)$") if not (mod and fn) then mod, fn = finder, finder end local ok, ret = pcall(function() return require("snacks.picker.source." .. mod)[fn] end) return ok and ret or nop end local did_setup = false function M.setup() if did_setup then return end did_setup = true require("snacks.picker.config.highlights") for source in pairs(Snacks.picker.config.get().sources) do M.wrap(source) end --- Automatically wrap new sources added after setup setmetatable(require("snacks.picker.config.sources"), { __newindex = function(t, k, v) rawset(t, k, v) M.wrap(k) end, }) end ---@param source string ---@param opts? {check?: boolean} function M.wrap(source, opts) if opts and opts.check then local config = M.get() if not config.sources[source] then return end end ---@type fun(opts: snacks.picker.Config): snacks.Picker local ret = function(_opts) return Snacks.picker.pick(source, _opts) end ---@diagnostic disable-next-line: no-unknown Snacks.picker[source] = ret return ret end return M