diff --git a/README.md b/README.md index 1e7d0c7..781c255 100644 --- a/README.md +++ b/README.md @@ -264,7 +264,13 @@ sections = { { 'mode', icon = nil, -- displays icon in front of the component - separator = nil, -- overwrites component_separators for component + separator = nil, -- Determines what separator to use for the component. + -- when a string is given it's treated as component_separator. + -- When a table is given it's treated as section_separator. + -- This options can be used to set colored separators + -- arround component. Option need to be set like `separator = {'', ''}`. + -- Where first element is left_separator and 2nd element is right separator. + -- Passing empty string disables that separator condition = nil, -- condition function, component is loaded when function returns true -- custom color for component in format -- color = {fg = '#rrggbb', bg= '#rrggbb', gui='style'} diff --git a/doc/lualine.txt b/doc/lualine.txt index 6132ddf..4834fd4 100644 --- a/doc/lualine.txt +++ b/doc/lualine.txt @@ -203,7 +203,13 @@ Local options~ { 'mode', icon = nil, -- displays icon in front of the component - separator = nil, -- overwrites component_separators for component + separator = nil, -- Determines what separator to use for the component. + -- when a string is given it's treated as component_separator. + -- When a table is given it's treated as section_separator. + -- This options can be used to set colored separators + -- arround component. Option need to be set like `separator = {'', ''}`. + -- Where first element is left_separator and 2nd element is right separator. + -- Passing empty string disables that separator condition = nil, -- condition function, component is loaded when function returns true -- custom color for component in format -- color = {fg = '#rrggbb', bg= '#rrggbb', gui='style'} diff --git a/lua/lualine/component.lua b/lua/lualine/component.lua index a7877c1..22cdd8f 100644 --- a/lua/lualine/component.lua +++ b/lua/lualine/component.lua @@ -1,3 +1,5 @@ +-- Copyright (c) 2020-2021 shadmansaleh +-- MIT license, see LICENSE for more details. local highlight = require 'lualine.highlight' -- Used to provide a unique id for each component @@ -24,7 +26,7 @@ local Component = { end, set_separator = function(self) - if type(self.options.separator) ~= 'string' then + if self.options.separator == nil then if self.options.component_separators then if self.options.self.section < 'lualine_x' then self.options.separator = self.options.component_separators[1] @@ -99,18 +101,41 @@ local Component = { -- Apply separator at end of component only when -- custom highlights haven't affected background apply_separator = function(self) - if self.options.separator and #self.options.separator > 0 then - self.status = self.status .. self.options.separator - self.applied_separator = self.options.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 + separator = self.options.component_separators[1] + else + separator = self.options.component_separators[2] + end + else + return + end + end + if separator and #separator > 0 then + self.status = self.status .. separator + self.applied_separator = separator end end, - strip_separator = function(self, default_highlight) - if not default_highlight then default_highlight = '' end + apply_section_separators = function(self) + if type(self.options.separator) ~= 'table' then return end + if self.options.separator[1] ~= '' then + self.status = string.format('%%s{%s}%s', self.options.separator[1], + self.status) + self.strip_previous_separator = true + end + if self.options.separator[2] ~= '' then + self.status = string.format('%s%%S{%s}', self.status, + self.options.separator[2]) + end + end, + + strip_separator = function(self) if not self.applied_separator then self.applied_separator = '' end self.status = self.status:sub(1, (#self.status - - (#self.applied_separator + - #default_highlight))) + (#self.applied_separator))) self.applied_separator = nil return self.status end, @@ -135,6 +160,7 @@ local Component = { self:apply_icon() self:apply_case() self:apply_padding() + self:apply_section_separators() self:apply_highlights(default_highlight) self:apply_separator() end diff --git a/lua/lualine/highlight.lua b/lua/lualine/highlight.lua index 3d63a9f..bd5fdaa 100644 --- a/lua/lualine/highlight.lua +++ b/lua/lualine/highlight.lua @@ -170,7 +170,7 @@ function M.get_transitional_highlights(left_section_data, right_section_data, -- 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('.*%%#(.-)#') + left_highlight_name = left_section_data:match('.*%%#(.-)#.-') else -- When right section us unavailable default to lualine_c left_highlight_name = append_mode('lualine_c') @@ -180,7 +180,7 @@ function M.get_transitional_highlights(left_section_data, right_section_data, end if right_section_data then -- extract highlight_name from %#highlight_name#.... - right_highlight_name = right_section_data:match('%%#(.-)#.*') + right_highlight_name = right_section_data:match('.-%%#(.-)#.*') else -- When right section us unavailable default to lualine_c right_highlight_name = append_mode('lualine_c') @@ -210,6 +210,7 @@ function M.get_transitional_highlights(left_section_data, right_section_data, -- be placed before section if reverse then fg, bg = bg, fg end if not fg or not bg then return '' end -- Color retrieval failed + if bg == fg then return '' end -- Separatoe won't be visible anyway M.highlight(highlight_name, fg, bg) end return '%#' .. highlight_name .. '#' diff --git a/lua/lualine/init.lua b/lua/lualine/init.lua index 10c252d..5253590 100644 --- a/lua/lualine/init.lua +++ b/lua/lualine/init.lua @@ -7,30 +7,97 @@ local config_module = require('lualine.config') local config = config_module.config +local function apply_transitional_separators(previous_section, current_section, + next_section) + + local function fill_section_separator(prev, next, sep, reverse) + if #sep == 0 then return 0 end + local transitional_highlight = highlight.get_transitional_highlights(prev, + next, + reverse) + if transitional_highlight and #transitional_highlight > 0 then + return transitional_highlight .. sep + else + return '' + end + end + + -- variable to track separators position + local sep_pos = 1 + + -- %s{sep} is marker for left separator and + -- %S{sep} is marker for right separator + -- Apply left separator + while sep_pos do + -- get what the separator char + local sep = current_section.data:match('%%s{(.-)}', sep_pos) + -- Get where separator starts from + sep_pos = current_section.data:find('%%s{.-}', sep_pos) + if not sep or not sep_pos then break end + -- part of section before separator . -1 since we don't want the % + local prev = current_section.data:sub(1, sep_pos - 1) + -- part of section after separator. 4 is length of "%s{}" + local nxt = current_section.data:sub(sep_pos + 4 + #sep) + -- prev might not exist when separator is the first element of section + -- use previous section as prev + if not prev or #prev == 0 or sep_pos == 1 then + prev = previous_section.data + end + if prev ~= previous_section.data then + -- Since the section isn't suppose to be highlighted with separators + -- separators highlight extract the last highlight and place it between + -- separator and section + local last_hl = prev:match('.*(%%#.-#).-') + current_section.data = prev .. + fill_section_separator(prev, nxt, sep, false) .. + last_hl .. nxt + else + current_section.data = fill_section_separator(prev, nxt, sep, true) .. nxt + end + end + + -- Reset pos for right separator + sep_pos = 1 + -- Apply right separator + while sep_pos do + local sep = current_section.data:match('%%S{(.-)}', sep_pos) + sep_pos = current_section.data:find('%%S{.-}', sep_pos) + if not sep or not sep_pos then break end + local prev = current_section.data:sub(1, sep_pos - 1) + local nxt = current_section.data:sub(sep_pos + 4 + #sep) + if not nxt or #nxt == 0 or sep_pos == #current_section.data then + nxt = next_section.data + end + if nxt ~= next_section.data then + current_section.data = prev .. + fill_section_separator(prev, nxt, sep, false) .. + nxt + else + current_section.data = prev .. + fill_section_separator(prev, nxt, sep, false) + end + sep_pos = sep_pos + 4 + #sep + end + return current_section.data +end + local function statusline(sections, is_focused) - 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_section.draw_section( - sections['lualine_' .. section_name], - section_highlight) - if #section_data > 0 then - table.insert(status_builder, - {name = section_name, data = section_data}) - end + + -- status_builder stores statusline without section_separators + -- 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_data = utils_section.draw_section( + sections['lualine_' .. section_name], + section_name, is_focused) + if #section_data > 0 then + table.insert(status_builder, {name = section_name, data = section_data}) end end - return status_builder end - -- status_builder stores statusline without section_separators - local status_builder = create_status_builder() -- Actual statusline local status = {} @@ -42,44 +109,16 @@ local function statusline(sections, is_focused) 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' and config.options.section_separators[2] ~= - '' then - local transitional_highlight = highlight.get_transitional_highlights( - previous_section.data, - current_section.data, true) - if transitional_highlight and config.options.section_separators and - config.options.section_separators[2] then - table.insert(status, transitional_highlight .. - config.options.section_separators[2]) - end - end + -- 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 {} - -- **( insert the actual section in the middle )** -- - table.insert(status, status_builder[i].data) + local section = apply_transitional_separators(previous_section, + current_section, next_section) - -- For 1st half we need to show separator after section - if current_section.name < 'c' and config.options.section_separators[1] ~= - '' then - local transitional_highlight = highlight.get_transitional_highlights( - current_section.data, - next_section.data) - if transitional_highlight and config.options.section_separators and - config.options.section_separators[1] then - table.insert(status, transitional_highlight .. - config.options.section_separators[1]) - end - end - else -- when not in focus - table.insert(status, status_builder[i].data) - end + table.insert(status, section) end -- incase none of x,y,z was configured lets not fill whole statusline with a,b,c section if not half_passed then diff --git a/lua/lualine/utils/section.lua b/lua/lualine/utils/section.lua index f8a12f8..4231268 100644 --- a/lua/lualine/utils/section.lua +++ b/lua/lualine/utils/section.lua @@ -2,8 +2,12 @@ -- MIT license, see LICENSE for more details. local M = {} local utils = require('lualine.utils.utils') +local highlight = require('lualine.highlight') -- Returns formated string for a section -function M.draw_section(section, highlight_name) +function M.draw_section(section, section_name, is_focused) + local highlight_name = highlight.format_highlight(is_focused, + 'lualine_' .. section_name) + local status = {} for _, component in pairs(section) do -- load components into status table @@ -15,29 +19,51 @@ function M.draw_section(section, highlight_name) end -- Flags required for knowing when to remove component separator - local next_component_colored = false + local strip_next_component = false local last_component_found = false + local first_component_no = #section -- Check through components to see when component separator need to be removed for component_no = #section, 1, -1 do + if #status[component_no] > 0 then first_component_no = component_no end -- Remove component separator with highlight for last component if not last_component_found and #status[component_no] > 0 then last_component_found = true - status[component_no] = section[component_no]:strip_separator( - highlight_name) + status[component_no] = section[component_no]:strip_separator() + if section_name < 'c' then + if type(section[first_component_no].options.separator) ~= 'table' and + section[1].options.section_separators[1] ~= '' then + status[component_no] = string.format('%s%%S{%s}', + status[component_no], section[1] + .options.section_separators[1]) + end + end end -- Remove component separator when color option is used in next component - if next_component_colored then - next_component_colored = false + if strip_next_component then + strip_next_component = false status[component_no] = section[component_no]:strip_separator() 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) or type(section[component_no].options.color) == 'string' then - next_component_colored = true + strip_next_component = true status[component_no] = section[component_no]:strip_separator() end + + if (section[component_no].strip_previous_separator == true) then + strip_next_component = true + end + end + + local left_sparator_string = '' + if section_name > 'x' and section[first_component_no] and + type(section[first_component_no].options.separator) ~= 'table' and + section[1].options.section_separators[2] ~= '' then + left_sparator_string = string.format('%%s{%s}', + section[first_component_no].options.ls_separator or + section[1].options.section_separators[2]) end -- Remove empty strings from status @@ -47,7 +73,7 @@ function M.draw_section(section, highlight_name) -- Don't prepend with old highlight when the component changes it imidiately return status_str else - return highlight_name .. status_str + return left_sparator_string .. highlight_name .. status_str end end diff --git a/lua/tests/spec/utils_spec.lua b/lua/tests/spec/utils_spec.lua index d207be6..f290edb 100644 --- a/lua/tests/spec/utils_spec.lua +++ b/lua/tests/spec/utils_spec.lua @@ -71,19 +71,29 @@ end) describe('Section genarator', function() local sec = require 'lualine.utils.section' it('can draw', function() - local opts = build_component_opts() + local opts = build_component_opts({section_separators = {'', ''}}) local section = { require('lualine.components.special.function_component'):new(opts), require('lualine.components.special.function_component'):new(opts) } - eq('%#MyHl# test %#MyHl# test ', sec.draw_section(section, '%#MyHl#')) + eq( + '%#lualine_MySection_normal# test %#lualine_MySection_normal# test %#lualine_MySection_normal#', + sec.draw_section(section, 'MySection')) end) it('can remove separators from component with custom colors', function() - local opts = build_component_opts() + local opts = build_component_opts({section_separators = {'', ''}}) local opts_colored = build_component_opts({color = 'MyColor'}) - local opts_colored2 = build_component_opts({color = {bg = '#223344'}}) - local opts_colored3 = build_component_opts({color = {fg = '#223344'}}) + local opts_colored2 = build_component_opts( + { + color = {bg = '#223344'}, + section_separators = {'', ''} + }) + local opts_colored3 = build_component_opts( + { + color = {fg = '#223344'}, + section_separators = {'', ''} + }) require'lualine.highlight'.create_highlight_groups( require 'lualine.themes.gruvbox') local section = { @@ -92,21 +102,29 @@ describe('Section genarator', function() require('lualine.components.special.function_component'):new(opts) } -- Removes separator on string color - eq('%#MyHl# test %#MyHl#%#MyColor# test %#MyHl# test ', - sec.draw_section(section, '%#MyHl#')) - section[2] = require('lualine.components.special.function_component'):new( - opts_colored2) + eq( + '%#lualine_MySection_normal# test %#lualine_MySection_normal#%#MyColor#' + .. ' test %#lualine_MySection_normal# test %#lualine_MySection_normal#', + sec.draw_section(section, 'MySection')) + section[2] = + require('lua.lualine.components.special.function_component'):new( + opts_colored2) local highlight_name = '%#lualine_c_' .. section[2].options.component_name .. '_normal#' -- Removes separator on color with bg - eq('%#MyHl# test %#MyHl#' .. highlight_name .. ' test %#MyHl# test ', - sec.draw_section(section, '%#MyHl#')) - section[2] = require('lualine.components.special.function_component'):new( - opts_colored3) + eq('%#lualine_MySection_normal# test %#lualine_MySection_normal#' .. + highlight_name .. + ' test %#lualine_MySection_normal# test %#lualine_MySection_normal#', + sec.draw_section(section, 'MySection')) + section[2] = + require('lua.lualine.components.special.function_component'):new( + opts_colored3) local highlight_name2 = '%#lualine_c_' .. section[2].options.component_name .. '_normal#' -- Doesn't remove separator on color without bg - eq('%#MyHl# test %#MyHl#' .. highlight_name2 .. ' test %#MyHl# test ', - sec.draw_section(section, '%#MyHl#')) + eq('%#lualine_MySection_normal# test %#lualine_MySection_normal#' .. + highlight_name2 .. + ' test %#lualine_MySection_normal# test %#lualine_MySection_normal#', + sec.draw_section(section, 'MySection')) end) end)