--[[ 50-color.lua — solarized highlight tweaks, a matching lualine statusline, and the custom tabline. Registers highlight and statusline appliers into dotfiles.color, which re-applies them on colorscheme / 'background' changes and once after startup. Also styles the popup menu and tabpage titles, and sets the default colorscheme (solarized) and background (dark). User commands: `ColorsBgToggle`: toggle light/dark background `TabRename`: rename the current tab `TabRenameFileName`: rename the current tab to the active buffer's file name `LualineConfig`: show the resolved lualine configuration ]] local color = require("dotfiles.color") local lualine = require("lualine") local solarized = { blue = "#268bd2" } ---------------------------------------------------------------------------------------------------- -- Highlights -------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- --[[ Highlight applier: adjust the solarized groups to fit my personal tastes. No-ops unless solarized is the active colorscheme. ]] local function apply_solarized_highlights() if vim.g.colors_name ~= "solarized" then return end -- Window bar style. vim.api.nvim_set_hl(0, "WinBarCwd", { fg = solarized.blue, italic = true }) vim.api.nvim_set_hl(0, "WinBarFilePath", { italic = true }) -- Matching delimiter. vim.api.nvim_set_hl(0, "MatchParen", { link = "CurSearch" }) -- Floating windows and popup menu windows follow the editor background, their borders too so that -- the popup menu border draws a clean separation. vim.opt.pumborder = "rounded" vim.opt.pumwidth = 25 -- Minimum popup menu width. vim.api.nvim_set_hl(0, "NormalFloat", { link = "Normal" }) local normal_bg = vim.api.nvim_get_hl(0, { name = "Normal", link = false }).bg vim.api.nvim_set_hl(0, "FloatBorder", { bg = normal_bg, update = true }) vim.api.nvim_set_hl(0, "Pmenu", { bg = normal_bg, update = true }) vim.api.nvim_set_hl(0, "PmenuSbar", { bg = normal_bg, update = true }) -- Currently selected tabpage. vim.api.nvim_set_hl(0, "TabLineSel", { bold = true, italic = true, update = true }) -- `Todo` highlight group. vim.api.nvim_set_hl(0, "Todo", { bg = "Yellow", fg = "Black", bold = true }) --[[ Fold lines sharing the cursor-line color (the default) make unfocused windows unreadable, so their background is set to blend in; the folds stay visible enough through their dotted lines. ]] vim.api.nvim_set_hl(0, "Folded", { bg = normal_bg, underline = true, update = true }) end --[[ Highlight applier: create custom highlight groups for styling the tabpage title bars. `TabLineWinCount` is the window count of a tabpage, `TabLineSelWinCount` that of the active one. ]] local function apply_tabpage_title_highlights() local normal = vim.api.nvim_get_hl(0, { name = "Normal", link = false }) local tabline = vim.api.nvim_get_hl(0, { name = "TabLine", link = false }) local tablinesel = vim.api.nvim_get_hl(0, { name = "TabLineSel", link = false }) vim.api.nvim_set_hl(0, "TabLineSel", { fg = normal.fg, update = true }) vim.api.nvim_set_hl(0, "TabLineWinCount", { fg = solarized.blue, bg = tabline.bg }) vim.api.nvim_set_hl( 0, "TabLineSelWinCount", { fg = solarized.blue, bg = tablinesel.bg, bold = true } ) end ---------------------------------------------------------------------------------------------------- -- Statusline -------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- -- Spell flag: which spell languages are enabled, or nil when 'spelllang' matches none of these. local function spell_status() if vim.o.spelllang == "fr" then return "+S(fr)" elseif vim.o.spelllang == "en_us" then return "+S(en)" elseif vim.o.spelllang == "en_us,fr" then return "+S(en,fr)" end end --[[ Custom status flags: `+F` when formatting is on for the buffer, `+H` when LSP inlay hints are on, `+R` when disk-change autoreload is enabled (see 50-autoreload.lua), followed by the spell flag. ]] local function statuses() return (vim.b.format and "+F" or "") .. (vim.lsp.inlay_hint.is_enabled({ bufnr = 0 }) and "+H" or "") .. (vim.g.autoreload and "+R" or "") .. (vim.o.spell and spell_status() or "") end --[[ lualine builds its own section-b-backed highlights for the diff/diagnostics components (`lualine_b_diff_added_normal`, `lualine_b_diagnostics_error_normal`, ...). Stamp bold onto them with `update = true` so only the bold attribute changes — fg and section b's background are left untouched. Must run after lualine (re)creates the groups: initial setup, theme switch, colorscheme. ]] local function bold_diff_diag() for name in pairs(vim.api.nvim_get_hl(0, {})) do if name:find("^lualine_b_diff") or name:find("^lualine_b_diagnostics") then vim.api.nvim_set_hl(0, name, { bold = true, update = true }) end end end local lualine_sections = { lualine_c = { statuses }, lualine_x = { "encoding", "fileformat", "filetype", "lsp_status" }, } local lualine_options = { disabled_filetypes = { "netrw", "qf" }, component_separators = { left = "|", right = "|" }, section_separators = { left = "", right = "" }, } -- Pick the lualine theme from 'background' and (re)apply the configuration. local function configure_lualine() lualine_options.theme = vim.o.background == "light" and "solarized_light" or "solarized_dark" lualine.setup({ options = lualine_options, sections = lualine_sections }) end --[[ Statusline applier: re-pick the lualine theme for the current 'background', then re-bold the diff/diagnostics groups once lualine has rebuilt them. ]] local function apply_statusline_highlights() configure_lualine() vim.schedule(bold_diff_diag) end ---------------------------------------------------------------------------------------------------- -- Tabline ----------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- --[[ Build the window-count segment of a tabpage label, e.g. ` (2)[+] ` (no styling). `is_modified` appends the `[+]` marker when any window in the tab has pending changes. ]] local function make_tab_info_label(win_count, is_modified) local tab_win_count = "(" .. (win_count >= 10 and ">9" or tostring(win_count)) .. ")" local tab_win_count_suffix = is_modified and "[+] " or " " return " " .. tab_win_count .. tab_win_count_suffix end --[[ Build the title segment of a tabpage label, padded or truncated to `width` (no styling). Uses the tab's `tabname` if set, else the active buffer's file name, else `[No Name]`, prefixed with `*` for the current tab and `-` for the last-accessed tab. ]] local function make_tab_label(id, is_current, width) local is_last_accessed = vim.api.nvim_tabpage_get_number(id) == vim.fn.tabpagenr("#") local tab_label_prefix = (is_current and "*" or "") .. (is_last_accessed and "-" or "") local tab_label = vim.t[id].tabname if tab_label == nil or tab_label == "" then local win = vim.api.nvim_tabpage_get_win(id) local bufname = vim.api.nvim_buf_get_name(vim.api.nvim_win_get_buf(win)) tab_label = vim.fs.basename(bufname) end if tab_label == "" then tab_label = "[No Name]" end tab_label = tab_label_prefix .. tab_label local display_width = vim.fn.strdisplaywidth(tab_label) return (display_width > width) and (vim.fn.strcharpart(tab_label, 0, width - 1) .. "…") or (tab_label .. string.rep(" ", width - display_width)) end --[[ Return a string compatible with the 'tabline' option, used to set the tabline content and style. Declared as a global function to be available as `v:lua.GetTabLine` in the Vimscript engine for the 'tabline' option. See . ]] function GetTabLine() local max_tab_label_width = 30 local s = "" for _, id in pairs(vim.api.nvim_list_tabpages()) do local is_current = id == vim.api.nvim_get_current_tabpage() local is_modified = false local wins = vim.api.nvim_tabpage_list_wins(id) for _, win in ipairs(wins) do if vim.bo[vim.api.nvim_win_get_buf(win)].modified then is_modified = true break end end -- Mark the start of the tabpage label (where the clickable region begins). s = s .. "%" .. vim.api.nvim_tabpage_get_number(id) .. "T" local tab_info_label = make_tab_info_label(#wins, is_modified) local tab_label_width = max_tab_label_width - vim.fn.strdisplaywidth(tab_info_label) local tab_label = make_tab_label(id, is_current, tab_label_width) s = s .. (is_current and "%#TabLineSelWinCount#" or "%#TabLineWinCount#") .. tab_info_label s = s .. (is_current and "%#TabLineSel#" or "%#TabLine#") .. tab_label end return s .. "%#TabLineFill#" end -- Name the current tab from the command argument. local function rename_tab(opts) vim.t.tabname = opts.args vim.cmd.redrawtabline() end -- Name the current tab after the active buffer's file name (without extension). local function rename_tab_to_cur_filename() vim.t.tabname = vim.fn.expand("%:t:r") vim.cmd.redrawtabline() end ---------------------------------------------------------------------------------------------------- -- Commands & init --------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- -- Flip between the light and dark backgrounds (the colorscheme reloads, OptionSet re-themes). 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 -- Show the resolved lualine configuration. local function show_lualine_config() vim.notify(vim.inspect(lualine.get_config())) end color.register(apply_solarized_highlights) color.register(apply_tabpage_title_highlights) color.register(apply_statusline_highlights) -- stylua: ignore start vim.api.nvim_create_user_command("ColorsBgToggle", toggle_theme, { desc = "Toggle light/dark theme" }) vim.api.nvim_create_user_command("TabRename", rename_tab, { nargs = 1 }) vim.api.nvim_create_user_command("TabRenameFileName", rename_tab_to_cur_filename, { nargs = 0 }) vim.api.nvim_create_user_command("LualineConfig", show_lualine_config, { desc = "Show lualine configuration" }) -- stylua: ignore end -- See . vim.o.tabline = "%!v:lua.GetTabLine()" vim.cmd.colorscheme("solarized") vim.opt.background = "dark"