Merge remote-tracking branch 'origin/main' into docs/sync-s3-auto-auth

# Conflicts:
#	doc/pending.txt
#	lua/pending/config.lua
#	lua/pending/forge.lua
#	spec/forge_spec.lua
This commit is contained in:
Barrett Ruth 2026-03-10 23:22:43 -04:00
commit d043c6aaee
5 changed files with 113 additions and 15 deletions

View file

@ -1500,7 +1500,7 @@ Configuration: ~
>lua
vim.g.pending = {
forge = {
auto_close = false,
close = false,
warn_missing_cli = true,
github = {
icon = '',
@ -1522,7 +1522,7 @@ Configuration: ~
<
Top-level fields: ~
{auto_close} (boolean, default: false) When true, tasks linked to
{close} (boolean, default: false) When true, tasks linked to
closed/merged remote issues are automatically marked
done on buffer open.
{warn_missing_cli} (boolean, default: true) When true, warns once per
@ -1550,7 +1550,7 @@ than 5 minutes are re-fetched asynchronously. The buffer renders immediately
with cached data and updates extmarks when the fetch completes.
State pull: ~
Requires `forge.auto_close = true`. After fetching, if the remote issue/PR
Requires `forge.close = true`. After fetching, if the remote issue/PR
is closed or merged and the local task is pending/wip/blocked, the task is
automatically marked as done. Disabled by default. One-way: local status
changes do not push back to the forge.

View file

@ -1,4 +1,5 @@
local config = require('pending.config')
local forge = require('pending.forge')
---@class pending.CompletionItem
---@field word string
@ -109,6 +110,17 @@ local function recur_completions()
return result
end
---@param source string
---@return boolean
function M._is_forge_source(source)
for _, b in ipairs(forge.backends()) do
if b.shorthand == source then
return true
end
end
return false
end
---@type string?
local _complete_source = nil
@ -128,10 +140,10 @@ function M.omnifunc(findstart, base)
{ vim.pesc(dk) .. ':([%S]*)$', dk },
{ 'cat:([%S]*)$', 'cat' },
{ vim.pesc(rk) .. ':([%S]*)$', rk },
{ 'gh:([%S]*)$', 'gh' },
{ 'gl:([%S]*)$', 'gl' },
{ 'cb:([%S]*)$', 'cb' },
}
for _, b in ipairs(forge.backends()) do
table.insert(checks, { vim.pesc(b.shorthand) .. ':([%S]*)$', b.shorthand })
end
for _, check in ipairs(checks) do
local start = before:find(check[1])
@ -172,7 +184,7 @@ function M.omnifunc(findstart, base)
table.insert(matches, { word = c.word, menu = '[' .. source .. ']', info = c.info })
end
end
elseif source == 'gh' or source == 'gl' or source == 'cb' then
elseif M._is_forge_source(source) then
local s = require('pending.buffer').store()
if s then
local seen = {}

View file

@ -37,9 +37,10 @@
---@field icon? string
---@field issue_format? string
---@field instances? string[]
---@field shorthand? string
---@class pending.ForgeConfig
---@field auto_close? boolean
---@field close? boolean
---@field warn_missing_cli? boolean
---@field [string] pending.ForgeInstanceConfig
@ -153,7 +154,7 @@ local defaults = {
},
sync = {},
forge = {
auto_close = false,
close = false,
warn_missing_cli = true,
github = {
icon = '',

View file

@ -63,6 +63,14 @@ function M.backends()
return _backends
end
function M._reset_instances()
_instances_resolved = false
_by_shorthand = {}
for _, b in ipairs(_backends) do
_by_shorthand[b.shorthand] = b
end
end
local function _ensure_instances()
if _instances_resolved then
return
@ -74,17 +82,27 @@ local function _ensure_instances()
for _, inst in ipairs(forge_cfg.instances or {}) do
_by_host[inst] = backend
end
if forge_cfg.shorthand and forge_cfg.shorthand ~= backend.shorthand then
_by_shorthand[backend.shorthand] = nil
backend.shorthand = forge_cfg.shorthand
_by_shorthand[backend.shorthand] = backend
end
end
end
---@param token string
---@return pending.ForgeRef?
function M._parse_shorthand(token)
local prefix, rest = token:match('^(%l%l):(.+)$')
if not prefix then
return nil
_ensure_instances()
local backend, rest
for prefix, b in pairs(_by_shorthand) do
local candidate = token:match('^' .. vim.pesc(prefix) .. ':(.+)$')
if candidate then
backend = b
rest = candidate
break
end
end
local backend = _by_shorthand[prefix]
if not backend then
return nil
end
@ -291,7 +309,7 @@ end
---@param s pending.Store
function M.refresh(s)
local forge_cfg = config.get().forge or {}
if not forge_cfg.auto_close then
if not forge_cfg.close then
return
end
local tasks = s:tasks()
@ -309,7 +327,7 @@ function M.refresh(s)
any_fetched = true
local forge_cfg = config.get().forge or {}
if
forge_cfg.auto_close
forge_cfg.close
and (cache.state == 'closed' or cache.state == 'merged')
and (task.status == 'pending' or task.status == 'wip' or task.status == 'blocked')
then

View file

@ -391,6 +391,73 @@ describe('forge registry', function()
end)
end)
describe('custom forge prefixes', function()
local config = require('pending.config')
local complete = require('pending.complete')
it('parses custom-length shorthand (3+ chars)', function()
local custom = forge.gitea_backend({
name = 'customforge',
shorthand = 'cgf',
default_host = 'custom.example.com',
})
forge.register(custom)
local ref = forge._parse_shorthand('cgf:alice/proj#99')
assert.is_not_nil(ref)
assert.equals('customforge', ref.forge)
assert.equals('alice', ref.owner)
assert.equals('proj', ref.repo)
assert.equals(99, ref.number)
end)
it('parse_ref dispatches custom-length shorthand', function()
local ref = forge.parse_ref('cgf:alice/proj#5')
assert.is_not_nil(ref)
assert.equals('customforge', ref.forge)
assert.equals(5, ref.number)
end)
it('find_refs finds custom-length shorthand', function()
local refs = forge.find_refs('Fix cgf:alice/proj#12')
assert.equals(1, #refs)
assert.equals('customforge', refs[1].ref.forge)
assert.equals(12, refs[1].ref.number)
end)
it('completion returns entries for custom backends', function()
assert.is_true(complete._is_forge_source('cgf'))
end)
it('config shorthand override re-registers backend', function()
vim.g.pending = {
forge = {
github = { shorthand = 'github' },
},
}
config.reset()
forge._reset_instances()
local ref = forge._parse_shorthand('github:user/repo#1')
assert.is_not_nil(ref)
assert.equals('github', ref.forge)
assert.equals('user', ref.owner)
assert.equals('repo', ref.repo)
assert.equals(1, ref.number)
assert.is_nil(forge._parse_shorthand('gh:user/repo#1'))
vim.g.pending = nil
config.reset()
for _, b in ipairs(forge.backends()) do
if b.name == 'github' then
b.shorthand = 'gh'
end
end
forge._reset_instances()
end)
end)
describe('forge diff integration', function()
local store = require('pending.store')
local diff = require('pending.diff')