feat: add cancelled task status with configurable state chars (#158)

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:55:21 -04:00
parent e9f21c0f0b
commit 2fd95e6dde
9 changed files with 109 additions and 45 deletions

View file

@ -347,6 +347,7 @@ Default buffer-local keys: ~
`gr` Prompt for a recurrence pattern (`recur`)
`gw` Toggle work-in-progress status (`wip`)
`gb` Toggle blocked status (`blocked`)
`g/` Toggle cancelled status (`cancelled`)
`gf` Prompt for filter predicates (`filter`)
`<Tab>` Switch between category / queue view (`view`)
`gz` Undo the last `:w` save (`undo`)
@ -470,6 +471,12 @@ old keys to `false`: >lua
Toggle blocked status for the task under the cursor.
If the task is already `blocked`, reverts to `pending`.
*<Plug>(pending-cancelled)*
<Plug>(pending-cancelled)
Toggle cancelled status for the task under the cursor.
If the task is already `cancelled`, reverts to `pending`.
Toggling on a `done` task switches it to `cancelled`.
*<Plug>(pending-priority-up)*
<Plug>(pending-priority-up)
Increment the priority level for the task under the cursor, clamped
@ -537,14 +544,15 @@ Category view (default): ~ *pending-view-category*
Tasks are grouped under their category header. Categories appear in the
order tasks were added unless `category_order` is set (see
|pending-config|). Blank lines separate categories. Within each category,
tasks are sorted by status (wip → pending → blocked → done), then by
tasks are sorted by status (wip → pending → blocked → done → cancelled), then by
priority, then by insertion order. Category sections are foldable with
`zc` and `zo`.
Queue view: ~ *pending-view-queue*
A flat list of all tasks sorted by a configurable tiebreak chain
(default: status → priority → due → order → id). See
`view.queue.sort` in |pending-config| for customization. Category
`view.queue.sort` in |pending-config| for customization. Status
order: wip → pending → blocked → done → cancelled. Category
names are shown as right-aligned virtual text alongside the due date
virtual text so tasks remain identifiable across categories. The
buffer is named `pending://queue`.
@ -581,6 +589,8 @@ Available predicates: ~
`blocked` Show only tasks with status `blocked`.
`cancelled` Show only tasks with status `cancelled`.
`clear` Special value for |:Pending-filter| — clears the active filter
and shows all tasks.
@ -778,6 +788,7 @@ loads: >lua
move_up = 'K',
wip = 'gw',
blocked = 'gb',
cancelled = 'g/',
},
sync = {
gcal = {},
@ -957,17 +968,21 @@ Fields: ~
See |pending-gcal|, |pending-gtasks|, |pending-s3|.
{icons} (table) *pending.Icons*
Icon characters displayed in the buffer. The
{pending}, {done}, {priority}, {wip}, and
{blocked} characters appear inside brackets
(`[icon]`) as an overlay on the checkbox. The
{category} character prefixes both header lines
and EOL category labels. Fields:
Icon characters used for rendering and parsing
task checkboxes. The {pending}, {done},
{priority}, {wip}, {blocked}, and {cancelled}
characters determine what is written inside
brackets (`[icon]`) in the buffer text and how
status is inferred on `:w`. Each must be a
single character. The {category} character
prefixes header lines and EOL category labels.
Fields:
{pending} Pending task character. Default: ' '
{done} Done task character. Default: 'x'
{priority} Priority task character. Default: '!'
{wip} Work-in-progress character. Default: '>'
{blocked} Blocked task character. Default: '='
{wip} Work-in-progress character. Default: 'w'
{blocked} Blocked task character. Default: 'b'
{cancelled} Cancelled task character. Default: 'c'
{due} Due date prefix. Default: '.'
{recur} Recurrence prefix. Default: '~'
{category} Category prefix. Default: '#'
@ -1024,6 +1039,10 @@ PendingWip Applied to the checkbox icon of work-in-progress tasks.
PendingBlocked Applied to the checkbox icon and text of blocked tasks.
Default: links to `DiagnosticError`.
*PendingCancelled*
PendingCancelled Applied to the checkbox icon and text of cancelled tasks.
Default: links to `NonText`.
*PendingPriority*
PendingPriority Applied to the checkbox icon of priority 1 tasks.
Default: links to `DiagnosticWarn`.
@ -1593,8 +1612,8 @@ with cached data and updates extmarks when the fetch completes.
State pull: ~
Requires `forge.close = true`. After fetching, if the remote issue/PR
is closed or merged and the local task is pending/wip/blocked, the task is
automatically marked as done. Disabled by default. One-way: local status
is closed or merged and the local task is pending/wip/blocked (not cancelled),
the task is automatically marked as done. Disabled by default. One-way: local status
changes do not push back to the forge.
Highlight groups: ~
@ -1622,7 +1641,7 @@ Task fields: ~
{id} (integer) Unique, auto-incrementing task identifier.
{description} (string) Task text as shown in the buffer.
{status} (string) `'pending'`, `'wip'`, `'blocked'`, `'done'`,
or `'deleted'`.
`'cancelled'`, or `'deleted'`.
{category} (string) Category name. Defaults to `default_category`.
{priority} (integer) Priority level: `0` (none), `1``3` (or up to
`max_priority`). Higher values sort first.