ci: some fixes
Some checks are pending
quality / changes (push) Waiting to run
quality / Lua Format Check (push) Blocked by required conditions
quality / Lua Lint Check (push) Blocked by required conditions
quality / Lua Type Check (push) Blocked by required conditions
quality / Markdown Format Check (push) Blocked by required conditions
test / Test (Neovim nightly) (push) Waiting to run
test / Test (Neovim stable) (push) Waiting to run
Some checks are pending
quality / changes (push) Waiting to run
quality / Lua Format Check (push) Blocked by required conditions
quality / Lua Lint Check (push) Blocked by required conditions
quality / Lua Type Check (push) Blocked by required conditions
quality / Markdown Format Check (push) Blocked by required conditions
test / Test (Neovim nightly) (push) Waiting to run
test / Test (Neovim stable) (push) Waiting to run
This commit is contained in:
parent
2b75843dab
commit
e816e6fb7e
3 changed files with 111 additions and 77 deletions
|
|
@ -140,9 +140,9 @@ COMMANDS *pending-commands*
|
||||||
:Pending add Work: standup due:tomorrow rec:weekdays
|
:Pending add Work: standup due:tomorrow rec:weekdays
|
||||||
:Pending add Buy milk due:fri +!!
|
:Pending add Buy milk due:fri +!!
|
||||||
<
|
<
|
||||||
Trailing `+!`, `+!!`, or `+!!!` tokens set the priority level (capped
|
`+!`, `+!!`, or `+!!!` tokens anywhere in the text set the priority
|
||||||
at `max_priority`). If the buffer is currently open it is re-rendered
|
level (capped at `max_priority`). If the buffer is currently open it
|
||||||
after the add.
|
is re-rendered after the add.
|
||||||
|
|
||||||
*:Pending-archive*
|
*:Pending-archive*
|
||||||
:Pending archive [{duration}]
|
:Pending archive [{duration}]
|
||||||
|
|
@ -638,8 +638,8 @@ task data.
|
||||||
==============================================================================
|
==============================================================================
|
||||||
INLINE METADATA *pending-metadata*
|
INLINE METADATA *pending-metadata*
|
||||||
|
|
||||||
Metadata tokens may be appended to any task line before saving. Tokens are
|
Metadata tokens may appear anywhere in a task line. On save, tokens are
|
||||||
parsed from the right and consumed until a non-metadata token is reached.
|
extracted from any position and the remaining words form the description.
|
||||||
|
|
||||||
Supported tokens: ~
|
Supported tokens: ~
|
||||||
|
|
||||||
|
|
@ -663,9 +663,8 @@ On `:w`, the description becomes `Buy milk`, the due date is stored as
|
||||||
`2026-03-15` and rendered as right-aligned virtual text, and the task is
|
`2026-03-15` and rendered as right-aligned virtual text, and the task is
|
||||||
placed under the `Errands` category header.
|
placed under the `Errands` category header.
|
||||||
|
|
||||||
Parsing stops at the first token that is not a recognised metadata token.
|
Only the first occurrence of each metadata type is consumed — duplicate
|
||||||
Repeated tokens of the same type also stop parsing — only one `due:`, one
|
tokens are silently dropped.
|
||||||
`cat:`, and one `rec:` per task line are consumed.
|
|
||||||
|
|
||||||
Omnifunc completion is available for `due:`, `cat:`, and `rec:` token types.
|
Omnifunc completion is available for `due:`, `cat:`, and `rec:` token types.
|
||||||
In insert mode, type the token prefix and press `<C-x><C-o>` to see
|
In insert mode, type the token prefix and press `<C-x><C-o>` to see
|
||||||
|
|
|
||||||
|
|
@ -536,7 +536,6 @@ function M.body(text)
|
||||||
end
|
end
|
||||||
|
|
||||||
local metadata = {}
|
local metadata = {}
|
||||||
local i = #tokens
|
|
||||||
local ck = category_key()
|
local ck = category_key()
|
||||||
local dk = date_key()
|
local dk = date_key()
|
||||||
local rk = recur_key()
|
local rk = recur_key()
|
||||||
|
|
@ -544,84 +543,82 @@ function M.body(text)
|
||||||
local date_pattern_strict = '^' .. vim.pesc(dk) .. ':(%d%d%d%d%-%d%d%-%d%d[T%d:]*)$'
|
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 date_pattern_any = '^' .. vim.pesc(dk) .. ':(.+)$'
|
||||||
local rec_pattern = '^' .. vim.pesc(rk) .. ':(%S+)$'
|
local rec_pattern = '^' .. vim.pesc(rk) .. ':(%S+)$'
|
||||||
local forge_indices = {}
|
local desc_tokens = {}
|
||||||
|
local forge_tokens = {}
|
||||||
|
|
||||||
|
for _, token in ipairs(tokens) do
|
||||||
|
local consumed = false
|
||||||
|
|
||||||
while i >= 1 do
|
|
||||||
local token = tokens[i]
|
|
||||||
local due_val = token:match(date_pattern_strict)
|
local due_val = token:match(date_pattern_strict)
|
||||||
if due_val then
|
if due_val and is_valid_datetime(due_val) then
|
||||||
if metadata.due then
|
if not metadata.due then
|
||||||
break
|
metadata.due = due_val
|
||||||
end
|
end
|
||||||
if not is_valid_datetime(due_val) then
|
consumed = true
|
||||||
break
|
end
|
||||||
end
|
if not consumed then
|
||||||
metadata.due = due_val
|
|
||||||
i = i - 1
|
|
||||||
else
|
|
||||||
local raw_val = token:match(date_pattern_any)
|
local raw_val = token:match(date_pattern_any)
|
||||||
if raw_val then
|
if raw_val then
|
||||||
if metadata.due then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
local resolved = M.resolve_date(raw_val)
|
local resolved = M.resolve_date(raw_val)
|
||||||
if not resolved then
|
if resolved then
|
||||||
break
|
if not metadata.due then
|
||||||
end
|
metadata.due = resolved
|
||||||
metadata.due = resolved
|
|
||||||
i = i - 1
|
|
||||||
else
|
|
||||||
local cat_val = token:match(cat_pattern)
|
|
||||||
if cat_val then
|
|
||||||
if metadata.category then
|
|
||||||
break
|
|
||||||
end
|
end
|
||||||
|
consumed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not consumed then
|
||||||
|
local cat_val = token:match(cat_pattern)
|
||||||
|
if cat_val then
|
||||||
|
if not metadata.category then
|
||||||
metadata.category = cat_val
|
metadata.category = cat_val
|
||||||
i = i - 1
|
|
||||||
else
|
|
||||||
local pri_bangs = token:match('^%+(!+)$')
|
|
||||||
if pri_bangs then
|
|
||||||
if metadata.priority then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
local max = config.get().max_priority or 3
|
|
||||||
metadata.priority = math.min(#pri_bangs, max)
|
|
||||||
i = i - 1
|
|
||||||
else
|
|
||||||
local rec_val = token:match(rec_pattern)
|
|
||||||
if rec_val then
|
|
||||||
if metadata.recur then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
local recur = require('pending.recur')
|
|
||||||
local raw_spec = rec_val
|
|
||||||
if raw_spec:sub(1, 1) == '!' then
|
|
||||||
metadata.recur_mode = 'completion'
|
|
||||||
raw_spec = raw_spec:sub(2)
|
|
||||||
end
|
|
||||||
if not recur.validate(raw_spec) then
|
|
||||||
break
|
|
||||||
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
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
consumed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not consumed then
|
||||||
|
local pri_bangs = token:match('^%+(!+)$')
|
||||||
|
if pri_bangs then
|
||||||
|
if not metadata.priority then
|
||||||
|
local max = config.get().max_priority or 3
|
||||||
|
metadata.priority = math.min(#pri_bangs, max)
|
||||||
|
end
|
||||||
|
consumed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not consumed then
|
||||||
|
local rec_val = token:match(rec_pattern)
|
||||||
|
if rec_val then
|
||||||
|
local recur = require('pending.recur')
|
||||||
|
local raw_spec = rec_val
|
||||||
|
if raw_spec:sub(1, 1) == '!' then
|
||||||
|
raw_spec = raw_spec:sub(2)
|
||||||
|
end
|
||||||
|
if recur.validate(raw_spec) then
|
||||||
|
if not metadata.recur then
|
||||||
|
metadata.recur_mode = rec_val:sub(1, 1) == '!' and 'completion' or nil
|
||||||
|
metadata.recur = raw_spec
|
||||||
|
end
|
||||||
|
consumed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not consumed then
|
||||||
|
if forge.parse_ref(token) then
|
||||||
|
table.insert(forge_tokens, token)
|
||||||
|
else
|
||||||
|
table.insert(desc_tokens, token)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local desc_tokens = {}
|
for _, ft in ipairs(forge_tokens) do
|
||||||
for j = 1, i do
|
table.insert(desc_tokens, ft)
|
||||||
table.insert(desc_tokens, tokens[j])
|
|
||||||
end
|
|
||||||
for fi = #forge_indices, 1, -1 do
|
|
||||||
table.insert(desc_tokens, tokens[forge_indices[fi]])
|
|
||||||
end
|
end
|
||||||
local description = table.concat(desc_tokens, ' ')
|
local description = table.concat(desc_tokens, ' ')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,10 +48,16 @@ describe('parse', function()
|
||||||
assert.are.equal('Errands', meta.category)
|
assert.are.equal('Errands', meta.category)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('stops at duplicate key', function()
|
it('first occurrence wins for duplicate keys', function()
|
||||||
local desc, meta = parse.body('Buy milk due:2026-03-15 due:2026-04-01')
|
local desc, meta = parse.body('Buy milk due:2026-03-15 due:2026-04-01')
|
||||||
assert.are.equal('Buy milk due:2026-03-15', desc)
|
assert.are.equal('Buy milk', desc)
|
||||||
assert.are.equal('2026-04-01', meta.due)
|
assert.are.equal('2026-03-15', meta.due)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('drops identical duplicate metadata tokens', function()
|
||||||
|
local desc, meta = parse.body('Buy milk due:tomorrow due:tomorrow')
|
||||||
|
assert.are.equal('Buy milk', desc)
|
||||||
|
assert.are.equal(os.date('%Y-%m-%d', os.time() + 86400), meta.due)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('stops at non-meta token', function()
|
it('stops at non-meta token', function()
|
||||||
|
|
@ -138,6 +144,38 @@ describe('parse', function()
|
||||||
assert.are.equal('Work', meta.category)
|
assert.are.equal('Work', meta.category)
|
||||||
assert.truthy(desc:find('gl:a/b#12', 1, true))
|
assert.truthy(desc:find('gl:a/b#12', 1, true))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('extracts leading metadata', function()
|
||||||
|
local desc, meta = parse.body('due:2026-03-15 Fix the bug')
|
||||||
|
assert.are.equal('Fix the bug', desc)
|
||||||
|
assert.are.equal('2026-03-15', meta.due)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('extracts metadata from the middle', function()
|
||||||
|
local desc, meta = parse.body('Fix due:2026-03-15 the bug')
|
||||||
|
assert.are.equal('Fix the bug', desc)
|
||||||
|
assert.are.equal('2026-03-15', meta.due)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('extracts multiple metadata from any position', function()
|
||||||
|
local desc, meta = parse.body('cat:Work Fix due:2026-03-15 the bug')
|
||||||
|
assert.are.equal('Fix the bug', desc)
|
||||||
|
assert.are.equal('2026-03-15', meta.due)
|
||||||
|
assert.are.equal('Work', meta.category)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('extracts all metadata types from mixed positions', function()
|
||||||
|
local today = os.date('*t') --[[@as osdate]]
|
||||||
|
local tomorrow = os.date(
|
||||||
|
'%Y-%m-%d',
|
||||||
|
os.time({ year = today.year, month = today.month, day = today.day + 1 })
|
||||||
|
)
|
||||||
|
local desc, meta = parse.body('due:tomorrow cat:Work Fix the bug +!')
|
||||||
|
assert.are.equal('Fix the bug', desc)
|
||||||
|
assert.are.equal(tomorrow, meta.due)
|
||||||
|
assert.are.equal('Work', meta.category)
|
||||||
|
assert.are.equal(1, meta.priority)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe('parse.resolve_date', function()
|
describe('parse.resolve_date', function()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue