aboutsummaryrefslogtreecommitdiffstats
path: root/plugin/40-colors.lua
blob: 32ae452e179e77e4acc7a80542cb79cec2a0f5bf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.W
--
-- Color scheme, custom highlights & per-window highlight namespaces
--

local solarized_colors = {
	base03 = "#002b36",
	base02 = "#073642",
	base01 = "#586e75",
	base00 = "#657b83",
	base0 = "#839496",
	base1 = "#93a1a1",
	base2 = "#eee8d5",
	base3 = "#fdf6e3",
	yellow = "#b58900",
	orange = "#cb4b16",
	red = "#dc322f",
	magenta = "#d33682",
	violet = "#6c71c4",
	blue = "#268bd2",
	cyan = "#2aa198",
	green = "#859900",
	-- solarized.nvim "mix" tints: subtle highlight backgrounds, theme-specific
	mix_base1 = "#2c4e56", -- dark  (active cursor line)
	mix_blue_light = "#e7ebe1", -- light (active cursor line)
	mix_green = "#274c25", -- dark  (inactive cursor line: muted green)
	mix_magenta_light = "#f8e2d9", -- light (inactive cursor line: muted pink)
}

-- Per-window highlight namespaces: one for the focused window, one for the
-- unfocused windows. A window's active namespace decides its CursorLine color,
-- which is how the focused window is made obvious. Both namespaces must define
-- `dotfiles.Todo`: an extmark's hl_group is resolved against the window's
-- active namespace and does NOT fall back to the global namespace, so the TODO
-- highlight (placed in 50-extmarks.lua) only renders if the group exists in
-- whichever namespace the window currently uses.
local ns_active = vim.api.nvim_create_namespace("extmarks")
local ns_inactive = vim.api.nvim_create_namespace("extmarks.inactive")
local todo_hl = { bg = "Yellow", fg = "Black", bold = true }
vim.api.nvim_set_hl(ns_active, "dotfiles.Todo", todo_hl)
vim.api.nvim_set_hl(ns_inactive, "dotfiles.Todo", todo_hl)

-- Publish the extmark storage namespace and TODO group name for 50-extmarks.lua.
-- This file is named 40- (not 50-) so it is sourced before 50-extmarks.lua and
-- the contract below exists when that file reads it. Preserve the existing
-- `dotfiles` table (augroup).
local dotfiles = vim.g.dotfiles
dotfiles.todo_ns = ns_active
dotfiles.todo_hl = "dotfiles.Todo"
vim.g.dotfiles = dotfiles

-- Custom highlights in the global namespace, independent of the per-window ones
local function adjust_highlight()
	if vim.g.colors_name == "solarized" then
		vim.api.nvim_set_hl(0, "EndOfBuffer", { fg = solarized_colors.base0, update = true })
		vim.api.nvim_set_hl(0, "MatchParen", { link = "CurSearch" })
		vim.api.nvim_set_hl(0, "WinBarCwd", { fg = solarized_colors.blue, italic = true })
		vim.api.nvim_set_hl(0, "WinBarFile", { italic = true })
		vim.api.nvim_set_hl(0, "TabLine", { fg = solarized_colors.yellow, bold = true })
		if vim.o.background == "light" then
			vim.api.nvim_set_hl(0, "TabLineSel", { bg = "#edffd5", fg = solarized_colors.yellow, bold = true })
		elseif vim.o.background == "dark" then
			vim.api.nvim_set_hl(0, "TabLineSel", { bg = "#043624" })
		end
	end
end

-- Active and inactive cursorline highlight
local function set_cursorline_hl()
	local accent = vim.o.background == "light" and solarized_colors.mix_blue_light or solarized_colors.mix_base1
	vim.api.nvim_set_hl(ns_active, "CursorLine", { bg = accent })
	vim.api.nvim_set_hl(ns_active, "CursorLineNr", { bg = accent })
	vim.api.nvim_set_hl(ns_inactive, "CursorLine", { bg = accent, underline = true })
	vim.api.nvim_set_hl(ns_inactive, "CursorLineNr", { bg = accent, underline = true })
end

local function apply_highlights()
	adjust_highlight()
	set_cursorline_hl()
end

-- Single owner of every window's highlight namespace: the focused window gets
-- `ns_active`, all others `ns_inactive`. Reapplied to every window on each
-- focus change because WinLeave alone misses windows that existed at startup
-- or were restored from a session.
local function is_normal_win(win)
	return vim.api.nvim_win_is_valid(win) and vim.api.nvim_win_get_config(win).relative == ""
end

local function update_focus_ns()
	local cur = vim.api.nvim_get_current_win()
	-- Focus in a floating window (completion, hover, ...) must not recolor the
	-- split underneath; leave every window as it is.
	if not is_normal_win(cur) then
		return
	end
	for _, win in ipairs(vim.api.nvim_list_wins()) do
		if is_normal_win(win) then
			vim.api.nvim_win_set_hl_ns(win, (win == cur) and ns_active or ns_inactive)
		end
	end
end

-- Note: a closed fold's screen line is always drawn with `Folded` (see
-- `:help fold.txt` / hl-Folded); Neovim has no per-line override, so the
-- unfocused cursor line is not visible when it sits on a closed fold. This is
-- standard behavior (cursorline never renders on closed folds in any window)
-- and is accepted as-is.

local function next_colorscheme()
	local colorschemes = vim.fn.getcompletion("", "color")
	vim.g.colors_name = vim.g.colors_name or "default"
	for i = 1, #colorschemes do
		if colorschemes[i] == vim.g.colors_name then
			vim.cmd.colorscheme(colorschemes[(i % #colorschemes) + 1])
			vim.notify("Color scheme set to " .. vim.g.colors_name)
			return
		end
	end
end

local function random_colorscheme()
	local colorschemes = vim.fn.getcompletion("", "color")
	vim.cmd.colorscheme(colorschemes[math.random(#colorschemes)])
	vim.notify("Color scheme set to " .. vim.g.colors_name)
end

local function toggle_theme()
	vim.opt.background = vim.o.background == "light" and "dark" or "light"
	vim.notify("Color theme set to " .. vim.o.background)
end

-- Reapply custom highlights after the colorscheme loads and on theme change
vim.api.nvim_create_autocmd("ColorScheme", {
	desc = "Reapply custom highlights",
	group = vim.g.dotfiles.augroup,
	callback = apply_highlights,
})
vim.api.nvim_create_autocmd("OptionSet", {
	desc = "Reapply custom highlights on theme change",
	pattern = "background",
	group = vim.g.dotfiles.augroup,
	callback = apply_highlights,
})

-- Keep the focused/unfocused cursor line correct as windows and focus change
vim.api.nvim_create_autocmd(
	{ "VimEnter", "WinEnter", "WinNew", "WinClosed", "TabEnter", "SessionLoadPost", "FileType" },
	{
		desc = "Track the focused window for the cursor line",
		group = vim.g.dotfiles.augroup,
		callback = function()
			-- Defer so the window list and current window have settled.
			vim.schedule(update_focus_ns)
		end,
	}
)

vim.cmd.colorscheme("solarized") -- Default colorscheme
vim.opt.background = "dark" -- Default theme
apply_highlights()

vim.api.nvim_create_user_command("ColorsNext", next_colorscheme, { desc = "Load next color scheme" })
vim.api.nvim_create_user_command("ColorsRandom", random_colorscheme, { desc = "Load random color scheme" })
vim.api.nvim_create_user_command("ColorsThemeToggle", toggle_theme, { desc = "Toggle light/dark theme" })