diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 978d8515c8..bb28000719 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -259,18 +259,20 @@ 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, NULL); - } else { - tp = win_find_tabpage(parent); - switchwin_T switchwin; - // `parent` is valid in `tp`, so switch_win should not fail. - const int result = switch_win(&switchwin, parent, tp, true); - assert(result == OK); - (void)result; - wp = win_split_ins(0, flags, NULL, 0, NULL); - restore_win(&switchwin, true); - } + TRY_WRAP(err, { + if (parent == NULL || parent == curwin) { + wp = win_split_ins(0, flags, NULL, 0, NULL); + } else { + tp = win_find_tabpage(parent); + switchwin_T switchwin; + // `parent` is valid in `tp`, so switch_win should not fail. + const int result = switch_win(&switchwin, parent, tp, true); + assert(result == OK); + (void)result; + wp = win_split_ins(0, flags, NULL, 0, NULL); + restore_win(&switchwin, true); + } + }); if (wp) { wp->w_config = fconfig; } @@ -278,7 +280,9 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err wp = win_new_float(NULL, false, fconfig, err); } if (!wp) { - api_set_error(err, kErrorTypeException, "Failed to create window"); + if (!ERROR_SET(err)) { + api_set_error(err, kErrorTypeException, "Failed to create window"); + } return 0; } @@ -448,17 +452,47 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) return; // error already set } - if (was_split) { - win_T *new_curwin = NULL; + bool to_split_ok = false; + // If we are moving curwin to another tabpage, switch windows *before* we remove it from the + // window list or remove its frame (if non-floating), so it's valid for autocommands. + const bool curwin_moving_tp + = win == curwin && parent != NULL && win_tp != win_find_tabpage(parent); + if (curwin_moving_tp) { + if (was_split) { + int dir; + win_goto(winframe_find_altwin(win, &dir, NULL, NULL)); + } else { + win_goto(win_valid(prevwin) && prevwin != win ? prevwin : firstwin); + } + // Autocommands may have been a real nuisance and messed things up... + if (curwin == win) { + api_set_error(err, kErrorTypeException, "Failed to switch away from window %d", + win->handle); + return; + } + win_tp = win_find_tabpage(win); + if (!win_tp || !win_valid_any_tab(parent)) { + api_set_error(err, kErrorTypeException, "Windows to split were closed"); + goto restore_curwin; + } + if (was_split == win->w_floating || parent->w_floating) { + api_set_error(err, kErrorTypeException, "Floating state of windows to split changed"); + goto restore_curwin; + } + } + + int dir = 0; + frame_T *unflat_altfr = NULL; + if (was_split) { // If the window is the last in the tabpage or `fconfig.win` is // a handle to itself, we can't split it. if (win->w_frame->fr_parent == NULL) { // FIXME(willothy): if the window is the last in the tabpage but there is another tabpage // and the target window is in that other tabpage, should we move the window to that // tabpage and close the previous one, or just error? - api_set_error(err, kErrorTypeValidation, "Cannot move last window"); - return; + api_set_error(err, kErrorTypeException, "Cannot move last window"); + goto restore_curwin; } else if (parent != NULL && parent->handle == win->handle) { int n_frames = 0; for (frame_T *fr = win->w_frame->fr_parent->fr_child; fr != NULL; fr = fr->fr_next) { @@ -494,64 +528,67 @@ 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, NULL); + winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr); } 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, NULL); - new_curwin = neighbor; + neighbor = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr); } else { // There is only one window in the frame, we can't split it. - api_set_error(err, kErrorTypeValidation, "Cannot split window into itself"); - return; + api_set_error(err, kErrorTypeException, "Cannot split window into itself"); + goto restore_curwin; } - // Set the parent to whatever the correct - // neighbor window was determined to be. + // Set the parent to whatever the correct neighbor window was determined to be. parent = neighbor; } else { - int dir; - new_curwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, NULL); + winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr); } - win_remove(win, win_tp == curtab ? NULL : win_tp); - // move to neighboring window if we're moving the current window to a new tabpage - if (curwin == win && parent != NULL && new_curwin != NULL - && win_tp != win_find_tabpage(parent)) { - win_enter(new_curwin, true); - if (!win_valid_any_tab(parent)) { - // win_enter autocommands closed the `parent` to split from. - api_set_error(err, kErrorTypeException, "Window to split was closed"); - return; - } - } - } else { - win_remove(win, win_tp == curtab ? NULL : win_tp); } + win_remove(win, win_tp == curtab ? NULL : win_tp); int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER; - - if (parent == NULL) { - 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; - } - } else { + TRY_WRAP(err, { + const bool need_switch = parent != NULL && parent != curwin; switchwin_T switchwin; - // `parent` is valid in its tabpage, so switch_win should not fail. - const int result = switch_win(&switchwin, parent, win_find_tabpage(parent), true); - (void)result; - assert(result == OK); - win_split_ins(0, flags, win, 0, NULL); - restore_win(&switchwin, true); + if (need_switch) { + // `parent` is valid in its tabpage, so switch_win should not fail. + const int result = switch_win(&switchwin, parent, win_find_tabpage(parent), true); + (void)result; + assert(result == OK); + } + to_split_ok = win_split_ins(0, flags, win, 0, unflat_altfr) != NULL; + if (!to_split_ok) { + // Restore `win` to the window list now, so it's valid for restore_win (if used). + win_append(win->w_prev, win, win_tp == curtab ? NULL : win_tp); + } + if (need_switch) { + restore_win(&switchwin, true); + } + }); + if (!to_split_ok) { + if (was_split) { + // win_split_ins doesn't change sizes or layout if it fails to insert an existing window, so + // just undo winframe_remove. + winframe_restore(win, dir, unflat_altfr); + } + if (!ERROR_SET(err)) { + api_set_error(err, kErrorTypeException, "Failed to move window %d into split", win->handle); + } + +restore_curwin: + // If `win` was the original curwin, and autocommands didn't move it outside of curtab, be a + // good citizen and try to return to it. + if (curwin_moving_tp && win_valid(win)) { + win_goto(win); + } + return; } + if (HAS_KEY_X(config, width)) { win_setwidth_win(fconfig.width, win); } if (HAS_KEY_X(config, height)) { win_setheight_win(fconfig.height, win); } - return; } else { win_config_float(win, fconfig); win->w_pos_changed = true; diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 3f93906942..652b6ba74e 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -1333,7 +1333,7 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) block_autocmds(); // We don't want BufEnter/WinEnter autocommands. if (need_append) { - win_append(lastwin, auc_win); + win_append(lastwin, auc_win, NULL); pmap_put(int)(&window_handles, auc_win->handle, auc_win); win_config_float(auc_win, auc_win->w_config); } diff --git a/src/nvim/window.c b/src/nvim/window.c index 80cb9ab6a0..b55dd79260 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1218,13 +1218,13 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir, frame_T *to_fl if (new_wp == NULL) { wp = win_alloc(oldwin, false); } else { - win_append(oldwin, wp); + win_append(oldwin, wp, NULL); } } else { if (new_wp == NULL) { wp = win_alloc(oldwin->w_prev, false); } else { - win_append(oldwin->w_prev, wp); + win_append(oldwin->w_prev, wp, NULL); } } @@ -1783,13 +1783,13 @@ static void win_exchange(int Prenum) if (wp->w_prev != curwin) { win_remove(curwin, NULL); frame_remove(curwin->w_frame); - win_append(wp->w_prev, curwin); + win_append(wp->w_prev, curwin, NULL); frame_insert(frp, curwin->w_frame); } if (wp != wp2) { win_remove(wp, NULL); frame_remove(wp->w_frame); - win_append(wp2, wp); + win_append(wp2, wp, NULL); if (frp2 == NULL) { frame_insert(wp->w_frame->fr_parent->fr_child, wp->w_frame); } else { @@ -1863,7 +1863,7 @@ static void win_rotate(bool upwards, int count) // find last frame and append removed window/frame after it for (; frp->fr_next != NULL; frp = frp->fr_next) {} - win_append(frp->fr_win, wp1); + win_append(frp->fr_win, wp1, NULL); frame_append(frp, wp1->w_frame); wp2 = frp->fr_win; // previously last window @@ -1878,7 +1878,7 @@ static void win_rotate(bool upwards, int count) assert(frp->fr_parent->fr_child); // append the removed window/frame before the first in the list - win_append(frp->fr_parent->fr_child->fr_win->w_prev, wp1); + win_append(frp->fr_parent->fr_child->fr_win->w_prev, wp1, NULL); frame_insert(frp->fr_parent->fr_child, frp); } @@ -1937,7 +1937,7 @@ int win_splitmove(win_T *wp, int size, int flags) // Split a window on the desired side and put "wp" there. if (win_split_ins(size, flags, wp, dir, unflat_altfr) == NULL) { - win_append(wp->w_prev, wp); + win_append(wp->w_prev, wp, NULL); if (!wp->w_floating) { // win_split_ins doesn't change sizes or layout if it fails to insert an // existing window, so just undo winframe_remove. @@ -2016,7 +2016,7 @@ void win_move_after(win_T *win1, win_T *win2) } win_remove(win1, NULL); frame_remove(win1->w_frame); - win_append(win2, win1); + win_append(win2, win1, NULL); frame_append(win2->w_frame, win1->w_frame); win_comp_pos(); // recompute w_winrow for all windows @@ -3151,6 +3151,55 @@ void win_free_all(void) /// @return a pointer to the window that got the freed up space. win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp, frame_T **unflat_altfr) FUNC_ATTR_NONNULL_ARG(1, 2) +{ + frame_T *altfr; + win_T *wp = winframe_find_altwin(win, dirp, tp, &altfr); + if (wp == NULL) { + return NULL; + } + + frame_T *frp_close = win->w_frame; + // Remove this frame from the list of frames. + frame_remove(frp_close); + + if (*dirp == 'v') { + frame_new_height(altfr, altfr->fr_height + frp_close->fr_height, + altfr == frp_close->fr_next, false); + } else { + assert(*dirp == 'h'); + frame_new_width(altfr, altfr->fr_width + frp_close->fr_width, + altfr == frp_close->fr_next, false); + } + + // If rows/columns go to a window below/right its positions need to be + // updated. Can only be done after the sizes have been updated. + if (altfr == frp_close->fr_next) { + int row = win->w_winrow; + int col = win->w_wincol; + + frame_comp_pos(altfr, &row, &col); + } + + if (unflat_altfr == NULL) { + frame_flatten(altfr); + } else { + *unflat_altfr = altfr; + } + + return wp; +} + +/// Find the window that will get the freed space from a call to `winframe_remove`. +/// Makes no changes to the window layout. +/// +/// @param dirp set to 'v' or 'h' for the direction where "altfr" will be resized +/// to fill the space +/// @param tp tab page "win" is in, NULL for current +/// @param altfr if not NULL, set to pointer of frame that will get the space +/// +/// @return a pointer to the window that will get the freed up space. +win_T *winframe_find_altwin(win_T *win, int *dirp, tabpage_T *tp, frame_T **altfr) + FUNC_ATTR_NONNULL_ARG(1, 2) { assert(tp == NULL || tp != curtab); @@ -3161,13 +3210,10 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp, frame_T **unflat_al frame_T *frp_close = win->w_frame; - // Remove the window from its frame. + // Find the window and frame that gets the space. frame_T *frp2 = win_altframe(win, tp); win_T *wp = frame2win(frp2); - // Remove this frame from the list of frames. - frame_remove(frp_close); - if (frp_close->fr_parent->fr_layout == FR_COL) { // When 'winfixheight' is set, try to find another frame in the column // (as close to the closed frame as possible) to distribute the height @@ -3194,8 +3240,6 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp, frame_T **unflat_al } } } - frame_new_height(frp2, frp2->fr_height + frp_close->fr_height, - frp2 == frp_close->fr_next, false); *dirp = 'v'; } else { // When 'winfixwidth' is set, try to find another frame in the column @@ -3223,24 +3267,12 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp, frame_T **unflat_al } } } - frame_new_width(frp2, frp2->fr_width + frp_close->fr_width, - frp2 == frp_close->fr_next, false); *dirp = 'h'; } - // If rows/columns go to a window below/right its positions need to be - // updated. Can only be done after the sizes have been updated. - if (frp2 == frp_close->fr_next) { - int row = win->w_winrow; - int col = win->w_wincol; - - frame_comp_pos(frp2, &row, &col); - } - - if (unflat_altfr == NULL) { - frame_flatten(frp2); - } else { - *unflat_altfr = frp2; + assert(wp != win && frp2 != frp_close); + if (altfr != NULL) { + *altfr = frp2; } return wp; @@ -3305,9 +3337,10 @@ static void frame_flatten(frame_T *frp) } /// Undo changes from a prior call to winframe_remove, also restoring lost -/// vertical separators and statuslines. +/// vertical separators and statuslines, and changed window positions for +/// windows within "unflat_altfr". /// 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) +void winframe_restore(win_T *wp, int dir, frame_T *unflat_altfr) FUNC_ATTR_NONNULL_ALL { frame_T *frp = wp->w_frame; @@ -3333,13 +3366,24 @@ static void winframe_restore(win_T *wp, int dir, frame_T *unflat_altfr) } } + int row = wp->w_winrow; + int col = wp->w_wincol; + // 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); + row += frp->fr_height; } else if (dir == 'h') { frame_new_width(unflat_altfr, unflat_altfr->fr_width - frp->fr_width, unflat_altfr == frp->fr_next, false); + col += frp->fr_width; + } + + // If rows/columns went to a window below/right, its positions need to be + // restored. Can only be done after the sizes have been updated. + if (unflat_altfr == frp->fr_next) { + frame_comp_pos(unflat_altfr, &row, &col); } } @@ -4445,7 +4489,7 @@ static void tabpage_check_windows(tabpage_T *old_curtab) if (wp->w_floating) { if (wp->w_config.external) { win_remove(wp, old_curtab); - win_append(lastwin_nofloating(), wp); + win_append(lastwin_nofloating(), wp, NULL); } else { ui_comp_remove_grid(&wp->w_grid_alloc); } @@ -5085,7 +5129,7 @@ win_T *win_alloc(win_T *after, bool hidden) block_autocmds(); // link the window in the window list if (!hidden) { - win_append(after, new_wp); + win_append(after, new_wp, NULL); } new_wp->w_wincol = 0; @@ -5255,21 +5299,29 @@ void win_free_grid(win_T *wp, bool reinit) } } -// Append window "wp" in the window list after window "after". -void win_append(win_T *after, win_T *wp) +/// Append window "wp" in the window list after window "after". +/// +/// @param tp tab page "win" (and "after", if not NULL) is in, NULL for current +void win_append(win_T *after, win_T *wp, tabpage_T *tp) + FUNC_ATTR_NONNULL_ARG(2) { + assert(tp == NULL || tp != curtab); + + win_T **first = tp == NULL ? &firstwin : &tp->tp_firstwin; + win_T **last = tp == NULL ? &lastwin : &tp->tp_lastwin; + // after NULL is in front of the first - win_T *before = after == NULL ? firstwin : after->w_next; + win_T *before = after == NULL ? *first : after->w_next; wp->w_next = before; wp->w_prev = after; if (after == NULL) { - firstwin = wp; + *first = wp; } else { after->w_next = wp; } if (before == NULL) { - lastwin = wp; + *last = wp; } else { before->w_prev = wp; } diff --git a/src/nvim/winfloat.c b/src/nvim/winfloat.c index cddc4ed9dd..10d8c7ac90 100644 --- a/src/nvim/winfloat.c +++ b/src/nvim/winfloat.c @@ -61,7 +61,7 @@ win_T *win_new_float(win_T *wp, bool last, WinConfig fconfig, Error *err) XFREE_CLEAR(wp->w_frame); win_comp_pos(); // recompute window positions win_remove(wp, NULL); - win_append(lastwin_nofloating(), wp); + win_append(lastwin_nofloating(), wp, NULL); } wp->w_floating = true; wp->w_status_height = 0; diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 6e20c81fc2..abe1ca9344 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1654,6 +1654,18 @@ describe('API/win', function() api.nvim_open_win(new_buf, false, { split = 'left' }) eq('foobarbaz', eval('triggered')) end) + + it('sets error when no room', function() + matches('E36: Not enough room$', pcall_err(command, 'execute "split|"->repeat(&lines)')) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_open_win, 0, true, { split = 'above', win = 0 }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_open_win, 0, true, { split = 'below', win = 0 }) + ) + end) end) describe('set_config', function() @@ -1965,23 +1977,89 @@ describe('API/win', function() it('closing new curwin when moving window to other tabpage works', function() command('split | tabnew') - local w = api.nvim_get_current_win() - local t = api.nvim_get_current_tabpage() - command('tabfirst | autocmd WinEnter * quit') - api.nvim_win_set_config(0, { win = w, split = 'left' }) - -- New tabpage is now the only one, as WinEnter closed the new curwin in the original. - eq(t, api.nvim_get_current_tabpage()) - eq({ t }, api.nvim_list_tabpages()) + local t2_win = api.nvim_get_current_win() + command('tabfirst | autocmd WinEnter * ++once quit') + local t1_move_win = api.nvim_get_current_win() + -- win_set_config fails to switch away from "t1_move_win" because the WinEnter autocmd that + -- closed the window we're switched to returns us to "t1_move_win", as it filled the space. + eq( + 'Failed to switch away from window ' .. t1_move_win, + pcall_err(api.nvim_win_set_config, t1_move_win, { win = t2_win, split = 'left' }) + ) + eq(t1_move_win, api.nvim_get_current_win()) + + command('split | split | autocmd WinEnter * ++once quit') + t1_move_win = api.nvim_get_current_win() + -- In this case, we closed the window that we got switched to, but doing so didn't switch us + -- back to "t1_move_win", which is fine. + api.nvim_win_set_config(t1_move_win, { win = t2_win, split = 'left' }) + neq(t1_move_win, api.nvim_get_current_win()) end) - it('closing split parent when moving window to other tabpage aborts', function() + it('messing with "win" or "parent" when moving "win" to other tabpage', function() command('split | tabnew') - local w = api.nvim_get_current_win() - command('tabfirst | autocmd WinEnter * call nvim_win_close(' .. w .. ', 1)') + local t2 = api.nvim_get_current_tabpage() + local t2_win1 = api.nvim_get_current_win() + command('split') + local t2_win2 = api.nvim_get_current_win() + command('split') + local t2_win3 = api.nvim_get_current_win() + + command('tabfirst | autocmd WinEnter * ++once call nvim_win_close(' .. t2_win1 .. ', 1)') + local cur_win = api.nvim_get_current_win() eq( - 'Window to split was closed', - pcall_err(api.nvim_win_set_config, 0, { win = w, split = 'left' }) + 'Windows to split were closed', + pcall_err(api.nvim_win_set_config, 0, { win = t2_win1, split = 'left' }) ) + eq(cur_win, api.nvim_get_current_win()) + + command('split | autocmd WinLeave * ++once quit!') + cur_win = api.nvim_get_current_win() + eq( + 'Windows to split were closed', + pcall_err(api.nvim_win_set_config, 0, { win = t2_win2, split = 'left' }) + ) + neq(cur_win, api.nvim_get_current_win()) + + exec([[ + split + autocmd WinLeave * ++once + \ call nvim_win_set_config(0, #{relative:'editor', row:0, col:0, width:5, height:5}) + ]]) + cur_win = api.nvim_get_current_win() + eq( + 'Floating state of windows to split changed', + pcall_err(api.nvim_win_set_config, 0, { win = t2_win3, split = 'left' }) + ) + eq('editor', api.nvim_win_get_config(0).relative) + eq(cur_win, api.nvim_get_current_win()) + + command('autocmd WinLeave * ++once wincmd J') + cur_win = api.nvim_get_current_win() + eq( + 'Floating state of windows to split changed', + pcall_err(api.nvim_win_set_config, 0, { win = t2_win3, split = 'left' }) + ) + eq('', api.nvim_win_get_config(0).relative) + eq(cur_win, api.nvim_get_current_win()) + + -- Try to make "parent" floating. This should give the same error as before, but because + -- changing a split from another tabpage into a float isn't supported yet, check for that + -- error instead for now. + -- Use ":silent!" to avoid the one second delay from printing the error message. + exec(([[ + autocmd WinLeave * ++once silent! + \ call nvim_win_set_config(%d, #{relative:'editor', row:0, col:0, width:5, height:5}) + ]]):format(t2_win3)) + cur_win = api.nvim_get_current_win() + api.nvim_win_set_config(0, { win = t2_win3, split = 'left' }) + matches( + 'Cannot change window from different tabpage into float$', + api.nvim_get_vvar('errmsg') + ) + -- The error doesn't abort moving the window (or maybe it should, if that's wanted?) + neq(cur_win, api.nvim_get_current_win()) + eq(t2, api.nvim_win_get_tabpage(cur_win)) end) it('expected autocmds when moving window to other tabpage', function() @@ -2031,6 +2109,223 @@ describe('API/win', function() command('autocmd BufHidden * ++once call nvim_win_set_config(' .. w .. ', #{split: "left"})') command('new | quit') end) + + --- Returns a function to get information about the window layout, sizes and positions of a + --- tabpage. + local function define_tp_info_function() + exec_lua([[ + function tp_info(tp) + return { + layout = vim.fn.winlayout(vim.api.nvim_tabpage_get_number(tp)), + pos_sizes = vim.tbl_map( + function(w) + local pos = vim.fn.win_screenpos(w) + return { + row = pos[1], + col = pos[2], + width = vim.fn.winwidth(w), + height = vim.fn.winheight(w) + } + end, + vim.api.nvim_tabpage_list_wins(tp) + ) + } + end + ]]) + + return function(tp) + return exec_lua('return tp_info(...)', tp) + end + end + + it('attempt to move window with no room', function() + -- Fill the 2nd tabpage full of windows until we run out of room. + -- Use &laststatus=0 to ensure restoring missing statuslines doesn't affect things. + command('set laststatus=0 | tabnew') + matches('E36: Not enough room$', pcall_err(command, 'execute "split|"->repeat(&lines)')) + command('vsplit | wincmd | | wincmd p') + local t2 = api.nvim_get_current_tabpage() + local t2_cur_win = api.nvim_get_current_win() + local t2_top_split = fn.win_getid(1) + local t2_bot_split = fn.win_getid(fn.winnr('$')) + local t2_float = api.nvim_open_win( + 0, + false, + { relative = 'editor', row = 0, col = 0, width = 10, height = 10 } + ) + local t2_float_config = api.nvim_win_get_config(t2_float) + local tp_info = define_tp_info_function() + local t2_info = tp_info(t2) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_top_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_top_split, split = 'below' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_bot_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_bot_split, split = 'below' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t2_float, { win = t2_top_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t2_float, { win = t2_top_split, split = 'below' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t2_float, { win = t2_bot_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t2_float, { win = t2_bot_split, split = 'below' }) + ) + eq(t2_cur_win, api.nvim_get_current_win()) + eq(t2_info, tp_info(t2)) + eq(t2_float_config, api.nvim_win_get_config(t2_float)) + + -- Try to move windows from the 1st tabpage to the 2nd. + command('tabfirst | split | wincmd _') + local t1 = api.nvim_get_current_tabpage() + local t1_cur_win = api.nvim_get_current_win() + local t1_float = api.nvim_open_win( + 0, + false, + { relative = 'editor', row = 5, col = 3, width = 7, height = 6 } + ) + local t1_float_config = api.nvim_win_get_config(t1_float) + local t1_info = tp_info(t1) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_top_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_top_split, split = 'below' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_bot_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_bot_split, split = 'below' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t1_float, { win = t2_top_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t1_float, { win = t2_top_split, split = 'below' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t1_float, { win = t2_bot_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t1_float, { win = t2_bot_split, split = 'below' }) + ) + eq(t1_cur_win, api.nvim_get_current_win()) + eq(t1_info, tp_info(t1)) + eq(t1_float_config, api.nvim_win_get_config(t1_float)) + end) + + it('attempt to move window from other tabpage with no room', function() + -- Fill up the 1st tabpage with horizontal splits, then create a 2nd with only a few. Go back + -- to the 1st and try to move windows from the 2nd (while it's non-current) to it. Check that + -- window positions and sizes in the 2nd are unchanged. + command('set laststatus=0') + matches('E36: Not enough room$', pcall_err(command, 'execute "split|"->repeat(&lines)')) + + command('tab split') + local t2 = api.nvim_get_current_tabpage() + local t2_top = api.nvim_get_current_win() + command('belowright split') + local t2_mid_left = api.nvim_get_current_win() + command('belowright vsplit') + local t2_mid_right = api.nvim_get_current_win() + command('split | wincmd J') + local t2_bot = api.nvim_get_current_win() + local tp_info = define_tp_info_function() + local t2_info = tp_info(t2) + eq({ + 'col', + { + { 'leaf', t2_top }, + { + 'row', + { + { 'leaf', t2_mid_left }, + { 'leaf', t2_mid_right }, + }, + }, + { 'leaf', t2_bot }, + }, + }, t2_info.layout) + + local function try_move_t2_wins_to_t1() + for _, w in ipairs({ t2_bot, t2_mid_left, t2_mid_right, t2_top }) do + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, w, { win = 0, split = 'below' }) + ) + eq(t2_info, tp_info(t2)) + end + end + command('tabfirst') + try_move_t2_wins_to_t1() + -- Go to the 2nd tabpage to ensure nothing changes after win_comp_pos, last_status, .etc. + -- from enter_tabpage. + command('tabnext') + eq(t2_info, tp_info(t2)) + + -- Check things are fine with the global statusline too, for good measure. + -- Set it while the 2nd tabpage is current, so last_status runs for it. + command('set laststatus=3') + t2_info = tp_info(t2) + command('tabfirst') + try_move_t2_wins_to_t1() + end) + + it('does not switch window when textlocked or in the cmdwin', function() + command('tabnew') + local t2_win = api.nvim_get_current_win() + command('tabfirst') + feed('q:') + local cur_win = api.nvim_get_current_win() + eq( + 'Failed to switch away from window ' .. cur_win, + pcall_err(api.nvim_win_set_config, 0, { split = 'left', win = t2_win }) + ) + eq( + 'E11: Invalid in command-line window; executes, CTRL-C quits', + api.nvim_get_vvar('errmsg') + ) + eq(cur_win, api.nvim_get_current_win()) + command('quit!') + + exec(([[ + new + call setline(1, 'foo') + setlocal debug=throw indentexpr=nvim_win_set_config(0,#{split:'left',win:%d}) + ]]):format(t2_win)) + cur_win = api.nvim_get_current_win() + matches( + 'E565: Not allowed to change text or change window$', + pcall_err(command, 'normal! ==') + ) + eq(cur_win, api.nvim_get_current_win()) + end) end) describe('get_config', function()