pending.nvim/doc/pending.txt
Barrett Ruth 586a8e81e3 docs: update vimdoc to reflect current feature set
Problem: doc/pending.txt was written before the undo stack, folds,
:Pending due, D mapping, and BufEnter reload were added. Several
entries were factually wrong (single-level undo, d vs D key,
:Pending undo listed as non-existent) and highlight group defaults
referenced stale hex colours.

Solution: correct all factual errors and add missing entries —
:Pending due command, :Pending undo command, zc/zo fold mappings,
PendingOverdue highlight group, semantic link defaults for all groups,
category fold docs, BufEnter auto-reload note, and multi-level undo
description.
2026-02-24 19:56:38 -05:00

432 lines
19 KiB
Text

*pending.txt* Buffer-centric task management for Neovim
Author: Barrett Ruth <br.barrettruth@gmail.com>
License: MIT
==============================================================================
INTRODUCTION *pending.nvim*
pending.nvim is a buffer-centric task manager for Neovim. Tasks live in a
plain, editable buffer — add with `o`, delete with `dd`, reorder with
`dd`/`p`, rename by typing. Writing the buffer with `:w` computes a diff
against the JSON store and applies only the changes. No floating windows,
no special UI, no abstraction between you and your tasks.
The buffer looks like this: >
School
! Read chapter 5 Feb 28
Submit homework Feb 25
Errands
Buy groceries Mar 01
Clean apartment
<
Category headers sit at column 0. Tasks are indented two spaces below them.
`!` marks a priority task. Due dates appear as right-aligned virtual text.
Completed tasks are rendered with strikethrough. Task IDs are embedded as
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
- 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
- Google Calendar one-way push via OAuth PKCE
==============================================================================
REQUIREMENTS *pending-requirements*
- Neovim 0.10+
- No external dependencies for local use
- `curl` and `openssl` are required for Google Calendar sync
==============================================================================
INSTALL *pending-install*
Install with lazy.nvim: >lua
{ 'barrettruth/pending.nvim' }
<
Install with luarocks: >vim
luarocks install pending.nvim
<
No `setup()` call is needed. The plugin loads automatically and works with
defaults. To customize behavior, set |vim.g.pending| before the plugin loads.
See |pending-config|.
==============================================================================
USAGE *pending-usage*
Open the task buffer: >vim
:Pending
<
The buffer named `pending://` opens in the current window. From there, use
standard Vim editing:
- `o` / `O` to add a new task line under or above the cursor
- `dd` to remove a task (deletion is applied on `:w`)
- `dd` + `p` to reorder tasks (pasted tasks receive new IDs)
- `:w` to save — all additions, deletions, and edits are diffed against the
store and committed atomically
Buffer-local keys are set automatically when the buffer opens. See
|pending-mappings| for the full list.
The buffer uses `buftype=acwrite` so `:w` always routes through pending.nvim's
write handler rather than writing to disk directly. The `pending://` buffer
persists across window switches; reopening with `:Pending` focuses the
existing window if one is open. The buffer is automatically reloaded from
disk when entered unmodified.
==============================================================================
INLINE METADATA *pending-metadata*
Metadata tokens may be appended to any task line before saving. Tokens are
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`
`cat:Name` Move the task to the named category on save.
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.
Example: >
Buy milk due:2026-03-15 cat:Errands
<
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
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.
==============================================================================
COMMANDS *pending-commands*
*:Pending*
:Pending
Open the task buffer. If the buffer is already displayed in a window,
focus that window. Equivalent to |<Plug>(pending-open)|.
*:Pending-add*
:Pending add {text}
Quick-add a task without opening the buffer. Inline metadata tokens in
{text} are parsed exactly as they are in the buffer. A `Category: ` prefix
(uppercase first letter, colon, space) assigns the category directly: >vim
:Pending add Buy groceries due:2026-03-15
:Pending add School: Submit homework
:Pending add Errands: Pick up dry cleaning due:fri
<
If the buffer is currently open it is re-rendered after the add.
*:Pending-archive*
:Pending archive [{days}]
Permanently remove done and deleted tasks whose completion timestamp is
older than {days} days. {days} defaults to 30 if not provided. >vim
:Pending archive " remove tasks completed more than 30 days ago
:Pending archive 7 " remove tasks completed more than 7 days ago
<
*:Pending-due*
:Pending due
Populate the quickfix list with all tasks that are overdue or due today.
Open the list with |:copen| to navigate to each task's category.
*:Pending-sync*
:Pending sync
Push pending tasks that have a due date to Google Calendar as all-day
events. Requires |pending-gcal| to be configured. See |pending-gcal| for
full details on what gets created, updated, and deleted.
*:Pending-undo*
:Pending undo
Undo the last `:w` save, restoring the task store to its previous state.
Equivalent to the `U` buffer-local key (see |pending-mappings|). Up to 20
levels of undo are retained per session.
==============================================================================
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: ~
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
`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.
*<Plug>(pending-open)*
<Plug>(pending-open)
Open the task buffer. Maps to |:Pending| with no arguments.
*<Plug>(pending-toggle)*
<Plug>(pending-toggle)
Toggle complete / uncomplete for the task under the cursor.
*<Plug>(pending-priority)*
<Plug>(pending-priority)
Toggle the priority flag for the task under the cursor.
*<Plug>(pending-date)*
<Plug>(pending-date)
Prompt for a due date for the task under the cursor.
*<Plug>(pending-view)*
<Plug>(pending-view)
Switch between category view and priority view.
Example configuration: >lua
vim.keymap.set('n', '<leader>t', '<Plug>(pending-open)')
vim.keymap.set('n', '<leader>T', '<Plug>(pending-toggle)')
<
==============================================================================
VIEWS *pending-views*
Two views are available. Switch with `<Tab>` or |<Plug>(pending-view)|.
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,
pending tasks appear before done tasks. Priority tasks (`!`) are sorted
first within each group. Category sections are foldable with `zc` and
`zo`.
Priority view: ~ *pending-view-priority*
A flat list of all tasks sorted by priority, then by due date (tasks
without a due date sort last), then by internal order. Done tasks appear
after all pending tasks. Category names are shown as right-aligned virtual
text alongside the due date virtual text so tasks remain identifiable
across categories.
==============================================================================
CONFIGURATION *pending-config*
Configuration is done via `vim.g.pending`. Set this before the plugin
loads: >lua
vim.g.pending = {
data_path = vim.fn.stdpath('data') .. '/pending/tasks.json',
default_view = 'category',
default_category = 'Inbox',
date_format = '%b %d',
date_syntax = 'due',
category_order = {},
gcal = {
calendar = 'Tasks',
credentials_path = '/path/to/client_secret.json',
},
}
<
All fields are optional. Unset fields use the defaults shown above.
*pending.Config*
Fields: ~
{data_path} (string)
Path to the JSON file where tasks are stored.
Default: `stdpath('data') .. '/pending/tasks.json'`.
The directory is created automatically on first save.
{default_view} ('category'|'priority', default: 'category')
The view to use when the buffer is opened for the
first time in a session.
{default_category} (string, default: 'Inbox')
Category assigned to new tasks when no `cat:` token
is present and no `Category: ` prefix is used with
`:Pending add`.
{date_format} (string, default: '%b %d')
strftime format string used to render due dates as
virtual text in the buffer. Examples: `'%Y-%m-%d'`
for ISO dates, `'%d %b'` for day-first.
{date_syntax} (string, default: 'due')
The token name for inline due-date metadata. Change
this to use a different keyword, for example `'by'`
to write `by:2026-03-15` instead of `due:2026-03-15`.
{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.
{gcal} (table, default: nil)
Google Calendar sync configuration. See
|pending.GcalConfig|. Omit this field entirely to
disable Google Calendar sync.
==============================================================================
GOOGLE CALENDAR *pending-gcal*
pending.nvim can push tasks with due dates to a dedicated Google Calendar as
all-day events. This is a one-way push; changes made in Google Calendar are
not pulled back into pending.nvim.
Configuration: >lua
vim.g.pending = {
gcal = {
calendar = 'Tasks',
credentials_path = '/path/to/client_secret.json',
},
}
<
*pending.GcalConfig*
Fields: ~
{calendar} (string, default: 'Pendings')
Name of the Google Calendar to sync to. If a calendar
with this name does not exist it is created
automatically on the first sync.
{credentials_path} (string)
Path to the OAuth client secret JSON file downloaded
from the Google Cloud Console. Default:
`stdpath('data')..'/pending/gcal_credentials.json'`.
The file may be in the `installed` wrapper format
that Google provides or as a bare credentials object.
OAuth flow: ~
On the first `:Pending sync` call the plugin detects that no refresh token
exists and opens the Google authorization URL in the browser using
|vim.ui.open()|. A temporary local HTTP server listens on port 18392 for the
OAuth redirect. The PKCE (Proof Key for Code Exchange) flow is used —
`openssl` generates the code challenge. After the user grants consent, the
authorization code is exchanged for tokens and the refresh token is stored at
`stdpath('data')/pending/gcal_tokens.json` with mode `600`. Subsequent syncs
use the stored refresh token and refresh the access token automatically when
it is about to expire.
`:Pending sync` behavior: ~
For each task in the store:
- A pending task with a due date and no existing event: a new all-day event is
created and the event ID is stored in the task's `_extra` table.
- A pending task with a due date and an existing event: the event summary and
date are updated in place.
- A done or deleted task with an existing event: the event is deleted.
- A pending task with no due date that had an existing event: the event is
deleted.
A summary notification is shown after sync: `created: N, updated: N,
deleted: N`.
==============================================================================
HIGHLIGHT GROUPS *pending-highlights*
pending.nvim defines the following highlight groups. All groups are set with
`default`, so colorschemes can override them by defining the group without
`default` before or after the plugin loads.
*PendingHeader*
PendingHeader Applied to category header lines (text at column 0).
Default: links to `Title`.
*PendingDue*
PendingDue Applied to the due date virtual text shown at the right
margin of each task line.
Default: links to `DiagnosticHint`.
*PendingOverdue*
PendingOverdue Applied to the due date virtual text of overdue tasks.
Default: links to `DiagnosticError`.
*PendingDone*
PendingDone Applied to the text of completed tasks.
Default: links to `Comment`.
*PendingPriority*
PendingPriority Applied to the `! ` priority marker on priority tasks.
Default: links to `DiagnosticWarn`.
To override a group in your colorscheme or config: >lua
vim.api.nvim_set_hl(0, 'PendingDue', { fg = '#aaaaaa', italic = true })
<
==============================================================================
HEALTH CHECK *pending-health*
Run |:checkhealth| pending to verify your setup: >vim
:checkhealth pending
<
Checks performed: ~
- Config loads without error
- Reports active configuration values (data path, default view, default
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
- Whether `curl` is available (required for Google Calendar sync)
- Whether `openssl` is available (required for OAuth PKCE)
==============================================================================
DATA FORMAT *pending-data*
Tasks are stored as JSON at `data_path`. The file is safe to edit by hand and
is forward-compatible — unknown fields are preserved on every read/write cycle
via the `_extra` table.
Schema: >
{
"version": 1,
"next_id": 42,
"tasks": [ ... ]
}
<
Task fields: ~
{id} (integer) Unique, auto-incrementing task identifier.
{description} (string) Task text as shown in the buffer.
{status} (string) `'pending'`, `'done'`, or `'deleted'`.
{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.
{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.
{order} (integer) Relative ordering within a category.
Any field not in the list above is preserved in `_extra` and written back on
save. This is used internally to store the Google Calendar event ID
(`_gcal_event_id`) and allows third-party tooling to annotate tasks without
data loss.
The `version` field is checked on load. If the file version is newer than the
version the plugin supports, loading is aborted with an error message asking
you to update the plugin.
==============================================================================
vim:tw=78:ts=8:ft=help:norl: