btv.hl
btv.hl.get(ns, opts)
No documentation comment in the prelude.
Defined in api.lua.
btv.hl.exists(name)
btv.hl.exists(name): is the highlight group name defined? Returns a native
boolean (the rest of btv.* is boolean, not vim’s 1/0). Backed by the same
btv._hl_defs registry nvim_get_hl reads (concrete groups and links both count).
Defined in api.lua.
btv.hl.palette()
btv.hl.palette() -> the ACTIVE colorscheme’s semantic palette, as a table of
"#rrggbb" strings. This is the canonical way a plugin picks default colors: read
the hues the running theme actually uses instead of hardcoding one theme’s hex
values, which go wrong the moment any other colorscheme (or a light flavour of the
same one) is loaded.
Each slot resolves through a chain of the canonical vim/treesitter groups that carry
that hue, most specific first, falling back to the built-in bemtvi colorscheme’s
value when the active theme defines none of them — the editor’s default look, so a
bare session still lands on the editor’s own colors. The slot names are the
roles (purple is the keyword hue, green the “added” hue), whatever colour the
running theme gives them:
SURFACES
bg Normal.bg
bg_alt NormalFloat.bg -> StatusLine.bg (a float / sidebar / status strip)
bg_cursorline CursorLine.bg
bg_sel Visual.bg
TEXT
fg Normal.fg
muted Comment.fg -> LineNr.fg -> NonText.fg (guides, dimmed entries)
ACCENTS
red DiagnosticError.fg -> ErrorMsg.fg -> Exception.fg
green Added.fg -> String.fg -> DiagnosticOk.fg
yellow Type.fg -> DiagnosticWarn.fg -> WarningMsg.fg
blue Function.fg -> Directory.fg -> Title.fg
purple Keyword.fg -> Statement.fg -> PreProc.fg
cyan Operator.fg -> Special.fg -> DiagnosticInfo.fg
orange Constant.fg -> Number.fg -> Boolean.fg
Links are followed to the concrete definition, so a theme that writes
Function = { link = "Blue" } still resolves. A fresh table is returned each call
and nothing is cached: re-call it from a ColorScheme handler to restyle live.
local function paint()
local p = btv.hl.palette()
btv.hl.define(0, "MyPluginKey", { fg = p.cyan })
btv.hl.define(0, "MyPluginDim", { fg = p.muted, italic = true })
end
paint()
btv.on("ColorScheme", {}, paint)
Defined in api.lua.
btv.hl.blend(a, b, ratio)
btv.hl.blend(a, b, ratio) -> "#rrggbb": colour a mixed into colour b, ratio
of the way (0 is b, 1 is a; each channel is ratio * a + (1 - ratio) * b,
rounded). Each colour is a "#rrggbb" string or a colour number as btv.hl.get
returns it. This is how a plugin derives a background tint from the active theme,
which btv.hl.palette alone can’t give: a palette accent faded into the palette
background reads as that theme’s own “added” line or highlighted row, on a dark and
a light flavour alike. A colour that isn’t one, or a ratio outside 0..1, raises.
local p = btv.hl.palette()
btv.hl.fallback("MyPluginAdded", { bg = btv.hl.blend(p.green, p.bg, 0.2) })
Defined in api.lua.
btv.hl.fallback(name, spec)
btv.hl.fallback(name, spec) -> true when it installed: define highlight group
name as a DEFAULT that yields to the active colorscheme and to the user. It writes
spec only when the group is undefined, or when the group still holds exactly what
this API last wrote for it — so a theme (or an explicit btv.hl.define) that claims
the group is never clobbered, and a stale default from a previous theme is.
That second half is what a plain if not btv.hl.exists(name) guard gets wrong.
:colorscheme drops only the outgoing scheme’s OWN groups; a plugin’s stay. So a
group the new theme doesn’t model still holds the default derived from the old one,
exists reports true, and the guard skips the re-apply — leaving, say, a
dark-flavour hex on a light theme. Pair this with btv.hl.palette and a
ColorScheme handler and the defaults track whatever is loaded:
local function paint()
local p = btv.hl.palette()
btv.hl.fallback("MyPluginKey", { fg = p.cyan })
btv.hl.fallback("MyPluginDim", { fg = p.muted, italic = true })
end
paint()
btv.on("ColorScheme", {}, paint)
Defined in api.lua.