aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Dewar <6256228+seandewar@users.noreply.github.com>2024-02-25 01:03:26 +0000
committerSean Dewar <6256228+seandewar@users.noreply.github.com>2024-03-08 23:24:04 +0000
commit1c6b693ec1592f9d193fc9cc1bb03e738fb2bef6 (patch)
tree9771486507e8955b3e1f906c4a150c938040ca0f
parent24dfa47e4f4ca41d0c5f8c1c0f851602362c81d3 (diff)
downloadrneovim-1c6b693ec1592f9d193fc9cc1bb03e738fb2bef6.tar.gz
rneovim-1c6b693ec1592f9d193fc9cc1bb03e738fb2bef6.tar.bz2
rneovim-1c6b693ec1592f9d193fc9cc1bb03e738fb2bef6.zip
vim-patch:9.1.0118: Use different restoration strategy in win_splitmove
Problem: saving and restoring all frames to split-move is overkill now that WinNewPre is not fired when split-moving. Solution: defer the flattening of frames until win_split_ins begins reorganising them, and attempt to restore the layout by undoing our changes. (Sean Dewar) https://github.com/vim/vim/commit/704966c2545897dfcf426dd9ef946aeb6fa80c38 Adjust winframe_restore to account for Nvim's horizontal separators when the global statusline is in use. Add a test.
-rw-r--r--src/nvim/api/win_config.c14
-rw-r--r--src/nvim/window.c267
-rw-r--r--src/nvim/winfloat.c2
-rw-r--r--test/functional/ui/statusline_spec.lua20
4 files changed, 152 insertions, 151 deletions
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
index c308cadb7c..978d8515c8 100644
--- a/src/nvim/api/win_config.c
+++ b/src/nvim/api/win_config.c
@@ -260,7 +260,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err
int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER;
if (parent == NULL) {
- wp = win_split_ins(0, flags, NULL, 0);
+ wp = win_split_ins(0, flags, NULL, 0, NULL);
} else {
tp = win_find_tabpage(parent);
switchwin_T switchwin;
@@ -268,7 +268,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err
const int result = switch_win(&switchwin, parent, tp, true);
assert(result == OK);
(void)result;
- wp = win_split_ins(0, flags, NULL, 0);
+ wp = win_split_ins(0, flags, NULL, 0, NULL);
restore_win(&switchwin, true);
}
if (wp) {
@@ -495,11 +495,11 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err)
// If the frame doesn't have a parent, the old frame
// was the root frame and we need to create a top-level split.
int dir;
- new_curwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp);
+ new_curwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, NULL);
} else if (n_frames == 2) {
// There are two windows in the frame, we can just rotate it.
int dir;
- neighbor = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp);
+ neighbor = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, NULL);
new_curwin = neighbor;
} else {
// There is only one window in the frame, we can't split it.
@@ -511,7 +511,7 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err)
parent = neighbor;
} else {
int dir;
- new_curwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp);
+ new_curwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, NULL);
}
win_remove(win, win_tp == curtab ? NULL : win_tp);
// move to neighboring window if we're moving the current window to a new tabpage
@@ -531,7 +531,7 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err)
int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER;
if (parent == NULL) {
- if (!win_split_ins(0, flags, win, 0)) {
+ if (!win_split_ins(0, flags, win, 0, NULL)) {
// TODO(willothy): What should this error message say?
api_set_error(err, kErrorTypeException, "Failed to split window");
return;
@@ -542,7 +542,7 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err)
const int result = switch_win(&switchwin, parent, win_find_tabpage(parent), true);
(void)result;
assert(result == OK);
- win_split_ins(0, flags, win, 0);
+ win_split_ins(0, flags, win, 0, NULL);
restore_win(&switchwin, true);
}
if (HAS_KEY_X(config, width)) {
diff --git a/src/nvim/window.c b/src/nvim/window.c
index b1135d59fc..2d0010ad6c 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -981,16 +981,19 @@ int win_split(int size, int flags)
clear_snapshot(curtab, SNAP_HELP_IDX);
}
- return win_split_ins(size, flags, NULL, 0) == NULL ? FAIL : OK;
+ return win_split_ins(size, flags, NULL, 0, NULL) == NULL ? FAIL : OK;
}
/// When "new_wp" is NULL: split the current window in two.
/// When "new_wp" is not NULL: insert this window at the far
/// top/left/right/bottom.
+/// When "to_flatten" is not NULL: flatten this frame before reorganising frames;
+/// remains unflattened on failure.
+///
/// On failure, if "new_wp" was not NULL, no changes will have been made to the
/// window layout or sizes.
/// @return NULL for failure, or pointer to new window
-win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir)
+win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir, frame_T *to_flatten)
{
win_T *wp = new_wp;
@@ -1261,6 +1264,11 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir)
CLEAR_FIELD(wp->w_border_adj);
}
+ // Going to reorganize frames now, make sure they're flat.
+ if (to_flatten != NULL) {
+ frame_flatten(to_flatten);
+ }
+
bool before;
frame_T *curfrp;
@@ -1915,38 +1923,29 @@ int win_splitmove(win_T *wp, int size, int flags)
return FAIL;
}
- frame_T *frp = NULL;
+ frame_T *unflat_altfr = NULL;
if (wp->w_floating) {
win_remove(wp, NULL);
} else {
- // Undoing changes to frames if splitting fails is complicated.
- // Save a full snapshot to restore instead.
- frp = make_full_snapshot();
-
- // Remove the window and frame from the tree of frames.
- winframe_remove(wp, &dir, NULL);
+ // Remove the window and frame from the tree of frames. Don't flatten any
+ // frames yet so we can restore things if win_split_ins fails.
+ winframe_remove(wp, &dir, NULL, &unflat_altfr);
win_remove(wp, NULL);
last_status(false); // may need to remove last status line
win_comp_pos(); // recompute window positions
}
// Split a window on the desired side and put "wp" there.
- if (win_split_ins(size, flags, wp, dir) == NULL) {
+ if (win_split_ins(size, flags, wp, dir, unflat_altfr) == NULL) {
win_append(wp->w_prev, wp);
if (!wp->w_floating) {
- // Restore the previous layout from the snapshot.
- xfree(wp->w_frame);
- restore_full_snapshot(frp);
-
- // Vertical separators to the left may have been lost. Restore them.
- frp = wp->w_frame;
- if (frp->fr_parent->fr_layout == FR_ROW && frp->fr_prev != NULL) {
- frame_add_vsep(frp->fr_prev);
- }
+ // win_split_ins doesn't change sizes or layout if it fails to insert an
+ // existing window, so just undo winframe_remove.
+ winframe_restore(wp, dir, unflat_altfr);
+ win_comp_pos(); // recompute window positions
}
return FAIL;
}
- clear_snapshot_rec(frp);
// If splitting horizontally, try to preserve height.
// Note that win_split_ins autocommands may have immediately made "wp" floating!
@@ -3065,7 +3064,7 @@ static win_T *win_free_mem(win_T *win, int *dirp, tabpage_T *tp)
if (!win->w_floating) {
// Remove the window and its frame from the tree of frames.
frame_T *frp = win->w_frame;
- wp = winframe_remove(win, dirp, tp);
+ wp = winframe_remove(win, dirp, tp, NULL);
xfree(frp);
} else {
*dirp = 'h'; // Dummy value.
@@ -3146,9 +3145,11 @@ void win_free_all(void)
///
/// @param dirp set to 'v' or 'h' for direction if 'ea'
/// @param tp tab page "win" is in, NULL for current
+/// @param unflat_altfr if not NULL, set to pointer of frame that got
+/// the space, and it is not flattened
///
/// @return a pointer to the window that got the freed up space.
-win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp)
+win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp, frame_T **unflat_altfr)
FUNC_ATTR_NONNULL_ARG(1, 2)
{
assert(tp == NULL || tp != curtab);
@@ -3236,56 +3237,110 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp)
frame_comp_pos(frp2, &row, &col);
}
- if (frp2->fr_next == NULL && frp2->fr_prev == NULL) {
- // There is no other frame in this list, move its info to the parent
- // and remove it.
- frp2->fr_parent->fr_layout = frp2->fr_layout;
- frp2->fr_parent->fr_child = frp2->fr_child;
- frame_T *frp;
- FOR_ALL_FRAMES(frp, frp2->fr_child) {
- frp->fr_parent = frp2->fr_parent;
- }
- frp2->fr_parent->fr_win = frp2->fr_win;
- if (frp2->fr_win != NULL) {
- frp2->fr_win->w_frame = frp2->fr_parent;
+ if (unflat_altfr == NULL) {
+ frame_flatten(frp2);
+ } else {
+ *unflat_altfr = frp2;
+ }
+
+ return wp;
+}
+
+/// Flatten "frp" into its parent frame if it's the only child, also merging its
+/// list with the grandparent if they share the same layout.
+/// Frees "frp" if flattened; also "frp->fr_parent" if it has the same layout.
+/// "frp" must be valid in the current tabpage.
+static void frame_flatten(frame_T *frp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (frp->fr_next != NULL || frp->fr_prev != NULL) {
+ return;
+ }
+
+ // There is no other frame in this list, move its info to the parent
+ // and remove it.
+ frp->fr_parent->fr_layout = frp->fr_layout;
+ frp->fr_parent->fr_child = frp->fr_child;
+ frame_T *frp2;
+ FOR_ALL_FRAMES(frp2, frp->fr_child) {
+ frp2->fr_parent = frp->fr_parent;
+ }
+ frp->fr_parent->fr_win = frp->fr_win;
+ if (frp->fr_win != NULL) {
+ frp->fr_win->w_frame = frp->fr_parent;
+ }
+ frp2 = frp->fr_parent;
+ if (topframe->fr_child == frp) {
+ topframe->fr_child = frp2;
+ }
+ xfree(frp);
+
+ frp = frp2->fr_parent;
+ if (frp != NULL && frp->fr_layout == frp2->fr_layout) {
+ // The frame above the parent has the same layout, have to merge
+ // the frames into this list.
+ if (frp->fr_child == frp2) {
+ frp->fr_child = frp2->fr_child;
+ }
+ assert(frp2->fr_child);
+ frp2->fr_child->fr_prev = frp2->fr_prev;
+ if (frp2->fr_prev != NULL) {
+ frp2->fr_prev->fr_next = frp2->fr_child;
+ }
+ for (frame_T *frp3 = frp2->fr_child;; frp3 = frp3->fr_next) {
+ frp3->fr_parent = frp;
+ if (frp3->fr_next == NULL) {
+ frp3->fr_next = frp2->fr_next;
+ if (frp2->fr_next != NULL) {
+ frp2->fr_next->fr_prev = frp3;
+ }
+ break;
+ }
}
- frp = frp2->fr_parent;
if (topframe->fr_child == frp2) {
topframe->fr_child = frp;
}
xfree(frp2);
+ }
+}
- frp2 = frp->fr_parent;
- if (frp2 != NULL && frp2->fr_layout == frp->fr_layout) {
- // The frame above the parent has the same layout, have to merge
- // the frames into this list.
- if (frp2->fr_child == frp) {
- frp2->fr_child = frp->fr_child;
- }
- assert(frp->fr_child);
- frp->fr_child->fr_prev = frp->fr_prev;
- if (frp->fr_prev != NULL) {
- frp->fr_prev->fr_next = frp->fr_child;
- }
- frame_T *frp3;
- for (frp3 = frp->fr_child;; frp3 = frp3->fr_next) {
- frp3->fr_parent = frp2;
- if (frp3->fr_next == NULL) {
- frp3->fr_next = frp->fr_next;
- if (frp->fr_next != NULL) {
- frp->fr_next->fr_prev = frp3;
- }
- break;
- }
- }
- if (topframe->fr_child == frp) {
- topframe->fr_child = frp2;
- }
- xfree(frp);
+/// Undo changes from a prior call to winframe_remove, also restoring lost
+/// vertical separators and statuslines.
+/// Caller must ensure no other changes were made to the layout or window sizes!
+static void winframe_restore(win_T *wp, int dir, frame_T *unflat_altfr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ frame_T *frp = wp->w_frame;
+
+ // Put "wp"'s frame back where it was.
+ if (frp->fr_prev != NULL) {
+ frame_append(frp->fr_prev, frp);
+ } else {
+ frame_insert(frp->fr_next, frp);
+ }
+
+ // 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);
+ }
+
+ // Statuslines or horizontal separators above may have been lost. Restore them.
+ if (frp->fr_parent->fr_layout == FR_COL && frp->fr_prev != NULL) {
+ if (global_stl_height() == 0 && wp->w_status_height == 0) {
+ frame_add_statusline(frp->fr_prev);
+ } else if (wp->w_hsep_height == 0) {
+ frame_add_hsep(frp->fr_prev);
}
}
- return wp;
+ // Restore the lost room that was redistributed to the altframe.
+ if (dir == 'v') {
+ frame_new_height(unflat_altfr, unflat_altfr->fr_height - frp->fr_height,
+ unflat_altfr == frp->fr_next, false);
+ } else if (dir == 'h') {
+ frame_new_width(unflat_altfr, unflat_altfr->fr_width - frp->fr_width,
+ unflat_altfr == frp->fr_next, false);
+ }
}
/// If 'splitbelow' or 'splitright' is set, the space goes above or to the left
@@ -7248,23 +7303,23 @@ void reset_lnums(void)
void make_snapshot(int idx)
{
clear_snapshot(curtab, idx);
- make_snapshot_rec(topframe, &curtab->tp_snapshot[idx], false);
+ make_snapshot_rec(topframe, &curtab->tp_snapshot[idx]);
}
-static void make_snapshot_rec(frame_T *fr, frame_T **frp, bool snap_wins)
+static void make_snapshot_rec(frame_T *fr, frame_T **frp)
{
*frp = xcalloc(1, sizeof(frame_T));
(*frp)->fr_layout = fr->fr_layout;
(*frp)->fr_width = fr->fr_width;
(*frp)->fr_height = fr->fr_height;
if (fr->fr_next != NULL) {
- make_snapshot_rec(fr->fr_next, &((*frp)->fr_next), snap_wins);
+ make_snapshot_rec(fr->fr_next, &((*frp)->fr_next));
}
if (fr->fr_child != NULL) {
- make_snapshot_rec(fr->fr_child, &((*frp)->fr_child), snap_wins);
+ make_snapshot_rec(fr->fr_child, &((*frp)->fr_child));
}
- if (fr->fr_layout == FR_LEAF && (snap_wins || fr->fr_win == curwin)) {
- (*frp)->fr_win = fr->fr_win;
+ if (fr->fr_layout == FR_LEAF && fr->fr_win == curwin) {
+ (*frp)->fr_win = curwin;
}
}
@@ -7381,80 +7436,6 @@ static win_T *restore_snapshot_rec(frame_T *sn, frame_T *fr)
return wp;
}
-/// Return a snapshot of all frames in the current tabpage and which windows are
-/// in them.
-/// Use clear_snapshot_rec to free the snapshot.
-static frame_T *make_full_snapshot(void)
-{
- frame_T *frp;
- make_snapshot_rec(topframe, &frp, true);
- return frp;
-}
-
-/// Restore all frames in the full snapshot "sn" for the current tabpage.
-/// Caller must ensure that the screen size didn't change, no windows with frames
-/// in the snapshot were freed, and windows with frames not in the snapshot are
-/// removed from their frames!
-/// Doesn't restore changed window vertical separators.
-/// Frees the old frames. Don't call clear_snapshot_rec on "sn" afterwards!
-static void restore_full_snapshot(frame_T *sn)
-{
- if (sn == NULL) {
- return;
- }
-
- clear_snapshot_rec(topframe);
- restore_full_snapshot_rec(sn);
- curtab->tp_topframe = topframe = sn;
- last_status(false);
-
- // If the amount of space available changed, first try setting the sizes of
- // windows with 'winfix{width,height}'. If that doesn't result in the right
- // size, forget about that option.
- if (topframe->fr_width != Columns) {
- frame_new_width(topframe, Columns, false, true);
- if (!frame_check_width(topframe, Columns)) {
- frame_new_width(topframe, Columns, false, false);
- }
- }
- if (topframe->fr_height != ROWS_AVAIL) {
- frame_new_height(topframe, (int)ROWS_AVAIL, false, true);
- if (!frame_check_height(topframe, (int)ROWS_AVAIL)) {
- frame_new_height(topframe, (int)ROWS_AVAIL, false, false);
- }
- }
-
- win_comp_pos();
-}
-
-static void restore_full_snapshot_rec(frame_T *sn)
-{
- if (sn == NULL) {
- return;
- }
-
- if (sn->fr_child != NULL) {
- sn->fr_child->fr_parent = sn;
- }
- if (sn->fr_next != NULL) {
- sn->fr_next->fr_parent = sn->fr_parent;
- sn->fr_next->fr_prev = sn;
- }
- if (sn->fr_win != NULL) {
- sn->fr_win->w_frame = sn;
- // Assume for now that all windows have statuslines, so last_status in restore_full_snapshot
- // doesn't resize frames to fit any missing statuslines.
- sn->fr_win->w_status_height = STATUS_HEIGHT;
- sn->fr_win->w_hsep_height = 0;
-
- // Resize window to fit the frame.
- frame_new_height(sn, sn->fr_height, false, false);
- frame_new_width(sn, sn->fr_width, false, false);
- }
- restore_full_snapshot_rec(sn->fr_child);
- restore_full_snapshot_rec(sn->fr_next);
-}
-
/// Check that "topfrp" and its children are at the right height.
///
/// @param topfrp top frame pointer
diff --git a/src/nvim/winfloat.c b/src/nvim/winfloat.c
index 8fe0315230..cddc4ed9dd 100644
--- a/src/nvim/winfloat.c
+++ b/src/nvim/winfloat.c
@@ -57,7 +57,7 @@ win_T *win_new_float(win_T *wp, bool last, WinConfig fconfig, Error *err)
return NULL;
}
int dir;
- winframe_remove(wp, &dir, NULL);
+ winframe_remove(wp, &dir, NULL, NULL);
XFREE_CLEAR(wp->w_frame);
win_comp_pos(); // recompute window positions
win_remove(wp, NULL);
diff --git a/test/functional/ui/statusline_spec.lua b/test/functional/ui/statusline_spec.lua
index fee4b64d44..000e2b1b04 100644
--- a/test/functional/ui/statusline_spec.lua
+++ b/test/functional/ui/statusline_spec.lua
@@ -11,6 +11,7 @@ local exec = helpers.exec
local exec_lua = helpers.exec_lua
local eval = helpers.eval
local sleep = vim.uv.sleep
+local pcall_err = helpers.pcall_err
local mousemodels = { 'extend', 'popup', 'popup_setpos' }
@@ -474,6 +475,25 @@ describe('global statusline', function()
|
]])
end)
+
+ it('horizontal separators unchanged when failing to split-move window', function()
+ exec([[
+ botright split
+ let &winwidth = &columns
+ let &winminwidth = &columns
+ ]])
+ eq('Vim(wincmd):E36: Not enough room', pcall_err(command, 'wincmd L'))
+ command('mode')
+ screen:expect([[
+ |
+ {1:~ }|*5
+ ────────────────────────────────────────────────────────────|
+ ^ |
+ {1:~ }|*6
+ {2:[No Name] 0,0-1 All}|
+ |
+ ]])
+ end)
end)
it('statusline does not crash if it has Arabic characters #19447', function()