From 37a314b9e3b65807c20ea6bbb5821efe45163af4 Mon Sep 17 00:00:00 2001 From: Shadman <13149513+shadmansaleh@users.noreply.github.com> Date: Wed, 2 Mar 2022 13:37:08 +0000 Subject: [PATCH] feat: add support for dynamic color with functions in color options (#566) * feat: allow functions in color options. * update_evilline * docs: document color functions * remove unnecesery stuff * add dynamic color supoort for themes * chore: autogen (vimdocs+formating) * fix dynamic colors not working as color fallback * fix transitional separators not updating for dynamic colors dynamic colors * fix failing tests * apply format * Allow cases where theme doesn't even define nornal color for some mode * allow color function to return nil * some enhancements * more enhancements * code cleanup * if we don't have even normal in theme we should just nvim highlight it with it's ususal stl colors * not sure how it get here . It should be in different pr * keep only c of lualine_c in component section name * use sh to run docgen * fix filetype component not respecting color option properly * fix section x,y,z not falling back to correct colors * auto format * actually fix xyz not falling back to correct mode * fix comp sep not correctly removed properly on function hl * pass only section in color fn * more enhancements * update docs * update create_comp_hl call locations * enhancements+fixes * fix broken hls in tabline * Fix function color options not inheriting right colors * some enhancements * fix tests * tweek docs Co-authored-by: shadmansaleh --- Makefile | 2 +- README.md | 8 +- doc/lualine.txt | 8 +- examples/evil_lualine.lua | 7 +- lua/lualine.lua | 4 +- lua/lualine/component.lua | 22 +- lua/lualine/components/buffers/buffer.lua | 4 +- lua/lualine/components/buffers/init.lua | 14 +- lua/lualine/components/diagnostics/init.lua | 12 +- lua/lualine/components/diff/init.lua | 9 +- lua/lualine/components/filetype.lua | 11 +- lua/lualine/components/tabs/init.lua | 12 +- lua/lualine/components/tabs/tab.lua | 4 +- lua/lualine/highlight.lua | 355 +++++++++++++++----- lua/lualine/utils/loader.lua | 4 +- lua/lualine/utils/section.lua | 16 +- lua/lualine/utils/utils.lua | 25 +- lua/tests/helpers.lua | 6 +- lua/tests/spec/component_spec.lua | 36 +- lua/tests/spec/utils_spec.lua | 25 +- 20 files changed, 425 insertions(+), 159 deletions(-) diff --git a/Makefile b/Makefile index 06dcc84..08883ee 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ endif @rm -rf tmp_home docgen: - @bash ./scripts/docgen.sh + @sh ./scripts/docgen.sh precommit_check: docgen format test lint diff --git a/README.md b/README.md index a7e01fd..dcf22ae 100644 --- a/README.md +++ b/README.md @@ -374,15 +374,19 @@ sections = { -- Defines a custom color for the component: -- - -- 'highlight_group_name' | { fg = '#rrggbb'|cterm_value(0-255)|'color_name(red)', bg= '#rrggbb', gui='style' } + -- 'highlight_group_name' | { fg = '#rrggbb'|cterm_value(0-255)|'color_name(red)', bg= '#rrggbb', gui='style' } | function -- Note: -- '|' is synonymous with 'or', meaning a different acceptable format for that placeholder. + -- color function has to return one of other color types ('highlight_group_name' | { fg = '#rrggbb'|cterm_value(0-255)|'color_name(red)', bg= '#rrggbb', gui='style' }) + -- color functions can be used to have different colors based on state as shown below. -- -- Examples: -- color = { fg = '#ffaa88', bg = 'grey', gui='italic,bold' }, -- color = { fg = 204 } -- When fg/bg are omitted, they default to the your theme's fg/bg. -- color = 'WarningMsg' -- Highlight groups can also be used. - -- + -- color = function(section) + -- return { fg = vim.bo.modified and '#aa3355' or '#33aa88' } + -- end, color = nil, -- The default is your theme's color for that section and mode. -- Specify what type a component is, if omitted, lualine will guess it for you. diff --git a/doc/lualine.txt b/doc/lualine.txt index 45b243a..024b8a0 100644 --- a/doc/lualine.txt +++ b/doc/lualine.txt @@ -387,15 +387,19 @@ General component options These are options that control behavior -- Defines a custom color for the component: -- - -- 'highlight_group_name' | { fg = '#rrggbb'|cterm_value(0-255)|'color_name(red)', bg= '#rrggbb', gui='style' } + -- 'highlight_group_name' | { fg = '#rrggbb'|cterm_value(0-255)|'color_name(red)', bg= '#rrggbb', gui='style' } | function -- Note: -- '|' is synonymous with 'or', meaning a different acceptable format for that placeholder. + -- color function has to return one of other color types ('highlight_group_name' | { fg = '#rrggbb'|cterm_value(0-255)|'color_name(red)', bg= '#rrggbb', gui='style' }) + -- color functions can be used to have different colors based on state as shown below. -- -- Examples: -- color = { fg = '#ffaa88', bg = 'grey', gui='italic,bold' }, -- color = { fg = 204 } -- When fg/bg are omitted, they default to the your theme's fg/bg. -- color = 'WarningMsg' -- Highlight groups can also be used. - -- + -- color = function(section) + -- return { fg = vim.bo.modified and '#aa3355' or '#33aa88' } + -- end, color = nil, -- The default is your theme's color for that section and mode. -- Specify what type a component is, if omitted, lualine will guess it for you. diff --git a/examples/evil_lualine.lua b/examples/evil_lualine.lua index 09e66b9..a5be3da 100644 --- a/examples/evil_lualine.lua +++ b/examples/evil_lualine.lua @@ -89,6 +89,9 @@ ins_left { ins_left { -- mode component function() + return '' + end, + color = function() -- auto change color according to neovims mode local mode_color = { n = colors.red, @@ -112,10 +115,8 @@ ins_left { ['!'] = colors.red, t = colors.red, } - vim.api.nvim_command('hi! LualineMode guifg=' .. mode_color[vim.fn.mode()] .. ' guibg=' .. colors.bg) - return '' + return { fg = mode_color[vim.fn.mode()] } end, - color = 'LualineMode', padding = { right = 1 }, } diff --git a/lua/lualine.lua b/lua/lualine.lua index 51ca0ae..bfd02ec 100644 --- a/lua/lualine.lua +++ b/lua/lualine.lua @@ -158,7 +158,7 @@ local statusline = modules.utils.retry_call_wrap(function(sections, is_focused) if #section_data > 0 then if not applied_midsection_divider and section_name > 'c' then applied_midsection_divider = true - section_data = modules.highlight.format_highlight('lualine_c') .. '%=' .. section_data + section_data = modules.highlight.format_highlight('c', is_focused) .. '%=' .. section_data end if not applied_trunc and section_name > 'b' then applied_trunc = true @@ -170,7 +170,7 @@ local statusline = modules.utils.retry_call_wrap(function(sections, is_focused) end if applied_midsection_divider == false and config.options.always_divide_middle ~= false then -- When non of section x,y,z is present - table.insert(status, modules.highlight.format_highlight('lualine_c') .. '%=') + table.insert(status, modules.highlight.format_highlight('c', is_focused) .. '%=') end return apply_transitional_separators(table.concat(status)) end) diff --git a/lua/lualine/component.lua b/lua/lualine/component.lua index b497399..fdf6212 100644 --- a/lua/lualine/component.lua +++ b/lua/lualine/component.lua @@ -38,7 +38,7 @@ end function M:set_separator() if self.options.separator == nil then if self.options.component_separators then - if self.options.self.section < 'lualine_x' then + if self.options.self.section < 'x' then self.options.separator = self.options.component_separators.left else self.options.separator = self.options.component_separators.right @@ -54,7 +54,8 @@ function M:create_option_highlights() self.options.color_highlight = highlight.create_component_highlight_group( self.options.color, self.options.component_name, - self.options + self.options, + false ) end end @@ -89,7 +90,9 @@ end ---applies custom highlights for component function M:apply_highlights(default_highlight) if self.options.color_highlight then - self.status = highlight.component_format_highlight(self.options.color_highlight) .. self.status + local hl_fmt + hl_fmt, M.color_fn_cache = highlight.component_format_highlight(self.options.color_highlight) + self.status = hl_fmt .. self.status end if type(self.options.separator) ~= 'table' and self.status:find('%%#') then -- Apply default highlight only when we aren't applying trans sep and @@ -119,7 +122,7 @@ function M:apply_separator() local separator = self.options.separator if type(separator) == 'table' then if self.options.separator[2] == '' then - if self.options.self.section < 'lualine_x' then + if self.options.self.section < 'x' then separator = self.options.component_separators.left else separator = self.options.component_separators.right @@ -159,6 +162,16 @@ function M:strip_separator() return self.status end +function M:get_default_hl() + if self.options.color_highlight then + return highlight.component_format_highlight(self.options.color_highlight) + elseif self.default_hl then + return self.default_hl + else + return highlight.format_highlight(self.options.self.section) + end +end + -- luacheck: push no unused args ---actual function that updates a component. Must be overwritten with component functionality function M:update_status(is_focused) end @@ -172,6 +185,7 @@ function M:draw(default_highlight, is_focused) if self.options.cond ~= nil and self.options.cond() ~= true then return self.status end + self.default_hl = default_highlight local status = self:update_status(is_focused) if self.options.fmt then status = self.options.fmt(status or '') diff --git a/lua/lualine/components/buffers/buffer.lua b/lua/lualine/components/buffers/buffer.lua index a5e6d78..2f9fa06 100644 --- a/lua/lualine/components/buffers/buffer.lua +++ b/lua/lualine/components/buffers/buffer.lua @@ -77,11 +77,11 @@ function Buffer:render() .. line -- apply separators - if self.options.self.section < 'lualine_x' and not self.first then + if self.options.self.section < 'x' and not self.first then local sep_before = self:separator_before() line = sep_before .. line self.len = self.len + vim.fn.strchars(sep_before) - elseif self.options.self.section >= 'lualine_x' and not self.last then + elseif self.options.self.section >= 'x' and not self.last then local sep_after = self:separator_after() line = line .. sep_after self.len = self.len + vim.fn.strchars(sep_after) diff --git a/lua/lualine/components/buffers/init.lua b/lua/lualine/components/buffers/init.lua index 5117ff4..0d1ea38 100644 --- a/lua/lualine/components/buffers/init.lua +++ b/lua/lualine/components/buffers/init.lua @@ -44,20 +44,22 @@ end function M:init(options) M.super.init(self, options) default_options.buffers_color = { - active = get_hl(options.self.section, true), - inactive = get_hl(options.self.section, false), + active = get_hl('lualine_' .. options.self.section, true), + inactive = get_hl('lualine_' .. options.self.section, false), } self.options = vim.tbl_deep_extend('keep', self.options or {}, default_options) self.highlights = { active = highlight.create_component_highlight_group( self.options.buffers_color.active, 'buffers_active', - self.options + self.options, + false ), inactive = highlight.create_component_highlight_group( self.options.buffers_color.inactive, - 'buffers_active', - self.options + 'buffers_inactive', + self.options, + false ), } end @@ -112,7 +114,7 @@ function M:update_status() if current == -2 then local b = Buffer { bufnr = vim.api.nvim_get_current_buf(), options = self.options, highlights = self.highlights } b.current = true - if self.options.self.section < 'lualine_x' then + if self.options.self.section < 'x' then b.last = true if #buffers > 0 then buffers[#buffers].last = nil diff --git a/lua/lualine/components/diagnostics/init.lua b/lua/lualine/components/diagnostics/init.lua index e818fa8..949606a 100644 --- a/lua/lualine/components/diagnostics/init.lua +++ b/lua/lualine/components/diagnostics/init.lua @@ -33,22 +33,26 @@ function M:init(options) error = modules.highlight.create_component_highlight_group( self.options.diagnostics_color.error, 'diagnostics_error', - self.options + self.options, + false ), warn = modules.highlight.create_component_highlight_group( self.options.diagnostics_color.warn, 'diagnostics_warn', - self.options + self.options, + false ), info = modules.highlight.create_component_highlight_group( self.options.diagnostics_color.info, 'diagnostics_info', - self.options + self.options, + false ), hint = modules.highlight.create_component_highlight_group( self.options.diagnostics_color.hint, 'diagnostics_hint', - self.options + self.options, + false ), } end diff --git a/lua/lualine/components/diff/init.lua b/lua/lualine/components/diff/init.lua index 939ce59..a797c8a 100644 --- a/lua/lualine/components/diff/init.lua +++ b/lua/lualine/components/diff/init.lua @@ -47,17 +47,20 @@ function M:init(options) added = modules.highlight.create_component_highlight_group( self.options.diff_color.added, 'diff_added', - self.options + self.options, + false ), modified = modules.highlight.create_component_highlight_group( self.options.diff_color.modified, 'diff_modified', - self.options + self.options, + false ), removed = modules.highlight.create_component_highlight_group( self.options.diff_color.removed, 'diff_removed', - self.options + self.options, + false ), } end diff --git a/lua/lualine/components/filetype.lua b/lua/lualine/components/filetype.lua index 28e947c..c1059ab 100644 --- a/lua/lualine/components/filetype.lua +++ b/lua/lualine/components/filetype.lua @@ -22,6 +22,7 @@ function M.update_status() return modules.utils.stl_escape(ft) end +local icon_hl_cache = {} function M:apply_icon() if not self.options.icons_enabled then return @@ -36,14 +37,16 @@ function M:apply_icon() if icon and self.options.colored then local highlight_color = modules.utils.extract_highlight_colors(icon_highlight_group, 'fg') - local default_highlight = modules.highlight.format_highlight(self.options.self.section) - local icon_highlight = self.options.self.section .. '_' .. icon_highlight_group - if not modules.highlight.highlight_exists(icon_highlight .. '_normal') then + local default_highlight = self:get_default_hl() + local icon_highlight = icon_hl_cache[highlight_color] + if not icon_highlight or not modules.highlight.highlight_exists(icon_highlight.name .. '_normal') then icon_highlight = modules.highlight.create_component_highlight_group( { fg = highlight_color }, icon_highlight_group, - self.options + self.options, + false ) + icon_hl_cache[highlight_color] = icon_highlight end icon = modules.highlight.component_format_highlight(icon_highlight) .. icon .. default_highlight diff --git a/lua/lualine/components/tabs/init.lua b/lua/lualine/components/tabs/init.lua index 2671e16..8174876 100644 --- a/lua/lualine/components/tabs/init.lua +++ b/lua/lualine/components/tabs/init.lua @@ -35,8 +35,8 @@ end function M:init(options) M.super.init(self, options) default_options.tabs_color = { - active = get_hl(options.self.section, true), - inactive = get_hl(options.self.section, false), + active = get_hl('lualine_' .. options.self.section, true), + inactive = get_hl('lualine_' .. options.self.section, false), } self.options = vim.tbl_deep_extend('keep', self.options or {}, default_options) -- stylua: ignore @@ -44,12 +44,14 @@ function M:init(options) active = highlight.create_component_highlight_group( self.options.tabs_color.active, 'tabs_active', - self.options + self.options, + false ), inactive = highlight.create_component_highlight_group( self.options.tabs_color.inactive, - 'tabs_active', - self.options + 'tabs_inactive', + self.options, + false ), } end diff --git a/lua/lualine/components/tabs/tab.lua b/lua/lualine/components/tabs/tab.lua index a3236a9..979f8da 100644 --- a/lua/lualine/components/tabs/tab.lua +++ b/lua/lualine/components/tabs/tab.lua @@ -70,11 +70,11 @@ function Tab:render() .. line -- apply separators - if self.options.self.section < 'lualine_x' and not self.first then + if self.options.self.section < 'x' and not self.first then local sep_before = self:separator_before() line = sep_before .. line self.len = self.len + vim.fn.strchars(sep_before) - elseif self.options.self.section >= 'lualine_x' and not self.last then + elseif self.options.self.section >= 'x' and not self.last then local sep_after = self:separator_after() line = line .. sep_after self.len = self.len + vim.fn.strchars(sep_after) diff --git a/lua/lualine/highlight.lua b/lua/lualine/highlight.lua index 350fa80..ea7b841 100644 --- a/lua/lualine/highlight.lua +++ b/lua/lualine/highlight.lua @@ -10,6 +10,7 @@ local modules = lualine_require.lazy_require { local section_highlight_map = { x = 'c', y = 'b', z = 'a' } local active_theme = nil +local theme_hls = {} local create_cterm_colors = false -- table to store the highlight names created by lualine @@ -44,14 +45,17 @@ end local function clear_highlights() for highlight_name, _ in pairs(loaded_highlights) do vim.cmd('highlight clear ' .. highlight_name) - loaded_highlights[highlight_name] = nil end + loaded_highlights = {} end ---converts cterm, color_name type colors to #rrggbb format ---@param color string|number ---@return string local function sanitize_color(color) + if color == nil or color == '' then + return 'None' + end if type(color) == 'string' then if color:sub(1, 1) == '#' then return color @@ -65,6 +69,28 @@ local function sanitize_color(color) end end +function M.get_lualine_hl(name) + local hl = loaded_highlights[name] + if hl and not hl.empty then + if hl.link then + return modules.utils.extract_highlight_colors(hl.link) + end + local hl_def = { + fg = hl.fg ~= 'None' and vim.deepcopy(hl.fg), + bg = hl.bg ~= 'None' and vim.deepcopy(hl.bg), + } + if hl.gui then + for _, flag in ipairs(vim.split(hl.gui, ',')) do + if flag ~= 'None' then + hl_def[flag] = true + end + end + end + + return hl_def + end +end + --- Define a hl_group ---@param name string ---@param foreground string|number: color @@ -74,46 +100,113 @@ end function M.highlight(name, foreground, background, gui, link) local command = { 'highlight!' } if link and #link > 0 then + if loaded_highlights[name] and loaded_highlights[name].link == link then + return + end vim.list_extend(command, { 'link', name, link }) else foreground = sanitize_color(foreground) background = sanitize_color(background) + gui = (gui ~= nil and gui ~= '') and gui or 'None' + if + loaded_highlights[name] + and loaded_highlights[name].fg == foreground + and loaded_highlights[name].bg == background + and loaded_highlights[name].gui == gui + then + return -- color is already defined why are we doing this anyway ? + end table.insert(command, name) - if foreground and foreground ~= 'None' then - table.insert(command, 'guifg=' .. foreground) - if create_cterm_colors then - table.insert(command, 'ctermfg=' .. modules.color_utils.rgb2cterm(foreground)) - end - end - if background and background ~= 'None' then - table.insert(command, 'guibg=' .. background) - if create_cterm_colors then - table.insert(command, 'ctermbg=' .. modules.color_utils.rgb2cterm(background)) - end - end - if gui then + table.insert(command, 'guifg=' .. foreground) + table.insert(command, 'guibg=' .. background) + table.insert(command, 'gui=' .. gui) + if create_cterm_colors then + table.insert(command, 'ctermfg=' .. modules.color_utils.rgb2cterm(foreground)) + table.insert(command, 'ctermbg=' .. modules.color_utils.rgb2cterm(background)) table.insert(command, 'cterm=' .. gui) - table.insert(command, 'gui=' .. gui) end end vim.cmd(table.concat(command, ' ')) - loaded_highlights[name] = true + + -- update attached hl groups + local old_hl_def = loaded_highlights[name] + if old_hl_def and next(old_hl_def.attached) then + -- Update attached hl groups as they announced to depend on hl_group 'name' + -- 'hl' being in 'name'a attached table means 'hl' + -- depends of 'name'. + -- 'hl' key in attached table will contain a table that + -- defines the reletaion between 'hl' & 'name'. + -- name.attached.hl = { bg = 'fg' } means + -- hl's fg is same as 'names' bg . So 'hl's fg should + -- be updated when ever 'name' changes it's 'bg' + local bg_changed = old_hl_def.bg ~= background + local fg_changed = old_hl_def.bg ~= foreground + local gui_changed = old_hl_def.gui ~= gui + for attach_name, attach_desc in pairs(old_hl_def.attached) do + if bg_changed and attach_desc.bg and loaded_highlights[attach_name] then + M.highlight( + attach_name, + attach_desc.bg == 'fg' and background or loaded_highlights[attach_name].fg, + attach_desc.bg == 'bg' and background or loaded_highlights[attach_name].bg, + loaded_highlights[attach_name].gui, + loaded_highlights[attach_name].link + ) + end + if fg_changed and attach_desc.fg and loaded_highlights[attach_name] then + M.highlight( + attach_name, + attach_desc.fg == 'fg' and foreground or loaded_highlights[attach_name].fg, + attach_desc.fg == 'bg' and foreground or loaded_highlights[attach_name].bg, + loaded_highlights[attach_name].gui, + loaded_highlights[attach_name].link + ) + end + if gui_changed and attach_desc.gui and loaded_highlights[attach_name] then + M.highlight( + attach_name, + loaded_highlights[attach_name].fg, + loaded_highlights[attach_name].bg, + gui, + loaded_highlights[attach_name].link + ) + end + end + end + -- store current hl state + loaded_highlights[name] = { + fg = foreground, + bg = background, + gui = gui, + link = link, + attached = old_hl_def and old_hl_def.attached or {}, + } end +---Attach an hl to another so attachee auto updated on change to hl it's attached too. +---@param provider string the hl receiver is getting attached to +---@param receiver string the hl that will be auto updated upon change to provider +---@param provider_el_type string (fg/bg) what element receiver relates to of provider +---@param receiver_el_type string (fg/bg) what element provider relates to of receiver +local function attach_hl(provider, receiver, provider_el_type, receiver_el_type) + if loaded_highlights[provider] == nil then + loaded_highlights[provider] = { empty = true, attached = {} } + end + loaded_highlights[provider].attached[receiver] = { [provider_el_type] = receiver_el_type } +end ---define hl_groups for a theme ---@param theme table function M.create_highlight_groups(theme) clear_highlights() active_theme = theme + theme_hls = {} + local psudo_options = { self = { section = 'a' } } create_cterm_colors = not vim.go.termguicolors for mode, sections in pairs(theme) do + theme_hls[mode] = {} for section, color in pairs(sections) do - local highlight_group_name = { 'lualine', section, mode } - if type(color) == 'string' then -- link to a highlight group - M.highlight(table.concat(highlight_group_name, '_'), nil, nil, nil, color) - else -- Define a new highlight - M.highlight(table.concat(highlight_group_name, '_'), color.fg, color.bg, color.gui, nil) - end + local hl_tag = mode + psudo_options.self.section = section + theme_hls[mode][section] = M.create_component_highlight_group(color, hl_tag, psudo_options, true) end end end @@ -121,7 +214,7 @@ end ---@description: adds '_mode' at end of highlight_group ---@param highlight_group string name of highlight group ---@return string highlight group name with mode -function M.append_mode(highlight_group, is_focused) +local function append_mode(highlight_group, is_focused) if is_focused == nil then is_focused = modules.utils.is_focused() end @@ -134,27 +227,67 @@ end -- Helper function for create component highlight ---Handles fall back of colors when creating highlight group +---@param hl_name string name of highlight that we are setting default values for +---@param mode string mode which default component color should be given. +---@param section string the lualine section component is in. ---@param color table color passed for creating component highlight ----@param options_color table color set by color option for component ---- this is first fall back ----@param default_color table colors et in theme this is 2nd fall back ----@param kind string fg/bg -local function get_default_component_color(color, options_color, default_color, kind) - if color[kind] then - return color[kind] +---@param options table Options table of component this is first fall back +local function get_default_component_color(hl_name, mode, section, color, options) + local default_theme_color + if active_theme[mode] and active_theme[mode][section] then + default_theme_color = active_theme[mode][section] + elseif section >= 'c' and active_theme[mode] and active_theme[mode][section_highlight_map[section]] then + default_theme_color = active_theme[mode][section_highlight_map[section]] + elseif section >= 'c' and active_theme.normal[section_highlight_map[section]] then + default_theme_color = active_theme.normal[section_highlight_map[section]] + else + default_theme_color = active_theme.normal[section] end - if options_color then - if type(options_color) == 'table' and options_color[kind] then - return options_color[kind] - elseif type(options_color) == 'string' then - return modules.utils.extract_highlight_colors(options_color, kind) + + local ret = { fg = color.fg, bg = color.bg, gui = color.gui } + if ret.fg and ret.bg then + return ret + end + + local function apply_default(def_color, def_name) + if type(def_color) == 'function' and loaded_highlights[def_name] and not loaded_highlights[def_name].empty then + if loaded_highlights[def_name].link then + def_color = loaded_highlights[def_name].link + else + def_color = loaded_highlights[def_name] + end + end + if type(def_color) == 'function' then + def_color = def_color { section = section } + end + if type(def_color) == 'string' then + def_color = modules.utils.extract_highlight_colors(def_color) + end + if type(def_color) == 'table' then + if not ret.fg then + ret.fg = def_color.fg + attach_hl(def_name, hl_name, 'fg', 'fg') + end + if not ret.bg then + ret.bg = def_color.bg + attach_hl(def_name, hl_name, 'bg', 'bg') + end end end - if type(default_color) == 'table' then - return default_color[kind] - elseif type(default_color) == 'string' then - return modules.utils.extract_highlight_colors(default_color, kind) + + if + options.color + and options.color_highlight + and options.color_highlight.name + and options.color_highlight.name .. '_' .. mode ~= hl_name + then + apply_default(options.color, options.color_highlight.name .. '_' .. mode) + else + apply_default(default_theme_color, string.format('lualine_%s_%s', section, mode)) end + ret.fg = sanitize_color(ret.fg) + ret.bg = sanitize_color(ret.bg) + return ret end ---Create highlight group with fg bg and gui from theme @@ -164,31 +297,50 @@ end ---@param highlight_tag string is unique tag for highlight group ---returns the name of highlight group ---@param options table is parameter of component.init() function ----@return string unique name that can be used by component_format_highlight +---@return table that can be used by component_format_highlight --- to retrieve highlight group -function M.create_component_highlight_group(color, highlight_tag, options) +function M.create_component_highlight_group(color, highlight_tag, options, apply_no_default) + local section = options.self.section + if section > 'c' and not active_theme.normal[section] then + section = section_highlight_map[section] + end + local tag_id = 0 while - M.highlight_exists(table.concat({ 'lualine', highlight_tag, 'no_mode' }, '_')) - or ( - options.self.section - and M.highlight_exists(table.concat({ options.self.section, highlight_tag, 'normal' }, '_')) - ) + M.highlight_exists(table.concat({ 'lualine', section, highlight_tag }, '_')) + or (section and M.highlight_exists(table.concat({ 'lualine', section, highlight_tag, 'normal' }, '_'))) do highlight_tag = highlight_tag .. '_' .. tostring(tag_id) tag_id = tag_id + 1 end + if type(color) == 'string' then - local highlight_group_name = table.concat({ 'lualine', highlight_tag, 'no_mode' }, '_') + local highlight_group_name = table.concat({ 'lualine', section, highlight_tag }, '_') M.highlight(highlight_group_name, nil, nil, nil, color) -- l8nk to group - return highlight_group_name + return { + name = highlight_group_name, + fn = nil, + no_mode = true, + link = true, + section = section, + options = options, + no_default = apply_no_default, + } end - if color.bg and color.fg then + + if type(color) ~= 'function' and (apply_no_default or (color.bg and color.fg)) then -- When bg and fg are both present we donn't need to set highlighs for -- each mode as they will surely look the same. So we can work without options - local highlight_group_name = table.concat({ 'lualine', highlight_tag, 'no_mode' }, '_') + local highlight_group_name = table.concat({ 'lualine', section, highlight_tag }, '_') M.highlight(highlight_group_name, color.fg, color.bg, color.gui, nil) - return highlight_group_name + return { + name = highlight_group_name, + fn = nil, + no_mode = true, + section = section, + options = options, + no_default = apply_no_default, + } end local modes = { @@ -200,64 +352,87 @@ function M.create_component_highlight_group(color, highlight_tag, options) 'terminal', 'inactive', } - local normal_hl - -- convert lualine_a -> a before setting section - local section = options.self.section:match('lualine_(.*)') - if section > 'c' and not active_theme.normal[section] then - section = section_highlight_map[section] - end for _, mode in ipairs(modes) do - local highlight_group_name = { options.self.section, highlight_tag, mode } - local default_color_table = active_theme[mode] and active_theme[mode][section] or active_theme.normal[section] - local bg = get_default_component_color(color, options.color, default_color_table, 'bg') - local fg = get_default_component_color(color, options.color, default_color_table, 'fg') - -- Check if it's same as normal mode if it is no need to create aditional highlight - if mode ~= 'normal' then - if bg ~= normal_hl.bg or fg ~= normal_hl.fg then - M.highlight(table.concat(highlight_group_name, '_'), fg, bg, color.gui, nil) - end - else - normal_hl = { bg = bg, fg = fg } - M.highlight(table.concat(highlight_group_name, '_'), fg, bg, color.gui, nil) + local hl_name = table.concat({ 'lualine', section, highlight_tag, mode }, '_') + local cl = color + if type(color) == 'function' then + cl = color { section = section } or {} end + if type(cl) == 'string' then + cl = { link = cl } + else + cl = get_default_component_color(hl_name, mode, section, cl, options) + end + M.highlight(hl_name, cl.fg, cl.bg, cl.gui, cl.link) end - return options.self.section .. '_' .. highlight_tag + return { + name = table.concat({ 'lualine', section, highlight_tag }, '_'), + fn = type(color) == 'function' and color, + no_mode = false, + link = false, + section = section, + options = options, + no_default = apply_no_default, + } end ---@description: retrieve highlight_groups for components ----@param highlight_name string highlight group name without mode +---@param highlight table return value of create_component_highlight_group --- return value of create_component_highlight_group is to be passed in --- this parameter to receive highlight that was created ---@return string formatted highlight group name -function M.component_format_highlight(highlight_name) - local highlight_group = highlight_name - if highlight_name:find('no_mode') == #highlight_name - #'no_mode' + 1 then - return '%#' .. highlight_group .. '#' - end - highlight_group = M.append_mode(highlight_group) - if M.highlight_exists(highlight_group) then +function M.component_format_highlight(highlight, is_focused) + if not highlight.fn then + local highlight_group = highlight.name + if highlight.no_mode then + return '%#' .. highlight_group .. '#' + end + highlight_group = append_mode(highlight_group, is_focused) return '%#' .. highlight_group .. '#' else - return '%#' .. highlight_name .. '_normal#' + local color = highlight.fn { section = highlight.section } or {} + local hl_name = highlight.name + if type(color) == 'string' then + M.highlight(hl_name, nil, nil, nil, color) + return '%#' .. hl_name .. '#' + elseif type(color) == 'table' then + if not highlight.no_default and not (color.fg and color.bg) then + hl_name = append_mode(highlight.name, is_focused) + color = get_default_component_color( + hl_name, + append_mode(''):sub(2), + highlight.section, + color, + highlight.options + ) + end + M.highlight(hl_name, color.fg, color.bg, color.gui, color.link) + return '%#' .. hl_name .. '#', color + end end end ---@description: retrieve highlight_groups for section ----@param highlight_group string highlight group name without mode +---@param section string highlight group name without mode --- return value of create_component_highlight_group is to be passed in --- this parameter to receive highlight that was created ---@param is_focused boolean ---@return string formatted highlight group name -function M.format_highlight(highlight_group, is_focused) - local highlight_name = M.append_mode(highlight_group, is_focused) - if highlight_group > 'lualine_c' and not M.highlight_exists(highlight_name) then - highlight_group = 'lualine_' .. section_highlight_map[highlight_group:match('lualine_(.)')] - highlight_name = M.append_mode(highlight_group, is_focused) +function M.format_highlight(section, is_focused) + local mode = append_mode('', is_focused):sub(2) + local ret = '' + + if theme_hls[mode] and theme_hls[mode][section] then + ret = M.component_format_highlight(theme_hls[mode][section], is_focused) + elseif theme_hls[mode] and section > 'c' and theme_hls[mode][section_highlight_map[section]] then + ret = M.component_format_highlight(theme_hls[mode][section_highlight_map[section]], is_focused) + elseif theme_hls['normal'] and theme_hls['normal'][section] then + ret = M.component_format_highlight(theme_hls['normal'][section], is_focused) + elseif theme_hls['normal'] and section > 'c' and theme_hls['normal'][section_highlight_map[section]] then + ret = M.component_format_highlight(theme_hls['normal'][section_highlight_map[section]], is_focused) end - if M.highlight_exists(highlight_name) then - return '%#' .. highlight_name .. '#' - end - return '%#' .. highlight_group .. '_normal#' + + return ret end ---@description : Provides transitional highlights for section separators. @@ -286,6 +461,8 @@ function M.get_transitional_highlights(left_hl, right_hl) return nil -- Separator won't be visible anyway end M.highlight(highlight_name, fg, bg, nil) + attach_hl(left_hl, highlight_name, 'bg', 'fg') + attach_hl(right_hl, highlight_name, 'bg', 'bg') end return '%#' .. highlight_name .. '#' end diff --git a/lua/lualine/utils/loader.lua b/lua/lualine/utils/loader.lua index d0f74f7..1805301 100644 --- a/lua/lualine/utils/loader.lua +++ b/lua/lualine/utils/loader.lua @@ -91,7 +91,7 @@ component type '%s' isn't recognised. Check if spelling is correct.]], end --- Shows notice about invalid types passed as component ---- @param index number the index of component jn section table +--- @param index number the index of component in section table --- @param component table containing component elements --- return bool whether check passed or not local function is_valid_component_type(index, component) @@ -141,7 +141,7 @@ local function load_sections(sections, options) end if is_valid_component_type(index, component) then component.self = {} - component.self.section = section_name + component.self.section = section_name:match('lualine_(.*)') -- apply default args component = vim.tbl_extend('keep', component, options) section[index] = component_loader(component) diff --git a/lua/lualine/utils/section.lua b/lua/lualine/utils/section.lua index 5b66d26..37fdffc 100644 --- a/lua/lualine/utils/section.lua +++ b/lua/lualine/utils/section.lua @@ -15,7 +15,7 @@ local highlight = require('lualine.highlight') ---@return string formated string for a section --TODO Clean this up this does lots of messy stuff. function M.draw_section(section, section_name, is_focused) - local highlight_name = highlight.format_highlight('lualine_' .. section_name, is_focused) + local highlight_name = highlight.format_highlight(section_name, is_focused) local status = {} for _, component in pairs(section) do @@ -26,6 +26,8 @@ function M.draw_section(section, section_name, is_focused) table.insert(status, component:draw(highlight_name, is_focused)) end + local section_color = utils.extract_highlight_colors(string.match(highlight_name, '%%#(.*)#')) + -- Flags required for knowing when to remove component separator local strip_next_component = false local last_component_found = false @@ -60,8 +62,18 @@ function M.draw_section(section, section_name, is_focused) end -- Remove component separator when color option is used to color background if - (type(section[component_no].options.color) == 'table' and section[component_no].options.color.bg) + ( + type(section[component_no].options.color) == 'table' + and section[component_no].options.color.bg + and section[component_no].options.color.bg ~= section_color.bg + ) or type(section[component_no].options.color) == 'string' + or ( + type(section[component_no].options.color) == 'function' + and section[component_no].color_fn_cache + and section[component_no].color_fn_cache.bg + and section[component_no].color_fn_cache.bg ~= section_color.bg + ) then strip_next_component = true status[component_no] = section[component_no]:strip_separator() diff --git a/lua/lualine/utils/utils.lua b/lua/lualine/utils/utils.lua index f6a7015..3ec25bf 100644 --- a/lua/lualine/utils/utils.lua +++ b/lua/lualine/utils/utils.lua @@ -9,17 +9,20 @@ local M = {} ---@return table|string returns #rrggbb formated color when scope is specified ---- or comolete color table when scope isn't specified function M.extract_highlight_colors(color_group, scope) - if vim.fn.hlexists(color_group) == 0 then - return nil - end - local color = vim.api.nvim_get_hl_by_name(color_group, true) - if color.background ~= nil then - color.bg = string.format('#%06x', color.background) - color.background = nil - end - if color.foreground ~= nil then - color.fg = string.format('#%06x', color.foreground) - color.foreground = nil + local color = require('lualine.highlight').get_lualine_hl(color_group) + if not color then + if vim.fn.hlexists(color_group) == 0 then + return nil + end + color = vim.api.nvim_get_hl_by_name(color_group, true) + if color.background ~= nil then + color.bg = string.format('#%06x', color.background) + color.background = nil + end + if color.foreground ~= nil then + color.fg = string.format('#%06x', color.foreground) + color.foreground = nil + end end if scope then return color[scope] diff --git a/lua/tests/helpers.lua b/lua/tests/helpers.lua index 58bb660..ab7d4af 100644 --- a/lua/tests/helpers.lua +++ b/lua/tests/helpers.lua @@ -29,10 +29,10 @@ function M.init_component(component, opts) end -- Checks ouput of a component -M.assert_component = function(component, opts, result) +M.assert_component = function(component, opts, result, is_active) local comp = M.init_component(component, opts) -- for testing global options - eq(result, comp:draw(opts.hl)) + eq(result, comp:draw(opts.hl, is_active or true)) end function M.assert_component_instence(comp, result) @@ -49,7 +49,7 @@ M.build_component_opts = function(opts) end end if not opts.self then - opts.self = { section = 'lualine_c' } + opts.self = { section = 'c' } end if not opts.theme then opts.theme = 'gruvbox' diff --git a/lua/tests/spec/component_spec.lua b/lua/tests/spec/component_spec.lua index b1f60de..83cb81f 100644 --- a/lua/tests/spec/component_spec.lua +++ b/lua/tests/spec/component_spec.lua @@ -15,7 +15,7 @@ describe('Component:', function() local comp = require('lualine.components.special.function_component')(opts) -- correct for lualine_c eq('', comp.options.separator) - local opts2 = build_component_opts { self = { section = 'lualine_y' } } + local opts2 = build_component_opts { self = { section = 'y' } } local comp2 = require('lualine.components.special.function_component')(opts2) -- correct for lualine_u eq('', comp2.options.separator) @@ -40,7 +40,12 @@ describe('Component:', function() -- color highlight wan't in options when create_comp_hl was -- called so remove it before assert comp1.options.color_highlight = nil - assert.stub(hl.create_component_highlight_group).was_called_with(color, comp1.options.component_name, comp1.options) + assert.stub(hl.create_component_highlight_group).was_called_with( + color, + comp1.options.component_name, + comp1.options, + false + ) hl.create_component_highlight_group:revert() color = 'MyHl' local opts2 = build_component_opts { color = color } @@ -51,7 +56,12 @@ describe('Component:', function() -- color highlight wan't in options when create_comp_hl was -- called so remove it before assert comp2.options.color_highlight = nil - assert.stub(hl.create_component_highlight_group).was_called_with(color, comp2.options.component_name, comp2.options) + assert.stub(hl.create_component_highlight_group).was_called_with( + color, + comp2.options.component_name, + comp2.options, + false + ) hl.create_component_highlight_group:revert() end) @@ -186,7 +196,7 @@ describe('Component:', function() color = 'MyHl', } local comp = require('lualine.components.special.function_component')(opts) - local custom_link_hl_name = 'lualine_' .. comp.options.component_name .. '_no_mode' + local custom_link_hl_name = 'lualine_c_' .. comp.options.component_name eq('%#' .. custom_link_hl_name .. '#test', comp:draw(opts.hl)) local opts2 = build_component_opts { component_separators = { left = '', right = '' }, @@ -280,26 +290,36 @@ describe('Filetype component', function() return '*', 'test_highlight_group' end, } - + vim.g.actual_curwin = tostring(vim.api.nvim_get_current_win()) local hl = require('lualine.highlight') local utils = require('lualine.utils.utils') stub(hl, 'create_component_highlight_group') + stub(hl, 'format_highlight') stub(utils, 'extract_highlight_colors') - hl.create_component_highlight_group.returns('MyCompHl') + hl.create_component_highlight_group.returns { name = 'MyCompHl', no_mode = false, section = 'a' } + hl.format_highlight.returns('%#lualine_c_normal#') utils.extract_highlight_colors.returns('#000') local opts = build_component_opts { component_separators = { left = '', right = '' }, + hl = '%#lualine_c_normal#', padding = 0, colored = true, icon_only = false, } - assert_component('filetype', opts, '%#MyCompHl_normal#*%#lualine_c_normal# lua') + assert_component('filetype', opts, '%#MyCompHl_normal#*%#lualine_c_normal# lua%#lualine_c_normal#') assert.stub(utils.extract_highlight_colors).was_called_with('test_highlight_group', 'fg') - assert.stub(hl.create_component_highlight_group).was_called_with({ fg = '#000' }, 'test_highlight_group', opts) + assert.stub(hl.create_component_highlight_group).was_called_with( + { fg = '#000' }, + 'test_highlight_group', + opts, + false + ) hl.create_component_highlight_group:revert() + hl.format_highlight:revert() utils.extract_highlight_colors:revert() package.loaded['nvim-web-devicons'] = nil + vim.g.actual_curwin = nil end) it("Doesn't color when colored is false", function() diff --git a/lua/tests/spec/utils_spec.lua b/lua/tests/spec/utils_spec.lua index c5c7eeb..bcb2fce 100644 --- a/lua/tests/spec/utils_spec.lua +++ b/lua/tests/spec/utils_spec.lua @@ -5,6 +5,7 @@ local helpers = require('tests.helpers') local eq = assert.are.same local build_component_opts = helpers.build_component_opts +local stub = require('luassert.stub') describe('Utils', function() local utils = require('lualine.utils.utils') @@ -55,6 +56,10 @@ describe('Cterm genarator', function() end) describe('Section genarator', function() + local hl = require('lualine.highlight') + stub(hl, 'format_highlight') + hl.format_highlight.returns('%#lualine_c_normal#') + local sec = require('lualine.utils.section') it('can draw', function() local opts = build_component_opts { section_separators = { left = '', right = '' } } @@ -62,10 +67,18 @@ describe('Section genarator', function() require('lualine.components.special.function_component')(opts), require('lualine.components.special.function_component')(opts), } - eq('%#lualine_MySection_normal# test %#lualine_MySection_normal# test ', sec.draw_section(section, 'MySection')) + eq('%#lualine_c_normal# test %#lualine_c_normal# test ', sec.draw_section(section, 'c', true)) + + hl.format_highlight:revert() end) it('can remove separators from component with custom colors', function() + stub(hl, 'format_highlight') + stub(hl, 'get_lualine_hl') + hl.format_highlight.returns('%#lualine_MySection_normal#') + hl.get_lualine_hl.returns { fg = '#000000', bg = '#ffffff' } + + vim.g.actual_curwin = tostring(vim.api.nvim_get_current_win()) local opts = build_component_opts { section_separators = { left = '', right = '' } } local opts_colored = build_component_opts { color = 'MyColor' } local opts_colored2 = build_component_opts { @@ -82,20 +95,20 @@ describe('Section genarator', function() require('lualine.components.special.function_component')(opts_colored), require('lualine.components.special.function_component')(opts), } - local highlight_name2 = 'lualine_' .. section[2].options.component_name .. '_no_mode' + local highlight_name2 = 'lualine_c_' .. section[2].options.component_name -- Removes separator on string color eq( '%#lualine_MySection_normal# test %#' .. highlight_name2 .. '#' .. ' test %#lualine_MySection_normal# test ', sec.draw_section(section, 'MySection') ) - section[2] = require('lua.lualine.components.special.function_component')(opts_colored2) + section[2] = require('lualine.components.special.function_component')(opts_colored2) local highlight_name = '%#lualine_c_' .. section[2].options.component_name .. '_normal#' -- Removes separator on color with bg eq( '%#lualine_MySection_normal# test ' .. highlight_name .. ' test %#lualine_MySection_normal# test ', sec.draw_section(section, 'MySection') ) - section[2] = require('lua.lualine.components.special.function_component')(opts_colored3) + section[2] = require('lualine.components.special.function_component')(opts_colored3) highlight_name2 = '%#lualine_c_' .. section[2].options.component_name .. '_normal#' -- Doesn't remove separator on color without bg eq( @@ -104,5 +117,9 @@ describe('Section genarator', function() .. ' test %#lualine_MySection_normal#%#lualine_MySection_normal# test ', sec.draw_section(section, 'MySection') ) + vim.g.actual_curwin = nil + + hl.format_highlight:revert() + hl.get_lualine_hl:revert() end) end)