local config = require('todo.config') local parse = require('todo.parse') local store = require('todo.store') ---@class todo.ParsedEntry ---@field type 'task'|'header'|'blank' ---@field id? integer ---@field description? string ---@field priority? integer ---@field category? string ---@field due? string ---@field lnum integer ---@class todo.diff local M = {} ---@return string local function timestamp() return os.date('!%Y-%m-%dT%H:%M:%SZ') end ---@param lines string[] ---@return todo.ParsedEntry[] function M.parse_buffer(lines) local result = {} local current_category = nil for i, line in ipairs(lines) do local id, body = line:match('^/(%d+)/( .+)$') if not id then body = line:match('^( .+)$') end if line == '' then table.insert(result, { type = 'blank', lnum = i }) elseif id or body then local stripped = body:match('^ (.+)$') or body local priority = 0 if stripped:match('^! ') then priority = 1 stripped = stripped:sub(3) end local description, metadata = parse.body(stripped) if description and description ~= '' then table.insert(result, { type = 'task', id = id and tonumber(id) or nil, description = description, priority = priority, category = metadata.cat or current_category or config.get().default_category, due = metadata.due, lnum = i, }) end elseif line:match('^%S') then current_category = line table.insert(result, { type = 'header', category = line, lnum = i }) end end return result end ---@param lines string[] function M.apply(lines) local parsed = M.parse_buffer(lines) local now = timestamp() local data = store.data() local old_by_id = {} for _, task in ipairs(data.tasks) do if task.status ~= 'deleted' then old_by_id[task.id] = task end end local seen_ids = {} local order_counter = 0 for _, entry in ipairs(parsed) do if entry.type ~= 'task' then goto continue end order_counter = order_counter + 1 if entry.id and old_by_id[entry.id] then if seen_ids[entry.id] then store.add({ description = entry.description, category = entry.category, priority = entry.priority, due = entry.due, order = order_counter, }) else seen_ids[entry.id] = true local task = old_by_id[entry.id] local changed = false if task.description ~= entry.description then task.description = entry.description changed = true end if task.category ~= entry.category then task.category = entry.category changed = true end if task.priority ~= entry.priority then task.priority = entry.priority changed = true end if task.due ~= entry.due then task.due = entry.due changed = true end if task.order ~= order_counter then task.order = order_counter changed = true end if changed then task.modified = now end end else store.add({ description = entry.description, category = entry.category, priority = entry.priority, due = entry.due, order = order_counter, }) end ::continue:: end for id, task in pairs(old_by_id) do if not seen_ids[id] then task.status = 'deleted' task['end'] = now task.modified = now end end store.save() end return M