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 <shadmansaleh@users.noreply.github.com>
This commit is contained in:
Shadman 2022-03-02 13:37:08 +00:00 committed by GitHub
parent 016a20711e
commit 37a314b9e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 425 additions and 159 deletions

View File

@ -31,7 +31,7 @@ endif
@rm -rf tmp_home
docgen:
@bash ./scripts/docgen.sh
@sh ./scripts/docgen.sh
precommit_check: docgen format test lint

View File

@ -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.

View File

@ -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.

View File

@ -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 },
}

View File

@ -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)

View File

@ -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 '')

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)
table.insert(command, 'guibg=' .. background)
table.insert(command, 'gui=' .. gui)
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, '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(default_color) == 'table' then
return default_color[kind]
elseif type(default_color) == 'string' then
return modules.utils.extract_highlight_colors(default_color, kind)
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
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)
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
normal_hl = { bg = bg, fg = fg }
M.highlight(table.concat(highlight_group_name, '_'), fg, bg, color.gui, nil)
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
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 = M.append_mode(highlight_group)
if M.highlight_exists(highlight_group) then
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

View File

@ -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)

View File

@ -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()

View File

@ -9,10 +9,12 @@ 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)
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
local color = vim.api.nvim_get_hl_by_name(color_group, true)
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
@ -21,6 +23,7 @@ function M.extract_highlight_colors(color_group, scope)
color.fg = string.format('#%06x', color.foreground)
color.foreground = nil
end
end
if scope then
return color[scope]
end

View File

@ -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'

View File

@ -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()

View File

@ -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)