pmacs/builtin/runtime/syntax.lua

87 lines
3.5 KiB
Lua

-- builtin/runtime/syntax.lua --- T M4.2 auto-attach grammar by extension.
--
-- The Rust side (`crate::syntax::BUILTIN_LANGUAGES`) keeps a config
-- table of `name → loader + extensions`. This file is the Lua-side
-- glue that fires on every successful file load: it asks
-- `pmacs.parse.language_for_path` whether the loaded file maps to a
-- known grammar, and dispatches a parse if so. The settle path is
-- handled by an after-tick step that drains finished parse jobs and
-- installs them into the buffer's view.
--
-- Adding a new grammar requires only:
-- 1. A `tree-sitter-foo = "X.Y"` line in `Cargo.toml`.
-- 2. A new `LanguageEntry` in `crate::syntax::BUILTIN_LANGUAGES`.
-- Nothing in this file needs to change.
-- Set of dispatched-but-not-yet-installed parse job ids. Populated
-- when `pmacs.parse._dispatch` is called below; drained by the tick
-- step.
local pending_parse_jobs = {}
local raw_dispatch = pmacs.parse._dispatch
-- Wrap `_dispatch` so every dispatched parse job lands in our
-- pending set. Calls into `_dispatch` from outside this file (e.g.
-- a hand-rolled user script) get the same tracking for free.
function pmacs.parse._dispatch(buf, lang)
local job_id = raw_dispatch(buf, lang)
pending_parse_jobs[job_id] = true
return job_id
end
-- Set of buffer ids that already have a highlight overlay
-- attached, keyed by raw id (number). A buffer that opens, gets
-- highlights, gets killed, and is reopened needs a fresh overlay
-- attach; the kill path clears the entry below if/when it lands.
local highlighted_buffers = {}
local function attach_for_active_buffer()
local buf = pmacs.window.buffer()
if not buf then return end
local path = buf:name()
if not path then return end
local lang = pmacs.parse.language_for_path(path)
if not lang then return end
pmacs.parse._dispatch(buf, lang)
-- T M4.3: install the syntax-highlight overlay for this buffer.
-- Idempotent --- repeated calls for the same buffer are a no-op
-- on our side, and the Rust side is also tolerant of double
-- attach (it pushes a fresh overlay each call, but `attach_for
-- _active_buffer` itself is gated by the `highlighted_buffers`
-- table so we only push once per (buffer, after-load) cycle).
-- `tostring(buf)` is stable per BufferId (the metamethod
-- formats the wrapped id), so it's a safe table-key
-- replacement for a `:id()` method we don't have to expose.
local key = tostring(buf)
if not highlighted_buffers[key] then
local ok = pmacs.parse._attach_highlight(buf, lang)
if ok then highlighted_buffers[key] = true end
end
end
pmacs.hook.add("buffer.after-load", function()
-- Best-effort: a missing grammar / re-entry / stale buffer
-- mustn't poison the rest of the after-load chain.
local ok, err = pcall(attach_for_active_buffer)
if not ok and pmacs.error then
pmacs.error("syntax.after-load: " .. tostring(err))
end
end)
-- After-tick step: any parse job that has settled gets its bundle
-- installed into the buffer's view (and its pending entry drained).
-- Extension hook on top of the async runtime's tick rather than a
-- Rust-side tick callback because the install path is policy
-- (which view to install into), not mechanism.
local prior_tick = pmacs._async.tick
pmacs._async.tick = function(...)
local ret = prior_tick(...)
for job_id in pairs(pending_parse_jobs) do
if pmacs._async._is_complete(job_id) then
pmacs.parse._install_settled(job_id)
pending_parse_jobs[job_id] = nil
end
end
return ret
end