feat: statusline API, counts, and PendingStatusChanged event (#40)

Problem: no way to know about overdue or due-today tasks without
opening :Pending. No ambient awareness for statusline plugins.

Solution: add counts(), statusline(), and has_due() public API
functions backed by a module-local cache that recomputes after every
store.save() and store.load(). Fire a User PendingStatusChanged event
on every recompute. Extract is_overdue() and is_today() from duplicate
locals into parse.lua as public functions. Refactor views.lua and
init.lua to use the shared date logic. Add vimdoc API section and
integration recipes for lualine, heirline, manual statusline, startup
notification, and event-driven refresh.
This commit is contained in:
Barrett Ruth 2026-02-26 16:30:06 -05:00
parent 92c2c670c5
commit cd1cd1afd4
6 changed files with 507 additions and 69 deletions

View file

@ -513,6 +513,57 @@ Fields: ~
|pending.GcalConfig|. Omit this field entirely to
disable Google Calendar sync.
==============================================================================
LUA API *pending-api*
The following functions are available on `require('pending')` for use in
statuslines, autocmds, and other integrations.
*pending.counts()*
pending.counts()
Returns a table of current task counts: >lua
{
overdue = 2, -- pending tasks past their due date/time
today = 1, -- pending tasks due today (not yet overdue)
pending = 10, -- total pending tasks (all statuses)
priority = 3, -- pending tasks with priority > 0
next_due = "2026-03-01", -- earliest future due date, or nil
}
<
The counts are read from a module-local cache that is invalidated on every
`:w`, toggle, date change, archive, undo, and sync. The first call triggers
a lazy `store.load()` if the store has not been loaded yet.
Done, deleted, and `someday` sentinel-dated tasks are excluded from the
`overdue` and `today` counts. The `someday` sentinel is the value of
`someday_date` in |pending-config| (default `9999-12-30`).
*pending.statusline()*
pending.statusline()
Returns a pre-formatted string suitable for embedding in a statusline:
- `"2 overdue, 1 today"` when both overdue and today counts are non-zero
- `"2 overdue"` when only overdue
- `"1 today"` when only today
- `""` (empty string) when nothing is actionable
*pending.has_due()*
pending.has_due()
Returns `true` when `overdue > 0` or `today > 0`. Useful as a conditional
for statusline components that should only render when tasks need attention.
*PendingStatusChanged*
PendingStatusChanged
A |User| autocmd event fired after every count recomputation. Use this to
trigger statusline refreshes or notifications: >lua
vim.api.nvim_create_autocmd('User', {
pattern = 'PendingStatusChanged',
callback = function()
vim.cmd.redrawstatus()
end,
})
<
==============================================================================
RECIPES *pending-recipes*
@ -526,6 +577,52 @@ Configure blink.cmp to use pending.nvim's omnifunc as a completion source: >lua
})
<
Lualine integration: >lua
require('lualine').setup({
sections = {
lualine_x = {
{
function() return require('pending').statusline() end,
cond = function() return require('pending').has_due() end,
},
},
},
})
<
Heirline integration: >lua
local Pending = {
condition = function() return require('pending').has_due() end,
provider = function() return require('pending').statusline() end,
}
<
Manual statusline: >vim
set statusline+=%{%v:lua.require('pending').statusline()%}
<
Startup notification: >lua
vim.api.nvim_create_autocmd('User', {
pattern = 'PendingStatusChanged',
once = true,
callback = function()
local c = require('pending').counts()
if c.overdue > 0 then
vim.notify(c.overdue .. ' overdue task(s)')
end
end,
})
<
Event-driven statusline refresh: >lua
vim.api.nvim_create_autocmd('User', {
pattern = 'PendingStatusChanged',
callback = function()
vim.cmd.redrawstatus()
end,
})
<
==============================================================================
GOOGLE CALENDAR *pending-gcal*