feat: add contrast foreground config option

This commit is contained in:
Webhooked 2025-07-25 11:00:04 +02:00
commit 024999f6f9
7 changed files with 226 additions and 76 deletions

View file

@ -29,7 +29,8 @@
- 🌈 Extensive support for `TreeSitter` syntax highlighting
- 🔌 Compatible with popular plugins out of the box
- ⚡ 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
## 📦 Installation
@ -93,11 +94,12 @@ require('kanso').setup({
overrides = function(colors) -- add/modify highlights
return {}
end,
theme = "zen", -- Load "zen" theme
theme = "ink", -- Load "ink" theme
background = { -- map the value of 'background' option to a theme
dark = "zen", -- try "ink" !
light = "pearl" -- try "mist" !
dark = "ink", -- try "zen" !
light = "ink" -- try "pearl" or "mist" !
},
foreground = "default", -- "default" or "contrast" (can also be a table like background)
})
-- setup must be called before loading
@ -177,6 +179,39 @@ require("kanso").load("zen")
</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
In Kansō, there are _two_ kinds of colors: `PaletteColors` and `ThemeColors`;

View file

@ -57,6 +57,30 @@ local palette = {
orange2 = "#b98d7b",
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 = "#C5C9C7",
fg2 = "#f2f1ef",
@ -108,6 +132,30 @@ local palette = {
pearlTeal2 = "#6693bf",
pearlTeal3 = "#5a7785",
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 = {}
@ -117,7 +165,7 @@ local M = {}
--- Defaults to KansoConfig.colors.
--- - theme: Use selected theme. Defaults to KansoConfig.theme
--- 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}
function M.setup(opts)
opts = opts or {}
@ -134,7 +182,14 @@ function M.setup(opts)
local updated_palette_colors = vim.tbl_extend("force", palette, override_colors.palette or {})
-- 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
local theme_overrides =

View file

@ -527,7 +527,7 @@ function M.setup(colors, config)
BufferLineBackground = { 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 },
BufferLineCloseButton = { bg = theme.ui.none },
BufferLineCloseButtonSelected = { bg = theme.ui.none },

View file

@ -25,12 +25,14 @@ M.config = {
return {}
end,
---@type { dark: string, light: string }
background = { dark = "ink", light = "pearl" },
background = { dark = "ink", light = "ink" },
theme = "ink",
---@type { dark: "default"|"contrast", light: "default"|"contrast" }|"default"|"contrast"
foreground = "default",
compile = false,
}
local function check_config(config)
local function check_config(_)
local err
return not err
end
@ -60,6 +62,22 @@ function M.load(theme)
vim.g.colors_name = "kanso"
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 utils.load_compiled(theme) then
return
@ -68,7 +86,11 @@ function M.load(theme)
M.compile()
utils.load_compiled(theme)
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)
require("kanso.highlights").highlight(highlights, M.config.terminalColors and colors.theme.term or {})
end
@ -76,9 +98,45 @@ end
function M.compile()
for theme, _ in pairs(require("kanso.themes")) do
local colors = require("kanso.colors").setup({ theme = theme, colors = M.config.colors })
local highlights = require("kanso.highlights").setup(colors, M.config)
require("kanso.utils").compile(theme, highlights, M.config.terminalColors and colors.theme.term or {})
-- 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)
require("kanso.utils").compile(theme, highlights, M.config.terminalColors and colors.theme.term or {})
end
end
end

View file

@ -1,5 +1,3 @@
local c = require("kanso.lib.color")
--TODO:
--PreProc needs its own color
--parameter and field should be different
@ -94,8 +92,9 @@ local c = require("kanso.lib.color")
return {
---@param palette PaletteColors
---@param foreground? "default"|"contrast"
---@return ThemeColors
zen = function(palette)
zen = function(palette, foreground)
return {
ui = {
none = "NONE",
@ -143,25 +142,25 @@ return {
},
},
syn = {
string = palette.green3,
string = foreground == "contrast" and palette.green3Contrast or palette.green3,
variable = "NONE",
number = palette.pink,
constant = palette.orange,
identifier = palette.violet2,
number = foreground == "contrast" and palette.pinkContrast or palette.pink,
constant = foreground == "contrast" and palette.orangeContrast or palette.orange,
identifier = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
parameter = palette.gray3,
fun = palette.blue3,
statement = palette.violet2,
keyword = palette.violet2,
fun = foreground == "contrast" and palette.blue3Contrast or palette.blue3,
statement = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
keyword = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
operator = palette.gray3,
preproc = palette.gray3,
type = palette.aqua,
regex = palette.red3,
type = foreground == "contrast" and palette.aquaContrast or palette.aqua,
regex = foreground == "contrast" and palette.red3Contrast or palette.red3,
deprecated = palette.gray,
punct = palette.gray3,
comment = palette.gray4,
special1 = palette.yellow3,
special2 = palette.violet2,
special3 = palette.violet2,
special1 = foreground == "contrast" and palette.yellow3Contrast or palette.yellow3,
special2 = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
special3 = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
},
diag = {
error = palette.red,
@ -205,8 +204,9 @@ return {
}
end,
---@param palette PaletteColors
---@param foreground? "default"|"contrast"
---@return ThemeColors
ink = function(palette)
ink = function(palette, foreground)
return {
ui = {
none = "NONE",
@ -254,25 +254,25 @@ return {
},
},
syn = {
string = palette.green3,
string = foreground == "contrast" and palette.green3Contrast or palette.green3,
variable = "NONE",
number = palette.pink,
constant = palette.orange,
identifier = palette.violet2,
number = foreground == "contrast" and palette.pinkContrast or palette.pink,
constant = foreground == "contrast" and palette.orangeContrast or palette.orange,
identifier = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
parameter = palette.gray3,
fun = palette.blue3,
statement = palette.violet2,
keyword = palette.violet2,
fun = foreground == "contrast" and palette.blue3Contrast or palette.blue3,
statement = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
keyword = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
operator = palette.gray3,
preproc = palette.gray3,
type = palette.aqua,
regex = palette.red3,
type = foreground == "contrast" and palette.aquaContrast or palette.aqua,
regex = foreground == "contrast" and palette.red3Contrast or palette.red3,
deprecated = palette.gray,
punct = palette.gray3,
comment = palette.gray4,
special1 = palette.yellow3,
special2 = palette.violet2,
special3 = palette.violet2,
special1 = foreground == "contrast" and palette.yellow3Contrast or palette.yellow3,
special2 = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
special3 = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
},
diag = {
error = palette.red,
@ -316,8 +316,9 @@ return {
}
end,
---@param palette PaletteColors
---@param foreground? "default"|"contrast"
---@return ThemeColors
pearl = function(palette)
pearl = function(palette, foreground)
return {
ui = {
none = "NONE",
@ -364,25 +365,25 @@ return {
},
},
syn = {
string = palette.pearlGreen,
string = foreground == "contrast" and palette.pearlGreenContrast or palette.pearlGreen,
variable = "NONE",
number = palette.pearlPink,
constant = palette.pearlOrange,
identifier = palette.pearlViolet4,
parameter = palette.pearlBlue5,
fun = palette.pearlBlue4,
statement = palette.pearlViolet4,
keyword = palette.pearlViolet4,
number = foreground == "contrast" and palette.pearlPinkContrast or palette.pearlPink,
constant = foreground == "contrast" and palette.pearlOrangeContrast or palette.pearlOrange,
identifier = foreground == "contrast" and palette.pearlViolet4Contrast or palette.pearlViolet4,
parameter = foreground == "contrast" and palette.pearlBlue5Contrast or palette.pearlBlue5,
fun = foreground == "contrast" and palette.pearlBlue4Contrast or palette.pearlBlue4,
statement = foreground == "contrast" and palette.pearlViolet4Contrast or palette.pearlViolet4,
keyword = foreground == "contrast" and palette.pearlViolet4Contrast or palette.pearlViolet4,
operator = palette.pearlGray3,
preproc = palette.pearlGray2,
type = palette.pearlAqua,
regex = palette.pearlYellow2,
type = foreground == "contrast" and palette.pearlAquaContrast or palette.pearlAqua,
regex = foreground == "contrast" and palette.pearlYellow2Contrast or palette.pearlYellow2,
deprecated = palette.pearlGray3,
comment = palette.pearlGray3,
punct = palette.pearlGray3,
special1 = palette.pearlYellow2,
special2 = palette.pearlViolet4,
special3 = palette.pearlViolet4,
special1 = foreground == "contrast" and palette.pearlYellow2Contrast or palette.pearlYellow2,
special2 = foreground == "contrast" and palette.pearlViolet4Contrast or palette.pearlViolet4,
special3 = foreground == "contrast" and palette.pearlViolet4Contrast or palette.pearlViolet4,
},
vcs = {
added = palette.pearlGreen2,
@ -426,8 +427,9 @@ return {
}
end,
---@param palette PaletteColors
---@param foreground? "default"|"contrast"
---@return ThemeColors
mist = function(palette)
mist = function(palette, foreground)
return {
ui = {
none = "NONE",
@ -475,25 +477,25 @@ return {
},
},
syn = {
string = palette.green3,
string = foreground == "contrast" and palette.green3Contrast or palette.green3,
variable = "NONE",
number = palette.pink,
constant = palette.orange,
identifier = palette.violet2,
number = foreground == "contrast" and palette.pinkContrast or palette.pink,
constant = foreground == "contrast" and palette.orangeContrast or palette.orange,
identifier = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
parameter = palette.gray3,
fun = palette.blue3,
statement = palette.violet2,
keyword = palette.violet2,
fun = foreground == "contrast" and palette.blue3Contrast or palette.blue3,
statement = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
keyword = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
operator = palette.gray3,
preproc = palette.gray3,
type = palette.aqua,
regex = palette.red3,
type = foreground == "contrast" and palette.aquaContrast or palette.aqua,
regex = foreground == "contrast" and palette.red3Contrast or palette.red3,
deprecated = palette.gray,
punct = palette.gray3,
comment = palette.gray4,
special1 = palette.yellow3,
special2 = palette.violet2,
special3 = palette.violet2,
special1 = foreground == "contrast" and palette.yellow3Contrast or palette.yellow3,
special2 = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
special3 = foreground == "contrast" and palette.violet2Contrast or palette.violet2,
},
diag = {
error = palette.red,
@ -509,9 +511,9 @@ return {
text = palette.diffYellow,
},
vcs = {
added = palette.autumnGreen,
removed = palette.autumnRed,
changed = palette.autumnYellow,
added = palette.gitGreen,
removed = palette.gitRed,
changed = palette.gitYellow,
untracked = palette.gray4,
},
term = {

View file

@ -1,5 +1,5 @@
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)
return table.concat({ vim.fn.stdpath("state"), "kanso", theme .. "_compiled.lua" }, PATH_SEP)
@ -8,14 +8,14 @@ end
---@return string theme
function M.get_theme_from_bg_opt()
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
---@param theme string
---@param highlights table
---@param termcolors table
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 file, err = io.open(fname, "wb")

View file

@ -3,8 +3,8 @@ local theme = require("kanso.colors").setup().theme
local kanso = {}
kanso.normal = {
a = { bg = theme.syn.fun, fg = theme.ui.bg },
b = { bg = theme.ui.none, fg = theme.syn.fun },
a = { bg = theme.ui.fg, fg = theme.ui.bg },
b = { bg = theme.ui.none, fg = theme.ui.fg },
c = { bg = theme.ui.none, fg = theme.ui.fg },
}