mirror of
https://github.com/folke/snacks.nvim
synced 2025-12-23 08:47:57 +00:00
## Description <!-- Describe the big picture of your changes to communicate to the maintainers why we should accept this pull request. --> Seems like an unintentional change from this PR https://github.com/folke/snacks.nvim/pull/2419 ``` Lua callback: ....local/share/nvim/lazy/snacks.nvim/lua/snacks/gh/buf.lua:1: module 'snacks.gh.actions' not found: no field package.preload['snacks.gh.actions'] cache_loader: module 'snacks.gh.actions' not found cache_loader_lib: module 'snacks.gh.actions' not found ``` ## Related Issue(s) <!-- If this PR fixes any issues, please link to the issue here. - Fixes #<issue_number> --> ## Screenshots <!-- Add screenshots of the changes if applicable. -->
745 lines
20 KiB
Lua
745 lines
20 KiB
Lua
local Api = require("snacks.gh.api")
|
|
local config = require("snacks.gh").config()
|
|
|
|
local M = {}
|
|
|
|
---@class snacks.gh.action.ctx
|
|
---@field items snacks.picker.gh.Item[]
|
|
---@field picker? snacks.Picker
|
|
---@field main? number
|
|
---@field action? snacks.picker.Action
|
|
|
|
---@class snacks.gh.cli.Action.ctx
|
|
---@field item snacks.picker.gh.Item
|
|
---@field args string[]
|
|
---@field opts snacks.gh.cli.Action
|
|
---@field picker? snacks.Picker
|
|
---@field scratch? snacks.win
|
|
---@field input? string
|
|
|
|
---@alias snacks.gh.action.fn fun(item?: snacks.picker.gh.Item, ctx: snacks.gh.action.ctx)
|
|
|
|
---@class snacks.gh.Action
|
|
---@field action snacks.gh.action.fn
|
|
---@field desc? string
|
|
---@field name? string
|
|
---@field priority? number
|
|
---@field title? string -- for items
|
|
---@field type? "pr" | "issue"
|
|
---@field enabled? fun(item: snacks.picker.gh.Item): boolean
|
|
|
|
---@class snacks.gh.actions: {[string]:snacks.gh.Action}
|
|
M.actions = setmetatable({}, {
|
|
__index = function(_, key)
|
|
if type(key) ~= "string" then
|
|
return nil
|
|
end
|
|
local action = M.cli_actions[key]
|
|
if action then
|
|
local ret = M.cli_action(action)
|
|
rawset(M.actions, key, ret)
|
|
return ret
|
|
end
|
|
end,
|
|
})
|
|
|
|
M.actions.gh_diff = {
|
|
desc = "View PR diff",
|
|
icon = " ",
|
|
priority = 100,
|
|
type = "pr",
|
|
title = "View diff for PR #{number}",
|
|
action = function(item, ctx)
|
|
if not item then
|
|
return
|
|
end
|
|
Snacks.picker.gh_diff({
|
|
show_delay = 0,
|
|
repo = item.repo,
|
|
pr = item.number,
|
|
})
|
|
end,
|
|
}
|
|
|
|
M.actions.gh_open = {
|
|
desc = "Open in buffer",
|
|
icon = " ",
|
|
priority = 100,
|
|
title = "Open {type} #{number} in buffer",
|
|
action = function(item, ctx)
|
|
if ctx.picker then
|
|
return Snacks.picker.actions.jump(ctx.picker, item, ctx.action)
|
|
end
|
|
end,
|
|
}
|
|
|
|
M.actions.gh_actions = {
|
|
desc = "Show available actions",
|
|
action = function(item, ctx)
|
|
-- NOTE: this forwards split/vsplit/tab/drop actions to jump
|
|
if ctx.action and ctx.action.cmd then
|
|
return Snacks.picker.actions.jump(ctx.picker, item, ctx.action)
|
|
end
|
|
ctx.main = ctx.main or ctx.picker and ctx.picker.main or nil
|
|
local actions = M.get_actions(item)
|
|
actions.gh_actions = nil -- remove this action
|
|
actions.gh_perform_action = nil -- remove this action
|
|
Snacks.picker.gh_actions({
|
|
item = item,
|
|
layout = {
|
|
config = function(layout)
|
|
-- Fit list height to number of items, up to 10
|
|
for _, box in ipairs(layout.layout) do
|
|
if box.win == "list" and not box.height then
|
|
box.height = math.max(math.min(vim.tbl_count(actions), vim.o.lines * 0.8 - 10), 3)
|
|
end
|
|
end
|
|
end,
|
|
},
|
|
---@param it snacks.picker.gh.Action
|
|
confirm = function(picker, it, action)
|
|
if not it then
|
|
return
|
|
end
|
|
ctx.action = action
|
|
if ctx.picker then
|
|
ctx.picker:focus()
|
|
end
|
|
ctx.main = ctx.main or picker and picker.main or nil
|
|
it.action.action(item, ctx)
|
|
picker:close()
|
|
end,
|
|
})
|
|
end,
|
|
}
|
|
|
|
M.actions.gh_perform_action = {
|
|
action = function(item, ctx)
|
|
if not item then
|
|
return
|
|
end
|
|
item.action.action(item.item, ctx)
|
|
ctx.picker:close()
|
|
end,
|
|
}
|
|
|
|
M.actions.gh_browse = {
|
|
desc = "Open in web browser",
|
|
title = "Open {type} #{number} in web browser",
|
|
icon = " ",
|
|
action = function(_, ctx)
|
|
for _, item in ipairs(ctx.items) do
|
|
Api.cmd(function()
|
|
Snacks.notify.info(("Opened #%s in web browser"):format(item.number))
|
|
end, {
|
|
args = { item.type, "view", tostring(item.number), "--web" },
|
|
repo = item.repo,
|
|
})
|
|
end
|
|
if ctx.picker then
|
|
ctx.picker.list:set_selected() -- clear selection
|
|
end
|
|
end,
|
|
}
|
|
|
|
M.actions.gh_react = {
|
|
desc = "Add reaction",
|
|
icon = " ",
|
|
action = function(item, ctx)
|
|
local reactions = { "+1", "-1", "laugh", "hooray", "confused", "heart", "rocket", "eyes" }
|
|
Snacks.picker.pick("gh_reactions", {
|
|
number = item.number,
|
|
repo = item.repo,
|
|
layout = {
|
|
config = function(layout)
|
|
-- Fit list height to number of items, up to 10
|
|
for _, box in ipairs(layout.layout) do
|
|
if box.win == "list" and not box.height then
|
|
box.height = math.max(math.min(#reactions, vim.o.lines * 0.8 - 10), 3)
|
|
end
|
|
end
|
|
end,
|
|
},
|
|
confirm = function(picker)
|
|
local items = picker:selected({ fallback = true })
|
|
for i, it in ipairs(items) do
|
|
if it.added then
|
|
M.run(item, {
|
|
api = {
|
|
endpoint = "/repos/{repo}/issues/{number}/reactions/" .. it.id,
|
|
method = "DELETE",
|
|
},
|
|
refresh = i == #items,
|
|
}, ctx)
|
|
else
|
|
M.run(item, {
|
|
api = {
|
|
endpoint = "/repos/{repo}/issues/{number}/reactions",
|
|
fields = { content = it.reaction },
|
|
},
|
|
refresh = i == #items,
|
|
}, ctx)
|
|
end
|
|
end
|
|
picker:close()
|
|
end,
|
|
})
|
|
end,
|
|
}
|
|
|
|
M.actions.gh_label = {
|
|
desc = "Add/Remove labels",
|
|
icon = " ",
|
|
action = function(item, ctx)
|
|
Snacks.picker.pick("gh_labels", {
|
|
number = item.number,
|
|
repo = item.repo,
|
|
type = item.type,
|
|
confirm = function(picker)
|
|
local labels = {} ---@type table<string, boolean>
|
|
for _, label in ipairs(item.item.labels or {}) do
|
|
labels[label.name] = true
|
|
end
|
|
for _, it in ipairs(picker:selected({ fallback = true })) do
|
|
labels[it.label] = not it.added or nil
|
|
end
|
|
M.run(item, {
|
|
api = {
|
|
endpoint = "/repos/{repo}/issues/{number}/labels",
|
|
method = "PUT",
|
|
input = vim.fn.json_encode({ labels = vim.tbl_keys(labels) }),
|
|
},
|
|
}, ctx)
|
|
picker:close()
|
|
end,
|
|
})
|
|
end,
|
|
}
|
|
|
|
M.actions.gh_yank = {
|
|
desc = "Yank URL(s) to clipboard",
|
|
icon = " ",
|
|
action = function(_, ctx)
|
|
if vim.fn.mode():find("^[vV]") and ctx.picker then
|
|
ctx.picker.list:select()
|
|
end
|
|
---@param it snacks.picker.gh.Item
|
|
local urls = vim.tbl_map(function(it)
|
|
return it.url
|
|
end, ctx.items)
|
|
if ctx.picker then
|
|
ctx.picker.list:set_selected() -- clear selection
|
|
end
|
|
local value = table.concat(urls, "\n")
|
|
vim.fn.setreg(vim.v.register or "+", value, "l")
|
|
Snacks.notify.info("Yanked " .. #urls .. " URL(s)")
|
|
end,
|
|
}
|
|
|
|
M.actions.gh_comment = {
|
|
desc = "Add comment",
|
|
title = "Comment on {type} #{number}",
|
|
icon = " ",
|
|
action = function(item, ctx)
|
|
local win = ctx.main or vim.api.nvim_get_current_win()
|
|
local buf = vim.api.nvim_win_get_buf(win)
|
|
|
|
local action = vim.deepcopy(M.cli_actions.gh_comment)
|
|
if item.uri == vim.api.nvim_buf_get_name(buf) then
|
|
local lino = vim.api.nvim_win_get_cursor(win)[1]
|
|
---@type {line:number, id:number}[]
|
|
local comments = vim.b[buf].snacks_gh_comments or {}
|
|
for _, c in ipairs(comments) do
|
|
if c.line == lino then
|
|
action.title = "Reply to comment on {type} #{number}"
|
|
action.api = {
|
|
endpoint = "/repos/{repo}/pulls/{number}/comments",
|
|
input = {
|
|
in_reply_to = c.id,
|
|
},
|
|
}
|
|
break
|
|
end
|
|
end
|
|
end
|
|
M.run(item, action, ctx)
|
|
end,
|
|
}
|
|
|
|
M.actions.gh_update_branch = {
|
|
icon = " ",
|
|
title = "Update branch of PR #{number}",
|
|
type = "pr",
|
|
enabled = function(item)
|
|
return item.state == "open"
|
|
end,
|
|
action = function(item, ctx)
|
|
Snacks.picker.select(
|
|
{ "1. Yes using the rebase method", "2. Yes using the merge method", "3. Cancel" },
|
|
{ title = "Are you sure you want to update the brnch of PR #" .. item.id .. "?" },
|
|
function(choice, idx)
|
|
if idx == 3 then
|
|
return
|
|
end
|
|
|
|
local action = vim.deepcopy(M.cli_actions.gh_update_branch)
|
|
if idx == 1 then
|
|
action.args = { "--rebase" }
|
|
end
|
|
M.run(item, action, ctx)
|
|
end
|
|
)
|
|
end,
|
|
}
|
|
|
|
---@type table<string, snacks.gh.cli.Action>
|
|
M.cli_actions = {
|
|
gh_comment = {
|
|
cmd = "comment",
|
|
icon = " ",
|
|
title = "Comment on {type} #{number}",
|
|
success = "Commented on {type} #{number}",
|
|
edit = "body-file",
|
|
},
|
|
gh_update_branch = {
|
|
cmd = "update-branch",
|
|
title = "Update branch of PR #{number}",
|
|
success = "Branch of PR #{number} updated",
|
|
type = "pr",
|
|
},
|
|
gh_checkout = {
|
|
cmd = "checkout",
|
|
icon = " ",
|
|
type = "pr",
|
|
confirm = "Are you sure you want to checkout PR #{number}?",
|
|
title = "Checkout PR #{number}",
|
|
success = "Checked out PR #{number}",
|
|
},
|
|
gh_close = {
|
|
edit = "comment",
|
|
icon = config.icons.crossmark,
|
|
cmd = "close",
|
|
title = "Close {type} #{number}",
|
|
success = "Closed {type} #{number}",
|
|
enabled = function(item)
|
|
return item.state == "open"
|
|
end,
|
|
},
|
|
gh_edit = {
|
|
cmd = "edit",
|
|
icon = " ",
|
|
fields = {
|
|
{ arg = "title", prop = "title", name = "Title" },
|
|
},
|
|
success = "Edited {type} #{number}",
|
|
edit = "body-file",
|
|
template = "{body}",
|
|
title = "Edit {type} #{number}",
|
|
},
|
|
gh_squash = {
|
|
cmd = "merge",
|
|
icon = config.icons.pr.merged,
|
|
type = "pr",
|
|
success = "Squashed and merged PR #{number}",
|
|
args = { "--squash" },
|
|
fields = {
|
|
{ arg = "subject", prop = "title", name = "Title" },
|
|
},
|
|
edit = "body-file",
|
|
confirm = "Are you sure you want to squash and merge PR #{number}?",
|
|
template = "{body}",
|
|
title = "Squash and merge PR #{number}",
|
|
enabled = function(item)
|
|
return item.state == "open"
|
|
end,
|
|
},
|
|
gh_merge_rebase = {
|
|
cmd = "merge",
|
|
icon = config.icons.pr.merged,
|
|
type = "pr",
|
|
success = "Rebased and merged PR #{number}",
|
|
args = { "--rebase" },
|
|
confirm = "Are you sure you want to rebase and merge PR #{number}?",
|
|
title = "Rebase and merge PR #{number}",
|
|
enabled = function(item)
|
|
return item.state == "open"
|
|
end,
|
|
},
|
|
gh_merge = {
|
|
cmd = "merge",
|
|
icon = config.icons.pr.merged,
|
|
type = "pr",
|
|
success = "Merged PR #{number}",
|
|
args = { "--merge" },
|
|
title = "Merge PR #{number}",
|
|
confirm = "Are you sure you want to merge PR #{number}?",
|
|
enabled = function(item)
|
|
return item.state == "open"
|
|
end,
|
|
},
|
|
gh_close_not_planned = {
|
|
cmd = "close",
|
|
icon = config.icons.crossmark,
|
|
type = "issue",
|
|
success = "Closed issue #{number} as not planned",
|
|
args = { "--reason", "not planned" },
|
|
edit = "comment",
|
|
title = "Close issue #{number} as not planned",
|
|
enabled = function(item)
|
|
return item.state == "open"
|
|
end,
|
|
},
|
|
gh_reopen = {
|
|
cmd = "reopen",
|
|
icon = " ",
|
|
edit = "comment",
|
|
title = "Reopen {type} #{number}",
|
|
success = "Reopened {type} #{number}",
|
|
enabled = function(item)
|
|
return item.state == "closed"
|
|
end,
|
|
},
|
|
gh_ready = {
|
|
cmd = "ready",
|
|
icon = config.icons.pr.open,
|
|
type = "pr",
|
|
title = "Mark PR #{number} as ready for review",
|
|
success = "Marked PR #{number} as ready for review",
|
|
enabled = function(item)
|
|
return item.state == "open" and item.isDraft
|
|
end,
|
|
},
|
|
gh_draft = {
|
|
cmd = "ready",
|
|
args = { "--undo" },
|
|
icon = config.icons.pr.draft,
|
|
type = "pr",
|
|
title = "Mark PR #{number} as draft",
|
|
success = "Marked PR #{number} as draft",
|
|
enabled = function(item)
|
|
return item.state == "open" and not item.isDraft
|
|
end,
|
|
},
|
|
gh_approve = {
|
|
cmd = "review",
|
|
icon = config.icons.checkmark,
|
|
type = "pr",
|
|
args = { "--approve" },
|
|
edit = "body-file", -- optional review summary
|
|
title = "Review: approve PR #{number}",
|
|
success = "Approved PR #{number}",
|
|
enabled = function(item)
|
|
return item.state == "open"
|
|
end,
|
|
},
|
|
gh_request_changes = {
|
|
cmd = "review",
|
|
type = "pr",
|
|
icon = " ",
|
|
args = { "--request-changes" },
|
|
edit = "body-file", -- explain what needs fixing
|
|
title = "Review: request changes on PR #{number}",
|
|
success = "Requested changes on PR #{number}",
|
|
enabled = function(item)
|
|
return item.state == "open"
|
|
end,
|
|
},
|
|
gh_review = {
|
|
cmd = "review",
|
|
type = "pr",
|
|
icon = " ",
|
|
args = { "--comment" },
|
|
edit = "body-file", -- general feedback
|
|
title = "Review: comment on PR #{number}",
|
|
success = "Commented on PR #{number}",
|
|
enabled = function(item)
|
|
return item.state == "open"
|
|
end,
|
|
}
|
|
}
|
|
|
|
---@param opts snacks.gh.cli.Action
|
|
function M.cli_action(opts)
|
|
---@type snacks.gh.Action
|
|
return setmetatable({
|
|
desc = opts.desc or opts.title,
|
|
---@type snacks.gh.action.fn
|
|
action = function(item, ctx)
|
|
M.run(item, opts, ctx)
|
|
end,
|
|
}, { __index = opts })
|
|
end
|
|
|
|
---@param str string
|
|
---@param ... table<string, any>
|
|
function M.tpl(str, ...)
|
|
local data = { ... }
|
|
return Snacks.picker.util.tpl(
|
|
str,
|
|
setmetatable({}, {
|
|
__index = function(_, key)
|
|
for _, d in ipairs(data) do
|
|
if d[key] ~= nil then
|
|
local ret = d[key]
|
|
return ret == "pr" and "PR" or ret
|
|
end
|
|
end
|
|
end,
|
|
})
|
|
)
|
|
end
|
|
|
|
---@param item snacks.picker.gh.Item
|
|
function M.get_actions(item)
|
|
local ret = {} ---@type table<string, snacks.gh.Action>
|
|
local keys = vim.tbl_keys(M.actions) ---@type string[]
|
|
vim.list_extend(keys, vim.tbl_keys(M.cli_actions))
|
|
for _, name in ipairs(keys) do
|
|
local action = M.actions[name]
|
|
local enabled = action.type == nil or action.type == item.type
|
|
enabled = enabled and (action.enabled == nil or action.enabled(item))
|
|
if enabled then
|
|
local a = setmetatable({}, { __index = action })
|
|
local ca = M.cli_actions[name] or {}
|
|
a.desc = a.title and M.tpl(a.title or name, item, ca) or a.desc
|
|
a.name = name
|
|
ret[name] = a
|
|
end
|
|
end
|
|
return ret
|
|
end
|
|
|
|
--- Executes a gh cli action
|
|
---@param item snacks.picker.gh.Item
|
|
---@param action snacks.gh.cli.Action
|
|
---@param ctx snacks.gh.action.ctx
|
|
function M.run(item, action, ctx)
|
|
local args = action.cmd and { item.type, action.cmd, tostring(item.number) } or {}
|
|
vim.list_extend(args, action.args or {})
|
|
if action.api then
|
|
action.api.endpoint = M.tpl(action.api.endpoint, item, action)
|
|
end
|
|
---@type snacks.gh.cli.Action.ctx
|
|
local cli_ctx = {
|
|
item = item,
|
|
args = args,
|
|
opts = action,
|
|
picker = ctx.picker,
|
|
}
|
|
if action.edit then
|
|
return M.edit(cli_ctx)
|
|
else
|
|
return M._run(cli_ctx)
|
|
end
|
|
end
|
|
|
|
--- Parses frontmatter fields from body and appends them to ctx.args
|
|
---@param body string
|
|
---@param ctx snacks.gh.cli.Action.ctx
|
|
function M.parse(body, ctx)
|
|
if not ctx.opts.fields then
|
|
return body
|
|
end
|
|
|
|
local fields = {} ---@type table<string, snacks.gh.Field>
|
|
for _, f in ipairs(ctx.opts.fields) do
|
|
fields[f.name] = f
|
|
end
|
|
|
|
local values = {} ---@type table<string, string>
|
|
--- parse markdown frontmatter for fields
|
|
body = body:gsub("^(%-%-%-\n.-\n%-%-%-\n%s*)", function(fm)
|
|
fm = fm:gsub("^%-%-%-\n", ""):gsub("\n%-%-%-\n%s*$", "") --[[@as string]]
|
|
local lines = vim.split(fm, "\n")
|
|
for _, line in ipairs(lines) do
|
|
local field, value = line:match("^(%w+):%s*(.-)%s*$")
|
|
if field and fields[field] then
|
|
values[field] = value
|
|
else
|
|
Snacks.notify.warn(("Unknown field `%s` in frontmatter"):format(field or line))
|
|
end
|
|
end
|
|
return ""
|
|
end) --[[@as string]]
|
|
|
|
for _, field in ipairs(ctx.opts.fields) do
|
|
local value = values[field.name]
|
|
if value then
|
|
if ctx.opts.api then
|
|
ctx.opts.api.fields = ctx.opts.api.fields or {}
|
|
ctx.opts.api.fields[field.arg] = value
|
|
else
|
|
vim.list_extend(ctx.args, { "--" .. field.arg, value })
|
|
end
|
|
else
|
|
Snacks.notify.error(("Missing required field `%s` in frontmatter"):format(field.name))
|
|
return
|
|
end
|
|
end
|
|
return body
|
|
end
|
|
|
|
--- Executes the action CLI command
|
|
---@param ctx snacks.gh.cli.Action.ctx
|
|
function M._run(ctx, force)
|
|
if not force and ctx.opts.confirm then
|
|
Snacks.picker.util.confirm(M.tpl(ctx.opts.confirm, ctx.item, ctx.opts), function()
|
|
M._run(ctx, true)
|
|
end)
|
|
return
|
|
end
|
|
|
|
local spinner = require("snacks.picker.util.spinner").loading()
|
|
local cb = function()
|
|
vim.schedule(function()
|
|
spinner:stop()
|
|
|
|
-- success message
|
|
if ctx.opts.success then
|
|
Snacks.notify.info(M.tpl(ctx.opts.success, ctx.item, ctx.opts))
|
|
end
|
|
|
|
-- refresh item and picker
|
|
if ctx.opts.refresh ~= false then
|
|
vim.schedule(function()
|
|
Api.refresh(ctx.item)
|
|
if ctx.picker and not ctx.picker.closed then
|
|
ctx.picker.list:set_selected()
|
|
ctx.picker.list:set_target()
|
|
ctx.picker:find()
|
|
vim.cmd.startinsert()
|
|
end
|
|
end)
|
|
if ctx.picker and not ctx.picker.closed then
|
|
ctx.picker:focus()
|
|
end
|
|
end
|
|
|
|
-- clean up scratch buffer
|
|
if ctx.scratch then
|
|
local buf = assert(ctx.scratch.buf)
|
|
local fname = vim.api.nvim_buf_get_name(buf)
|
|
ctx.scratch:on("WinClosed", function()
|
|
vim.schedule(function()
|
|
pcall(vim.api.nvim_buf_delete, buf, { force = true })
|
|
os.remove(fname)
|
|
os.remove(fname .. ".meta")
|
|
end)
|
|
end, { buf = true })
|
|
ctx.scratch:close()
|
|
end
|
|
end)
|
|
end
|
|
|
|
if ctx.opts.api then
|
|
Api.request(
|
|
cb,
|
|
Snacks.config.merge(ctx.opts.api or {}, {
|
|
args = ctx.args,
|
|
on_error = function()
|
|
spinner:stop()
|
|
end,
|
|
})
|
|
)
|
|
else
|
|
Api.cmd(cb, {
|
|
input = ctx.input,
|
|
args = ctx.args,
|
|
repo = ctx.item.repo or ctx.opts.repo,
|
|
on_error = function()
|
|
spinner:stop()
|
|
end,
|
|
})
|
|
end
|
|
end
|
|
|
|
--- Edit action body in scratch buffer
|
|
---@param ctx snacks.gh.cli.Action.ctx
|
|
function M.edit(ctx)
|
|
---@param s? string
|
|
local function tpl(s)
|
|
return s and M.tpl(s, ctx.item, ctx.opts) or nil
|
|
end
|
|
|
|
local template = ctx.opts.template or ""
|
|
if not vim.tbl_isempty(ctx.opts.fields or {}) then
|
|
local fm = { "---" }
|
|
for _, f in ipairs(ctx.opts.fields) do
|
|
fm[#fm + 1] = ("%s: {%s}"):format(f.name, f.prop)
|
|
end
|
|
fm[#fm + 1] = "---\n\n"
|
|
template = table.concat(fm, "\n") .. template
|
|
end
|
|
|
|
Snacks.scratch({
|
|
ft = "markdown",
|
|
icon = Snacks.gh.config().icons.logo,
|
|
name = tpl(ctx.opts.title or "{cmd} {type} #{number}"),
|
|
template = tpl(template),
|
|
filekey = {
|
|
cwd = false,
|
|
branch = false,
|
|
count = false,
|
|
id = tpl("{repo}/{type}/{cmd}"),
|
|
},
|
|
win = {
|
|
on_win = function()
|
|
vim.schedule(function()
|
|
vim.cmd.startinsert()
|
|
end)
|
|
end,
|
|
keys = {
|
|
submit = {
|
|
"<c-s>",
|
|
function(win)
|
|
ctx.scratch = win
|
|
M.submit(ctx)
|
|
end,
|
|
desc = "Submit",
|
|
mode = { "n", "i" },
|
|
},
|
|
},
|
|
},
|
|
})
|
|
end
|
|
|
|
--- Submit edited body
|
|
---@param ctx snacks.gh.cli.Action.ctx
|
|
function M.submit(ctx)
|
|
local edit = assert(ctx.opts.edit, "Submit called for action that doesn't need edit?")
|
|
local win = assert(ctx.scratch, "Submit not called from scratch window?")
|
|
ctx = setmetatable({
|
|
args = vim.deepcopy(ctx.args),
|
|
}, { __index = ctx }) -- shallow copy to avoid mutation
|
|
local body = M.parse(win:text(), ctx)
|
|
|
|
if not body then
|
|
return -- error already shown in M.parse
|
|
end
|
|
|
|
if body:find("%S") then
|
|
if edit == "body-file" then
|
|
if ctx.opts.api then
|
|
ctx.opts.api.input = ctx.opts.api.input or {}
|
|
ctx.opts.api.input.body = body
|
|
else
|
|
ctx.input = body
|
|
vim.list_extend(ctx.args, { "--body-file", "-" })
|
|
end
|
|
else
|
|
if ctx.opts.api then
|
|
ctx.opts.api.fields = ctx.opts.api.fields or {}
|
|
ctx.opts.api.fields[edit] = body
|
|
else
|
|
vim.list_extend(ctx.args, { "--" .. edit, body })
|
|
end
|
|
end
|
|
end
|
|
|
|
vim.cmd.stopinsert()
|
|
vim.schedule(function()
|
|
M._run(ctx)
|
|
end)
|
|
end
|
|
|
|
return M
|