-- raytheme.lua - Personal colorscheme framework -- A theme engine that applies palettes defined in colors/ local M = {} ---@class RaythemePalette ---@field bg string ---@field bg_dark string ---@field bg_darker string ---@field bg_float string ---@field bg_highlight string ---@field bg_visual string ---@field diff_add string ---@field diff_delete string ---@field diff_change string ---@field fg string ---@field fg_dark string ---@field fg_gutter string ---@field comment string ---@field keyword string ---@field tag string ---@field variable string ---@field string string ---@field string_escape string ---@field number string ---@field func string ---@field type string ---@field constant string ---@field support string ---@field misc string ---@field error string ---@field warning string ---@field info string ---@field hint string ---@field border string ---@field cursor string ---@field search string ---@field match string ---@class RaythemeConfig ---@field transparent boolean ---@field italic_comments boolean ---@field bold_keywords boolean ---@field overrides? table ---@type RaythemeConfig M.defaults = { transparent = false, italic_comments = true, bold_keywords = false, overrides = {}, } ---@type RaythemeConfig? M.config = nil ---@type RaythemePalette? M.palette = nil local function hi(group, opts) vim.api.nvim_set_hl(0, group, opts) end ---@param c RaythemePalette ---@param opts RaythemeConfig local function apply_highlights(c, opts) local bg = opts.transparent and 'NONE' or c.bg local bg_dark = opts.transparent and 'NONE' or c.bg_dark local bg_darker = opts.transparent and 'NONE' or c.bg_darker local comment_style = opts.italic_comments and { fg = c.comment, italic = true } or { fg = c.comment } local keyword_style = opts.bold_keywords and { fg = c.keyword, bold = true } or { fg = c.keyword } -- Editor UI hi('Normal', { fg = c.fg, bg = bg }) hi('NormalFloat', { fg = c.fg, bg = c.bg_float }) hi('FloatBorder', { fg = c.border, bg = c.bg_float }) hi('Cursor', { fg = c.bg, bg = c.cursor }) hi('CursorLine', { bg = c.bg_highlight }) hi('CursorColumn', { bg = c.bg_float }) hi('ColorColumn', { bg = bg_dark }) hi('LineNr', { fg = c.fg_gutter }) hi('CursorLineNr', { fg = c.misc, bold = true }) hi('SignColumn', { fg = c.fg_gutter, bg = bg }) hi('VertSplit', { fg = c.border }) hi('WinSeparator', { fg = c.border }) hi('Folded', { fg = c.comment, bg = bg_dark }) hi('FoldColumn', { fg = c.fg_gutter }) hi('NonText', { fg = c.fg_gutter }) hi('SpecialKey', { fg = c.fg_gutter }) hi('Whitespace', { fg = c.fg_gutter }) hi('EndOfBuffer', { fg = c.bg }) hi('Directory', { fg = c.misc }) hi('Conceal', { fg = c.fg_gutter }) hi('MatchParen', { fg = c.match, bold = true }) -- Selection & Search hi('Visual', { bg = c.bg_highlight }) hi('VisualNOS', { bg = c.bg_highlight }) hi('Search', { fg = c.bg, bg = c.search }) hi('IncSearch', { fg = c.bg, bg = c.match }) hi('CurSearch', { fg = c.bg, bg = c.match }) hi('Substitute', { fg = c.bg, bg = c.error }) -- Pmenu (completion menu) hi('Pmenu', { fg = c.fg, bg = bg_dark }) hi('PmenuSel', { fg = c.fg, bg = c.bg_highlight }) hi('PmenuSbar', { bg = c.bg_float }) hi('PmenuThumb', { bg = c.fg_gutter }) -- Statusline & Tabline hi('StatusLine', { fg = c.fg, bg = c.bg_float }) hi('StatusLineNC', { fg = c.fg_dark, bg = c.bg_float }) hi('TabLine', { fg = c.fg_dark, bg = bg_darker }) hi('TabLineFill', { bg = bg_darker }) hi('TabLineSel', { fg = c.fg, bg = bg }) hi('WinBar', { fg = c.fg, bg = c.bg_float }) hi('WinBarNC', { fg = c.fg_dark, bg = c.bg_float }) -- Messages hi('ModeMsg', { fg = c.fg, bold = true }) hi('MsgArea', { fg = c.fg }) hi('MoreMsg', { fg = c.string }) hi('Question', { fg = c.string }) hi('WarningMsg', { fg = c.warning }) hi('ErrorMsg', { fg = c.error }) hi('Title', { fg = c.tag, bold = true }) -- Diff hi('DiffAdd', { bg = c.diff_add }) hi('DiffChange', { bg = c.diff_change }) hi('DiffDelete', { bg = c.diff_delete }) hi('DiffText', { bg = c.bg_highlight }) hi('diffAdded', { fg = c.string }) hi('diffRemoved', { fg = c.error }) hi('diffChanged', { fg = c.misc }) hi('diffFile', { fg = c.tag }) hi('diffIndexLine', { fg = c.comment }) -- Spelling hi('SpellBad', { undercurl = true, sp = c.error }) hi('SpellCap', { undercurl = true, sp = c.warning }) hi('SpellLocal', { undercurl = true, sp = c.info }) hi('SpellRare', { undercurl = true, sp = c.hint }) -- Diagnostics hi('DiagnosticError', { fg = c.error }) hi('DiagnosticWarn', { fg = c.warning }) hi('DiagnosticInfo', { fg = c.info }) hi('DiagnosticHint', { fg = c.hint }) hi('DiagnosticOk', { fg = c.string }) hi('DiagnosticUnderlineError', { undercurl = true, sp = c.error }) hi('DiagnosticUnderlineWarn', { undercurl = true, sp = c.warning }) hi('DiagnosticUnderlineInfo', { undercurl = true, sp = c.info }) hi('DiagnosticUnderlineHint', { undercurl = true, sp = c.hint }) hi('DiagnosticUnderlineOk', { undercurl = true, sp = c.string }) hi('DiagnosticVirtualTextError', { fg = c.error, bg = c.diff_delete }) hi('DiagnosticVirtualTextWarn', { fg = c.warning, bg = c.diff_change }) hi('DiagnosticVirtualTextInfo', { fg = c.info, bg = c.bg_float }) hi('DiagnosticVirtualTextHint', { fg = c.hint, bg = c.bg_float }) -- Syntax highlighting hi('Comment', comment_style) hi('Constant', { fg = c.constant }) hi('String', { fg = c.string }) hi('Character', { fg = c.string }) hi('Number', { fg = c.number }) hi('Boolean', { fg = c.number }) hi('Float', { fg = c.number }) hi('Identifier', { fg = c.variable }) hi('Function', { fg = c.func }) hi('Statement', keyword_style) hi('Conditional', keyword_style) hi('Repeat', keyword_style) hi('Label', keyword_style) hi('Operator', { fg = c.tag }) hi('Keyword', keyword_style) hi('Exception', keyword_style) hi('PreProc', { fg = c.support }) hi('Include', keyword_style) hi('Define', { fg = c.support }) hi('Macro', { fg = c.support }) hi('PreCondit', { fg = c.support }) hi('Type', { fg = c.type }) hi('StorageClass', keyword_style) hi('Structure', { fg = c.type }) hi('Typedef', { fg = c.type }) hi('Special', { fg = c.misc }) hi('SpecialChar', { fg = c.string_escape }) hi('Tag', { fg = c.tag }) hi('Delimiter', { fg = c.fg }) hi('SpecialComment', { fg = c.comment, bold = true }) hi('Debug', { fg = c.warning }) hi('Underlined', { underline = true }) hi('Error', { fg = c.error }) hi('Todo', { fg = c.bg, bg = c.misc, bold = true }) -- Treesitter hi('@variable', { fg = c.variable }) hi('@variable.builtin', { fg = c.tag }) hi('@variable.parameter', { fg = c.variable }) hi('@variable.member', { fg = c.variable }) hi('@constant', { fg = c.constant }) hi('@constant.builtin', { fg = c.constant }) hi('@module', { fg = c.support }) hi('@label', { fg = c.misc }) hi('@string', { fg = c.string }) hi('@string.escape', { fg = c.string_escape }) hi('@string.regexp', { fg = c.string_escape }) hi('@string.special', { fg = c.string_escape }) hi('@string.special.url', { fg = c.misc, underline = true }) hi('@character', { fg = c.string }) hi('@number', { fg = c.number }) hi('@boolean', { fg = c.number }) hi('@float', { fg = c.number }) hi('@function', { fg = c.func }) hi('@function.builtin', { fg = c.func }) hi('@function.call', { fg = c.func }) hi('@function.method', { fg = c.func }) hi('@function.method.call', { fg = c.func }) hi('@constructor', { fg = c.support }) hi('@keyword', keyword_style) hi('@keyword.function', keyword_style) hi('@keyword.operator', keyword_style) hi('@keyword.import', keyword_style) hi('@keyword.return', keyword_style) hi('@keyword.conditional', keyword_style) hi('@keyword.repeat', keyword_style) hi('@keyword.exception', keyword_style) hi('@keyword.modifier', keyword_style) hi('@operator', { fg = c.tag }) hi('@punctuation', { fg = c.fg }) hi('@punctuation.bracket', { fg = c.fg }) hi('@punctuation.delimiter', { fg = c.fg }) hi('@punctuation.special', { fg = c.misc }) hi('@comment', comment_style) hi('@comment.todo', { fg = c.bg, bg = c.misc, bold = true }) hi('@comment.note', { fg = c.bg, bg = c.info, bold = true }) hi('@comment.warning', { fg = c.bg, bg = c.warning, bold = true }) hi('@comment.error', { fg = c.bg, bg = c.error, bold = true }) hi('@type', { fg = c.type }) hi('@type.builtin', { fg = c.type }) hi('@type.definition', { fg = c.type }) hi('@attribute', { fg = c.type }) hi('@property', { fg = c.variable }) hi('@tag', { fg = c.tag }) hi('@tag.attribute', { fg = c.variable }) hi('@tag.delimiter', { fg = c.fg }) hi('@diff.plus', { fg = c.string }) hi('@diff.minus', { fg = c.error }) hi('@diff.delta', { fg = c.misc }) -- LSP semantic tokens hi('@lsp.type.class', { fg = c.type }) hi('@lsp.type.decorator', { fg = c.func }) hi('@lsp.type.enum', { fg = c.type }) hi('@lsp.type.enumMember', { fg = c.constant }) hi('@lsp.type.function', { fg = c.func }) hi('@lsp.type.interface', { fg = c.type }) hi('@lsp.type.macro', { fg = c.support }) hi('@lsp.type.method', { fg = c.func }) hi('@lsp.type.namespace', { fg = c.support }) hi('@lsp.type.parameter', { fg = c.variable }) hi('@lsp.type.property', { fg = c.variable }) hi('@lsp.type.struct', { fg = c.type }) hi('@lsp.type.type', { fg = c.type }) hi('@lsp.type.typeParameter', { fg = c.type }) hi('@lsp.type.variable', { fg = c.variable }) hi('@lsp.mod.deprecated', { strikethrough = true }) hi('@lsp.mod.readonly', { italic = true }) -- LSP references hi('LspReferenceText', { bg = c.bg_highlight }) hi('LspReferenceRead', { bg = c.bg_highlight }) hi('LspReferenceWrite', { bg = c.bg_highlight }) hi('LspSignatureActiveParameter', { fg = c.match, bold = true }) hi('LspCodeLens', { fg = c.comment }) hi('LspInlayHint', { fg = c.fg_gutter, bg = c.bg_dark, italic = true }) -- Git signs hi('GitSignsAdd', { fg = c.string }) hi('GitSignsChange', { fg = c.misc }) hi('GitSignsDelete', { fg = c.error }) hi('GitSignsCurrentLineBlame', { fg = c.fg_gutter }) -- Neogit / Diffview hi('NeogitDiffAdd', { fg = c.string, bg = c.diff_add }) hi('NeogitDiffDelete', { fg = c.error, bg = c.diff_delete }) hi('NeogitDiffContext', { bg = c.bg_dark }) hi('NeogitHunkHeader', { fg = c.misc, bg = c.bg_float }) hi('NeogitBranch', { fg = c.keyword }) hi('NeogitRemote', { fg = c.support }) hi('DiffviewFilePanelTitle', { fg = c.tag, bold = true }) hi('DiffviewFilePanelCounter', { fg = c.misc }) -- Which-key hi('WhichKey', { fg = c.func }) hi('WhichKeyGroup', { fg = c.keyword }) hi('WhichKeyDesc', { fg = c.fg }) hi('WhichKeySeparator', { fg = c.comment }) hi('WhichKeyFloat', { bg = c.bg_float }) hi('WhichKeyBorder', { fg = c.border }) hi('WhichKeyValue', { fg = c.fg_dark }) -- Telescope / Snacks picker hi('TelescopeBorder', { fg = c.border }) hi('TelescopePromptBorder', { fg = c.border }) hi('TelescopeResultsBorder', { fg = c.border }) hi('TelescopePreviewBorder', { fg = c.border }) hi('TelescopeMatching', { fg = c.match, bold = true }) hi('TelescopeSelection', { bg = c.bg_highlight }) hi('TelescopePromptPrefix', { fg = c.misc }) hi('TelescopeSelectionCaret', { fg = c.misc }) hi('TelescopeTitle', { fg = c.tag, bold = true }) -- Blink.cmp hi('BlinkCmpMenu', { fg = c.fg, bg = bg_dark }) hi('BlinkCmpMenuBorder', { fg = c.border }) hi('BlinkCmpMenuSelection', { bg = c.bg_highlight }) hi('BlinkCmpLabel', { fg = c.fg }) hi('BlinkCmpLabelMatch', { fg = c.match, bold = true }) hi('BlinkCmpKind', { fg = c.support }) hi('BlinkCmpDoc', { fg = c.fg, bg = c.bg_float }) hi('BlinkCmpDocBorder', { fg = c.border }) -- Oil.nvim hi('OilDir', { fg = c.misc }) hi('OilDirIcon', { fg = c.misc }) hi('OilFile', { fg = c.fg }) hi('OilCreate', { fg = c.string }) hi('OilDelete', { fg = c.error }) hi('OilMove', { fg = c.warning }) hi('OilCopy', { fg = c.info }) hi('OilChange', { fg = c.misc }) hi('OilTitle', { fg = c.tag, bg = c.bg_float, bold = true }) hi('OilWinbar', { fg = c.fg, bg = c.bg_float }) -- Grapple hi('GrappleTitle', { fg = c.tag, bold = true }) hi('GrappleBorder', { fg = c.border }) hi('GrappleCurrent', { fg = c.misc, bold = true }) hi('GrappleFooter', { fg = c.comment }) -- Mini.statusline hi('MiniStatuslineModeNormal', { fg = c.bg, bg = c.misc, bold = true }) hi('MiniStatuslineModeInsert', { fg = c.bg, bg = c.string, bold = true }) hi('MiniStatuslineModeVisual', { fg = c.bg, bg = c.keyword, bold = true }) hi('MiniStatuslineModeReplace', { fg = c.bg, bg = c.error, bold = true }) hi('MiniStatuslineModeCommand', { fg = c.bg, bg = c.warning, bold = true }) hi('MiniStatuslineModeOther', { fg = c.bg, bg = c.support, bold = true }) hi('MiniStatuslineDevinfo', { fg = c.fg, bg = c.bg_highlight }) hi('MiniStatuslineFilename', { fg = c.fg, bg = c.bg_float }) hi('MiniStatuslineFileinfo', { fg = c.fg, bg = c.bg_highlight }) hi('MiniStatuslineInactive', { fg = c.fg_dark, bg = c.bg_float }) hi('MiniStatuslineMail', { fg = c.bg, bg = c.info, bold = true }) -- Mini.icons hi('MiniIconsAzure', { fg = c.info }) hi('MiniIconsBlue', { fg = c.misc }) hi('MiniIconsCyan', { fg = c.support }) hi('MiniIconsGreen', { fg = c.string }) hi('MiniIconsGrey', { fg = c.fg_dark }) hi('MiniIconsOrange', { fg = c.tag }) hi('MiniIconsPurple', { fg = c.number }) hi('MiniIconsRed', { fg = c.error }) hi('MiniIconsYellow', { fg = c.warning }) -- Find-file (custom) hi('FindFilePrompt', { fg = c.tag, bold = true }) hi('FindFileSep', { fg = c.comment }) hi('FindFileMatch', { fg = c.fg, bg = c.bg_float }) hi('FindFileMatchSelected', { fg = c.fg, bg = c.bg_highlight }) hi('FindFileMatchChar', { fg = c.match, bold = true }) -- Markdown hi('@markup.heading', { fg = c.tag, bold = true }) hi('@markup.heading.1', { fg = c.tag, bold = true }) hi('@markup.heading.2', { fg = c.keyword, bold = true }) hi('@markup.heading.3', { fg = c.func, bold = true }) hi('@markup.heading.4', { fg = c.type, bold = true }) hi('@markup.heading.5', { fg = c.constant, bold = true }) hi('@markup.heading.6', { fg = c.misc, bold = true }) hi('@markup.link', { fg = c.misc, underline = true }) hi('@markup.link.url', { fg = c.comment, underline = true }) hi('@markup.link.label', { fg = c.misc }) hi('@markup.raw', { fg = c.string }) hi('@markup.raw.block', { fg = c.string }) hi('@markup.list', { fg = c.tag }) hi('@markup.list.checked', { fg = c.string }) hi('@markup.list.unchecked', { fg = c.fg_gutter }) hi('@markup.italic', { italic = true }) hi('@markup.strong', { bold = true }) hi('@markup.strikethrough', { strikethrough = true }) hi('@markup.quote', { fg = c.comment, italic = true }) -- Apply user overrides if opts.overrides then for group, hl in pairs(opts.overrides) do hi(group, hl) end end end ---@param opts? RaythemeConfig function M.setup(opts) M.config = vim.tbl_deep_extend('force', M.defaults, opts or {}) end ---@param palette RaythemePalette ---@param name string function M.load(palette, name) if not palette then vim.notify('raytheme: palette is required', vim.log.levels.ERROR) return end local config = M.config or M.defaults if vim.g.colors_name then vim.cmd 'highlight clear' end vim.opt.background = 'dark' vim.g.colors_name = name or 'raytheme' M.palette = palette apply_highlights(palette, config) end --- Get current palette colors ---@return RaythemePalette? function M.colors() return M.palette end return M