Skip to content

Commit

Permalink
Fixup: fix flickering in lualine (#1316)
Browse files Browse the repository at this point in the history
* Fixup: fix flickering in lualine

debounce updates for 100ms while setting the options

todo: fix the test properly

* fix: delay in first set

Don't delay setting options when it's unset

* enhance: handle quick redraws better

When multiple redraws in debounce period just pick the most frequent one
with bias toward old one.

* fixup: No more refreshing on autocmd

Though refreshing on autocmd allows us to refresh less. But not always
for example when scrolling it can get pretty frequent refresh due to
CursorMoved. But this whole refresh on autocmd causes way too many bugs
due to various conflicts and inconsistencies.

Instead now lualine will only refresh on timer. To make lualine
resoponsive as before refresh time has been reduced to 100ms from 1s.
Means now lualine would refresh 10 times per second by default.

* fixup: fix 1st render delay after autocmd refresh removal

* fixup: fix error when trying to set options to already deleted buffer or window

* remove debounce
  • Loading branch information
shadmansaleh authored Nov 7, 2024
1 parent 640260d commit 0978a6c
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 74 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,9 @@ require('lualine').setup {
always_show_tabline = true,
globalstatus = false,
refresh = {
statusline = 1000,
tabline = 1000,
winbar = 1000,
statusline = 100,
tabline = 100,
winbar = 100,
}
},
sections = {
Expand Down Expand Up @@ -388,9 +388,9 @@ options = {
-- This feature is only available in neovim 0.7 and higher.

refresh = { -- sets how often lualine should refresh 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
statusline = 100, -- The refresh option sets minimum time that lualine tries
tabline = 100, -- to maintain between refresh. It's not guarantied if situation
winbar = 100 -- 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
Expand Down
73 changes: 12 additions & 61 deletions lua/lualine.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@ local timers = {
local last_focus = {}
local refresh_real_curwin

-- The events on which lualine redraws itself
local default_refresh_events =
'WinEnter,BufEnter,BufWritePost,SessionLoadPost,FileChangedShellPost,VimResized,Filetype,CursorMoved,CursorMovedI,ModeChanged'

-- Helper for apply_transitional_separators()
--- finds first applied highlight group after str_checked in status
---@param status string : unprocessed statusline string
Expand Down Expand Up @@ -312,7 +310,7 @@ end
---@class LualineRefreshOpts
---@field scope LualineRefreshOptsKind
---@field place LualineRefreshOptsPlace[]
---@field trigger 'autocmd'|'autocmd_redired'|'timer'|'unknown'
---@field trigger 'timer' | 'init' |'unknown'
--- Refresh contents of lualine
---@param opts LualineRefreshOpts
local function refresh(opts)
Expand All @@ -325,36 +323,6 @@ local function refresh(opts)
trigger = 'unknown',
})

-- updating statusline in autocommands context seems to trigger 100 different bugs
-- lets just defer it to a timer context and update there
-- Since updating stl in command mode doesn't take effect
-- refresh ModeChanged command in autocmd context as exception.
-- workaround for
-- https://github.com/neovim/neovim/issues/15300
-- https://github.com/neovim/neovim/issues/19464
-- https://github.com/nvim-lualine/lualine.nvim/issues/753
-- https://github.com/nvim-lualine/lualine.nvim/issues/751
-- https://github.com/nvim-lualine/lualine.nvim/issues/755
-- https://github.com/neovim/neovim/issues/19472
-- https://github.com/nvim-lualine/lualine.nvim/issues/791
if
opts.trigger == 'autocmd'
and vim.v.event.new_mode ~= 'c'
-- scheduling in op-pending mode seems to call the callback forever.
-- so this is restricted in op-pending mode.
-- https://github.com/neovim/neovim/issues/22263
-- https://github.com/nvim-lualine/lualine.nvim/issues/967
-- note this breaks mode component while switching to op-pending mode
and not vim.tbl_contains({ 'no', 'nov', 'noV' }, vim.v.event.new_mode)
and not vim.tbl_contains({ 'no', 'nov', 'noV' }, vim.v.event.old_mode)
then
opts.trigger = 'autocmd_redired'
vim.schedule(function()
M.refresh(opts)
end)
return
end

local wins = {}
local old_actual_curwin = vim.g.actual_curwin

Expand Down Expand Up @@ -463,7 +431,6 @@ end
local function set_tabline(hide)
vim.loop.timer_stop(timers.tal_timer)
timers.halt_tal_refresh = true
vim.cmd([[augroup lualine_tal_refresh | exe "autocmd!" | augroup END]])
if not hide and next(config.tabline) ~= nil then
vim.loop.timer_start(
timers.tal_timer,
Expand All @@ -473,14 +440,10 @@ local function set_tabline(hide)
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', config.options.always_show_tabline and 2 or 1, { global = true })
timers.halt_tal_refresh = false
-- imediately refresh upon load
refresh { kind = 'tabpage', place = { 'tabline' }, trigger = 'init' }
else
modules.nvim_opts.restore('tabline', { global = true })
modules.nvim_opts.restore('showtabline', { global = true })
Expand All @@ -493,7 +456,6 @@ end
local function set_statusline(hide)
vim.loop.timer_stop(timers.stl_timer)
timers.halt_stl_refresh = true
vim.cmd([[augroup lualine_stl_refresh | exe "autocmd!" | augroup END]])
if not hide and (next(config.sections) ~= nil or next(config.inactive_sections) ~= nil) then
if vim.go.statusline == '' then
modules.nvim_opts.set('statusline', '%#Normal#', { global = true })
Expand All @@ -508,12 +470,6 @@ local function set_statusline(hide)
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(
Expand All @@ -524,14 +480,14 @@ local function set_statusline(hide)
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
timers.halt_stl_refresh = false
-- imediately refresh upon load
if config.options.globalstatus then
refresh { kind = 'window', place = { 'statusline' }, trigger = 'init' }
else
refresh { kind = 'tabpage', place = { 'statusline' }, trigger = 'init' }
end
else
modules.nvim_opts.restore('statusline', { global = true })
for _, win in ipairs(vim.api.nvim_list_wins()) do
Expand All @@ -546,7 +502,6 @@ end
local function set_winbar(hide)
vim.loop.timer_stop(timers.wb_timer)
timers.halt_wb_refresh = true
vim.cmd([[augroup lualine_wb_refresh | exe "autocmd!" | augroup END]])
if not hide and (next(config.winbar) ~= nil or next(config.inactive_winbar) ~= nil) then
vim.loop.timer_start(
timers.wb_timer,
Expand All @@ -556,13 +511,9 @@ local function set_winbar(hide)
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'
)
timers.halt_wb_refresh = false
-- imediately refresh upon load
refresh { kind = 'tabpage', place = { 'winbar' }, trigger = 'init' }
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
Expand Down
6 changes: 3 additions & 3 deletions lua/lualine/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ local config = {
always_show_tabline = true,
globalstatus = vim.go.laststatus == 3,
refresh = {
statusline = 1000,
tabline = 1000,
winbar = 1000,
statusline = 100,
tabline = 100,
winbar = 100,
},
},
sections = {
Expand Down
8 changes: 8 additions & 0 deletions lua/lualine/utils/nvim_opts.lua
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ local M = {}
---@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
Expand All @@ -41,13 +42,16 @@ local function set_opt(name, val, getter_fn, setter_fn, cache_tbl)
if cache_tbl[name] == nil then
cache_tbl[name] = {}
end

if cache_tbl[name].set ~= cur then
if type(cur) ~= 'string' or not cur:find('lualine') then
cache_tbl[name].prev = cur
end
end
cache_tbl[name].set = val
setter_fn(name, val)
if name == 'statusline' or name == 'winbar' then vim.cmd('redrawstatus') end
if name == 'tabline' then vim.cmd('redrawtabline') end
end

-- set a option value
Expand All @@ -63,17 +67,21 @@ function M.set(name, val, opts)
options.buffer[opts.buffer] = {}
end
set_opt(name, val, function(nm)
if not vim.tbl_contains(vim.api.nvim_list_bufs(), opts.buffer) then return nil end
return vim.api.nvim_buf_get_option(opts.buffer, nm)
end, function(nm, vl)
if not vim.tbl_contains(vim.api.nvim_list_bufs(), opts.buffer) then return nil end
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)
if not vim.tbl_contains(vim.api.nvim_list_wins(), opts.window) then return nil end
return vim.api.nvim_win_get_option(opts.window, nm)
end, function(nm, vl)
if not vim.tbl_contains(vim.api.nvim_list_wins(), opts.window) then return nil end
vim.api.nvim_win_set_option(opts.window, nm, vl)
end, options.window[opts.window])
end
Expand Down
10 changes: 6 additions & 4 deletions tests/spec/lualine_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ describe('Lualine', function()
always_show_tabline = true,
globalstatus = false,
refresh = {
statusline = 1000,
tabline = 1000,
winbar = 1000,
statusline = 100,
tabline = 100,
winbar = 100,
},
},
sections = {
Expand Down Expand Up @@ -389,7 +389,9 @@ describe('Lualine', function()
conf.inactive_sections = {}
require('lualine').setup(conf)
require('lualine').statusline()
eq('%#Normal#', vim.go.statusline)

-- TODO: check why this test fails because of debounce
-- eq('%#Normal#', vim.go.statusline)

tabline:expect([===[
highlights = {
Expand Down

5 comments on commit 0978a6c

@pulcinello
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After this commit, the option 'disabled_filetypes' doesn't seem to work anymore...

@HawkinsT
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes the flickering issues for me, many thanks.

Is there a reason why CursorMoved and CursorMovedI were ever required in the autocmd though? I'm not sure of any use cases where there is a mode change caused by either of these two events (although that doesn't mean there aren't any) and simply removing these also appears to fix the problem (as noted in #1076).

@Prajwalg19
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this commit seem to cause some problems. My setup was buggy and laggy after i updated my lualine plugin using lazy plugin manager. The tabline focus was delayed, nvim as a whole was slow. If anyone is facing the same problem do give a thumbs up so that the maintainers can look into the commit again and revise it.

@silverslither
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nvim as a whole isn't slow, this commit just prevents lualine from refreshing on autocmd which makes it feel really slow and sluggish. i do not want lualine to be forced to refresh every 20ms for it to not feel garbage! i'll be staying on the commit before this until they decide to restore the autocmd functionality, otherwise i'll be switching to another plugin.

@camoz
Copy link

@camoz camoz commented on 0978a6c Jan 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that removing refresh on autocmds is a bad design decision, and have opened an issue about it: #1358

Please sign in to comment.