commit 32d3dd06504eeb4391e255bb29e38089ed803da9
parent 2478a7fbbdcf2fe971e5a606b0a6b0ba45dee0f7
Author: Sean Dewar <6256228+seandewar@users.noreply.github.com>
Date: Mon, 23 Feb 2026 16:22:13 +0000
fix(statusline): broken statusline on error #38000
Problem: after #33036, an error from evaluating 'statusline' clears it and
doesn't draw the statusline. (causing glitchy redraws)
Solution: use the default value instead. If 'stl' is somehow ever empty, still
call redraw_custom_statusline to at least draw an empty statusline.
Ideally our default 'stl' shouldn't itself error too! :-)
Also adjust some prior screen:expect()s to avoid immediate success warnings.
Diffstat:
6 files changed, 62 insertions(+), 21 deletions(-)
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
@@ -6286,10 +6286,10 @@ A jump table for the options with a short description can be found at |Q_op|.
current window and buffer, while %{} items are evaluated in the
context of the window that the statusline belongs to.
- When there is error while evaluating the option then it will be made
- empty to avoid further errors. Otherwise screen updating would loop.
- When the result contains unprintable characters the result is
- unpredictable.
+ When there is an error while evaluating the option it will be reset to
+ its default value to avoid further errors. Otherwise screen updating
+ would loop. When the result contains unprintable characters the
+ result is unpredictable.
Note that the only effect of 'ruler' when this option is set (and
'laststatus' is 2 or 3) is controlling the output of |CTRL-G|.
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
@@ -6728,10 +6728,10 @@ vim.wo.stc = vim.wo.statuscolumn
--- current window and buffer, while %{} items are evaluated in the
--- context of the window that the statusline belongs to.
---
---- When there is error while evaluating the option then it will be made
---- empty to avoid further errors. Otherwise screen updating would loop.
---- When the result contains unprintable characters the result is
---- unpredictable.
+--- When there is an error while evaluating the option it will be reset to
+--- its default value to avoid further errors. Otherwise screen updating
+--- would loop. When the result contains unprintable characters the
+--- result is unpredictable.
---
--- Note that the only effect of 'ruler' when this option is set (and
--- 'laststatus' is 2 or 3) is controlling the output of `CTRL-G`.
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
@@ -8794,10 +8794,10 @@ local options = {
current window and buffer, while %{} items are evaluated in the
context of the window that the statusline belongs to.
- When there is error while evaluating the option then it will be made
- empty to avoid further errors. Otherwise screen updating would loop.
- When the result contains unprintable characters the result is
- unpredictable.
+ When there is an error while evaluating the option it will be reset to
+ its default value to avoid further errors. Otherwise screen updating
+ would loop. When the result contains unprintable characters the
+ result is unpredictable.
Note that the only effect of 'ruler' when this option is set (and
'laststatus' is 2 or 3) is controlling the output of |CTRL-G|.
diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c
@@ -85,8 +85,7 @@ void win_redr_status(win_T *wp)
// Don't redraw right now, do it later. Don't update status line when
// popup menu is visible and may be drawn over it
wp->w_redr_status = true;
- } else if (*wp->w_p_stl != NUL
- || (*p_stl != NUL && (!wp->w_floating || (is_stl_global && wp == curwin)))) {
+ } else if (*wp->w_p_stl != NUL || !wp->w_floating || (is_stl_global && wp == curwin)) {
// redraw custom status line
redraw_custom_statusline(wp);
}
@@ -2136,13 +2135,12 @@ stcsign:
redraw_not_allowed = save_redraw_not_allowed;
// Check for an error. If there is one the display will be messed up and
- // might loop redrawing. Avoid that by making the corresponding option
- // empty.
+ // might loop redrawing. Avoid that by setting the option to its default.
// TODO(Bram): find out why using called_emsg_before makes tests fail, does it
// matter?
// if (called_emsg > called_emsg_before)
if (opt_idx != kOptInvalid && did_emsg > did_emsg_before) {
- set_option_direct(opt_idx, STATIC_CSTR_AS_OPTVAL(""), opt_scope, SID_ERROR);
+ set_option_direct(opt_idx, get_option_default(opt_idx, opt_scope), opt_scope, SID_ERROR);
}
// A user function may reset KeyTyped, restore it.
diff --git a/test/functional/ui/statusline_spec.lua b/test/functional/ui/statusline_spec.lua
@@ -949,6 +949,12 @@ describe('default statusline', function()
it('setting statusline to empty string sets default statusline', function()
exec_lua("vim.o.statusline = 'asdf'")
eq('asdf', eval('&statusline'))
+ screen:expect([[
+ ^ |
+ {1:~ }|*13
+ {3:asdf }|
+ |
+ ]])
local default_statusline = table.concat({
'%<',
@@ -962,15 +968,37 @@ describe('default statusline', function()
})
exec_lua("vim.o.statusline = ''")
-
eq(default_statusline, eval('&statusline'))
-
screen:expect([[
^ |
{1:~ }|*13
{3:[No Name] 0,0-1 All}|
|
]])
+
+ -- Reset to default (via the correct scope) if there's an error.
+ command('setglobal statusline=%{a%}')
+ eq(default_statusline, eval('&statusline'))
+ eq(default_statusline, eval('&g:statusline'))
+ eq('', eval('&l:statusline'))
+ command('redrawstatus') -- like Vim, statusline isn't immediately redrawn after an error
+ screen:expect([[
+ ^ |
+ {1:~ }|*13
+ {3:[No Name] 0,0-1 All}|
+ {9:E121: Undefined variable: a} |
+ ]])
+ command('setlocal statusline=%{b%}')
+ eq(default_statusline, eval('&statusline'))
+ eq(default_statusline, eval('&g:statusline'))
+ eq('', eval('&l:statusline'))
+ command('redrawstatus') -- like Vim, statusline isn't immediately redrawn after an error
+ screen:expect([[
+ ^ |
+ {1:~ }|*13
+ {3:[No Name] 0,0-1 All}|
+ {9:E121: Undefined variable: b} |
+ ]])
end)
it('shows busy status when buffer is set to be busy', function()
@@ -1064,7 +1092,7 @@ describe("'statusline' in floatwin", function()
-- no statusline is displayed because the statusline option was cleared
api.nvim_win_set_config(win, cfg)
- screen:expect(without_stl)
+ screen:expect_unchanged()
-- displayed after the option is reset
command(set_stl)
@@ -1090,6 +1118,19 @@ describe("'statusline' in floatwin", function()
]])
command('tabfirst')
api.nvim_win_set_config(win2, { style = 'minimal' })
+ screen:expect([[
+ {5: }{101:2}{5:+ [No Name] }{24: }{102:2}{24:+ [No Name] }{2: }{24:X}|
+ ┌──────────┐ |
+ {1:~}│{4:^1 }│{1: }|
+ {1:~}│{4:2 }│{1: }|
+ {1:~}│{4:3 }│{1: }|
+ {1:~}│{4:4 }│{1: }|
+ {1:~}│{3:<Name] [+]}│{1: }|
+ {1:~}└──────────┘{1: }|
+ {1:~ }|*10
+ {2:[No Name] }|
+ |
+ ]])
command('tabnext')
screen:expect([[
{24: }{102:2}{24:+ [No Name] }{5: }{101:2}{5:+ [No Name] }{2: }{24:X}|
diff --git a/test/old/testdir/test_statusline.vim b/test/old/testdir/test_statusline.vim
@@ -64,7 +64,9 @@ func Test_statusline_will_be_disabled_with_error()
catch
endtry
call assert_true(s:func_in_statusline_called)
- call assert_equal('', &statusline)
+ " Nvim: resets to default value instead.
+ " call assert_equal('', &statusline)
+ call assert_equal(nvim_get_option_info2('statusline', {}).default, &statusline)
set statusline=
endfunc