feat: add cancelled task status with configurable state chars

Problem: the task lifecycle only has `pending`, `wip`, `blocked`, and
`done`. There is no way to mark a task as abandoned. Additionally,
state characters (`>`, `=`) are hardcoded rather than reading from
`config.icons`, so customizing them has no effect on rendering or
parsing.

Solution: add a `cancelled` status with default state char `c`, `g/`
keymap, `PendingCancelled` highlight, filter predicate, and archive
support. Unify state chars by making `state_char()`, `parse_buffer()`,
and `infer_status()` read from `config.icons`. Change defaults to
mnemonic chars: `w` (wip), `b` (blocked), `c` (cancelled).
This commit is contained in:
Barrett Ruth 2026-03-12 20:15:35 -04:00
parent b2456580b5
commit cee291a20b
Signed by: barrett
GPG key ID: A6C96C9349D2FC81
9 changed files with 109 additions and 45 deletions

View file

@ -47,7 +47,7 @@ function M._recompute_counts()
local today_str = os.date('%Y-%m-%d') --[[@as string]]
for _, task in ipairs(get_store():active_tasks()) do
if task.status ~= 'done' and task.status ~= 'deleted' then
if task.status ~= 'done' and task.status ~= 'deleted' and task.status ~= 'cancelled' then
pending = pending + 1
if task.priority > 0 then
priority = priority + 1
@ -173,6 +173,11 @@ local function compute_hidden_ids(tasks, predicates)
visible = false
break
end
elseif pred == 'cancelled' then
if task.status ~= 'cancelled' then
visible = false
break
end
end
end
if not visible then
@ -368,6 +373,9 @@ function M._setup_buf_mappings(bufnr)
blocked = function()
M.toggle_status('blocked')
end,
cancelled = function()
M.toggle_status('cancelled')
end,
priority_up = function()
M.increment_priority()
end,
@ -843,7 +851,7 @@ function M.prompt_date()
end)
end
---@param target_status 'wip'|'blocked'
---@param target_status 'wip'|'blocked'|'cancelled'
---@return nil
function M.toggle_status(target_status)
local bufnr = buffer.bufnr()
@ -869,7 +877,7 @@ function M.toggle_status(target_status)
return
end
if task.status == target_status then
s:update(id, { status = 'pending' })
s:update(id, { status = 'pending', ['end'] = vim.NIL })
else
s:update(id, { status = target_status })
end
@ -1187,7 +1195,7 @@ function M.archive(arg)
log.debug(('archive: days=%d cutoff=%s total_tasks=%d'):format(days, cutoff, #tasks))
local count = 0
for _, task in ipairs(tasks) do
if (task.status == 'done' or task.status == 'deleted') and task['end'] then
if (task.status == 'done' or task.status == 'deleted' or task.status == 'cancelled') and task['end'] then
if task['end'] < cutoff then
count = count + 1
end
@ -1208,7 +1216,7 @@ function M.archive(arg)
function()
local kept = {}
for _, task in ipairs(tasks) do
if (task.status == 'done' or task.status == 'deleted') and task['end'] then
if (task.status == 'done' or task.status == 'deleted' or task.status == 'cancelled') and task['end'] then
if task['end'] < cutoff then
goto skip
end