diff --git a/README.md b/README.md index 3e80b85..fe3d1a2 100644 --- a/README.md +++ b/README.md @@ -86,17 +86,19 @@ All available themes are listed in [THEMES.md](./THEMES.md) Please create a pr if you managed to port a popular theme before me, [here is how to do it](./CONTRIBUTING.md). ### Changing separator in section -Lualine defines a separator between components in given section, the default -separator is `|`. You can change the separator this way: +Lualine defines two kinds of seperators. One is for sections and other is for components. Default section seperators are '', '' and component separators are '', ''. +They require powerline patched fonts. But you can easily change yours to something else like below ```lua -lualine.options.separator = '|' +lualine.section_separators = {'', ''} +lualine.component_separators = {'', ''} ``` or disable it ```lua -lualine.options.separator = '' +lualine.section_separators = nil +lualine.component_separators = nil ``` ### Changing components in lualine sections @@ -304,7 +306,8 @@ All available extensions are listed in [EXTENSIONS.md](./EXTENSIONS.md) local lualine = require('lualine') lualine.options = { theme = 'gruvbox', - separator = '|', + section_separators = {'', ''}, + component_separators = {'', ''}, icons_enabled = true, } lualine.sections = { @@ -341,7 +344,8 @@ lua << EOF local lualine = require('lualine') lualine.options = { theme = 'gruvbox', - separator = '|', + section_separators = {'', ''}, + component_separators = {'', ''}, icons_enabled = true, } lualine.sections = { diff --git a/doc/lualine.txt b/doc/lualine.txt index 40d2e9d..de47604 100644 --- a/doc/lualine.txt +++ b/doc/lualine.txt @@ -113,15 +113,19 @@ how to do it (./CONTRIBUTING.md). CHANGING SEPARATOR IN SECTION *lualine_changing_separator* -Lualine defines a separator between components in given section, the default -separator is `|`. You can change the separator this way: +Lualine defines two kinds of seperators. One is for sections and other is +for components. Default section seperators are '', '' and component +separators are '', ''.They require powerline patched fonts. But you can +easily change yours to something else like below. > - lualine.options.separator = '|' + lualine.section_separators = {'', ''} + lualine.component_separators = {'', ''} < or disable it > - lualine.options.separator = '' + lualine.section_separators = nil + lualine.component_separators = nil < CHANGING COMPONENTS IN LUALINE SECTIONS *lualine_changing_components* @@ -349,7 +353,8 @@ packer config local lualine = require('lualine') lualine.options = { theme = 'gruvbox', - separator = '|', + section_separators = {'', ''}, + component_separators = {'', ''}, icons_enabled = true, } lualine.sections = { @@ -385,9 +390,10 @@ vimrc config local lualine = require('lualine') lualine.options = { theme = 'gruvbox', - separator = '|', - icons_enabled = true, - } + section_separators = {'', ''}, + component_separators = {'', ''}, + icons_enabled = true, + } lualine.sections = { lualine_a = { 'mode' }, lualine_b = { 'branch' }, diff --git a/lua/lualine.lua b/lua/lualine.lua index 6a3a6db..75a832c 100644 --- a/lua/lualine.lua +++ b/lua/lualine.lua @@ -12,9 +12,11 @@ local theme_set = {} M.options = { icons_enabled = true, theme = 'gruvbox', - separator = '|', + component_separators = {'', ''}, + section_separators = {'', ''}, } + M.sections = { lualine_a = { 'mode' }, lualine_b = { 'branch' }, @@ -36,6 +38,17 @@ M.inactive_sections = { M.extensions = { } +local function check_single_separator() + local compoennt_separator = M.options.component_separators + local section_separator = M.options.section_separators + if type(M.options.component_separators) == 'string' then + M.options.component_separators = {compoennt_separator, compoennt_separator} + end + if type(M.options.section_separators) == 'string' then + M.options.section_separators = {section_separator, section_separator} + end +end + local function load_special_components(component) return function() -- precedence lualine_component > vim_var > lua_var > vim_function @@ -56,12 +69,12 @@ local function load_special_components(component) if ok then return return_val end return '' elseif loadstring(string.format('return %s ~= nil', component)) and - loadstring(string.format([[return %s ~= nil]], component))() then + loadstring(string.format([[return %s ~= nil]], component))() then -- lua veriable component return loadstring(string.format([[ - local ok, return_val = pcall(tostring, %s) - if ok then return return_val end - return '']], component))() + local ok, return_val = pcall(tostring, %s) + if ok then return return_val end + return '']], component))() else -- vim function component local ok, return_val = pcall(vim.fn[component]) @@ -138,43 +151,91 @@ local function load_extensions() end end -local function set_lualine_theme() +local function lualine_set_theme() if type(M.options.theme) == 'string' then M.options.theme = require('lualine.themes.'.. M.options.theme) + local function reset_component_theme(sections) + for _, section in pairs(sections)do + for _, component in pairs(section) do + if type(component) == 'table' then + component.theme = M.options.theme + end + end + end + end + reset_component_theme(M.sections) + reset_component_theme(M.inactive_sections) end + highlight.clear_highlights() highlight.create_highlight_groups(M.options.theme) theme_set = M.options.theme end local function statusline(sections, is_focused) - if M.theme ~= theme_set then - set_lualine_theme() + if M.options.theme ~= theme_set then + _G.lualine_set_theme() end + local function create_status_builder() + -- The sequence sections should maintain + local section_sequence = {'a', 'b', 'c', 'x', 'y', 'z'} + local status_builder = {} + for _, section_name in ipairs(section_sequence) do + if sections['lualine_'..section_name] then + -- insert highlight+components of this section to status_builder + local section_highlight = highlight.format_highlight(is_focused, + 'lualine_'..section_name) + local section_data = utils_component.draw_section(sections['lualine_'..section_name], section_highlight) + if #section_data > 0 then + table.insert(status_builder, {name = section_name, data = section_data,}) + end + end + end + return status_builder + end + -- status_builder stores statusline without section_separators + local status_builder = create_status_builder() + + -- Actual statusline local status = {} - if sections.lualine_a then - local hl = highlight.format_highlight(is_focused, 'lualine_a') - table.insert(status, utils_component.draw_section(sections.lualine_a, hl)) + local half_passed = false + for i=1,#status_builder do + -- midsection divider + if not half_passed and status_builder[i].name > 'c' then + table.insert(status, highlight.format_highlight(is_focused, 'lualine_c') .. '%=') + half_passed = true + end + -- provide section_separators when statusline is in focus + if is_focused then + -- component separator needs to have fg = current_section.bg + -- and bg = adjacent_section.bg + local previous_section = status_builder[i-1] or {} + local current_section = status_builder[i] + local next_section = status_builder[i+1] or {} + -- For 2nd half we need to show separator before section + if current_section.name > 'x' then + local transitional_highlight = highlight.get_transitional_highlights(previous_section.data, current_section.data, true) + if transitional_highlight and M.options.section_separators and M.options.section_separators[2] then + table.insert(status, transitional_highlight .. M.options.section_separators[2]) + end + end + + -- **( insert the actual section in the middle )** -- + table.insert(status, status_builder[i].data) + + -- For 1st half we need to show separator after section + if current_section.name < 'c' then + local transitional_highlight = highlight.get_transitional_highlights(current_section.data, next_section.data) + if transitional_highlight and M.options.section_separators and M.options.section_separators[1] then + table.insert(status, transitional_highlight .. M.options.section_separators[1]) + end + end + else -- when not in focus + table.insert(status, status_builder[i].data) + end end - if sections.lualine_b then - local hl = highlight.format_highlight(is_focused, 'lualine_b') - table.insert(status, utils_component.draw_section(sections.lualine_b, hl)) - end - if sections.lualine_c then - local hl = highlight.format_highlight(is_focused, 'lualine_c') - table.insert(status, utils_component.draw_section(sections.lualine_c, hl)) - end - table.insert(status, "%=") - if sections.lualine_x then - local hl = highlight.format_highlight(is_focused, 'lualine_c') - table.insert(status, utils_component.draw_section(sections.lualine_x, hl)) - end - if sections.lualine_y then - local hl = highlight.format_highlight(is_focused, 'lualine_b') - table.insert(status, utils_component.draw_section(sections.lualine_y, hl)) - end - if sections.lualine_z then - local hl = highlight.format_highlight(is_focused, 'lualine_a') - table.insert(status, utils_component.draw_section(sections.lualine_z, hl)) + -- incase none of x,y,z was configured lets not fill whole statusline with a,b,c section + if not half_passed then + table.insert(status, highlight.format_highlight(is_focused,'lualine_c').."%=") end return table.concat(status) end @@ -188,20 +249,21 @@ local function status_dispatch() end local function exec_autocommands() - _G.set_lualine_theme = set_lualine_theme + _G.lualine_set_theme = lualine_set_theme _G.lualine_statusline = status_dispatch vim.api.nvim_exec([[ - augroup lualine - autocmd! - autocmd ColorScheme * call v:lua.set_lualine_theme() - autocmd WinLeave,BufLeave * lua vim.wo.statusline=lualine_statusline() - autocmd WinEnter,BufEnter * setlocal statusline=%!v:lua.lualine_statusline() - augroup END + augroup lualine + autocmd! + autocmd ColorScheme * call v:lua.lualine_set_theme() + autocmd WinLeave,BufLeave * lua vim.wo.statusline=lualine_statusline() + autocmd WinEnter,BufEnter * setlocal statusline=%!v:lua.lualine_statusline() + augroup END ]], false) end function M.status() - set_lualine_theme() + check_single_separator() + lualine_set_theme() exec_autocommands() load_components() load_extensions() diff --git a/lua/lualine/components/signify.lua b/lua/lualine/components/signify.lua index 070f790..71c31b7 100644 --- a/lua/lualine/components/signify.lua +++ b/lua/lualine/components/signify.lua @@ -12,13 +12,13 @@ local function signify(options) if options.colored == nil then options.colored = true end -- apply colors if not options.color_added then - options.color_added = utils.extract_highlight_colors('diffAdded', 'foreground') or default_color_added + options.color_added = utils.extract_highlight_colors('diffAdded', 'guifg') or default_color_added end if not options.color_modified then - options.color_modified = utils.extract_highlight_colors('diffChanged', 'foreground') or default_color_modified + options.color_modified = utils.extract_highlight_colors('diffChanged', 'guifg') or default_color_modified end if not options.color_removed then - options.color_removed = utils.extract_highlight_colors('diffRemoved', 'foreground') or default_color_removed + options.color_removed = utils.extract_highlight_colors('diffRemoved', 'guifg') or default_color_removed end local highlights = {} @@ -36,7 +36,6 @@ local function signify(options) if options.colored then create_highlights() utils.expand_set_theme(create_highlights) - options.custom_highlight = true end -- Function that runs everytime statusline is updated diff --git a/lua/lualine/highlight.lua b/lua/lualine/highlight.lua index b24130f..256cfda 100644 --- a/lua/lualine/highlight.lua +++ b/lua/lualine/highlight.lua @@ -3,18 +3,38 @@ local M = { } local utils_colors = require "lualine.utils.cterm_colors" +local utils = require 'lualine.utils.utils' +local section_highlight_map = {x = 'c', y = 'b', z = 'a'} +local loaded_highlights = {} + +-- @description: clears Highlights in loaded_highlights +function M.clear_highlights() + for no, highlight_name in ipairs(loaded_highlights)do + if highlight_name and #highlight_name > 0 and utils.highlight_exists(highlight_name) then + vim.cmd('highlight clear ' .. highlight_name) + end + loaded_highlights[no] = nil + end +end local function highlight (name, foreground, background, gui) - local command = { - 'highlight', name, - 'ctermfg=' .. (foreground[2] or utils_colors.get_cterm_color(foreground)), - 'ctermbg=' .. (background[2] or utils_colors.get_cterm_color(background)), - 'cterm=' .. (gui or 'none'), - 'guifg=' .. (foreground[1] or foreground), - 'guibg=' .. (background[1] or background), - 'gui=' .. (gui or 'none'), - } - return table.concat(command, ' ') + local command = { 'highlight', name, } + if foreground then + table.insert(command, 'ctermfg=' .. (foreground[2] or + (foreground ~= 'none' and utils_colors.get_cterm_color(foreground)) or 'none')) + table.insert(command, 'guifg=' .. (foreground[1] or foreground)) + end + if background then + table.insert(command, 'ctermbg=' .. (background[2] or + (background ~= 'none' and utils_colors.get_cterm_color(background)) or 'none')) + table.insert(command, 'guibg=' .. (background[1] or background)) + end + if gui then + table.insert(command, 'cterm=' .. (gui or 'none')) + table.insert(command, 'gui=' .. (gui or 'none')) + end + vim.cmd(table.concat(command, ' ')) + table.insert(loaded_highlights, name) end local function apply_defaults_to_theme(theme) @@ -36,11 +56,14 @@ function M.create_highlight_groups(theme) for mode, sections in pairs(theme) do for section, colorscheme in pairs(sections) do local highlight_group_name = { 'lualine', section, mode } - vim.cmd(highlight(table.concat(highlight_group_name, '_'), colorscheme.fg, colorscheme.bg, colorscheme.gui)) + highlight(table.concat(highlight_group_name, '_'), colorscheme.fg, colorscheme.bg, colorscheme.gui) end end end +-- @description: adds '_mode' at end of highlight_group +-- @param highlight_group:(string) name of highlight group +-- @return: (string) highlight group name with mode local function append_mode(highlight_group) local mode = require('lualine.components.mode')() if mode == 'VISUAL' or mode == 'V-BLOCK' or mode == 'V-LINE' @@ -68,51 +91,138 @@ end -- @@highlight_tag is unique tag for highlight group -- returns the name of highlight group -- @@options is parameter of component.init() function +-- @return: (string) unique name that can be used by component_format_highlight +-- to retrive highlight group function M.create_component_highlight_group(color , highlight_tag, options) if 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 + -- 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'}, '_') - vim.cmd(highlight(highlight_group_name, color.fg, color.bg, color.gui)) + highlight(highlight_group_name, color.fg, color.bg, color.gui) return highlight_group_name end local modes = {'normal', 'insert', 'visual', 'replace', 'command', 'terminal', 'inactive'} + local normal_hl + -- convert lualine_a -> a before setting section + local section = options.self.section:match('lualine_(.*)') + if section > 'c' then + section = section_highlight_map[section] + end for _, mode in ipairs(modes) do local highlight_group_name = { options.self.section, highlight_tag, mode } - -- convert lualine_a -> a before setting section - local section = options.self.section:match('lualine_(.*)') - local bg = (color.bg or options.theme[mode][section]['bg']) - local fg = (color.fg or options.theme[mode][section]['fg']) - vim.cmd(highlight(table.concat(highlight_group_name, '_'), fg, bg, color.gui)) + local default_color_table = options.theme[mode] and + options.theme[mode][section] or options.theme.normal[section] + local bg = (color.bg or default_color_table.bg) + local fg = (color.fg or 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 and fg ~= normal_hl.fg then + highlight(table.concat(highlight_group_name, '_'), fg, bg, color.gui) + end + else + normal_hl = {bg = bg, fg = fg} + highlight(table.concat(highlight_group_name, '_'), fg, bg, color.gui) + end end return options.self.section..'_'..highlight_tag end --- retrieve highlight_groups for components --- @@highlight_name received from create_component_highlight_group +-- @description: retrieve highlight_groups for components +-- @param highlight_name:(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 +-- @return: (string) formated highlight group name function M.component_format_highlight(highlight_name) - local highlight_group = [[%#]]..highlight_name + local highlight_group = highlight_name if highlight_name:find('no_mode') == #highlight_name - #'no_mode' + 1 then - return highlight_group..'#' + return '%#'..highlight_group..'#' end if vim.g.statusline_winid == vim.fn.win_getid() then - highlight_group = append_mode(highlight_group)..'#' + highlight_group = append_mode(highlight_group) else - highlight_group = highlight_group..'_inactive'..'#' + highlight_group = highlight_group..'_inactive' + end + if utils.highlight_exists(highlight_group)then + return '%#'..highlight_group..'#' + else + return '%#'..highlight_name..'_normal#' end - return highlight_group end function M.format_highlight(is_focused, highlight_group) - highlight_group = [[%#]] .. highlight_group - if not is_focused then - highlight_group = highlight_group .. [[_inactive]] - else - highlight_group = append_mode(highlight_group) + if highlight_group > 'lualine_c' then + highlight_group = 'lualine_' .. section_highlight_map[highlight_group:match('lualine_(.)')] end - highlight_group = highlight_group .. [[#]] - return highlight_group + local highlight_name = highlight_group + if not is_focused then + return '%#' .. highlight_group .. [[_inactive#]] + else + highlight_name = append_mode(highlight_group) + end + return '%#' .. highlight_name ..'#' +end + +-- @description : Provides transitional highlights for section separators. +-- @param left_section_data :(string) section before separator +-- @param right_section_data:(string) section after separator +-- @param reverse :(string) Whether it's a left separator or right separator +-- '▶️' and '◀️' needs reverse colors so this parameter needs to be set true. +-- @return: (string) formated highlight group name +function M.get_transitional_highlights(left_section_data, right_section_data, reverse ) + local left_highlight_name, right_highlight_name + -- Grab the last highlighter of left section + if left_section_data then + -- extract highlight_name from .....%#highlight_name# + left_highlight_name = left_section_data:match('.*%%#(.*)#') + else + -- When right section us unavailable default to lualine_c + left_highlight_name = append_mode('lualine_c') + if not utils.highlight_exists(left_highlight_name) then + left_highlight_name = 'lualine_c_normal' + end + end + if right_section_data then + -- using vim-regex cause lua-paterns don't have non-greedy matching + -- extract highlight_name from %#highlight_name#.... + right_highlight_name = vim.fn.matchlist(right_section_data, [[%#\(.\{-\}\)#]])[2] + else + -- When right section us unavailable default to lualine_c + right_highlight_name = append_mode('lualine_c') + if not utils.highlight_exists(right_highlight_name) then + right_highlight_name = 'lualine_c_normal' + end + end + -- When both left and right highlights are same nothing to transition to + if left_highlight_name == right_highlight_name then return end + + -- construct the name of hightlight group + local highlight_name + if left_highlight_name:find('lualine_') == 1 then + highlight_name = left_highlight_name .. '_to_' .. right_highlight_name + else + highlight_name = 'lualine_' .. left_highlight_name .. '_to_' .. right_highlight_name + end + + if not utils.highlight_exists(highlight_name) then + -- Create the highlight_group if needed + local function set_transitional_highlights() + -- Get colors from highlights + -- using string.format to convert decimal to hexadecimal + local fg = utils.extract_highlight_colors(left_highlight_name, 'guibg') + local bg = utils.extract_highlight_colors(right_highlight_name, 'guibg') + if not fg then fg = 'none' end + if not bg then bg = 'none' end + -- swap the bg and fg when reverse is true. As in that case highlight will + -- be placed before section + if reverse then fg, bg = bg, fg end + highlight(highlight_name, fg, bg) + end + -- Create highlights and setup to survive colorscheme changes + set_transitional_highlights() + utils.expand_set_theme(set_transitional_highlights) + end + return '%#' .. highlight_name .. '#' end return M diff --git a/lua/lualine/utils/component.lua b/lua/lualine/utils/component.lua index 8afd5cf..3815df6 100644 --- a/lua/lualine/utils/component.lua +++ b/lua/lualine/utils/component.lua @@ -44,51 +44,76 @@ end -- Apply separator at end of component only when -- custom highlights haven't affected background -local function apply_spearator(status, options) - if options.separator and #options.separator > 0 and - (not options.color or not options.color.bg) then - status = status .. options.separator - options.separator_applied = true +local function apply_spearator(status, options, highlight_name) + local separator + if options.separator and #options.separator > 0 then + separator = options.separator + elseif options.component_separators then + if options.self.section < 'lualine_x' then separator = options.component_separators[1] + else separator = options.component_separators[2] end + if not separator then + options.separator_applied = nil + return status + end + options.separator = separator + else + options.separator_applied = nil + return status + end + separator = highlight_name .. separator + status = status .. separator + options.separator_applied = separator + return status +end + +local function strip_separator(status, options) + if options.separator_applied then + status = status:sub(1, #status - #options.separator_applied) + options.separator_applied = nil end return status end -- Returns formated string for a section -function M.draw_section(section, highlight) +function M.draw_section(section, highlight_name) local status = {} + local drawn_components = {} for _, component in pairs(section) do - -- Reset flags - component.drawn = false -- Flag to check if a component was drawn or not - component.separator_applied = false -- Flag to check if separator was applied local localstatus = component[1]() if #localstatus > 0 then + local custom_highlight_at_begining = localstatus:find('%%#.*#') == 1 or component.color ~= nil -- Apply modifier functions for options if component.format then localstatus = component.format(localstatus) end localstatus = apply_icon(localstatus, component) localstatus = apply_case(localstatus, component) localstatus = apply_padding(localstatus, component) localstatus = apply_highlights(localstatus, component) - localstatus = apply_spearator(localstatus, component) - table.insert(status, localstatus) - component.drawn = true + localstatus = apply_spearator(localstatus, component, highlight_name) + if custom_highlight_at_begining or + (#drawn_components > 0 and not drawn_components[#drawn_components].separator_applied)then + -- Don't prepend with old highlight when the component changes it imidiately + -- Or when it was already applied with separator + table.insert(status, localstatus) + else + table.insert(status, highlight_name .. localstatus) + end + table.insert(drawn_components, component) end end -- Draw nothing when all the components were empty if #status == 0 then return '' end - -- convert to single string - -- highlight is used as separator so custom highlights don't affect - -- later components - local status_str = highlight .. table.concat(status, highlight) - -- Remove separator from last drawn component if available - for last_component = #section, 1, -1 do - if section[last_component].drawn then - if section[last_component].separator_applied then - status_str = status_str:sub(1, #status_str - #section[last_component].separator) + -- Remove separators sorounding custom highlighted component + for i=1,#status do + if (drawn_components[i].color and drawn_components[i].color.bg) + or drawn_components[i].custom_highlight then + status[i] = strip_separator(status[i], drawn_components[i]) + if i > 1 then + status[i - 1] = strip_separator(status[i - 1], drawn_components[i - 1]) end - break end end - return status_str + status[#status] = strip_separator(status[#status], drawn_components[#status]) + return table.concat(status) end diff --git a/lua/lualine/utils/utils.lua b/lua/lualine/utils/utils.lua index 610c7b3..7c1a8a4 100644 --- a/lua/lualine/utils/utils.lua +++ b/lua/lualine/utils/utils.lua @@ -7,8 +7,8 @@ local M = {} -- functionality at runtime . function M.expand_set_theme(func) -- execute a local version of global function to not get in a inf recurtion - local set_theme = _G.set_lualine_theme - _G.set_lualine_theme = function() + local set_theme = _G.lualine_set_theme + _G.lualine_set_theme = function() set_theme() func() end @@ -16,8 +16,33 @@ end -- Note for now only works for termguicolors scope can be background or foreground function M.extract_highlight_colors(color_group, scope) - local color = string.format('#%06x', vim.api.nvim_get_hl_by_name(color_group, true)[scope]) - return color or nil + if not M.highlight_exists(color_group) then return nil end + local gui_colors = vim.api.nvim_get_hl_by_name(color_group, true) + local cterm_colors = vim.api.nvim_get_hl_by_name(color_group, false) + local color = { + ctermfg = cterm_colors.foreground, + ctermbg = cterm_colors.background, + } + if gui_colors.background then + color.guibg = string.format('#%06x', gui_colors.background) + gui_colors.background = nil + end + if gui_colors.foreground then + color.guifg = string.format('#%06x', gui_colors.foreground) + gui_colors.foreground = nil + end + cterm_colors.background = nil + cterm_colors.foreground = nil + color = vim.tbl_extend('keep', color, gui_colors, cterm_colors) + if scope then return color[scope] end + return color +end + +-- determine if an highlight exist and isn't cleared +function M.highlight_exists(highlight_name) + local ok, result = pcall(vim.api.nvim_exec, 'highlight '..highlight_name, true) + if not ok then return false end + return result:find('xxx cleared') == nil end return M