diff options
| author | Thomas Vanbesien <tvanbesi@proton.me> | 2026-05-19 12:00:21 +0200 |
|---|---|---|
| committer | Thomas Vanbesien <tvanbesi@proton.me> | 2026-05-19 15:06:47 +0200 |
| commit | 53563e59b9d20922c0f37c99cf20e2be8f703a74 (patch) | |
| tree | 4b8a5d0bbdddae9a15e522695fb35b755b8a653a /.config/nvim/plugin | |
| parent | f1a519f6c5d78238b42be77701c9ff98d9f5b805 (diff) | |
| download | dotfiles-53563e59b9d20922c0f37c99cf20e2be8f703a74.tar.gz dotfiles-53563e59b9d20922c0f37c99cf20e2be8f703a74.zip | |
feat(nvim): focus-aware cursor line via per-window highlight namespaces
Split 50-highlight.lua into 40-colors.lua (colorscheme, custom
highlights, per-window namespaces, focus tracking) and 50-extmarks.lua
(TODO scanner reading the namespace contract from vim.g.dotfiles). The
40- prefix guarantees colors load before extmarks.
Focused vs unfocused windows now differ by cursor-line color and a
CursorLineNr badge in the focus hue (green on dark, magenta on light);
the number column stays visible on closed folds where CursorLine cannot.
Diffstat (limited to '.config/nvim/plugin')
| -rw-r--r-- | .config/nvim/plugin/40-colors.lua | 179 | ||||
| -rw-r--r-- | .config/nvim/plugin/50-colors.lua | 85 | ||||
| -rw-r--r-- | .config/nvim/plugin/50-extmarks.lua (renamed from .config/nvim/plugin/50-highlight.lua) | 17 |
3 files changed, 190 insertions, 91 deletions
diff --git a/.config/nvim/plugin/40-colors.lua b/.config/nvim/plugin/40-colors.lua new file mode 100644 index 0000000..21c93e7 --- /dev/null +++ b/.config/nvim/plugin/40-colors.lua @@ -0,0 +1,179 @@ +-- +-- Color scheme, custom highlights & per-window highlight namespaces +-- +vim.pack.add({ "https://github.com/maxmx03/solarized.nvim" }) -- Solarized color scheme + +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" }) + if vim.o.background == "light" then + vim.api.nvim_set_hl(0, "TabLineSel", { bg = "#edffd5" }) + vim.api.nvim_set_hl(0, "Cursorline", { bg = "#edffd5" }) + vim.api.nvim_set_hl(0, "NormalFloat", { bg = solarized_colors.base2 }) + elseif vim.o.background == "dark" then + vim.api.nvim_set_hl(0, "TabLineSel", { bg = "#043624" }) + vim.api.nvim_set_hl(0, "Cursorline", { bg = "#043624" }) + vim.api.nvim_set_hl(0, "NormalFloat", { bg = solarized_colors.base02 }) + end + end +end + +-- Theme-aware cursor line, one focus hue (green on dark, magenta/pink on light) +-- at two intensities. CursorLineNr stays visible on closed folds (the number +-- column is NOT painted by Folded), so it carries the focus cue: active window +-- a muted "mix" of the hue, unfocused windows a bright bold badge of the same +-- hue. The cursor-line background backs this up: active a subtle neutral "mix" +-- tint (light enough to edit on, distinct from Folded), unfocused the muted +-- hue wash. +local function set_cursorline_hl() + local active, muted, badge + if vim.o.background == "light" then + active, muted = solarized_colors.mix_blue_light, solarized_colors.mix_magenta_light + badge = { bg = solarized_colors.magenta, fg = solarized_colors.base3, bold = true } + else + active, muted = solarized_colors.mix_base1, solarized_colors.mix_green + badge = { bg = solarized_colors.green, fg = solarized_colors.base03, bold = true } + end + vim.api.nvim_set_hl(ns_active, "CursorLine", { bg = active }) + vim.api.nvim_set_hl(ns_active, "CursorLineNr", { bg = muted }) + vim.api.nvim_set_hl(ns_inactive, "CursorLine", { bg = muted }) + vim.api.nvim_set_hl(ns_inactive, "CursorLineNr", badge) +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" }) diff --git a/.config/nvim/plugin/50-colors.lua b/.config/nvim/plugin/50-colors.lua deleted file mode 100644 index 4d9774e..0000000 --- a/.config/nvim/plugin/50-colors.lua +++ /dev/null @@ -1,85 +0,0 @@ --- --- Color scheme plugin --- -vim.pack.add({ "https://github.com/maxmx03/solarized.nvim" }) -- Solarized color scheme - -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", -} - -local function adjust_highlight() - if vim.g.colors_name == "solarized" then - -- General highlight - vim.api.nvim_set_hl(0, "EndOfBuffer", { fg = solarized_colors.base0, update = true }) - vim.api.nvim_set_hl(0, "MatchParen", { link = "CurSearch" }) - if vim.o.background == "light" then - vim.api.nvim_set_hl(0, "TabLineSel", { bg = "#edffd5" }) - vim.api.nvim_set_hl(0, "Cursorline", { bg = "#edffd5" }) - vim.api.nvim_set_hl(0, "NormalFloat", { bg = solarized_colors.base2 }) - elseif vim.o.background == "dark" then - vim.api.nvim_set_hl(0, "TabLineSel", { bg = "#043624" }) - vim.api.nvim_set_hl(0, "Cursorline", { bg = "#043624" }) - vim.api.nvim_set_hl(0, "NormalFloat", { bg = solarized_colors.base02 }) - end - end -end - -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 - --- Adjust colors after the color scheme has loaded -vim.api.nvim_create_autocmd("ColorScheme", { - desc = "Adjust color scheme", - group = vim.g.dotfiles.augroup, - callback = adjust_highlight, -}) - --- Adjust colors when the theme (light/dark) changes -vim.api.nvim_create_autocmd("OptionSet", { - desc = "Adjust color scheme", - pattern = "background", - group = vim.g.dotfiles.augroup, - callback = adjust_highlight, -}) - -vim.cmd.colorscheme("solarized") -- Default colorscheme -vim.opt.background = "dark" -- Default theme - -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" }) diff --git a/.config/nvim/plugin/50-highlight.lua b/.config/nvim/plugin/50-extmarks.lua index a509a86..c11d10c 100644 --- a/.config/nvim/plugin/50-highlight.lua +++ b/.config/nvim/plugin/50-extmarks.lua @@ -1,6 +1,11 @@ -- --- Custom highlighting plugin +-- TODO highlighting (treesitter-aware extmarks) -- +-- The `dotfiles.Todo` highlight group and the storage namespace are owned by +-- 40-colors.lua, because the same per-window namespaces also drive the +-- focused/unfocused cursor line. That file is numbered 40- so it is sourced +-- before this one and `vim.g.dotfiles.todo_ns` / `.todo_hl` exist below. Here +-- we only place the extmarks. -- Returns an iterator over the ascendants of `node`, starting at the root node local function node_ascendants(node) @@ -83,17 +88,17 @@ local function hl_todo_predicate(row, col) end local patterns = { todo = "TODO" } -local hl_ns = vim.api.nvim_create_namespace("extmarks") - -vim.api.nvim_set_hl(hl_ns, "dotfiles.Todo", { bg = "Yellow", fg = "Black", bold = true }) -local extmarks_todo_opts = { ns = hl_ns, hl_group = "dotfiles.Todo", predicate = hl_todo_predicate } +local extmarks_todo_opts = { + ns = vim.g.dotfiles.todo_ns, + hl_group = vim.g.dotfiles.todo_hl, + predicate = hl_todo_predicate, +} -- Initialize extmarks vim.api.nvim_create_autocmd("FileType", { desc = "Initialize extmarks", group = vim.g.dotfiles.augroup, callback = function() - vim.api.nvim_win_set_hl_ns(vim.api.nvim_get_current_win(), hl_ns) local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) set_extmarks(pmatches(lines, patterns.todo), extmarks_todo_opts) end, |
