feat(init): multi-level undo, quickfix due, BufEnter reload
Problem: undo was single-level with shallow references; no way to query due/overdue tasks via quickfix; two instances sharing tasks.json would diverge silently. Solution: replace _undo_state with _undo_states[] (cap 20, deep copies via store.snapshot()); add M.due() which populates the quickfix list with overdue/due-today tasks; add BufEnter autocmd that reloads from disk when the buffer is unmodified; expand show_help() with folds, :Pending due, relative date syntax, PendingOverdue, and empty-input date clearing.
This commit is contained in:
parent
40ebd0ebb2
commit
87eedc8610
1 changed files with 79 additions and 10 deletions
|
|
@ -3,11 +3,12 @@ local diff = require('pending.diff')
|
||||||
local parse = require('pending.parse')
|
local parse = require('pending.parse')
|
||||||
local store = require('pending.store')
|
local store = require('pending.store')
|
||||||
|
|
||||||
---@class task
|
---@class pending.init
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
---@type pending.Task[]?
|
---@type pending.Task[][]
|
||||||
local _undo_state = nil
|
local _undo_states = {}
|
||||||
|
local UNDO_MAX = 20
|
||||||
|
|
||||||
---@return integer bufnr
|
---@return integer bufnr
|
||||||
function M.open()
|
function M.open()
|
||||||
|
|
@ -27,6 +28,16 @@ function M._setup_autocmds(bufnr)
|
||||||
M._on_write(bufnr)
|
M._on_write(bufnr)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
vim.api.nvim_create_autocmd('BufEnter', {
|
||||||
|
group = group,
|
||||||
|
buffer = bufnr,
|
||||||
|
callback = function()
|
||||||
|
if not vim.bo[bufnr].modified then
|
||||||
|
store.load()
|
||||||
|
buffer.render(bufnr)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
|
|
@ -55,19 +66,23 @@ end
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
function M._on_write(bufnr)
|
function M._on_write(bufnr)
|
||||||
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||||
_undo_state = store.active_tasks()
|
local snapshot = store.snapshot()
|
||||||
|
table.insert(_undo_states, snapshot)
|
||||||
|
if #_undo_states > UNDO_MAX then
|
||||||
|
table.remove(_undo_states, 1)
|
||||||
|
end
|
||||||
diff.apply(lines)
|
diff.apply(lines)
|
||||||
buffer.render(bufnr)
|
buffer.render(bufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.undo_write()
|
function M.undo_write()
|
||||||
if not _undo_state then
|
if #_undo_states == 0 then
|
||||||
vim.notify('Nothing to undo.', vim.log.levels.WARN)
|
vim.notify('Nothing to undo.', vim.log.levels.WARN)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
store.replace_tasks(_undo_state)
|
local state = table.remove(_undo_states)
|
||||||
|
store.replace_tasks(state)
|
||||||
store.save()
|
store.save()
|
||||||
_undo_state = nil
|
|
||||||
buffer.render(buffer.bufnr())
|
buffer.render(buffer.bufnr())
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -227,6 +242,49 @@ function M.archive(days)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function M.due()
|
||||||
|
local today = os.date('%Y-%m-%d') --[[@as string]]
|
||||||
|
local bufnr = buffer.bufnr()
|
||||||
|
local is_valid = bufnr ~= nil and vim.api.nvim_buf_is_valid(bufnr)
|
||||||
|
local meta = is_valid and buffer.meta() or nil
|
||||||
|
local qf_items = {}
|
||||||
|
|
||||||
|
if meta and bufnr then
|
||||||
|
for lnum, m in ipairs(meta) do
|
||||||
|
if m.type == 'task' and m.raw_due and m.status ~= 'done' and m.raw_due <= today then
|
||||||
|
local task = store.get(m.id or 0)
|
||||||
|
local label = m.raw_due < today and '[OVERDUE] ' or '[DUE] '
|
||||||
|
table.insert(qf_items, {
|
||||||
|
bufnr = bufnr,
|
||||||
|
lnum = lnum,
|
||||||
|
col = 1,
|
||||||
|
text = label .. (task and task.description or ''),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
store.load()
|
||||||
|
for _, task in ipairs(store.active_tasks()) do
|
||||||
|
if task.status == 'pending' and task.due and task.due <= today then
|
||||||
|
local label = task.due < today and '[OVERDUE] ' or '[DUE] '
|
||||||
|
local text = label .. task.description
|
||||||
|
if task.category then
|
||||||
|
text = text .. ' [' .. task.category .. ']'
|
||||||
|
end
|
||||||
|
table.insert(qf_items, { text = text })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #qf_items == 0 then
|
||||||
|
vim.notify('No due or overdue tasks.')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.fn.setqflist(qf_items, 'r')
|
||||||
|
vim.cmd('copen')
|
||||||
|
end
|
||||||
|
|
||||||
function M.show_help()
|
function M.show_help()
|
||||||
local cfg = require('pending.config').get()
|
local cfg = require('pending.config').get()
|
||||||
local dk = cfg.date_syntax or 'due'
|
local dk = cfg.date_syntax or 'due'
|
||||||
|
|
@ -239,12 +297,14 @@ function M.show_help()
|
||||||
'd Set due date',
|
'd Set due date',
|
||||||
'U Undo last write',
|
'U Undo last write',
|
||||||
'o / O Add new task line',
|
'o / O Add new task line',
|
||||||
'dd Delete task (on :w)',
|
'dd Delete task line (on :w)',
|
||||||
'p / P Paste (duplicates get new IDs)',
|
'p / P Paste (duplicates get new IDs)',
|
||||||
|
'zc / zo Fold/unfold category (category view)',
|
||||||
':w Save all changes',
|
':w Save all changes',
|
||||||
'',
|
'',
|
||||||
':Pending add <text> Quick-add task',
|
':Pending add <text> Quick-add task',
|
||||||
':Pending add Cat: <text> Quick-add with category',
|
':Pending add Cat: <text> Quick-add with category',
|
||||||
|
':Pending due Show overdue/due qflist',
|
||||||
':Pending sync Push to Google Calendar',
|
':Pending sync Push to Google Calendar',
|
||||||
':Pending archive [days] Purge old done tasks',
|
':Pending archive [days] Purge old done tasks',
|
||||||
':Pending undo Undo last write',
|
':Pending undo Undo last write',
|
||||||
|
|
@ -253,13 +313,20 @@ function M.show_help()
|
||||||
' ' .. dk .. ':YYYY-MM-DD Set due date',
|
' ' .. dk .. ':YYYY-MM-DD Set due date',
|
||||||
' cat:Name Set category',
|
' cat:Name Set category',
|
||||||
'',
|
'',
|
||||||
|
'Due date input:',
|
||||||
|
' today, tomorrow, +Nd, mon-sun',
|
||||||
|
' Empty input clears due date',
|
||||||
|
'',
|
||||||
|
'Highlights:',
|
||||||
|
' PendingOverdue overdue tasks (red)',
|
||||||
|
'',
|
||||||
'Press q or <Esc> to close',
|
'Press q or <Esc> to close',
|
||||||
}
|
}
|
||||||
local buf = vim.api.nvim_create_buf(false, true)
|
local buf = vim.api.nvim_create_buf(false, true)
|
||||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
||||||
vim.bo[buf].modifiable = false
|
vim.bo[buf].modifiable = false
|
||||||
vim.bo[buf].bufhidden = 'wipe'
|
vim.bo[buf].bufhidden = 'wipe'
|
||||||
local width = 50
|
local width = 54
|
||||||
local height = #lines
|
local height = #lines
|
||||||
local win = vim.api.nvim_open_win(buf, true, {
|
local win = vim.api.nvim_open_win(buf, true, {
|
||||||
relative = 'editor',
|
relative = 'editor',
|
||||||
|
|
@ -292,6 +359,8 @@ function M.command(args)
|
||||||
elseif cmd == 'archive' then
|
elseif cmd == 'archive' then
|
||||||
local d = rest ~= '' and tonumber(rest) or nil
|
local d = rest ~= '' and tonumber(rest) or nil
|
||||||
M.archive(d)
|
M.archive(d)
|
||||||
|
elseif cmd == 'due' then
|
||||||
|
M.due()
|
||||||
elseif cmd == 'undo' then
|
elseif cmd == 'undo' then
|
||||||
M.undo_write()
|
M.undo_write()
|
||||||
else
|
else
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue