diff --git a/lua/mappings.lua b/lua/mappings.lua index c5399fd..79bf880 100644 --- a/lua/mappings.lua +++ b/lua/mappings.lua @@ -64,3 +64,12 @@ vim.keymap.set("n", "gb", ":Gitsigns blame_line", { noremap = true, -- Disable alt+h and alt+v terminal toggles vim.keymap.del({ "n", "t" }, "") vim.keymap.del({ "n", "t" }, "") + +-- td: time-track markdown todos (see ~/.local/bin/td and ~/.config/nvim/lua/td.lua) +local td = require("td") +vim.api.nvim_create_user_command("TdStart", td.start, {}) +vim.api.nvim_create_user_command("TdStop", td.stop, {}) +vim.api.nvim_create_user_command("TdReport", td.report, {}) +vim.keymap.set("n", "ts", td.start, { desc = "td: start timer on current task" }) +vim.keymap.set("n", "tp", td.stop, { desc = "td: stop (pause) timer" }) +vim.keymap.set("n", "tr", td.report, { desc = "td: report floating window" }) diff --git a/lua/td.lua b/lua/td.lua new file mode 100644 index 0000000..850e39b --- /dev/null +++ b/lua/td.lua @@ -0,0 +1,104 @@ +local M = {} + +local function vault_root() + local file = vim.api.nvim_buf_get_name(0) + local dir = file ~= "" and vim.fn.fnamemodify(file, ":p:h") or vim.fn.getcwd() + local probe = dir + while probe ~= "/" and probe ~= "" do + if vim.loop.fs_stat(probe .. "/time.csv") or vim.loop.fs_stat(probe .. "/.obsidian") then + return probe + end + local parent = vim.fn.fnamemodify(probe, ":h") + if parent == probe then break end + probe = parent + end + return vim.fn.getcwd() +end + +local function relpath(abs, base) + local a = vim.fn.fnamemodify(abs, ":p") + local b = vim.fn.fnamemodify(base, ":p"):gsub("/$", "") + if a:sub(1, #b + 1) == b .. "/" then return a:sub(#b + 2) end + return a +end + +local function run(cmd, cwd, on_done) + local stdout, stderr = {}, {} + vim.fn.jobstart(cmd, { + cwd = cwd, + stdout_buffered = true, + stderr_buffered = true, + on_stdout = function(_, d) for _, l in ipairs(d) do if l ~= "" then stdout[#stdout + 1] = l end end end, + on_stderr = function(_, d) for _, l in ipairs(d) do if l ~= "" then stderr[#stderr + 1] = l end end end, + on_exit = function(_, code) + vim.schedule(function() if on_done then on_done(stdout, stderr, code) end end) + end, + }) +end + +local function notify(lines, level) + if #lines == 0 then return end + vim.notify(table.concat(lines, "\n"), level or vim.log.levels.INFO) +end + +local function extract_desc(line) + return (line:gsub("^%s*%-%s*%[.%]%s*", ""):gsub("%s*%^%S+%s*$", ""):gsub("%s+$", "")) +end + +local function ensure_tid_on_line() + local line = vim.api.nvim_get_current_line() + local tid = line:match("%^(tid%-%S+)") + if tid then return tid end + if not line:match("^%s*%-%s*%[.%]") then + vim.notify("td: not on a todo line (- [ ] ...)", vim.log.levels.WARN) + return nil + end + tid = os.date("tid-%Y%m%d-%H%M%S") + local new = line:gsub("%s+$", "") .. " ^" .. tid + vim.api.nvim_set_current_line(new) + vim.cmd("silent! write") + return tid +end + +function M.start() + local tid = ensure_tid_on_line() + if not tid then return end + local cwd = vault_root() + local file = relpath(vim.api.nvim_buf_get_name(0), cwd) + local desc = extract_desc(vim.api.nvim_get_current_line()) + run({ "td", "start", tid, "--file", file, "--desc", desc }, cwd, function(out, err) + notify(out); notify(err, vim.log.levels.WARN) + end) +end + +function M.stop() + run({ "td", "stop" }, vault_root(), function(out, err) + notify(out); notify(err, vim.log.levels.WARN) + end) +end + +function M.report() + run({ "td", "report" }, vault_root(), function(out, err) + if #out == 0 then notify(err, vim.log.levels.WARN); return end + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(buf, 0, -1, false, out) + vim.api.nvim_buf_set_option(buf, "modifiable", false) + local width = math.min(100, vim.o.columns - 4) + local height = math.min(20, #out + 2) + vim.api.nvim_open_win(buf, true, { + relative = "editor", + width = width, + height = height, + row = math.floor((vim.o.lines - height) / 2), + col = math.floor((vim.o.columns - width) / 2), + style = "minimal", + border = "rounded", + title = " td report ", + title_pos = "center", + }) + vim.api.nvim_buf_set_keymap(buf, "n", "q", "close", { noremap = true, silent = true }) + vim.api.nvim_buf_set_keymap(buf, "n", "", "close", { noremap = true, silent = true }) + end) +end + +return M