From 162c6cecedf407acbff4f29f2c43d09d79df2ff8 Mon Sep 17 00:00:00 2001 From: Thomas Smallwood Date: Thu, 5 Mar 2026 23:56:42 +0100 Subject: [PATCH] mardown tasks --- .claude/settings.local.json | 4 +- lazy-lock.json | 3 +- lua/config/markdown-tasks.lua | 118 +++++++++++++++++++++++++++++----- 3 files changed, 107 insertions(+), 18 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 90dc654..38594f8 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,7 +1,9 @@ { "permissions": { "allow": [ - "Bash(grep -rn \"nvim_exec_autocmds\\\\|User.*ClaudeCode\\\\|ClaudeCode.*User\" /home/user/.local/share/nvim/lazy/claudecode.nvim/ --include=\"*.lua\" 2>/dev/null | grep -v test | grep -v spec)" + "Bash(grep -rn \"nvim_exec_autocmds\\\\|User.*ClaudeCode\\\\|ClaudeCode.*User\" /home/user/.local/share/nvim/lazy/claudecode.nvim/ --include=\"*.lua\" 2>/dev/null | grep -v test | grep -v spec)", + "Bash(grep:*)", + "Bash(python3:*)" ] } } diff --git a/lazy-lock.json b/lazy-lock.json index 8359819..8c7f3c7 100644 --- a/lazy-lock.json +++ b/lazy-lock.json @@ -1,10 +1,10 @@ { "CopilotChat.nvim": { "branch": "main", "commit": "743d6005fb412c85309d3f3aa45f18f3a2fb2098" }, "LazyVim": { "branch": "main", "commit": "fca0af57cc3851b14f96a795a9c9bfafc5096dd1" }, + "blink-copilot": { "branch": "main", "commit": "7ad8209b2f880a2840c94cdcd80ab4dc511d4f39" }, "blink.cmp": { "branch": "main", "commit": "4b18c32adef2898f95cdef6192cbd5796c1a332d" }, "bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" }, "catppuccin": { "branch": "main", "commit": "0a5de4da015a175f416d6ef1eda84661623e0500" }, - "claudecode.nvim": { "branch": "main", "commit": "aa9a5cebebdbfa449c1c5ff229ba5d98e66bafed" }, "conform.nvim": { "branch": "master", "commit": "40dcec5555f960b0a04340d76eabdf4efe78599d" }, "copilot.lua": { "branch": "master", "commit": "00446a63cba4cc59bb24fc1e210a555a3e4acdfb" }, "flash.nvim": { "branch": "main", "commit": "fcea7ff883235d9024dc41e638f164a450c14ca2" }, @@ -14,7 +14,6 @@ "lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" }, "lazydev.nvim": { "branch": "main", "commit": "5231c62aa83c2f8dc8e7ba957aa77098cda1257d" }, "lualine.nvim": { "branch": "master", "commit": "47f91c416daef12db467145e16bed5bbfe00add8" }, - "markdown-preview.nvim": { "branch": "master", "commit": "a923f5fc5ba36a3b17e289dc35dc17f66d0548ee" }, "mason-lspconfig.nvim": { "branch": "main", "commit": "a324581a3c83fdacdb9804b79de1cbe00ce18550" }, "mason.nvim": { "branch": "main", "commit": "44d1e90e1f66e077268191e3ee9d2ac97cc18e65" }, "mini.ai": { "branch": "main", "commit": "4b0a6207341d895b6cfe9bcb1e4d3e8607bfe4f4" }, diff --git a/lua/config/markdown-tasks.lua b/lua/config/markdown-tasks.lua index fd3ed8c..609fc22 100644 --- a/lua/config/markdown-tasks.lua +++ b/lua/config/markdown-tasks.lua @@ -25,15 +25,74 @@ local function new_task() end local function toggle_task() - local line = vim.api.nvim_get_current_line() + local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) + local row = vim.api.nvim_win_get_cursor(0)[1] + local line = lines[row] + if not line:find("%[[ x]%]") then return end + + -- Toggle current line + local new_state if line:find("%[ %]") then - vim.api.nvim_set_current_line((line:gsub("%[ %]", "[x]", 1))) - elseif line:find("%[x%]") then - vim.api.nvim_set_current_line((line:gsub("%[x%]", "[ ]", 1))) + lines[row] = line:gsub("%[ %]", "[x]", 1) + new_state = "[x]" else - return + lines[row] = line:gsub("%[x%]", "[ ]", 1) + new_state = "[ ]" end - -- stats updated below after toggle + + local cur_indent = #(line:match("^(%s*)") or "") + + -- Cascade down: set all deeper-indented children to same state + for i = row + 1, #lines do + local l = lines[i] + if not l:match("^%s*%-") then break end + local indent = #(l:match("^(%s*)") or "") + if indent <= cur_indent then break end + if new_state == "[x]" then + lines[i] = l:gsub("%[ %]", "[x]", 1) + else + lines[i] = l:gsub("%[x%]", "[ ]", 1) + end + end + + -- Cascade up: find parent (less indent, is a task) + local parent_row + for i = row - 1, 1, -1 do + local l = lines[i] + if not l:match("^%s*%-") then break end + local indent = #(l:match("^(%s*)") or "") + if indent < cur_indent and l:find("%[[ x]%]") then + parent_row = i + break + end + end + + if parent_row then + if new_state == "[ ]" and lines[parent_row]:find("%[x%]") then + -- Unchecking child: uncheck parent + lines[parent_row] = lines[parent_row]:gsub("%[x%]", "[ ]", 1) + elseif new_state == "[x]" then + -- Checking child: check parent if all siblings are now done + local parent_indent = #(lines[parent_row]:match("^(%s*)") or "") + local all_done = true + for i = parent_row + 1, #lines do + local l = lines[i] + if not l:match("^%s*%-") then break end + local indent = #(l:match("^(%s*)") or "") + if indent <= parent_indent then break end + if indent == cur_indent and l:find("%[ %]") then + all_done = false + break + end + end + if all_done then + lines[parent_row] = lines[parent_row]:gsub("%[ %]", "[x]", 1) + end + end + end + + vim.api.nvim_buf_set_lines(0, 0, -1, false, lines) + vim.api.nvim_win_set_cursor(0, { row, vim.api.nvim_win_get_cursor(0)[2] }) local update = require("config.markdown-tasks")._update_stats if update then update() end end @@ -62,9 +121,22 @@ end -- ── Section task stats ──────────────────────────────────────────────────────── +-- Parses a "DD-MM-YYYY" string into a Unix timestamp for comparison. +local function date_to_time(s) + local d, m, y = s:match("(%d+)-(%d+)-(%d+)") + if not d then return 0 end + return os.time({ year = tonumber(y), month = tonumber(m), day = tonumber(d) }) +end + -- Strips the stats suffix from a heading line, returning the bare heading. local function strip_stats(line) - return (line:gsub("%s+✅%d+/%d+%s*$", "")) + return (line + :gsub("%s+✅ ?%d+/%d+ ?✅%s*$", "") + :gsub("%s+❌ ?%d+/%d+ ?❌%s*$", "") + :gsub("%s+📜 ?%d+/%d+ ?📜%s*$", "") + :gsub("%s+⏳ ?%d+/%d+ ?⏳%s*$", "") + :gsub("%s+📅 ?%d+/%d+ ?📅%s*$", "") + :gsub("%s+🚨 ?%d+/%d+ ?🚨%s*$", "")) end -- Counts [x] and [ ] tasks in a section starting at header_line (1-indexed). @@ -81,6 +153,19 @@ local function count_section_tasks(lines, header_line) return done, todo end +local function classify_section(base) + local date_str = base:match("^## (%d%d%-%d%d%-%d%d%d%d)$") + if not date_str then return "general" end + local section_time = date_to_time(date_str) + local today_start = date_to_time(os.date("%d-%m-%Y")) + if section_time == today_start then return "today" end + return section_time > today_start and "future" or "past" +end + +local section_icons = { + general = "📜", today = "⏳", future = "📅", past = "❌", +} + -- Updates every ## heading with a live task count suffix. local function update_section_stats() local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) @@ -89,7 +174,17 @@ local function update_section_stats() if line:match("^## ") then local base = strip_stats(line) local done, todo = count_section_tasks(lines, i) - local new_line = base .. " ✅" .. done .. "/" .. todo + local total = done + todo + local kind = classify_section(base) + local icon + if total > 0 and done == total then + icon = "✅" + elseif kind == "today" and done == 0 and total >= 2 then + icon = "🚨" + else + icon = section_icons[kind] + end + local new_line = base .. " " .. icon .. " " .. done .. "/" .. total .. " " .. icon if new_line ~= line then lines[i] = new_line changed = true @@ -166,13 +261,6 @@ local function section_end(header_line) return last end --- Parses a "DD-MM-YYYY" string into a Unix timestamp for comparison. -local function date_to_time(s) - local d, m, y = s:match("(%d+)-(%d+)-(%d+)") - if not d then return 0 end - return os.time({ year = tonumber(y), month = tonumber(m), day = tonumber(d) }) -end - -- Inserts a new "## date_str" line in descending date order (newest first). -- Blank lines are left to normalize_spacing. local function insert_date_section(date_str)