generated from eblume/project-template
feat(nvim): inline #hashtags become tags on save (§8.3)
Some checks failed
Build / validate (pull_request) Failing after 4m57s
Some checks failed
Build / validate (pull_request) Failing after 4m57s
On save, whitespace-prefixed `#hashtags` in a node's body are unioned into its tag set (via `frontmatter.hashtags` + the existing tag diff), so you can tag a note by writing `#kitchen` inline. A markdown `# heading` has a space after the `#`, so it never matches. e2e covers it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d85ce3362f
commit
8dc98dc9c1
5 changed files with 54 additions and 9 deletions
|
|
@ -79,6 +79,35 @@ local function date_to_ms(s)
|
|||
return os.time({ year = tonumber(y), month = tonumber(m), day = tonumber(d), hour = 0, min = 0, sec = 0 }) * 1000
|
||||
end
|
||||
|
||||
--- Whitespace-prefixed inline `#hashtags` in `body`, de-duplicated in order.
|
||||
--- A markdown heading (`# Title`, `## foo`) has a space after the `#`, so it
|
||||
--- never matches. (Scanned across the whole body, code fences included — a
|
||||
--- pragmatic v1 simplification.)
|
||||
function M.hashtags(body)
|
||||
local seen, out = {}, {}
|
||||
for tag in (" " .. (body or "")):gmatch("%s#([%w_%-]+)") do
|
||||
if not seen[tag] then
|
||||
seen[tag] = true
|
||||
out[#out + 1] = tag
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
--- Union of two scalar lists, order-stable, de-duplicated.
|
||||
local function union(a, b)
|
||||
local seen, out = {}, {}
|
||||
for _, list in ipairs({ a or {}, b or {} }) do
|
||||
for _, x in ipairs(list) do
|
||||
if not seen[x] then
|
||||
seen[x] = true
|
||||
out[#out + 1] = x
|
||||
end
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
--- Set difference of two scalar lists → (added, removed).
|
||||
local function set_diff(old, new)
|
||||
local oldset, newset = {}, {}
|
||||
|
|
@ -104,12 +133,16 @@ end
|
|||
|
||||
--- Apply the difference between `canonical` (rendered on read) and the buffer's
|
||||
--- `fm` by issuing the matching RPCs. `node_id` is the opened node (title/tags
|
||||
--- target); task scalars route to the owning task (`fm.task`). Raises on any
|
||||
--- RPC error (e.g. a mistyped `state` or an unknown `project`).
|
||||
function M.apply(node_id, canonical, fm)
|
||||
--- target); task scalars route to the owning task (`fm.task`). Inline
|
||||
--- `#hashtags` in `body` are unioned into the desired tag set (so they manage
|
||||
--- tags too); `fm.tags` is updated to that union so the caller caches it as the
|
||||
--- new canonical. Raises on any RPC error (a mistyped `state`, an unknown
|
||||
--- `project`).
|
||||
function M.apply(node_id, canonical, fm, body)
|
||||
canonical = canonical or {}
|
||||
|
||||
-- Tags on the opened node.
|
||||
-- Tags on the opened node = the frontmatter list ∪ inline #hashtags.
|
||||
fm.tags = union(fm.tags, M.hashtags(body))
|
||||
local added, removed = set_diff(canonical.tags, fm.tags)
|
||||
for _, t in ipairs(added) do
|
||||
rpc.call("tag.add", { node_id = node_id, tag = t })
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue