feat: refresh lualine based on timer + winbar support. (#736)

* feat: refresh lualine based on timer.

* fix config test

* fix lag on win change issue

* handle errors in timer callback

* feat: add winbar support

Pull in winbar changes form pr #689 and adapt them
Co-authored-by: shadmansaleh <13149513+shadmansaleh@users.noreply.github.com>

* make winbar disapear when winbar evals empty

* only update stl of curwin with globalstatus

* properly clear win local stl and wbr opts

* add version guards for winbar feature

* only add winbar if height > 1

* fix tests?

* refresh lualine on ModeChanged event

* ignore floating windows for refresh

* properply restore options to previous state

* fix stl not updating in cmd mode + some optimizations

* fix tests on <nvim-0.7

* merge status_dispatch & winbar_dispatch + winbar support for extensions

* fix globalstatus option not live updating

* update docs

* feat: allow disabling winbar and statusline separately

* fix tests

* fix: winbar some times oddly throwing errors

about not having space in floating windows.

This implements a temporary workaround the issue(https://github.com/neovim/neovim/issues/19464)
until the bug in neovim gets fixed.

Co-authored-by: Diego Fujii <android.mxdiego9@gmail.com>
This commit is contained in:
Shadman 2022-07-22 19:29:55 +06:00 committed by GitHub
parent 8d956c1825
commit 53aa3d82d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 475 additions and 79 deletions

View File

@ -121,9 +121,17 @@ require('lualine').setup {
theme = 'auto', theme = 'auto',
component_separators = { left = '', right = ''}, component_separators = { left = '', right = ''},
section_separators = { left = '', right = ''}, section_separators = { left = '', right = ''},
disabled_filetypes = {}, disabled_filetypes = {
statusline = {},
winbar = {},
},
always_divide_middle = true, always_divide_middle = true,
globalstatus = false, globalstatus = false,
refresh = {
statusline = 1000,
tabline = 1000,
winbar = 1000,
}
}, },
sections = { sections = {
lualine_a = {'mode'}, lualine_a = {'mode'},
@ -142,6 +150,8 @@ require('lualine').setup {
lualine_z = {} lualine_z = {}
}, },
tabline = {}, tabline = {},
winbar = {},
inactive_winbar = {},
extensions = {} extensions = {}
} }
``` ```
@ -336,13 +346,28 @@ options = {
theme = 'auto', -- lualine theme theme = 'auto', -- lualine theme
component_separators = { left = '', right = '' }, component_separators = { left = '', right = '' },
section_separators = { left = '', right = '' }, section_separators = { left = '', right = '' },
disabled_filetypes = {}, -- Filetypes to disable lualine for. disabled_filetypes = { -- Filetypes to disable lualine for.
statusline = {}, -- only ignores the ft for statusline.
winbar = {}, -- only ignores the ft for winbar.
},
always_divide_middle = true, -- When set to true, left sections i.e. 'a','b' and 'c' always_divide_middle = true, -- When set to true, left sections i.e. 'a','b' and 'c'
-- can't take over the entire statusline even -- can't take over the entire statusline even
-- if neither of 'x', 'y' or 'z' are present. -- if neither of 'x', 'y' or 'z' are present.
globalstatus = false, -- enable global statusline (have a single statusline globalstatus = false, -- enable global statusline (have a single statusline
-- at bottom of neovim instead of one for every window). -- at bottom of neovim instead of one for every window).
-- This feature is only available in neovim 0.7 and higher. -- This feature is only available in neovim 0.7 and higher.
refresh = { -- sets how often lualine should refreash it's contents (in ms)
statusline = 1000, -- The refresh option sets minimum time that lualine tries
tabline = 1000, -- to maintain between refresh. It's not guarantied if situation
winbar = 1000 -- arises that lualine needs to refresh itself before this time
-- it'll do it.
-- Also you can force lualine's refresh by calling refresh function
-- like require('lualine').refresh()
}
} }
``` ```
@ -689,6 +714,34 @@ tabline = {
} }
``` ```
### Winbar
From neovim-0.8 you can customize your winbar with lualine.
Winbar configuration is similar to statusline.
```lua
winbar = {
lualine_a = {},
lualine_b = {},
lualine_c = {'filename'},
lualine_x = {},
lualine_y = {},
lualine_z = {}
}
inactive_winbar = {
lualine_a = {},
lualine_b = {},
lualine_c = {'filename'},
lualine_x = {},
lualine_y = {},
lualine_z = {}
}
```
Just like statusline you can separately specify winbar for active and inactive
windows. Any lualine component can be placed in winbar. All kinds of custom
components supported in statusline are also suported for winbar too. In general
You can treat winbar as another lualine statusline that just appears on top
of windows instead of at bottom.
#### Buffers #### Buffers
Shows currently open buffers. Like bufferline . See Shows currently open buffers. Like bufferline . See

View File

@ -1,5 +1,7 @@
-- Copyright (c) 2020-2021 hoob3rt -- Copyright (c) 2020-2021 hoob3rt
-- MIT license, see LICENSE for more details. -- MIT license, see LICENSE for more details.
local M = {}
local lualine_require = require('lualine_require') local lualine_require = require('lualine_require')
local modules = lualine_require.lazy_require { local modules = lualine_require.lazy_require {
highlight = 'lualine.highlight', highlight = 'lualine.highlight',
@ -8,9 +10,20 @@ local modules = lualine_require.lazy_require {
utils = 'lualine.utils.utils', utils = 'lualine.utils.utils',
utils_notices = 'lualine.utils.notices', utils_notices = 'lualine.utils.notices',
config_module = 'lualine.config', config_module = 'lualine.config',
nvim_opts = 'lualine.utils.nvim_opts'
} }
local config -- Stores currently applied config local config -- Stores currently applied config
local timers = {
stl_timer = vim.loop.new_timer(),
tal_timer = vim.loop.new_timer(),
wb_timer = vim.loop.new_timer(),
}
-- The events on which lualine redraws itself
local default_refresh_events = 'WinEnter,BufEnter,SessionLoadPost,FileChangedShellPost,VimResized'
if vim.fn.has('nvim-0.7') == 1 then -- utilize ModeChanged event introduced in 0.7
default_refresh_events = default_refresh_events..',ModeChanged'
end
-- Helper for apply_transitional_separators() -- Helper for apply_transitional_separators()
--- finds first applied highlight group after str_checked in status --- finds first applied highlight group after str_checked in status
---@param status string : unprocessed statusline string ---@param status string : unprocessed statusline string
@ -140,7 +153,7 @@ end
--- component objects --- component objects
---@param is_focused boolean : whether being evaluated for focused window or not ---@param is_focused boolean : whether being evaluated for focused window or not
---@return string statusline string ---@return string statusline string
local statusline = modules.utils.retry_call_wrap(function(sections, is_focused) local statusline = modules.utils.retry_call_wrap(function(sections, is_focused, is_winbar)
-- The sequence sections should maintain [SECTION_SEQUENCE] -- The sequence sections should maintain [SECTION_SEQUENCE]
local section_sequence = { 'a', 'b', 'c', 'x', 'y', 'z' } local section_sequence = { 'a', 'b', 'c', 'x', 'y', 'z' }
local status = {} local status = {}
@ -167,7 +180,7 @@ local statusline = modules.utils.retry_call_wrap(function(sections, is_focused)
end end
end end
end end
if applied_midsection_divider == false and config.options.always_divide_middle ~= false then if applied_midsection_divider == false and config.options.always_divide_middle ~= false and not is_winbar then
-- When non of section x,y,z is present -- When non of section x,y,z is present
table.insert(status, modules.highlight.format_highlight('c', is_focused) .. '%=') table.insert(status, modules.highlight.format_highlight('c', is_focused) .. '%=')
end end
@ -182,15 +195,10 @@ end)
-- TODO: change this so it uses a hash table instead of iteration over list -- TODO: change this so it uses a hash table instead of iteration over list
-- to improve redraws. Add buftype / bufname for extensions -- to improve redraws. Add buftype / bufname for extensions
-- or some kind of cond ? -- or some kind of cond ?
local function get_extension_sections(current_ft, is_focused) local function get_extension_sections(current_ft, is_focused, sec_name)
for _, extension in ipairs(config.extensions) do for _, extension in ipairs(config.extensions) do
for _, filetype in ipairs(extension.filetypes) do if vim.tbl_contains(extension.filetypes, current_ft) then
if current_ft == filetype then return extension[(is_focused and '' or 'inactive_') .. sec_name]
if is_focused == false and extension.inactive_sections then
return extension.inactive_sections
end
return extension.sections
end
end end
end end
return nil return nil
@ -252,63 +260,192 @@ local function setup_theme()
autocmd lualine OptionSet background lua require'lualine'.setup()]]) autocmd lualine OptionSet background lua require'lualine'.setup()]])
end end
---@alias StatusDispatchSecs
---| 'sections'
---| 'winbar'
--- generates lualine.statusline & lualine.winbar function
--- creates a closer that can draw sections of sec_name.
---@param sec_name StatusDispatchSecs
---@return function(focused:bool):string
local function status_dispatch(sec_name)
return function(focused)
local retval
local current_ft = vim.bo.filetype
local is_focused = focused ~= nil and focused or modules.utils.is_focused()
if vim.tbl_contains(config.options.disabled_filetypes[(sec_name == 'sections' and 'statusline' or sec_name)],
current_ft) then
-- disable on specific filetypes
return ''
end
local extension_sections = get_extension_sections(current_ft, is_focused, sec_name)
if extension_sections ~= nil then
retval = statusline(extension_sections, is_focused, sec_name == 'winbar')
else
retval = statusline(config[(is_focused and '' or 'inactive_')..sec_name], is_focused, sec_name == 'winbar')
end
return retval
end
end
---@alias LualineRefreshOptsKind
---| 'all'
---| 'tabpage'
---| 'window'
---@alias LualineRefreshOptsPlace
---| 'statusline'
---| 'tabline'
---| 'winbar'
---@class LualineRefreshOpts
---@field kind LualineRefreshOptsKind
---@field place LualineRefreshOptsPlace[]
---@field trigger 'autocmd'|'timer'|'unknown'
--- Refresh contents of lualine
---@param opts LualineRefreshOpts
local function refresh(opts)
if opts == nil then
opts = {kind = 'tabpage', place = {'statusline', 'winbar', 'tabline'}, trigger='unknown'}
end
-- workaround for https://github.com/neovim/neovim/issues/19464
if (opts.trigger == 'autocmd'
and vim.api.nvim_win_get_height(vim.api.nvim_get_current_win()) <= 1
and vim.tbl_contains(opts.place, 'winbar')
) then
local id
for index, value in ipairs(opts.place) do
if value == 'winbar' then
id = index
break
end
end
table.remove(opts.place, id)
end
local wins = {}
local old_actual_curwin = vim.g.actual_curwin
vim.g.actual_curwin = vim.api.nvim_get_current_win()
-- gather which windows needs update
if opts.kind == 'all' then
if vim.tbl_contains(opts.place, 'statusline')
or vim.tbl_contains(opts.place, 'winbar') then
wins = vim.tbl_filter(function (win)
return vim.fn.win_gettype(win) ~= 'popup'
end, vim.api.nvim_list_wins())
end
elseif opts.kind == 'tabpage' then
if vim.tbl_contains(opts.place, 'statusline')
or vim.tbl_contains(opts.place, 'winbar') then
wins = vim.tbl_filter(function (win)
return vim.fn.win_gettype(win) ~= 'popup'
end, vim.api.nvim_tabpage_list_wins(0))
end
elseif opts.kind == 'window' then
wins = {vim.api.nvim_get_current_win()}
end
-- update them
if vim.tbl_contains(opts.place, 'statusline') then
for _, win in ipairs(wins) do
modules.nvim_opts.set('statusline',
vim.api.nvim_win_call(win, M.statusline), {window=win})
end
end
if vim.tbl_contains(opts.place, 'winbar') then
for _, win in ipairs(wins) do
if vim.api.nvim_win_get_height(win) > 1 then
modules.nvim_opts.set('winbar',
vim.api.nvim_win_call(win, M.winbar), {window=win})
end
end
end
if vim.tbl_contains(opts.place, 'tabline') then
modules.nvim_opts.set('tabline',
vim.api.nvim_win_call(vim.api.nvim_get_current_win(), tabline),
{global=true})
end
-- call redraw
if vim.tbl_contains(opts.place, 'statusline')
or vim.tbl_contains(opts.place, 'winbar') then
vim.cmd('redrawstatus')
elseif vim.tbl_contains(opts.place, 'tabline') then
vim.cmd('redrawtabline')
end
vim.g.actual_curwin = old_actual_curwin
end
--- Sets &tabline option to lualine --- Sets &tabline option to lualine
local function set_tabline() local function set_tabline()
vim.loop.timer_stop(timers.tal_timer)
vim.cmd([[augroup lualine_tal_refresh | exe "autocmd!" | augroup END]])
if next(config.tabline) ~= nil then if next(config.tabline) ~= nil then
vim.go.tabline = "%{%v:lua.require'lualine'.tabline()%}" vim.loop.timer_start(timers.tal_timer, 0, config.options.refresh.tabline,
vim.go.showtabline = 2 modules.utils.timer_call(timers.stl_timer, 'lualine_tal_refresh', function ()
elseif vim.go.tabline == "%{%v:lua.require'lualine'.tabline()%}" then refresh({kind='tabpage', place={'tabline'}, trigger='timer'})
vim.go.tabline = '' end, 3, "lualine: Failed to refresh tabline"))
vim.go.showtabline = 1 modules.utils.define_autocmd(default_refresh_events,
'*', "call v:lua.require'lualine'.refresh({'kind': 'tabpage', 'place': ['tabline'], 'trigger': 'autocmd'})",
'lualine_tal_refresh')
modules.nvim_opts.set('showtabline', 2, {global=true})
else
modules.nvim_opts.restore('tabline', {global=true})
modules.nvim_opts.restore('showtabline', {global=true})
end end
end end
--- Sets &statusline option to lualine --- Sets &statusline option to lualine
--- adds auto command to redraw lualine on VimResized event --- adds auto command to redraw lualine on VimResized event
local function set_statusline() local function set_statusline()
vim.loop.timer_stop(timers.stl_timer)
vim.cmd([[augroup lualine_stl_refresh | exe "autocmd!" | augroup END]])
if next(config.sections) ~= nil or next(config.inactive_sections) ~= nil then if next(config.sections) ~= nil or next(config.inactive_sections) ~= nil then
vim.cmd('autocmd lualine VimResized * redrawstatus')
vim.go.statusline = "%{%v:lua.require'lualine'.statusline()%}"
if config.options.globalstatus then if config.options.globalstatus then
vim.go.laststatus = 3 modules.nvim_opts.set('laststatus', 3, {global=true})
vim.loop.timer_start(timers.stl_timer, 0, config.options.refresh.statusline,
modules.utils.timer_call(timers.stl_timer, 'lualine_stl_refresh', function ()
refresh({kind='window', place={'statusline'}, trigger='timer'})
end, 3, "lualine: Failed to refresh statusline"))
modules.utils.define_autocmd(default_refresh_events,
'*', "call v:lua.require'lualine'.refresh({'kind': 'window', 'place': ['statusline'], 'trigger': 'autocmd'})",
'lualine_stl_refresh')
else
modules.nvim_opts.set('laststatus', 2, {global=true})
vim.loop.timer_start(timers.stl_timer, 0, config.options.refresh.statusline,
modules.utils.timer_call(timers.stl_timer, 'lualine_stl_refresh', function ()
refresh({kind='tabpage', place={'statusline'}, trigger='timer'})
end, 3, "lualine: Failed to refresh statusline"))
modules.utils.define_autocmd(default_refresh_events,
'*', "call v:lua.require'lualine'.refresh({'kind': 'tabpage', 'place': ['statusline'], 'trigger': 'autocmd'})",
'lualine_stl_refresh')
end end
elseif vim.go.statusline == "%{%v:lua.require'lualine'.statusline()%}" then else
vim.go.statusline = '' modules.nvim_opts.restore('statusline', {global=true})
if config.options.globalstatus then for _, win in ipairs(vim.api.nvim_list_wins()) do
vim.go.laststatus = 2 modules.nvim_opts.restore('statusline', {window=win})
end end
modules.nvim_opts.restore('laststatus', {global=true})
end end
end end
-- lualine.statusline function --- Sets &winbar option to lualine
--- Draw correct statusline for current window local function set_winbar()
---@param focused boolean : force the value of is_focused . Useful for debugging vim.loop.timer_stop(timers.wb_timer)
---@return string statusline string vim.cmd([[augroup lualine_wb_refresh | exe "autocmd!" | augroup END]])
local function status_dispatch(focused) if next(config.winbar) ~= nil or next(config.inactive_winbar) ~= nil then
local retval vim.loop.timer_start(timers.stl_timer, 0, config.options.refresh.winbar,
local current_ft = vim.bo.filetype modules.utils.timer_call(timers.stl_timer, 'lualine_wb_refresh', function ()
local is_focused = focused ~= nil and focused or modules.utils.is_focused() refresh({kind='tabpage', place={'winbar'}, trigger='timer'})
for _, ft in pairs(config.options.disabled_filetypes) do end, 3, "lualine: Failed to refresh winbar"))
-- disable on specific filetypes modules.utils.define_autocmd(default_refresh_events,
if ft == current_ft then '*', "call v:lua.require'lualine'.refresh({'kind': 'tabpage', 'place': ['winbar'], 'trigger': 'autocmd'})",
return '' 'lualine_wb_refresh')
elseif vim.fn.has('nvim-0.8') == 1 then
modules.nvim_opts.restore('winbar', {global=true})
for _, win in ipairs(vim.api.nvim_list_wins()) do
modules.nvim_opts.restore('winbar', {window=win})
end end
end end
local extension_sections = get_extension_sections(current_ft, is_focused)
if is_focused then
if extension_sections ~= nil then
retval = statusline(extension_sections, is_focused)
else
retval = statusline(config.sections, is_focused)
end
else
if extension_sections ~= nil then
retval = statusline(extension_sections, is_focused)
else
retval = statusline(config.inactive_sections, is_focused)
end
end
return retval
end end
-- lualine.setup function -- lualine.setup function
@ -332,14 +469,19 @@ local function setup(user_config)
modules.loader.load_all(config) modules.loader.load_all(config)
set_statusline() set_statusline()
set_tabline() set_tabline()
set_winbar()
if package.loaded['lualine.utils.notices'] then if package.loaded['lualine.utils.notices'] then
modules.utils_notices.notice_message_startup() modules.utils_notices.notice_message_startup()
end end
end end
return { M = {
setup = setup, setup = setup,
statusline = status_dispatch, statusline = status_dispatch('sections'),
tabline = tabline, tabline = tabline,
get_config = modules.config_module.get_config, get_config = modules.config_module.get_config,
refresh = refresh,
winbar = status_dispatch('winbar'),
} }
return M

View File

@ -12,9 +12,17 @@ local config = {
theme = 'auto', theme = 'auto',
component_separators = { left = '', right = '' }, component_separators = { left = '', right = '' },
section_separators = { left = '', right = '' }, section_separators = { left = '', right = '' },
disabled_filetypes = {}, disabled_filetypes = {
statusline = {},
winbar = {}
},
always_divide_middle = true, always_divide_middle = true,
globalstatus = false, globalstatus = vim.go.laststatus == 3,
refresh = {
statusline = 1000,
tabline = 1000,
winbar = 1000,
}
}, },
sections = { sections = {
lualine_a = { 'mode' }, lualine_a = { 'mode' },
@ -33,6 +41,8 @@ local config = {
lualine_z = {}, lualine_z = {},
}, },
tabline = {}, tabline = {},
winbar = {},
inactive_winbar = {},
extensions = {}, extensions = {},
} }
@ -48,6 +58,24 @@ local function fix_separators(separators)
return separators return separators
end end
---copy raw disabled_filetypes to inner statusline & winbar tables.
---@param disabled_filetypes table
---@return table
local function fix_disabled_filetypes(disabled_filetypes)
if disabled_filetypes == nil then return end
if disabled_filetypes.statusline == nil then
disabled_filetypes.statusline = {}
end
if disabled_filetypes.winbar == nil then
disabled_filetypes.winbar = {}
end
for k, disabled_ft in ipairs(disabled_filetypes) do
table.insert(disabled_filetypes.statusline, disabled_ft)
table.insert(disabled_filetypes.winbar, disabled_ft)
disabled_filetypes[k] = nil
end
return disabled_filetypes
end
---extends config based on config_table ---extends config based on config_table
---@param config_table table ---@param config_table table
---@return table copy of config ---@return table copy of config
@ -73,15 +101,25 @@ local function apply_configuration(config_table)
) )
config_table.options.globalstatus = false config_table.options.globalstatus = false
end end
if vim.fn.has('nvim-0.8') == 0 and (next(config_table.winbar or {}) or next(config_table.inactive_winbar or {})) then
modules.utils_notices.add_notice(
'### winbar\nSorry `winbar can only be used in neovim 0.8 or higher.\n'
)
config_table.winbar = {}
config_table.inactive_winbar = {}
end
parse_sections('options') parse_sections('options')
parse_sections('sections') parse_sections('sections')
parse_sections('inactive_sections') parse_sections('inactive_sections')
parse_sections('tabline') parse_sections('tabline')
parse_sections('winbar')
parse_sections('inactive_winbar')
if config_table.extensions then if config_table.extensions then
config.extensions = utils.deepcopy(config_table.extensions) config.extensions = utils.deepcopy(config_table.extensions)
end end
config.options.section_separators = fix_separators(config.options.section_separators) config.options.section_separators = fix_separators(config.options.section_separators)
config.options.component_separators = fix_separators(config.options.component_separators) config.options.component_separators = fix_separators(config.options.component_separators)
config.options.disabled_filetypes = fix_disabled_filetypes(config.options.disabled_filetypes)
return utils.deepcopy(config) return utils.deepcopy(config)
end end

View File

@ -154,29 +154,22 @@ end
---loads all the configs (active, inactive, tabline) ---loads all the configs (active, inactive, tabline)
---@param config table user config ---@param config table user config
local function load_components(config) local function load_components(config)
load_sections(config.sections, config.options) local sec_names = {'sections', 'inactive_sections', 'tabline', 'winbar', 'inactive_winbar'}
load_sections(config.inactive_sections, config.options) for _, section in ipairs(sec_names) do
load_sections(config.tabline, config.options) load_sections(config[section], config.options)
end
end end
---loads all the extensions ---loads all the extensions
---@param config table user config ---@param config table user config
local function load_extensions(config) local function load_extensions(config)
local loaded_extensions = {} local loaded_extensions = {}
local sec_names = {'sections', 'inactive_sections', 'winbar', 'inactive_winbar'}
for _, extension in pairs(config.extensions) do for _, extension in pairs(config.extensions) do
if type(extension) == 'string' then if type(extension) == 'string' then
local ok, local_extension = pcall(require, 'lualine.extensions.' .. extension) local ok
if ok then ok, extension = pcall(require, 'lualine.extensions.' .. extension)
local_extension = modules.utils.deepcopy(local_extension) if not ok then
load_sections(local_extension.sections, config.options)
if local_extension.inactive_sections then
load_sections(local_extension.inactive_sections, config.options)
end
if type(local_extension.init) == 'function' then
local_extension.init()
end
table.insert(loaded_extensions, local_extension)
else
modules.notice.add_notice(string.format( modules.notice.add_notice(string.format(
[[ [[
### Extensions ### Extensions
@ -185,11 +178,13 @@ Extension named `%s` was not found . Check if spelling is correct.
extension extension
)) ))
end end
elseif type(extension) == 'table' then end
if type(extension) == 'table' then
local local_extension = modules.utils.deepcopy(extension) local local_extension = modules.utils.deepcopy(extension)
load_sections(local_extension.sections, config.options) for _, section in ipairs(sec_names) do
if local_extension.inactive_sections then if local_extension[section] then
load_sections(local_extension.inactive_sections, config.options) load_sections(local_extension[section], config.options)
end
end end
if type(local_extension.init) == 'function' then if type(local_extension.init) == 'function' then
local_extension.init() local_extension.init()
@ -205,6 +200,7 @@ end
local function load_all(config) local function load_all(config)
require('lualine.component')._reset_components() require('lualine.component')._reset_components()
modules.fn_store.clear_fns() modules.fn_store.clear_fns()
require('lualine.utils.nvim_opts').reset_cache()
load_components(config) load_components(config)
load_extensions(config) load_extensions(config)
end end

View File

@ -0,0 +1,126 @@
local M = {}
-- keeps backup of options that we cahge so we can restore it.
-- format:
-- options {
-- global = <1> {
-- name = {prev, set}
-- },
-- buffer = {
-- buf1 = <1>,
-- buf2 = <1>
-- },
-- window = {
-- win1 = <1>,
-- win2 = <1>
-- }
-- }
---@class LualineNvimOptCacheOptStore
---@field prev any
---@field set any
---@alias LualineNvimOptCacheOpt table<string, LualineNvimOptCacheOptStore>
---@class LualineNvimOptCache
---@field global LualineNvimOptCacheOpt[]
---@field buffer table<number, LualineNvimOptCacheOpt[]>
---@field window table<number, LualineNvimOptCacheOpt[]>
---@type LualineNvimOptCache
local options = {global={}, buffer={}, window={}}
-- helper function for M.set
local function set_opt(name, val, getter_fn, setter_fn, cache_tbl)
-- before nvim 0.7 nvim_win_get_option... didn't return default value when
-- the option wasn't set instead threw error.
-- So we need pcall (probably just for test)
local ok, cur = pcall(getter_fn, name)
if not ok then cur = nil end
if cur == val then return end
if cache_tbl[name] == nil then cache_tbl[name] = {} end
if cache_tbl[name].set ~= cur then
cache_tbl[name].prev = cur
end
cache_tbl[name].set = val
setter_fn(name, val)
end
-- set a option value
---@param name string
---@param val any
---@param opts table|nil when table can be {global=true | buffer = bufnr | window = winnr}
--- when nil it's treated as {global = true}
function M.set(name, val, opts)
if opts == nil or opts.global then
set_opt(name, val, vim.api.nvim_get_option, vim.api.nvim_set_option, options.global)
elseif opts.buffer then
if options.buffer[opts.buffer] == nil then
options.buffer[opts.buffer] = {}
end
set_opt(name, val, function (nm)
return vim.api.nvim_buf_get_option(opts.buffer, nm)
end, function (nm, vl)
vim.api.nvim_buf_set_option(opts.buffer, nm, vl)
end, options.buffer[opts.buffer])
elseif opts.window then
if options.window[opts.window] == nil then
options.window[opts.window] = {}
end
set_opt(name, val, function (nm)
return vim.api.nvim_win_get_option(opts.window, nm)
end, function (nm, vl)
vim.api.nvim_win_set_option(opts.window, nm, vl)
end, options.window[opts.window])
end
end
-- resoters old value of option name
---@param name string
---@param opts table|nil same as M.set
function M.restore(name, opts)
if opts == nil or opts.global then
if options.global[name] ~= nil and options.global[name].prev ~= nil then
vim.api.nvim_set_option(name, options.global[name].prev)
end
elseif opts.buffer then
if options.buffer[opts.buffer] ~= nil
and options.buffer[opts.buffer][name] ~= nil
and options.buffer[opts.buffer][name].prev ~= nil then
vim.api.nvim_buf_set_option(opts.buffer, name, options.buffer[opts.buffer][name].prev)
end
elseif opts.window then
if options.window[opts.window] ~= nil
and options.window[opts.window][name] ~= nil
and options.window[opts.window][name].prev ~= nil then
vim.api.nvim_win_set_option(opts.window, name, options.window[opts.window][name].prev)
end
end
end
-- returns cache for the option name
---@param name string
---@param opts table|nil same as M.set
function M.get_cache(name, opts)
if opts == nil or opts.global then
if options.global[name] ~= nil and options.global[name].prev ~= nil then
return options.global[name].prev
end
elseif opts.buffer then
if options.buffer[opts.buffer] ~= nil
and options.buffer[opts.buffer][name] ~= nil
and options.buffer[opts.buffer][name].prev ~= nil then
return options.buffer[opts.buffer][name].prev
end
elseif opts.window then
if options.window[opts.window] ~= nil
and options.window[opts.window][name] ~= nil
and options.window[opts.window][name].prev ~= nil then
return options.window[opts.window][name].prev
end
end
end
-- resets cache for options
function M.reset_cache()
options = {global={}, buffer={}, window={}}
end
return M

View File

@ -81,13 +81,14 @@ end
---@param event string event name ---@param event string event name
---@param pattern string event pattern ---@param pattern string event pattern
---@param cmd string command to run on event ---@param cmd string command to run on event
function M.define_autocmd(event, pattern, cmd) ---@param group string group name defaults to lualine
function M.define_autocmd(event, pattern, cmd, group)
if not cmd then if not cmd then
cmd = pattern cmd = pattern
pattern = '*' pattern = '*'
end end
if not autocmd_is_defined(event, pattern, cmd) then if not autocmd_is_defined(event, pattern, cmd) then
vim.cmd(string.format('autocmd lualine %s %s %s', event, pattern, cmd)) vim.cmd(string.format('autocmd %s %s %s %s', group or 'lualine', event, pattern, cmd))
end end
end end
@ -183,4 +184,34 @@ function M.stl_escape(str)
return str:gsub('%%', '%%%%') return str:gsub('%%', '%%%%')
end end
---A safe call inside a timmer
---@param timer userdata
---@param augroup string|nil autocmd group to reset too on error.
---@param fn function
---@param max_err integer
---@param err_msg string
---@return function a wraped fn that can be called inside a timer and that
---stops the timer after max_err errors in calling fn
function M.timer_call(timer, augroup, fn, max_err, err_msg)
local err_cnt, ret = 0, nil
max_err = max_err or 3
return vim.schedule_wrap(function(...)
if err_cnt > max_err then
vim.loop.timer_stop(timer)
if augroup then
vim.cmd(string.format([[augroup %s | exe "autocmd!" | augroup END]], augroup))
end
error(err_msg..':\n'..tostring(ret))
end
local ok
ok, ret = pcall(fn, ...)
if ok then
err_cnt = 0
else
err_cnt = err_cnt + 1
end
return ret
end)
end
return M return M

View File

@ -86,12 +86,12 @@ describe('config parsing', function()
describe('disabled filetypes', function() describe('disabled filetypes', function()
it('default', function() it('default', function()
local config = config_module.apply_configuration {} local config = config_module.apply_configuration {}
eq(config.options.disabled_filetypes, {}) eq(config.options.disabled_filetypes, {statusline={}, winbar={}})
end) end)
it('custom', function() it('custom', function()
local config = { options = { disabled_filetypes = { 'lua' } } } local config = { options = { disabled_filetypes = { 'lua' } } }
config = config_module.apply_configuration(config) config = config_module.apply_configuration(config)
eq(config.options.disabled_filetypes, { 'lua' }) eq(config.options.disabled_filetypes, {statusline={'lua'}, winbar={'lua'}})
end) end)
end) end)

View File

@ -15,9 +15,17 @@ describe('Lualine', function()
theme = 'gruvbox', theme = 'gruvbox',
component_separators = { left = '', right = '' }, component_separators = { left = '', right = '' },
section_separators = { left = '', right = '' }, section_separators = { left = '', right = '' },
disabled_filetypes = {}, disabled_filetypes = {
statusline = {},
winbar = {},
},
always_divide_middle = true, always_divide_middle = true,
globalstatus = false, globalstatus = false,
refresh = {
statusline = 1000,
tabline = 1000,
winbar = 1000,
}
}, },
sections = { sections = {
lualine_a = { 'mode' }, lualine_a = { 'mode' },
@ -48,6 +56,8 @@ describe('Lualine', function()
lualine_z = {}, lualine_z = {},
}, },
tabline = {}, tabline = {},
winbar = {},
inactive_winbar = {},
extensions = {}, extensions = {},
} }