mirror of
https://github.com/folke/snacks.nvim
synced 2025-12-23 08:47:57 +00:00
This commit is contained in:
parent
50436373c2
commit
bd3c1a0714
5 changed files with 86 additions and 88 deletions
|
|
@ -237,7 +237,8 @@ M.actions.gh_yank = {
|
|||
}
|
||||
|
||||
M.actions.gh_comment = {
|
||||
desc = "Comment on {type} #{number}",
|
||||
desc = "Add comment",
|
||||
title = "Comment on {type} #{number}",
|
||||
icon = " ",
|
||||
action = function(item, ctx)
|
||||
local win = ctx.main or vim.api.nvim_get_current_win()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
local Async = require("snacks.picker.util.async")
|
||||
local Item = require("snacks.gh.item")
|
||||
local Proc = require("snacks.util.spawn")
|
||||
|
||||
---@class snacks.gh.api
|
||||
local M = {}
|
||||
|
|
@ -148,6 +149,7 @@ end
|
|||
---@param cb fun(proc: snacks.spawn.Proc, data?: string)
|
||||
---@param opts snacks.gh.api.Cmd
|
||||
function M.cmd(cb, opts)
|
||||
opts = opts or {}
|
||||
local args = vim.deepcopy(opts.args)
|
||||
if opts.repo then
|
||||
vim.list_extend(args, { "--repo", opts.repo })
|
||||
|
|
@ -173,12 +175,14 @@ function M.cmd(cb, opts)
|
|||
if err then
|
||||
vim.schedule(function()
|
||||
if not proc.aborted then
|
||||
Snacks.debug.cmd({
|
||||
header = "GH Error",
|
||||
cmd = { "gh", unpack(args) },
|
||||
footer = proc:err(),
|
||||
level = vim.log.levels.ERROR,
|
||||
})
|
||||
if opts.notify ~= false then
|
||||
Snacks.debug.cmd({
|
||||
header = "GH Error",
|
||||
cmd = { "gh", unpack(args) },
|
||||
footer = proc:err(),
|
||||
level = vim.log.levels.ERROR,
|
||||
})
|
||||
end
|
||||
if opts.on_error then
|
||||
opts.on_error(proc, proc:err())
|
||||
end
|
||||
|
|
@ -203,6 +207,7 @@ function M.fetch(cb, opts)
|
|||
end, {
|
||||
args = args,
|
||||
repo = opts.repo,
|
||||
notify = opts.notify,
|
||||
})
|
||||
end
|
||||
M.fetch_sync = wrap_sync(M.fetch)
|
||||
|
|
@ -306,7 +311,7 @@ function M.list(what, cb, opts)
|
|||
end
|
||||
|
||||
---@param cb fun(item?: snacks.picker.gh.Item, updated?: boolean)
|
||||
---@param item snacks.gh.api.View
|
||||
---@param item snacks.gh.api.View|{number?: number}
|
||||
---@param opts? { fields?: string[], force?: boolean }
|
||||
function M.view(cb, item, opts)
|
||||
opts = opts or {}
|
||||
|
|
@ -458,77 +463,60 @@ function M.comments(item, cb)
|
|||
})
|
||||
end
|
||||
|
||||
function M.get_branch()
|
||||
local branch = vim.fn.system({ "git", "branch", "--show-current" }):gsub("\n", "")
|
||||
|
||||
-- Get all config in one call
|
||||
local git_config =
|
||||
vim.fn.system({ "git", "config", "--get-regexp", ("^(branch\\.%s\\.|remote\\.)"):format(branch) }):gsub("\n$", "")
|
||||
|
||||
local cfg = {} ---@type table<string, string>
|
||||
for _, line in ipairs(vim.split(git_config, "\n")) do
|
||||
local key, value = line:match("^([^%s]+)%s+(.+)$")
|
||||
if key then
|
||||
cfg[key] = value
|
||||
end
|
||||
---@async
|
||||
function M.current_pr()
|
||||
local root = Snacks.git.get_root(vim.uv.cwd() or ".")
|
||||
if not root then
|
||||
return
|
||||
end
|
||||
---@type snacks.picker.gh.Item?
|
||||
local pr
|
||||
local branch = Proc.exec({ "git", "branch", "--show-current" })
|
||||
|
||||
-- Extract values
|
||||
local remote = cfg[("branch.%s.remote"):format(branch)] or ""
|
||||
local merge = cfg[("branch.%s.merge"):format(branch)] or ""
|
||||
|
||||
-- Get fork URL (either named remote or direct URL)
|
||||
local url = (remote:match("^https://") or remote:match("^git@")) and remote
|
||||
or cfg[("remote.%s.url"):format(remote)]
|
||||
or remote
|
||||
|
||||
---@param u? string
|
||||
---@return string?
|
||||
local function parse(u)
|
||||
return u and (u:match("github%.com[:/](.+/.+)%.git") or u:match("github%.com[:/](.+/.+)$")) or nil
|
||||
end
|
||||
|
||||
-- Parse author from fork URL
|
||||
local author = (parse(url) or ""):match("^([^/]+)/")
|
||||
|
||||
-- Parse repo from upstream or origin
|
||||
local repo = parse(cfg["remote.upstream.url"]) or parse(cfg["remote.origin.url"])
|
||||
|
||||
-- Parse head from merge ref
|
||||
local head = merge:match("^refs/heads/(.+)$") or branch
|
||||
|
||||
-- Get base branch (default branch from origin)
|
||||
local base = vim.fn.system({ "git", "symbolic-ref", "refs/remotes/origin/HEAD" }):gsub("\n", "")
|
||||
base = base:match("refs/remotes/origin/(.+)") or "main"
|
||||
|
||||
---@type snacks.gh.api.Branch
|
||||
return {
|
||||
url = url,
|
||||
author = author,
|
||||
repo = repo,
|
||||
branch = branch,
|
||||
head = head,
|
||||
base = base,
|
||||
}
|
||||
end
|
||||
|
||||
---@param cb fun(item?: snacks.picker.gh.Item)
|
||||
function M.current_pr(cb)
|
||||
local branch = M.get_branch()
|
||||
local key = string.format("%s:%s/%s->%s", branch.author or "", branch.repo or "", branch.head, branch.base)
|
||||
local key = root .. "::" .. branch
|
||||
if pr_cache[key] then
|
||||
return cb(pr_cache[key])
|
||||
return pr_cache[key]
|
||||
end
|
||||
return M.list("pr", function(items)
|
||||
pr_cache[key] = items and items[1] or nil
|
||||
cb(pr_cache[key])
|
||||
end, {
|
||||
author = branch.author,
|
||||
head = branch.head,
|
||||
base = branch.base,
|
||||
repo = branch.repo,
|
||||
limit = 1,
|
||||
|
||||
-- try with `pr view` first
|
||||
local api_opts = get_opts("pr", "list")
|
||||
pr = M.fetch_sync({
|
||||
args = { "pr", "view" },
|
||||
fields = api_opts.fields,
|
||||
notify = false,
|
||||
})
|
||||
pr = pr and cache_set(Item.new(pr, api_opts)) or nil
|
||||
if pr then
|
||||
pr_cache[key] = pr
|
||||
return pr
|
||||
end
|
||||
|
||||
-- assume this is the main branch of a fork
|
||||
local author, main = branch:match("^(.-)/(.+)$")
|
||||
if not author or not main then
|
||||
return
|
||||
end
|
||||
local repo = M.cmd_sync({
|
||||
args = { "repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner" },
|
||||
notify = false,
|
||||
})
|
||||
if not repo then
|
||||
return
|
||||
end
|
||||
repo = vim.trim(repo)
|
||||
|
||||
M.list("pr", function(items)
|
||||
pr_cache[key] = items and items[1] or nil
|
||||
pr = pr_cache[key]
|
||||
end, {
|
||||
author = author,
|
||||
head = main,
|
||||
base = main,
|
||||
repo = repo,
|
||||
limit = 1,
|
||||
notify = false,
|
||||
}):wait()
|
||||
return pr
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
---@field args string[]
|
||||
---@field repo? string
|
||||
---@field input? string
|
||||
---@field notify? boolean
|
||||
---@field on_error? fun(proc: snacks.spawn.Proc, err: string)
|
||||
|
||||
---@class snacks.gh.api.Api
|
||||
|
|
|
|||
|
|
@ -70,18 +70,12 @@ end
|
|||
---@type snacks.picker.finder
|
||||
function M.get_actions(opts, ctx)
|
||||
opts = opts or {}
|
||||
local proc ---@type snacks.spawn.Proc?
|
||||
if not opts.item and not opts.number then
|
||||
proc = Api.current_pr(function(pr)
|
||||
opts.item = pr
|
||||
end)
|
||||
end
|
||||
---@async
|
||||
return function(cb)
|
||||
if proc then
|
||||
proc:wait()
|
||||
end
|
||||
local item = opts.item
|
||||
if not opts.item and not opts.number then
|
||||
item = Api.current_pr()
|
||||
end
|
||||
|
||||
if not item then
|
||||
local required = { "type", "repo", "number" }
|
||||
|
|
@ -98,7 +92,7 @@ function M.get_actions(opts, ctx)
|
|||
return
|
||||
end
|
||||
item = Api.get({ type = opts.type or "pr", repo = opts.repo, number = opts.number })
|
||||
proc = Api.view(function(it)
|
||||
local proc = Api.view(function(it)
|
||||
item = it
|
||||
end, item)
|
||||
|
||||
|
|
|
|||
|
|
@ -124,6 +124,7 @@ function Proc:wait()
|
|||
while not self.did_exit or self:running() do
|
||||
self.async:suspend()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function Proc:run()
|
||||
|
|
@ -228,10 +229,10 @@ function Proc:on_exit()
|
|||
close(self.stderr)
|
||||
if self.opts.on_exit then
|
||||
self.opts.on_exit(self, self.code ~= 0 or self.signal ~= 0 or self.aborted or false)
|
||||
self.did_exit = true
|
||||
if self.async then
|
||||
self.async:resume()
|
||||
end
|
||||
end
|
||||
self.did_exit = true
|
||||
if self.async then
|
||||
self.async:resume()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
|
@ -285,4 +286,17 @@ end
|
|||
|
||||
M.new = Proc.new
|
||||
|
||||
---@param cmd string[]
|
||||
---@async
|
||||
function M.exec(cmd)
|
||||
return vim.trim(M.new({
|
||||
cmd = cmd[1],
|
||||
args = vim.list_slice(cmd, 2),
|
||||
stdout_buffered = true,
|
||||
stderr_buffered = true,
|
||||
})
|
||||
:wait()
|
||||
:out())
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue