feat(picker): added git_stash picker. Closes #762

This commit is contained in:
Folke Lemaitre 2025-01-28 16:05:30 +01:00
parent 1a5a0871c8
commit bb3db117a4
No known key found for this signature in database
GPG key ID: 41F8B1FBACAE2040
5 changed files with 87 additions and 4 deletions

View file

@ -155,6 +155,16 @@ function M.git_stage(picker)
end
end
function M.git_stash_apply(_, item)
if not item then
return
end
local cmd = { "git", "stash", "apply", item.stash }
Snacks.picker.util.cmd(cmd, function()
Snacks.notify("Stash applied: `" .. item.stash .. "`", { title = "Snacks Picker" })
end, { cwd = item.cwd })
end
function M.git_checkout(picker, item)
picker:close()
if item then

View file

@ -190,6 +190,13 @@ M.git_log_line = {
confirm = "git_checkout",
}
M.git_stash = {
finder = "git_stash",
format = "git_stash",
preview = "git_stash",
confirm = "git_stash_apply",
}
M.git_status = {
finder = "git_status",
format = "git_status",

View file

@ -101,7 +101,8 @@ function M.git_log(item, picker)
local a = Snacks.picker.util.align
local ret = {} ---@type snacks.picker.Highlight[]
ret[#ret + 1] = { picker.opts.icons.git.commit, "SnacksPickerGitCommit" }
ret[#ret + 1] = { item.commit, "SnacksPickerGitCommit" }
local c = item.commit or item.branch or "HEAD"
ret[#ret + 1] = { a(c, 7, { truncate = true }), "SnacksPickerGitCommit" }
ret[#ret + 1] = { " " }
if item.date then
@ -158,6 +159,20 @@ function M.git_branch(item, picker)
return ret
end
function M.git_stash(item, picker)
local a = Snacks.picker.util.align
local ret = {} ---@type snacks.picker.Highlight[]
ret[#ret + 1] = { a(item.stash, 10), "SnacksPickerIdx" }
ret[#ret + 1] = { " " }
ret[#ret + 1] = { a(item.branch, 10, { truncate = true }), "SnacksPickerGitBranch" }
ret[#ret + 1] = { " " }
local offset = Snacks.picker.highlight.offset(ret)
local log = M.git_log(item, picker)
Snacks.picker.highlight.fix_offset(log, offset)
vim.list_extend(ret, log)
return ret
end
function M.indent(item, picker)
local ret = {} ---@type snacks.picker.Highlight[]
local indents = picker.opts.icons.indent

View file

@ -307,6 +307,24 @@ function M.git_diff(ctx)
M.cmd(cmd, ctx, { ft = not native and "diff" or nil })
end
---@param ctx snacks.picker.preview.ctx
function M.git_stash(ctx)
local native = ctx.picker.opts.previewers.git.native
local cmd = {
"git",
"-c",
"delta." .. vim.o.background .. "=true",
"stash",
"show",
"--patch",
ctx.item.stash,
}
if not native then
table.insert(cmd, 2, "--no-pager")
end
M.cmd(cmd, ctx, { ft = not native and "diff" or nil })
end
---@param ctx snacks.picker.preview.ctx
function M.git_status(ctx)
local ss = ctx.item.status

View file

@ -2,6 +2,8 @@ local M = {}
local uv = vim.uv or vim.loop
local commit_pat = ("[a-z0-9]"):rep(7)
---@class snacks.picker
---@field git_files fun(opts?: snacks.picker.git.files.Config): snacks.Picker
---@field git_log fun(opts?: snacks.picker.git.log.Config): snacks.Picker
@ -173,13 +175,12 @@ function M.branches(opts, ctx)
local cwd = vim.fs.normalize(opts and opts.cwd or uv.cwd() or ".") or nil
cwd = Snacks.git.get_root(cwd)
local pattern_hash = "[a-zA-Z0-9]+"
local patterns = {
-- stylua: ignore start
--- e.g. "* (HEAD detached at f65a2c8) f65a2c8 chore(build): auto-generate docs"
"^(.)%s(%b())%s+(" .. pattern_hash .. ")%s*(.*)$",
"^(.)%s(%b())%s+(" .. commit_pat .. ")%s*(.*)$",
--- e.g. " main d2b2b7b [origin/main: behind 276] chore(build): auto-generate docs"
"^(.)%s(%S+)%s+(".. pattern_hash .. ")%s*(.*)$",
"^(.)%s(%S+)%s+(".. commit_pat .. ")%s*(.*)$",
-- stylua: ignore end
} ---@type string[]
@ -211,4 +212,36 @@ function M.branches(opts, ctx)
}, ctx)
end
---@param opts snacks.picker.Config
---@type snacks.picker.finder
function M.stash(opts, ctx)
local args = { "--no-pager", "stash", "list" }
local cwd = vim.fs.normalize(opts and opts.cwd or uv.cwd() or ".") or nil
cwd = Snacks.git.get_root(cwd)
return require("snacks.picker.source.proc").proc({
opts,
{
cwd = cwd,
cmd = "git",
args = args,
---@param item snacks.picker.finder.Item
transform = function(item)
item.cwd = cwd
local stash, branch, msg = item.text:gsub(": On main:", ": WIP on main:"):match("^(%S+): WIP on (%S+): (.*)$")
if stash then
local commit, m = msg:match("^(" .. commit_pat .. ") (.*)")
item.stash = stash
item.branch = branch
item.commit = commit
item.msg = m or msg
return
end
Snacks.notify.warn("failed to parse stash:\n```git\n" .. item.text .. "\n```")
return false -- skip items we could not parse
end,
},
}, ctx)
end
return M