From c39361726fa9cf1623d49ea1b9b2dd7e43f67622 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Sat, 14 Nov 2026 11:27:41 +0100 Subject: feat(calendar): list due tasks from a task vdir beside the events The sampler reads open VTODOs with a due date from ~/.local/share/tasks//*.ics (a vdirsyncer filesystem storage, override with UDT_TASKS_DIR) and writes them to the cache as ISO 'task' lines. The card merges them into the week's rows by date, with a checkbox before the title and an urgency colour: overdue critical, due today warning, later label. Overdue tasks stay until done, so they lead the table. Tasks never mark the month grid. Co-Authored-By: Claude Opus 5.5 --- lib/data.lua | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) (limited to 'lib/data.lua') diff --git a/lib/data.lua b/lib/data.lua index dca039a..75dedcd 100644 --- a/lib/data.lua +++ b/lib/data.lua @@ -36,7 +36,8 @@ end -- The calendar cache written by bin/calendar-sample.sh. -- -- Returns { today = { y, m, d }, colors = { = }, --- events = { { y, m, d, start, finish, calendar, title }, ... } }, or nil when +-- events = { { y, m, d, start, finish, calendar, title }, ... }, +-- tasks = { { y, m, d, start, title, task = true }, ... } }, or nil when -- the cache is absent or carries no 'generated' key. nil means "the sampler -- never ran", which the card names; an empty events list means "nothing in the -- next week", which is a different and equally valid state. @@ -50,7 +51,7 @@ end function M.calendar_parse(text) if type(text) ~= 'string' then return nil end - local out = { colors = {}, events = {} } + local out = { colors = {}, events = {}, tasks = {} } local generated = false for line in text:gmatch('[^\n]+') do @@ -77,6 +78,16 @@ function M.calendar_parse(text) title = title, } end + elseif kind == 'task' then + -- Written by the sampler from the task vdir, so the date is ISO: no + -- positional guessing as for khal's events. + local y, m, d, st, title = rest:match('^(%d+)-(%d+)-(%d+)|([^|]*)|(.*)$') + if y then + out.tasks[#out.tasks + 1] = { + y = tonumber(y), m = tonumber(m), d = tonumber(d), + start = st ~= '' and st or nil, title = title, task = true, + } + end end end @@ -146,6 +157,25 @@ function M.calendar_upcoming(events, today, days) return out end +-- The card's table: the week's events and the open tasks due in it, as one +-- list by date. Overdue tasks pass calendar_upcoming's window (a negative day +-- offset is under any limit), so they lead the list, which is where they +-- belong. Within a day an all-day row ('' start) sorts before a timed one. +function M.calendar_agenda(cal, today, days) + local rows = M.calendar_upcoming(cal.events, today, days) + for _, t in ipairs(M.calendar_upcoming(cal.tasks, today, days)) do + rows[#rows + 1] = t + end + -- The index breaks ties, since table.sort is not stable. + local key = {} + for i, e in ipairs(rows) do + key[e] = string.format('%04d%02d%02d %-5s %04d', e.y, e.m, e.d, + e.start or '', i) + end + table.sort(rows, function(a, b) return key[a] < key[b] end) + return rows +end + -- Days in a month, and the weekday its 1st falls on. -- -- os.time/os.date rather than a leap-year rule: the C library already knows, -- cgit v1.2.3