* feat(config): add category_order field Problem: category display order was always insertion order with no way to configure it. Solution: add category_order to config defaults so users can declare a preferred category ordering; unspecified categories append after. * feat(parse): add relative date resolution Problem: due dates required full YYYY-MM-DD input, adding friction for common cases like "today" or "next monday". Solution: add resolve_date() supporting today, tomorrow, +Nd, and weekday abbreviations; extend inline token parsing to resolve relative values before falling back to strict date validation. * feat(views): overdue flag, category in priority view, category ordering Problem: overdue tasks were visually indistinct from upcoming ones; priority view had no category context; category display order was not configurable. Solution: compute overdue meta flag for pending tasks past their due date; set show_category on priority view task meta; reorder categories according to config.category_order when present. * feat(buffer): overdue highlight, category virt text in priority view Problem: overdue tasks had no visual distinction; priority view showed no category context alongside due dates. Solution: add PendingOverdue highlight group; render category name as right-aligned virtual text in priority view, composited with the due date when both are present. * feat(init): undo write and buffer-local default mappings Problem: _undo_state was captured on every save but never consumed; toggle_priority and prompt_date had no buffer-local defaults, requiring manual <Plug> configuration. Solution: implement undo_write() to restore pre-save task state; add !, d, and U as buffer-local defaults following fugitive's philosophy of owning the buffer; expose :Pending undo as a command alias. * test(views): add views spec Problem: views.lua had no test coverage. Solution: add 26 tests covering category_view and priority_view including sort order, line format, overdue detection, show_category meta, and category_order config behavior. * test(archive): add archive spec Problem: archive had no test coverage. Solution: add 9 tests covering cutoff logic, custom day counts, pending task preservation, deleted task cleanup, and notify output. * docs: add vimdoc Problem: no :help documentation existed. Solution: add doc/pending.txt covering all features — commands, mappings, views, configuration, Google Calendar sync, highlight groups, data format, and health check — following standard vimdoc conventions. * ci: format * fix: resolve lint and type check errors Problem: selene flagged unused variables in new spec files; LuaLS flagged os.date/os.time return type mismatches, integer? assignments, and stale task.Task/task.GcalConfig type references. Solution: prefix unused spec variables with _ or drop unnecessary assignments; add --[[@as string/integer]] casts for os.date and os.time calls; add category_order field to pending.Config annotation; fix task.GcalConfig -> pending.GcalConfig and task.Task[] -> pending.Task[]; add nil guards on meta[row].id before store calls; cast store.data() return to non-optional. * ci: format * fix: sync * ci: format |
||
|---|---|---|
| .github | ||
| doc | ||
| lua/pending | ||
| plugin | ||
| spec | ||
| syntax | ||
| .busted | ||
| .editorconfig | ||
| .gitignore | ||
| .luarc.json | ||
| .pre-commit-config.yaml | ||
| flake.lock | ||
| flake.nix | ||
| LICENSE | ||
| pending.nvim-scm-1.rockspec | ||
| README.md | ||
| selene.toml | ||
| stylua.toml | ||
| vim.yaml | ||
pending.nvim
Edit tasks like text. :w saves them.
A buffer-centric task manager for Neovim. Tasks live in a plain buffer — add
with o, delete with dd, reorder with dd/p, rename by editing. Write the
buffer and the diff is computed against a JSON store. No UI chrome, no floating
windows, no abstractions between you and your tasks.
How it works
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 below them. ! marks
priority. Due dates appear as right-aligned virtual text. Done tasks get
strikethrough. Everything you see is editable buffer text — the IDs are
concealed, and metadata is parsed from inline syntax on save.
Install
luarocks install pending.nvim
lazy.nvim:
{ 'barrettruth/pending.nvim' }
Requires Neovim 0.10+. No external dependencies for local use. Google Calendar
sync requires curl and openssl.
Usage
:Pending opens the task buffer. From there, it's just vim:
| Key | Action |
|---|---|
o / O |
Add a new task |
dd |
Delete a task (on :w) |
p |
Paste (duplicates get new IDs) |
:w |
Save all changes |
<CR> |
Toggle complete (immediate) |
<Tab> |
Switch category / priority view |
g? |
Show keybind help |
Inline metadata
Type metadata tokens at the end of a task line before saving:
Buy milk due:2026-03-15 cat:Errands
On :w, the date and category are extracted. The description becomes Buy milk,
the due date renders as virtual text, and the task moves under the Errands
header.
Quick add
:Pending add Buy groceries due:2026-03-15
:Pending add School: Submit homework
Archive
:Pending archive " purge done tasks older than 30 days
:Pending archive 7 " purge done tasks older than 7 days
Configuration
No setup() call required. Set vim.g.pending before the plugin loads:
vim.g.pending = {
data_path = vim.fn.stdpath('data') .. '/pending/tasks.json',
default_view = 'category', -- 'category' or 'priority'
default_category = 'Inbox',
date_format = '%b %d', -- strftime format for virtual text
date_syntax = 'due', -- inline token name (e.g. 'by' for by:2026-03-15)
}
All fields are optional. Absent keys use the defaults shown above.
Google Calendar sync
One-way push of tasks with due dates to a dedicated Google Calendar as all-day events.
vim.g.pending = {
gcal = {
calendar = 'Tasks',
credentials_path = '/path/to/client_secret.json',
},
}
:Pending sync
On first run, a browser window opens for OAuth consent. The refresh token is
stored at stdpath('data')/pending/gcal_tokens.json. Completed or deleted tasks
have their calendar events removed. Due date changes update events in place.
Mappings
The plugin defines <Plug> mappings for custom keybinds:
vim.keymap.set('n', '<leader>t', '<Plug>(pending-open)')
vim.keymap.set('n', '<leader>T', '<Plug>(pending-toggle)')
| Plug mapping | Action |
|---|---|
<Plug>(pending-open) |
Open task buffer |
<Plug>(pending-toggle) |
Toggle complete |
<Plug>(pending-view) |
Switch view |
<Plug>(pending-priority) |
Toggle priority flag |
<Plug>(pending-date) |
Prompt for due date |
Data format
Tasks are stored as JSON at stdpath('data')/pending/tasks.json. The schema is
versioned and forward-compatible — unknown fields are preserved on round-trip.
Documentation
:checkhealth pending