feat: time-aware due dates, persistent undo, @return audit
Problem: Due dates had no time component, the undo stack was lost on restart and stored in a separate file, and many public functions lacked required @return annotations. Solution: Add YYYY-MM-DDThh:mm support across parse, views, recur, complete, and init with time-aware overdue checks. Merge the undo stack into the task store JSON so a single file holds all state. Add @return nil annotations to all 27 void public functions across every module.
This commit is contained in:
parent
c69afacc87
commit
ee2d125846
11 changed files with 369 additions and 118 deletions
|
|
@ -35,7 +35,7 @@ Features: ~
|
||||||
names, month names, ordinals, and more
|
names, month names, ordinals, and more
|
||||||
- Recurring tasks with automatic next-date spawning on completion
|
- Recurring tasks with automatic next-date spawning on completion
|
||||||
- Two views: category (default) and priority flat list
|
- Two views: category (default) and priority flat list
|
||||||
- Multi-level undo (up to 20 `:w` saves, session-only)
|
- Multi-level undo (up to 20 `:w` saves, persisted across sessions)
|
||||||
- Quick-add from the command line with `:Pending add`
|
- Quick-add from the command line with `:Pending add`
|
||||||
- Quickfix list of overdue/due-today tasks via `:Pending due`
|
- Quickfix list of overdue/due-today tasks via `:Pending due`
|
||||||
- Foldable category sections (`zc`/`zo`) in category view
|
- Foldable category sections (`zc`/`zo`) in category view
|
||||||
|
|
@ -242,7 +242,7 @@ COMMANDS *pending-commands*
|
||||||
:Pending undo
|
:Pending undo
|
||||||
Undo the last `:w` save, restoring the task store to its previous state.
|
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
|
Equivalent to the `U` buffer-local key (see |pending-mappings|). Up to 20
|
||||||
levels of undo are retained per session.
|
levels of undo are persisted across sessions.
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
MAPPINGS *pending-mappings*
|
MAPPINGS *pending-mappings*
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,12 @@ function M.current_view_name()
|
||||||
return current_view
|
return current_view
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return nil
|
||||||
function M.clear_winid()
|
function M.clear_winid()
|
||||||
task_winid = nil
|
task_winid = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return nil
|
||||||
function M.close()
|
function M.close()
|
||||||
if task_winid and vim.api.nvim_win_is_valid(task_winid) then
|
if task_winid and vim.api.nvim_win_is_valid(task_winid) then
|
||||||
vim.api.nvim_win_close(task_winid, false)
|
vim.api.nvim_win_close(task_winid, false)
|
||||||
|
|
@ -79,6 +81,7 @@ local function setup_syntax(bufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param above boolean
|
---@param above boolean
|
||||||
|
---@return nil
|
||||||
function M.open_line(above)
|
function M.open_line(above)
|
||||||
local bufnr = task_bufnr
|
local bufnr = task_bufnr
|
||||||
if not bufnr or not vim.api.nvim_buf_is_valid(bufnr) then
|
if not bufnr or not vim.api.nvim_buf_is_valid(bufnr) then
|
||||||
|
|
@ -205,6 +208,7 @@ local function restore_folds(bufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param bufnr? integer
|
---@param bufnr? integer
|
||||||
|
---@return nil
|
||||||
function M.render(bufnr)
|
function M.render(bufnr)
|
||||||
bufnr = bufnr or task_bufnr
|
bufnr = bufnr or task_bufnr
|
||||||
if not bufnr or not vim.api.nvim_buf_is_valid(bufnr) then
|
if not bufnr or not vim.api.nvim_buf_is_valid(bufnr) then
|
||||||
|
|
@ -249,6 +253,7 @@ function M.render(bufnr)
|
||||||
restore_folds(bufnr)
|
restore_folds(bufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return nil
|
||||||
function M.toggle_view()
|
function M.toggle_view()
|
||||||
if current_view == 'category' then
|
if current_view == 'category' then
|
||||||
current_view = 'priority'
|
current_view = 'priority'
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,18 @@ local function date_completions()
|
||||||
'soq',
|
'soq',
|
||||||
'soy',
|
'soy',
|
||||||
'later',
|
'later',
|
||||||
|
'today@08:00',
|
||||||
|
'today@09:00',
|
||||||
|
'today@10:00',
|
||||||
|
'today@12:00',
|
||||||
|
'today@14:00',
|
||||||
|
'today@17:00',
|
||||||
|
'tomorrow@08:00',
|
||||||
|
'tomorrow@09:00',
|
||||||
|
'tomorrow@10:00',
|
||||||
|
'tomorrow@12:00',
|
||||||
|
'tomorrow@14:00',
|
||||||
|
'tomorrow@17:00',
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ function M.get()
|
||||||
return _resolved
|
return _resolved
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return nil
|
||||||
function M.reset()
|
function M.reset()
|
||||||
_resolved = nil
|
_resolved = nil
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ function M.parse_buffer(lines)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param lines string[]
|
---@param lines string[]
|
||||||
|
---@return nil
|
||||||
function M.apply(lines)
|
function M.apply(lines)
|
||||||
local parsed = M.parse_buffer(lines)
|
local parsed = M.parse_buffer(lines)
|
||||||
local now = timestamp()
|
local now = timestamp()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
---@return nil
|
||||||
function M.check()
|
function M.check()
|
||||||
vim.health.start('pending.nvim')
|
vim.health.start('pending.nvim')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
local buffer = require('pending.buffer')
|
local buffer = require('pending.buffer')
|
||||||
|
local config = require('pending.config')
|
||||||
local diff = require('pending.diff')
|
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')
|
||||||
|
|
@ -6,8 +7,6 @@ local store = require('pending.store')
|
||||||
---@class pending.init
|
---@class pending.init
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
---@type pending.Task[][]
|
|
||||||
local _undo_states = {}
|
|
||||||
local UNDO_MAX = 20
|
local UNDO_MAX = 20
|
||||||
|
|
||||||
---@return integer bufnr
|
---@return integer bufnr
|
||||||
|
|
@ -19,6 +18,7 @@ function M.open()
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
|
---@return nil
|
||||||
function M._setup_autocmds(bufnr)
|
function M._setup_autocmds(bufnr)
|
||||||
local group = vim.api.nvim_create_augroup('PendingBuffer', { clear = true })
|
local group = vim.api.nvim_create_augroup('PendingBuffer', { clear = true })
|
||||||
vim.api.nvim_create_autocmd('BufWriteCmd', {
|
vim.api.nvim_create_autocmd('BufWriteCmd', {
|
||||||
|
|
@ -49,6 +49,7 @@ function M._setup_autocmds(bufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
|
---@return nil
|
||||||
function M._setup_buf_mappings(bufnr)
|
function M._setup_buf_mappings(bufnr)
|
||||||
local cfg = require('pending.config').get()
|
local cfg = require('pending.config').get()
|
||||||
local km = cfg.keymaps
|
local km = cfg.keymaps
|
||||||
|
|
@ -91,28 +92,33 @@ function M._setup_buf_mappings(bufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
|
---@return nil
|
||||||
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)
|
||||||
local snapshot = store.snapshot()
|
local snapshot = store.snapshot()
|
||||||
table.insert(_undo_states, snapshot)
|
local stack = store.undo_stack()
|
||||||
if #_undo_states > UNDO_MAX then
|
table.insert(stack, snapshot)
|
||||||
table.remove(_undo_states, 1)
|
if #stack > UNDO_MAX then
|
||||||
|
table.remove(stack, 1)
|
||||||
end
|
end
|
||||||
diff.apply(lines)
|
diff.apply(lines)
|
||||||
buffer.render(bufnr)
|
buffer.render(bufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return nil
|
||||||
function M.undo_write()
|
function M.undo_write()
|
||||||
if #_undo_states == 0 then
|
local stack = store.undo_stack()
|
||||||
|
if #stack == 0 then
|
||||||
vim.notify('Nothing to undo.', vim.log.levels.WARN)
|
vim.notify('Nothing to undo.', vim.log.levels.WARN)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local state = table.remove(_undo_states)
|
local state = table.remove(stack)
|
||||||
store.replace_tasks(state)
|
store.replace_tasks(state)
|
||||||
store.save()
|
store.save()
|
||||||
buffer.render(buffer.bufnr())
|
buffer.render(buffer.bufnr())
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return nil
|
||||||
function M.toggle_complete()
|
function M.toggle_complete()
|
||||||
local bufnr = buffer.bufnr()
|
local bufnr = buffer.bufnr()
|
||||||
if not bufnr then
|
if not bufnr then
|
||||||
|
|
@ -137,9 +143,7 @@ function M.toggle_complete()
|
||||||
if task.recur and task.due then
|
if task.recur and task.due then
|
||||||
local recur = require('pending.recur')
|
local recur = require('pending.recur')
|
||||||
local mode = task.recur_mode or 'scheduled'
|
local mode = task.recur_mode or 'scheduled'
|
||||||
local base = mode == 'completion' and os.date('%Y-%m-%d') --[[@as string]]
|
local next_date = recur.next_due(task.due, task.recur, mode)
|
||||||
or task.due
|
|
||||||
local next_date = recur.next_due(base, task.recur, mode)
|
|
||||||
store.add({
|
store.add({
|
||||||
description = task.description,
|
description = task.description,
|
||||||
category = task.category,
|
category = task.category,
|
||||||
|
|
@ -161,6 +165,7 @@ function M.toggle_complete()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return nil
|
||||||
function M.toggle_priority()
|
function M.toggle_priority()
|
||||||
local bufnr = buffer.bufnr()
|
local bufnr = buffer.bufnr()
|
||||||
if not bufnr then
|
if not bufnr then
|
||||||
|
|
@ -191,6 +196,7 @@ function M.toggle_priority()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return nil
|
||||||
function M.prompt_date()
|
function M.prompt_date()
|
||||||
local bufnr = buffer.bufnr()
|
local bufnr = buffer.bufnr()
|
||||||
if not bufnr then
|
if not bufnr then
|
||||||
|
|
@ -205,7 +211,7 @@ function M.prompt_date()
|
||||||
if not id then
|
if not id then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
vim.ui.input({ prompt = 'Due date (YYYY-MM-DD): ' }, function(input)
|
vim.ui.input({ prompt = 'Due date (YYYY-MM-DD[Thh:mm]): ' }, function(input)
|
||||||
if not input then
|
if not input then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -214,8 +220,11 @@ function M.prompt_date()
|
||||||
local resolved = parse.resolve_date(due)
|
local resolved = parse.resolve_date(due)
|
||||||
if resolved then
|
if resolved then
|
||||||
due = resolved
|
due = resolved
|
||||||
elseif not due:match('^%d%d%d%d%-%d%d%-%d%d$') then
|
elseif
|
||||||
vim.notify('Invalid date format. Use YYYY-MM-DD.', vim.log.levels.ERROR)
|
not due:match('^%d%d%d%d%-%d%d%-%d%d$')
|
||||||
|
and not due:match('^%d%d%d%d%-%d%d%-%d%dT%d%d:%d%d$')
|
||||||
|
then
|
||||||
|
vim.notify('Invalid date format. Use YYYY-MM-DD or YYYY-MM-DDThh:mm.', vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -226,6 +235,7 @@ function M.prompt_date()
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param text string
|
---@param text string
|
||||||
|
---@return nil
|
||||||
function M.add(text)
|
function M.add(text)
|
||||||
if not text or text == '' then
|
if not text or text == '' then
|
||||||
vim.notify('Usage: :Pending add <description>', vim.log.levels.ERROR)
|
vim.notify('Usage: :Pending add <description>', vim.log.levels.ERROR)
|
||||||
|
|
@ -252,6 +262,7 @@ function M.add(text)
|
||||||
vim.notify('Pending added: ' .. description)
|
vim.notify('Pending added: ' .. description)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return nil
|
||||||
function M.sync()
|
function M.sync()
|
||||||
local ok, gcal = pcall(require, 'pending.sync.gcal')
|
local ok, gcal = pcall(require, 'pending.sync.gcal')
|
||||||
if not ok then
|
if not ok then
|
||||||
|
|
@ -262,6 +273,7 @@ function M.sync()
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param days? integer
|
---@param days? integer
|
||||||
|
---@return nil
|
||||||
function M.archive(days)
|
function M.archive(days)
|
||||||
days = days or 30
|
days = days or 30
|
||||||
local cutoff = os.time() - (days * 86400)
|
local cutoff = os.time() - (days * 86400)
|
||||||
|
|
@ -298,8 +310,46 @@ function M.archive(days)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.due()
|
---@param due string
|
||||||
|
---@return boolean
|
||||||
|
local function is_due_or_overdue(due)
|
||||||
|
local now = os.date('*t') --[[@as osdate]]
|
||||||
local today = os.date('%Y-%m-%d') --[[@as string]]
|
local today = os.date('%Y-%m-%d') --[[@as string]]
|
||||||
|
local date_part, time_part = due:match('^(.+)T(.+)$')
|
||||||
|
if not date_part then
|
||||||
|
return due <= today
|
||||||
|
end
|
||||||
|
if date_part < today then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if date_part > today then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local current_time = string.format('%02d:%02d', now.hour, now.min)
|
||||||
|
return time_part <= current_time
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param due string
|
||||||
|
---@return boolean
|
||||||
|
local function is_overdue(due)
|
||||||
|
local now = os.date('*t') --[[@as osdate]]
|
||||||
|
local today = os.date('%Y-%m-%d') --[[@as string]]
|
||||||
|
local date_part, time_part = due:match('^(.+)T(.+)$')
|
||||||
|
if not date_part then
|
||||||
|
return due < today
|
||||||
|
end
|
||||||
|
if date_part < today then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if date_part > today then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local current_time = string.format('%02d:%02d', now.hour, now.min)
|
||||||
|
return time_part < current_time
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return nil
|
||||||
|
function M.due()
|
||||||
local bufnr = buffer.bufnr()
|
local bufnr = buffer.bufnr()
|
||||||
local is_valid = bufnr ~= nil and vim.api.nvim_buf_is_valid(bufnr)
|
local is_valid = bufnr ~= nil and vim.api.nvim_buf_is_valid(bufnr)
|
||||||
local meta = is_valid and buffer.meta() or nil
|
local meta = is_valid and buffer.meta() or nil
|
||||||
|
|
@ -307,9 +357,9 @@ function M.due()
|
||||||
|
|
||||||
if meta and bufnr then
|
if meta and bufnr then
|
||||||
for lnum, m in ipairs(meta) do
|
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
|
if m.type == 'task' and m.raw_due and m.status ~= 'done' and is_due_or_overdue(m.raw_due) then
|
||||||
local task = store.get(m.id or 0)
|
local task = store.get(m.id or 0)
|
||||||
local label = m.raw_due < today and '[OVERDUE] ' or '[DUE] '
|
local label = is_overdue(m.raw_due) and '[OVERDUE] ' or '[DUE] '
|
||||||
table.insert(qf_items, {
|
table.insert(qf_items, {
|
||||||
bufnr = bufnr,
|
bufnr = bufnr,
|
||||||
lnum = lnum,
|
lnum = lnum,
|
||||||
|
|
@ -321,8 +371,8 @@ function M.due()
|
||||||
else
|
else
|
||||||
store.load()
|
store.load()
|
||||||
for _, task in ipairs(store.active_tasks()) do
|
for _, task in ipairs(store.active_tasks()) do
|
||||||
if task.status == 'pending' and task.due and task.due <= today then
|
if task.status == 'pending' and task.due and is_due_or_overdue(task.due) then
|
||||||
local label = task.due < today and '[OVERDUE] ' or '[DUE] '
|
local label = is_overdue(task.due) and '[OVERDUE] ' or '[DUE] '
|
||||||
local text = label .. task.description
|
local text = label .. task.description
|
||||||
if task.category then
|
if task.category then
|
||||||
text = text .. ' [' .. task.category .. ']'
|
text = text .. ' [' .. task.category .. ']'
|
||||||
|
|
@ -342,6 +392,7 @@ function M.due()
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param args string
|
---@param args string
|
||||||
|
---@return nil
|
||||||
function M.command(args)
|
function M.command(args)
|
||||||
if not args or args == '' then
|
if not args or args == '' then
|
||||||
M.open()
|
M.open()
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,28 @@ local function is_valid_date(s)
|
||||||
return check.year == yn and check.month == mn and check.day == dn
|
return check.year == yn and check.month == mn and check.day == dn
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param s string
|
||||||
|
---@return boolean
|
||||||
|
local function is_valid_time(s)
|
||||||
|
local h, m = s:match('^(%d%d):(%d%d)$')
|
||||||
|
if not h then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local hn = tonumber(h) --[[@as integer]]
|
||||||
|
local mn = tonumber(m) --[[@as integer]]
|
||||||
|
return hn >= 0 and hn <= 23 and mn >= 0 and mn <= 59
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param s string
|
||||||
|
---@return boolean
|
||||||
|
local function is_valid_datetime(s)
|
||||||
|
local date_part, time_part = s:match('^(.+)T(.+)$')
|
||||||
|
if not date_part then
|
||||||
|
return is_valid_date(s)
|
||||||
|
end
|
||||||
|
return is_valid_date(date_part) and is_valid_time(time_part)
|
||||||
|
end
|
||||||
|
|
||||||
---@return string
|
---@return string
|
||||||
local function date_key()
|
local function date_key()
|
||||||
return config.get().date_syntax or 'due'
|
return config.get().date_syntax or 'due'
|
||||||
|
|
@ -65,146 +87,217 @@ local function today_str(today)
|
||||||
return os.date('%Y-%m-%d', os.time({ year = today.year, month = today.month, day = today.day })) --[[@as string]]
|
return os.date('%Y-%m-%d', os.time({ year = today.year, month = today.month, day = today.day })) --[[@as string]]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param date_part string
|
||||||
|
---@param time_suffix? string
|
||||||
|
---@return string
|
||||||
|
local function append_time(date_part, time_suffix)
|
||||||
|
if time_suffix then
|
||||||
|
return date_part .. 'T' .. time_suffix
|
||||||
|
end
|
||||||
|
return date_part
|
||||||
|
end
|
||||||
|
|
||||||
---@param text string
|
---@param text string
|
||||||
---@return string|nil
|
---@return string|nil
|
||||||
function M.resolve_date(text)
|
function M.resolve_date(text)
|
||||||
local lower = text:lower()
|
local date_input, time_suffix = text:match('^(.+)@(%d%d:%d%d)$')
|
||||||
|
if time_suffix then
|
||||||
|
if not is_valid_time(time_suffix) then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
else
|
||||||
|
date_input = text
|
||||||
|
end
|
||||||
|
|
||||||
|
local dt = date_input:match('^(%d%d%d%d%-%d%d%-%d%dT%d%d:%d%d)$')
|
||||||
|
if dt then
|
||||||
|
local dp, tp = dt:match('^(.+)T(.+)$')
|
||||||
|
if is_valid_date(dp) and is_valid_time(tp) then
|
||||||
|
return dt
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if is_valid_date(date_input) then
|
||||||
|
return append_time(date_input, time_suffix)
|
||||||
|
end
|
||||||
|
|
||||||
|
local lower = date_input:lower()
|
||||||
local today = os.date('*t') --[[@as osdate]]
|
local today = os.date('*t') --[[@as osdate]]
|
||||||
|
|
||||||
if lower == 'today' or lower == 'eod' then
|
if lower == 'today' or lower == 'eod' then
|
||||||
return today_str(today)
|
return append_time(today_str(today), time_suffix)
|
||||||
end
|
end
|
||||||
|
|
||||||
if lower == 'yesterday' then
|
if lower == 'yesterday' then
|
||||||
return os.date(
|
return append_time(
|
||||||
'%Y-%m-%d',
|
os.date('%Y-%m-%d', os.time({ year = today.year, month = today.month, day = today.day - 1 })) --[[@as string]],
|
||||||
os.time({ year = today.year, month = today.month, day = today.day - 1 })
|
time_suffix
|
||||||
) --[[@as string]]
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
if lower == 'tomorrow' then
|
if lower == 'tomorrow' then
|
||||||
return os.date(
|
return append_time(
|
||||||
'%Y-%m-%d',
|
os.date('%Y-%m-%d', os.time({ year = today.year, month = today.month, day = today.day + 1 })) --[[@as string]],
|
||||||
os.time({ year = today.year, month = today.month, day = today.day + 1 })
|
time_suffix
|
||||||
) --[[@as string]]
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
if lower == 'sow' then
|
if lower == 'sow' then
|
||||||
local delta = -((today.wday - 2) % 7)
|
local delta = -((today.wday - 2) % 7)
|
||||||
return os.date(
|
return append_time(
|
||||||
'%Y-%m-%d',
|
os.date(
|
||||||
os.time({ year = today.year, month = today.month, day = today.day + delta })
|
'%Y-%m-%d',
|
||||||
) --[[@as string]]
|
os.time({ year = today.year, month = today.month, day = today.day + delta })
|
||||||
|
) --[[@as string]],
|
||||||
|
time_suffix
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
if lower == 'eow' then
|
if lower == 'eow' then
|
||||||
local delta = (1 - today.wday) % 7
|
local delta = (1 - today.wday) % 7
|
||||||
return os.date(
|
return append_time(
|
||||||
'%Y-%m-%d',
|
os.date(
|
||||||
os.time({ year = today.year, month = today.month, day = today.day + delta })
|
'%Y-%m-%d',
|
||||||
) --[[@as string]]
|
os.time({ year = today.year, month = today.month, day = today.day + delta })
|
||||||
|
) --[[@as string]],
|
||||||
|
time_suffix
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
if lower == 'som' then
|
if lower == 'som' then
|
||||||
return os.date('%Y-%m-%d', os.time({ year = today.year, month = today.month, day = 1 })) --[[@as string]]
|
return append_time(
|
||||||
|
os.date('%Y-%m-%d', os.time({ year = today.year, month = today.month, day = 1 })) --[[@as string]],
|
||||||
|
time_suffix
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
if lower == 'eom' then
|
if lower == 'eom' then
|
||||||
return os.date('%Y-%m-%d', os.time({ year = today.year, month = today.month + 1, day = 0 })) --[[@as string]]
|
return append_time(
|
||||||
|
os.date('%Y-%m-%d', os.time({ year = today.year, month = today.month + 1, day = 0 })) --[[@as string]],
|
||||||
|
time_suffix
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
if lower == 'soq' then
|
if lower == 'soq' then
|
||||||
local q = math.ceil(today.month / 3)
|
local q = math.ceil(today.month / 3)
|
||||||
local first_month = (q - 1) * 3 + 1
|
local first_month = (q - 1) * 3 + 1
|
||||||
return os.date('%Y-%m-%d', os.time({ year = today.year, month = first_month, day = 1 })) --[[@as string]]
|
return append_time(
|
||||||
|
os.date('%Y-%m-%d', os.time({ year = today.year, month = first_month, day = 1 })) --[[@as string]],
|
||||||
|
time_suffix
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
if lower == 'eoq' then
|
if lower == 'eoq' then
|
||||||
local q = math.ceil(today.month / 3)
|
local q = math.ceil(today.month / 3)
|
||||||
local last_month = q * 3
|
local last_month = q * 3
|
||||||
return os.date('%Y-%m-%d', os.time({ year = today.year, month = last_month + 1, day = 0 })) --[[@as string]]
|
return append_time(
|
||||||
|
os.date('%Y-%m-%d', os.time({ year = today.year, month = last_month + 1, day = 0 })) --[[@as string]],
|
||||||
|
time_suffix
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
if lower == 'soy' then
|
if lower == 'soy' then
|
||||||
return os.date('%Y-%m-%d', os.time({ year = today.year, month = 1, day = 1 })) --[[@as string]]
|
return append_time(
|
||||||
|
os.date('%Y-%m-%d', os.time({ year = today.year, month = 1, day = 1 })) --[[@as string]],
|
||||||
|
time_suffix
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
if lower == 'eoy' then
|
if lower == 'eoy' then
|
||||||
return os.date('%Y-%m-%d', os.time({ year = today.year, month = 12, day = 31 })) --[[@as string]]
|
return append_time(
|
||||||
|
os.date('%Y-%m-%d', os.time({ year = today.year, month = 12, day = 31 })) --[[@as string]],
|
||||||
|
time_suffix
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
if lower == 'later' or lower == 'someday' then
|
if lower == 'later' or lower == 'someday' then
|
||||||
return config.get().someday_date
|
return append_time(config.get().someday_date, time_suffix)
|
||||||
end
|
end
|
||||||
|
|
||||||
local n = lower:match('^%+(%d+)d$')
|
local n = lower:match('^%+(%d+)d$')
|
||||||
if n then
|
if n then
|
||||||
return os.date(
|
return append_time(
|
||||||
'%Y-%m-%d',
|
os.date(
|
||||||
os.time({
|
'%Y-%m-%d',
|
||||||
year = today.year,
|
os.time({
|
||||||
month = today.month,
|
year = today.year,
|
||||||
day = today.day + (
|
month = today.month,
|
||||||
tonumber(n) --[[@as integer]]
|
day = today.day + (
|
||||||
),
|
tonumber(n) --[[@as integer]]
|
||||||
})
|
),
|
||||||
) --[[@as string]]
|
})
|
||||||
|
) --[[@as string]],
|
||||||
|
time_suffix
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
n = lower:match('^%+(%d+)w$')
|
n = lower:match('^%+(%d+)w$')
|
||||||
if n then
|
if n then
|
||||||
return os.date(
|
return append_time(
|
||||||
'%Y-%m-%d',
|
os.date(
|
||||||
os.time({
|
'%Y-%m-%d',
|
||||||
year = today.year,
|
os.time({
|
||||||
month = today.month,
|
year = today.year,
|
||||||
day = today.day + (
|
month = today.month,
|
||||||
tonumber(n) --[[@as integer]]
|
day = today.day + (
|
||||||
) * 7,
|
tonumber(n) --[[@as integer]]
|
||||||
})
|
) * 7,
|
||||||
) --[[@as string]]
|
})
|
||||||
|
) --[[@as string]],
|
||||||
|
time_suffix
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
n = lower:match('^%+(%d+)m$')
|
n = lower:match('^%+(%d+)m$')
|
||||||
if n then
|
if n then
|
||||||
return os.date(
|
return append_time(
|
||||||
'%Y-%m-%d',
|
os.date(
|
||||||
os.time({
|
'%Y-%m-%d',
|
||||||
year = today.year,
|
os.time({
|
||||||
month = today.month + (
|
year = today.year,
|
||||||
tonumber(n) --[[@as integer]]
|
month = today.month + (
|
||||||
),
|
tonumber(n) --[[@as integer]]
|
||||||
day = today.day,
|
),
|
||||||
})
|
day = today.day,
|
||||||
) --[[@as string]]
|
})
|
||||||
|
) --[[@as string]],
|
||||||
|
time_suffix
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
n = lower:match('^%-(%d+)d$')
|
n = lower:match('^%-(%d+)d$')
|
||||||
if n then
|
if n then
|
||||||
return os.date(
|
return append_time(
|
||||||
'%Y-%m-%d',
|
os.date(
|
||||||
os.time({
|
'%Y-%m-%d',
|
||||||
year = today.year,
|
os.time({
|
||||||
month = today.month,
|
year = today.year,
|
||||||
day = today.day - (
|
month = today.month,
|
||||||
tonumber(n) --[[@as integer]]
|
day = today.day - (
|
||||||
),
|
tonumber(n) --[[@as integer]]
|
||||||
})
|
),
|
||||||
) --[[@as string]]
|
})
|
||||||
|
) --[[@as string]],
|
||||||
|
time_suffix
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
n = lower:match('^%-(%d+)w$')
|
n = lower:match('^%-(%d+)w$')
|
||||||
if n then
|
if n then
|
||||||
return os.date(
|
return append_time(
|
||||||
'%Y-%m-%d',
|
os.date(
|
||||||
os.time({
|
'%Y-%m-%d',
|
||||||
year = today.year,
|
os.time({
|
||||||
month = today.month,
|
year = today.year,
|
||||||
day = today.day - (
|
month = today.month,
|
||||||
tonumber(n) --[[@as integer]]
|
day = today.day - (
|
||||||
) * 7,
|
tonumber(n) --[[@as integer]]
|
||||||
})
|
) * 7,
|
||||||
) --[[@as string]]
|
})
|
||||||
|
) --[[@as string]],
|
||||||
|
time_suffix
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
local ord = lower:match('^(%d+)[snrt][tdh]$')
|
local ord = lower:match('^(%d+)[snrt][tdh]$')
|
||||||
|
|
@ -222,7 +315,7 @@ function M.resolve_date(text)
|
||||||
local t = os.time({ year = y, month = m, day = day_num })
|
local t = os.time({ year = y, month = m, day = day_num })
|
||||||
local check = os.date('*t', t) --[[@as osdate]]
|
local check = os.date('*t', t) --[[@as osdate]]
|
||||||
if check.day == day_num then
|
if check.day == day_num then
|
||||||
return os.date('%Y-%m-%d', t) --[[@as string]]
|
return append_time(os.date('%Y-%m-%d', t) --[[@as string]], time_suffix)
|
||||||
end
|
end
|
||||||
m = m + 1
|
m = m + 1
|
||||||
if m > 12 then
|
if m > 12 then
|
||||||
|
|
@ -232,7 +325,7 @@ function M.resolve_date(text)
|
||||||
t = os.time({ year = y, month = m, day = day_num })
|
t = os.time({ year = y, month = m, day = day_num })
|
||||||
check = os.date('*t', t) --[[@as osdate]]
|
check = os.date('*t', t) --[[@as osdate]]
|
||||||
if check.day == day_num then
|
if check.day == day_num then
|
||||||
return os.date('%Y-%m-%d', t) --[[@as string]]
|
return append_time(os.date('%Y-%m-%d', t) --[[@as string]], time_suffix)
|
||||||
end
|
end
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
@ -244,17 +337,23 @@ function M.resolve_date(text)
|
||||||
if today.month >= target_month then
|
if today.month >= target_month then
|
||||||
y = y + 1
|
y = y + 1
|
||||||
end
|
end
|
||||||
return os.date('%Y-%m-%d', os.time({ year = y, month = target_month, day = 1 })) --[[@as string]]
|
return append_time(
|
||||||
|
os.date('%Y-%m-%d', os.time({ year = y, month = target_month, day = 1 })) --[[@as string]],
|
||||||
|
time_suffix
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
local target_wday = weekday_map[lower]
|
local target_wday = weekday_map[lower]
|
||||||
if target_wday then
|
if target_wday then
|
||||||
local current_wday = today.wday
|
local current_wday = today.wday
|
||||||
local delta = (target_wday - current_wday) % 7
|
local delta = (target_wday - current_wday) % 7
|
||||||
return os.date(
|
return append_time(
|
||||||
'%Y-%m-%d',
|
os.date(
|
||||||
os.time({ year = today.year, month = today.month, day = today.day + delta })
|
'%Y-%m-%d',
|
||||||
) --[[@as string]]
|
os.time({ year = today.year, month = today.month, day = today.day + delta })
|
||||||
|
) --[[@as string]],
|
||||||
|
time_suffix
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -273,7 +372,7 @@ function M.body(text)
|
||||||
local i = #tokens
|
local i = #tokens
|
||||||
local dk = date_key()
|
local dk = date_key()
|
||||||
local rk = recur_key()
|
local rk = recur_key()
|
||||||
local date_pattern_strict = '^' .. vim.pesc(dk) .. ':(%d%d%d%d%-%d%d%-%d%d)$'
|
local date_pattern_strict = '^' .. vim.pesc(dk) .. ':(%d%d%d%d%-%d%d%-%d%d[T%d:]*)$'
|
||||||
local date_pattern_any = '^' .. vim.pesc(dk) .. ':(.+)$'
|
local date_pattern_any = '^' .. vim.pesc(dk) .. ':(.+)$'
|
||||||
local rec_pattern = '^' .. vim.pesc(rk) .. ':(%S+)$'
|
local rec_pattern = '^' .. vim.pesc(rk) .. ':(%S+)$'
|
||||||
|
|
||||||
|
|
@ -284,7 +383,7 @@ function M.body(text)
|
||||||
if metadata.due then
|
if metadata.due then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
if not is_valid_date(due_val) then
|
if not is_valid_datetime(due_val) then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
metadata.due = due_val
|
metadata.due = due_val
|
||||||
|
|
|
||||||
|
|
@ -80,20 +80,33 @@ function M.validate(spec)
|
||||||
return M.parse(spec) ~= nil
|
return M.parse(spec) ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param due string
|
||||||
|
---@return string date_part
|
||||||
|
---@return string? time_part
|
||||||
|
local function split_datetime(due)
|
||||||
|
local dp, tp = due:match('^(.+)T(.+)$')
|
||||||
|
if dp then
|
||||||
|
return dp, tp
|
||||||
|
end
|
||||||
|
return due, nil
|
||||||
|
end
|
||||||
|
|
||||||
---@param base_date string
|
---@param base_date string
|
||||||
---@param freq string
|
---@param freq string
|
||||||
---@param interval integer
|
---@param interval integer
|
||||||
---@return string
|
---@return string
|
||||||
local function advance_date(base_date, freq, interval)
|
local function advance_date(base_date, freq, interval)
|
||||||
local y, m, d = base_date:match('^(%d+)-(%d+)-(%d+)$')
|
local date_part, time_part = split_datetime(base_date)
|
||||||
|
local y, m, d = date_part:match('^(%d+)-(%d+)-(%d+)$')
|
||||||
local yn = tonumber(y) --[[@as integer]]
|
local yn = tonumber(y) --[[@as integer]]
|
||||||
local mn = tonumber(m) --[[@as integer]]
|
local mn = tonumber(m) --[[@as integer]]
|
||||||
local dn = tonumber(d) --[[@as integer]]
|
local dn = tonumber(d) --[[@as integer]]
|
||||||
|
|
||||||
|
local result
|
||||||
if freq == 'daily' then
|
if freq == 'daily' then
|
||||||
return os.date('%Y-%m-%d', os.time({ year = yn, month = mn, day = dn + interval })) --[[@as string]]
|
result = os.date('%Y-%m-%d', os.time({ year = yn, month = mn, day = dn + interval })) --[[@as string]]
|
||||||
elseif freq == 'weekly' then
|
elseif freq == 'weekly' then
|
||||||
return os.date('%Y-%m-%d', os.time({ year = yn, month = mn, day = dn + interval * 7 })) --[[@as string]]
|
result = os.date('%Y-%m-%d', os.time({ year = yn, month = mn, day = dn + interval * 7 })) --[[@as string]]
|
||||||
elseif freq == 'monthly' then
|
elseif freq == 'monthly' then
|
||||||
local new_m = mn + interval
|
local new_m = mn + interval
|
||||||
local new_y = yn
|
local new_y = yn
|
||||||
|
|
@ -103,14 +116,20 @@ local function advance_date(base_date, freq, interval)
|
||||||
end
|
end
|
||||||
local last_day = os.date('*t', os.time({ year = new_y, month = new_m + 1, day = 0 })) --[[@as osdate]]
|
local last_day = os.date('*t', os.time({ year = new_y, month = new_m + 1, day = 0 })) --[[@as osdate]]
|
||||||
local clamped_d = math.min(dn, last_day.day --[[@as integer]])
|
local clamped_d = math.min(dn, last_day.day --[[@as integer]])
|
||||||
return os.date('%Y-%m-%d', os.time({ year = new_y, month = new_m, day = clamped_d })) --[[@as string]]
|
result = os.date('%Y-%m-%d', os.time({ year = new_y, month = new_m, day = clamped_d })) --[[@as string]]
|
||||||
elseif freq == 'yearly' then
|
elseif freq == 'yearly' then
|
||||||
local new_y = yn + interval
|
local new_y = yn + interval
|
||||||
local last_day = os.date('*t', os.time({ year = new_y, month = mn + 1, day = 0 })) --[[@as osdate]]
|
local last_day = os.date('*t', os.time({ year = new_y, month = mn + 1, day = 0 })) --[[@as osdate]]
|
||||||
local clamped_d = math.min(dn, last_day.day --[[@as integer]])
|
local clamped_d = math.min(dn, last_day.day --[[@as integer]])
|
||||||
return os.date('%Y-%m-%d', os.time({ year = new_y, month = mn, day = clamped_d })) --[[@as string]]
|
result = os.date('%Y-%m-%d', os.time({ year = new_y, month = mn, day = clamped_d })) --[[@as string]]
|
||||||
|
else
|
||||||
|
return base_date
|
||||||
end
|
end
|
||||||
return base_date
|
|
||||||
|
if time_part then
|
||||||
|
return result .. 'T' .. time_part
|
||||||
|
end
|
||||||
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param base_date string
|
---@param base_date string
|
||||||
|
|
@ -124,13 +143,16 @@ function M.next_due(base_date, spec, mode)
|
||||||
end
|
end
|
||||||
|
|
||||||
local today = os.date('%Y-%m-%d') --[[@as string]]
|
local today = os.date('%Y-%m-%d') --[[@as string]]
|
||||||
|
local _, time_part = split_datetime(base_date)
|
||||||
|
|
||||||
if mode == 'completion' then
|
if mode == 'completion' then
|
||||||
return advance_date(today, parsed.freq, parsed.interval)
|
local base = time_part and (today .. 'T' .. time_part) or today
|
||||||
|
return advance_date(base, parsed.freq, parsed.interval)
|
||||||
end
|
end
|
||||||
|
|
||||||
local next_date = advance_date(base_date, parsed.freq, parsed.interval)
|
local next_date = advance_date(base_date, parsed.freq, parsed.interval)
|
||||||
while next_date <= today do
|
local compare_today = time_part and (today .. 'T' .. time_part) or today
|
||||||
|
while next_date <= compare_today do
|
||||||
next_date = advance_date(next_date, parsed.freq, parsed.interval)
|
next_date = advance_date(next_date, parsed.freq, parsed.interval)
|
||||||
end
|
end
|
||||||
return next_date
|
return next_date
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ local config = require('pending.config')
|
||||||
---@field version integer
|
---@field version integer
|
||||||
---@field next_id integer
|
---@field next_id integer
|
||||||
---@field tasks pending.Task[]
|
---@field tasks pending.Task[]
|
||||||
|
---@field undo pending.Task[][]
|
||||||
|
|
||||||
---@class pending.store
|
---@class pending.store
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
@ -34,6 +35,7 @@ local function empty_data()
|
||||||
version = SUPPORTED_VERSION,
|
version = SUPPORTED_VERSION,
|
||||||
next_id = 1,
|
next_id = 1,
|
||||||
tasks = {},
|
tasks = {},
|
||||||
|
undo = {},
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -165,13 +167,24 @@ function M.load()
|
||||||
version = decoded.version or SUPPORTED_VERSION,
|
version = decoded.version or SUPPORTED_VERSION,
|
||||||
next_id = decoded.next_id or 1,
|
next_id = decoded.next_id or 1,
|
||||||
tasks = {},
|
tasks = {},
|
||||||
|
undo = {},
|
||||||
}
|
}
|
||||||
for _, t in ipairs(decoded.tasks or {}) do
|
for _, t in ipairs(decoded.tasks or {}) do
|
||||||
table.insert(_data.tasks, table_to_task(t))
|
table.insert(_data.tasks, table_to_task(t))
|
||||||
end
|
end
|
||||||
|
for _, snapshot in ipairs(decoded.undo or {}) do
|
||||||
|
if type(snapshot) == 'table' then
|
||||||
|
local tasks = {}
|
||||||
|
for _, raw in ipairs(snapshot) do
|
||||||
|
table.insert(tasks, table_to_task(raw))
|
||||||
|
end
|
||||||
|
table.insert(_data.undo, tasks)
|
||||||
|
end
|
||||||
|
end
|
||||||
return _data
|
return _data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return nil
|
||||||
function M.save()
|
function M.save()
|
||||||
if not _data then
|
if not _data then
|
||||||
return
|
return
|
||||||
|
|
@ -182,10 +195,18 @@ function M.save()
|
||||||
version = _data.version,
|
version = _data.version,
|
||||||
next_id = _data.next_id,
|
next_id = _data.next_id,
|
||||||
tasks = {},
|
tasks = {},
|
||||||
|
undo = {},
|
||||||
}
|
}
|
||||||
for _, task in ipairs(_data.tasks) do
|
for _, task in ipairs(_data.tasks) do
|
||||||
table.insert(out.tasks, task_to_table(task))
|
table.insert(out.tasks, task_to_table(task))
|
||||||
end
|
end
|
||||||
|
for _, snapshot in ipairs(_data.undo) do
|
||||||
|
local serialized = {}
|
||||||
|
for _, task in ipairs(snapshot) do
|
||||||
|
table.insert(serialized, task_to_table(task))
|
||||||
|
end
|
||||||
|
table.insert(out.undo, serialized)
|
||||||
|
end
|
||||||
local encoded = vim.json.encode(out)
|
local encoded = vim.json.encode(out)
|
||||||
local tmp = path .. '.tmp'
|
local tmp = path .. '.tmp'
|
||||||
local f = io.open(tmp, 'w')
|
local f = io.open(tmp, 'w')
|
||||||
|
|
@ -300,6 +321,7 @@ function M.find_index(id)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param tasks pending.Task[]
|
---@param tasks pending.Task[]
|
||||||
|
---@return nil
|
||||||
function M.replace_tasks(tasks)
|
function M.replace_tasks(tasks)
|
||||||
M.data().tasks = tasks
|
M.data().tasks = tasks
|
||||||
end
|
end
|
||||||
|
|
@ -325,11 +347,24 @@ function M.snapshot()
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return pending.Task[][]
|
||||||
|
function M.undo_stack()
|
||||||
|
return M.data().undo
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param stack pending.Task[][]
|
||||||
|
---@return nil
|
||||||
|
function M.set_undo_stack(stack)
|
||||||
|
M.data().undo = stack
|
||||||
|
end
|
||||||
|
|
||||||
---@param id integer
|
---@param id integer
|
||||||
|
---@return nil
|
||||||
function M.set_next_id(id)
|
function M.set_next_id(id)
|
||||||
M.data().next_id = id
|
M.data().next_id = id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return nil
|
||||||
function M.unload()
|
function M.unload()
|
||||||
_data = nil
|
_data = nil
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,10 @@ local function format_due(due)
|
||||||
if not due then
|
if not due then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
local y, m, d = due:match('^(%d%d%d%d)-(%d%d)-(%d%d)$')
|
local y, m, d, hh, mm = due:match('^(%d%d%d%d)-(%d%d)-(%d%d)T(%d%d):(%d%d)$')
|
||||||
|
if not y then
|
||||||
|
y, m, d = due:match('^(%d%d%d%d)-(%d%d)-(%d%d)$')
|
||||||
|
end
|
||||||
if not y then
|
if not y then
|
||||||
return due
|
return due
|
||||||
end
|
end
|
||||||
|
|
@ -30,7 +33,30 @@ local function format_due(due)
|
||||||
month = tonumber(m) --[[@as integer]],
|
month = tonumber(m) --[[@as integer]],
|
||||||
day = tonumber(d) --[[@as integer]],
|
day = tonumber(d) --[[@as integer]],
|
||||||
})
|
})
|
||||||
return os.date(config.get().date_format, t) --[[@as string]]
|
local formatted = os.date(config.get().date_format, t) --[[@as string]]
|
||||||
|
if hh then
|
||||||
|
formatted = formatted .. ' ' .. hh .. ':' .. mm
|
||||||
|
end
|
||||||
|
return formatted
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param due string
|
||||||
|
---@return boolean
|
||||||
|
local function is_overdue(due)
|
||||||
|
local now = os.date('*t') --[[@as osdate]]
|
||||||
|
local today = os.date('%Y-%m-%d') --[[@as string]]
|
||||||
|
local date_part, time_part = due:match('^(.+)T(.+)$')
|
||||||
|
if not date_part then
|
||||||
|
return due < today
|
||||||
|
end
|
||||||
|
if date_part < today then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if date_part > today then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local current_time = string.format('%02d:%02d', now.hour, now.min)
|
||||||
|
return time_part < current_time
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param tasks pending.Task[]
|
---@param tasks pending.Task[]
|
||||||
|
|
@ -74,7 +100,6 @@ end
|
||||||
---@return string[] lines
|
---@return string[] lines
|
||||||
---@return pending.LineMeta[] meta
|
---@return pending.LineMeta[] meta
|
||||||
function M.category_view(tasks)
|
function M.category_view(tasks)
|
||||||
local today = os.date('%Y-%m-%d') --[[@as string]]
|
|
||||||
local by_cat = {}
|
local by_cat = {}
|
||||||
local cat_order = {}
|
local cat_order = {}
|
||||||
local cat_seen = {}
|
local cat_seen = {}
|
||||||
|
|
@ -149,7 +174,7 @@ function M.category_view(tasks)
|
||||||
raw_due = task.due,
|
raw_due = task.due,
|
||||||
status = task.status,
|
status = task.status,
|
||||||
category = cat,
|
category = cat,
|
||||||
overdue = task.status == 'pending' and task.due ~= nil and task.due < today or nil,
|
overdue = task.status == 'pending' and task.due ~= nil and is_overdue(task.due) or nil,
|
||||||
recur = task.recur,
|
recur = task.recur,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
@ -162,7 +187,6 @@ end
|
||||||
---@return string[] lines
|
---@return string[] lines
|
||||||
---@return pending.LineMeta[] meta
|
---@return pending.LineMeta[] meta
|
||||||
function M.priority_view(tasks)
|
function M.priority_view(tasks)
|
||||||
local today = os.date('%Y-%m-%d') --[[@as string]]
|
|
||||||
local pending = {}
|
local pending = {}
|
||||||
local done = {}
|
local done = {}
|
||||||
|
|
||||||
|
|
@ -200,7 +224,7 @@ function M.priority_view(tasks)
|
||||||
raw_due = task.due,
|
raw_due = task.due,
|
||||||
status = task.status,
|
status = task.status,
|
||||||
category = task.category,
|
category = task.category,
|
||||||
overdue = task.status == 'pending' and task.due ~= nil and task.due < today or nil,
|
overdue = task.status == 'pending' and task.due ~= nil and is_overdue(task.due) or nil,
|
||||||
show_category = true,
|
show_category = true,
|
||||||
recur = task.recur,
|
recur = task.recur,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue