feat: complete task editing coverage (#109)

Problem: the task editing surface had gaps — category and recurrence had
no keymaps, `:Pending edit` required knowing the task ID, tasks couldn't
be reordered with a keymap, priority was binary (0/1), and `wip`/`blocked`
states were documented but unimplemented.

Solution: fill every cell so every property is editable in every way.

- `gc`/`gr` keymaps for category select and recurrence prompt
- cursor-aware `:Pending edit` (omit ID to use task under cursor)
- `J`/`K` keymaps to reorder tasks within a category
- multi-level priorities (`max_priority` config, `g!` cycles 0→1→2→3→0)
- `+!!`/`+!!!` tokens in `:Pending edit`, `:Pending add`, `parse.body()`
- `PendingPriority2`/`PendingPriority3` highlight groups
- `gw`/`gb` keymaps toggle `wip`/`blocked` status
- `>`/`=` state chars in buffer rendering and diff parsing
- `PendingWip`/`PendingBlocked` highlight groups
- sort order: wip → pending → blocked → done
- `wip`/`blocked` filter predicates and icons
This commit is contained in:
Barrett Ruth 2026-03-08 19:44:03 -04:00 committed by GitHub
parent 073541424e
commit b06249f101
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 498 additions and 101 deletions

View file

@ -133,8 +133,11 @@ COMMANDS *pending-commands*
:Pending add School: Submit homework
:Pending add Errands: Pick up dry cleaning due:fri
:Pending add Work: standup due:tomorrow rec:weekdays
:Pending add Buy milk due:fri +!!
<
If the buffer is currently open it is re-rendered after the add.
Trailing `+!`, `+!!`, or `+!!!` tokens set the priority level (capped
at `max_priority`). If the buffer is currently open it is re-rendered
after the add.
*:Pending-archive*
:Pending archive [{days}]
@ -215,18 +218,22 @@ COMMANDS *pending-commands*
See |pending-filters| for the full list of supported predicates.
*:Pending-edit*
:Pending edit {id} [{operations}]
Edit metadata on an existing task without opening the buffer. {id} is the
numeric task ID. One or more operations follow: >vim
:Pending edit [{id}] [{operations}]
Edit metadata on an existing task. {id} is the numeric task ID. When
{id} is omitted and the task buffer is open, the task under the cursor
is used. This makes `:Pending edit +!` work without knowing the ID.
One or more operations follow: >vim
:Pending edit 5 due:tomorrow cat:Work +!
:Pending edit 5 -due -cat -rec
:Pending edit 5 rec:!weekly due:fri
:Pending edit +!!
<
Operations: ~
`due:<date>` Set due date (accepts all |pending-dates| vocabulary).
`cat:<name>` Set category.
`rec:<pattern>` Set recurrence (prefix `!` for completion-based).
`+!` Add priority flag.
`+!` Set priority to 1.
`+!!` Set priority to 2.
`+!!!` Set priority to 3 (capped at `max_priority`).
`-!` Remove priority flag.
`-due` Clear due date.
`-cat` Clear category.
@ -267,13 +274,19 @@ Default buffer-local keys: ~
------- ------------------------------------------------
`q` Close the task buffer (`close`)
`<CR>` Toggle complete / uncomplete (`toggle`)
`g!` Toggle the priority flag (`priority`)
`g!` Cycle priority: 0→1→2→3→0 (`priority`)
`gd` Prompt for a due date (`date`)
`gc` Select a category from existing categories (`category`)
`gr` Prompt for a recurrence pattern (`recur`)
`gw` Toggle work-in-progress status (`wip`)
`gb` Toggle blocked status (`blocked`)
`gf` Prompt for filter predicates (`filter`)
`<Tab>` Switch between category / queue view (`view`)
`gz` Undo the last `:w` save (`undo`)
`o` Insert a new task line below (`open_line`)
`O` Insert a new task line above (`open_line_above`)
`J` Move task down within its category (`move_down`)
`K` Move task up within its category (`move_up`)
`zc` Fold the current category section (requires `folding`)
`zo` Unfold the current category section (requires `folding`)
@ -338,7 +351,8 @@ old keys to `false`: >lua
*<Plug>(pending-priority)*
<Plug>(pending-priority)
Toggle the priority flag for the task under the cursor.
Cycle the priority level for the task under the cursor (0→1→2→3→0).
The maximum level is controlled by `max_priority` in |pending-config|.
*<Plug>(pending-date)*
<Plug>(pending-date)
@ -356,6 +370,35 @@ old keys to `false`: >lua
<Plug>(pending-filter)
Prompt for filter predicates via |vim.ui.input|.
*<Plug>(pending-category)*
<Plug>(pending-category)
Select a category for the task under the cursor via |vim.ui.select|.
*<Plug>(pending-recur)*
<Plug>(pending-recur)
Prompt for a recurrence pattern for the task under the cursor.
Prefix with `!` for completion mode (e.g. `!weekly`). Empty input
removes recurrence.
*<Plug>(pending-move-down)*
<Plug>(pending-move-down)
Swap the task under the cursor with the one below it. In category
view, movement is limited to tasks within the same category.
*<Plug>(pending-move-up)*
<Plug>(pending-move-up)
Swap the task under the cursor with the one above it.
*<Plug>(pending-wip)*
<Plug>(pending-wip)
Toggle work-in-progress status for the task under the cursor.
If the task is already `wip`, reverts to `pending`.
*<Plug>(pending-blocked)*
<Plug>(pending-blocked)
Toggle blocked status for the task under the cursor.
If the task is already `blocked`, reverts to `pending`.
*<Plug>(pending-open-line)*
<Plug>(pending-open-line)
Insert a correctly-formatted blank task line below the cursor.
@ -414,16 +457,13 @@ Category view (default): ~ *pending-view-category*
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
priority, then by insertion order. The within-category sort order is
configurable via `category.sort` (see |pending-sort|). Category sections
are foldable with `zc` and `zo`.
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 status (wip → pending → blocked →
done), then by priority, then by due date (tasks without a due date sort
last), then by internal order. The sort order is configurable via
`queue.sort` (see |pending-sort|). Category names are shown as
right-aligned virtual
last), then by internal order. 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`.
@ -618,6 +658,7 @@ loads: >lua
date_syntax = 'due',
recur_syntax = 'rec',
someday_date = '9999-12-30',
max_priority = 3,
view = {
default = 'category',
eol_format = '%c %r %d',
@ -645,6 +686,12 @@ loads: >lua
prev_header = '[[',
next_task = ']t',
prev_task = '[t',
category = 'gc',
recur = 'gr',
move_down = 'J',
move_up = 'K',
wip = 'gw',
blocked = 'gb',
},
sync = {
gcal = {},
@ -732,11 +779,6 @@ Fields: ~
in this list appear in the given order;
others are appended after.
{sort} (string|string[], default: 'default')
Sort order within each category. See
|pending-sort| for syntax. The `'default'`
preset is priority → order → id.
{folding} (boolean|table, default: true)
*pending.FoldingConfig*
Controls category-level folds. `true`
@ -751,43 +793,6 @@ Fields: ~
{queue} (table) *pending.QueueViewConfig*
Queue (priority) view settings.
{sort} (string|string[], default: 'default')
Sort order for the queue view. See
|pending-sort| for syntax. The `'default'`
preset is priority → due → order → id.
Sort keys: ~ *pending-sort*
Both `category.sort` and `queue.sort` accept a named
preset string or an ordered list of sort keys.
Presets: ~
`'default'` priority → due → order → id
`'due-first'` due → priority → order → id
`'alphabetical'` description → priority → order → id
`'newest-first'` entry (desc) → priority → order → id
`'recent'` modified (desc) → priority → order → id
Available keys: ~
`'priority'` Higher priority first (descending)
`'due'` Earlier due date first (nil last)
`'status'` Pending before done
`'category'` Alphabetical by category
`'description'` Alphabetical by task text
`'entry'` Oldest creation date first
`'modified'` Oldest modification first
`'order'` Internal insertion order
`'id'` Task creation order
Prefix a key with `-` to flip its default direction
(e.g. `'-due'` for latest-first). `'priority'`
defaults to descending; all others default to
ascending. Implicit `order`, `id` tiebreakers are
appended when absent for stable, deterministic sort.
When `'status'` appears in the key list, the
pending-before-done split is disabled and status
participates as a normal sort field.
Examples: >lua
vim.g.pending = {
view = {
@ -795,12 +800,8 @@ Fields: ~
eol_format = '%d | %r',
category = {
order = { 'Work', 'Personal' },
sort = { 'due', 'priority', 'order' },
folding = { foldtext = '%c: %n items' },
},
queue = {
sort = 'due-first',
},
},
}
<
@ -812,6 +813,15 @@ Fields: ~
See |pending-mappings| for the full list of actions
and their default keys.
{max_priority} (integer, default: 3)
Maximum priority level. The `g!` keymap cycles
through `0 → 1 → … → max_priority → 0`. Priority
levels map to highlight groups: `PendingPriority`
(1), `PendingPriority2` (2), `PendingPriority3`
(3+). `:Pending edit +!!` and `:Pending add +!!!`
accept multi-bang syntax capped at this value.
Set to `1` for the old binary on/off behavior.
{debug} (boolean, default: false)
Enable diagnostic logging. When `true`, textobj
motions, mapping registration, and cursor jumps
@ -899,9 +909,17 @@ PendingBlocked Applied to the checkbox icon and text of blocked tasks.
Default: links to `DiagnosticError`.
*PendingPriority*
PendingPriority Applied to the `! ` priority marker on priority tasks.
PendingPriority Applied to the checkbox icon of priority 1 tasks.
Default: links to `DiagnosticWarn`.
*PendingPriority2*
PendingPriority2 Applied to the checkbox icon of priority 2 tasks.
Default: links to `DiagnosticError`.
*PendingPriority3*
PendingPriority3 Applied to the checkbox icon of priority 3+ tasks.
Default: links to `DiagnosticError`.
*PendingRecur*
PendingRecur Applied to the recurrence indicator virtual text shown
alongside due dates for recurring tasks.
@ -1266,7 +1284,8 @@ Task fields: ~
{status} (string) `'pending'`, `'wip'`, `'blocked'`, `'done'`,
or `'deleted'`.
{category} (string) Category name. Defaults to `default_category`.
{priority} (integer) `1` for priority tasks, `0` otherwise.
{priority} (integer) Priority level: `0` (none), `1``3` (or up to
`max_priority`). Higher values sort first.
{due} (string) ISO date string `YYYY-MM-DD`, or absent.
{recur} (string) Recurrence shorthand (e.g. `weekly`), or absent.
{recur_mode} (string) `'scheduled'` or `'completion'`, or absent.