177 lines
6.0 KiB
Lua
177 lines
6.0 KiB
Lua
-- lua/theme.lua
|
|
-- Reads Noctalia's generated Alacritty theme (~/.config/alacritty/themes/noctalia.toml)
|
|
-- and applies matching highlight groups in Neovim, including Treesitter captures.
|
|
-- Monotone palette: only keywords, functions, parameters, strings, and comments
|
|
-- carry a distinct color. Everything else (variables, types, constants,
|
|
-- punctuation, operators, tags, numbers, booleans) sits in foreground or gray.
|
|
local M = {}
|
|
|
|
local THEME_PATH = vim.fn.expand("~/.config/alacritty/themes/noctalia.toml")
|
|
|
|
local function parse_alacritty_colors(path)
|
|
local f = io.open(path, "r")
|
|
if not f then return nil end
|
|
|
|
local colors = { primary = {}, normal = {}, bright = {} }
|
|
local section
|
|
|
|
for line in f:lines() do
|
|
local sec = line:match("^%[colors%.(%a+)%]")
|
|
if sec then
|
|
-- only track top-level sections we care about (primary, normal, bright);
|
|
-- nested ones like [colors.search.matches] won't match %a+ and are skipped
|
|
section = colors[sec] and sec or nil
|
|
elseif section then
|
|
local key, val = line:match("^(%a+)%s*=%s*['\"](#%x+)['\"]")
|
|
if key and val then
|
|
colors[section][key] = val
|
|
end
|
|
end
|
|
end
|
|
|
|
f:close()
|
|
return colors
|
|
end
|
|
|
|
function M.load()
|
|
local c = parse_alacritty_colors(THEME_PATH)
|
|
if not c or not c.primary.background or not c.primary.foreground then
|
|
vim.notify("noctalia theme not found or unparsable: " .. THEME_PATH, vim.log.levels.WARN)
|
|
return
|
|
end
|
|
|
|
local bg, fg = c.primary.background, c.primary.foreground
|
|
local n, b = c.normal, c.bright
|
|
local hl = vim.api.nvim_set_hl
|
|
|
|
local gray_dim = "#5a5a5a" -- comments, punctuation, gutter — de-emphasized
|
|
local gray_mid = "#7a7a7a" -- line numbers
|
|
|
|
-- The five accent colors — everything else stays monotone
|
|
local c_keyword = n.magenta
|
|
local c_function = n.blue
|
|
local c_parameter = b.white -- subtly brighter than fg, not a hue, keeps "args" scannable
|
|
local c_string = n.green
|
|
local c_comment = "#968466"
|
|
|
|
-- Base UI
|
|
hl(0, "Normal", { fg = fg, bg = bg })
|
|
hl(0, "NormalFloat", { fg = fg, bg = bg })
|
|
hl(0, "CursorLine", { bg = n.black })
|
|
hl(0, "Visual", { bg = n.black, fg = fg })
|
|
hl(0, "Pmenu", { fg = fg, bg = n.black })
|
|
hl(0, "PmenuSel", { fg = bg, bg = b.white })
|
|
hl(0, "LineNr", { fg = gray_mid })
|
|
hl(0, "CursorLineNr", { fg = b.white, bold = true })
|
|
hl(0, "SignColumn", { bg = bg })
|
|
hl(0, "VertSplit", { fg = n.black })
|
|
hl(0, "WinSeparator", { fg = n.black })
|
|
hl(0, "StatusLine", { fg = fg, bg = n.black })
|
|
hl(0, "StatusLineNC", { fg = gray_mid, bg = n.black })
|
|
hl(0, "Search", { fg = "#161616", bg = gray_mid })
|
|
hl(0, "IncSearch", { fg = "#161616", bg = b.white })
|
|
|
|
-- Treesitter capture groups — the five accented categories
|
|
hl(0, "@comment", { fg = c_comment, italic = true })
|
|
|
|
hl(0, "@keyword", { fg = c_keyword })
|
|
hl(0, "@keyword.function", { fg = c_keyword })
|
|
hl(0, "@keyword.return", { fg = c_keyword })
|
|
hl(0, "@conditional", { fg = c_keyword })
|
|
hl(0, "@repeat", { fg = c_keyword })
|
|
|
|
hl(0, "@function", { fg = c_function })
|
|
hl(0, "@function.builtin", { fg = c_function })
|
|
hl(0, "@function.call", { fg = c_function })
|
|
hl(0, "@method", { fg = c_function })
|
|
hl(0, "@method.call", { fg = c_function })
|
|
|
|
hl(0, "@parameter", { fg = c_parameter, italic = true })
|
|
|
|
hl(0, "@string", { fg = c_string })
|
|
hl(0, "@string.escape", { fg = c_string, bold = true })
|
|
|
|
-- Everything else — monotone, falls back to fg or gray
|
|
hl(0, "@variable", { fg = fg })
|
|
hl(0, "@variable.builtin", { fg = fg })
|
|
hl(0, "@type", { fg = fg })
|
|
hl(0, "@type.builtin", { fg = fg })
|
|
hl(0, "@constructor", { fg = fg })
|
|
hl(0, "@constant", { fg = fg })
|
|
hl(0, "@constant.builtin", { fg = fg })
|
|
hl(0, "@property", { fg = fg })
|
|
hl(0, "@field", { fg = fg })
|
|
hl(0, "@number", { fg = fg })
|
|
hl(0, "@boolean", { fg = fg })
|
|
hl(0, "@tag", { fg = fg })
|
|
hl(0, "@tag.attribute", { fg = fg })
|
|
hl(0, "@operator", { fg = gray_mid })
|
|
hl(0, "@punctuation.delimiter", { fg = gray_dim })
|
|
hl(0, "@punctuation.bracket", { fg = gray_dim })
|
|
|
|
-- Legacy / non-Treesitter syntax groups (classic :syntax highlighting —
|
|
-- used by filetypes with no Treesitter parser, or plugins that still
|
|
-- rely on regex-based syntax files). Mirrors the same monotone scheme.
|
|
hl(0, "Comment", { fg = c_comment, italic = true })
|
|
|
|
hl(0, "Statement", { fg = c_keyword })
|
|
hl(0, "Keyword", { fg = c_keyword })
|
|
hl(0, "Conditional", { fg = c_keyword })
|
|
hl(0, "Repeat", { fg = c_keyword })
|
|
hl(0, "Label", { fg = c_keyword })
|
|
hl(0, "Exception", { fg = c_keyword })
|
|
|
|
hl(0, "Function", { fg = c_function })
|
|
|
|
hl(0, "String", { fg = c_string })
|
|
hl(0, "Character", { fg = c_string })
|
|
|
|
-- Everything else — monotone, no separate hue
|
|
hl(0, "Identifier", { fg = fg })
|
|
hl(0, "Constant", { fg = fg })
|
|
hl(0, "Number", { fg = fg })
|
|
hl(0, "Boolean", { fg = fg })
|
|
hl(0, "Float", { fg = fg })
|
|
hl(0, "Type", { fg = fg })
|
|
hl(0, "StorageClass", { fg = fg })
|
|
hl(0, "Structure", { fg = fg })
|
|
hl(0, "Typedef", { fg = fg })
|
|
hl(0, "PreProc", { fg = fg })
|
|
hl(0, "Include", { fg = fg })
|
|
hl(0, "Define", { fg = fg })
|
|
hl(0, "Macro", { fg = fg })
|
|
hl(0, "PreCondit", { fg = fg })
|
|
hl(0, "Special", { fg = fg })
|
|
hl(0, "SpecialChar", { fg = fg })
|
|
hl(0, "SpecialComment", { fg = c_comment, italic = true })
|
|
hl(0, "Tag", { fg = fg })
|
|
hl(0, "Debug", { fg = fg })
|
|
hl(0, "Underlined", { fg = fg, underline = true })
|
|
hl(0, "Ignore", { fg = gray_dim })
|
|
|
|
hl(0, "Operator", { fg = gray_mid })
|
|
hl(0, "Delimiter", { fg = gray_dim })
|
|
|
|
-- Kept distinct: these are signals, not syntax decoration
|
|
hl(0, "Todo", { fg = n.yellow, bold = true })
|
|
hl(0, "Error", { fg = n.red, bold = true })
|
|
|
|
-- Diagnostics stay colored — functional signal, not syntax decoration
|
|
hl(0, "DiagnosticError", { fg = n.red })
|
|
hl(0, "DiagnosticWarn", { fg = n.yellow })
|
|
hl(0, "DiagnosticInfo", { fg = n.blue })
|
|
hl(0, "DiagnosticHint", { fg = n.cyan })
|
|
end
|
|
|
|
-- Reapply automatically whenever Noctalia regenerates the Alacritty theme
|
|
-- (e.g. on a theme switch), without needing to restart Neovim.
|
|
function M.watch()
|
|
local w = vim.uv.new_fs_event()
|
|
if not w then return end
|
|
w:start(THEME_PATH, {}, vim.schedule_wrap(function()
|
|
M.load()
|
|
end))
|
|
end
|
|
|
|
return M
|