feat(picker): pin picker as a split to left/bottom/top/right with ctrl+z+(hjkl)

This commit is contained in:
Folke Lemaitre 2025-02-05 07:13:12 +01:00
parent eac06ed1a5
commit 27cba535a6
No known key found for this signature in database
GPG key ID: 41F8B1FBACAE2040
4 changed files with 57 additions and 4 deletions

View file

@ -5,6 +5,9 @@ local M = {}
---@class snacks.picker.jump.Action: snacks.picker.Action
---@field cmd? snacks.picker.EditCmd
---@class snacks.picker.layout.Action: snacks.picker.Action
---@field layout? snacks.picker.layout.Config|string
---@enum (key) snacks.picker.EditCmd
local edit_cmd = {
edit = "buffer",
@ -155,6 +158,27 @@ M.edit_split = M.split
M.edit_vsplit = M.vsplit
M.edit_tab = M.tab
function M.layout(picker, _, action)
---@cast action snacks.picker.layout.Action
assert(action.layout, "Layout action requires a layout")
local opts = type(action.layout) == "table" and { layout = action.layout } or action.layout
---@cast opts snacks.picker.Config
local layout = Snacks.picker.config.layout(opts)
picker:set_layout(layout)
-- Adjust some options for split layouts
if (layout.layout.position or "float") ~= "float" then
picker.opts.auto_close = false
picker.opts.jump.close = false
picker:toggle_preview(false)
picker.list.win:focus()
end
end
M.layout_top = { action = "layout", layout = "top" }
M.layout_bottom = { action = "layout", layout = "bottom" }
M.layout_left = { action = "layout", layout = "left" }
M.layout_right = { action = "layout", layout = "right" }
function M.toggle_maximize(picker)
picker.layout:maximize()
end

View file

@ -219,6 +219,14 @@ local defaults = {
["<c-s>"] = { "edit_split", mode = { "i", "n" } },
["<c-u>"] = { "list_scroll_up", mode = { "i", "n" } },
["<c-v>"] = { "edit_vsplit", mode = { "i", "n" } },
["<c-z>h"] = { "layout_left", mode = { "i", "n" } },
["<c-z><c-h>"] = { "layout_left", mode = { "i", "n" } },
["<c-z>j"] = { "layout_bottom", mode = { "i", "n" } },
["<c-z><c-j>"] = { "layout_bottom", mode = { "i", "n" } },
["<c-z>k"] = { "layout_top", mode = { "i", "n" } },
["<c-z><c-k>"] = { "layout_top", mode = { "i", "n" } },
["<c-z>l"] = { "layout_right", mode = { "i", "n" } },
["<c-z><c-l>"] = { "layout_right", mode = { "i", "n" } },
["?"] = "toggle_help_input",
["G"] = "list_bottom",
["gg"] = "list_top",
@ -260,6 +268,14 @@ local defaults = {
["<c-s>"] = "edit_split",
["<c-u>"] = "list_scroll_up",
["<c-v>"] = "edit_vsplit",
["<c-z>h"] = { "layout_left", mode = { "i", "n" } },
["<c-z><c-h>"] = { "layout_left", mode = { "i", "n" } },
["<c-z>j"] = { "layout_bottom", mode = { "i", "n" } },
["<c-z><c-j>"] = { "layout_bottom", mode = { "i", "n" } },
["<c-z>k"] = { "layout_top", mode = { "i", "n" } },
["<c-z><c-k>"] = { "layout_top", mode = { "i", "n" } },
["<c-z>l"] = { "layout_right", mode = { "i", "n" } },
["<c-z><c-l>"] = { "layout_right", mode = { "i", "n" } },
["?"] = "toggle_help_list",
["G"] = "list_bottom",
["gg"] = "list_top",

View file

@ -209,12 +209,20 @@ function M.layout(opts)
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 {})
local layouts = opts.layouts or M.get().layouts or {}
local done = {} ---@type table<string, boolean>
local todo = { layout } ---@type snacks.picker.layout.Config[]
while true do
local preset = M.resolve(todo[1].preset or "custom", opts.source)
if not preset or done[preset] or not layouts[preset] then
break
end
done[preset] = true
table.insert(todo, 1, vim.deepcopy(layouts[preset]))
end
-- Merge and return the layout
return Snacks.config.merge(ret, layout)
return Snacks.config.merge(unpack(todo))
end
---@generic T

View file

@ -175,4 +175,9 @@ M.vscode = {
},
}
M.left = M.sidebar
M.right = { preset = "sidebar", layout = { position = "right" } }
M.top = { preset = "ivy", layout = { position = "top" } }
M.bottom = { preset = "ivy", layout = { position = "bottom" } }
return M