--- Task list views (tech-spec §8): Tactical `next` (the "what is next?" ranking) --- and Organizational `list` (the whole outstanding set). Both render the same --- titled rows the daemon returns into a scratch buffer, and are interactive: --- open the task's canonical-context doc --- a add a new task (prompt title + attention) from the list --- d mark the task under the cursor done --- r refresh local rpc = require("heph.rpc") local M = {} -- buf -> { tasks = , refresh = fn }; line N maps to tasks[N]. M._views = {} local hint_ns = vim.api.nvim_create_namespace("heph_view_hint") local HINT = " open a add d done r refresh" local ATTENTIONS = { "white", "orange", "red", "blue" } local function row(t) local tag = t.attention and ("[" .. t.attention .. "]") or "[ ]" return string.format("%s %s", tag, t.title) end local function task_on_line(buf) local view = M._views[buf] if not view then return nil end return view.tasks[vim.api.nvim_win_get_cursor(0)[1]] end -- Find or create the named scratch buffer, fill it, and (re)bind its keymaps. -- `refresh` re-runs the query+render so actions can reflect their changes. local function render(name, tasks, refresh) local buf for _, b in ipairs(vim.api.nvim_list_bufs()) do if vim.api.nvim_buf_get_name(b) == name then buf = b break end end if not buf then buf = vim.api.nvim_create_buf(false, true) -- unlisted scratch vim.api.nvim_buf_set_name(buf, name) end vim.bo[buf].buftype = "nofile" vim.bo[buf].bufhidden = "hide" vim.bo[buf].swapfile = false local lines = {} for _, t in ipairs(tasks) do lines[#lines + 1] = row(t) end if #lines == 0 then lines = { "(nothing here — press 'a' to add a task)" } end vim.bo[buf].modifiable = true vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) vim.bo[buf].modifiable = false -- A dimmed key hint above the first row — a virtual line, so it isn't a task -- row (cursor lines still map 1:1 to tasks). vim.api.nvim_buf_clear_namespace(buf, hint_ns, 0, -1) vim.api.nvim_buf_set_extmark(buf, hint_ns, 0, 0, { virt_lines_above = true, virt_lines = { { { HINT, "Comment" } } }, }) M._views[buf] = { tasks = tasks, refresh = refresh } local function map(lhs, fn, desc) vim.keymap.set("n", lhs, fn, { buffer = buf, desc = desc }) end map("", function() M.open_under_cursor(buf) end, "heph: open task context") map("a", function() M.add_from(buf) end, "heph: add a task") map("d", function() M.done_under_cursor(buf) end, "heph: mark task done") map("r", function() M.refresh(buf) end, "heph: refresh") vim.api.nvim_set_current_buf(buf) return buf end --- Open the canonical-context doc of the task on the cursor line. function M.open_under_cursor(buf) buf = buf or vim.api.nvim_get_current_buf() local t = task_on_line(buf) if t then require("heph.node").open(t.canonical_context_id or t.node_id) end end --- Re-run the view's query and re-render in place. function M.refresh(buf) local view = M._views[buf or vim.api.nvim_get_current_buf()] if view and view.refresh then view.refresh() end end --- Add a task from the list: prompt a title, pick an attention, capture, refresh. function M.add_from(buf) vim.ui.input({ prompt = "New task: " }, function(title) if not title or #title == 0 then return end require("heph.picker").select(ATTENTIONS, { prompt = "attention for: " .. title }, function(attention) require("heph.task").capture(title, { attention = attention }) require("heph.util").notify("captured: " .. title) M.refresh(buf) end) end) end --- Mark the task on the cursor line done, then refresh. function M.done_under_cursor(buf) local t = task_on_line(buf) if not t then return end rpc.call("task.set_state", { id = t.node_id, state = "done" }) require("heph.util").notify("done: " .. t.title) M.refresh(buf) end --- Tactical "what is next?" — render the ranking, return the rows. function M.next(opts) opts = opts or {} local tasks = rpc.call("next", { scope = opts.scope, limit = opts.limit or 5 }) render("heph://next", tasks, function() M.next(opts) end) return tasks end --- Organizational survey — render the outstanding set, return the rows. function M.list(opts) opts = opts or {} local tasks = rpc.call("list", { scope = opts.scope, attention = opts.attention, include_blue = opts.include_blue ~= false, }) render("heph://list", tasks, function() M.list(opts) end) return tasks end return M