diff --git a/src/nvim/option.c b/src/nvim/option.c index 0e33292f77..4644fba49a 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1579,6 +1579,7 @@ int do_set(char *arg, int opt_flags) arg++; // Only for :set command set global value of local options. set_options_default(opt_flags); + didset_options_all(); didset_options(); didset_options2(); ui_refresh_options(); @@ -1827,6 +1828,26 @@ static void didset_options2(void) tabstop_set(curbuf->b_p_vts, &curbuf->b_p_vts_array); } +/// Repair UI state after `:set all&`. +/// +/// `set_options_default` resets option values via `set_option_default` and +/// `set_option_direct` without invoking per-option `did_set` callbacks, so +/// UI-derived state (cursor shape, statusline, tabline) can get out of sync. +/// This function patches the known cases. +/// +/// Note: We intentionally do not replay all `did_set` callbacks +/// (`opt_did_set_cb`) because they have order-dependent side effects and +/// old/new transition logic that does not hold when values are already reset. +static void didset_options_all(void) +{ + const char *errmsg = parse_shape_opt(SHAPE_CURSOR); + assert(errmsg == NULL); + (void)errmsg; + last_status(false); + win_float_update_statusline(); + win_new_screen_rows(); +} + /// Check for string options that are NULL (normally only termcap options). void check_options(void) { diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua index 1a075534ed..fb636df3af 100644 --- a/test/functional/ui/cursor_spec.lua +++ b/test/functional/ui/cursor_spec.lua @@ -378,6 +378,22 @@ describe('ui/cursor', function() end) end) + it("'set all&' reapplies 'guicursor'", function() + command('set guicursor=n:ver25') + screen:expect(function() + eq('vertical', screen._mode_info[1].cursor_shape) + eq(25, screen._mode_info[1].cell_percentage) + end) + + command('set all&') + screen:expect(function() + eq('block', screen._mode_info[1].cursor_shape) + eq(0, screen._mode_info[1].blinkon) + eq(0, screen._mode_info[1].blinkoff) + eq(true, screen._cursor_style_enabled) + end) + end) + it(':sleep does not hide cursor when sleeping', function() n.feed(':sleep 300m | echo 42') screen:expect([[ diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua index 7dda24207f..0ce427fbb1 100644 --- a/test/functional/ui/options_spec.lua +++ b/test/functional/ui/options_spec.lua @@ -172,6 +172,33 @@ describe('UI receives option updates', function() end) end) + it("restores statusline and tabline after 'set all&'", function() + reset() + command('tabnew | tabnew') + command('set laststatus=0 showtabline=0') + screen:expect({ + unchanged = true, + condition = function() + local function row_text(row) + local chunks = {} + for _, cell in ipairs(screen._grid.rows[row]) do + table.insert(chunks, cell.text) + end + return table.concat(chunks) + end + + eq(nil, row_text(1):find('%[No Name%]')) + eq(nil, row_text(screen._grid.height - 1):find('All', 1, true)) + end, + }) + + command('set all&') + screen:expect({ + unchanged = true, + any = { '[No Name]', 'All' }, + }) + end) + it('with UI extensions', function() local expected = reset({ ext_cmdline = true, ext_wildmenu = true })