From 7923e847ca5db131c22cef9f8d93454f5b6a47de Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Sun, 5 Oct 2025 18:17:43 +0100 Subject: [PATCH] vim-patch:9.1.1831: stray vseps in right-most 'winfixwidth' window Problem: vertical separator of 'winfixwidth' windows may remain if they become right-most windows from closing windows to the right. Solution: Don't implicitly rely on frame_new_width to fix vseps, as the call may be skipped for 'winfixwidth' windows to preserve their width; do it explicitly in winframe_remove (Sean Dewar). Note that I prefer win_new_width here over setting w_width directly, which would've previously been done by win_split_ins after frame_add_vsep, as this wasn't true for winframe_remove. Though the equivalent issue of bottom 'winfixheight' windows leaving stray statuslines with &ls=0 doesn't seem to exist, test it anyway. closes: vim/vim#18481 https://github.com/vim/vim/commit/620c6556778a0df15be4fa33647fff1f6ab36255 Nvim: calling win_new_width over setting w_width directly is especially important in making sure stuff like w_view_width is correct here. Co-authored-by: Sean Dewar <6256228+seandewar@users.noreply.github.com> --- src/nvim/window.c | 25 ++++++---- test/functional/legacy/window_cmd_spec.lua | 53 ++++++++++++++++++++++ test/old/testdir/test_window_cmd.vim | 46 +++++++++++++++++++ 3 files changed, 116 insertions(+), 8 deletions(-) diff --git a/src/nvim/window.c b/src/nvim/window.c index 85673566bb..5c148ca9c7 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1440,7 +1440,7 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir, frame_T *to_fl } if (toplevel) { if (flags & WSP_BOT) { - frame_add_vsep(curfrp); + frame_set_vsep(curfrp, true); } // Set width of neighbor frame frame_new_width(curfrp, curfrp->fr_width @@ -3230,6 +3230,12 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp, frame_T **unflat_al int row = topleft->w_winrow; int col = topleft->w_wincol; + // If this is a rightmost window, remove vertical separators to the left. + if (win->w_vsep_width == 0 && frp_close->fr_parent->fr_layout == FR_ROW + && frp_close->fr_prev != NULL) { + frame_set_vsep(frp_close->fr_prev, false); + } + // Remove this frame from the list of frames. frame_remove(frp_close); @@ -3422,7 +3428,7 @@ void winframe_restore(win_T *wp, int dir, frame_T *unflat_altfr) // Vertical separators to the left may have been lost. Restore them. if (wp->w_vsep_width == 0 && frp->fr_parent->fr_layout == FR_ROW && frp->fr_prev != NULL) { - frame_add_vsep(frp->fr_prev); + frame_set_vsep(frp->fr_prev, true); } // Statuslines or horizontal separators above may have been lost. Restore them. @@ -3868,23 +3874,26 @@ static void frame_new_width(frame_T *topfrp, int width, bool leftfirst, bool wfw topfrp->fr_width = width; } -/// Add the vertical separator to windows at the right side of "frp". +/// Add or remove the vertical separator of windows to the right side of "frp". /// Note: Does not check if there is room! -static void frame_add_vsep(const frame_T *frp) +static void frame_set_vsep(const frame_T *frp, bool add) FUNC_ATTR_NONNULL_ARG(1) { if (frp->fr_layout == FR_LEAF) { win_T *wp = frp->fr_win; - if (wp->w_vsep_width == 0) { + if (add && wp->w_vsep_width == 0) { if (wp->w_width > 0) { // don't make it negative - wp->w_width--; + win_new_width(wp, wp->w_width - 1); } wp->w_vsep_width = 1; + } else if (!add && wp->w_vsep_width == 1) { + win_new_width(wp, wp->w_width + 1); + wp->w_vsep_width = 0; } } else if (frp->fr_layout == FR_COL) { // Handle all the frames in the column. FOR_ALL_FRAMES(frp, frp->fr_child) { - frame_add_vsep(frp); + frame_set_vsep(frp, add); } } else { assert(frp->fr_layout == FR_ROW); @@ -3893,7 +3902,7 @@ static void frame_add_vsep(const frame_T *frp) while (frp->fr_next != NULL) { frp = frp->fr_next; } - frame_add_vsep(frp); + frame_set_vsep(frp, add); } } diff --git a/test/functional/legacy/window_cmd_spec.lua b/test/functional/legacy/window_cmd_spec.lua index 37d1101ff2..6f71e17e97 100644 --- a/test/functional/legacy/window_cmd_spec.lua +++ b/test/functional/legacy/window_cmd_spec.lua @@ -1,11 +1,14 @@ +local t = require('test.testutil') local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') local clear = n.clear +local eq = t.eq local exec = n.exec local exec_lua = n.exec_lua local command = n.command local feed = n.feed +local fn = n.fn -- oldtest: Test_window_cmd_ls0_split_scrolling() it('scrolling with laststatus=0 and :botright split', function() @@ -354,3 +357,53 @@ describe('splitkeep', function() ]]) end) end) + +-- oldtest: Test_winfixsize_vsep_statusline() +it("'winfixwidth/height' does not leave stray vseps/statuslines", function() + clear() + local screen = Screen.new(75, 8) + exec([[ + set noequalalways splitbelow splitright + vsplit + setlocal winfixwidth + vsplit + ]]) + eq(16, fn.winwidth(1)) + eq(37, fn.winwidth(2)) + eq(20, fn.winwidth(3)) + + command('quit') + screen:expect([[ + ^ │ | + {1:~ }│{1:~ }|*5 + {3:[No Name] }{2:[No Name] }| + | + ]]) + -- Checks w_view_width in Nvim; especially important as the screen test may still pass if only + -- w_width changed to make room for the vsep. + eq(36, fn.winwidth(1)) + eq(38, fn.winwidth(2)) + + exec([[ + set laststatus=0 + only + split + set winfixheight + split + ]]) + eq(1, fn.winheight(1)) + eq(3, fn.winheight(2)) + eq(1, fn.winheight(3)) + + command('quit') + screen:expect([[ + ^ | + {1:~ }| + {3:[No Name] }| + | + {1:~ }|*3 + | + ]]) + eq(2, fn.winheight(1)) + eq(4, fn.winheight(2)) +end) diff --git a/test/old/testdir/test_window_cmd.vim b/test/old/testdir/test_window_cmd.vim index ae59adbc6e..6439b9d4a3 100644 --- a/test/old/testdir/test_window_cmd.vim +++ b/test/old/testdir/test_window_cmd.vim @@ -2261,4 +2261,50 @@ func Test_winfixsize_positions() %bwipe endfunc +func Test_winfixsize_vsep_statusline() + CheckScreendump + let lines =<< trim END + set noequalalways splitbelow splitright + vsplit + setlocal winfixwidth + vsplit + func SetupWfh() + set laststatus=0 + only + split + set winfixheight + split + endfunc + END + call writefile(lines, 'XTestWinfixsizeVsepStatusline', 'D') + let buf = RunVimInTerminal('-S XTestWinfixsizeVsepStatusline', #{rows: 8}) + + call term_sendkeys(buf, ":echo winwidth(1) winwidth(2) winwidth(3)\n") + call WaitForAssert({-> assert_match('^16 37 20\>', term_getline(buf, 8))}) + + call term_sendkeys(buf, ":quit\n") + call VerifyScreenDump(buf, 'Test_winfixsize_vsep_statusline_1', {}) + + " Reported widths should be consistent with the screen dump. + call term_sendkeys(buf, ":echo winwidth(1) winwidth(2)\n") + " (May be better if 'wfw' window remains at 37 columns, but the resize is + " consistent with how things currently work for 'winfix*' windows) + call WaitForAssert({-> assert_match('^36 38\>', term_getline(buf, 8))}) + + " For good measure, also check bottom-most 'winfixheight' windows don't leave + " stray statuslines with &laststatus=0. + call term_sendkeys(buf, + \ ":call SetupWfh() | echo winheight(1) winheight(2) winheight(3)\n") + call WaitForAssert({-> assert_match('^1 3 1\>', term_getline(buf, 8))}) + + call term_sendkeys(buf, ":quit\n") + call VerifyScreenDump(buf, 'Test_winfixsize_vsep_statusline_2', {}) + + call term_sendkeys(buf, ":echo winheight(1) winheight(2)\n") + " (Likewise, may be better if 'wfh' window remains at 3 rows) + call WaitForAssert({-> assert_match('^2 4\>', term_getline(buf, 8))}) + + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab