mirror of
https://github.com/folke/snacks.nvim
synced 2025-12-23 08:47:57 +00:00
feat(picker.git_diff): git_diff now also shows staged hunks and added stage/unstage/restore actions for hunks. Closes #2382
This commit is contained in:
parent
aa8a318779
commit
1fb3f4de49
5 changed files with 77 additions and 11 deletions
|
|
@ -344,15 +344,21 @@ function M.git_stage(picker)
|
||||||
local items = picker:selected({ fallback = true })
|
local items = picker:selected({ fallback = true })
|
||||||
local done = 0
|
local done = 0
|
||||||
for _, item in ipairs(items) do
|
for _, item in ipairs(items) do
|
||||||
|
local opts = { cwd = item.cwd } ---@type snacks.picker.util.cmd.Opts
|
||||||
|
|
||||||
local cmd = item.status:sub(2) == " " and { "git", "restore", "--staged", item.file } or { "git", "add", item.file }
|
local cmd = item.status:sub(2) == " " and { "git", "restore", "--staged", item.file } or { "git", "add", item.file }
|
||||||
Snacks.picker.util.cmd(cmd, function(data, code)
|
if item.diff then
|
||||||
|
opts.input = item.diff
|
||||||
|
cmd = { "git", "apply", "--cached", item.staged and "--reverse" or nil }
|
||||||
|
end
|
||||||
|
Snacks.picker.util.cmd(cmd, function()
|
||||||
done = done + 1
|
done = done + 1
|
||||||
if done == #items then
|
if done == #items then
|
||||||
picker.list:set_selected()
|
picker.list:set_selected()
|
||||||
picker.list:set_target()
|
picker.list:set_target()
|
||||||
picker:find()
|
picker:find()
|
||||||
end
|
end
|
||||||
end, { cwd = item.cwd })
|
end, opts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -370,14 +376,22 @@ function M.git_restore(picker)
|
||||||
local msg = #items == 1 and ("Discard changes to `%s`?"):format(files[1])
|
local msg = #items == 1 and ("Discard changes to `%s`?"):format(files[1])
|
||||||
or ("Discard changes to %d files?"):format(#items)
|
or ("Discard changes to %d files?"):format(#items)
|
||||||
|
|
||||||
Snacks.picker.select({ "No", "Yes" }, { prompt = msg }, function(_, idx)
|
Snacks.picker.util.confirm(msg, function()
|
||||||
if not idx and idx == 2 then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local done = 0
|
local done = 0
|
||||||
for _, item in ipairs(items) do
|
for _, item in ipairs(items) do
|
||||||
local cmd = { "git", "restore", item.file }
|
local cmd = { "git", "restore", item.file }
|
||||||
Snacks.picker.util.cmd(cmd, function(data, code)
|
local opts = { cwd = item.cwd }
|
||||||
|
|
||||||
|
if item.diff then
|
||||||
|
opts.input = item.diff
|
||||||
|
if item.staged then
|
||||||
|
cmd = { "git", "apply", "--reverse", "--cached" }
|
||||||
|
else
|
||||||
|
cmd = { "git", "apply", "--reverse" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Snacks.picker.util.cmd(cmd, function()
|
||||||
done = done + 1
|
done = done + 1
|
||||||
if done == #items then
|
if done == #items then
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
|
|
@ -385,9 +399,10 @@ function M.git_restore(picker)
|
||||||
picker.list:set_target()
|
picker.list:set_target()
|
||||||
picker:find()
|
picker:find()
|
||||||
vim.cmd.startinsert()
|
vim.cmd.startinsert()
|
||||||
|
vim.cmd.checktime()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end, { cwd = item.cwd })
|
end, opts)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -340,8 +340,17 @@ M.git_status = {
|
||||||
M.git_diff = {
|
M.git_diff = {
|
||||||
group = false,
|
group = false,
|
||||||
finder = "git_diff",
|
finder = "git_diff",
|
||||||
format = "file",
|
format = "git_status",
|
||||||
preview = "diff",
|
preview = "diff",
|
||||||
|
matcher = { sort_empty = true },
|
||||||
|
win = {
|
||||||
|
input = {
|
||||||
|
keys = {
|
||||||
|
["<Tab>"] = { "git_stage", mode = { "n", "i" } },
|
||||||
|
["<c-r>"] = { "git_restore", mode = { "n", "i" } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class snacks.picker.grep.Config: snacks.picker.proc.Config
|
---@class snacks.picker.grep.Config: snacks.picker.proc.Config
|
||||||
|
|
|
||||||
|
|
@ -573,6 +573,7 @@ function M.git_status(item, picker)
|
||||||
["?"] = "SnacksPickerGitStatusUntracked",
|
["?"] = "SnacksPickerGitStatusUntracked",
|
||||||
}
|
}
|
||||||
local hl = hls[s] or "SnacksPickerGitStatus"
|
local hl = hls[s] or "SnacksPickerGitStatus"
|
||||||
|
hl = item.status:sub(1, 1) == "M" and "SnacksPickerGitStatusStaged" or hl
|
||||||
ret[#ret + 1] = { a(item.status, 2, { align = "right" }), hl }
|
ret[#ret + 1] = { a(item.status, 2, { align = "right" }), hl }
|
||||||
ret[#ret + 1] = { " " }
|
ret[#ret + 1] = { " " }
|
||||||
if item.rename then
|
if item.rename then
|
||||||
|
|
|
||||||
|
|
@ -263,7 +263,10 @@ function M.diff(opts, ctx)
|
||||||
if opts.staged then
|
if opts.staged then
|
||||||
table.insert(args, "--cached")
|
table.insert(args, "--cached")
|
||||||
end
|
end
|
||||||
return require("snacks.picker.source.diff").diff(
|
|
||||||
|
local Diff = require("snacks.picker.source.diff")
|
||||||
|
local finders = {} ---@type snacks.picker.finder.result[]
|
||||||
|
finders[#finders + 1] = Diff.diff(
|
||||||
ctx:opts({
|
ctx:opts({
|
||||||
cmd = "git",
|
cmd = "git",
|
||||||
args = args,
|
args = args,
|
||||||
|
|
@ -271,6 +274,39 @@ function M.diff(opts, ctx)
|
||||||
}),
|
}),
|
||||||
ctx
|
ctx
|
||||||
)
|
)
|
||||||
|
if opts.staged == nil and opts.base == nil then
|
||||||
|
finders[#finders + 1] = Diff.diff(
|
||||||
|
ctx:opts({
|
||||||
|
cmd = "git",
|
||||||
|
args = vim.list_extend(vim.deepcopy(args), { "--cached" }),
|
||||||
|
cwd = ctx:git_root(),
|
||||||
|
}),
|
||||||
|
ctx
|
||||||
|
)
|
||||||
|
end
|
||||||
|
return function(cb)
|
||||||
|
local items = {} ---@type snacks.picker.finder.Item[]
|
||||||
|
for f, finder in ipairs(finders) do
|
||||||
|
finder(function(item)
|
||||||
|
item.staged = opts.staged or f == 2
|
||||||
|
if item.staged then
|
||||||
|
item.status = "M "
|
||||||
|
else
|
||||||
|
item.status = " M"
|
||||||
|
end
|
||||||
|
items[#items + 1] = item
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
table.sort(items, function(a, b)
|
||||||
|
if a.file ~= b.file then
|
||||||
|
return a.file < b.file
|
||||||
|
end
|
||||||
|
return a.pos[1] < b.pos[1]
|
||||||
|
end)
|
||||||
|
for _, item in ipairs(items) do
|
||||||
|
cb(item)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param opts snacks.picker.git.branches.Config
|
---@param opts snacks.picker.git.branches.Config
|
||||||
|
|
|
||||||
|
|
@ -83,10 +83,12 @@ function M.confirm(prompt, fn)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@alias snacks.picker.util.cmd.Opts {env?: table<string, string>, cwd?: string, input?: string}
|
||||||
---@param cmd string|string[]
|
---@param cmd string|string[]
|
||||||
---@param cb fun(output: string[], code: number)
|
---@param cb fun(output: string[], code: number)
|
||||||
---@param opts? {env?: table<string, string>, cwd?: string}
|
---@param opts? snacks.picker.util.cmd.Opts
|
||||||
function M.cmd(cmd, cb, opts)
|
function M.cmd(cmd, cb, opts)
|
||||||
|
opts = opts or {}
|
||||||
local output = {} ---@type string[]
|
local output = {} ---@type string[]
|
||||||
local id = vim.fn.jobstart(
|
local id = vim.fn.jobstart(
|
||||||
cmd,
|
cmd,
|
||||||
|
|
@ -114,6 +116,9 @@ function M.cmd(cmd, cb, opts)
|
||||||
)
|
)
|
||||||
if id <= 0 then
|
if id <= 0 then
|
||||||
Snacks.notify.error(("Failed to start job `%s`"):format(cmd))
|
Snacks.notify.error(("Failed to start job `%s`"):format(cmd))
|
||||||
|
elseif opts.input then
|
||||||
|
vim.fn.chansend(id, opts.input .. "\n")
|
||||||
|
vim.fn.chanclose(id, "stdin")
|
||||||
end
|
end
|
||||||
return id > 0 and id or nil
|
return id > 0 and id or nil
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue