refactor(forge): remove %l eol specifier, add auto_close config, fix icons

Problem: `%l` was dead code after inline overlays replaced EOL
rendering. Auto-close was always on with no opt-out. Forge icon
defaults were empty strings.

Solution: remove `%l` from the eol format parser and renderer. Add
`forge.auto_close` (default `false`) to gate state-pull. Set nerd
font icons: `` (GitHub), `` (GitLab), `` (Codeberg). Keep
conceal active in insert mode via `concealcursor = 'nic'`.
This commit is contained in:
Barrett Ruth 2026-03-10 19:54:42 -04:00
parent e764b34ecb
commit 36898898a7
5 changed files with 22 additions and 23 deletions

View file

@ -842,7 +842,6 @@ Fields: ~
{eol_format} (string, default: '%c %r %d') {eol_format} (string, default: '%c %r %d')
Format string for end-of-line virtual text. Format string for end-of-line virtual text.
Specifiers: Specifiers:
`%l` forge link label (`PendingForge`)
`%c` category icon + name (`PendingHeader`) `%c` category icon + name (`PendingHeader`)
`%r` recurrence icon + pattern (`PendingRecur`) `%r` recurrence icon + pattern (`PendingRecur`)
`%d` due icon + date (`PendingDue`/`PendingOverdue`) `%d` due icon + date (`PendingDue`/`PendingOverdue`)
@ -1486,10 +1485,6 @@ inline with a formatted label using overlay extmarks (same technique as
checkbox icons). Multiple forge references in one line are each overlaid checkbox icons). Multiple forge references in one line are each overlaid
independently. independently.
The `%l` specifier in `eol_format` is still supported for users who prefer
the link label in EOL virtual text, but it is no longer in the default
format (`'%c %r %d'`).
Format string: ~ Format string: ~
*pending-forge-format* *pending-forge-format*
Each forge has a configurable `issue_format` string with these placeholders: Each forge has a configurable `issue_format` string with these placeholders:
@ -1505,6 +1500,7 @@ Configuration: ~
>lua >lua
vim.g.pending = { vim.g.pending = {
forge = { forge = {
auto_close = false,
github = { github = {
token = nil, token = nil,
icon = '', icon = '',
@ -1527,6 +1523,11 @@ Configuration: ~
} }
< <
Top-level fields: ~
{auto_close} (boolean, default: false) When true, tasks linked to
closed/merged remote issues are automatically marked
done on buffer open.
Fields (per forge): ~ Fields (per forge): ~
{token} (string, optional) API token for authenticated requests. {token} (string, optional) API token for authenticated requests.
Falls back to CLI: `gh auth token` (GitHub), `glab auth Falls back to CLI: `gh auth token` (GitHub), `glab auth
@ -1551,9 +1552,10 @@ than 5 minutes are re-fetched asynchronously. The buffer renders immediately
with cached data and updates extmarks when the fetch completes. with cached data and updates extmarks when the fetch completes.
State pull: ~ State pull: ~
After fetching, if the remote issue/PR is closed or merged and the local Requires `forge.auto_close = true`. After fetching, if the remote issue/PR
task is pending/wip/blocked, the task is automatically marked as done. This is closed or merged and the local task is pending/wip/blocked, the task is
is one-way: local status changes do not push back to the forge. automatically marked as done. Disabled by default. One-way: local status
changes do not push back to the forge.
Highlight groups: ~ Highlight groups: ~
|PendingForge| Open issue/PR link label |PendingForge| Open issue/PR link label

View file

@ -379,7 +379,7 @@ end
---@param winid integer ---@param winid integer
local function set_win_options(winid) local function set_win_options(winid)
vim.wo[winid].conceallevel = 3 vim.wo[winid].conceallevel = 3
vim.wo[winid].concealcursor = 'nc' vim.wo[winid].concealcursor = 'nic'
vim.wo[winid].winfixheight = true vim.wo[winid].winfixheight = true
end end
@ -446,7 +446,7 @@ end
---@class pending.EolSegment ---@class pending.EolSegment
---@field type 'specifier'|'literal' ---@field type 'specifier'|'literal'
---@field key? 'c'|'r'|'d'|'l' ---@field key? 'c'|'r'|'d'
---@field text? string ---@field text? string
---@param fmt string ---@param fmt string
@ -458,7 +458,7 @@ local function parse_eol_format(fmt)
while pos <= len do while pos <= len do
if fmt:sub(pos, pos) == '%' and pos + 1 <= len then if fmt:sub(pos, pos) == '%' and pos + 1 <= len then
local key = fmt:sub(pos + 1, pos + 1) local key = fmt:sub(pos + 1, pos + 1)
if key == 'c' or key == 'r' or key == 'd' or key == 'l' then if key == 'c' or key == 'r' or key == 'd' then
table.insert(segments, { type = 'specifier', key = key }) table.insert(segments, { type = 'specifier', key = key })
pos = pos + 2 pos = pos + 2
else else
@ -485,10 +485,7 @@ local function build_eol_virt(segments, m, icons)
for i, seg in ipairs(segments) do for i, seg in ipairs(segments) do
if seg.type == 'specifier' then if seg.type == 'specifier' then
local text, hl local text, hl
if seg.key == 'l' and m.forge_ref then if seg.key == 'c' and m.show_category and m.category then
local forge = require('pending.forge')
text, hl = forge.format_label(m.forge_ref, m.forge_cache)
elseif seg.key == 'c' and m.show_category and m.category then
text = icons.category .. ' ' .. m.category text = icons.category .. ' ' .. m.category
hl = 'PendingHeader' hl = 'PendingHeader'
elseif seg.key == 'r' and m.recur then elseif seg.key == 'r' and m.recur then

View file

@ -40,6 +40,7 @@
---@field instances? string[] ---@field instances? string[]
---@class pending.ForgeConfig ---@class pending.ForgeConfig
---@field auto_close? boolean
---@field github? pending.ForgeInstanceConfig ---@field github? pending.ForgeInstanceConfig
---@field gitlab? pending.ForgeInstanceConfig ---@field gitlab? pending.ForgeInstanceConfig
---@field codeberg? pending.ForgeInstanceConfig ---@field codeberg? pending.ForgeInstanceConfig
@ -154,18 +155,19 @@ local defaults = {
}, },
sync = {}, sync = {},
forge = { forge = {
auto_close = false,
github = { github = {
icon = '', icon = '',
issue_format = '%i %o/%r#%n', issue_format = '%i %o/%r#%n',
instances = {}, instances = {},
}, },
gitlab = { gitlab = {
icon = '', icon = '',
issue_format = '%i %o/%r#%n', issue_format = '%i %o/%r#%n',
instances = {}, instances = {},
}, },
codeberg = { codeberg = {
icon = '', icon = '',
issue_format = '%i %o/%r#%n', issue_format = '%i %o/%r#%n',
instances = {}, instances = {},
}, },

View file

@ -396,8 +396,10 @@ function M.refresh(s)
if cache then if cache then
task._extra._forge_cache = cache task._extra._forge_cache = cache
any_fetched = true any_fetched = true
local forge_cfg = config.get().forge or {}
if if
(cache.state == 'closed' or cache.state == 'merged') forge_cfg.auto_close
and (cache.state == 'closed' or cache.state == 'merged')
and (task.status == 'pending' or task.status == 'wip' or task.status == 'blocked') and (task.status == 'pending' or task.status == 'wip' or task.status == 'blocked')
then then
task.status = 'done' task.status = 'done'

View file

@ -19,8 +19,6 @@ local parse = require('pending.parse')
---@field show_category? boolean ---@field show_category? boolean
---@field priority? integer ---@field priority? integer
---@field recur? string ---@field recur? string
---@field forge_ref? pending.ForgeRef
---@field forge_cache? pending.ForgeCache
---@field forge_spans? pending.ForgeLineMeta[] ---@field forge_spans? pending.ForgeLineMeta[]
---@class pending.views ---@class pending.views
@ -219,8 +217,6 @@ function M.category_view(tasks)
priority = task.priority, priority = task.priority,
overdue = task.status ~= 'done' and task.due ~= nil and parse.is_overdue(task.due) or nil, overdue = task.status ~= 'done' and task.due ~= nil and parse.is_overdue(task.due) or nil,
recur = task.recur, recur = task.recur,
forge_ref = task._extra and task._extra._forge_ref or nil,
forge_cache = task._extra and task._extra._forge_cache or nil,
forge_spans = compute_forge_spans(task, prefix_len), forge_spans = compute_forge_spans(task, prefix_len),
}) })
end end