mirror of
https://github.com/zoriya/flake.git
synced 2026-08-15 18:43:42 +00:00
Add jj stage picker
This commit is contained in:
+189
-10
@@ -11,6 +11,19 @@ local function jj_cwd(from, opts, ctx)
|
||||
return cwd
|
||||
end
|
||||
|
||||
local STATUS_HL = {
|
||||
["A"] = "SnacksPickerGitStatusAdded",
|
||||
["M"] = "SnacksPickerGitStatusModified",
|
||||
["D"] = "SnacksPickerGitStatusDeleted",
|
||||
["R"] = "SnacksPickerGitStatusRenamed",
|
||||
["C"] = "SnacksPickerGitStatusCopied",
|
||||
["?"] = "SnacksPickerGitStatusUntracked",
|
||||
}
|
||||
|
||||
local function status_hl(status)
|
||||
return STATUS_HL[status] or "SnacksPickerGitStatus"
|
||||
end
|
||||
|
||||
Snacks.picker.jj_log = function(file)
|
||||
local title = "jj log"
|
||||
if file then
|
||||
@@ -171,6 +184,164 @@ Snacks.picker.jj_log_file = function()
|
||||
Snacks.picker.jj_log(vim.fn.expand("%:p"))
|
||||
end
|
||||
|
||||
-- Stage-area style picker: show the combined diff of `@` and `@-`, with a two
|
||||
-- character status column (`@` first, `@-` second) à la git's staged/unstaged
|
||||
-- columns, and let <tab> move a file's change between `@` and `@-`.
|
||||
Snacks.picker.jj_stage = function()
|
||||
local from = vim.api.nvim_buf_get_name(0)
|
||||
if from == "" then
|
||||
from = nil
|
||||
end
|
||||
|
||||
local title = "jj stage @ / @-"
|
||||
local root = vim.fs.root(vim.fs.normalize(from or vim.uv.cwd() or "."), ".jj")
|
||||
if root then
|
||||
local function desc(rev)
|
||||
local out = vim.system({
|
||||
"jj", "log", "--no-graph", "--color=never", "-r", rev, "-T",
|
||||
"if(description, description.first_line(), description_placeholder)",
|
||||
}, { cwd = root, text = true }):wait()
|
||||
return vim.trim(out.stdout or "")
|
||||
end
|
||||
title = string.format("@ %s │ @- %s", desc("@"), desc("@-"))
|
||||
end
|
||||
|
||||
Snacks.picker.pick({
|
||||
title = title,
|
||||
supports_live = false,
|
||||
finder = function(opts, ctx)
|
||||
local cwd = jj_cwd(from, opts, ctx)
|
||||
if not cwd then
|
||||
return {}
|
||||
end
|
||||
|
||||
-- Parse `jj diff --summary` for a revision into { [file] = entry }.
|
||||
local function summary(rev)
|
||||
local out = vim.system(
|
||||
{ "jj", "diff", "--summary", "--color=never", "-r", rev },
|
||||
{ cwd = cwd, text = true }
|
||||
):wait()
|
||||
local files = {}
|
||||
for line in vim.gsplit(out.stdout or "", "\n", { trimempty = true }) do
|
||||
local status, file = line:match("^(.) (.+)$")
|
||||
if status then
|
||||
local entry = { status = status, file = file }
|
||||
local base, prev, next, suffix = file:match("^(.*){(.*) => (.*)}(.*)$")
|
||||
if base ~= nil then
|
||||
entry.rename = base .. prev .. suffix
|
||||
entry.file = base .. next .. suffix
|
||||
end
|
||||
files[entry.file] = entry
|
||||
end
|
||||
end
|
||||
return files
|
||||
end
|
||||
|
||||
local at = summary("@")
|
||||
local parent = summary("@-")
|
||||
|
||||
local order, seen = {}, {}
|
||||
for _, map in ipairs({ at, parent }) do
|
||||
for k in pairs(map) do
|
||||
if not seen[k] then
|
||||
seen[k] = true
|
||||
order[#order + 1] = k
|
||||
end
|
||||
end
|
||||
end
|
||||
table.sort(order)
|
||||
|
||||
local items = {}
|
||||
for _, k in ipairs(order) do
|
||||
local a, p = at[k], parent[k]
|
||||
local at_status = a and a.status or " "
|
||||
local parent_status = p and p.status or " "
|
||||
items[#items + 1] = {
|
||||
text = k,
|
||||
cwd = cwd,
|
||||
file = k,
|
||||
rename = (a and a.rename) or (p and p.rename),
|
||||
-- Two status columns, `@` first then `@-`: `MM`, `M `, ` M`, ...
|
||||
at_status = at_status,
|
||||
parent_status = parent_status,
|
||||
in_at = a ~= nil,
|
||||
}
|
||||
end
|
||||
return items
|
||||
end,
|
||||
format = function(item, picker)
|
||||
local ret = {} ---@type snacks.picker.Highlight[]
|
||||
|
||||
table.insert(ret, { item.at_status, status_hl(item.at_status) })
|
||||
table.insert(ret, { item.parent_status, status_hl(item.parent_status) })
|
||||
table.insert(ret, { " " })
|
||||
if item.rename then
|
||||
local file = item.file
|
||||
item.file = item.rename
|
||||
item._path = nil
|
||||
vim.list_extend(ret, Snacks.picker.format.filename(item, picker))
|
||||
item.file = file
|
||||
item._path = nil
|
||||
ret[#ret + 1] = { "-> ", "SnacksPickerDelim" }
|
||||
ret[#ret + 1] = { " " }
|
||||
end
|
||||
vim.list_extend(ret, Snacks.picker.format.filename(item, picker))
|
||||
return ret
|
||||
end,
|
||||
preview = function(ctx)
|
||||
local cmd = { "jj", "diff", "--git", "--no-pager", "--from", "@--", "--to", "@" }
|
||||
table.insert(cmd, string.format("file:'%s'", ctx.item.file))
|
||||
Snacks.picker.preview.cmd(cmd, ctx, { ft = "diff" })
|
||||
end,
|
||||
sort = { fields = { 'score:desc', 'idx' } },
|
||||
win = {
|
||||
input = {
|
||||
keys = {
|
||||
["<tab>"] = { "jj_toggle", mode = { "i", "n" } },
|
||||
["<c-s>"] = { "jj_gsplit", mode = { "i" } },
|
||||
["<c-v>"] = { "jj_gvsplit", mode = { "i" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
actions = {
|
||||
-- Move a file's change between `@` and `@-`. When it lives in `@`
|
||||
-- (`M ` or `MM`) squash it down into `@-`; when it only lives in `@-`
|
||||
-- (` M`) move it back up into `@`.
|
||||
jj_toggle = function(picker)
|
||||
local items = picker:selected({ fallback = true })
|
||||
local done = 0
|
||||
for _, item in ipairs(items) do
|
||||
local paths = { string.format("file:'%s'", item.file) }
|
||||
if item.rename then
|
||||
table.insert(paths, string.format("file:'%s'", item.rename))
|
||||
end
|
||||
local cmd
|
||||
if item.in_at then
|
||||
cmd = { "jj", "squash", "--into", "@-" }
|
||||
else
|
||||
cmd = { "jj", "squash", "--from", "@-", "--into", "@" }
|
||||
end
|
||||
vim.list_extend(cmd, paths)
|
||||
Snacks.picker.util.cmd(cmd, function()
|
||||
done = done + 1
|
||||
if done == #items then
|
||||
picker:refresh()
|
||||
end
|
||||
end, { cwd = item.cwd })
|
||||
end
|
||||
end,
|
||||
jj_gsplit = function(picker, item)
|
||||
picker:close()
|
||||
vim.cmd({ cmd = "Jsplit", args = { "@:" .. item.file } })
|
||||
end,
|
||||
jj_gvsplit = function(picker, item)
|
||||
picker:close()
|
||||
vim.cmd({ cmd = "Jvsplit", args = { "@:" .. item.file } })
|
||||
end,
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
Snacks.picker.jj_show = function(spec)
|
||||
if type(spec) == "string" then
|
||||
spec = { ref = spec }
|
||||
@@ -242,16 +413,7 @@ Snacks.picker.jj_show = function(spec)
|
||||
format = function(item, picker)
|
||||
local ret = {} ---@type snacks.picker.Highlight[]
|
||||
|
||||
local hls = {
|
||||
["A"] = "SnacksPickerGitStatusAdded",
|
||||
["M"] = "SnacksPickerGitStatusModified",
|
||||
["D"] = "SnacksPickerGitStatusDeleted",
|
||||
["R"] = "SnacksPickerGitStatusRenamed",
|
||||
["C"] = "SnacksPickerGitStatusCopied",
|
||||
["?"] = "SnacksPickerGitStatusUntracked",
|
||||
}
|
||||
local hl = hls[item.status] or "SnacksPickerGitStatus"
|
||||
table.insert(ret, { item.status, hl })
|
||||
table.insert(ret, { item.status, status_hl(item.status) })
|
||||
table.insert(ret, { " " })
|
||||
if item.rename then
|
||||
local file = item.file
|
||||
@@ -281,6 +443,7 @@ Snacks.picker.jj_show = function(spec)
|
||||
input = {
|
||||
keys = {
|
||||
["<a-s>"] = { "jj_split", mode = { "i" } },
|
||||
["<c-s-s>"] = { "jj_squash", mode = { "i" } },
|
||||
["<c-s>"] = { "jj_gsplit", mode = { "i" } },
|
||||
["<c-v>"] = { "jj_gvsplit", mode = { "i" } },
|
||||
},
|
||||
@@ -297,6 +460,22 @@ Snacks.picker.jj_show = function(spec)
|
||||
table.concat(items, "' | '")
|
||||
))
|
||||
end,
|
||||
jj_squash = function(picker)
|
||||
local items = picker:selected({ fallback = true })
|
||||
if #items == 0 then
|
||||
return
|
||||
end
|
||||
local cmd = { "jj", "squash", "-r", show_ref }
|
||||
for _, item in ipairs(items) do
|
||||
table.insert(cmd, string.format("file:'%s'", item.file))
|
||||
if item.rename then
|
||||
table.insert(cmd, string.format("file:'%s'", item.rename))
|
||||
end
|
||||
end
|
||||
Snacks.picker.util.cmd(cmd, function()
|
||||
picker:refresh()
|
||||
end, { cwd = items[1].cwd })
|
||||
end,
|
||||
jj_gsplit = function(picker, item)
|
||||
picker:close()
|
||||
vim.cmd({ cmd = "Jsplit", args = { show_ref .. ":" .. item.file } })
|
||||
|
||||
Reference in New Issue
Block a user