From fb76359579bed6f85b47c9e8354b129eb9629a26 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Wed, 11 Mar 2026 13:02:06 -0400 Subject: [PATCH] fix(parse): skip forge refs in right-to-left metadata scan 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. --- lua/pending/diff.lua | 7 ++----- lua/pending/parse.lua | 8 ++++++++ spec/diff_spec.lua | 36 ++++++++++++++++++++++++++++++++++++ spec/parse_spec.lua | 28 ++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 5 deletions(-) diff --git a/lua/pending/diff.lua b/lua/pending/diff.lua index 24645a2..ac38f7a 100644 --- a/lua/pending/diff.lua +++ b/lua/pending/diff.lua @@ -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 diff --git a/lua/pending/parse.lua b/lua/pending/parse.lua index c38fa54..1b36578 100644 --- a/lua/pending/parse.lua +++ b/lua/pending/parse.lua @@ -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 diff --git a/spec/diff_spec.lua b/spec/diff_spec.lua index 355d2db..b69bd5a 100644 --- a/spec/diff_spec.lua +++ b/spec/diff_spec.lua @@ -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) diff --git a/spec/parse_spec.lua b/spec/parse_spec.lua index 8f1135f..aebe0c7 100644 --- a/spec/parse_spec.lua +++ b/spec/parse_spec.lua @@ -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()