fix(parse): skip forge refs in right-to-left metadata scan (#142)

Problem: `parse.body()` scans tokens right-to-left and breaks on the
first non-metadata token. Forge refs like `gl:a/b#12` halted the scan,
preventing metadata tokens to their left (e.g. `due:tomorrow`) from
being parsed. Additionally, `diff.parse_buffer()` ignored
`metadata.priority` from `+!!` tokens and only used checkbox-derived
priority, and priority updates between two non-zero values were silently
skipped.

Solution: Recognize forge ref tokens via `forge.parse_ref()` during the
right-to-left scan and skip past them, re-appending them to the
description so `forge.find_refs()` still works. Prefer
`metadata.priority` over checkbox priority in `parse_buffer()`, and
simplify the priority update condition to catch all value changes.
This commit is contained in:
Barrett Ruth 2026-03-11 13:02:55 -04:00 committed by GitHub
parent b131d6d391
commit c590e86e9d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 74 additions and 5 deletions

View file

@ -63,7 +63,7 @@ function M.parse_buffer(lines)
type = 'task',
id = id and tonumber(id) or nil,
description = description,
priority = priority,
priority = metadata.priority or priority,
status = status,
category = metadata.category or current_category or config.get().default_category,
due = metadata.due,
@ -146,10 +146,7 @@ function M.apply(lines, s, hidden_ids)
task.category = entry.category
changed = true
end
if entry.priority == 0 and task.priority > 0 then
task.priority = 0
changed = true
elseif entry.priority > 0 and task.priority == 0 then
if entry.priority ~= task.priority then
task.priority = entry.priority
changed = true
end

View file

@ -1,4 +1,5 @@
local config = require('pending.config')
local forge = require('pending.forge')
---@class pending.Metadata
---@field due? string
@ -543,6 +544,7 @@ function M.body(text)
local date_pattern_strict = '^' .. vim.pesc(dk) .. ':(%d%d%d%d%-%d%d%-%d%d[T%d:]*)$'
local date_pattern_any = '^' .. vim.pesc(dk) .. ':(.+)$'
local rec_pattern = '^' .. vim.pesc(rk) .. ':(%S+)$'
local forge_indices = {}
while i >= 1 do
local token = tokens[i]
@ -602,6 +604,9 @@ function M.body(text)
end
metadata.recur = raw_spec
i = i - 1
elseif forge.parse_ref(token) then
table.insert(forge_indices, i)
i = i - 1
else
break
end
@ -615,6 +620,9 @@ function M.body(text)
for j = 1, i do
table.insert(desc_tokens, tokens[j])
end
for fi = #forge_indices, 1, -1 do
table.insert(desc_tokens, tokens[forge_indices[fi]])
end
local description = table.concat(desc_tokens, ' ')
return description, metadata

View file

@ -379,5 +379,41 @@ describe('diff', function()
local task = s:get(1)
assert.are.equal(0, task.priority)
end)
it('sets priority from +!! token', function()
local lines = {
'# Inbox',
'- [ ] Pay bills +!!',
}
diff.apply(lines, s)
s:load()
local task = s:get(1)
assert.are.equal(2, task.priority)
end)
it('updates priority between non-zero values', function()
s:add({ description = 'Task name', priority = 2 })
s:save()
local lines = {
'# Inbox',
'/1/- [!] Task name',
}
diff.apply(lines, s)
s:load()
local task = s:get(1)
assert.are.equal(1, task.priority)
end)
it('parses metadata with forge ref on same line', function()
local lines = {
'# Inbox',
'- [ ] Fix bug due:2026-03-15 gh:user/repo#42',
}
diff.apply(lines, s)
s:load()
local task = s:get(1)
assert.are.equal('2026-03-15', task.due)
assert.is_not_nil(task._extra._forge_ref)
end)
end)
end)

View file

@ -110,6 +110,34 @@ describe('parse', function()
assert.is_nil(meta.due)
assert.truthy(desc:find('due:garbage', 1, true))
end)
it('parses metadata before a forge ref', function()
local desc, meta = parse.body('Fix bug due:2026-03-15 gh:user/repo#42')
assert.are.equal('2026-03-15', meta.due)
assert.truthy(desc:find('gh:user/repo#42', 1, true))
assert.truthy(desc:find('Fix bug', 1, true))
end)
it('parses metadata after a forge ref', function()
local desc, meta = parse.body('Fix bug gh:user/repo#42 due:2026-03-15')
assert.are.equal('2026-03-15', meta.due)
assert.truthy(desc:find('gh:user/repo#42', 1, true))
assert.truthy(desc:find('Fix bug', 1, true))
end)
it('parses all metadata around forge ref', function()
local desc, meta = parse.body('Fix bug due:tomorrow gh:user/repo#42 cat:Work')
assert.are.equal(os.date('%Y-%m-%d', os.time() + 86400), meta.due)
assert.are.equal('Work', meta.category)
assert.truthy(desc:find('gh:user/repo#42', 1, true))
end)
it('parses forge ref between metadata tokens', function()
local desc, meta = parse.body('Fix bug cat:Work gl:a/b#12 due:2026-03-15')
assert.are.equal('2026-03-15', meta.due)
assert.are.equal('Work', meta.category)
assert.truthy(desc:find('gl:a/b#12', 1, true))
end)
end)
describe('parse.resolve_date', function()