feat: powerline like section seperators (#94)

This commit is contained in:
Shadman 2021-02-20 09:21:05 +06:00 committed by GitHub
parent 6af9932d89
commit 202487d2ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 347 additions and 116 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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