diff --git a/README.md b/README.md index 73f3fea6d..1d74045d7 100644 --- a/README.md +++ b/README.md @@ -121,9 +121,17 @@ require('lualine').setup { theme = 'auto', component_separators = { left = '', right = ''}, section_separators = { left = '', right = ''}, - disabled_filetypes = {}, + disabled_filetypes = { + statusline = {}, + winbar = {}, + }, always_divide_middle = true, globalstatus = false, + refresh = { + statusline = 1000, + tabline = 1000, + winbar = 1000, + } }, sections = { lualine_a = {'mode'}, @@ -142,6 +150,8 @@ require('lualine').setup { lualine_z = {} }, tabline = {}, + winbar = {}, + inactive_winbar = {}, extensions = {} } ``` @@ -336,13 +346,28 @@ options = { theme = 'auto', -- lualine theme component_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' -- can't take over the entire statusline even -- if neither of 'x', 'y' or 'z' are present. + globalstatus = false, -- enable global statusline (have a single statusline -- at bottom of neovim instead of one for every window). -- 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 Shows currently open buffers. Like bufferline . See diff --git a/lua/lualine.lua b/lua/lualine.lua index c2356bdb8..4b3fed63d 100644 --- a/lua/lualine.lua +++ b/lua/lualine.lua @@ -1,5 +1,7 @@ -- Copyright (c) 2020-2021 hoob3rt -- MIT license, see LICENSE for more details. +local M = {} + local lualine_require = require('lualine_require') local modules = lualine_require.lazy_require { highlight = 'lualine.highlight', @@ -8,9 +10,20 @@ local modules = lualine_require.lazy_require { utils = 'lualine.utils.utils', utils_notices = 'lualine.utils.notices', config_module = 'lualine.config', + nvim_opts = 'lualine.utils.nvim_opts' } 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() --- finds first applied highlight group after str_checked in status ---@param status string : unprocessed statusline string @@ -140,7 +153,7 @@ end --- component objects ---@param is_focused boolean : whether being evaluated for focused window or not ---@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] local section_sequence = { 'a', 'b', 'c', 'x', 'y', 'z' } local status = {} @@ -167,7 +180,7 @@ local statusline = modules.utils.retry_call_wrap(function(sections, is_focused) 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 table.insert(status, modules.highlight.format_highlight('c', is_focused) .. '%=') end @@ -182,15 +195,10 @@ end) -- TODO: change this so it uses a hash table instead of iteration over list -- to improve redraws. Add buftype / bufname for extensions -- 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 _, filetype in ipairs(extension.filetypes) do - if current_ft == filetype then - if is_focused == false and extension.inactive_sections then - return extension.inactive_sections - end - return extension.sections - end + if vim.tbl_contains(extension.filetypes, current_ft) then + return extension[(is_focused and '' or 'inactive_') .. sec_name] end end return nil @@ -252,63 +260,192 @@ local function setup_theme() autocmd lualine OptionSet background lua require'lualine'.setup()]]) 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 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 - vim.go.tabline = "%{%v:lua.require'lualine'.tabline()%}" - vim.go.showtabline = 2 - elseif vim.go.tabline == "%{%v:lua.require'lualine'.tabline()%}" then - vim.go.tabline = '' - vim.go.showtabline = 1 + vim.loop.timer_start(timers.tal_timer, 0, config.options.refresh.tabline, + modules.utils.timer_call(timers.stl_timer, 'lualine_tal_refresh', function () + refresh({kind='tabpage', place={'tabline'}, trigger='timer'}) + end, 3, "lualine: Failed to refresh tabline")) + 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 --- Sets &statusline option to lualine --- adds auto command to redraw lualine on VimResized event 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 - vim.cmd('autocmd lualine VimResized * redrawstatus') - vim.go.statusline = "%{%v:lua.require'lualine'.statusline()%}" 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 - elseif vim.go.statusline == "%{%v:lua.require'lualine'.statusline()%}" then - vim.go.statusline = '' - if config.options.globalstatus then - vim.go.laststatus = 2 + else + modules.nvim_opts.restore('statusline', {global=true}) + for _, win in ipairs(vim.api.nvim_list_wins()) do + modules.nvim_opts.restore('statusline', {window=win}) end + modules.nvim_opts.restore('laststatus', {global=true}) end end --- lualine.statusline function ---- Draw correct statusline for current window ----@param focused boolean : force the value of is_focused . Useful for debugging ----@return string statusline string -local function status_dispatch(focused) - local retval - local current_ft = vim.bo.filetype - local is_focused = focused ~= nil and focused or modules.utils.is_focused() - for _, ft in pairs(config.options.disabled_filetypes) do - -- disable on specific filetypes - if ft == current_ft then - return '' +--- Sets &winbar option to lualine +local function set_winbar() + vim.loop.timer_stop(timers.wb_timer) + vim.cmd([[augroup lualine_wb_refresh | exe "autocmd!" | augroup END]]) + if next(config.winbar) ~= nil or next(config.inactive_winbar) ~= nil then + vim.loop.timer_start(timers.stl_timer, 0, config.options.refresh.winbar, + modules.utils.timer_call(timers.stl_timer, 'lualine_wb_refresh', function () + refresh({kind='tabpage', place={'winbar'}, trigger='timer'}) + end, 3, "lualine: Failed to refresh winbar")) + modules.utils.define_autocmd(default_refresh_events, + '*', "call v:lua.require'lualine'.refresh({'kind': 'tabpage', 'place': ['winbar'], 'trigger': 'autocmd'})", + '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 - 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 -- lualine.setup function @@ -332,14 +469,19 @@ local function setup(user_config) modules.loader.load_all(config) set_statusline() set_tabline() + set_winbar() if package.loaded['lualine.utils.notices'] then modules.utils_notices.notice_message_startup() end end -return { +M = { setup = setup, - statusline = status_dispatch, + statusline = status_dispatch('sections'), tabline = tabline, get_config = modules.config_module.get_config, + refresh = refresh, + winbar = status_dispatch('winbar'), } + +return M diff --git a/lua/lualine/config.lua b/lua/lualine/config.lua index dc4a47663..f750353c0 100644 --- a/lua/lualine/config.lua +++ b/lua/lualine/config.lua @@ -12,9 +12,17 @@ local config = { theme = 'auto', component_separators = { left = '', right = '' }, section_separators = { left = '', right = '' }, - disabled_filetypes = {}, + disabled_filetypes = { + statusline = {}, + winbar = {} + }, always_divide_middle = true, - globalstatus = false, + globalstatus = vim.go.laststatus == 3, + refresh = { + statusline = 1000, + tabline = 1000, + winbar = 1000, + } }, sections = { lualine_a = { 'mode' }, @@ -33,6 +41,8 @@ local config = { lualine_z = {}, }, tabline = {}, + winbar = {}, + inactive_winbar = {}, extensions = {}, } @@ -48,6 +58,24 @@ local function fix_separators(separators) return separators 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 ---@param config_table table ---@return table copy of config @@ -73,15 +101,25 @@ local function apply_configuration(config_table) ) config_table.options.globalstatus = false 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('sections') parse_sections('inactive_sections') parse_sections('tabline') + parse_sections('winbar') + parse_sections('inactive_winbar') if config_table.extensions then config.extensions = utils.deepcopy(config_table.extensions) end config.options.section_separators = fix_separators(config.options.section_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) end diff --git a/lua/lualine/utils/loader.lua b/lua/lualine/utils/loader.lua index 74f58bc52..f5f5afd39 100644 --- a/lua/lualine/utils/loader.lua +++ b/lua/lualine/utils/loader.lua @@ -154,29 +154,22 @@ end ---loads all the configs (active, inactive, tabline) ---@param config table user config local function load_components(config) - load_sections(config.sections, config.options) - load_sections(config.inactive_sections, config.options) - load_sections(config.tabline, config.options) + local sec_names = {'sections', 'inactive_sections', 'tabline', 'winbar', 'inactive_winbar'} + for _, section in ipairs(sec_names) do + load_sections(config[section], config.options) + end end ---loads all the extensions ---@param config table user config local function load_extensions(config) local loaded_extensions = {} + local sec_names = {'sections', 'inactive_sections', 'winbar', 'inactive_winbar'} for _, extension in pairs(config.extensions) do if type(extension) == 'string' then - local ok, local_extension = pcall(require, 'lualine.extensions.' .. extension) - if ok then - local_extension = modules.utils.deepcopy(local_extension) - 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 + local ok + ok, extension = pcall(require, 'lualine.extensions.' .. extension) + if not ok then modules.notice.add_notice(string.format( [[ ### Extensions @@ -185,11 +178,13 @@ Extension named `%s` was not found . Check if spelling is correct. extension )) end - elseif type(extension) == 'table' then + end + if type(extension) == 'table' then local local_extension = modules.utils.deepcopy(extension) - load_sections(local_extension.sections, config.options) - if local_extension.inactive_sections then - load_sections(local_extension.inactive_sections, config.options) + for _, section in ipairs(sec_names) do + if local_extension[section] then + load_sections(local_extension[section], config.options) + end end if type(local_extension.init) == 'function' then local_extension.init() @@ -205,6 +200,7 @@ end local function load_all(config) require('lualine.component')._reset_components() modules.fn_store.clear_fns() + require('lualine.utils.nvim_opts').reset_cache() load_components(config) load_extensions(config) end diff --git a/lua/lualine/utils/nvim_opts.lua b/lua/lualine/utils/nvim_opts.lua new file mode 100644 index 000000000..e20420c63 --- /dev/null +++ b/lua/lualine/utils/nvim_opts.lua @@ -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 +---@class LualineNvimOptCache +---@field global LualineNvimOptCacheOpt[] +---@field buffer table +---@field window table +---@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 diff --git a/lua/lualine/utils/utils.lua b/lua/lualine/utils/utils.lua index ed768d6a5..794ddaac6 100644 --- a/lua/lualine/utils/utils.lua +++ b/lua/lualine/utils/utils.lua @@ -81,13 +81,14 @@ end ---@param event string event name ---@param pattern string event pattern ---@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 cmd = pattern pattern = '*' end 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 @@ -183,4 +184,34 @@ function M.stl_escape(str) return str:gsub('%%', '%%%%') 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 diff --git a/tests/spec/config_spec.lua b/tests/spec/config_spec.lua index a66e596ab..71d29a019 100644 --- a/tests/spec/config_spec.lua +++ b/tests/spec/config_spec.lua @@ -86,12 +86,12 @@ describe('config parsing', function() describe('disabled filetypes', function() it('default', function() local config = config_module.apply_configuration {} - eq(config.options.disabled_filetypes, {}) + eq(config.options.disabled_filetypes, {statusline={}, winbar={}}) end) it('custom', function() local config = { options = { disabled_filetypes = { 'lua' } } } config = config_module.apply_configuration(config) - eq(config.options.disabled_filetypes, { 'lua' }) + eq(config.options.disabled_filetypes, {statusline={'lua'}, winbar={'lua'}}) end) end) diff --git a/tests/spec/lualine_spec.lua b/tests/spec/lualine_spec.lua index 430fb8869..274b0da2f 100644 --- a/tests/spec/lualine_spec.lua +++ b/tests/spec/lualine_spec.lua @@ -15,9 +15,17 @@ describe('Lualine', function() theme = 'gruvbox', component_separators = { left = '', right = '' }, section_separators = { left = '', right = '' }, - disabled_filetypes = {}, + disabled_filetypes = { + statusline = {}, + winbar = {}, + }, always_divide_middle = true, globalstatus = false, + refresh = { + statusline = 1000, + tabline = 1000, + winbar = 1000, + } }, sections = { lualine_a = { 'mode' }, @@ -48,6 +56,8 @@ describe('Lualine', function() lualine_z = {}, }, tabline = {}, + winbar = {}, + inactive_winbar = {}, extensions = {}, }