mirror of
https://github.com/rose-pine/neovim.git
synced 2025-10-15 12:38:53 +02:00
326 lines
11 KiB
Lua
326 lines
11 KiB
Lua
---@alias Highlight { link: string } | { fg: string, bg: string, sp: string, bold: boolean, italic: boolean, undercurl: boolean, underline: boolean, underdouble: boolean, underdotted: boolean, underdashed: boolean, strikethrough: boolean, blend: number, extend: boolean }
|
|
|
|
local M = {}
|
|
|
|
---@param hex string A string representing the hex colour, e.g. "#fa8072"
|
|
---@return integer Red, integer Green, integer Blue
|
|
---@private
|
|
local function hex_to_rgb(hex)
|
|
hex = hex:gsub("#", "")
|
|
return tonumber(hex:sub(1, 2), 16), tonumber(hex:sub(3, 4), 16), tonumber(hex:sub(5, 6), 16)
|
|
end
|
|
|
|
---@param r number Red (0-255)
|
|
---@param g number Green (0-255)
|
|
---@param b number Blue (0-255)
|
|
---@return string
|
|
---@private
|
|
local function rgb_to_hex(r, g, b)
|
|
return string.format("#%02x%02x%02x", r, g, b)
|
|
end
|
|
|
|
local blend_cache = {}
|
|
|
|
---@param fg string The foreground colour as a hex string, e.g. "#fa8072"
|
|
---@param bg string The background colour as a hex string, e.g. "#000000"
|
|
---@param alpha number A number between 0 (background) and 1 (foreground)
|
|
---@return string
|
|
---@private
|
|
local function blend(fg, bg, alpha)
|
|
local cache_key = fg .. bg .. alpha
|
|
if blend_cache[cache_key] then
|
|
return blend_cache[cache_key]
|
|
end
|
|
|
|
local r1, g1, b1 = hex_to_rgb(fg)
|
|
local r2, g2, b2 = hex_to_rgb(bg)
|
|
|
|
local r = r1 * alpha + r2 * (1 - alpha)
|
|
local g = g1 * alpha + g2 * (1 - alpha)
|
|
local b = b1 * alpha + b2 * (1 - alpha)
|
|
|
|
local result = rgb_to_hex(math.floor(r), math.floor(g), math.floor(b))
|
|
|
|
blend_cache[cache_key] = result
|
|
return result
|
|
end
|
|
|
|
---@param palette Palette
|
|
---@param key string
|
|
---@return string
|
|
---@private
|
|
local function find_palette_value_by_key(palette, key)
|
|
return palette[key] or key or ""
|
|
end
|
|
|
|
M.colorscheme = function()
|
|
---@class Config
|
|
local config = {
|
|
appearance = vim.tbl_extend("force", {
|
|
bold = true,
|
|
italic = true,
|
|
transparent = false,
|
|
}, vim.g.rose_pine_appearance or {}),
|
|
|
|
--- Helper table to set user highlight groups, extending the theme's
|
|
--- group values. The palette is accessible by name, e.g. "love":
|
|
--- ```lua
|
|
--- vim.g.rose_pine_groups = {
|
|
--- Normal = { fg = "love" }
|
|
--- }
|
|
--- ```
|
|
---
|
|
--- To override a value completely, use the Neovim api directly:
|
|
--- ```lua
|
|
--- vim.api.nvim_create_autocmd("ColorScheme", {
|
|
--- pattern = { "rose-pine", "rose-pine-main", "rose-pine-moon", "rose-pine-dawn" },
|
|
--- callback = function()
|
|
--- vim.api.nvim_set_hl(0, { ... })
|
|
--- end
|
|
--- })
|
|
--- ```
|
|
groups = vim.g.rose_pine_groups or {},
|
|
|
|
--- Table to set shared styles.
|
|
--- ```lua
|
|
--- vim.g.rose_pine_styles = {
|
|
--- git_add = "iris"
|
|
--- }
|
|
--- ```
|
|
styles = vim.tbl_extend("force", {
|
|
git_add = "foam",
|
|
git_change = "rose",
|
|
git_delete = "love",
|
|
git_dirty = "rose",
|
|
git_ignore = "muted",
|
|
git_merge = "iris",
|
|
git_rename = "pine",
|
|
git_stage = "iris",
|
|
git_text = "rose",
|
|
git_untracked = "subtle",
|
|
|
|
status_deprecated = "muted",
|
|
status_error = "love",
|
|
status_hint = "iris",
|
|
status_info = "foam",
|
|
status_note = "subtle",
|
|
status_ok = "pine",
|
|
status_todo = "rose",
|
|
status_warn = "gold",
|
|
}, vim.g.rose_pine_groups or {})
|
|
}
|
|
|
|
if vim.g.colors_name then
|
|
vim.cmd("hi clear")
|
|
vim.cmd("syntax reset")
|
|
end
|
|
vim.g.colors_name = "rose-pine"
|
|
|
|
---@class Palette
|
|
local palette = {
|
|
base = "#faf4ed",
|
|
surface = "#fffaf3",
|
|
overlay = "#f2e9e1",
|
|
muted = "#9893a5",
|
|
subtle = "#797593",
|
|
text = "#464261",
|
|
love = "#b4637a",
|
|
gold = "#ea9d34",
|
|
rose = "#d7827e",
|
|
pine = "#286983",
|
|
foam = "#56949f",
|
|
iris = "#907aa9",
|
|
}
|
|
|
|
if vim.o.background == "dark" then
|
|
if vim.g.rose_pine_dark_variant == "moon" then
|
|
palette = {
|
|
base = "#232136",
|
|
surface = "#2a273f",
|
|
overlay = "#393552",
|
|
muted = "#6e6a86",
|
|
subtle = "#908caa",
|
|
text = "#e0def4",
|
|
love = "#eb6f92",
|
|
gold = "#f6c177",
|
|
rose = "#ea9a97",
|
|
pine = "#3e8fb0",
|
|
foam = "#9ccfd8",
|
|
iris = "#c4a7e7",
|
|
}
|
|
else
|
|
palette = {
|
|
base = "#191724",
|
|
surface = "#1f1d2e",
|
|
overlay = "#26233a",
|
|
muted = "#6e6a86",
|
|
subtle = "#908caa",
|
|
text = "#e0def4",
|
|
love = "#eb6f92",
|
|
gold = "#f6c177",
|
|
rose = "#ebbcba",
|
|
pine = "#31748f",
|
|
foam = "#9ccfd8",
|
|
iris = "#c4a7e7",
|
|
}
|
|
end
|
|
end
|
|
|
|
local styles = {}
|
|
for style, value in pairs(config.styles) do
|
|
styles[style] = find_palette_value_by_key(palette, value)
|
|
end
|
|
|
|
local dynamic_styles = {
|
|
float_bg = vim.o.winborder == "none" and palette.surface or palette.base
|
|
}
|
|
|
|
-- TODO: Check that winborder option exists
|
|
-- Reload colorscheme on relevant option change
|
|
vim.api.nvim_create_autocmd("OptionSet", {
|
|
pattern = { "winborder" },
|
|
callback = function()
|
|
vim.cmd.colorscheme(vim.g.colors_name)
|
|
end
|
|
})
|
|
|
|
local highlights = {
|
|
-- Interface
|
|
CursorLine = { bg = palette.muted, fg = palette.text, opacity = 0.15 },
|
|
Directory = { fg = palette.text, bold = true },
|
|
LineNr = { fg = palette.muted },
|
|
SpecialKey = { fg = palette.muted },
|
|
Normal = { bg = palette.base, fg = palette.text },
|
|
QuickFixLine = { bg = palette.muted, fg = palette.foam, opacity = 0.1 },
|
|
StatusLine = { bg = palette.surface, fg = palette.subtle },
|
|
StatusLineNC = { bg = palette.surface, fg = palette.muted },
|
|
Visual = { bg = palette.iris, fg = palette.iris, opacity = 0.2 },
|
|
WinSeparator = { fg = palette.overlay },
|
|
|
|
-- Message area
|
|
ModeMsg = { fg = palette.muted },
|
|
MoreMsg = { fg = palette.muted },
|
|
-- hl(0, "MsgArea", { bg = palette.surface, fg = palette.subtle })
|
|
|
|
-- Menus
|
|
Pmenu = { bg = palette.surface, fg = palette.subtle },
|
|
PmenuExtra = { fg = palette.muted, italic = true },
|
|
PmenuExtraSel = { bg = palette.muted, fg = palette.subtle, opacity = 0.2, italic = true },
|
|
PmenuKind = { fg = palette.muted, italic = true },
|
|
PmenuKindSel = { bg = palette.muted, fg = palette.subtle, opacity = 0.2, italic = true },
|
|
PmenuMatch = { fg = palette.iris },
|
|
PmenuMatchSel = { fg = palette.iris, bold = true },
|
|
PmenuSbar = { bg = palette.overlay },
|
|
PmenuSel = { bg = palette.muted, fg = palette.text, opacity = 0.2, bold = true },
|
|
PmenuThumb = { bg = palette.muted },
|
|
|
|
-- Floats
|
|
FloatBorder = { bg = dynamic_styles.float_bg, fg = palette.overlay },
|
|
FloatFooter = { bg = dynamic_styles.float_bg, fg = palette.muted },
|
|
FloatTitle = { bg = dynamic_styles.float_bg, fg = palette.text, bold = true },
|
|
NormalFloat = { bg = dynamic_styles.float_bg, fg = palette.subtle },
|
|
|
|
-- Search
|
|
CurSearch = { bg = palette.gold, fg = palette.base },
|
|
IncSearch = { bg = palette.text, fg = palette.base },
|
|
Search = { bg = palette.overlay, fg = palette.text },
|
|
|
|
-- Diagnostics
|
|
DiagnosticDeprecated = { fg = styles.status_deprecated, strikethrough = true },
|
|
DiagnosticError = { fg = styles.status_error },
|
|
DiagnosticHint = { fg = styles.status_hint },
|
|
DiagnosticInfo = { fg = styles.status_info },
|
|
DiagnosticOk = { fg = styles.status_ok },
|
|
DiagnosticWarn = { fg = styles.status_warn },
|
|
DiagnosticUnderlineError = { sp = styles.status_error, undercurl = true },
|
|
DiagnosticUnderlineHint = { sp = styles.status_hint, undercurl = true },
|
|
DiagnosticUnderlineInfo = { sp = styles.status_info, undercurl = true },
|
|
DiagnosticUnderlineOk = { sp = styles.status_ok, undercurl = true },
|
|
DiagnosticUnderlineWarn = { sp = styles.status_warn, undercurl = true },
|
|
|
|
-- Diff
|
|
Added = { fg = styles.git_add },
|
|
Changed = { fg = styles.git_change },
|
|
Removed = { fg = styles.git_delete },
|
|
DiffAdd = { bg = styles.git_add, fg = styles.git_add, opacity = 0.2 },
|
|
DiffChange = { bg = styles.git_change, fg = styles.git_change, opacity = 0.2 },
|
|
DiffDelete = { bg = styles.git_delete, fg = styles.git_delete, opacity = 0.2 },
|
|
DiffText = { bg = styles.git_text, fg = styles.git_text, opacity = 0.2 },
|
|
|
|
-- Syntax
|
|
Comment = { fg = palette.muted },
|
|
Constant = { fg = palette.subtle },
|
|
Delimiter = { fg = palette.muted },
|
|
Function = { fg = palette.rose },
|
|
Identifier = { fg = palette.text },
|
|
MatchParen = { bg = palette.text, fg = palette.base, underline = true },
|
|
NonText = { fg = palette.muted, opacity = 0.5 },
|
|
Operator = { fg = palette.muted },
|
|
Question = { fg = palette.subtle },
|
|
Special = { fg = palette.foam },
|
|
SpellBad = { sp = palette.muted, underdotted = true },
|
|
SpellCap = { sp = palette.muted, underdotted = true },
|
|
SpellRare = { sp = palette.muted, underdotted = true },
|
|
SpellLocal = { sp = palette.muted, underdotted = true },
|
|
Statement = { fg = palette.text },
|
|
String = { fg = palette.gold },
|
|
Type = { fg = palette.text, bold = true },
|
|
Underlined = { sp = palette.muted, underline = true },
|
|
|
|
-- Treesitter syntax
|
|
["@variable"] = { fg = palette.text },
|
|
}
|
|
|
|
-- Merge user highlight groups
|
|
for group, highlight in pairs(config.groups) do
|
|
local original = highlights[group] or {}
|
|
local base = highlight
|
|
|
|
-- Find root link to extend its properties
|
|
while original.link ~= nil do
|
|
original = highlights[original.link] or {}
|
|
end
|
|
while base.link ~= nil do
|
|
base = highlights[base.link] or {}
|
|
end
|
|
|
|
local parsed_highlight = vim.tbl_extend("force", {}, base)
|
|
|
|
if highlight.fg ~= nil then
|
|
parsed_highlight.fg = find_palette_value_by_key(palette, highlight.fg)
|
|
end
|
|
|
|
if highlight.bg ~= nil then
|
|
parsed_highlight.bg = find_palette_value_by_key(palette, highlight.bg)
|
|
end
|
|
|
|
if highlight.sp ~= nil then
|
|
parsed_highlight.sp = find_palette_value_by_key(palette, highlight.sp)
|
|
end
|
|
|
|
highlights[group] = vim.tbl_extend("force", original, parsed_highlight)
|
|
end
|
|
|
|
-- Apply all highlights
|
|
for group, highlight in pairs(highlights) do
|
|
if highlight.opacity ~= nil then
|
|
if highlight.bg ~= nil then
|
|
highlight.bg = blend(highlight.bg, palette.base, highlight.opacity)
|
|
else
|
|
highlight.fg = blend(highlight.fg, palette.base, highlight.opacity)
|
|
end
|
|
|
|
highlight.opacity = nil
|
|
end
|
|
|
|
if config.appearance.transparent then
|
|
if highlight.bg ~= nil and highlight.bg == palette.base then
|
|
highlight.bg = "NONE"
|
|
end
|
|
end
|
|
|
|
vim.api.nvim_set_hl(0, group, highlight)
|
|
end
|
|
end
|
|
|
|
return M
|