Welcome to the advanced usage guide for nvim-highlite! This will teach you how to create your own colorscheme using this plugin, whether it be from scratch, one of our built-in colorschemes, or another existing colorscheme.
At last, you are ready to create a colorscheme! You should follow these sections in order to ensure your success.
Colorschemes must be defined in a very specific location in order to be visible to Neovim's :colorscheme command. In order for :colorscheme foo to work, there must be a colors/foo.vim or colors/foo.lua file located within your &runtimepath.
Note
To check your runtime path, do
:lua for _, p in ipairs(vim.opt.rtp:get()) do print(p) end.
If you are making this colorscheme for yourself, that file should be in your configuration directory. Run this snippet to show where that is:
:lua = vim.fn.stdpath('config') .. '/colors/'If you are making this colorscheme for others to be able to install— first, consider contributing! I'd love to have more colorschemes built-in with this repository.
If you'd rather have the colorscheme live separately from this repository, create a repository and git clone https://github.com/YOUR_USERNAME/YOUR_REPO YOUR_PROGRAMMING_DIR/YOUR_REPO. You can then use a plugin manager to load this directory— here is an example for lazy.nvim:
require('lazy').setup(
{
{'YOUR_USERNAME/YOUR_REPO',
config = function() vim.api.nvim_command 'colorscheme YOUR_COLORSCHEME' end,
dependencies = 'Iron-E/nvim-highlite',
priority = 1000,
},
},
{dev = {fallback = true, path = 'YOUR_PROGRAMMING_DIR', patterns = {'YOUR_USERNAME'}}}
)Make sure to replace YOUR_COLORSCHEME, YOUR_PROGRAMMING_DIR, YOUR_REPO, YOUR_USERNAME with what those values would actually be.
Now that your file has been created, you can start developing your colorscheme. The first thing to do is decide on your palette: will base it off of one that is built-in, or create a new one?
- If you like the look of one of the built-in colorschemes, you should use one of the built-in palettes.
- If you want to make a new colorscheme, or port another colorscheme over to
highlite, you should derive it.
There are currently 80+ fields to a palette, and that number will probably only grow over time. To make the process of creating a palette for nvim-highlite easier, there is Palette.derive. Using this function, only 6(!!) colors need to be defined:
local Palette = require 'highlite.color.palette' --- @type highlite.color.Palette
local colors = Palette.derive('dark', {
bg = '#202020', -- colors can be a string
error = 0xAA0000, -- or an integer
ok = '#00aa00',
text = '#cccccc',
statement = '#33ccFF',
storage = '#cc7700',
})Of course… that means your colorscheme only uses six colors. Even though only those six are required, it is recommended to define more. With more information, derive becomes more accurate. Some groups that are recommended are:
funchint(&info, if you want them be distinct fromhints)uriwarning
Here is another example showing all of the above, which includes detection of light/dark backgrounds:
local Palette = require 'highlite.color.palette' --- @type highlite.color.Palette
local bg = vim.api.nvim_get_option 'background'
local colors = Palette.derive(bg, bg == 'dark' and {
bg = '#202020',
error = '#aa0000',
func = '#cc00aa',
hint = '#cc0055',
info = '#ccbb88',
ok = '#33bb55',
statement = '#33ccFF',
storage = '#cc7700',
text = '#cccccc',
uri = '#00aa00',
warning = '#ffbb00',
} or {
-- light palette goes here
})To define a terminal palette, all you have to do is define a list with colors in the following order:
- black
- dark red
- dark green
- dark yellow / orange
- blue
- dark magenta
- dark cyan
- gray / "dark white"
- dark gray / "light black"
- red
- green
- yellow
- light blue
- magenta
- cyan
- white
Warning
While other colors may either be a string (e.g.
'#FFFFFF') or an integer (e.g.0xFFFFFF), but the colors in the terminal palette must be strings.
For example:
local terminal_palette = {
'#101017', '#aa0000', '#33bb55', '#cc7700', '#133099', '#990066', '#33ccFF', '#cccccc',
'#808080', '#FF2222', '#00aa00', '#7f6f20', '#1259ff', '#cc00aa', '#3388aa', '#ffffff',
}local Highlite = require 'highlite' --- @type Highlite
local palette, terminal_palette = Highlite.palette 'highlite' -- or any of the built-in palettes, e.g. 'ayu'The terminal_palette will automatically be nil when you turn off terminal highlighting in generate.
| Name | Description |
|---|---|
ayu |
Based on ayu from ayu-theme/ayu-vim |
everforest |
Based on everforest from sainnhe/everforest |
gruvbox-material |
Based on gruvbox-material from sainnhe/gruvbox-material |
gruvbox |
Based on gruvbox from morhetz/gruvbox |
highlite |
The original nvim-highlite palette |
iceberg |
Based on iceberg from cocopon/iceberg.vim |
molokai |
Based on molokai from tomasr/molokai |
papercolor |
Based on papercolor from nlknguyen/papercolor-theme |
seoul256-light |
Based on seoul256-light from junegunn/seoul256.vim |
seoul256 |
Based on seoul256 from junegunn/seoul256.vim |
solarized8-flat |
Based on solarized8-flat from lifepillar/vim-solarized8 |
solarized8-high |
Based on solarized8-high from lifepillar/vim-solarized8 |
solarized8-low |
Based on solarized8-low from lifepillar/vim-solarized8 |
solarized8 |
Based on solarized8 from lifepillar/vim-solarized8 |
sonokai |
Based on sonokai from sainnhe/sonokai |
Remember: you do not need to define all of these.
| Field | Default | Definition |
|---|---|---|
annotation |
attribute |
|
attribute_builtin |
special |
builtin attributes |
attribute |
preproc |
|
bg_contrast_high |
bg |
an accent to the bg color which contrasts |
bg_contrast_low |
bg |
an accent to the bg color which barely contrasts |
bg |
the background color | |
boolean |
constant |
true and false |
buffer_active |
text_contrast_bg_high |
the underline of an active buffer |
buffer_alternate |
search |
the underline of the alternate buffer |
buffer_current |
select |
the underline of the current buffer |
character_special |
special |
special characters e.g. wildcards |
character |
string |
character literals (e.g. 'a') |
class |
structure |
data with behavior |
comment_documentation |
comment |
documentation comments |
comment_special |
special tokens in a comment | |
comment |
text_contrast_bg_low |
normal comments |
conditional |
keyword |
e.g. if, match, switch |
constant_builtin |
special |
built-in constant values |
constant |
storage |
constant values |
constructor |
type |
invoked by new |
decorator |
attribute |
|
define |
preproc |
e.g. #define in C |
diff_add |
ok |
added text |
diff_change |
number |
changed text, also applies to modified buffers in the tabline |
diff_delete |
error |
removed text |
diff_syntax_add |
ok |
added text within a (universal) diff |
diff_syntax_change |
number |
changed text within a (universal) diff |
diff_syntax_delete |
error |
removed text within a (universal) diff |
enum |
structure |
an enumeration |
error |
diagnostic errors | |
event |
loop |
events e.g. public event FooEventHandler FooEvent in C# |
field_enum |
constant |
a variant of an enum e.g. Ok in Rust |
field |
property |
a member of a class/struct which is accessed directly |
float |
number |
floating point numbers |
fold |
func |
the background of &foldtext |
func_builtin |
special |
builtin function |
func |
statement |
functions, e.g. print('a') |
heading_1 |
error |
level 1 heading |
heading_2 |
warning |
level 2 heading |
heading_3 |
diff_change |
level 3 heading |
heading_4 |
string |
level 4 heading |
heading_5 |
type |
level 5 heading |
heading_6 |
fold |
level 6 heading |
hint |
info |
diagnostic hints |
identifier |
text |
misc. identifier |
include |
preproc |
e.g. #include in C |
info |
warning |
diagnostic info |
interface_builtin |
type_builtin |
e.g. interface Foo |
interface |
type |
e.g. interface Foo |
keyword_coroutine |
keyword |
e.g. await |
keyword_function |
type_builtin |
e.g. fn in Rust, def in Python |
keyword_modifier |
keyword |
modifiers for items, e.g. public |
keyword_operator |
operator |
e.g. and in Lua |
keyword_return |
keyword |
e.g. return, yield |
keyword_type |
keyword |
e.g. "struct" |
keyword |
statement |
keywords, e.g. local in Lua |
label |
special |
switch cases, loop labels (e.g. 'outer for x in y { break 'outer } in Rust) |
loop |
conditional |
e.g. loop, for, while |
macro |
define |
e.g. println! in Rust |
markup_link_label |
string_special |
markup link/reference descriptions |
markup_list_checked |
string_special |
markup todo-style markers (checked) |
markup_list_unchecked |
string_special |
markup todo-style markers (unchecked) |
markup_list |
string_special |
markup list markers |
markup_quote |
comment |
|
message |
info |
Builtin Vim messages |
method |
func |
function of a class |
namespace_builtin |
namespace |
namespace, but builtin |
namespace |
uri |
e.g. foo::bar in Rust/C++ |
number |
constant |
number literals (e.g 9) |
ok |
positive reinforcement from LSP or Git | |
operator |
keyword |
e.g. +, -, && |
parameter_builtin |
special |
a builtin function parameter |
parameter |
identifier |
a function parameter |
preproc_conditional |
preproc |
pre-processor conditionals (e.g. #[cfg] in Rust) |
preproc |
func |
pre-processor commands (e.g. #[derive] in Rust) |
property |
identifier |
a member of a class or structure which is transparently a function |
punctuation_bracket |
punctuation |
balanced punctuation, e.g. [], "", () |
punctuation_delimiter |
punctuation |
single punctuation, e.g. ,, ; |
punctuation_special |
string_special |
special punctuation, e.g. {} in a format string |
punctuation |
text_contrast_bg_high |
misc. delimiters |
search |
select |
Doing :/ or :s/ |
select |
bg_contrast_low |
selected text, e.g. in visual mode or selecting LSP completions |
special |
statement |
unclassified special symbols in a language |
statement |
programming language statements which do not fall into another category | |
storage |
pointer symbols (e.g. &foo) |
|
string_documentation |
comment_documentation |
strings documenting code |
string_escape |
character |
escaped characters in a string |
string_regex |
string |
regular expressions |
string_special_symbol |
string_special |
symbols or atoms |
string_special |
identifier |
special strings (e.g. dates) |
string |
constant |
a misc. string |
structure |
type |
plain-old data |
syntax_error |
error |
non-diagnostic errors; sometimes shown in the actual highlighting of the code you write |
tag_attribute |
field |
XML/HTML tag attributes |
tag_delimiter |
punctuation_bracket |
</> in XML/HTML tags |
tag |
structure |
XML/HTML tags |
text_contrast_bg_high |
text |
an accent to the text color which contrasts |
text_contrast_bg_low |
text |
an accent to the text color which barely contrasts |
text_environment_name |
label |
text environment name |
text_environment |
text_contrast_bg_low |
text environment delimiter |
text_literal |
statement |
literal text |
text_math |
number |
mathematical text |
text_reference |
uri |
references to variables in text |
text |
plaintext | |
throw |
error |
a keyword which triggers or receives errors (e.g. throw MyError) |
todo |
message |
TODO comments |
type_builtin |
type |
a type from the standard library |
type_definition |
type |
typedefs |
type_parameter |
type |
generic type |
type |
keyword |
type names e.g. int, String |
uri |
text |
links (HTTP, tags, match paren, etc). |
variable_builtin |
keyword |
a variable from the language, e.g. self |
variable |
identifier |
a variable |
warning |
error |
diagnostic warnings |
You may add extra colors to the palette, if what is available is not fine-grained enough. You can add as many colors as you like. Example:
local Highlite = require 'highlite' --- @type Highlite
local palette, terminal_palette = Highlite.palette 'highlite'
-- also works with derive
palette.my_custom_color = '#FF0000'
local groups = Highlite.groups('default', palette)
groups.Error = {fg = palette.my_custom_color}
-- … other colorscheme logicNote
This section is optional. Both
Palette.deriveand the built-in palettes handle colors automatically. However, if you wish to have finer control, you may use these functions.
function Color.saturate(color: integer|string, factor: integer) -> integerSaturates the given color multiplicatively using factor. Example:
local Color = require 'highlite.color' --- @type highlite.Color
assert(Color.saturate('#202020', 2) == 0x404040)
assert(Color.saturate(0x202020, 0.5) == 0x101010)To generate groups, you can do:
local Highlite = require 'highlite' --- @type Highlite
local palette = … -- derive or get a built-in palette
local groups = Highlite.groups('default', palette) -- or any of the built-in groups| Name | Description | Parent Group |
|---|---|---|
default |
The default groups: all Neovim built-ins and some Lua plugins. | |
highlite |
Overrides for the highlite pallete. |
default |
seoul256-light |
Overrides for the seoul256-light pallete. |
seoul256 |
seoul256 |
Overrides for the seoul256 pallete. |
default |
nvim-highlite groups follow the format of nvim_set_hl's {val} parameter. Example:
local colors = require('highlite.color.palette').derive(…) -- see "Deriving a Palette" for more info
local normal = {fg = colors.text, bg = colors.bg}nvim-highlite also accepts a shorthand :highlight link syntax:
local float = 'Number' -- same as `{link = 'Number'}`nvim-highlite provides a convenient way to resolve highlight links within the defined colorscheme:
--- @param group string
--- @return nil|tableExample:
local palette = … -- see instructions for generating a palette
local groups = … -- see above instructions for generating groups
-- create a new highlight group `Foo`
groups.Foo = {fg = palette.text}
-- link `Bar` to `Foo`
groups.Bar = 'Foo'
-- `groups.XYZ` will retrieve `XYZ`'s raw value.
assert(vim.deep_equal(groups.Foo, {fg = palette.text}))
assert(vim.deep_equal(groups.Bar, 'Foo'))
-- `groups'XYZ'` will retrieve `XYZ`'s unlinked value
assert(vim.deep_equal(groups'Foo', {fg = palette.text}))
assert(vim.deep_equal(groups'Bar', {fg = palette.text}))
--[[ You can use this to choose whether to reference a link or a definition. ]]
-- link 'Baz' to 'Foo', since 'Bar' is linked to 'Foo' too
groups.Baz = groups.Bar
-- create a new highlight group `Xyz` using `Bar`'s unlinked `fg` attribute
groups.Xyz = {fg = groups'Bar'.fg}
-- you can chain resolves, in case one group isn't available
groups.Zyx = {fg = (groups"Doesn't Exist" or groups'Might Exist' or {}).fg}- For LSP and treesitter groups: if
@foo.baris not defined explicitly, the un-linker will not attempt to lookup@foo.- As a workaround, you can do
groups'@foo.bar' or groups'@foo' or {}to check for the existence of fallback highlights manually.
- As a workaround, you can do
Note
This section is optional. Feel free to use
vim.deepcopy,vim.tbl_extend, etc. and come back later if you feel like you need more performance.
Working with raw highlight groups can be slow using built-in methods.
For example, let's say we want to highlight Lua's [[/]] delimiters differently than normal delimiters. How would you do that normally?
local delimiter = {fg = '#FFFFFF'}
local lua_delimiter = vim.tbl_extend('keep', {italic = true}, delimiter)Using the Bench function in my config, you can determine that this is ⪆4× faster:
local delimiter = {fg = '#FFFFFF'}
local lua_delimiter = Groups.extend({italic = true}, delimiter)All of the functions in Groups are specialized for working with highlight groups; they can be minimal in ways which built-in functions should not be. You should prefer to use these functions, where they apply.
Warning
None of the following functions work with the shorthand link syntax:
local Groups = require 'highlite.groups' --- @type highlight.Groups local DiagnosticError = 'Error' local cloned = Groups.clone(DiagnosticError) -- invalid!There is a way to resolve highlight links into a valid group definition— see Resolving Highlight Links.
function Groups.clone(group: table<string, any>) -> table<string, any>Return a new highlight group definition with the same attributes of some group.
Perf
Faster than
vim.deepcopyfor the case of copying highlight groups.
Example:
local Groups = require 'highlite.groups' --- @type highlite.Groups
local delimiter = {fg = '#FFFFFF'}
local err = {fg = '#c0c0c0', bg = '#aa0000'}
local err_delimiter = Groups.extend(Groups.clone(delimiter), err)
assert(not vim.deep_equal(delimiter, delimiter_clone))function Groups.extend(child: table<string, any>, parent: table<string, any>)
-> table<string, any>Assign all attributes of parent to child which are not already defined.
Perf
Faster than
vim.tbl_extendfor the case of extending highlight groups.
Warning
This mutates
child! UseGroups.clone()first if this is a problem.
Example:
local Groups = require 'highlite.groups' --- @type highlite.Groups
local char = {fg = '#FF0000'}
local special_char = Groups.extend({italic = true}, char)function Groups.extend_selected(
child: table<string, any>,
parent: table<string, any>,
opts: table<string, boolean>
) -> table<string, any>Like Groups.extend, but allows forcing and skipping attributes. Example:
local Groups = require 'highlite.groups' --- @type highlite.Groups
local white_bold = {fg = '#FFFFFF', bold = true}
local green_red_italic = {fg = '#00FF00', bg = '#FF0000', bold = false, italic = true}
local white_red_italic = Groups.extend_selected(green_red_italic, white_bold_text, {fg = true})
local green_red_bold_italic = Groups.extend_selected(green_red_italic, white_bold_text, {bold = true, fg = false})Perf
Faster than
vim.tbl_extending the output ofvim.tbl_filterfor the case of extending highlight groups.
Warning
This mutates
child! UseGroups.clone()first if this is a problem.
Finally, now that you have your palette and your groups, you can generate your colorscheme:
require('highlite').generate(
'<name>', -- replace `<name>` with the name of the file you created in step 1
groups,
terminal_palette -- OPTIONAL: set the terminal colors
)local Groups = require 'highlite.groups' --- @type highlite.Groups
local Highlite = require 'highlite' --- @type Highlite
local Palette = require 'highlite.color.palette' --- @type highlite.color.Palette
--[[ Generate Palette
NOTE: this section shows using `derive`, but you can also use one of the builtin palettes.
See "Generating a Palette" for more information.
]]
local bg = vim.api.nvim_get_option 'background' -- get the background
local palette = Palette.derive(bg, bg == 'dark' and {
bg = '#202020',
error = '#aa0000',
func = '#cc00aa',
hint = '#cc0055',
info = '#ccbb88',
ok = '#33bb55',
statement = '#33ccFF',
storage = '#cc7700',
text = '#cccccc',
uri = '#00aa00',
warning = '#ffbb00',
} or {
-- light palette goes here
})
--- NOTE: this part is optional!
--- @type highlite.color.palette.terminal
local terminal_palette = {
'#101017', '#aa0000', '#33bb55', '#cc7700', '#133099', '#990066', '#33ccFF', '#cccccc',
'#808080', '#FF2222', '#00aa00', '#7f6f20', '#1259ff', '#cc00aa', '#3388aa', '#ffffff',
}
--[[ Generate Groups ]]
local groups = Highlite.groups('default', palette)
--[[ Override Groups ]]
-- Get the bracket group, and extend it to add italics
groups.luaStringLongTag = Groups.extend({italic = true}, groups '@punctuation.bracket')
-- Use `msgsep` in `fillchars`? Override `MsgSeparator` to only highlight the foreground
groups.MsgSeparator = {fg = colors.text_contrast_bg_high}
--[[ Generate ]]
Highlite.generate('highlite-example', groups, terminal_palette)Note
If you are trying to port a colorscheme as a contribution to
nvim-highlite, there is an even easier way. See Porting Colorschemes.
Note
This feature requires requires Neovim 0.9+.
nvim-highlite provides an import module to allow importing colorschemes from various formats to one that it recognizes.
| Data Type | &bg |
Field |
|---|---|---|
| Color Palette | Dark | dark.palette |
| Color Palette | Light | light.palette |
| Terminal Palette | Dark | dark.terminal |
| Terminal Palette | Light | light.terminal |
For example, if you do:
local Import = require 'highlite.import' --- @type highlite.Import
local imported = Import.nvim 'zellner'Then imported.dark.palette is the color palette for a dark &background.
Warning
Importing is slow! You should not import another colorscheme in your colorscheme file. For example, this colorscheme will load slowly:
local Import = require 'highlite.import' --- @type highlite.Import local Highlite = require 'highlite' --- @type Highlite local bg = vim.api.nvim_get_option 'background' local imported = Import.nvim 'zellner' Highlite.generate( 'example', Highlite.groups('default', imported[bg].palette), imported[bg].terminal )Instead, you should use something like
MiniMisc.put_textorvim.inspect&nvim_buf_set_textto insert the import into the colorscheme file directly.
Supports any colorscheme that can be accessed via :colorscheme. Doesn't matter if it was written in Lua, Python, Rust, VimScript, etc.
local Import = require 'highlite.import' --- @type highlite.Import
local imported = Import.nvim 'zellner'Neovim 0.9+ comes with the built-in :Inspect & :InspectTree commands. They let you analyze the highlights that are being applied to the character under your cursor.
echasnovski/mini.colors is a fantastic plugin (as is the rest of mini.nvim) that allows you to easily manipulate colors, fiddle with existing colorschemes, and generate light/dark variants of a specific palette.
If you have used rktjmp/lush.nvim and miss its suite of utilities to work with colors / preview your work in this plugin, mini.colors may be able to help bridge the gap!