feat: add contrast foreground config option

This commit is contained in:
Webhooked
2025-07-25 11:45:27 +02:00
parent 5ae1ef3c4f
commit 024999f6f9
7 changed files with 226 additions and 76 deletions
+39 -4
View File
@@ -29,7 +29,8 @@
- 🌈 Extensive support for `TreeSitter` syntax highlighting - 🌈 Extensive support for `TreeSitter` syntax highlighting
- 🔌 Compatible with popular plugins out of the box - 🔌 Compatible with popular plugins out of the box
- ⚡ Compilation to lua byte code for fast startup times - ⚡ Compilation to lua byte code for fast startup times
- 🎨 Three beautiful theme variants to match your mood and environment - 🎨 Four beautiful theme variants to match your mood and environment
- 🔆 Contrast mode for enhanced syntax highlighting visibility
- 👁️ WCAG 2.1 AA compliant - 👁️ WCAG 2.1 AA compliant
## 📦 Installation ## 📦 Installation
@@ -93,11 +94,12 @@ require('kanso').setup({
overrides = function(colors) -- add/modify highlights overrides = function(colors) -- add/modify highlights
return {} return {}
end, end,
theme = "zen", -- Load "zen" theme theme = "ink", -- Load "ink" theme
background = { -- map the value of 'background' option to a theme background = { -- map the value of 'background' option to a theme
dark = "zen", -- try "ink" ! dark = "ink", -- try "zen" !
light = "pearl" -- try "mist" ! light = "ink" -- try "pearl" or "mist" !
}, },
foreground = "default", -- "default" or "contrast" (can also be a table like background)
}) })
-- setup must be called before loading -- setup must be called before loading
@@ -177,6 +179,39 @@ require("kanso").load("zen")
</details> </details>
## 🌟 Foreground Contrast
Kansō now supports a foreground contrast option that enhances the saturation of syntax highlighting colors while keeping the same background colors. This is useful for improving visibility in certain lighting conditions or personal preference.
<details>
<summary><strong>🔆 Using Contrast Mode</strong></summary>
The `foreground` option can be configured per background mode:
```lua
require('kanso').setup({
foreground = {
dark = "default", -- Use default colors in dark mode
light = "contrast" -- Use higher saturation in light mode
},
})
```
When set to `"contrast"`, syntax highlighting colors will have increased saturation making them stand out more against the background:
- Zen, Ink, and Mist themes: 20% more vibrant colors
- Pearl theme: 40% more vibrant colors
This is particularly useful:
- In bright environments where you need more color distinction
- For users who prefer more vibrant syntax highlighting
- When using the light themes where contrast can be beneficial
The contrast adjustment only affects syntax highlighting colors (strings, keywords, functions, etc.) and does not change UI elements or background colors.
</details>
## 🧰 Customization ## 🧰 Customization
In Kansō, there are _two_ kinds of colors: `PaletteColors` and `ThemeColors`; In Kansō, there are _two_ kinds of colors: `PaletteColors` and `ThemeColors`;
+57 -2
View File
@@ -57,6 +57,30 @@ local palette = {
orange2 = "#b98d7b", orange2 = "#b98d7b",
aqua = "#8ea4a2", aqua = "#8ea4a2",
-- Contrast variants (20% more saturation)
redContrast = "#C93134",
red2Contrast = "#ED5965",
red3Contrast = "#CA675F",
yellowContrast = "#E59F49",
yellow2Contrast = "#EDC272",
yellow3Contrast = "#CAAC7A",
greenContrast = "#8FC055",
green2Contrast = "#7CAF7C",
green3Contrast = "#7F9F6E",
green4Contrast = "#5B9A82",
green5Contrast = "#6BAE97",
blueContrast = "#6EBBD4",
blue2Contrast = "#568B8F",
blue3Contrast = "#7EAABA",
blue4Contrast = "#81AAA9",
violetContrast = "#8A88B0",
violet2Contrast = "#7E91AF",
violet3Contrast = "#8A9FBE",
pinkContrast = "#A08AA2",
orangeContrast = "#BC8A6C",
orange2Contrast = "#BF856B",
aquaContrast = "#81AAA9",
-- Fg and Comments -- Fg and Comments
fg = "#C5C9C7", fg = "#C5C9C7",
fg2 = "#f2f1ef", fg2 = "#f2f1ef",
@@ -108,6 +132,30 @@ local palette = {
pearlTeal2 = "#6693bf", pearlTeal2 = "#6693bf",
pearlTeal3 = "#5a7785", pearlTeal3 = "#5a7785",
pearlCyan = "#d7e3d8", pearlCyan = "#d7e3d8",
-- Pearl contrast variants (40% more saturation)
pearlGreenContrast = "#5E8F2F",
pearlGreen2Contrast = "#5B9945",
pearlGreen3Contrast = "#A8DA9B",
pearlPinkContrast = "#C04062",
pearlOrangeContrast = "#E05700",
pearlOrange2Contrast = "#FF7700",
pearlYellowContrast = "#656720",
pearlYellow2Contrast = "#72612B",
pearlYellow3Contrast = "#F28C00",
pearlYellow4Contrast = "#FFD56D",
pearlRedContrast = "#D72436",
pearlRed2Contrast = "#E42D2C",
pearlRed3Contrast = "#F50000",
pearlRed4Contrast = "#E4977B",
pearlAquaContrast = "#3E8366",
pearlAqua2Contrast = "#428F6A",
pearlTeal1Contrast = "#2E96B0",
pearlTeal2Contrast = "#469FD3",
pearlTeal3Contrast = "#3D8077",
pearlBlue4Contrast = "#2A73B1",
pearlBlue5Contrast = "#3E56B8",
pearlViolet4Contrast = "#44418F",
} }
local M = {} local M = {}
@@ -117,7 +165,7 @@ local M = {}
--- Defaults to KansoConfig.colors. --- Defaults to KansoConfig.colors.
--- - theme: Use selected theme. Defaults to KansoConfig.theme --- - theme: Use selected theme. Defaults to KansoConfig.theme
--- according to the value of 'background' option. --- according to the value of 'background' option.
---@param opts? { colors?: table, theme?: string } ---@param opts? { colors?: table, theme?: string, foreground?: "default"|"contrast" }
---@return { theme: ThemeColors, palette: PaletteColors} ---@return { theme: ThemeColors, palette: PaletteColors}
function M.setup(opts) function M.setup(opts)
opts = opts or {} opts = opts or {}
@@ -134,7 +182,14 @@ function M.setup(opts)
local updated_palette_colors = vim.tbl_extend("force", palette, override_colors.palette or {}) local updated_palette_colors = vim.tbl_extend("force", palette, override_colors.palette or {})
-- Generate the theme according to the updated palette colors -- Generate the theme according to the updated palette colors
local theme_colors = require("kanso.themes")[theme](updated_palette_colors) local kanso_config = require("kanso").config
local bg_mode = vim.o.background
local foreground = opts.foreground
or (type(kanso_config.foreground) == "table" and kanso_config.foreground[bg_mode])
or kanso_config.foreground
or "default"
---@cast foreground "default"|"contrast"
local theme_colors = require("kanso.themes")[theme](updated_palette_colors, foreground)
-- Add to and/or override theme_colors -- Add to and/or override theme_colors
local theme_overrides = local theme_overrides =
+1 -1
View File
@@ -527,7 +527,7 @@ function M.setup(colors, config)
BufferLineBackground = { fg = theme.ui.none, bg = theme.ui.none }, BufferLineBackground = { fg = theme.ui.none, bg = theme.ui.none },
BufferLineBuffer = { fg = theme.ui.none, bg = theme.ui.none }, BufferLineBuffer = { fg = theme.ui.none, bg = theme.ui.none },
BufferLineBufferSelected = { bg = theme.ui.none }, BufferLineBufferSelected = { bg = theme.ui.none, fg = theme.ui.fg },
BufferLineBufferVisible = { bg = theme.ui.none }, BufferLineBufferVisible = { bg = theme.ui.none },
BufferLineCloseButton = { bg = theme.ui.none }, BufferLineCloseButton = { bg = theme.ui.none },
BufferLineCloseButtonSelected = { bg = theme.ui.none }, BufferLineCloseButtonSelected = { bg = theme.ui.none },
+62 -4
View File
@@ -25,12 +25,14 @@ M.config = {
return {} return {}
end, end,
---@type { dark: string, light: string } ---@type { dark: string, light: string }
background = { dark = "ink", light = "pearl" }, background = { dark = "ink", light = "ink" },
theme = "ink", theme = "ink",
---@type { dark: "default"|"contrast", light: "default"|"contrast" }|"default"|"contrast"
foreground = "default",
compile = false, compile = false,
} }
local function check_config(config) local function check_config(_)
local err local err
return not err return not err
end end
@@ -60,6 +62,22 @@ function M.load(theme)
vim.g.colors_name = "kanso" vim.g.colors_name = "kanso"
vim.o.termguicolors = true vim.o.termguicolors = true
-- Setup autocommand to reload theme when background changes
if not M._autocmd_created then
M._autocmd_created = true
vim.api.nvim_create_autocmd("OptionSet", {
pattern = "background",
callback = function()
if vim.g.colors_name == "kanso" then
-- Clear cached modules to force reload
package.loaded["kanso.colors"] = nil
package.loaded["kanso.themes"] = nil
M.load()
end
end,
})
end
if M.config.compile then if M.config.compile then
if utils.load_compiled(theme) then if utils.load_compiled(theme) then
return return
@@ -68,7 +86,11 @@ function M.load(theme)
M.compile() M.compile()
utils.load_compiled(theme) utils.load_compiled(theme)
else else
local colors = require("kanso.colors").setup({ theme = theme, colors = M.config.colors }) local foreground_setting = type(M.config.foreground) == "table" and M.config.foreground[vim.o.background]
or M.config.foreground
---@cast foreground_setting "default"|"contrast"
local colors =
require("kanso.colors").setup({ theme = theme, colors = M.config.colors, foreground = foreground_setting })
local highlights = require("kanso.highlights").setup(colors, M.config) local highlights = require("kanso.highlights").setup(colors, M.config)
require("kanso.highlights").highlight(highlights, M.config.terminalColors and colors.theme.term or {}) require("kanso.highlights").highlight(highlights, M.config.terminalColors and colors.theme.term or {})
end end
@@ -76,10 +98,46 @@ end
function M.compile() function M.compile()
for theme, _ in pairs(require("kanso.themes")) do for theme, _ in pairs(require("kanso.themes")) do
local colors = require("kanso.colors").setup({ theme = theme, colors = M.config.colors }) -- Compile both foreground variants if foreground is a table
if type(M.config.foreground) == "table" then
-- Compile for dark mode
local colors_dark = require("kanso.colors").setup({
theme = theme,
colors = M.config.colors,
foreground = M.config.foreground.dark,
})
local highlights_dark = require("kanso.highlights").setup(colors_dark, M.config)
require("kanso.utils").compile(
theme .. "_dark_" .. M.config.foreground.dark,
highlights_dark,
M.config.terminalColors and colors_dark.theme.term or {}
)
-- Compile for light mode
local colors_light = require("kanso.colors").setup({
theme = theme,
colors = M.config.colors,
foreground = M.config.foreground.light,
})
local highlights_light = require("kanso.highlights").setup(colors_light, M.config)
require("kanso.utils").compile(
theme .. "_light_" .. M.config.foreground.light,
highlights_light,
M.config.terminalColors and colors_light.theme.term or {}
)
else
-- Fallback for backward compatibility
local foreground_str = M.config.foreground
---@cast foreground_str "default"|"contrast"
local colors = require("kanso.colors").setup({
theme = theme,
colors = M.config.colors,
foreground = foreground_str,
})
local highlights = require("kanso.highlights").setup(colors, M.config) local highlights = require("kanso.highlights").setup(colors, M.config)
require("kanso.utils").compile(theme, highlights, M.config.terminalColors and colors.theme.term or {}) require("kanso.utils").compile(theme, highlights, M.config.terminalColors and colors.theme.term or {})
end end
end
end end
vim.api.nvim_create_user_command("KansoCompile", function() vim.api.nvim_create_user_command("KansoCompile", function()
+60 -58
View File
@@ -1,5 +1,3 @@
local c = require("kanso.lib.color")
--TODO: --TODO:
--PreProc needs its own color --PreProc needs its own color
--parameter and field should be different --parameter and field should be different
@@ -94,8 +92,9 @@ local c = require("kanso.lib.color")
return { return {
---@param palette PaletteColors ---@param palette PaletteColors
---@param foreground? "default"|"contrast"
---@return ThemeColors ---@return ThemeColors
zen = function(palette) zen = function(palette, foreground)
return { return {
ui = { ui = {
none = "NONE", none = "NONE",
@@ -143,25 +142,25 @@ return {
}, },
}, },
syn = { syn = {
string = palette.green3, string = foreground == "contrast" and palette.green3Contrast or palette.green3,
variable = "NONE", variable = "NONE",
number = palette.pink, number = foreground == "contrast" and palette.pinkContrast or palette.pink,
constant = palette.orange, constant = foreground == "contrast" and palette.orangeContrast or palette.orange,
identifier = palette.violet2, identifier = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
parameter = palette.gray3, parameter = palette.gray3,
fun = palette.blue3, fun = foreground == "contrast" and palette.blue3Contrast or palette.blue3,
statement = palette.violet2, statement = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
keyword = palette.violet2, keyword = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
operator = palette.gray3, operator = palette.gray3,
preproc = palette.gray3, preproc = palette.gray3,
type = palette.aqua, type = foreground == "contrast" and palette.aquaContrast or palette.aqua,
regex = palette.red3, regex = foreground == "contrast" and palette.red3Contrast or palette.red3,
deprecated = palette.gray, deprecated = palette.gray,
punct = palette.gray3, punct = palette.gray3,
comment = palette.gray4, comment = palette.gray4,
special1 = palette.yellow3, special1 = foreground == "contrast" and palette.yellow3Contrast or palette.yellow3,
special2 = palette.violet2, special2 = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
special3 = palette.violet2, special3 = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
}, },
diag = { diag = {
error = palette.red, error = palette.red,
@@ -205,8 +204,9 @@ return {
} }
end, end,
---@param palette PaletteColors ---@param palette PaletteColors
---@param foreground? "default"|"contrast"
---@return ThemeColors ---@return ThemeColors
ink = function(palette) ink = function(palette, foreground)
return { return {
ui = { ui = {
none = "NONE", none = "NONE",
@@ -254,25 +254,25 @@ return {
}, },
}, },
syn = { syn = {
string = palette.green3, string = foreground == "contrast" and palette.green3Contrast or palette.green3,
variable = "NONE", variable = "NONE",
number = palette.pink, number = foreground == "contrast" and palette.pinkContrast or palette.pink,
constant = palette.orange, constant = foreground == "contrast" and palette.orangeContrast or palette.orange,
identifier = palette.violet2, identifier = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
parameter = palette.gray3, parameter = palette.gray3,
fun = palette.blue3, fun = foreground == "contrast" and palette.blue3Contrast or palette.blue3,
statement = palette.violet2, statement = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
keyword = palette.violet2, keyword = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
operator = palette.gray3, operator = palette.gray3,
preproc = palette.gray3, preproc = palette.gray3,
type = palette.aqua, type = foreground == "contrast" and palette.aquaContrast or palette.aqua,
regex = palette.red3, regex = foreground == "contrast" and palette.red3Contrast or palette.red3,
deprecated = palette.gray, deprecated = palette.gray,
punct = palette.gray3, punct = palette.gray3,
comment = palette.gray4, comment = palette.gray4,
special1 = palette.yellow3, special1 = foreground == "contrast" and palette.yellow3Contrast or palette.yellow3,
special2 = palette.violet2, special2 = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
special3 = palette.violet2, special3 = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
}, },
diag = { diag = {
error = palette.red, error = palette.red,
@@ -316,8 +316,9 @@ return {
} }
end, end,
---@param palette PaletteColors ---@param palette PaletteColors
---@param foreground? "default"|"contrast"
---@return ThemeColors ---@return ThemeColors
pearl = function(palette) pearl = function(palette, foreground)
return { return {
ui = { ui = {
none = "NONE", none = "NONE",
@@ -364,25 +365,25 @@ return {
}, },
}, },
syn = { syn = {
string = palette.pearlGreen, string = foreground == "contrast" and palette.pearlGreenContrast or palette.pearlGreen,
variable = "NONE", variable = "NONE",
number = palette.pearlPink, number = foreground == "contrast" and palette.pearlPinkContrast or palette.pearlPink,
constant = palette.pearlOrange, constant = foreground == "contrast" and palette.pearlOrangeContrast or palette.pearlOrange,
identifier = palette.pearlViolet4, identifier = foreground == "contrast" and palette.pearlViolet4Contrast or palette.pearlViolet4,
parameter = palette.pearlBlue5, parameter = foreground == "contrast" and palette.pearlBlue5Contrast or palette.pearlBlue5,
fun = palette.pearlBlue4, fun = foreground == "contrast" and palette.pearlBlue4Contrast or palette.pearlBlue4,
statement = palette.pearlViolet4, statement = foreground == "contrast" and palette.pearlViolet4Contrast or palette.pearlViolet4,
keyword = palette.pearlViolet4, keyword = foreground == "contrast" and palette.pearlViolet4Contrast or palette.pearlViolet4,
operator = palette.pearlGray3, operator = palette.pearlGray3,
preproc = palette.pearlGray2, preproc = palette.pearlGray2,
type = palette.pearlAqua, type = foreground == "contrast" and palette.pearlAquaContrast or palette.pearlAqua,
regex = palette.pearlYellow2, regex = foreground == "contrast" and palette.pearlYellow2Contrast or palette.pearlYellow2,
deprecated = palette.pearlGray3, deprecated = palette.pearlGray3,
comment = palette.pearlGray3, comment = palette.pearlGray3,
punct = palette.pearlGray3, punct = palette.pearlGray3,
special1 = palette.pearlYellow2, special1 = foreground == "contrast" and palette.pearlYellow2Contrast or palette.pearlYellow2,
special2 = palette.pearlViolet4, special2 = foreground == "contrast" and palette.pearlViolet4Contrast or palette.pearlViolet4,
special3 = palette.pearlViolet4, special3 = foreground == "contrast" and palette.pearlViolet4Contrast or palette.pearlViolet4,
}, },
vcs = { vcs = {
added = palette.pearlGreen2, added = palette.pearlGreen2,
@@ -426,8 +427,9 @@ return {
} }
end, end,
---@param palette PaletteColors ---@param palette PaletteColors
---@param foreground? "default"|"contrast"
---@return ThemeColors ---@return ThemeColors
mist = function(palette) mist = function(palette, foreground)
return { return {
ui = { ui = {
none = "NONE", none = "NONE",
@@ -475,25 +477,25 @@ return {
}, },
}, },
syn = { syn = {
string = palette.green3, string = foreground == "contrast" and palette.green3Contrast or palette.green3,
variable = "NONE", variable = "NONE",
number = palette.pink, number = foreground == "contrast" and palette.pinkContrast or palette.pink,
constant = palette.orange, constant = foreground == "contrast" and palette.orangeContrast or palette.orange,
identifier = palette.violet2, identifier = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
parameter = palette.gray3, parameter = palette.gray3,
fun = palette.blue3, fun = foreground == "contrast" and palette.blue3Contrast or palette.blue3,
statement = palette.violet2, statement = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
keyword = palette.violet2, keyword = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
operator = palette.gray3, operator = palette.gray3,
preproc = palette.gray3, preproc = palette.gray3,
type = palette.aqua, type = foreground == "contrast" and palette.aquaContrast or palette.aqua,
regex = palette.red3, regex = foreground == "contrast" and palette.red3Contrast or palette.red3,
deprecated = palette.gray, deprecated = palette.gray,
punct = palette.gray3, punct = palette.gray3,
comment = palette.gray4, comment = palette.gray4,
special1 = palette.yellow3, special1 = foreground == "contrast" and palette.yellow3Contrast or palette.yellow3,
special2 = palette.violet2, special2 = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
special3 = palette.violet2, special3 = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
}, },
diag = { diag = {
error = palette.red, error = palette.red,
@@ -509,9 +511,9 @@ return {
text = palette.diffYellow, text = palette.diffYellow,
}, },
vcs = { vcs = {
added = palette.autumnGreen, added = palette.gitGreen,
removed = palette.autumnRed, removed = palette.gitRed,
changed = palette.autumnYellow, changed = palette.gitYellow,
untracked = palette.gray4, untracked = palette.gray4,
}, },
term = { term = {
+3 -3
View File
@@ -1,5 +1,5 @@
local M = {} local M = {}
local PATH_SEP = vim.loop.os_uname().version:match("Windows") and "\\" or "/" local PATH_SEP = vim.uv.os_uname().version:match("Windows") and "\\" or "/"
local get_compiled_path = function(theme) local get_compiled_path = function(theme)
return table.concat({ vim.fn.stdpath("state"), "kanso", theme .. "_compiled.lua" }, PATH_SEP) return table.concat({ vim.fn.stdpath("state"), "kanso", theme .. "_compiled.lua" }, PATH_SEP)
@@ -8,14 +8,14 @@ end
---@return string theme ---@return string theme
function M.get_theme_from_bg_opt() function M.get_theme_from_bg_opt()
local config = require("kanso").config local config = require("kanso").config
return config.theme[vim.o.background] or config.theme.default return config.background[vim.o.background] or config.theme
end end
---@param theme string ---@param theme string
---@param highlights table ---@param highlights table
---@param termcolors table ---@param termcolors table
function M.compile(theme, highlights, termcolors) function M.compile(theme, highlights, termcolors)
vim.loop.fs_mkdir(vim.fn.stdpath("state") .. PATH_SEP .. "kanso", 448) vim.uv.fs_mkdir(vim.fn.stdpath("state") .. PATH_SEP .. "kanso", 448)
local fname = get_compiled_path(theme) local fname = get_compiled_path(theme)
local file, err = io.open(fname, "wb") local file, err = io.open(fname, "wb")
+2 -2
View File
@@ -3,8 +3,8 @@ local theme = require("kanso.colors").setup().theme
local kanso = {} local kanso = {}
kanso.normal = { kanso.normal = {
a = { bg = theme.syn.fun, fg = theme.ui.bg }, a = { bg = theme.ui.fg, fg = theme.ui.bg },
b = { bg = theme.ui.none, fg = theme.syn.fun }, b = { bg = theme.ui.none, fg = theme.ui.fg },
c = { bg = theme.ui.none, fg = theme.ui.fg }, c = { bg = theme.ui.none, fg = theme.ui.fg },
} }