hephaestus/heph.nvim/tests/e2e/frontmatter_spec.lua
Erich Blume a030ad3034 feat(nvim): italicize inline #hashtags in node buffers (§8.3)
A buffer-local syntax match (`HephHashtag`, italic, `default`-overridable)
highlights whitespace-prefixed #hashtags so they're visually obvious —
mirroring the save-time tag detection (a `# heading` doesn't match).
Attached alongside the <CR> link follow. Syntax match is the right-sized
tool here (treesitter has no hashtag node; extmark conceal is reserved
for the upcoming [[link]] display layer). e2e asserts the italic hl group.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 12:01:36 -07:00

99 lines
3.8 KiB
Lua

-- Frontmatter edit surface (tech-spec §8.3): a node buffer opens with an
-- editable YAML block on top, and saving routes each changed field to the right
-- structured RPC (rename / set_attention / set_schedule / set_project /
-- tag.add). The body itself still round-trips through node.update.
local h = require("e2e.helpers")
-- Replace `key: …` in the frontmatter `lines`, or insert it before the closing
-- `---` fence when the key isn't present yet.
local function set_field(lines, key, value)
for i, line in ipairs(lines) do
if line:match("^" .. key .. ":") then
lines[i] = key .. ": " .. value
return
end
end
local fences = 0
for i, line in ipairs(lines) do
if line == "---" then
fences = fences + 1
if fences == 2 then
table.insert(lines, i, key .. ": " .. value)
return
end
end
end
end
describe("frontmatter edit surface", function()
local ctx
before_each(function()
ctx = h.start()
end)
after_each(function()
h.stop(ctx)
end)
it("renders an editable block atop a node body", function()
local doc = h.create_doc("Roof", "# Roof\n\nnotes")
local buf = h.open(doc.id)
local first = vim.api.nvim_buf_get_lines(buf, 0, 1, false)[1]
assert.are.equal("---", first, "buffer should open with a frontmatter fence")
assert.is_truthy(h.find(buf, "^title: Roof"), "title not in frontmatter")
assert.is_truthy(h.find(buf, "# Roof"), "body content missing below frontmatter")
-- Inline #hashtags are rendered italic for visibility.
assert.is_true(vim.api.nvim_get_hl(0, { name = "HephHashtag" }).italic, "hashtag hl not italic")
end)
it("routes frontmatter edits to structured RPCs on save", function()
local proj = ctx.q:call("node.create", { kind = "project", title = "Camano" })
local task = ctx.q:call("task.create", { title = "Fix roof", attention = "red" })
local ctxid
for _, l in ipairs(ctx.q:call("links.outgoing", { id = task.node_id })) do
if l.link_type == "canonical-context" then
ctxid = l.dst_id
end
end
local buf = h.open(ctxid)
local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
set_field(lines, "title", "Fix the roof") -- rename the (doc) node
set_field(lines, "attention", "blue") -- → task.set_attention
set_field(lines, "tags", "[roofing]") -- → tag.add on the doc
set_field(lines, "project", "Camano") -- → task.set_project
set_field(lines, "do_date", "2026-06-10") -- → task.set_schedule
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
h.save(buf)
-- The task picked up attention, project, and a do-date.
local t = ctx.q:call("task.get", { id = task.node_id })
assert.are.equal("blue", t.attention)
assert.is_truthy(t.do_date, "do_date should be set")
local filed = false
for _, l in ipairs(ctx.q:call("links.outgoing", { id = task.node_id })) do
if l.link_type == "in-project" and l.dst_id == proj.id then
filed = true
end
end
assert.is_true(filed, "task should be filed under Camano")
-- The opened node was renamed and tagged.
assert.are.equal("Fix the roof", ctx.q:call("node.get", { id = ctxid }).title)
local tags = ctx.q:call("tag.list", { node_id = ctxid })
assert.are.equal(1, #tags)
assert.are.equal("roofing", tags[1])
end)
it("adds inline #hashtags from the body as tags on save", function()
local doc = h.create_doc("Notes", "# Notes")
local buf = h.open(doc.id)
-- Append a body line with an inline hashtag (a `# heading` must not match).
vim.api.nvim_buf_set_lines(buf, -1, -1, false, { "", "see #kitchen and # not-a-tag" })
h.save(buf)
local tags = ctx.q:call("tag.list", { node_id = doc.id })
assert.are.equal(1, #tags, "exactly the one inline tag")
assert.are.equal("kitchen", tags[1])
end)
end)