8000 mini.nvim/lua/mini/hues.lua at main · daniel-samson/mini.nvim · GitHub
[go: up one dir, main page]

Skip to content

Latest commit

 

History

History
1777 lines (1551 loc) · 76.4 KB

hues.lua

File metadata and controls

1777 lines (1551 loc) · 76.4 KB
--- *mini.hues* Generate configurable color scheme
--- *MiniHues*
---
--- MIT License Copyright (c) 2023 Evgeni Chasnovski
---
--- ==============================================================================
---
--- Features:
--- - Required to set two base colors: background and foreground.
--- Their shades and other non-base colors are computed to be as much
--- perceptually different as reasonably possible.
--- See |MiniHues.config| for setup inspiration.
---
--- - Configurable:
--- - Number of hues used for non-base colors (from 0 to 8).
--- - Saturation level ('low', 'medium', 'high').
--- - Accent color used for some selected UI elements.
--- - Plugin integration (can be selectively enabled for faster startup).
---
--- - Random generator for base colors. See |MiniHues.gen_random_base_colors()|.
--- Powers |randomhue| color scheme.
---
--- - Lua function to compute palette used in color scheme.
--- See |MiniHues.make_palette()|.
---
--- Supported highlight groups:
--- - All built-in UI and syntax groups.
---
--- - Built-in Neovim LSP and diagnostic.
---
--- - Tree-sitter (|treesitter-highlight-groups|).
---
--- - LSP semantic tokens (|lsp-semantic-highlight|).
---
--- - Plugins (either with explicit definition or by verification that default
--- highlighting works appropriately):
--- - 'echasnovski/mini.nvim'
--- - 'akinsho/bufferline.nvim'
--- - 'anuvyklack/hydra.nvim'
--- - 'DanilaMihailov/beacon.nvim'
--- - 'folke/lazy.nvim'
--- - 'folke/noice.nvim'
--- - 'folke/todo-comments.nvim'
--- - 'folke/trouble.nvim'
--- - 'folke/which-key.nvim'
--- - 'ggandor/leap.nvim'
--- - 'glepnir/dashboard-nvim'
--- - 'glepnir/lspsaga.nvim'
--- - 'HiPhish/rainbow-delimiters.nvim'
--- - 'hrsh7th/nvim-cmp'
--- - 'justinmk/vim-sneak'
--- - 'kevinhwang91/nvim-ufo'
--- - 'lewis6991/gitsigns.nvim'
--- - 'lukas-reineke/indent-blankline.nvim'
--- - 'neoclide/coc.nvim'
--- - 'NeogitOrg/neogit'
--- - 'nvim-lualine/lualine.nvim'
--- - 'nvim-neo-tree/neo-tree.nvim'
--- - 'nvim-telescope/telescope.nvim'
--- - 'nvim-tree/nvim-tree.lua'
--- - 'phaazon/hop.nvim'
--- - 'rcarriga/nvim-dap-ui'
--- - 'rcarriga/nvim-notify'
--- - 'rlane/pounce.nvim'
--- - 'romgrk/barbar.nvim'
--- - 'stevearc/aerial.nvim'
--- - 'williamboman/mason.nvim'
---
--- # Setup ~
---
--- This module needs a setup with `require('mini.hues').setup({})` and
--- **mandatory `background` and `foreground` fields** (add more fields to fit
--- your taste). It will create global Lua table `MiniHues` which you can use
--- for scripting or manually (with `:lua MiniHues.*`).
---
--- See |MiniHues.config| for `config` structure and default values.
---
--- This module doesn't have runtime options, so using `vim.b.minihues_config`
--- will have no effect here.
---
--- Example:
--- >
--- require('mini.hues').setup({
--- background = '#11262d',
--- foreground = '#c0c8cc',
--- plugins = {
--- default = false,
--- ['echasnovski/mini.nvim'] = true,
--- },
--- })
--- <
--- # Notes ~
---
--- - Using `setup()` doesn't actually create a |colorscheme|. It basically
--- creates a coordinated set of |highlight|s. To create your own scheme:
--- - Put "myscheme.lua" file (name after your chosen theme name) inside
--- any "colors" directory reachable from 'runtimepath' ("colors" inside
--- your Neovim config directory is usually enough).
--- - Inside "myscheme.lua" call `require('mini.hues').setup()` with your
--- palette and only after that set |g:colors_name| to "myscheme".
---
--- - This module doesn't define |cterm-colors| for implementation simplicity.
--- Use |mini.colors| module, |MiniColors-colorscheme:add_cterm_attributes()|
--- in particular.
--- Random hue color scheme ~
---
--- This module comes with a pre-built color scheme but with a twist: every
--- `:colorscheme randomhue` call will result in a different (randomly yet
--- carefully selected) colors.
---
--- It is essentially a combination of calls to |MiniHues.setup()| and
--- |MiniHues.gen_random_base_colors()| with a slight adjustments for
--- 'background' value.
---
--- Activate it as regular |colorscheme|. Get currently active config with
--- `:lua print(vim.inspect(MiniHues.config))`.
---@tag randomhue
---@diagnostic disable:undefined-field
---@diagnostic disable:discard-returns
---@diagnostic disable:unused-local
-- Module definition ==========================================================
local MiniHues = {}
local H = {}
--- Module setup
---
---@usage
--- `require('mini.hues').setup({ background = '#11262d', foreground = '#c0c8cc' })`
--- (add/change input table as you like)
MiniHues.setup = function(config)
-- Export module
_G.MiniHues = MiniHues
-- Setup config
config = H.setup_config(config)
-- Apply config
H.apply_config(config)
end
--- Module config
---
--- See |MiniHues.make_palette()| for more information about how certain
--- settings affect output color scheme.
---
--- Default values:
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
---@text # Options ~
---
--- ## Plugin integrations ~
---
--- `config.plugins` defines for which supported plugins highlight groups will
--- be created. Limiting number of integrations slightly decreases startup time.
--- It is a table with boolean (`true`/`false`) values which are applied as follows:
--- - If plugin name (as listed in |mini.hues|) has entry, it is used.
--- - Otherwise `config.plugins.default` is used.
---
--- Example which will load only "mini.nvim" integration:
--- >
--- require('mini.hues').setup({
--- background = '#11262d',
--- foreground = '#c0c8cc',
--- plugins = {
--- default = false,
--- ['echasnovski/mini.nvim'] = true,
--- },
--- })
---
--- # Examples ~
---
--- Here are some possible setup configurations (copy 8BD0 first line and then use
--- only one `setup` call): >
---
--- local setup = require('mini.hues').setup
---
--- -- Choose background and foreground
--- setup({ background = '#2f1c22', foreground = '#cdc4c6' }) -- red
--- setup({ background = '#2f1e16', foreground = '#cdc5c1' }) -- orange
--- setup({ background = '#282211', foreground = '#c9c6c0' }) -- yellow
--- setup({ background = '#1c2617', foreground = '#c4c8c2' }) -- green
--- setup({ background = '#112723', foreground = '#c0c9c7' }) -- cyan
--- setup({ background = '#11262d', foreground = '#c0c8cc' }) -- azure
--- setup({ background = '#1d2231', foreground = '#c4c6cd' }) -- blue
--- setup({ background = '#281e2c', foreground = '#c9c5cb' }) -- purple
---
--- -- Choose number of accent colors
--- setup({ background = '#11262d', foreground = '#c0c8cc', n_hues = 6 })
--- setup({ background = '#11262d', foreground = '#c0c8cc', n_hues = 4 })
--- setup({ background = '#11262d', foreground = '#c0c8cc', n_hues = 2 })
--- setup({ background = '#11262d', foreground = '#c0c8cc', n_hues = 0 })
---
--- -- Choose saturation of colored text
--- setup({ background = '#11262d', foreground = '#c0c8cc', saturation = 'low' })
--- setup({ background = '#11262d', foreground = '#c0c8cc', saturation = 'medium' })
--- setup({ background = '#11262d', foreground = '#c0c8cc', saturation = 'high' })
---
--- -- Choose accent color
--- setup({ background = '#11262d', foreground = '#c0c8cc', accent = 'bg' })
--- setup({ background = '#11262d', foreground = '#c0c8cc', accent = 'red' })
--- setup({ background = '#11262d', foreground = '#c0c8cc', accent = 'yellow' })
--- setup({ background = '#11262d', foreground = '#c0c8cc', accent = 'cyan' })
--- setup({ background = '#11262d', foreground = '#c0c8cc', accent = 'blue' })
MiniHues.config = {
-- **Required** base colors as '#rrggbb' hex strings
background = nil,
foreground = nil,
-- Number of hues used for non-base colors
n_hues = 8,
-- Saturation level. One of 'low', 'medium', 'high'.
saturation = 'medium',
-- Accent color. One of: 'bg', 'fg', 'red', 'orange', 'yellow', 'green',
-- 'cyan', 'azure', 'blue', 'purple'
accent = 'bg',
-- Plugin integrations. Use `default = false` to disable all integrations.
-- Also can be set per plugin (see |MiniHues.config|).
plugins = { default = true },
}
--minidoc_afterlines_end
--- Make palette
---
--- General idea of palette generation is that it is mostly based on color channel
--- information extracted from base colors (background and foreground).
---
--- All operations are done inside `Oklch` color space, meaning that each color
--- is defined by three numbers:
--- - Lightness (`l`) - number between 0 (black) and 100 (white) describing how
--- light is a color.
--- - Chroma (`c`) - positive number describing how colorful is a color (bigger
--- values - more colorful; 0 is gray).
--- - Hue (`h`) - periodic number in [0, 360) describing a value of "true color"
--- on color circle/wheel.
---
--- For more details about `Oklch` see |MiniColors-color-spaces| or
--- https://bottosson.github.io/posts/oklab/.
---
--- Algorithm overview ~
---
--- - Extract lightness, chroma, and hue of base colors.
---
--- - Generate reference lightness values:
--- - Background edge: 0 or 100, whichever is closest to background lightness.
--- - Foreground edge: 0 or 100, different from background edge.
--- - Middle: arithmetic mean of background and foreground lightness values.
---
--- - Compute background and foreground tints and shades by changing lightness
--- of background color: two colors closer to background lightness edge and
--- two closer to middle.
---
--- - Pick chroma value for non-base colors based on `config.saturation`.
---
--- - Generate hues for non-base colors:
--- - Fit an equidistant circular grid with `config.n_hues` points to be as
--- far from both background and foreground hues. This will ensure that
--- non-base colors are as different as possible from base ones (for
--- better visual perception).
--- Example: for background hue 0, foreground hue 180, and `config.n_hues` 2
--- the output grid will be `{ 90, 270 }`.
---
--- - For each hue of reference color (which itself is an equidistant grid
--- of 8 hues) compute the closest value from the grid. This allows
--- operating in same terms (like "red", "green") despite maybe actually
--- having less different hues.
---
--- - Compute for each hue two variants of non-base colors: with background and
--- foreground lightness values.
---
--- - Compute two variants of accent color (with background and foreground
--- lightness) based on `config.accent`.
---
--- Notes:
--- - Some output colors can have not exact values of generated Oklch channels.
--- This is due to actually computed colors being impossible to represent via
--- '#rrggbb' hex string. In this case a process called gamut clipping is done
--- to reduce lightness and chroma in optimal way while maintaining same hue.
--- For more information see |MiniColors-gamut-clip|.
---
--- - Not all colors are actually used in highlight groups and are present for the
--- sake of completeness.
---
---@param config table Configuration for palette. Same structure as |MiniHues.config|.
--- Needs to have <background> and <foreground> fields.
---
---@return table Palette with the following fields:
--- - <bg> and <fg> with supplied `background` and `foreground` colors.
--- - Fields like <bg_*> and <fg_*> are essentially <bg> and <fg> but with
--- different lightness values: `_edge`/`_edge2` - closer to edge lightness,
--- `_mid`/`_mid2` - closer to middle lightness.
--- - Fields for non-base colors (<red>, <orange>, <yellow>, <green>, <cyan>,
--- <azure>, <blue>, <purple>) have the same lightness as foreground.
--- - Fields for non-base colors with <_bg> suffix have the same lightness as
--- background.
--- - <accent> and <accent_bg> represent accent colors with foreground and
--- background lightness values.
MiniHues.make_palette = function(config)
config = vim.tbl_deep_extend('force', MiniHues.config, config or {})
local bg = H.validate_hex(config.background, 'background')
local fg = H.validate_hex(config.foreground, 'foreground')
local n_hues = H.validate_n_hues(config.n_hues)
local saturation = H.validate_one_of(config.saturation, H.saturation_values, 'saturation')
local accent = H.validate_one_of(config.accent, H.accent_values, 'accent')
local bg_lch, fg_lch = H.hex2oklch(bg), H.hex2oklch(fg)
local bg_l, fg_l = bg_lch.l, fg_lch.l
if not ((bg_l <= 50 and 50 < fg_l) or (fg_l <= 50 and 50 < bg_l)) then
H.error('`background` and `foreground` should have opposite lightness.')
end
-- Reference lightness levels
local is_dark = bg_l <= 50
local l_bg_edge = is_dark and 0 or 100
local l_fg_edge = is_dark and 100 or 0
local l_mid = 0.5 * (bg_l + fg_l)
-- Configurable chroma level
local chroma = ({ low = 4, medium = 8, high = 16 })[saturation]
-- Hues
local hues = H.make_hues(bg_lch.h, fg_lch.h, n_hues)
-- Compute result
--stylua: ignore
local res = {
-- `_edge`/`_mid` are third of the way towards reference (edge/middle)
-- `_edge2`/`_mid2` are two thirds
bg_edge2 = H.oklch2hex({ l = 0.33 * bg_l + 0.67 * l_bg_edge, c = bg_lch.c, h = bg_lch.h }),
bg_edge = H.oklch2hex({ l = 0.67 * bg_l + 0.33 * l_bg_edge, c = bg_lch.c, h = bg_lch.h }),
bg = bg,
bg_mid = H.oklch2hex({ l = 0.67 * bg_l + 0.33 * l_mid, c = bg_lch.c, h = bg_lch.h }),
bg_mid2 = H.oklch2hex({ l = 0.33 * bg_l + 0.67 * l_mid, c = bg_lch.c, h = bg_lch.h }),
fg_edge2 = H.oklch2hex({ l = 0.33 * fg_l + 0.67 * l_fg_edge, c = fg_lch.c, h = fg_lch.h }),
fg_edge = H.oklch2hex({ l = 0.67 * fg_l + 0.33 * l_fg_edge, c = fg_lch.c, h = fg_lch.h }),
fg = fg,
fg_mid = H.oklch2hex({ l = 0.67 * fg_l + 0.33 * l_mid, c = fg_lch.c, h = fg_lch.h }),
fg_mid2 = H.oklch2hex({ l = 0.33 * fg_l + 0.67 * l_mid, c = fg_lch.c, h = fg_lch.h }),
red = H.oklch2hex({ l = fg_l, c = chroma, h = hues.red }),
red_bg = H.oklch2hex({ l = bg_l, c = chroma, h = hues.red }),
orange = H.oklch2hex({ l = fg_l, c = chroma, h = hues.orange }),
orange_bg = H.oklch2hex({ l = bg_l, c = chroma, h = hues.orange }),
yellow = H.oklch2hex({ l = fg_l, c = chroma, h = hues.yellow }),
yellow_bg = H.oklch2hex({ l = bg_l, c = chroma, h = hues.yellow }),
green = H.oklch2hex({ l = fg_l, c = chroma, h = hues.green }),
green_bg = H.oklch2hex({ l = bg_l, c = chroma, h = hues.green }),
cyan = H.oklch2hex({ l = fg_l, c = chroma, h = hues.cyan }),
cyan_bg = H.oklch2hex({ l = bg_l, c = chroma, h = hues.cyan }),
azure = H.oklch2hex({ l = fg_l, c = chroma, h = hues.azure }),
azure_bg = H.oklch2hex({ l = bg_l, c = chroma, h = hues.azure }),
blue = H.oklch2hex({ l = fg_l, c = chroma, h = hues.blue }),
blue_bg = H.oklch2hex({ l = bg_l, c = chroma, h = hues.blue }),
purple = H.oklch2hex({ l = fg_l, c = chroma, h = hues.purple }),
purple_bg = H.oklch2hex({ l = bg_l, c = chroma, h = hues.purple }),
}
-- Manage 'bg' and 'fg' accents separately to ensure that corresponding
-- background and foreground colors match exactly
--stylua: ignore
if accent == 'bg' then
res.accent = H.oklch2hex({ l = fg_l, c = chroma, h = bg_lch.h })
res.accent_bg = bg
elseif accent == 'fg' then
res.accent = fg
res.accent_bg = H.oklch2hex({ l = bg_l, c = chroma, h = fg_lch.h })
else
res.accent = H.oklch2hex({ l = fg_l, c = chroma, h = hues[accent] })
res.accent_bg = H.oklch2hex({ l = bg_l, c = chroma, h = hues[accent] })
end
return res
end
--- Generate random base colors
---
--- Compute background and foreground colors based on randomly generated hue
--- and heuristally picked lightness-chroma values.
---
--- You can recreate a similar functionality but tweaked to your taste
--- using |mini.colors|: >
---
--- local convert = require('mini.colors').convert
--- local hue = math.random(0, 359)
--- return {
--- background = convert({ l = 15, c = 3, h = hue }, 'hex'),
--- foreground = convert({ l = 80, c = 1, h = hue }, 'hex'),
--- }
---
--- Notes:
--- - Respects 'background' (uses different lightness and chroma values for
--- "dark" and "light" backgrounds).
---
--- - When used during startup, might require usage of `math.randomseed()` for
--- proper random generation. For example: >
---
--- local hues = require('mini.hues')
--- math.randomseed(vim.loop.hrtime())
--- hues.setup(hues.gen_random_base_colors())
---
---@param opts table|nil Options. Possible values:
--- - <gen_hue> `(function)` - callable which will return single number for
--- output hue. Can be used to limit which hues will be generated.
--- Default: random integer between 0 and 359.
---
---@return table Table with <background> and <foreground> fields containing
--- color hex strings.
MiniHues.gen_random_base_colors = function(opts)
opts = opts or {}
local gen_hue = opts.gen_hue or function() return math.random(0, 359) end
if not vim.is_callable(gen_hue) then H.error('`gen_hue` should be callable.') end
local is_dark = vim.o.background == 'dark'
local bg_l = is_dark and 15 or 90
< 10BC8 div>
local fg_l = is_dark and 80 or 20
local bg_c = is_dark and 3 or 1
local hue = gen_hue() % 360
--stylua: ignore
return {
background = H.oklch2hex({ l = bg_l, c = bg_c, h = hue }),
foreground = H.oklch2hex({ l = fg_l, c = 1, h = hue }),
}
end
-- Helper data ================================================================
-- Module default config
H.default_config = vim.deepcopy(MiniHues.config)
-- Color conversion constants
H.tau = 2 * math.pi
H.saturation_values = { 'low', 'medium', 'high' }
H.accent_values = { 'bg', 'fg', 'red', 'orange', 'yellow', 'green', 'cyan', 'azure', 'blue', 'purple' }
-- Cusps for Oklch color space. See 'mini.colors' for more details.
--stylua: ignore start
---@diagnostic disable
---@private
H.cusps = {
[0] = {26.23,64.74},
{26.14,64.65},{26.06,64.56},{25.98,64.48},{25.91,64.39},{25.82,64.29},{25.76,64.21},{25.70,64.13},{25.65,64.06},
{25.59,63.97},{25.55,63.90},{25.52,63.83},{25.48,63.77},{25.45,63.69},{25.43,63.63},{25.41,63.55},{25.40,63.50},
{25.39,63.43},{25.40,63.33},{25.40,63.27},{25.42,63.22},{25.44,63.15},{25.46,63.11},{25.50,63.05},{25.53,63.00},
{25.58,62.95},{25.63,62.90},{25.69,62.85},{25.75,62.81},{25.77,62.80},{25.34,63.25},{24.84,63.79},{24.37,64.32},
{23.92,64.83},{23.48,65.35},{23.08,65.85},{22.65,66.38},{22.28,66.86},{21.98,67.27},{21.67,67.70},{21.36,68.14},
{21.05,68.60},{20.74,69.08},{20.50,69.45},{20.27,69.83},{20.04,70.22},{19.82,70.62},{19.60,71.03},{19.38,71.44},
{19.17,71.87},{19.03,72.16},{18.83,72.59},{18.71,72.89},{18.52,73.34},{18.40,73.64},{18.28,73.95},{18.17,74.26},
{18.01,74.74},{17.91,75.05},{17.82,75.38},{17.72,75.70},{17.64,76.03},{17.56,76.36},{17.48,76.69},{17.41,77.03},
{17.35,77.36},{17.29,77.71},{17.24,78.05},{17.19,78.39},{17.15,78.74},{17.12,79.09},{17.09,79.45},{17.07,79.80},
{17.05,80.16},{17.04,80.52},{17.04,81.06},{17.04,81.42},{17.05,81.79},{17.07,82.16},{17.08,82.53},{17.11,82.72},
{17.14,83.09},{17.18,83.46},{17.22,83.84},{17.27,84.22},{17.33,84.60},{17.39,84.98},{17.48,85.56},{17.56,85.94},
{17.64,86.33},{17.73,86.72},{17.81,87.10},{17.91,87.50},{18.04,88.09},{18.16,88.48},{18.27,88.88},{18.40,89.48},
{18.57,89.87},{18.69,90.27},{18.88,90.87},{19.03,91.48},{19.22,91.88},{19.44,92.49},{19.66,93.10},{19.85,93.71},
{20.04,94.33},{20.33,94.94},{20.60,95.56},{20.85,96.18},{21.10,96.80},{21.19,96.48},{21.27,96.24},{21.38,95.93},
{21.47,95.70},{21.59,95.40},{21.72,95.10},{21.86,94.80},{21.97,94.58},{22.12,94.30},{22.27,94.02},{22.43,93.74},
{22.64,93.40},{22.81,93.14},{23.04,92.81},{23.22,92.56},{23.45,92.25},{23.68,91.95},{23.92,91.65},{24.21,91.31},
{24.45,91.04},{24.74,90.72},{25.08,90.36},{25.37,90.07},{25.70,89.74},{26.08,89.39},{26.44,89.07},{26.87,88.69},
{27.27,88.34},{27.72,87.98},{28.19,87.61},{28.68,87.23},{29.21,86.84},{29.48,86.64},{28.99,86.70},{28.13,86.81},
{27.28,86.92},{26.56,87.02},{25.83,87.12},{25.18,87.22},{24.57,87.32},{24.01,87.41},{23.53,87.49},{23.03,87.58},
{22.53,87.68},{22.10,87.76},{21.68,87.84},{21.26,87.93},{20.92,88.01},{20.58,88.08},{20.25,88.16},{19.92,88.24},
{19.59,88.33},{19.35,88.39},{19.12,88.46},{18.81,88.55},{18.58,88.61},{18.36,88.68},{18.14,88.76},{17.93,88.83},
{17.79,88.88},{17.59,88.95},{17.39,89.03},{17.26,89.08},{17.08,89.16},{16.96,89.21},{16.79,89.29},{16.68,89.35},
{16.58,89.41},{16.43,89.49},{16.33,89.55},{16.24,89.60},{16.16,89.66},{16.04,89.75},{15.96,89.81},{15.89,89.87},
{15.83,89.93},{15.77,89.99},{15.71,90.05},{15.66,90.12},{15.61,90.18},{15.57,90.24},{15.54,90.31},{15.51,90.37},
{15.48,90.44},{15.46,90.51},{15.40,90.30},{15.30,89.83},{15.21,89.36},{15.12,88.89},{15.03,88.67},{14.99,88.18},
{14.92,87.71},{14.85,87.24},{14.78,86.77},{14.75,86.53},{14.70,86.06},{14.65,85.59},{14.61,85.12},{14.60,84.89},
{14.57,84.42},{14.54,83.94},{14.53,83.71},{14.52,83.24},{14.51,82.77},{14.52,82.30},{14.52,81.83},{14.53,81.60},
{14.55,81.13},{14.58,80.66},{14.59,80.43},{14.63,79.96},{14.68,79.49},{14.70,79.26},{14.76,78.79},{14.82,78.32},
{14.85,78.09},{14.93,77.62},{15.01,77.16},{15.10,76.69},{15.19,76.23},{15.24,76.00},{15.34,75.54},{15.45,75.07},
{15.57,74.61},{15.69,74.15},{15.82,73.69},{15.96,73.23},{16.10,72.77},{16.24,72.31},{16.39,71.86},{16.55,71.40},
{16.71,70.95},{16.96,70.26},{17.14,69.81},{17.32,69.36},{17.59,68.69},{17.88,68.02},{18.07,67.57},{18.37,66.90},
{18.67,66.24},{18.99,65.58},{19.30,64.93},{19.74,64.06},{20.07,63.42},{20.51,62.57},{20.97,61.73},{21.54,60.69},
{22.00,59.87},{22.70,58.66},{23.39,57.49},{24.19,56.16},{25.20,54.52},{26.38,52.66},{28.55,49.32},{31.32,45.20},
{31.15,45.42},{30.99,45.64},{30.85,45.85},{30.72,46.06},{30.57,46.31},{30.47,46.50},{30.34,46.75},{30.23,46.97},
{30.13,47.20},{30.03,47.45},{29.93,47.71},{29.86,47.91},{29.77,48.20},{29.71,48.43},{29.65,48.66},{29.58,48.98},
{29.53,49.23},{29.48,49.48},{29.44,49.74},{29.41,50.01},{29.37,50.29},{29.35,50.57},{29.33,50.86},{29.31,51.16},
{29.30,51.56},{29.29,51.87},{29.29,52.39},{29.30,52.72},{29.31,53.05},{29.33,53.38},{29.35,53.72},{29.37,54.06},
{29.40,54.41},{29.43,54.76},{29.47,55.12},{29.52,55.60},{29.56,55.97},{29.61,56.34},{29.66,56.72},{29.73,57.22},
{29.79,57.61},{29.84,57.99},{29.93,58.52},{29.99,58.91},{30.08,59.44},{30.15,59.84},{30.24,60.38},{30.34,60.93},
{30.42,61.34},{30.52,61.90},{30.63,62.45},{30.73,63.02},{30.85,63.58},{30.96,64.15},{31.08,64.72},{31.19,65.30},
{31.31,65.88},{31.44,66.46},{31.59,67.20},{31.72,67.79},{31.88,68.53},{32.01,69.12},{32.18,69.87},{32.25,70.17},
{32.06,69.99},{31.76,69.70},{31.45,69.42},{31.21,69.20},{30.97,68.98},{30.68,68.71},{30.44,68.50},{30.21,68.29},
{29.98,68.09},{29.75,67.89},{29.53,67.69},{29.31,67.50},{29.09,67.31},{28.88,67.12},{28.72,66.98},{28.52,66.80},
{28.31,66.63},{28.16,66.50},{27.97,66.33},{27.78,66.17},{27.64,66.05},{27.49,65.94},{27.33,65.77},{27.20,65.66},
{27.04,65.51},{26.92,65.40},{26.81,65.30},{26.66,65.16},{26.55,65.06},{26.45,64.96},{26.35,64.87},
}
--stylua: ignore end
-- Helper functionality =======================================================
-- Settings -------------------------------------------------------------------
H.setup_config = function(config)
-- General idea: if some table elements are not present in user-supplied
-- `config`, take them from default config
vim.validate({ config = { config, 'table', true } })
config = vim.tbl_deep_extend('force', vim.deepcopy(H.default_config), config or {})
if config.background == nil or config.foreground == nil then
H.error('`setup()` needs both `background` and `foreground`.')
end
vim.validate({
background = { config.background, H.is_hex },
foreground = { config.foreground, H.is_hex },
n_hues = { config.n_hues, H.is_n_hues },
saturation = { config.saturation, H.is_saturation },
accent = { config.accent, H.is_accent },
plugins = { config.plugins, 'table' },
})
return config
end
H.apply_config = function(config)
MiniHues.config = config
H.apply_colorscheme(config)
end
H.is_hex = function(x)
local res = type(x) == 'string' and x:find('^#%x%x%x%x%x%x$') ~= nil
if res then return true, nil end
return false, 'Color string in the form "#rrggbb"'
end
H.is_n_hues = function(x)
local res = type(x) == 'number' and 0 <= x and x <= 8
if res then return true, nil end
return false, 'Number between 0 and 8'
end
H.is_saturation = function(x)
local res = vim.tbl_contains(H.saturation_values, x)
if res then return true, nil end
return false, 'One of ' .. table.concat(vim.tbl_map(vim.inspect, H.saturation_values), ', ')
end
H.is_accent = function(x)
local res = vim.tbl_contains(H.accent_values, x)
if res then return true, nil end
return false, 'One of ' .. table.concat(vim.tbl_map(vim.inspect, H.accent_values), ', ')
end
-- Palette --------------------------------------------------------------------
H.make_hues = function(bg_h, fg_h, n_hues)
local res = { bg = bg_h, fg = fg_h }
if n_hues == 0 then return res end
-- Generate equidistant circular grid of hues which is the most distant from
-- background and foreground hues. Distance between two sets is assumed as
-- minimum distance between all pairs of points.
local period = 360 / n_hues
local half_period = 0.5 * period
-- - Compute delta which determines the furthest grid
local d
if bg_h == nil and fg_h == nil then d = 0 end
if bg_h ~= nil and fg_h == nil then d = (bg_h % period + half_period) % period end
if bg_h == nil and fg_h ~= nil then d = (fg_h % period + half_period) % period end
if bg_h ~= nil and fg_h ~= nil then
local ref_bg, ref_fg = bg_h % period, fg_h % period
local mid = 0.5 * (ref_bg + ref_fg)
local mid_alt = (mid + half_period) % period
d = H.dist_period(mid, ref_bg, period) < H.dist_period(mid_alt, ref_bg, period) and mid_alt or mid
end
local grid = {}
for i = 0, n_hues - 1 do
table.insert(grid, i * period + d)
end
-- Normalize equidistant grid to be base 8 colors
local dist_fun = function(x, y) return H.dist_period(x, y, 360) end
local approx = function(ref_hue) return H.get_closest(ref_hue, grid, dist_fun) end
--stylua: ignore start
res.red = approx(0)
res.orange = approx(45)
res.yellow = approx(90)
res.green = approx(135)
res.cyan = approx(180)
res.azure = approx(225)
res.blue = approx(270)
res.purple = approx(315)
--stylua: ignore end
return res
end
H.validate_hex = function(x, name)
if H.is_hex(x) then return x end
local msg = string.format('`%s` should be hex color string in the form "#rrggbb", not %s.', name, vim.inspect(x))
H.error(msg)
end
H.validate_n_hues = function(x)
if H.is_n_hues(x) then return x end
local msg = string.format('`n_hues` should be a number between 0 and 8', name)
H.error(msg)
end
H.validate_one_of = function(x, choices, name)
if vim.tbl_contains(choices, x) then return x end
local choices_string = table.concat(vim.tbl_map(vim.inspect, choices), ', ')
local msg = string.format('`%s` should be one of ', name, choices_string)
H.error(msg)
end
-- Highlighting ---------------------------------------------------------------
---@diagnostic disable
---@private
-- stylua: ignore
H.apply_colorscheme = function(config)
-- Prepare highlighting application. Notes:
-- - Clear current highlight only if other theme was loaded previously.
-- - No need to `syntax reset` because *all* syntax groups are defined later.
if vim.g.colors_name then vim.cmd('highlight clear') end
-- As this doesn't create colorscheme, don't store any name. Not doing it
-- might cause some issues with `syntax on`.
vim.g.colors_name = nil
local p = MiniHues.make_palette(config)
local hi = function(name, data) vim.api.nvim_set_hl(0, name, data) end
local has_integration = function(name)
local entry = config.plugins[name]
if entry == nil then return config.plugins.default end
return entry
end
-- NOTE: recommendations for adding new highlight groups:
-- - Put all related groups (like for new plugin) in single paragraph.
-- - Sort within group alphabetically (by hl-group name) ignoring case.
-- - Link all repeated groups within paragraph (lowers execution time).
-- - Align by commas.
-- Builtin highlighting groups
hi('ColorColumn', { fg=nil, bg=p.bg_mid2 })
hi('Conceal', { fg=p.azure, bg=nil })
hi('CurSearch', { fg=p.bg, bg=p.yellow })
hi('Cursor', { fg=p.bg, bg=p.fg })
hi('CursorColumn', { fg=nil, bg=p.bg_mid })
hi('CursorIM', { link='Cursor' })
hi('CursorLine', { fg=nil, bg=p.bg_mid })
hi('CursorLineFold', { fg=p.bg_mid2, bg=nil })
hi('CursorLineNr', { fg=p.accent, bg=nil, bold=true })
hi('CursorLineSign', { fg=p.bg_mid2, bg=nil })
hi('DiffAdd', { fg=nil, bg=p.green_bg })
hi('DiffChange', { fg=nil, bg=p.yellow_bg })
hi('DiffDelete', { fg=nil, bg=p.red_bg })
hi('DiffText', { fg=nil, bg=p.bg_mid2 })
hi('Directory', { fg=p.azure, bg=nil })
hi('EndOfBuffer', { fg=p.bg_mid2, bg=nil })
hi('ErrorMsg', { fg=p.red, bg=nil })
hi('FloatBorder', { fg=p.accent, bg=p.bg_edge })
hi('FloatTitle', { fg=p.accent, bg=p.bg_edge, bold = true })
hi('FoldColumn', { fg=p.bg_mid2, bg=nil })
hi('Folded', { fg=p.fg_mid2, bg=p.bg_mid })
hi('IncSearch', { fg=p.bg, bg=p.yellow })
hi('lCursor', { fg=p.bg, bg=p.fg })
hi('LineNr', { fg=p.bg_mid2, bg=nil })
hi('LineNrAbove', { link='LineNr' })
hi('LineNrBelow', { link='LineNr' })
hi('MatchParen', { fg=nil, bg=p.bg_mid2, bold=true })
hi('ModeMsg', { fg=p.green, bg=nil })
hi('MoreMsg', { fg=p.azure, bg=nil })
hi('MsgArea', { link='Normal' })
hi('MsgSeparator', { fg=p.fg_mid2, bg=p.bg_mid2 })
hi('NonText', { fg=p.bg_mid2, bg=nil })
hi('Normal', { fg=p.fg, bg=p.bg })
hi('NormalFloat', { fg=p.fg, bg=p.bg_edge })
hi('NormalNC', { link='Normal' })
hi('PMenu', { fg=p.fg, bg=p.bg_mid })
hi('PMenuExtra', { link='PMenu' })
hi('PMenuExtraSel', { link='PMenuSel' })
hi('PMenuKind', { link='PMenu' })
hi('PMenuKindSel', { link='PMenuSel' })
hi('PMenuSbar', { link='PMenu' })
hi('PMenuSel', { fg=p.bg, bg=p.fg, blend=0 })
hi('PMenuThumb', { fg=nil, bg=p.bg_mid2 })
hi('Question', { fg=p.azure, bg=nil })
hi('QuickFixLine', { fg=nil, bg=p.bg_mid })
hi('Search', { fg=p.bg, bg=p.accent })
hi('SignColumn', { fg=p.bg_mid2, bg=nil })
hi('SpecialKey', { fg=p.bg_mid2, bg=nil })
hi('SpellBad', { fg=nil, bg=nil, sp=p.red, undercurl=true })
hi('SpellCap', { fg=nil, bg=nil, sp=p.cyan, undercurl=true })
hi('SpellLocal', { fg=nil, bg=nil, sp=p.yellow, undercurl=true })
hi('SpellRare', { fg=nil, bg=nil, sp=p.blue, undercurl=true })
hi('StatusLine', { fg=p.fg_mid, bg=p.accent_bg })
hi('StatusLineNC', { fg=p.fg_mid, bg=p.bg_edge })
hi('Substitute', { fg=p.bg, bg=p.blue })
hi('TabLine', { fg=p.fg_mid, bg=p.bg_edge })
hi('TabLineFill', { link='Tabline' })
hi('TabLineSel', { fg=p.accent, bg=p.bg_edge })
hi('TermCursor', { fg=nil, bg=nil, reverse=true })
hi('TermCursorNC', { fg=nil, bg=nil, reverse=true })
hi('Title', { fg=p.accent, bg=nil })
hi('VertSplit', { fg=p.accent, bg=nil })
hi('Visual', { fg=nil, bg=p.bg_mid2 })
hi('VisualNOS', { fg=nil, bg=p.bg_mid })
hi('WarningMsg', { fg=p.yellow, bg=nil })
hi('Whitespace', { fg=p.bg_mid2, bg=nil })
hi('WildMenu', { link='PMenuSel' })
hi('WinBar', { link='StatusLine' })
hi('WinBarNC', { link='StatusLineNC' })
hi('WinSeparator', { fg=p.accent, bg=nil })
-- Standard syntax (affects treesitter)
hi('Boolean', { link='Constant' })
hi('Character', { link='Constant' })
hi('Comment', { fg=p.fg_mid2, bg=nil })
hi('Conditional', { link='Statement' })
hi('Constant', { fg=p.purple, bg=nil })
hi('Debug', { link='Special' })
hi('Define', { link='PreProc' })
hi('Delimiter', { fg=p.orange, bg=nil })
hi('Error', { fg=nil, bg=p.red_bg })
hi('Exception', { link='Statement' })
hi('Float', { link='Constant' })
hi('Function', { fg=p.azure, bg=nil })
hi('Identifier', { fg=p.yellow, bg=nil })
hi('Ignore', { fg=nil, bg=nil })
hi('Include', { link='PreProc' })
hi('Keyword', { link='Statement' })
hi('Label', { link='Statement' })
hi('Macro', { link='PreProc' })
hi('Number', { link='Constant' })
hi('Operator', { fg=p.fg, bg=nil })
hi('PreCondit', { link='PreProc' })
hi('PreProc', { fg=p.blue, bg=nil })
hi('Repeat', { link='Statement' })
hi('Special', { fg=p.cyan, bg=nil })
hi('SpecialChar', { link='Special' })
hi('SpecialComment', { link='Special' })
hi('Statement', { fg=p.fg, bg=nil, bold=true })
hi('StorageClass', { link='Type' })
hi('String', { fg=p.green, bg=nil })
hi('Structure', { link='Type' })
hi('Tag', { link='Special' })
hi('Todo', { fg=p.accent, bg=p.accent_bg, bold=true })
hi('Type', { fg=p.fg, bg=nil })
hi('Typedef', { link='Type' })
-- Other community standard
hi('Bold', { fg=nil, bg=nil, bold=true })
hi('Italic', { fg=nil, bg=nil, italic=true })
hi('Underlined', { fg=nil, bg=nil, underline=true })
-- Patch diff
hi('diffAdded', { fg=p.green, bg=nil })
hi('diffChanged', { fg=p.cyan, bg=nil })
hi('diffFile', { fg=p.yellow, bg=nil })
hi('diffLine', { fg=p.blue, bg=nil })
hi('diffRemoved', { fg=p.red, bg=nil })
hi('Added', { fg=p.green, bg=nil })
hi('Changed', { fg=p.cyan, bg=nil })
hi('Removed', { fg=p.red, bg=nil })
-- Git commit
hi('gitcommitBranch', { fg=p.orange, bg=nil, bold=true })
hi('gitcommitComment', { link='Comment' })
hi('gitcommitDiscarded', { link='Comment' })
hi('gitcommitDiscardedFile', { fg=p.yellow, bg=nil, bold=true })
hi('gitcommitDiscardedType', { fg=p.azure, bg=nil })
hi('gitcommitHeader', { link='Title' })
hi('gitcommitOverflow', { fg=p.yellow, bg=nil })
hi('gitcommitSelected', { link='Comment' })
hi('gitcommitSelectedFile', { fg=p.green, bg=nil, bold=true })
hi('gitcommitSelectedType', { link='gitcommitDiscardedType' })
hi('gitcommitSummary', { fg=p.green, bg=nil })
hi('gitcommitUnmergedFile', { link='gitcommitDiscardedFile' })
hi('gitcommitUnmergedType', { link='gitcommitDiscardedType' })
hi('gitcommitUntracked', { link='Comment' })
hi('gitcommitUntrackedFile', { fg=p.cyan, bg=nil })
-- Built-in diagnostic
-- Logic:
-- - Error is red.
-- - Distance from hue to error hue should increase the less important it is
-- (warning - info - ok - hint).
hi('DiagnosticError', { fg=p.red, bg=nil })
hi('DiagnosticHint', { fg=p.cyan, bg=nil })
hi('DiagnosticInfo', { fg=p.blue, bg=nil })
hi('DiagnosticOk', { fg=p.green, bg=nil })
hi('DiagnosticWarn', { fg=p.yellow, bg=nil })
hi('DiagnosticUnderlineError', { fg=nil, bg=nil, sp=p.red, underline=true })
hi('DiagnosticUnderlineHint', { fg=nil, bg=nil, sp=p.cyan, underline=true })
hi('DiagnosticUnderlineInfo', { fg=nil, bg=nil, sp=p.blue, underline=true })
hi('DiagnosticUnderlineOk', { fg=nil, bg=nil, sp=p.green, underline=true })
hi('DiagnosticUnderlineWarn', { fg=nil, bg=nil, sp=p.yellow, underline=true })
hi('DiagnosticFloatingError', { fg=p.red, bg=p.bg_edge })
hi('DiagnosticFloatingHint', { fg=p.cyan, bg=p.bg_edge })
hi('DiagnosticFloatingInfo', { fg=p.blue, bg=p.bg_edge })
hi('DiagnosticFloatingOk', { fg=p.green, bg=p.bg_edge })
hi('DiagnosticFloatingWarn', { fg=p.yellow, bg=p.bg_edge })
hi('DiagnosticVirtualTextError', { link='DiagnosticError' })
hi('DiagnosticVirtualTextWarn', { link='DiagnosticWarn' })
hi('DiagnosticVirtualTextInfo', { link='DiagnosticInfo' })
hi('DiagnosticVirtualTextHint', { link='DiagnosticHint' })
hi('DiagnosticVirtualTextOk', { link='DiagnosticOk' })
hi('DiagnosticSignError', { link='DiagnosticError' })
hi('DiagnosticSignWarn', { link='DiagnosticWarn' })
hi('DiagnosticSignInfo', { link='DiagnosticInfo' })
hi('DiagnosticSignHint', { link='DiagnosticHint' })
hi('DiagnosticSignOk', { link='DiagnosticOk' })
hi('DiagnosticDeprecated', { fg=nil, bg=nil, sp=p.red, strikethrough=true })
hi('DiagnosticUnnecessary', { link='Comment' })
-- Built-in LSP
hi('LspReferenceText', { fg=nil, bg=p.bg_mid2 })
hi('LspReferenceRead', { link='LspReferenceText' })
hi('LspReferenceWrite', { link='LspReferenceText' })
hi('LspSignatureActiveParameter', { link='LspReferenceText' })
hi('LspCodeLens', { link='Comment' })
hi('LspCodeLensSeparator', { link='Comment' })
-- Tree-sitter
if vim.fn.has('nvim-0.8') == 1 then
-- Sources:
-- - `:h treesitter-highlight-groups`
-- - https://github.com/nvim-treesitter/nvim-treesitter/blob/master/CONTRIBUTING.md#highlights
hi('@text.literal', { link='Special' })
hi('@text.reference', { link='Identifier' })
hi('@text.title', { link='Title' })
hi('@text.uri', { link='Underlined' })
hi('@text.todo', { link='Todo' })
hi('@text.note', { link='MoreMsg' })
hi('@text.warning', { link='WarningMsg' })
hi('@text.danger', { link='ErrorMsg' })
hi('@text.strong', { fg=nil, bg=nil, bold=true })
hi('@text.emphasis', { fg=nil, bg=nil, italic=true })
hi('@text.strike', { fg=nil, bg=nil, strikethrough=true })
hi('@text.underline', { link='Underlined' })
hi('@comment', { link='Comment' })
hi('@punctuation', { link='Delimiter' })
hi('@constant', { link='Constant' })
hi('@constant.builtin', { link='Special' })
hi('@constant.macro', { link='Macro' })
hi('@define', { link='Define' })
hi('@macro', { link='Macro' })
hi('@string', { link='String' })
hi('@string.escape', { link='SpecialChar' })
hi('@string.special', { link='SpecialChar' })
hi('@character', { link='Character' })
hi('@character.special', { link='SpecialChar' })
hi('@number', { link='Number' })
hi('@boolean', { link='Boolean' })
hi('@float', { link='Float' })
hi('@function', { link='Function' })
hi('@function.builtin', { link='Special' })
hi('@function.call', { link='Function' })
hi('@function.macro', { link='Macro' })
hi('@parameter', { fg=p.blue, bg=nil })
hi('@method', { link='Function' })
hi('@method.call', { link='Function' })
hi('@field', { link='Identifier' })
hi('@property', { link='Identifier' })
hi('@constructor', { link='Special' })
hi('@conditional', { link='Conditional' })
hi('@repeat', { link='Repeat' })
hi('@label', { link='Label' })
hi('@operator', { link='Operator' })
hi('@keyword', { link='Keyword' })
hi('@keyword.return', { fg=p.orange, bg=nil, bold=true })
hi('@exception', { link='Exception' })
hi('@variable', { fg=p.fg, bg=nil })
hi('@variable.builtin', { link='Special' })
hi('@type', { link='Type' })
hi('@type.builtin', { link='Special' })
hi('@type.definition', { link='Typedef' })
hi('@storageclass', { link='StorageClass' })
hi('@structure', { link='Structure' })
hi('@namespace', { link='Identifier' })
hi('@include', { link='Include' })
hi('@preproc', { link='PreProc' })
hi('@debug', { link='Debug' })
hi('@tag', { link='Tag' })
hi('@symbol', { link='Keyword' })
hi('@none', { link='Normal'})
end
-- Semantic tokens
if vim.fn.has('nvim-0.9') == 1 then
-- Source: `:h lsp-semantic-highlight`
hi('@lsp.type.class', { link='@structure' })
hi('@lsp.type.decorator', { link='@function' })
hi('@lsp.type.enum', { link='@type' })
hi('@lsp.type.enumMember', { link='@constant' })
hi('@lsp.type.function', { link='@function' })
hi('@lsp.type.interface', { link='@type' })
hi('@lsp.type.macro', { link='@macro' })
hi('@lsp.type.method', { link='@method' })
hi('@lsp.type.namespace', { link='@namespace' })
hi('@lsp.type.parameter', { link='@parameter' })
hi('@lsp.type.property', { link='@property' })
hi('@lsp.type.struct', { link='@structure' })
hi('@lsp.type.type', { link='@type' })
hi('@lsp.type.typeParameter', { link='@type.definition' })
hi('@lsp.type.variable', { link='@variable' })
hi('@lsp.mod.defaultLibrary', { link='Special' })
hi('@lsp.mod.deprecated', { fg=p.red, bg=nil })
end
-- New tree-sitter groups
if vim.fn.has('nvim-0.9') == 1 then
-- Sources:
-- - `:h treesitter-highlight-groups`
-- - https://github.com/nvim-treesitter/nvim-treesitter/commit/1ae9b0e4558fe7868f8cda2db65239cfb14836d0
-- NOTE: commented groups are the same as in Neovim<0.10 defined earlier
-- @variable
-- @variable.builtin
hi('@variable.parameter', { link='@parameter' })
hi('@variable.member', { link='@field' })
-- @constant
-- @constant.builtin
-- @constant.macro
hi('@module', { link='@namespace' })
hi('@module.builtin', { link='@variable.builtin' })
-- @label
-- @string
hi('@string.documentation', { link='@string' })
hi('@string.regexp', { link='SpecialChar' })
-- @string.escape
-- @string.special
hi('@string.special.symbol', { link='@constant' })
hi('@string.special.path', { link='Directory' })
hi('@string.special.url', { link='@markup.link.url' })
-- @character
-- @character.special
-- @boolean
-- @number
hi('@number.float', { link='@float' })
-- @type
-- @type.builtin
-- @type.definition
hi('@type.qualifier', { link='StorageClass' })
hi('@attribute', { link='Macro' })
-- @property
-- @function
-- @function.builtin
-- @function.call
-- @function.macro
hi('@function.method', { link='@method' })
hi('@function.method.call', { link='@method.call' })
-- @constructor
-- @operator
-- @keyword
hi('@keyword.coroutine', { link='@keyword' })
hi('@keyword.function', { link='@keyword' })
hi('@keyword.operator', { link='@keyword' })
hi('@keyword.import', { fg=p.blue, bg=nil, bold=true })
hi('@keyword.storage', { fg=p.fg, bg=nil, bold=true })
hi('@keyword.repeat', { link='@keyword' })
-- @keyword.return
hi('@keyword.debug', { fg=p.cyan, bg=nil, bold=true })
hi('@keyword.exception', { link='@keyword' })
hi('@keyword.conditional', { link='@keyword' })
hi('@keyword.conditional.ternary', { link='keyword' })
0