-
-
Notifications
You must be signed in to change notification settings - Fork 6.3k
fix(win): respect window size constraints and separator state #35601
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
This is a Vim bug: #32854 (comment) |
It might be a bit different — in nvim_open_win the window width is set directly, rather than resized🤔. I’m not completely sure about this. The fix for win_remove should also apply to Vim. I’ll try it out there. |
Looks like the vsep bug didn't exist back in v9.0.0000; gonna bisect to see what introduced it (hopefully it wasn't me 😇) |
because in
what's wrong of element ..always offline for me 😮💨 |
Yup, matrix servers are super duper offline at the moment 😁 |
ohhhh 💣 💥 |
Okay after bisecting looks like it was indeed me that caused the vsep issue in vim/vim@5866bc3 🚀 |
Okay, seems this isn't actually a bug in my commit (phew 😌); the vsep issue reproduces in versions like v9.0.0000 (probably even earlier), but only if It just so happens that before that commit, the frame layout is slightly different such that |
😊 that clears up my doubt. Since your commit didn’t change it, I was curious why it was that commit. |
src/nvim/window.c
Outdated
if (tp == NULL) { | ||
lastwin = curtab->tp_lastwin = wp->w_prev; | ||
} else { | ||
tp->tp_lastwin = wp->w_prev; | ||
} | ||
win_T *new_last = (tp == NULL) ? lastwin : tp->tp_lastwin; | ||
if (new_last != NULL && !new_last->w_floating) { | ||
new_last->w_vsep_width = 0; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this doesn't fix the vsep issue if the window with the vsep isn't the last window:
botright vsplit
set winfixwidth
botright vsplit
botright split
wincmd p
quit
I think it's probably more appropriate to fixup the vsep somewhere like winframe_remove
instead anyway? win_remove
relates moreso to the window list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
range FOR_ALL_WINDOWS_IN_TAB(fix_wp, target_tp)
and find if frp->fr_parent == NULL
then reset vsep_width
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh it works .. do you have any other more smart way 🤔
if (*dirp == 'h') {
tabpage_T *target_tp = tp ? tp : curtab;
FOR_ALL_WINDOWS_IN_TAB(fix_wp, target_tp) {
if (!fix_wp->w_floating) {
// Check if this window is now the rightmost in its row
frame_T *frp;
for (frp = fix_wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) {
if (frp->fr_parent->fr_layout == FR_ROW && frp->fr_next != NULL) {
break;
}
}
if (frp->fr_parent == NULL) {
fix_wp->w_vsep_width = 0; // rightmost window doesn't need separator
}
}
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That'll work (though the w_width
will be wrong), but it'll maybe be more efficient to check if the closed window was a rightmost window, and then recursively remove vseps of the windows to the left.
Might be a bit tricky; I'll see if I can come up with something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this'll work (and shouldn't cause issues with any unflattened frames after the remove):
diff --git a/src/nvim/window.c b/src/nvim/window.c
index d5f325bf4f..97fafcf3d4 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
@@ -3214,6 +3214,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);
@@ -3405,7 +3411,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.
@@ -3851,23 +3857,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 to windows at 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--;
}
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);
@@ -3876,7 +3885,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);
}
}
This assumes that if the window doesn't have a vsep, then it must already be a rightmost window (so it avoids the loop from before).
Similar to the same assumption I make in winframe_restore
, but the inverse.
win_new_width
is needed otherwise the last window column is wrong due to w_view_width
being out-of-date (same as this PR, but w_width
was also wrong). Not sure why it's not used when add
is true; might be correct to use it there too if it doesn't regress anything...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That doesn't call winframe_restore
and fails on master and v0.9 (maybe other versions too). Don't think it's worth looking into here.
You need to trigger E36 from moving (not splitting) a window with some 'wfw'
windows nearby. Maybe run the test via gdb or add some logging to check you're hitting it. There's some "no room" tests in that file that you can copy and adjust to make that easier.
I can write one later if you're stuck, but it might be a while as today's looking busy. 🥲
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe i can use help
win for test 🤔
I can write one later if you're stuck, but it might be a while as today's looking busy.
no worry :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sigh, we also shouldn't resize the frame if the 'wfw'
window will get the space anyway, otherwise it'll be off by one when it does...
Plus, we should only be checking if there is a 'wfw'
window in a rightmost leaf child in the left frame; frame_fixed_width
doesn't do that... (need to write a test specifically for this; I haven't done that here)
I think the following should fix those, but I'm starting to think fixing the resizing isn't worth all the complexity: 🫠
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 2cc3ea4ddb..7b540ecef0 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -3221,10 +3221,10 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp, frame_T **unflat_al
// 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);
- // Windows resized to fill their vseps. If one is 'winfixwidth', resize the frame to maintain
+ // Windows resize to fill their vseps. If one is 'winfixwidth', resize the frame to maintain
// its previous width.
- if (frame_fixed_width(frp_close->fr_prev)) {
+ bool right_has_wfw = frame_set_vsep(frp_close->fr_prev, false);
+ if (right_has_wfw && frp_close->fr_prev != altfr) {
frame_new_width(frp_close->fr_prev, frp_close->fr_prev->fr_width - 1, false, false);
}
}
@@ -3422,10 +3422,10 @@ void winframe_restore(win_T *wp, int dir, frame_T *unflat_altfr)
if (wp->w_vsep_width == 0 && frp->fr_parent->fr_layout == FR_ROW && frp->fr_prev != NULL) {
// Windows resize to fit their vseps. If one is 'winfixwidth', resize the frame to maintain its
// previous width.
- if (frame_fixed_width(frp->fr_prev)) {
+ bool right_had_wfw = frame_set_vsep(frp->fr_prev, true);
+ if (right_had_wfw && frp->fr_prev != unflat_altfr) {
frame_new_width(frp->fr_prev, frp->fr_prev->fr_width + 1, false, false);
}
- frame_set_vsep(frp->fr_prev, true);
}
// Statuslines or horizontal separators above may have been lost. Restore them.
@@ -3873,7 +3873,8 @@ static void frame_new_width(frame_T *topfrp, int width, bool leftfirst, bool wfw
/// Add or remove the vertical separator to windows at the right side of "frp".
/// Note: Does not check if there is room!
-static void frame_set_vsep(const frame_T *frp, bool add)
+/// @return whether any rightmost window has 'winfixwidth' set.
+static bool frame_set_vsep(const frame_T *frp, bool add)
FUNC_ATTR_NONNULL_ARG(1)
{
if (frp->fr_layout == FR_LEAF) {
@@ -3887,11 +3888,14 @@ static void frame_set_vsep(const frame_T *frp, bool add)
win_new_width(wp, wp->w_width + 1);
wp->w_vsep_width = 0;
}
+ return wp->w_p_wfw;
} else if (frp->fr_layout == FR_COL) {
// Handle all the frames in the column.
+ bool has_wfw = false;
FOR_ALL_FRAMES(frp, frp->fr_child) {
- frame_set_vsep(frp, add);
+ has_wfw = frame_set_vsep(frp, add) || has_wfw;
}
+ return has_wfw;
} else {
assert(frp->fr_layout == FR_ROW);
// Only need to handle the last frame in the row.
@@ -3899,7 +3903,7 @@ static void frame_set_vsep(const frame_T *frp, bool add)
while (frp->fr_next != NULL) {
frp = frp->fr_next;
}
- frame_set_vsep(frp, add);
+ return frame_set_vsep(frp, add);
}
}
diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua
index 0962547197..1177c0fca9 100644
--- a/test/functional/api/window_spec.lua
+++ b/test/functional/api/window_spec.lua
@@ -2296,6 +2296,15 @@ describe('API/win', function()
{ 20, 20 },
{ api.nvim_win_get_width(wins[#wins]), api.nvim_win_get_width(wins[#wins - 1]) }
)
+ -- If a 'winfixwidth' window gets the space anyway, check it has the correct size.
+ command('set winfixwidth | only | mode')
+ screen:expect([[
+ {5:1000 }|
+ ^ |
+ {1:~ }|*7
+ |
+ ]])
+ eq(eval('&columns'), api.nvim_win_get_width(0))
end)
it('opens a large right split window without crashing', function()
api.nvim_command('vsplit')
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perfect the resize case can be done in anther PR 🤔 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As you've already applied the change it can stay here, but it could do with a test where 'wfw'
doesn't cause the frame to resize if it's not the altfr
, if that's possible. (Also a winframe_restore
test as mentioned earlier)
021a17b
to
0412048
Compare
ad18b3f
to
2d41c77
Compare
5e78e95
to
64ac485
Compare
Problem: 1. Rightmost window retained vsep after adjacent window closed 2. Explicit width/height ignored due to win_equal() override 3. winfixwidth windows expanded when receiving space from closed windows Solution: 1. Clear vsep_width for new lastwin in win_remove() 2. Add WSP_NOEQUAL flag to skip equalization when size explicitly set 3. Skip frame resizing for winfixwidth windows in winframe_remove() Co-authored-by: Sean Dewar <[email protected]>
Problem:
Solution:
Fix #32854