From a030ad3034d3ec5309380095acae799a5987eb4b Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Wed, 3 Jun 2026 12:01:36 -0700 Subject: [PATCH] =?UTF-8?q?feat(nvim):=20italicize=20inline=20#hashtags=20?= =?UTF-8?q?in=20node=20buffers=20(=C2=A78.3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 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) --- docs/changelog.d/v1-prototype.feature.md | 2 +- heph.nvim/lua/heph/link.lua | 11 ++++++++++- heph.nvim/tests/e2e/frontmatter_spec.lua | 2 ++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/changelog.d/v1-prototype.feature.md b/docs/changelog.d/v1-prototype.feature.md index 68d643c..451405b 100644 --- a/docs/changelog.d/v1-prototype.feature.md +++ b/docs/changelog.d/v1-prototype.feature.md @@ -28,6 +28,6 @@ Begin the v1 prototype (Phase 1, tech-spec §11.1), built in TDD slices: - `heph-tui` task-list visuals (§8.1): each row now leads with an attention **flag** (`⚑`, colored red/orange/blue; blank for white) and a **project-colored bullet** — the bullet's color is derived stably from the project id (so it survives projects being added/removed), letting you scan a mixed list by project at a glance. The list also grows a **scrollbar** and keeps the selected task scrolled into view when there are more tasks than fit. - `heph-tui` sort toggle (§8.1): **`s`** flips the task list between two orders — **default** (attention → most-overdue → project → creation) and **by-project** (grouped under dimmed `──── Project ────` separators, then the same sub-order). The view's filter still applies first. (To free `s`, **skip** moved to **`S`**.) - `heph.nvim` task-view rows (§8): `:Heph next`/`:Heph list` rows now show a compact **do/late date chip** (and a recurrence `↻`), so you can see scheduling at a glance; `` still jumps to a task's context doc. -- Frontmatter editing in heph.nvim (§8.3): opening a node now shows an editable **YAML frontmatter** block on top of the body (`id`/`kind`/`title`/`tags`, and for a task or its context doc the task's `state`/`attention`/`do_date`/`late_on`/`recurrence`/`project`). On save, the plugin diffs the block and issues the right RPC per changed field — rename, set-attention, reschedule (dates as `YYYY-MM-DD`), move-to-project (by name), and tag add/remove — then saves the body; the store strips the block so it never persists. A mistyped `state` surfaces a validation error; a buffer with no block changes no metadata (so deleting the block can't wipe your tags). Inline **`#hashtags`** typed in the body are also added as tags on save (a `# heading` doesn't count). Link-follow and promotion are unaffected (they're content-relative, not line-absolute). +- Frontmatter editing in heph.nvim (§8.3): opening a node now shows an editable **YAML frontmatter** block on top of the body (`id`/`kind`/`title`/`tags`, and for a task or its context doc the task's `state`/`attention`/`do_date`/`late_on`/`recurrence`/`project`). On save, the plugin diffs the block and issues the right RPC per changed field — rename, set-attention, reschedule (dates as `YYYY-MM-DD`), move-to-project (by name), and tag add/remove — then saves the body; the store strips the block so it never persists. A mistyped `state` surfaces a validation error; a buffer with no block changes no metadata (so deleting the block can't wipe your tags). Inline **`#hashtags`** typed in the body are also added as tags on save (a `# heading` doesn't count) and are rendered in **italics** so they stand out. Link-follow and promotion are unaffected (they're content-relative, not line-absolute). - Frontmatter projection (§8.3): a node can now be fetched with an editable **YAML frontmatter** block prepended — `node.get {frontmatter: true}` renders `id`/`kind`/`title`/`tags`, and for a task (or its context doc) the owning task's `state`/`attention`/`do_date`/`late_on`/`recurrence`/`project` plus a `task:` ref. Dates are local `YYYY-MM-DD`. On write, the store **strips and ignores** any leading frontmatter (conservatively — a real `---` hrule in prose survives) before the CRDT diff, so frontmatter never persists and an unchanged read→write is a no-op; a naive editor can't corrupt metadata. This is the read/write groundwork for editing a node's metadata as frontmatter in heph.nvim (the diff-into-RPCs layer is next). - Tags (§4, §8.3): nodes can now be **tagged**. A tag is a `tag`-kind node whose id is deterministic in `(owner, name)`, so the same name is **one canonical tag** shared across everything it's applied to (and replicas converge — no duplicate tags). Tagging is an OR-set link, so adding/removing is idempotent and merge-safe. Surfaced as `tag.add`/`tag.remove`/`tag.list` RPCs and `heph tag add|rm|list` (list a node's tags, or every tag with no node). Tag names are trimmed; a canonical case/spelling normalization is deferred to the future zk import. This is the groundwork for the `tags:` line of the upcoming frontmatter edit surface. diff --git a/heph.nvim/lua/heph/link.lua b/heph.nvim/lua/heph/link.lua index 1af44a4..7b8e963 100644 --- a/heph.nvim/lua/heph/link.lua +++ b/heph.nvim/lua/heph/link.lua @@ -68,11 +68,20 @@ function M.follow() require("heph.node").open(node.id) end ---- Attach the buffer-local `` follow keymap (only on heph:// buffers). +--- Attach the buffer-local `` follow keymap and inline-`#hashtag` +--- highlighting (only on heph:// buffers). function M.attach(buf) vim.keymap.set("n", "", function() M.follow() end, { buffer = buf, desc = "heph: follow [[link]]" }) + + -- Render inline #hashtags in italics so they stand out — matching the + -- save-time tag detection (whitespace-prefixed `#word`, never a `# heading`). + -- `default = true` leaves a user's own `HephHashtag` definition intact. + vim.api.nvim_set_hl(0, "HephHashtag", { italic = true, default = true }) + vim.api.nvim_buf_call(buf, function() + vim.cmd([[syntax match HephHashtag /\v%(^|\s)@<=#[0-9A-Za-z_-]+/ containedin=ALL]]) + end) end return M diff --git a/heph.nvim/tests/e2e/frontmatter_spec.lua b/heph.nvim/tests/e2e/frontmatter_spec.lua index da0dc32..c0317b4 100644 --- a/heph.nvim/tests/e2e/frontmatter_spec.lua +++ b/heph.nvim/tests/e2e/frontmatter_spec.lua @@ -42,6 +42,8 @@ describe("frontmatter edit surface", function() 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()