feat: omnifunc completion, recurring tasks, expanded date syntax (#27)
* feat(config): add recur_syntax and someday_date fields Problem: the plugin needs configuration for the recurrence token name and the sentinel date used by the `later`/`someday` named dates. Solution: add `recur_syntax` (default 'rec') and `someday_date` (default '9999-12-30') to pending.Config and the defaults table. * feat(parse): expand date vocabulary with named dates Problem: the date input only supports today, tomorrow, +Nd, and weekday names, lacking relative offsets like weeks/months, period boundaries, ordinals, month names, and backdating. Solution: add yesterday, eod, sow/eow, som/eom, soq/eoq, soy/eoy, +Nw, +Nm, -Nd, -Nw, ordinals (1st-31st), month names (jan-dec), and later/someday to resolve_date(). Add tests for all new tokens. * feat(recur): add recurrence parsing and next-date computation Problem: the plugin has no concept of recurring tasks, which is needed for habits and repeating deadlines. Solution: add recur.lua with parse(), validate(), next_due(), to_rrule(), and shorthand_list(). Supports named shorthands (daily, weekdays, weekly, etc.), interval notation (Nd, Nw, Nm, Ny), raw RRULE passthrough, and ! prefix for completion-based mode. Includes day-clamping for month/year advancement. * feat(store): add recur and recur_mode task fields Problem: the task schema has no fields for storing recurrence rules. Solution: add recur and recur_mode to the Task class, known_fields, task_to_table, table_to_task, and the add() signature. * feat(parse): add rec: inline token parsing Problem: the buffer parser does not recognize recurrence tokens, so users cannot set recurrence rules inline. Solution: add recur_key() helper and rec: token parsing in body() and command_add(), with ! prefix handling for completion-based mode and validation via recur.validate(). * feat(diff): propagate recurrence through buffer reconciliation Problem: the diff layer does not extract or apply recurrence fields, so rec: tokens written in the buffer are silently ignored on :w. Solution: add rec and rec_mode to ParsedEntry, extract them in parse_buffer(), and pass them through create and update paths in apply(). * feat(init): spawn next task on recurring task completion Problem: completing a recurring task does not create the next occurrence, and :Pending add does not pass recurrence fields. Solution: in toggle_complete(), detect recurrence and spawn a new pending task with the next due date. Wire rec/rec_mode through the add() command path. * feat(views): add recurrence to LineMeta Problem: LineMeta does not carry recurrence info, so the buffer layer cannot display recurrence indicators. Solution: add recur field to LineMeta and populate it in both category_view() and priority_view(). * feat(buffer): add PendingRecur highlight and recurrence virtual text Problem: recurring tasks have no visual indicator in the buffer, and the extmark logic uses a rigid if/elseif chain that does not compose well with additional virtual text fields. Solution: add PendingRecur highlight group linking to DiagnosticInfo. Refactor apply_extmarks() to build virtual text parts dynamically, appending category, recurrence indicator, and due date as separate composable segments. Set omnifunc on the pending buffer. * feat(complete): add omnifunc for cat:, due:, and rec: tokens Problem: the pending buffer has no completion source, requiring users to type metadata tokens from memory. Solution: add complete.lua with an omnifunc that completes cat: tokens from existing categories, due: tokens from the named date vocabulary, and rec: tokens from recurrence shorthands. * docs: document recurrence, expanded dates, omnifunc, new config Problem: the vimdoc does not cover recurrence, expanded date syntax, omnifunc completion, or the new config fields. Solution: add DATE INPUT and RECURRENCE sections, update INLINE METADATA, COMMANDS, CONFIGURATION, HIGHLIGHT GROUPS, HEALTH CHECK, and DATA FORMAT. Expand the help popup with recurrence patterns and new date tokens. Add recurrence validation to healthcheck. * ci: fix * fix(recur): resolve LuaLS type errors Problem: LuaLS reported undefined-field for `_raw` on RecurSpec and param-type-mismatch for `last_day.day` in `advance_date` because `osdate.day` infers as `string|integer`. Solution: Add `_raw` to the RecurSpec class annotation and cast `last_day.day` to integer in both `math.min` call sites. * refactor(init): remove help popup, use config-driven keymaps Problem: Buffer-local keymaps were hardcoded with no way for users to customize them. The g? help popup duplicated information already in the vimdoc. Solution: Remove show_help() and the g? mapping. Refactor _setup_buf_mappings to read from cfg.keymaps, letting users override or disable any buffer-local binding via vim.g.pending. * feat(config): add keymaps table for buffer-local bindings Problem: Users had no way to customize or disable buffer-local key bindings in the pending buffer. Solution: Add a pending.Keymaps class and keymaps field to pending.Config with defaults for all eight buffer actions. Setting any key to false disables that binding. * feat(plugin): add Plug mappings for all buffer actions Problem: Only five of nine buffer actions had <Plug> mappings, so users could not bind close, undo, open-line, or open-line-above globally. Solution: Add <Plug>(pending-close), <Plug>(pending-undo), <Plug>(pending-open-line), and <Plug>(pending-open-line-above). * docs: update mappings and config for keymaps and new Plug entries Problem: Vimdoc still listed g? help popup, lacked documentation for the four new <Plug> mappings, and had no keymaps config section. Solution: Remove g? from mappings table, document all nine <Plug> mappings, add keymaps table to the config example and field reference, and note that buffer-local keys are configurable.
This commit is contained in:
parent
6911c091f6
commit
7d93c4bb45
18 changed files with 1536 additions and 134 deletions
178
doc/pending.txt
178
doc/pending.txt
|
|
@ -30,13 +30,16 @@ concealed tokens and are never visible during editing.
|
|||
|
||||
Features: ~
|
||||
- Oil-style buffer editing: standard Vim motions for all task operations
|
||||
- Inline metadata syntax: `due:` and `cat:` tokens parsed on `:w`
|
||||
- Relative date input: `today`, `tomorrow`, `+Nd`, weekday names
|
||||
- Inline metadata syntax: `due:`, `cat:`, and `rec:` tokens parsed on `:w`
|
||||
- Relative date input: `today`, `tomorrow`, `+Nd`, `+Nw`, `+Nm`, weekday
|
||||
names, month names, ordinals, and more
|
||||
- Recurring tasks with automatic next-date spawning on completion
|
||||
- Two views: category (default) and priority flat list
|
||||
- Multi-level undo (up to 20 `:w` saves, session-only)
|
||||
- Quick-add from the command line with `:Pending add`
|
||||
- Quickfix list of overdue/due-today tasks via `:Pending due`
|
||||
- Foldable category sections (`zc`/`zo`) in category view
|
||||
- Omnifunc completion for `cat:`, `due:`, and `rec:` tokens (`<C-x><C-o>`)
|
||||
- Google Calendar one-way push via OAuth PKCE
|
||||
|
||||
==============================================================================
|
||||
|
|
@ -95,20 +98,18 @@ parsed from the right and consumed until a non-metadata token is reached.
|
|||
Supported tokens: ~
|
||||
|
||||
`due:YYYY-MM-DD` Set a due date using an absolute date.
|
||||
`due:today` Resolve to today's date.
|
||||
`due:tomorrow` Resolve to tomorrow's date.
|
||||
`due:+Nd` Resolve to N days from today (e.g. `due:+3d`).
|
||||
`due:mon` Resolve to the next occurrence of that weekday.
|
||||
Supported: `sun` `mon` `tue` `wed` `thu` `fri` `sat`
|
||||
`due:<name>` Resolve a named date (see |pending-dates| below).
|
||||
`cat:Name` Move the task to the named category on save.
|
||||
`rec:<pattern>` Set a recurrence rule (see |pending-recurrence|).
|
||||
|
||||
The token name for due dates defaults to `due` and is configurable via
|
||||
`date_syntax` in |pending-config|. If `date_syntax` is set to `by`, write
|
||||
`by:2026-03-15` instead.
|
||||
`date_syntax` in |pending-config|. The token name for recurrence defaults to
|
||||
`rec` and is configurable via `recur_syntax`.
|
||||
|
||||
Example: >
|
||||
|
||||
Buy milk due:2026-03-15 cat:Errands
|
||||
Take out trash due:monday rec:weekly
|
||||
<
|
||||
|
||||
On `:w`, the description becomes `Buy milk`, the due date is stored as
|
||||
|
|
@ -116,8 +117,87 @@ On `:w`, the description becomes `Buy milk`, the due date is stored as
|
|||
placed under the `Errands` category header.
|
||||
|
||||
Parsing stops at the first token that is not a recognised metadata token.
|
||||
Repeated tokens of the same type also stop parsing — only one `due:` and one
|
||||
`cat:` per task line are consumed.
|
||||
Repeated tokens of the same type also stop parsing — only one `due:`, one
|
||||
`cat:`, and one `rec:` per task line are consumed.
|
||||
|
||||
Omnifunc completion is available for all three token types. In insert mode,
|
||||
type `due:`, `cat:`, or `rec:` and press `<C-x><C-o>` to see suggestions.
|
||||
|
||||
==============================================================================
|
||||
DATE INPUT *pending-dates*
|
||||
|
||||
Named dates can be used anywhere a date is accepted: the `due:` inline
|
||||
token, the `D` prompt, and `:Pending add`.
|
||||
|
||||
Token Resolves to ~
|
||||
----- -----------
|
||||
`today` Today's date
|
||||
`tomorrow` Tomorrow's date
|
||||
`yesterday` Yesterday's date
|
||||
`eod` Today (end of day semantics)
|
||||
`+Nd` N days from today (e.g. `+3d`)
|
||||
`+Nw` N weeks from today (e.g. `+2w`)
|
||||
`+Nm` N months from today (e.g. `+1m`)
|
||||
`-Nd` N days ago (e.g. `-2d`)
|
||||
`-Nw` N weeks ago (e.g. `-1w`)
|
||||
`mon`–`sun` Next occurrence of that weekday
|
||||
`jan`–`dec` 1st of next occurrence of that month
|
||||
`1st`–`31st` Next occurrence of that day-of-month
|
||||
`sow` / `eow` Monday / Sunday of current week
|
||||
`som` / `eom` First / last day of current month
|
||||
`soq` / `eoq` First / last day of current quarter
|
||||
`soy` / `eoy` January 1 / December 31 of current year
|
||||
`later` / `someday` Sentinel date (default: `9999-12-30`)
|
||||
|
||||
==============================================================================
|
||||
RECURRENCE *pending-recurrence*
|
||||
|
||||
Tasks can recur on a schedule. Add a `rec:` token to set recurrence: >
|
||||
|
||||
- [ ] Take out trash due:monday rec:weekly
|
||||
- [ ] Pay rent due:2026-03-01 rec:monthly
|
||||
- [ ] Standup due:tomorrow rec:weekdays
|
||||
<
|
||||
|
||||
When a recurring task is marked done with `<CR>`:
|
||||
1. The current task stays as done (preserving history).
|
||||
2. A new pending task is created with the same description, category,
|
||||
priority, and recurrence — with the due date advanced to the next
|
||||
occurrence.
|
||||
|
||||
Shorthand patterns: ~
|
||||
|
||||
Pattern Meaning ~
|
||||
------- -------
|
||||
`daily` Every day
|
||||
`weekdays` Monday through Friday
|
||||
`weekly` Every week
|
||||
`biweekly` Every 2 weeks (alias: `2w`)
|
||||
`monthly` Every month
|
||||
`quarterly` Every 3 months (alias: `3m`)
|
||||
`yearly` Every year (alias: `annual`)
|
||||
`Nd` Every N days (e.g. `3d`)
|
||||
`Nw` Every N weeks (e.g. `2w`)
|
||||
`Nm` Every N months (e.g. `6m`)
|
||||
`Ny` Every N years (e.g. `2y`)
|
||||
|
||||
For patterns the shorthand cannot express, use a raw RRULE fragment: >
|
||||
rec:FREQ=MONTHLY;BYDAY=1MO
|
||||
<
|
||||
|
||||
Completion-based recurrence: ~ *pending-recur-completion*
|
||||
By default, recurrence is schedule-based: the next due date advances from the
|
||||
original schedule, skipping to the next future occurrence. Prefix the pattern
|
||||
with `!` for completion-based mode, where the next due date advances from the
|
||||
completion date: >
|
||||
rec:!weekly
|
||||
<
|
||||
Schedule-based is like org-mode `++`; completion-based is like `.+`.
|
||||
|
||||
Google Calendar: ~
|
||||
Recurrence patterns map directly to iCalendar RRULE strings for future GCal
|
||||
sync support. Completion-based recurrence cannot be synced (it is inherently
|
||||
local).
|
||||
|
||||
==============================================================================
|
||||
COMMANDS *pending-commands*
|
||||
|
|
@ -135,6 +215,7 @@ COMMANDS *pending-commands*
|
|||
:Pending add Buy groceries due:2026-03-15
|
||||
:Pending add School: Submit homework
|
||||
:Pending add Errands: Pick up dry cleaning due:fri
|
||||
:Pending add Work: standup due:tomorrow rec:weekdays
|
||||
<
|
||||
If the buffer is currently open it is re-rendered after the add.
|
||||
|
||||
|
|
@ -169,27 +250,34 @@ MAPPINGS *pending-mappings*
|
|||
The following keys are set buffer-locally when the task buffer opens. They
|
||||
are active only in the `pending://` buffer.
|
||||
|
||||
Buffer-local keys: ~
|
||||
Buffer-local keys are configured via the `keymaps` table in |pending-config|.
|
||||
The defaults are shown below. Set any key to `false` to disable it.
|
||||
|
||||
Default buffer-local keys: ~
|
||||
|
||||
Key Action ~
|
||||
------- ------------------------------------------------
|
||||
`<CR>` Toggle complete / uncomplete the task at cursor
|
||||
`!` Toggle the priority flag on the task at cursor
|
||||
`D` Prompt for a due date on the task at cursor
|
||||
`<Tab>` Switch between category view and priority view
|
||||
`U` Undo the last `:w` save
|
||||
`g?` Show a help popup with available keys
|
||||
`q` Close the task buffer (`close`)
|
||||
`<CR>` Toggle complete / uncomplete (`toggle`)
|
||||
`!` Toggle the priority flag (`priority`)
|
||||
`D` Prompt for a due date (`date`)
|
||||
`<Tab>` Switch between category / priority view (`view`)
|
||||
`U` 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`)
|
||||
`zc` Fold the current category section (category view only)
|
||||
`zo` Unfold the current category section (category view only)
|
||||
|
||||
`o` and `O` are overridden to insert a correctly-formatted blank task line
|
||||
at the position below or above the cursor rather than using standard Vim
|
||||
indentation. `dd`, `p`, `P`, and `:w` work as expected.
|
||||
`dd`, `p`, `P`, and `:w` work as standard Vim operations.
|
||||
|
||||
*<Plug>(pending-open)*
|
||||
<Plug>(pending-open)
|
||||
Open the task buffer. Maps to |:Pending| with no arguments.
|
||||
|
||||
*<Plug>(pending-close)*
|
||||
<Plug>(pending-close)
|
||||
Close the task buffer window.
|
||||
|
||||
*<Plug>(pending-toggle)*
|
||||
<Plug>(pending-toggle)
|
||||
Toggle complete / uncomplete for the task under the cursor.
|
||||
|
|
@ -206,6 +294,18 @@ indentation. `dd`, `p`, `P`, and `:w` work as expected.
|
|||
<Plug>(pending-view)
|
||||
Switch between category view and priority view.
|
||||
|
||||
*<Plug>(pending-undo)*
|
||||
<Plug>(pending-undo)
|
||||
Undo the last `:w` save.
|
||||
|
||||
*<Plug>(pending-open-line)*
|
||||
<Plug>(pending-open-line)
|
||||
Insert a correctly-formatted blank task line below the cursor.
|
||||
|
||||
*<Plug>(pending-open-line-above)*
|
||||
<Plug>(pending-open-line-above)
|
||||
Insert a correctly-formatted blank task line above the cursor.
|
||||
|
||||
Example configuration: >lua
|
||||
vim.keymap.set('n', '<leader>t', '<Plug>(pending-open)')
|
||||
vim.keymap.set('n', '<leader>T', '<Plug>(pending-toggle)')
|
||||
|
|
@ -242,7 +342,19 @@ loads: >lua
|
|||
default_category = 'Inbox',
|
||||
date_format = '%b %d',
|
||||
date_syntax = 'due',
|
||||
recur_syntax = 'rec',
|
||||
someday_date = '9999-12-30',
|
||||
category_order = {},
|
||||
keymaps = {
|
||||
close = 'q',
|
||||
toggle = '<CR>',
|
||||
view = '<Tab>',
|
||||
priority = '!',
|
||||
date = 'D',
|
||||
undo = 'U',
|
||||
open_line = 'o',
|
||||
open_line_above = 'O',
|
||||
},
|
||||
gcal = {
|
||||
calendar = 'Tasks',
|
||||
credentials_path = '/path/to/client_secret.json',
|
||||
|
|
@ -278,12 +390,28 @@ Fields: ~
|
|||
this to use a different keyword, for example `'by'`
|
||||
to write `by:2026-03-15` instead of `due:2026-03-15`.
|
||||
|
||||
{recur_syntax} (string, default: 'rec')
|
||||
The token name for inline recurrence metadata. Change
|
||||
this to use a different keyword, for example
|
||||
`'repeat'` to write `repeat:weekly`.
|
||||
|
||||
{someday_date} (string, default: '9999-12-30')
|
||||
The date that `later` and `someday` resolve to. This
|
||||
acts as a "no date" sentinel for GTD-style workflows.
|
||||
|
||||
{category_order} (string[], default: {})
|
||||
Ordered list of category names. In category view,
|
||||
categories that appear in this list are shown in the
|
||||
given order. Categories not in the list are appended
|
||||
after the ordered ones in their natural order.
|
||||
|
||||
{keymaps} (table, default: see below) *pending.Keymaps*
|
||||
Buffer-local key bindings. Each field maps an action
|
||||
name to a key string. Set a field to `false` to
|
||||
disable that binding. Unset fields use the default.
|
||||
See |pending-mappings| for the full list of actions
|
||||
and their default keys.
|
||||
|
||||
{gcal} (table, default: nil)
|
||||
Google Calendar sync configuration. See
|
||||
|pending.GcalConfig|. Omit this field entirely to
|
||||
|
|
@ -371,6 +499,11 @@ PendingDone Applied to the text of completed tasks.
|
|||
PendingPriority Applied to the `! ` priority marker on priority tasks.
|
||||
Default: links to `DiagnosticWarn`.
|
||||
|
||||
*PendingRecur*
|
||||
PendingRecur Applied to the recurrence indicator virtual text shown
|
||||
alongside due dates for recurring tasks.
|
||||
Default: links to `DiagnosticInfo`.
|
||||
|
||||
To override a group in your colorscheme or config: >lua
|
||||
vim.api.nvim_set_hl(0, 'PendingDue', { fg = '#aaaaaa', italic = true })
|
||||
<
|
||||
|
|
@ -388,6 +521,7 @@ Checks performed: ~
|
|||
category, date format, date syntax)
|
||||
- Whether the data directory exists (warning if not yet created)
|
||||
- Whether the data file exists and can be parsed; reports total task count
|
||||
- Validates recurrence specs on stored tasks
|
||||
- Whether `curl` is available (required for Google Calendar sync)
|
||||
- Whether `openssl` is available (required for OAuth PKCE)
|
||||
|
||||
|
|
@ -414,6 +548,8 @@ Task fields: ~
|
|||
{category} (string) Category name. Defaults to `default_category`.
|
||||
{priority} (integer) `1` for priority tasks, `0` otherwise.
|
||||
{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.
|
||||
{entry} (string) ISO 8601 UTC timestamp of creation.
|
||||
{modified} (string) ISO 8601 UTC timestamp of last modification.
|
||||
{end} (string) ISO 8601 UTC timestamp of completion or deletion.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue