mirror of
https://github.com/folke/snacks.nvim
synced 2025-07-07 21:25:11 +00:00
fix(picker.config): better config merging and tests
This commit is contained in:
parent
a194bbc374
commit
9986b47707
5 changed files with 99 additions and 35 deletions
|
@ -31,7 +31,11 @@ M.config = setmetatable({}, {
|
|||
end,
|
||||
})
|
||||
|
||||
local function can_merge(v)
|
||||
local islist = vim.islist or vim.tbl_islist
|
||||
local is_dict_like = function(v) -- has string and number keys
|
||||
return type(v) == "table" and (vim.tbl_isempty(v) or not islist(v))
|
||||
end
|
||||
local is_dict = function(v) -- has only string keys
|
||||
return type(v) == "table" and (vim.tbl_isempty(v) or not v[1])
|
||||
end
|
||||
|
||||
|
@ -44,7 +48,7 @@ function M.config.merge(...)
|
|||
local ret = select(1, ...)
|
||||
for i = 2, select("#", ...) do
|
||||
local value = select(i, ...)
|
||||
if can_merge(ret) and can_merge(value) then
|
||||
if is_dict_like(ret) and is_dict(value) then
|
||||
for k, v in pairs(value) do
|
||||
ret[k] = M.config.merge(ret[k], v)
|
||||
end
|
||||
|
|
|
@ -75,7 +75,7 @@ local M = {}
|
|||
--- Preset options
|
||||
---@field previewers? snacks.picker.previewers.Config|{}
|
||||
---@field formatters? snacks.picker.formatters.Config|{}
|
||||
---@field sources? snacks.picker.sources.Config|{}
|
||||
---@field sources? snacks.picker.sources.Config|{}|table<string, snacks.picker.Config|{}>
|
||||
---@field layouts? table<string, snacks.picker.layout.Config>
|
||||
--- Actions
|
||||
---@field actions? table<string, snacks.picker.Action.spec> actions used by keymaps
|
||||
|
|
|
@ -11,45 +11,57 @@ M.alias = {
|
|||
}
|
||||
|
||||
local key_cache = {} ---@type table<string, string>
|
||||
local defaults ---@type snacks.picker.Config?
|
||||
|
||||
--- Fixes keys before merging configs for correctly resolving keymaps.
|
||||
--- For example: <c-s> -> <C-S>
|
||||
---@param opts? snacks.picker.Config
|
||||
function M.fix_keys(opts)
|
||||
if not (opts and opts.win) then
|
||||
return
|
||||
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
|
||||
key_cache[key] = key_cache[key] or vim.fn.keytrans(Snacks.util.keycode(key))
|
||||
if key ~= key_cache[key] then
|
||||
win.keys[key_cache[key]], win.keys[key] = win.keys[key], nil
|
||||
local norm = key_cache[key]
|
||||
if key ~= norm then
|
||||
key_cache[norm] = norm
|
||||
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 = opts or {}
|
||||
opts = M.fix_keys(opts)
|
||||
|
||||
local sources = require("snacks.picker.config.sources")
|
||||
local defaults = require("snacks.picker.config.defaults").defaults
|
||||
defaults.sources = sources
|
||||
local user = Snacks.config.picker or {}
|
||||
M.fix_keys(user)
|
||||
M.fix_keys(defaults)
|
||||
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 {}
|
||||
M.fix_keys(source)
|
||||
---@type snacks.picker.Config[]
|
||||
local todo = {
|
||||
vim.deepcopy(defaults),
|
||||
|
@ -58,6 +70,7 @@ function M.get(opts)
|
|||
opts,
|
||||
}
|
||||
|
||||
-- Merge the confirm action into the actions table
|
||||
for _, t in ipairs(todo) do
|
||||
if t.confirm then
|
||||
t.actions = t.actions or {}
|
||||
|
@ -65,13 +78,8 @@ function M.get(opts)
|
|||
end
|
||||
end
|
||||
|
||||
local ret = Snacks.config.merge(unpack(todo))
|
||||
ret.layouts = ret.layouts or {}
|
||||
local layouts = require("snacks.picker.config.layouts")
|
||||
for k, v in pairs(layouts or {}) do
|
||||
ret.layouts[k] = ret.layouts[k] or v
|
||||
end
|
||||
return ret
|
||||
-- Merge the configs
|
||||
return Snacks.config.merge(unpack(todo))
|
||||
end
|
||||
|
||||
--- Resolve the layout configuration
|
||||
|
@ -80,19 +88,22 @@ function M.layout(opts)
|
|||
if type(opts) == "string" then
|
||||
opts = M.get({ layout = { preset = opts } })
|
||||
end
|
||||
local layouts = require("snacks.picker.config.layouts")
|
||||
|
||||
-- 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)
|
||||
local ret = vim.deepcopy(opts.layouts and opts.layouts[preset] or layouts[preset] or {})
|
||||
-- NOTE: use deep extend instead of merge to allow merging list-like tables
|
||||
ret = vim.tbl_deep_extend("force", ret, layout)
|
||||
ret.preset = nil
|
||||
return ret
|
||||
---@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
|
||||
|
|
|
@ -24,11 +24,6 @@ function M.select(items, opts, on_choice)
|
|||
|
||||
local title = opts.prompt or "Select"
|
||||
title = title:gsub("^%s*", ""):gsub("[%s:]*$", "")
|
||||
local layout = Snacks.picker.config.layout("select")
|
||||
layout.preview = false
|
||||
layout.layout.height = math.floor(math.min(vim.o.lines * 0.8 - 10, #items + 2) + 0.5) + 10
|
||||
layout.layout.title = " " .. title .. " "
|
||||
layout.layout.title_pos = "center"
|
||||
|
||||
---@type snacks.picker.finder.Item[]
|
||||
return Snacks.picker.pick({
|
||||
|
@ -36,6 +31,15 @@ function M.select(items, opts, on_choice)
|
|||
items = finder_items,
|
||||
main = { current = true },
|
||||
format = Snacks.picker.format.ui_select(opts.kind, #items),
|
||||
layout = {
|
||||
preset = "select",
|
||||
preview = false,
|
||||
layout = {
|
||||
height = math.floor(math.min(vim.o.lines * 0.8 - 10, #items + 2) + 0.5) + 10,
|
||||
title = " " .. title .. " ",
|
||||
title_pos = "center",
|
||||
},
|
||||
},
|
||||
actions = {
|
||||
confirm = function(picker, item)
|
||||
picker:close()
|
||||
|
@ -44,7 +48,6 @@ function M.select(items, opts, on_choice)
|
|||
end)
|
||||
end,
|
||||
},
|
||||
layout = layout,
|
||||
})
|
||||
end
|
||||
|
||||
|
|
46
tests/config_spec.lua
Normal file
46
tests/config_spec.lua
Normal file
|
@ -0,0 +1,46 @@
|
|||
---@module 'luassert'
|
||||
|
||||
local function d(v)
|
||||
return vim.inspect(v):gsub("%s+", " ")
|
||||
end
|
||||
|
||||
describe("config", function()
|
||||
local tests = {
|
||||
{
|
||||
{ 1, 2 },
|
||||
{ 3, 4 },
|
||||
{ 3, 4 },
|
||||
},
|
||||
{
|
||||
{ 1, 2 },
|
||||
nil,
|
||||
{ 1, 2 },
|
||||
},
|
||||
{
|
||||
{ a = 1, b = 2 },
|
||||
{ c = 3 },
|
||||
{ a = 1, b = 2, c = 3 },
|
||||
},
|
||||
{
|
||||
{ 1, 2, a = 1 },
|
||||
{ 3, 4, b = 2 },
|
||||
{ 3, 4, b = 2 },
|
||||
},
|
||||
{
|
||||
{ 3, 4, b = 2 },
|
||||
{ 1, 2 },
|
||||
{ 1, 2 },
|
||||
},
|
||||
{
|
||||
{ 1, 2, a = 1 },
|
||||
{ b = 2 },
|
||||
{ 1, 2, b = 2, a = 1 },
|
||||
},
|
||||
}
|
||||
for _, t in ipairs(tests) do
|
||||
it("merges correctly " .. d(t), function()
|
||||
local ret = Snacks.config.merge(t[1], t[2])
|
||||
assert.are.same(ret, t[3])
|
||||
end)
|
||||
end
|
||||
end)
|
Loading…
Add table
Add a link
Reference in a new issue