generated from eblume/project-template
heph.nvim: interactive next/list views (add, done, refresh) + key hint
Some checks failed
Build / validate (pull_request) Failing after 4m47s
Some checks failed
Build / validate (pull_request) Failing after 4m47s
The Tactical next and Organizational list buffers are now actionable: - a add a task from the list (prompt title + attention) - d mark the task under the cursor done - r refresh - <CR> open the task's context (as before) A dimmed key hint renders above the rows as a virtual line (extmark), so it's discoverable without taking a task row. e2e covers add-from-list and done-from-list via stubbed vim.ui.input/select. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d0930aa6a3
commit
0462a6e43b
4 changed files with 149 additions and 20 deletions
|
|
@ -1,22 +1,38 @@
|
|||
--- 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; `<CR>` opens the task
|
||||
--- under the cursor's canonical-context doc (the one-keystroke jump).
|
||||
|
||||
--- titled rows the daemon returns into a scratch buffer, and are interactive:
|
||||
--- <CR> 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 = <RankedTask[]> }; line N maps to tasks[N].
|
||||
-- buf -> { tasks = <RankedTask[]>, refresh = fn }; line N maps to tasks[N].
|
||||
M._views = {}
|
||||
|
||||
local hint_ns = vim.api.nvim_create_namespace("heph_view_hint")
|
||||
local HINT = " <CR> 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
|
||||
|
||||
-- Find or create the named scratch buffer and fill it with task rows.
|
||||
local function render(name, tasks)
|
||||
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
|
||||
|
|
@ -37,16 +53,36 @@ local function render(name, tasks)
|
|||
lines[#lines + 1] = row(t)
|
||||
end
|
||||
if #lines == 0 then
|
||||
lines = { "(nothing here)" }
|
||||
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
|
||||
|
||||
M._views[buf] = { tasks = tasks }
|
||||
vim.keymap.set("n", "<CR>", function()
|
||||
-- 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("<CR>", function()
|
||||
M.open_under_cursor(buf)
|
||||
end, { buffer = buf, desc = "heph: open task context" })
|
||||
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
|
||||
|
|
@ -54,23 +90,52 @@ 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 view = M._views[buf]
|
||||
if not view then
|
||||
return
|
||||
local t = task_on_line(buf)
|
||||
if t then
|
||||
require("heph.node").open(t.canonical_context_id or t.node_id)
|
||||
end
|
||||
local lnum = vim.api.nvim_win_get_cursor(0)[1]
|
||||
local t = view.tasks[lnum]
|
||||
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
|
||||
require("heph.node").open(t.canonical_context_id or t.node_id)
|
||||
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)
|
||||
render("heph://next", tasks, function()
|
||||
M.next(opts)
|
||||
end)
|
||||
return tasks
|
||||
end
|
||||
|
||||
|
|
@ -82,7 +147,9 @@ function M.list(opts)
|
|||
attention = opts.attention,
|
||||
include_blue = opts.include_blue ~= false,
|
||||
})
|
||||
render("heph://list", tasks)
|
||||
render("heph://list", tasks, function()
|
||||
M.list(opts)
|
||||
end)
|
||||
return tasks
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue