aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSean Dewar <6256228+seandewar@users.noreply.github.com>2024-03-09 22:32:20 +0000
committerGitHub <noreply@github.com>2024-03-09 22:32:20 +0000
commitb596732831b5e947ce83c1153f0df10a0553c88d (patch)
tree0a6e874de0ba7fb9eb3ef71d89516281731401e7 /src
parentade1b12f49c3b3914c74847d791eb90ea90b56b7 (diff)
parentc3d22d32ee4b4c1911ec15f2a77683d09b09f845 (diff)
downloadrneovim-b596732831b5e947ce83c1153f0df10a0553c88d.tar.gz
rneovim-b596732831b5e947ce83c1153f0df10a0553c88d.tar.bz2
rneovim-b596732831b5e947ce83c1153f0df10a0553c88d.zip
Merge pull request #27330 from seandewar/win_set_config-fixes
fix(api): various window-related function fixes This is a big one!
Diffstat (limited to 'src')
-rw-r--r--src/nvim/api/tabpage.c6
-rw-r--r--src/nvim/api/win_config.c256
-rw-r--r--src/nvim/autocmd.c2
-rw-r--r--src/nvim/buffer.c5
-rw-r--r--src/nvim/drawscreen.c2
-rw-r--r--src/nvim/eval.lua8
-rw-r--r--src/nvim/eval/window.c79
-rw-r--r--src/nvim/globals.h1
-rw-r--r--src/nvim/tui/tui.c2
-rw-r--r--src/nvim/window.c481
-rw-r--r--src/nvim/winfloat.c35
11 files changed, 557 insertions, 320 deletions
diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c
index 040abb1e3f..56a3f1cf23 100644
--- a/src/nvim/api/tabpage.c
+++ b/src/nvim/api/tabpage.c
@@ -146,7 +146,11 @@ void nvim_tabpage_set_win(Tabpage tabpage, Window win, Error *err)
}
if (tp == curtab) {
- win_enter(wp, true);
+ try_start();
+ win_goto(wp);
+ if (!try_end(err) && curwin != wp) {
+ api_set_error(err, kErrorTypeException, "Failed to switch to window %d", win);
+ }
} else if (tp->tp_curwin != wp) {
tp->tp_prevwin = tp->tp_curwin;
tp->tp_curwin = wp;
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
index 543c7b8113..43cff9b2c3 100644
--- a/src/nvim/api/win_config.c
+++ b/src/nvim/api/win_config.c
@@ -12,6 +12,7 @@
#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/autocmd_defs.h"
+#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/decoration.h"
#include "nvim/decoration_defs.h"
@@ -198,9 +199,9 @@
/// - footer_pos: Footer position. Must be set with `footer` option.
/// Value can be one of "left", "center", or "right".
/// Default is `"left"`.
-/// - noautocmd: If true then no buffer-related autocommand events such as
-/// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from
-/// calling this function.
+/// - noautocmd: If true then autocommands triggered from setting the
+/// `buffer` to display are blocked (e.g: |BufEnter|, |BufLeave|,
+/// |BufWinEnter|).
/// - fixed: If true when anchor is NW or SW, the float window
/// would be kept fixed even if the window would be truncated.
/// - hide: If true the floating window will be hidden.
@@ -245,6 +246,10 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err
}
}
+ if (!check_split_disallowed_err(parent ? parent : curwin, err)) {
+ return 0; // error already set
+ }
+
if (HAS_KEY_X(config, vertical) && !HAS_KEY_X(config, split)) {
if (config->vertical) {
fconfig.split = p_spr ? kWinSplitRight : kWinSplitLeft;
@@ -254,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);
- } 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);
- (void)result;
- assert(result == OK);
- wp = win_split_ins(0, flags, NULL, 0);
- 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;
}
@@ -273,21 +280,49 @@ 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;
}
+
+ // Autocommands may close `wp` or move it to another tabpage, so update and check `tp` after each
+ // event. In each case, `wp` should already be valid in `tp`, so switch_win should not fail.
+ // Also, autocommands may free the `buf` to switch to, so store a bufref to check.
+ bufref_T bufref;
+ set_bufref(&bufref, buf);
switchwin_T switchwin;
- if (switch_win_noblock(&switchwin, wp, tp, true) == OK) {
- apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf);
+ {
+ const int result = switch_win_noblock(&switchwin, wp, tp, true);
+ assert(result == OK);
+ (void)result;
+ if (apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf)) {
+ tp = win_find_tabpage(wp);
+ }
+ restore_win_noblock(&switchwin, true);
}
- restore_win_noblock(&switchwin, true);
- if (enter) {
+ if (tp && enter) {
goto_tabpage_win(tp, wp);
+ tp = win_find_tabpage(wp);
}
- if (win_valid_any_tab(wp) && buf != wp->w_buffer) {
- win_set_buf(wp, buf, !enter || fconfig.noautocmd, err);
+ if (tp && bufref_valid(&bufref) && buf != wp->w_buffer) {
+ // win_set_buf temporarily makes `wp` the curwin to set the buffer.
+ // If not entering `wp`, block Enter and Leave events. (cringe)
+ const bool au_no_enter_leave = curwin != wp && !fconfig.noautocmd;
+ if (au_no_enter_leave) {
+ autocmd_no_enter++;
+ autocmd_no_leave++;
+ }
+ win_set_buf(wp, buf, fconfig.noautocmd, err);
+ if (!fconfig.noautocmd) {
+ tp = win_find_tabpage(wp);
+ }
+ if (au_no_enter_leave) {
+ autocmd_no_enter--;
+ autocmd_no_leave--;
+ }
}
- if (!win_valid_any_tab(wp)) {
+ if (!tp) {
api_set_error(err, kErrorTypeException, "Window was closed immediately");
return 0;
}
@@ -330,11 +365,11 @@ static int win_split_flags(WinSplit split, bool toplevel)
return flags;
}
-/// Configures window layout. Currently only for floating and external windows
-/// (including changing a split window to those layouts).
+/// Configures window layout. Cannot be used to move the last window in a
+/// tabpage to a different one.
///
-/// When reconfiguring a floating window, absent option keys will not be
-/// changed. `row`/`col` and `relative` must be reconfigured together.
+/// When reconfiguring a window, absent option keys will not be changed.
+/// `row`/`col` and `relative` must be reconfigured together.
///
/// @see |nvim_open_win()|
///
@@ -413,17 +448,59 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err)
return;
}
- if (was_split) {
- win_T *new_curwin = NULL;
+ if (!check_split_disallowed_err(win, err)) {
+ return; // error already set
+ }
+ // Can't move the cmdwin or its old curwin to a different tabpage.
+ if ((win == cmdwin_win || win == cmdwin_old_curwin) && parent != NULL
+ && win_find_tabpage(parent) != win_tp) {
+ api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
+ return;
+ }
+
+ 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_float_find_altwin(win, NULL));
+ }
+ // 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;
+ win_T *altwin = 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) {
@@ -459,83 +536,82 @@ 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);
+ altwin = 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);
- new_curwin = neighbor;
+ altwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr);
+ neighbor = altwin;
} 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);
+ altwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr);
}
- // 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);
- }
- win_remove(win, win_tp == curtab ? NULL : win_tp);
} else {
- win_remove(win, win_tp == curtab ? NULL : win_tp);
- ui_comp_remove_grid(&win->w_grid_alloc);
- if (win->w_config.external) {
- for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
- if (tp == curtab) {
- continue;
- }
- if (tp->tp_curwin == win) {
- tp->tp_curwin = tp->tp_firstwin;
- }
- }
- }
- win->w_pos_changed = true;
+ altwin = win_float_find_altwin(win, win_tp == curtab ? NULL : win_tp);
}
- int flags = win_split_flags(fconfig.split, parent == NULL);
+ win_remove(win, win_tp == curtab ? NULL : win_tp);
+ if (win_tp == curtab) {
+ last_status(false); // may need to remove last status line
+ win_comp_pos(); // recompute window positions
+ }
- if (parent == NULL) {
- if (!win_split_ins(0, flags, win, 0)) {
- // TODO(willothy): What should this error message say?
- api_set_error(err, kErrorTypeException, "Failed to split window");
- return;
- }
- } else {
- win_execute_T args;
+ int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER;
+ tabpage_T *const parent_tp = parent ? win_find_tabpage(parent) : curtab;
- tabpage_T *tp = win_find_tabpage(parent);
- if (!win_execute_before(&args, parent, tp)) {
- // TODO(willothy): how should we handle this / what should the message be?
- api_set_error(err, kErrorTypeException, "Failed to switch to tabpage %d", tp->handle);
- win_execute_after(&args);
- return;
+ TRY_WRAP(err, {
+ const bool need_switch = parent != NULL && parent != curwin;
+ switchwin_T switchwin;
+ if (need_switch) {
+ // `parent` is valid in its tabpage, so switch_win should not fail.
+ const int result = switch_win(&switchwin, parent, parent_tp, true);
+ (void)result;
+ assert(result == OK);
}
- // This should return the same ptr to `win`, but we check for
- // NULL to detect errors.
- win_T *res = win_split_ins(0, flags, win, 0);
- win_execute_after(&args);
- if (!res) {
- // TODO(willothy): What should this error message say?
- api_set_error(err, kErrorTypeException, "Failed to split window");
- return;
+ 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 `win` moved tabpages and was the curwin of its old one, select a new curwin for it.
+ if (win_tp != parent_tp && win_tp->tp_curwin == win) {
+ win_tp->tp_curwin = altwin;
}
+
if (HAS_KEY_X(config, width)) {
win_setwidth_win(fconfig.width, win);
}
if (HAS_KEY_X(config, height)) {
win_setheight_win(fconfig.height, win);
}
- redraw_later(win, UPD_NOT_VALID);
- return;
} else {
win_config_float(win, fconfig);
win->w_pos_changed = true;
@@ -1071,11 +1147,15 @@ static bool parse_float_config(Dict(win_config) *config, WinConfig *fconfig, boo
fconfig->window = config->win;
}
}
- } else if (has_relative) {
- if (HAS_KEY_X(config, win)) {
+ } else if (HAS_KEY_X(config, win)) {
+ if (has_relative) {
api_set_error(err, kErrorTypeValidation,
"'win' key is only valid with relative='win' and relative=''");
return false;
+ } else if (!is_split) {
+ api_set_error(err, kErrorTypeValidation,
+ "non-float with 'win' requires at least 'split' or 'vertical'");
+ return false;
}
}
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/buffer.c b/src/nvim/buffer.c
index f6c7229485..7154be36be 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -117,7 +117,6 @@
# include "buffer.c.generated.h"
#endif
-static const char *e_auabort = N_("E855: Autocommands caused command to abort");
static const char e_attempt_to_delete_buffer_that_is_in_use_str[]
= N_("E937: Attempt to delete a buffer that is in use: %s");
@@ -569,7 +568,7 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i
}
buf->b_locked--;
buf->b_locked_split--;
- if (abort_if_last && last_nonfloat(win)) {
+ if (abort_if_last && one_window(win)) {
// Autocommands made this the only window.
emsg(_(e_auabort));
return false;
@@ -588,7 +587,7 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i
}
buf->b_locked--;
buf->b_locked_split--;
- if (abort_if_last && last_nonfloat(win)) {
+ if (abort_if_last && one_window(win)) {
// Autocommands made this the only window.
emsg(_(e_auabort));
return false;
diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c
index 1626e46cf6..402f7fa428 100644
--- a/src/nvim/drawscreen.c
+++ b/src/nvim/drawscreen.c
@@ -1513,7 +1513,7 @@ static void win_update(win_T *wp)
// Make sure skipcol is valid, it depends on various options and the window
// width.
- if (wp->w_skipcol > 0) {
+ if (wp->w_skipcol > 0 && wp->w_width_inner > win_col_off(wp)) {
int w = 0;
int width1 = wp->w_width_inner - win_col_off(wp);
int width2 = width1 + win_col_off2(wp);
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index b7120d5dd5..febd022254 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -12699,10 +12699,10 @@ M.funcs = {
args = { 2, 3 },
base = 1,
desc = [=[
- Move the window {nr} to a new split of the window {target}.
- This is similar to moving to {target}, creating a new window
- using |:split| but having the same contents as window {nr}, and
- then closing {nr}.
+ Temporarily switch to window {target}, then move window {nr}
+ to a new split adjacent to {target}.
+ Unlike commands such as |:split|, no new windows are created
+ (the |window-ID| of window {nr} is unchanged after the move).
Both {nr} and {target} can be window numbers or |window-ID|s.
Both must be in the current tab page.
diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c
index b8aa0c9641..e54f46dcc3 100644
--- a/src/nvim/eval/window.c
+++ b/src/nvim/eval/window.c
@@ -14,6 +14,7 @@
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/window.h"
+#include "nvim/ex_getln.h"
#include "nvim/garray.h"
#include "nvim/garray_defs.h"
#include "nvim/gettext_defs.h"
@@ -583,9 +584,13 @@ void f_win_getid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
void f_win_gotoid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
int id = (int)tv_get_number(&argvars[0]);
+ if (curwin->handle == id) {
+ // Nothing to do.
+ rettv->vval.v_number = 1;
+ return;
+ }
- if (cmdwin_type != 0) {
- emsg(_(e_cmdwin));
+ if (text_or_buf_locked()) {
return;
}
FOR_ALL_TAB_WINDOWS(tp, wp) {
@@ -659,55 +664,19 @@ void f_win_screenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1);
}
-/// Move the window wp into a new split of targetwin in a given direction
-static void win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags)
-{
- int height = wp->w_height;
- win_T *oldwin = curwin;
-
- if (wp == targetwin || is_aucmd_win(wp)) {
- return;
- }
-
- // Jump to the target window
- if (curwin != targetwin) {
- win_goto(targetwin);
- }
-
- // Remove the old window and frame from the tree of frames
- int dir;
- winframe_remove(wp, &dir, NULL);
- 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 the old window there
- win_split_ins(size, flags, wp, dir);
-
- // If splitting horizontally, try to preserve height
- if (size == 0 && !(flags & WSP_VERT)) {
- win_setheight_win(height, wp);
- if (p_ea) {
- win_equal(wp, true, 'v');
- }
- }
-
- if (oldwin != curwin) {
- win_goto(oldwin);
- }
-}
-
/// "win_splitmove()" function
void f_win_splitmove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
win_T *wp = find_win_by_nr_or_id(&argvars[0]);
win_T *targetwin = find_win_by_nr_or_id(&argvars[1]);
+ win_T *oldwin = curwin;
+
+ rettv->vval.v_number = -1;
if (wp == NULL || targetwin == NULL || wp == targetwin
|| !win_valid(wp) || !win_valid(targetwin)
- || win_float_valid(wp) || win_float_valid(targetwin)) {
+ || targetwin->w_floating) {
emsg(_(e_invalwindow));
- rettv->vval.v_number = -1;
return;
}
@@ -732,7 +701,27 @@ void f_win_splitmove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
size = (int)tv_dict_get_number(d, "size");
}
- win_move_into_split(wp, targetwin, size, flags);
+ // Check if we can split the target before we bother switching windows.
+ if (is_aucmd_win(wp) || text_or_buf_locked() || check_split_disallowed(targetwin) == FAIL) {
+ return;
+ }
+
+ if (curwin != targetwin) {
+ win_goto(targetwin);
+ }
+
+ // Autocommands may have sent us elsewhere or closed "wp" or "oldwin".
+ if (curwin == targetwin && win_valid(wp)) {
+ if (win_splitmove(wp, size, flags) == OK) {
+ rettv->vval.v_number = 0;
+ }
+ } else {
+ emsg(_(e_auabort));
+ }
+
+ if (oldwin != curwin && win_valid(oldwin)) {
+ win_goto(oldwin);
+ }
}
/// "win_gettype(nr)" function
@@ -969,9 +958,11 @@ int switch_win_noblock(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool n
if (no_display) {
curtab->tp_firstwin = firstwin;
curtab->tp_lastwin = lastwin;
+ curtab->tp_topframe = topframe;
curtab = tp;
firstwin = curtab->tp_firstwin;
lastwin = curtab->tp_lastwin;
+ topframe = curtab->tp_topframe;
} else {
goto_tabpage_tp(tp, false, false);
}
@@ -1000,9 +991,11 @@ void restore_win_noblock(switchwin_T *switchwin, bool no_display)
if (no_display) {
curtab->tp_firstwin = firstwin;
curtab->tp_lastwin = lastwin;
+ curtab->tp_topframe = topframe;
curtab = switchwin->sw_curtab;
firstwin = curtab->tp_firstwin;
lastwin = curtab->tp_lastwin;
+ topframe = curtab->tp_topframe;
} else {
goto_tabpage_tp(switchwin->sw_curtab, false, false);
}
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 22f7daa823..c1c9ae456c 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -939,6 +939,7 @@ EXTERN const char e_using_float_as_string[] INIT(= N_("E806: Using a Float as a
EXTERN const char e_cannot_edit_other_buf[] INIT(= N_("E788: Not allowed to edit another buffer now"));
EXTERN const char e_using_number_as_bool_nr[] INIT(= N_("E1023: Using a Number as a Bool: %d"));
EXTERN const char e_not_callable_type_str[] INIT(= N_("E1085: Not a callable type: %s"));
+EXTERN const char e_auabort[] INIT(= N_("E855: Autocommands caused command to abort"));
EXTERN const char e_api_error[] INIT(= N_("E5555: API call: %s"));
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 7fae34d33f..c332c17e43 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -1643,7 +1643,7 @@ static void invalidate(TUIData *tui, int top, int bot, int left, int right)
static void ensure_space_buf_size(TUIData *tui, size_t len)
{
if (len > tui->space_buf_len) {
- tui->space_buf = xrealloc(tui->space_buf, len * sizeof *tui->space_buf);
+ tui->space_buf = xrealloc(tui->space_buf, len);
memset(tui->space_buf + tui->space_buf_len, ' ', len - tui->space_buf_len);
tui->space_buf_len = len;
}
diff --git a/src/nvim/window.c b/src/nvim/window.c
index e2c4524eaa..ecd2e83500 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -455,9 +455,14 @@ newwindow:
case 'H':
case 'L':
CHECK_CMDWIN;
- win_totop(Prenum,
- ((nchar == 'H' || nchar == 'L') ? WSP_VERT : 0)
- | ((nchar == 'H' || nchar == 'K') ? WSP_TOP : WSP_BOT));
+ if (one_window(curwin)) {
+ beep_flush();
+ } else {
+ const int dir = ((nchar == 'H' || nchar == 'L') ? WSP_VERT : 0)
+ | ((nchar == 'H' || nchar == 'K') ? WSP_TOP : WSP_BOT);
+
+ win_splitmove(curwin, Prenum, dir);
+ }
break;
// make all windows the same width and/or height
@@ -718,6 +723,7 @@ void win_set_buf(win_T *win, buf_T *buf, bool noautocmd, Error *err)
kErrorTypeException,
"Failed to switch to window %d",
win->handle);
+ goto cleanup;
}
try_start();
@@ -729,10 +735,11 @@ void win_set_buf(win_T *win, buf_T *buf, bool noautocmd, Error *err)
buf->handle);
}
- // If window is not current, state logic will not validate its cursor.
- // So do it now.
+ // If window is not current, state logic will not validate its cursor. So do it now.
+ // Still needed if do_buffer returns FAIL (e.g: autocmds abort script after buffer was set).
validate_cursor();
+cleanup:
restore_win_noblock(&switchwin, true);
if (noautocmd) {
unblock_autocmds();
@@ -903,19 +910,35 @@ void ui_ext_win_viewport(win_T *wp)
}
}
-/// If "split_disallowed" is set give an error and return FAIL.
+/// If "split_disallowed" is set or "wp"s buffer is closing, give an error and return FAIL.
/// Otherwise return OK.
-static int check_split_disallowed(void)
+int check_split_disallowed(const win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ Error err = ERROR_INIT;
+ const bool ok = check_split_disallowed_err(wp, &err);
+ if (ERROR_SET(&err)) {
+ emsg(_(err.msg));
+ api_clear_error(&err);
+ }
+ return ok ? OK : FAIL;
+}
+
+/// Like `check_split_disallowed`, but set `err` to the (untranslated) error message on failure and
+/// return false. Otherwise return true.
+/// @see check_split_disallowed
+bool check_split_disallowed_err(const win_T *wp, Error *err)
+ FUNC_ATTR_NONNULL_ALL
{
if (split_disallowed > 0) {
- emsg(_("E242: Can't split a window while closing another"));
- return FAIL;
+ api_set_error(err, kErrorTypeException, "E242: Can't split a window while closing another");
+ return false;
}
- if (curwin->w_buffer->b_locked_split) {
- emsg(_(e_cannot_split_window_when_closing_buffer));
- return FAIL;
+ if (wp->w_buffer->b_locked_split) {
+ api_set_error(err, kErrorTypeException, "%s", e_cannot_split_window_when_closing_buffer);
+ return false;
}
- return OK;
+ return true;
}
// split the current window, implements CTRL-W s and :split
@@ -934,7 +957,7 @@ static int check_split_disallowed(void)
// return FAIL for failure, OK otherwise
int win_split(int size, int flags)
{
- if (check_split_disallowed() == FAIL) {
+ if (check_split_disallowed(curwin) == FAIL) {
return FAIL;
}
@@ -958,14 +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;
@@ -986,13 +1014,12 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir)
int need_status = 0;
int new_size = size;
- bool new_in_layout = (new_wp == NULL || new_wp->w_floating);
bool vertical = flags & WSP_VERT;
bool toplevel = flags & (WSP_TOP | WSP_BOT);
// add a status line when p_ls == 1 and splitting the first window
- if (one_nonfloat() && p_ls == 1 && oldwin->w_status_height == 0) {
- if (oldwin->w_height <= p_wmh && new_in_layout) {
+ if (one_window(firstwin) && p_ls == 1 && oldwin->w_status_height == 0) {
+ if (oldwin->w_height <= p_wmh) {
emsg(_(e_noroom));
return NULL;
}
@@ -1041,7 +1068,7 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir)
available = oldwin->w_frame->fr_width;
needed += minwidth;
}
- if (available < needed && new_in_layout) {
+ if (available < needed) {
emsg(_(e_noroom));
return NULL;
}
@@ -1121,7 +1148,7 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir)
available = oldwin->w_frame->fr_height;
needed += minheight;
}
- if (available < needed && new_in_layout) {
+ if (available < needed) {
emsg(_(e_noroom));
return NULL;
}
@@ -1191,13 +1218,13 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir)
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);
}
}
@@ -1211,13 +1238,37 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir)
// make the contents of the new window the same as the current one
win_init(wp, curwin, flags);
} else if (wp->w_floating) {
- new_frame(wp);
+ ui_comp_remove_grid(&wp->w_grid_alloc);
+ if (ui_has(kUIMultigrid)) {
+ wp->w_pos_changed = true;
+ } else {
+ // No longer a float, a non-multigrid UI shouldn't draw it as such
+ ui_call_win_hide(wp->w_grid_alloc.handle);
+ win_free_grid(wp, true);
+ }
+
+ // External windows are independent of tabpages, and may have been the curwin of others.
+ if (wp->w_config.external) {
+ FOR_ALL_TABS(tp) {
+ if (tp != curtab && tp->tp_curwin == wp) {
+ tp->tp_curwin = tp->tp_firstwin;
+ }
+ }
+ }
+
wp->w_floating = false;
+ new_frame(wp);
+
// non-floating window doesn't store float config or have a border.
wp->w_config = WIN_CONFIG_INIT;
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;
@@ -1453,7 +1504,7 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir)
if (!(flags & WSP_NOENTER)) {
// make the new window the current window
- win_enter_ext(wp, WEE_TRIGGER_NEW_AUTOCMDS | WEE_TRIGGER_ENTER_AUTOCMDS
+ win_enter_ext(wp, (new_wp == NULL ? WEE_TRIGGER_NEW_AUTOCMDS : 0) | WEE_TRIGGER_ENTER_AUTOCMDS
| WEE_TRIGGER_LEAVE_AUTOCMDS);
}
if (vertical) {
@@ -1690,7 +1741,7 @@ static void win_exchange(int Prenum)
return;
}
- if (firstwin == curwin && lastwin_nofloating() == curwin) {
+ if (one_window(curwin)) {
// just one window
beep_flush();
return;
@@ -1732,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 {
@@ -1782,7 +1833,7 @@ static void win_rotate(bool upwards, int count)
return;
}
- if (count <= 0 || (firstwin == curwin && lastwin_nofloating() == curwin)) {
+ if (count <= 0 || one_window(curwin)) {
// nothing to do
beep_flush();
return;
@@ -1812,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
@@ -1827,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);
}
@@ -1856,48 +1907,58 @@ static void win_rotate(bool upwards, int count)
redraw_all_later(UPD_NOT_VALID);
}
-// Move the current window to the very top/bottom/left/right of the screen.
-static void win_totop(int size, int flags)
+/// Move "wp" into a new split in a given direction, possibly relative to the
+/// current window.
+/// "wp" must be valid in the current tabpage.
+/// Returns FAIL for failure, OK otherwise.
+int win_splitmove(win_T *wp, int size, int flags)
{
int dir = 0;
- int height = curwin->w_height;
+ int height = wp->w_height;
- if (firstwin == curwin && lastwin_nofloating() == curwin) {
- beep_flush();
- return;
+ if (firstwin == wp && lastwin_nofloating() == wp) {
+ return OK; // nothing to do
}
- if (is_aucmd_win(curwin)) {
- return;
+ if (is_aucmd_win(wp) || check_split_disallowed(wp) == FAIL) {
+ return FAIL;
}
- if (check_split_disallowed() == FAIL) {
- return;
+
+ frame_T *unflat_altfr = NULL;
+ if (wp->w_floating) {
+ win_remove(wp, NULL);
+ } else {
+ // 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
}
- if (curwin->w_floating) {
- ui_comp_remove_grid(&curwin->w_grid_alloc);
- if (ui_has(kUIMultigrid)) {
- curwin->w_pos_changed = true;
- } else {
- // No longer a float, a non-multigrid UI shouldn't draw it as such
- ui_call_win_hide(curwin->w_grid_alloc.handle);
- win_free_grid(curwin, true);
+ // 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, 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.
+ winframe_restore(wp, dir, unflat_altfr);
+ win_comp_pos(); // recompute window positions
}
- } else {
- // Remove the window and frame from the tree of frames.
- winframe_remove(curwin, &dir, NULL);
+ return FAIL;
}
- win_remove(curwin, 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 the window there.
- win_split_ins(size, flags, curwin, dir);
- if (!(flags & WSP_VERT)) {
- win_setheight(height);
+ // If splitting horizontally, try to preserve height.
+ // Note that win_split_ins autocommands may have immediately closed "wp", or made it floating!
+ if (size == 0 && !(flags & WSP_VERT) && win_valid(wp) && !wp->w_floating) {
+ win_setheight_win(height, wp);
if (p_ea) {
- win_equal(curwin, true, 'v');
+ // Equalize windows. Note that win_split_ins autocommands may have
+ // made a window other than "wp" current.
+ win_equal(curwin, curwin == wp, 'v');
}
}
+
+ return OK;
}
// Move window "win1" to below/right of "win2" and make "win1" the current
@@ -1955,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
@@ -2434,37 +2495,13 @@ bool last_window(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
}
/// Check if "win" is the only non-floating window in the current tabpage.
-bool one_window(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- if (win->w_floating) {
- return false;
- }
-
- bool seen_one = false;
-
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (!wp->w_floating) {
- if (seen_one) {
- return false;
- }
- seen_one = true;
- }
- }
- return true;
-}
-
-/// Like ONE_WINDOW but only considers non-floating windows
-bool one_nonfloat(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- return firstwin->w_next == NULL || firstwin->w_next->w_floating;
-}
-
-/// if wp is the last non-floating window
///
-/// always false for a floating window
-bool last_nonfloat(win_T *wp) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+/// This should be used in place of ONE_WINDOW when necessary,
+/// with "firstwin" or the affected window as argument depending on the situation.
+bool one_window(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- return wp != NULL && firstwin == wp && !(wp->w_next && !wp->w_floating);
+ assert(!firstwin->w_floating);
+ return firstwin == win && (win->w_next == NULL || win->w_next->w_floating);
}
/// Check if floating windows in the current tab can be closed.
@@ -2759,13 +2796,10 @@ int win_close(win_T *win, bool free_buf, bool force)
ui_comp_remove_grid(&win->w_grid_alloc);
assert(first_tabpage != NULL); // suppress clang "Dereference of NULL pointer"
if (win->w_config.external) {
- for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
- if (tp == curtab) {
- continue;
- }
- if (tp->tp_curwin == win) {
+ FOR_ALL_TABS(tp) {
+ if (tp != curtab && tp->tp_curwin == win) {
// NB: an autocmd can still abort the closing of this window,
- // bur carring out this change anyway shouldn't be a catastrophe.
+ // but carrying out this change anyway shouldn't be a catastrophe.
tp->tp_curwin = tp->tp_firstwin;
}
}
@@ -3006,24 +3040,11 @@ 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.
- if (tp == NULL) {
- if (win_valid(prevwin) && prevwin != win) {
- wp = prevwin;
- } else {
- wp = firstwin;
- }
- } else {
- assert(tp != curtab);
- if (tabpage_win_valid(tp, tp->tp_prevwin) && tp->tp_prevwin != win) {
- wp = tp->tp_prevwin;
- } else {
- wp = tp->tp_firstwin;
- }
- }
+ wp = win_float_find_altwin(win, tp);
}
win_free(win, tp);
@@ -3087,9 +3108,60 @@ 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)
+{
+ 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);
@@ -3101,13 +3173,10 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp)
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
@@ -3134,8 +3203,6 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp)
}
}
}
- 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
@@ -3163,70 +3230,124 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp)
}
}
}
- 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;
+ assert(wp != win && frp2 != frp_close);
+ if (altfr != NULL) {
+ *altfr = frp2;
+ }
+
+ return wp;
+}
- frame_comp_pos(frp2, &row, &col);
+/// 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;
}
- 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;
+ // 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, and changed window positions for
+/// windows within "unflat_altfr".
+/// Caller must ensure no other changes were made to the layout or window sizes!
+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;
+ 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);
+ }
}
/// If 'splitbelow' or 'splitright' is set, the space goes above or to the left
@@ -3792,7 +3913,7 @@ void close_others(int message, int forceit)
return;
}
- if (one_nonfloat() && !lastwin->w_floating) {
+ if (one_window(firstwin) && !lastwin->w_floating) {
if (message
&& !autocmd_busy) {
msg(_(m_onlyone), 0);
@@ -4331,7 +4452,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);
}
@@ -4971,7 +5092,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;
@@ -5141,21 +5262,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;
}
@@ -7058,7 +7187,7 @@ int global_stl_height(void)
/// @param morewin pretend there are two or more windows if true.
int last_stl_height(bool morewin)
{
- return (p_ls > 1 || (p_ls == 1 && (morewin || !one_nonfloat()))) ? STATUS_HEIGHT : 0;
+ return (p_ls > 1 || (p_ls == 1 && (morewin || !one_window(firstwin)))) ? STATUS_HEIGHT : 0;
}
/// Return the minimal number of rows that is needed on the screen to display
diff --git a/src/nvim/winfloat.c b/src/nvim/winfloat.c
index 8fe0315230..65d2c1306b 100644
--- a/src/nvim/winfloat.c
+++ b/src/nvim/winfloat.c
@@ -55,13 +55,26 @@ win_T *win_new_float(win_T *wp, bool last, WinConfig fconfig, Error *err)
api_set_error(err, kErrorTypeException,
"Cannot change window from different tabpage into float");
return NULL;
+ } else if (cmdwin_win != NULL && !cmdwin_win->w_floating) {
+ // cmdwin can't become the only non-float. Check for others.
+ bool other_nonfloat = false;
+ for (win_T *wp2 = firstwin; wp2 != NULL && !wp2->w_floating; wp2 = wp2->w_next) {
+ if (wp2 != wp && wp2 != cmdwin_win) {
+ other_nonfloat = true;
+ break;
+ }
+ }
+ if (!other_nonfloat) {
+ api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
+ 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);
- win_append(lastwin_nofloating(), wp);
+ win_append(lastwin_nofloating(), wp, NULL);
}
wp->w_floating = true;
wp->w_status_height = 0;
@@ -306,3 +319,21 @@ win_T *win_float_find_preview(void)
}
return NULL;
}
+
+/// Select an alternative window to `win` (assumed floating) in tabpage `tp`.
+///
+/// Useful for finding a window to switch to if `win` is the current window, but is then closed or
+/// moved to a different tabpage.
+///
+/// @param tp `win`'s original tabpage, or NULL for current.
+win_T *win_float_find_altwin(const win_T *win, const tabpage_T *tp)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ if (tp == NULL) {
+ return (win_valid(prevwin) && prevwin != win) ? prevwin : firstwin;
+ }
+
+ assert(tp != curtab);
+ return (tabpage_win_valid(tp, tp->tp_prevwin) && tp->tp_prevwin != win) ? tp->tp_prevwin
+ : tp->tp_firstwin;
+}