feat: overdue highlighting, relative dates, undo write, buffer mappings (#1)
* 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
This commit is contained in:
parent
7b97e9b840
commit
3a35fab6cf
11 changed files with 1137 additions and 30 deletions
|
|
@ -41,6 +41,15 @@ function M._setup_buf_mappings(bufnr)
|
|||
vim.keymap.set('n', 'g?', function()
|
||||
M.show_help()
|
||||
end, opts)
|
||||
vim.keymap.set('n', '!', function()
|
||||
M.toggle_priority()
|
||||
end, opts)
|
||||
vim.keymap.set('n', 'd', function()
|
||||
M.prompt_date()
|
||||
end, opts)
|
||||
vim.keymap.set('n', 'U', function()
|
||||
M.undo_write()
|
||||
end, opts)
|
||||
end
|
||||
|
||||
---@param bufnr integer
|
||||
|
|
@ -51,6 +60,17 @@ function M._on_write(bufnr)
|
|||
buffer.render(bufnr)
|
||||
end
|
||||
|
||||
function M.undo_write()
|
||||
if not _undo_state then
|
||||
vim.notify('Nothing to undo.', vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
store.replace_tasks(_undo_state)
|
||||
store.save()
|
||||
_undo_state = nil
|
||||
buffer.render(buffer.bufnr())
|
||||
end
|
||||
|
||||
function M.toggle_complete()
|
||||
local bufnr = buffer.bufnr()
|
||||
if not bufnr then
|
||||
|
|
@ -62,6 +82,9 @@ function M.toggle_complete()
|
|||
return
|
||||
end
|
||||
local id = meta[row].id
|
||||
if not id then
|
||||
return
|
||||
end
|
||||
local task = store.get(id)
|
||||
if not task then
|
||||
return
|
||||
|
|
@ -86,6 +109,9 @@ function M.toggle_priority()
|
|||
return
|
||||
end
|
||||
local id = meta[row].id
|
||||
if not id then
|
||||
return
|
||||
end
|
||||
local task = store.get(id)
|
||||
if not task then
|
||||
return
|
||||
|
|
@ -107,13 +133,19 @@ function M.prompt_date()
|
|||
return
|
||||
end
|
||||
local id = meta[row].id
|
||||
if not id then
|
||||
return
|
||||
end
|
||||
vim.ui.input({ prompt = 'Due date (YYYY-MM-DD): ' }, function(input)
|
||||
if not input then
|
||||
return
|
||||
end
|
||||
local due = input ~= '' and input or nil
|
||||
if due then
|
||||
if not due:match('^%d%d%d%d%-%d%d%-%d%d$') then
|
||||
local resolved = parse.resolve_date(due)
|
||||
if resolved then
|
||||
due = resolved
|
||||
elseif not due:match('^%d%d%d%d%-%d%d%-%d%d$') then
|
||||
vim.notify('Invalid date format. Use YYYY-MM-DD.', vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
|
@ -170,12 +202,12 @@ function M.archive(days)
|
|||
local y, mo, d, h, mi, s = task['end']:match('^(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+)Z$')
|
||||
if y then
|
||||
local t = os.time({
|
||||
year = tonumber(y),
|
||||
month = tonumber(mo),
|
||||
day = tonumber(d),
|
||||
hour = tonumber(h),
|
||||
min = tonumber(mi),
|
||||
sec = tonumber(s),
|
||||
year = tonumber(y) --[[@as integer]],
|
||||
month = tonumber(mo) --[[@as integer]],
|
||||
day = tonumber(d) --[[@as integer]],
|
||||
hour = tonumber(h) --[[@as integer]],
|
||||
min = tonumber(mi) --[[@as integer]],
|
||||
sec = tonumber(s) --[[@as integer]],
|
||||
})
|
||||
if t < cutoff then
|
||||
archived = archived + 1
|
||||
|
|
@ -203,6 +235,9 @@ function M.show_help()
|
|||
'',
|
||||
'<CR> Toggle complete/uncomplete',
|
||||
'<Tab> Switch category/priority view',
|
||||
'! Toggle priority',
|
||||
'd Set due date',
|
||||
'U Undo last write',
|
||||
'o / O Add new task line',
|
||||
'dd Delete task (on :w)',
|
||||
'p / P Paste (duplicates get new IDs)',
|
||||
|
|
@ -212,6 +247,7 @@ function M.show_help()
|
|||
':Pending add Cat: <text> Quick-add with category',
|
||||
':Pending sync Push to Google Calendar',
|
||||
':Pending archive [days] Purge old done tasks',
|
||||
':Pending undo Undo last write',
|
||||
'',
|
||||
'Inline metadata (on new lines before :w):',
|
||||
' ' .. dk .. ':YYYY-MM-DD Set due date',
|
||||
|
|
@ -256,6 +292,8 @@ function M.command(args)
|
|||
elseif cmd == 'archive' then
|
||||
local d = rest ~= '' and tonumber(rest) or nil
|
||||
M.archive(d)
|
||||
elseif cmd == 'undo' then
|
||||
M.undo_write()
|
||||
else
|
||||
vim.notify('Unknown Pending subcommand: ' .. cmd, vim.log.levels.ERROR)
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue