-- Extmark engine. Maintains a global highlight namespace of extended marks: features register a -- highlight group plus a finder that locates the positions to mark, and the engine marks every -- buffer on 'FileType' and keeps the marks in sync as the buffer changes. The whole namespace can -- be toggled on and off. Marks are registered in plugin/50-extmarks.lua. -- -- Marks follow the theme on their own: register highlight groups that link to a built-in group -- (e.g. `Todo`), so a colorscheme / 'background' change re-colors them with no extra wiring. -- -- Multi-line patterns are not supported. -- -- Global variables: -- vim.g.hl_ns — the active global highlight namespace (the engine's namespace, or 0 when off) -- -- API: -- register(hl_group, hl_def, find) — define `hl_group` (with the highlight `hl_def`) in the -- engine's namespace and mark the positions yielded by -- `find(buf, first, last)` with it. `find` returns an iterator -- of { y, x1, x2 } tables: 0-based row, start column inclusive, -- end column exclusive. -- toggle() — toggle the whole extended-marks namespace on or off local M = {} -- The engine's extended-marks namespace. local ns = vim.api.nvim_create_namespace("dotfiles_extmarks") -- Registered marks, applied in registration order. Each is { hl_group, find }. M.marks = {} ---------------------------------------------------------------------------------------------------- -- Highlight namespace toggle ---------------------------------------------------------------------------------------------------- vim.g.hl_ns = 0 -- Set the global highlight namespace to `hl_ns` and remember it in `vim.g.hl_ns`; the global -- highlight namespace is not stored anywhere by default. local function set_global_hl_ns(hl_ns) vim.g.hl_ns = hl_ns vim.api.nvim_set_hl_ns(hl_ns) end -- Toggle the extended-marks namespace on (the engine's namespace) or off (the default 0). function M.toggle() set_global_hl_ns(vim.g.hl_ns == 0 and ns or 0) end ---------------------------------------------------------------------------------------------------- -- Marking ---------------------------------------------------------------------------------------------------- -- Register the highlight group `hl_group` (defined in the engine's namespace as `hl_def`) and mark -- the positions yielded by `find` with it. See the header for the `find` contract. function M.register(hl_group, hl_def, find) assert(type(hl_group) == "string", "extmark.register: hl_group must be a string") assert(type(hl_def) == "table", "extmark.register: hl_def must be a table") assert(type(find) == "function", "extmark.register: find must be a function") vim.api.nvim_set_hl(ns, hl_group, hl_def) M.marks[#M.marks + 1] = { hl_group = hl_group, find = find } end -- Refresh the marks in buffer `buf` in range `first`:`last`. Clears the namespace there, then for -- each registered mark sets an extmark on every position its `find` yields. local function mark_all(buf, first, last) vim.api.nvim_buf_clear_namespace(buf, ns, first, last) for _, mark in ipairs(M.marks) do for p in mark.find(buf, first, last) do vim.api.nvim_buf_set_extmark(buf, ns, p.y, p.x1, { end_col = p.x2, hl_group = mark.hl_group, }) end end end -- Mark buffer `ev.buf` and, once per buffer, attach to keep its marks in sync as lines change. local function watch_extmarks(ev) mark_all(ev.buf, 0, -1) if vim.b[ev.buf].dotfiles_extmarks_attached then return end vim.b[ev.buf].dotfiles_extmarks_attached = true vim.api.nvim_buf_attach(ev.buf, false, { on_lines = function(_, b, _, first, _, last_new) vim.schedule(function() if vim.api.nvim_buf_is_valid(b) then mark_all(b, first, last_new) end end) end, }) end ---------------------------------------------------------------------------------------------------- vim.api.nvim_create_autocmd("FileType", { desc = "Set extended marks watcher", group = vim.g.dotfiles.augroup, callback = watch_extmarks, }) -- Turn the namespace on at startup. set_global_hl_ns(ns) return M