Merge #35816 nvim_win_set_config can move floatwin to another tabpage

This commit is contained in:
Justin M. Keyes
2026-03-14 17:44:06 -04:00
committed by GitHub
14 changed files with 1024 additions and 177 deletions

View File

@@ -3921,9 +3921,11 @@ nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()*
Default is `"left"`.
• vertical: Split vertically |:vertical|.
• width: Window width (in character cells). Minimum of 1.
• win: |window-ID| window to split, or relative window when
creating a float (relative="win"). When splitting,
negative value works like |:topleft|, |:botright|.
• win: |window-ID| target window. Can be in a different tab
page. Determines the window to split (negative values act
like |:topleft|, |:botright|), the relative window for a
`relative="win"` float, or just the target tab page
(inferred from the window) for others.
• zindex: Stacking order. floats with higher `zindex` go on
top on floats with lower indices. Must be larger than
zero. The following screen elements have hard-coded

View File

@@ -167,6 +167,7 @@ API
they were so specified in `nvim_create_user_command()`.
• |nvim_open_win()| floating windows can show a 'statusline'. Plugins can use
`style='minimal'` or `:setlocal statusline=` to hide the statusline.
• |nvim_win_set_config()| can move windows to other tab pages as floats.
• Added experimental |nvim__exec_lua_fast()| to allow remote API clients to
execute code while nvim is blocking for input.
• |vim.secure.trust()| accepts `path` for the `allow` action.

View File

@@ -1848,8 +1848,9 @@ function vim.api.nvim_open_term(buffer, opts) end
--- Default is `"left"`.
--- - vertical: Split vertically `:vertical`.
--- - width: Window width (in character cells). Minimum of 1.
--- - win: `window-ID` window to split, or relative window when creating a float (relative="win").
--- When splitting, negative value works like `:topleft`, `:botright`.
--- - win: `window-ID` target window. Can be in a different tab page. Determines the window to
--- split (negative values act like `:topleft`, `:botright`), the relative window for a
--- `relative="win"` float, or just the target tab page (inferred from the window) for others.
--- - zindex: Stacking order. floats with higher `zindex` go on top on
--- floats with lower indices. Must be larger than zero. The
--- following screen elements have hard-coded z-indices:

View File

@@ -18,6 +18,8 @@
#include "nvim/drawscreen.h"
#include "nvim/errors.h"
#include "nvim/eval/window.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/globals.h"
#include "nvim/highlight_group.h"
#include "nvim/macros_defs.h"
@@ -32,6 +34,7 @@
#include "nvim/syntax.h"
#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
#include "nvim/ui_defs.h"
#include "nvim/vim_defs.h"
#include "nvim/window.h"
@@ -39,6 +42,8 @@
#include "api/win_config.c.generated.h"
#define HAS_KEY_X(d, key) HAS_KEY(d, win_config, key)
/// Opens a new split window, floating window, or external window.
///
/// - Specify `relative` to create a floating window. Floats are drawn over the split layout,
@@ -177,8 +182,9 @@
/// Default is `"left"`.
/// - vertical: Split vertically |:vertical|.
/// - width: Window width (in character cells). Minimum of 1.
/// - win: |window-ID| window to split, or relative window when creating a float (relative="win").
/// When splitting, negative value works like |:topleft|, |:botright|.
/// - win: |window-ID| target window. Can be in a different tab page. Determines the window to
/// split (negative values act like |:topleft|, |:botright|), the relative window for a
/// `relative="win"` float, or just the target tab page (inferred from the window) for others.
/// - zindex: Stacking order. floats with higher `zindex` go on top on
/// floats with lower indices. Must be larger than zero. The
/// following screen elements have hard-coded z-indices:
@@ -197,7 +203,6 @@
Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Error *err)
FUNC_API_SINCE(6) FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
#define HAS_KEY_X(d, key) HAS_KEY(d, win_config, key)
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
return 0;
@@ -348,7 +353,6 @@ cleanup:
unblock_autocmds();
}
return rv;
#undef HAS_KEY_X
}
static WinSplit win_split_dir(win_T *win)
@@ -381,10 +385,56 @@ static int win_split_flags(WinSplit split, bool toplevel)
return flags;
}
static bool win_config_split(win_T *win, Dict(win_config) *config, WinConfig *fconfig, Error *err)
/// Checks if window `wp` can be moved to tabpage `tp`.
static bool win_can_move_tp(win_T *wp, tabpage_T *tp, Error *err)
FUNC_ATTR_NONNULL_ALL
{
if (one_window(wp, tp == curtab ? NULL : tp)) {
api_set_error(err, kErrorTypeException, "Cannot move last non-floating window");
return false;
}
// Like closing, moving windows between tabpages makes win_valid return false. Helpful when e.g:
// walking the window list, as w_next/w_prev can unexpectedly refer to windows in another tabpage!
// Check related locks, in case they were set to avoid checking win_valid.
if (win_locked(wp)) {
api_set_error(err, kErrorTypeException, "Cannot move window to another tabpage whilst in use");
return false;
}
if (window_layout_locked_err(CMD_SIZE, err)) {
return false; // error already set
}
if (textlock || expr_map_locked()) {
api_set_error(err, kErrorTypeException, "%s", e_textlock);
return false;
}
if (is_aucmd_win(wp)) {
api_set_error(err, kErrorTypeException, "Cannot move autocmd window to another tabpage");
return false;
}
// Can't move the cmdwin or its old curwin to a different tabpage.
if (wp == cmdwin_win || wp == cmdwin_old_curwin) {
api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
return false;
}
return true;
}
static win_T *win_find_altwin(win_T *win, tabpage_T *tp)
FUNC_ATTR_NONNULL_ALL
{
if (win->w_floating) {
return win_float_find_altwin(win, tp == curtab ? NULL : tp);
} else {
int dir;
return winframe_find_altwin(win, &dir, tp == curtab ? NULL : tp, NULL);
}
}
/// Configures `win` into a split, also moving it to another tabpage if requested.
static bool win_config_split(win_T *win, const Dict(win_config) *config, WinConfig *fconfig,
Error *err)
FUNC_ATTR_NONNULL_ALL
{
#define HAS_KEY_X(d, key) HAS_KEY(d, win_config, key)
bool was_split = !win->w_floating;
bool has_split = HAS_KEY_X(config, split);
bool has_vertical = HAS_KEY_X(config, vertical);
@@ -423,14 +473,8 @@ static bool win_config_split(win_T *win, Dict(win_config) *config, WinConfig *fc
api_set_error(err, kErrorTypeException, "Cannot split a floating window");
return false;
}
if (is_aucmd_win(win) && win_tp != parent_tp) {
api_set_error(err, kErrorTypeException, "Cannot move autocmd window to another tabpage");
return false;
}
// Can't move the cmdwin or its old curwin to a different tabpage.
if ((win == cmdwin_win || win == cmdwin_old_curwin) && win_tp != parent_tp) {
api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
return false;
if (win_tp != parent_tp && !win_can_move_tp(win, win_tp, err)) {
return false; // error already set
}
}
@@ -443,24 +487,13 @@ static bool win_config_split(win_T *win, Dict(win_config) *config, WinConfig *fc
// window list or remove its frame (if non-floating), so it's valid for autocommands.
const bool curwin_moving_tp = win == curwin && parent && win_tp != parent_tp;
if (curwin_moving_tp) {
if (was_split) {
int dir;
win_T *altwin = winframe_find_altwin(win, &dir, NULL, NULL);
// Autocommands may still make this the last non-float after this check.
// That case will be caught later when trying to move the window.
if (!altwin) {
api_set_error(err, kErrorTypeException, "Cannot move last non-floating window");
return false;
}
win_goto(altwin);
} else {
win_goto(win_float_find_altwin(win, NULL));
}
win_T *altwin = win_find_altwin(win, win_tp);
assert(altwin); // win_can_move_tp ensures `win` is not the only window
win_goto(altwin);
// 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);
api_set_error(err, kErrorTypeException, "Failed to switch away from window %d", win->handle);
return false;
}
win_tp = win_find_tabpage(win);
@@ -608,7 +641,98 @@ resize:
}
merge_win_config(&win->w_config, *fconfig);
return true;
#undef HAS_KEY_X
}
/// Configures `win` into a float, also moving it to another tabpage if requested.
static bool win_config_float_tp(win_T *win, const Dict(win_config) *config,
const WinConfig *fconfig, Error *err)
FUNC_ATTR_NONNULL_ALL
{
tabpage_T *win_tp = win_find_tabpage(win);
win_T *parent = win;
tabpage_T *parent_tp = win_tp;
if (HAS_KEY_X(config, win)) {
parent = find_window_by_handle(fconfig->window, err);
if (!parent) {
return false; // error already set
}
parent_tp = win_find_tabpage(parent);
}
bool curwin_moving_tp = false;
win_T *altwin = NULL;
if (win_tp != parent_tp) {
if (!win_can_move_tp(win, win_tp, err)) {
return false; // error already set
}
altwin = win_find_altwin(win, win_tp);
assert(altwin); // win_can_move_tp ensures `win` is not the only window
// 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.
if (curwin == win) {
curwin_moving_tp = true;
win_goto(altwin);
// 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 false;
}
win_tp = win_find_tabpage(win);
parent_tp = win_find_tabpage(parent);
if (!win_tp || !parent_tp) {
api_set_error(err, kErrorTypeException, "Target windows were closed");
goto restore_curwin;
}
if (win_tp != parent_tp && !win_can_move_tp(win, win_tp, err)) {
goto restore_curwin; // error already set
}
altwin = win_find_altwin(win, win_tp);
assert(altwin); // win_can_move_tp ensures `win` is not the only window
}
}
// Convert the window to a float if needed.
if (!win->w_floating) {
if (!win_new_float(win, false, *fconfig, err)) {
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 false;
}
redraw_later(win, UPD_NOT_VALID);
}
if (win_tp != parent_tp) {
win_remove(win, win_tp == curtab ? NULL : win_tp);
tabpage_T *append_tp = parent_tp == curtab ? NULL : parent_tp;
win_append(lastwin_nofloating(append_tp), win, append_tp);
// If `win` was the curwin of its old tabpage, select a new curwin for it.
if (win_tp != curtab && win_tp->tp_curwin == win) {
win_tp->tp_curwin = altwin;
}
// Remove grid if present. More reliable than checking curtab, as tabpage_check_windows may not
// run when temporarily switching tabpages, meaning grids may be stale from another tabpage!
// (e.g: switch_win_noblock with no_display=true)
ui_comp_remove_grid(&win->w_grid_alloc);
// Redraw tabline, update window's hl attribs, etc. Set must_redraw here, as redraw_later might
// not if w_redr_type >= UPD_NOT_VALID was set in the old tabpage.
redraw_later(win, UPD_NOT_VALID);
set_must_redraw(UPD_NOT_VALID);
}
win_config_float(win, *fconfig);
return true;
}
/// Reconfigures the layout and properties of a window.
@@ -631,7 +755,6 @@ resize:
void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err)
FUNC_API_SINCE(6)
{
#define HAS_KEY_X(d, key) HAS_KEY(d, win_config, key)
win_T *win = find_window_by_handle(window, err);
if (!win) {
return;
@@ -645,24 +768,21 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err)
WinConfig fconfig = win->w_config;
bool to_split = config->relative.size == 0
&& !(HAS_KEY_X(config, external) ? config->external : fconfig.external)
&& !(HAS_KEY_X(config, external) && config->external)
&& (has_split || has_vertical || was_split);
if (!parse_win_config(win, config, &fconfig, !was_split || to_split, err)) {
return;
}
if (was_split && !to_split) {
if (!win_new_float(win, false, fconfig, err)) {
return;
}
redraw_later(win, UPD_NOT_VALID);
} else if (to_split) {
if (to_split) {
if (!win_config_split(win, config, &fconfig, err)) {
return;
}
} else {
win_config_float(win, fconfig);
if (!win_config_float_tp(win, config, &fconfig, err)) {
return;
}
}
if (fconfig.style == kWinStyleMinimal && old_style != fconfig.style) {
@@ -675,7 +795,6 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err)
} else if (win == cmdline_win && fconfig._cmdline_offset == INT_MAX) {
cmdline_win = NULL;
}
#undef HAS_KEY_X
}
#define PUT_KEY_X(d, key, value) PUT_KEY(d, win_config, key, value)
@@ -1144,7 +1263,6 @@ bool parse_winborder(WinConfig *fconfig, char *border_opt, Error *err)
static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fconfig, bool reconf,
Error *err)
{
#define HAS_KEY_X(d, key) HAS_KEY(d, win_config, key)
bool has_relative = false, relative_is_win = false, is_split = false;
if (config->relative.size > 0) {
if (!parse_float_relative(config->relative, &fconfig->relative)) {
@@ -1167,6 +1285,7 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
} else if (!config->external) {
if (HAS_KEY_X(config, vertical) || HAS_KEY_X(config, split)) {
is_split = true;
fconfig->external = false;
} else if (wp == NULL) { // new win
api_set_error(err, kErrorTypeValidation,
"Must specify 'relative' or 'external' when creating a float");
@@ -1258,36 +1377,6 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
goto fail;
}
if (relative_is_win || is_split) {
if (reconf && relative_is_win) {
win_T *target_win = find_window_by_handle(config->win, err);
if (!target_win) {
goto fail;
}
if (target_win == wp) {
api_set_error(err, kErrorTypeException, "floating window cannot be relative to itself");
goto fail;
}
}
fconfig->window = curwin->handle;
if (HAS_KEY_X(config, win)) {
if (config->win > 0) {
fconfig->window = 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=''");
goto fail;
} else if (!is_split) {
api_set_error(err, kErrorTypeValidation,
"non-float with 'win' requires at least 'split' or 'vertical'");
goto fail;
}
}
if (HAS_KEY_X(config, external)) {
fconfig->external = config->external;
if (has_relative && fconfig->external) {
@@ -1301,6 +1390,38 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
}
}
if (HAS_KEY_X(config, win) && fconfig->external) {
api_set_error(err, kErrorTypeValidation, "external window cannot have 'win'");
goto fail;
}
if (relative_is_win || (HAS_KEY_X(config, win) && !is_split && wp && wp->w_floating
&& fconfig->relative == kFloatRelativeWindow)) {
// When relative=win is given, missing win field means win=0.
win_T *target_win = find_window_by_handle(config->win, err);
if (!target_win) {
goto fail;
}
if (target_win == wp) {
api_set_error(err, kErrorTypeException, "floating window cannot be relative to itself");
goto fail;
}
fconfig->window = target_win->handle;
} else {
// Handle is not validated here, as win_config_split can accept negative values.
if (HAS_KEY_X(config, win)) {
if (!is_split && !has_relative && (!wp || !wp->w_floating)) {
api_set_error(err, kErrorTypeValidation,
"non-float with 'win' requires at least 'split' or 'vertical'");
goto fail;
}
fconfig->window = config->win;
}
// Resolve, but skip validating. E.g: win_config_split accepts negative "win".
if (fconfig->window == 0) {
fconfig->window = curwin->handle;
}
}
if (HAS_KEY_X(config, focusable)) {
fconfig->focusable = config->focusable;
fconfig->mouse = config->focusable;
@@ -1422,5 +1543,4 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
fail:
merge_win_config(fconfig, wp != NULL ? wp->w_config : WIN_CONFIG_INIT);
return false;
#undef HAS_KEY_X
}

View File

@@ -1116,7 +1116,7 @@ static void do_arg_all(int count, int forceit, int keep_tabs)
last_curwin = curwin;
last_curtab = curtab;
// lastwin may be aucmd_win
win_enter(lastwin_nofloating(), false);
win_enter(lastwin_nofloating(NULL), false);
// Open up to "count" windows.
arg_all_open_windows(&aall, count);

View File

@@ -3692,7 +3692,7 @@ void ex_buffer_all(exarg_T *eap)
// Don't execute Win/Buf Enter/Leave autocommands here.
autocmd_no_enter++;
// lastwin may be aucmd_win
win_enter(lastwin_nofloating(), false);
win_enter(lastwin_nofloating(NULL), false);
autocmd_no_leave++;
for (buf_T *buf = firstbuf; buf != NULL && open_wins < count; buf = buf->b_next) {
// Check if this buffer needs a window

View File

@@ -4062,7 +4062,7 @@ void compute_cmdrow(void)
if (exmode_active || msg_scrolled != 0) {
cmdline_row = Rows - 1;
} else {
win_T *wp = lastwin_nofloating();
win_T *wp = lastwin_nofloating(NULL);
cmdline_row = wp->w_winrow + wp->w_height
+ wp->w_hsep_height + wp->w_status_height + global_stl_height();
}

View File

@@ -440,7 +440,7 @@ void win_redr_winbar(win_T *wp)
void redraw_ruler(void)
{
static int did_ruler_col = -1;
win_T *wp = curwin->w_status_height == 0 ? curwin : lastwin_nofloating();
win_T *wp = curwin->w_status_height == 0 ? curwin : lastwin_nofloating(NULL);
bool is_stl_global = global_stl_height() > 0;
// Check if ruler should be drawn, clear if it was drawn before.

View File

@@ -28,7 +28,6 @@
#include "nvim/eval/window.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
@@ -143,12 +142,26 @@ bool frames_locked(void)
/// error message. When closing window(s) and the command isn't easy to know,
/// passing CMD_SIZE will also work.
bool window_layout_locked(cmdidx_T cmd)
{
Error err = ERROR_INIT;
const bool locked = window_layout_locked_err(cmd, &err);
if (ERROR_SET(&err)) {
emsg(_(err.msg));
api_clear_error(&err);
}
return locked;
}
/// Like `window_layout_locked`, but set `err` to the (untranslated) error message when locked.
/// @see window_layout_locked
bool window_layout_locked_err(cmdidx_T cmd, Error *err)
{
if (split_disallowed > 0 || close_disallowed > 0) {
if (close_disallowed == 0 && cmd == CMD_tabnew) {
emsg(_(e_cannot_split_window_when_closing_buffer));
api_set_error(err, kErrorTypeException, "%s", e_cannot_split_window_when_closing_buffer);
} else {
emsg(_(e_not_allowed_to_change_window_layout_in_this_autocmd));
api_set_error(err, kErrorTypeException, "%s",
e_not_allowed_to_change_window_layout_in_this_autocmd);
}
return true;
}
@@ -505,7 +518,7 @@ newwindow:
// cursor to bottom-right window
case 'b':
case Ctrl_B:
win_goto(lastwin_nofloating());
win_goto(lastwin_nofloating(NULL));
break;
// cursor to last accessed (previous) window
@@ -1158,7 +1171,7 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir, frame_T *to_fl
oldwin = firstwin;
} else if (flags & WSP_BOT || curwin->w_floating) {
// can't split float, use last nonfloating window instead
oldwin = lastwin_nofloating();
oldwin = lastwin_nofloating(NULL);
} else {
oldwin = curwin;
}
@@ -4800,7 +4813,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, NULL);
win_append(lastwin_nofloating(NULL), wp, NULL);
} else {
ui_comp_remove_grid(&wp->w_grid_alloc);
}
@@ -6237,7 +6250,7 @@ static void frame_setheight(frame_T *curfrp, int height)
if (curfrp->fr_width != Columns) {
room_cmdline = 0;
} else {
win_T *wp = lastwin_nofloating();
win_T *wp = lastwin_nofloating(NULL);
room_cmdline = Rows - (int)p_ch - global_stl_height()
- (wp->w_winrow + wp->w_height + wp->w_hsep_height + wp->w_status_height);
room_cmdline = MAX(room_cmdline, 0);
@@ -7051,7 +7064,7 @@ void command_height(void)
int old_p_ch = (int)curtab->tp_ch_used;
// Find bottom frame with width of screen.
frame_T *frp = lastwin_nofloating()->w_frame;
frame_T *frp = lastwin_nofloating(NULL)->w_frame;
while (frp->fr_width != Columns && frp->fr_parent != NULL) {
frp = frp->fr_parent;
}
@@ -7804,9 +7817,11 @@ void win_ui_flush(bool validate)
msg_ui_flush();
}
win_T *lastwin_nofloating(void)
/// @return last non-floating window in `tp`, or NULL for current tabpage.
win_T *lastwin_nofloating(tabpage_T *tp)
{
win_T *res = lastwin;
assert(tp != curtab || !tp);
win_T *res = tp ? tp->tp_lastwin : lastwin;
while (res->w_floating) {
res = res->w_prev;
}

View File

@@ -35,10 +35,9 @@
#include "winfloat.c.generated.h"
/// Create a new float.
/// Creates a new float, or transforms an existing window to a float.
///
/// @param wp if NULL, allocate a new window, otherwise turn existing window into a float.
/// It must then already belong to the current tabpage!
/// @param last make the window the last one in the window list.
/// Only used when allocating the autocommand window.
/// @param config must already have been validated!
@@ -46,7 +45,7 @@ win_T *win_new_float(win_T *wp, bool last, WinConfig fconfig, Error *err)
{
if (wp == NULL) {
tabpage_T *tp = NULL;
win_T *tp_last = last ? lastwin : lastwin_nofloating();
win_T *tp_last = last ? lastwin : lastwin_nofloating(NULL);
if (fconfig.window != 0) {
assert(!last);
win_T *parent_wp = find_window_by_handle(fconfig.window, err);
@@ -57,10 +56,7 @@ win_T *win_new_float(win_T *wp, bool last, WinConfig fconfig, Error *err)
if (!tp) {
return NULL;
}
tp_last = tp == curtab ? lastwin : tp->tp_lastwin;
while (tp_last->w_floating && tp_last->w_prev) {
tp_last = tp_last->w_prev;
}
tp_last = lastwin_nofloating(tp == curtab ? NULL : tp);
}
wp = win_alloc(tp_last, false);
win_init(wp, curwin, 0);
@@ -77,19 +73,19 @@ win_T *win_new_float(win_T *wp, bool last, WinConfig fconfig, Error *err)
} else {
assert(!last);
assert(!wp->w_floating);
if (firstwin == wp && lastwin_nofloating() == wp) {
// last non-float
api_set_error(err, kErrorTypeException,
"Cannot change last window into float");
return NULL;
} else if (!win_valid(wp)) {
api_set_error(err, kErrorTypeException,
"Cannot change window from different tabpage into float");
tabpage_T *win_tp = win_find_tabpage(wp);
assert(win_tp);
if ((win_tp == curtab && firstwin == wp && lastwin_nofloating(NULL) == wp)
|| (win_tp != curtab && win_tp->tp_firstwin == wp && lastwin_nofloating(win_tp) == wp)) {
api_set_error(err, kErrorTypeException, "Cannot change last window 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) {
FOR_ALL_WINDOWS_IN_TAB(wp2, win_tp) {
if (wp2->w_floating) {
break;
}
if (wp2 != wp && wp2 != cmdwin_win) {
other_nonfloat = true;
break;
@@ -100,12 +96,16 @@ win_T *win_new_float(win_T *wp, bool last, WinConfig fconfig, Error *err)
return NULL;
}
}
tabpage_T *tp = win_tp == curtab ? NULL : win_tp;
int dir;
winframe_remove(wp, &dir, NULL, NULL);
winframe_remove(wp, &dir, tp, NULL);
XFREE_CLEAR(wp->w_frame);
win_comp_pos(); // recompute window positions
win_remove(wp, NULL);
win_append(lastwin_nofloating(), wp, NULL);
win_remove(wp, tp);
if (win_tp == curtab) {
last_status(false); // may need to remove last status line
win_comp_pos(); // recompute window positions
}
win_append(lastwin_nofloating(tp), wp, tp);
}
wp->w_floating = true;
wp->w_status_height = wp->w_p_stl && *wp->w_p_stl != NUL

View File

@@ -2561,60 +2561,88 @@ describe('API/win', function()
eq(win, layout[2][2][2])
end)
it('moves splits to other tabpages', function()
local curtab = api.nvim_get_current_tabpage()
it('moves windows to other tabpages', function()
local first_tab = api.nvim_get_current_tabpage()
local first_win = api.nvim_get_current_win()
local win = api.nvim_open_win(0, false, { split = 'left' })
command('tabnew')
local tabnr = api.nvim_get_current_tabpage()
command('tabprev') -- return to the initial tab
api.nvim_win_set_config(win, {
split = 'right',
win = api.nvim_tabpage_get_win(tabnr),
})
eq(tabnr, api.nvim_win_get_tabpage(win))
local new_tab = api.nvim_get_current_tabpage()
local tab2_win = api.nvim_get_current_win()
api.nvim_set_current_tabpage(first_tab)
-- move new win to new tabpage
api.nvim_win_set_config(win, { split = 'right', win = api.nvim_tabpage_get_win(new_tab) })
eq(new_tab, api.nvim_win_get_tabpage(win))
-- we are changing the config, the current tabpage should not change
eq(curtab, api.nvim_get_current_tabpage())
eq(first_tab, api.nvim_get_current_tabpage())
command('tabnext') -- switch to the new tabpage so we can get the layout
api.nvim_set_current_tabpage(new_tab)
local layout = fn.winlayout()
eq({
'row',
{
{ 'leaf', api.nvim_tabpage_get_win(tabnr) },
{ 'leaf', api.nvim_tabpage_get_win(new_tab) },
{ 'leaf', win },
},
}, layout)
-- directly convert split into a float for a different tabpage
local win2 = api.nvim_open_win(0, true, { split = 'below' })
eq('', api.nvim_win_get_config(win2).relative)
api.nvim_win_set_config(
win2,
{ relative = 'editor', row = 0, col = 0, width = 1, height = 1, win = first_win }
)
eq(first_tab, api.nvim_win_get_tabpage(win2))
eq('editor', api.nvim_win_get_config(win2).relative)
eq({ first_win, win2 }, api.nvim_tabpage_list_wins(first_tab))
eq({ tab2_win, win }, api.nvim_tabpage_list_wins(new_tab))
-- convert new win to float in new tabpage
api.nvim_win_set_config(win, { relative = 'editor', row = 2, col = 2, height = 2, width = 2 })
api.nvim_set_current_tabpage(first_tab)
-- move to other tabpage
api.nvim_win_set_config(win, { win = first_win })
eq(first_tab, api.nvim_win_get_tabpage(win))
eq({ first_win, win, win2 }, api.nvim_tabpage_list_wins(first_tab))
eq({ tab2_win }, api.nvim_tabpage_list_wins(new_tab))
-- unlike splits, negative win is invalid
eq('Invalid window id: -1', pcall_err(api.nvim_win_set_config, win, { win = -1 }))
-- can't convert only window in other tabpage to float
command('tabnew')
local only_win = api.nvim_get_current_win()
command('tabprevious')
eq(
'Cannot change last window into float',
pcall_err(
api.nvim_win_set_config,
only_win,
{ relative = 'editor', width = 5, height = 5, row = 0, col = 0 }
)
)
end)
it('correctly moves curwin when moving curwin to a different tabpage', function()
local curtab = api.nvim_get_current_tabpage()
local tab1 = api.nvim_get_current_tabpage()
local tab1_win = api.nvim_get_current_win()
command('tabnew')
local tab2 = api.nvim_get_current_tabpage()
local tab2_win = api.nvim_get_current_win()
command('tabprev') -- return to the initial tab
local neighbor = api.nvim_get_current_win()
api.nvim_set_current_tabpage(tab1) -- return to the initial tab
-- create and enter a new split
local win = api.nvim_open_win(0, true, {
vertical = false,
})
eq(curtab, api.nvim_win_get_tabpage(win))
eq({ win, neighbor }, api.nvim_tabpage_list_wins(curtab))
eq(tab1, api.nvim_win_get_tabpage(win))
eq({ win, tab1_win }, api.nvim_tabpage_list_wins(tab1))
-- move the current win to a different tabpage
api.nvim_win_set_config(win, {
split = 'right',
win = api.nvim_tabpage_get_win(tab2),
})
eq(curtab, api.nvim_get_current_tabpage())
eq(tab1, api.nvim_get_current_tabpage())
-- win should have moved to tab2
eq(tab2, api.nvim_win_get_tabpage(win))
@@ -2622,10 +2650,18 @@ describe('API/win', function()
eq(tab2_win, api.nvim_tabpage_get_win(tab2))
-- win lists should be correct
eq({ tab2_win, win }, api.nvim_tabpage_list_wins(tab2))
eq({ neighbor }, api.nvim_tabpage_list_wins(curtab))
eq({ tab1_win }, api.nvim_tabpage_list_wins(tab1))
-- current win should have moved to neighboring win
eq(neighbor, api.nvim_tabpage_get_win(curtab))
eq(tab1_win, api.nvim_tabpage_get_win(tab1))
api.nvim_set_current_tabpage(tab2)
-- convert new win to float
api.nvim_win_set_config(win, { relative = 'editor', row = 2, col = 2, height = 2, width = 2 })
api.nvim_set_current_win(win)
api.nvim_win_set_config(win, { relative = 'win', win = tab1_win, row = 3, col = 3 })
eq(tab1, api.nvim_win_get_tabpage(win))
eq(tab2, api.nvim_get_current_tabpage())
eq({ tab1_win, win }, api.nvim_tabpage_list_wins(tab1))
end)
it('splits windows in non-current tabpage', function()
@@ -2752,7 +2788,6 @@ describe('API/win', function()
it('messing with "win" or "parent" when moving "win" to other tabpage', function()
command('split | tabnew')
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()
@@ -2797,23 +2832,19 @@ describe('API/win', function()
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.
-- Try to make "parent" floating. This should give the same error as before.
exec(([[
autocmd WinLeave * ++once silent!
autocmd WinLeave * ++once
\ 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')
eq(
'Floating state of windows to split changed',
pcall_err(api.nvim_win_set_config, 0, { win = t2_win3, split = 'left' })
)
-- 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))
eq('editor', api.nvim_win_get_config(t2_win3).relative)
eq('', api.nvim_win_get_config(0).relative)
eq(cur_win, api.nvim_get_current_win())
end)
it('expected autocmds when moving window to other tabpage', function()
@@ -2834,18 +2865,35 @@ describe('API/win', function()
eq({ 'Leave', win, 'Enter', new_curwin }, eval('result'))
end)
it('no autocmds when moving window within same tabpage', function()
it('no autocmds when moving window in same or other tabpage', function()
local parent = api.nvim_get_current_win()
exec([[
split
let result = []
autocmd WinEnter * let result += ["Enter", win_getid()]
autocmd WinLeave * let result += ["Leave", win_getid()]
autocmd WinNew * let result += ["New", win_getid()]
let g:result = []
autocmd WinEnter * let g:result += ["Enter", win_getid()]
autocmd WinLeave * let g:result += ["Leave", win_getid()]
autocmd WinNew * let g:result += ["New", win_getid()]
]])
api.nvim_win_set_config(0, { win = parent, split = 'left' })
-- Shouldn't see any of those events, as we remain in the same window.
eq({}, eval('result'))
eq({}, eval('g:result'))
-- move float window from tab2 to tab1
command('tabdo only')
local tab1 = api.nvim_get_current_tabpage()
local tab1_win1 = api.nvim_get_current_win()
command('tabnew')
local fwin = api.nvim_open_win(0, false, {
relative = 'editor',
row = 2,
col = 2,
height = 2,
width = 2,
})
api.nvim_set_current_tabpage(tab1)
api.nvim_set_var('result', {})
api.nvim_win_set_config(fwin, { win = tab1_win1 })
eq({}, eval('g:result'))
end)
it('checks if splitting disallowed', function()
@@ -3120,7 +3168,6 @@ describe('API/win', function()
)
command('quit!')
-- Can't switch away from window before moving it to a different tabpage during textlock.
exec(([[
new
call setline(1, 'foo')
@@ -3132,6 +3179,41 @@ describe('API/win', function()
pcall_err(command, 'normal! ==')
)
eq(cur_win, api.nvim_get_current_win())
exec(([[
wincmd p
call setline(1, 'bar')
setlocal indentexpr=nvim_win_set_config(win_getid(winnr('#')),#{split:'left',win:%d})
]]):format(t2_win))
neq(cur_win, api.nvim_get_current_win())
matches(
'E565: Not allowed to change text or change window$',
pcall_err(command, 'normal! ==')
)
-- expr_map_lock
exec(([[
nnoremap <expr> @ nvim_win_set_config(win_getid(winnr('#')),#{split:'left',win:%d})
]]):format(t2_win))
neq(cur_win, api.nvim_get_current_win())
matches(
'E565: Not allowed to change text or change window$',
pcall_err(fn.feedkeys, '@', 'x')
)
exec(([[
wincmd p
autocmd WinNewPre * ++once call nvim_win_set_config(0, #{relative:'editor', win:%d, row:0, col:0, width:1, height:1})
]]):format(t2_win))
matches(
'E1312: Not allowed to change the window layout in this autocmd$',
pcall_err(command, 'split')
)
eq(cur_win, api.nvim_get_current_win()) -- :split didn't enter new window due to error
exec(([[
autocmd WinLeave * ++once call nvim_win_set_config(0, #{relative:'editor', win:%d, row:0, col:0, width:1, height:1})
]]):format(t2_win))
matches('Cannot move window to another tabpage whilst in use$', pcall_err(command, 'quit'))
eq(cur_win, api.nvim_get_current_win()) -- :quit didn't close window due to error
end)
it('updates statusline when moving bottom split', function()
@@ -3190,6 +3272,35 @@ describe('API/win', function()
api.nvim_win_set_config(t2_cur_win, { split = 'left', win = 0 })
eq(t2_alt_win, api.nvim_tabpage_get_win(t2))
eq(t1, api.nvim_win_get_tabpage(t2_cur_win))
-- Very fun: move curwin between tabpages, converting from split to float, but with an autocmd
-- that deletes altwin after we're bumped to it, re-enters curwin, then switches to a 3rd
-- tabpage. tp_curwin of the window's old tabpage shouldn't be set to the freed altwin!
command('tablast | tab split | tabprevious | split')
command('autocmd WinEnter * ++once quit | let expect_alt = win_getid() | wincmd p | tabnext')
api.nvim_win_set_config(0, {
relative = 'editor',
win = api.nvim_tabpage_get_win(t1),
row = 0,
col = 0,
width = 5,
height = 5,
})
eq(eval('g:expect_alt'), api.nvim_tabpage_get_win(t2))
-- Same, but for float -> float.
command('tabprevious | split')
api.nvim_open_win(0, true, { relative = 'editor', row = 0, col = 0, width = 1, height = 1 })
command('autocmd WinEnter * ++once quit | let expect_alt = win_getid() | wincmd p | tabnext')
api.nvim_win_set_config(0, {
relative = 'editor',
win = api.nvim_tabpage_get_win(t1),
row = 0,
col = 0,
width = 5,
height = 5,
})
eq(eval('g:expect_alt'), api.nvim_tabpage_get_win(t2))
end)
it('set_config cannot change "noautocmd" #36409', function()
@@ -3203,6 +3314,64 @@ describe('API/win', function()
pcall_err(api.nvim_win_set_config, win, cfg)
)
end)
it('removes last statusline if needed', function()
local screen = Screen.new(30, 9)
command('set laststatus=1 | botright split')
screen:expect([[
|
{1:~ }|*2
{2:[No Name] }|
^ |
{1:~ }|*2
{3:[No Name] }|
|
]])
api.nvim_win_set_config(0, { relative = 'editor', row = 0, col = 0, width = 4, height = 4 })
screen:expect([[
{4:^ } |
{11:~ }{1: }|*3
{1:~ }|*4
|
]])
command('quit | set laststatus=2 | botright split')
screen:expect([[
|
{1:~ }|*2
{2:[No Name] }|
^ |
{1:~ }|*2
{3:[No Name] }|
|
]])
api.nvim_win_set_config(0, { relative = 'editor', row = 1, col = 5, width = 4, height = 4 })
screen:expect([[
|
{1:~ }{4:^ }{1: }|
{1:~ }{11:~ }{1: }|*3
{1:~ }|*2
{2:[No Name] }|
|
]])
end)
it('can convert external window to non-external', function()
Screen.new(20, 7, { ext_multigrid = true }) -- multigrid needed for external windows
api.nvim_open_win(0, true, { external = true, width = 5, height = 5 })
eq(true, api.nvim_win_get_config(0).external)
api.nvim_win_set_config(0, { split = 'below', win = fn.win_getid(1) })
eq(false, api.nvim_win_get_config(0).external)
api.nvim_win_set_config(0, { external = true, width = 5, height = 5 })
eq(true, api.nvim_win_get_config(0).external)
api.nvim_win_set_config(0, { relative = 'editor', row = 3, col = 3 })
eq(false, api.nvim_win_get_config(0).external)
api.nvim_win_set_config(0, { external = true, width = 5, height = 5 })
eq(true, api.nvim_win_get_config(0).external)
api.nvim_win_set_config(0, { external = false })
eq(false, api.nvim_win_get_config(0).external)
end)
end)
describe('get_config', function()
@@ -3542,20 +3711,25 @@ describe('API/win', function()
end)
it('cannot move autocmd window between tabpages', function()
local win_type, split_ok, err = exec_lua(function()
local win_type, split_ok, split_err, float_ok, float_err = exec_lua(function()
local other_tp_win = vim.api.nvim_get_current_win()
vim.cmd.tabnew()
local win_type, split_ok, err
local win_type, split_ok, split_err, float_ok, float_err
vim.api.nvim_buf_call(vim.api.nvim_create_buf(true, true), function()
win_type = vim.fn.win_gettype()
split_ok, err =
split_ok, split_err =
pcall(vim.api.nvim_win_set_config, 0, { win = other_tp_win, split = 'right' })
float_ok, float_err = pcall(vim.api.nvim_win_set_config, 0, { win = other_tp_win })
end)
return win_type, split_ok, err
return win_type, split_ok, split_err, float_ok, float_err
end)
eq('autocmd', win_type)
eq({ false, 'Cannot move autocmd window to another tabpage' }, { split_ok, err })
eq({ false, 'Cannot move autocmd window to another tabpage' }, { split_ok, split_err })
eq({ false, 'Cannot move autocmd window to another tabpage' }, { float_ok, float_err })
end)
it('cannot move cmdwin between tabpages', function()
@@ -3656,5 +3830,35 @@ describe('API/win', function()
api.nvim_open_win(0, true, { split = 'below', style = 'minimal' })
command('quit')
end)
it('preserve current floating window when moving fails', function()
local buf = api.nvim_create_buf(false, true)
local float_win = api.nvim_open_win(buf, true, {
relative = 'editor',
row = 1,
col = 1,
width = 10,
height = 5,
})
command('tabnew')
local tab2_win = api.nvim_get_current_win()
command('tabprev')
api.nvim_set_current_win(float_win)
command('autocmd WinLeave * ++once call nvim_win_close(' .. tab2_win .. ', v:true)')
eq(
'Target windows were closed',
pcall_err(api.nvim_win_set_config, float_win, { win = tab2_win })
)
eq(float_win, api.nvim_get_current_win())
command('tabnew')
local tab3_win = api.nvim_get_current_win()
command('tabprev | autocmd WinEnter * ++once wincmd p')
eq(
('Failed to switch away from window %d'):format(float_win),
pcall_err(api.nvim_win_set_config, float_win, { win = tab3_win })
)
eq(float_win, api.nvim_get_current_win())
end)
end)
end)

View File

@@ -3782,10 +3782,7 @@ describe('float window', function()
it('API has proper error messages', function()
local buf = api.nvim_create_buf(false, false)
eq("Invalid key: 'bork'", pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 2, bork = true }))
eq(
"'win' key is only valid with relative='win' and relative=''",
pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 2, relative = 'editor', row = 0, col = 0, win = 0 })
)
eq("Must specify 'relative' or 'external' when creating a float", pcall_err(api.nvim_open_win, buf, false, { win = 0 }))
eq(
"floating windows cannot have 'vertical'",
pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 2, relative = 'editor', row = 0, col = 0, vertical = true })
@@ -3824,6 +3821,18 @@ describe('float window', function()
)
eq("Must specify 'width'", pcall_err(api.nvim_open_win, buf, false, { relative = 'editor', row = 0, col = 0 }))
eq("Must specify 'height'", pcall_err(api.nvim_open_win, buf, false, { relative = 'editor', row = 0, col = 0, width = 2 }))
if multigrid then
eq(
"external window cannot have 'win'",
pcall_err(api.nvim_open_win, buf, false, { external = true, win = 0, width = 10, height = 10 })
)
api.nvim_open_win(buf, true, { external = true, width = 10, height = 10 })
eq("external window cannot have 'win'", pcall_err(api.nvim_win_set_config, 0, { win = 0 }))
-- OK to include "win" if external window is also reconfigured to a normal float.
api.nvim_win_set_config(0, { relative = 'editor', win = 0, row = 0, col = 0, width = 5, height = 5 })
eq('editor', api.nvim_win_get_config(0).relative)
end
end)
it('can be placed relative window or cursor', function()
@@ -7758,12 +7767,11 @@ describe('float window', function()
screen:expect {
grid = [[
## grid 1
[2:----------------------------------------]|*5
{5:[No Name] [+] }|
[2:----------------------------------------]|*6
[3:----------------------------------------]|
## grid 2
x |
{0:~ }|*4
{0:~ }|*5
## grid 3
|
## grid 4
@@ -11191,6 +11199,19 @@ describe('float window', function()
local winid = api.nvim_open_win(buf, false, config)
api.nvim_set_current_win(winid)
eq('floating window cannot be relative to itself', pcall_err(api.nvim_win_set_config, winid, config))
eq('floating window cannot be relative to itself', pcall_err(api.nvim_win_set_config, winid, { win = winid }))
-- Don't assume win=0 if no win given for existing relative=win float; so no error.
api.nvim_win_set_config(winid, { width = 7 })
eq(7, api.nvim_win_get_config(winid).width)
-- Don't expect the error when configuring to something other than relative=win, as win=self
-- is fine in those cases. (though maybe pointless) Other errors might be expected, though.
eq('Cannot split a floating window', pcall_err(api.nvim_win_set_config, winid, { split = 'above', win = winid }))
eq('win', api.nvim_win_get_config(winid).relative)
api.nvim_win_set_config(winid, { relative = 'editor', win = winid, row = 3, col = 3 })
eq('editor', api.nvim_win_get_config(winid).relative)
-- An error when configuring split into relative=win float.
command('split')
eq('floating window cannot be relative to itself', pcall_err(api.nvim_win_set_config, 0, config))
end)
it('bufpos out of range', function()
@@ -11869,6 +11890,485 @@ describe('float window', function()
]])
end
end)
it('redrawn after moving tabpages via nvim_win_set_config()', function()
local tab1_win = api.nvim_get_current_win()
fn.setline(1, 'hello')
command('tab split')
local tab2_win = api.nvim_get_current_win()
-- Schedule an UPD_NOT_VALID redraw, but in one event move the float out of curtab before it's
-- handled. Do not flush before then.
local float = exec_lua(function()
local float = vim.api.nvim_open_win(0, true, { relative = 'editor', width = 10, height = 5, row = 1, col = 1 })
vim.api.nvim__redraw({ valid = false, flush = false })
vim.api.nvim_win_set_config(float, { win = tab1_win })
return float
end)
if multigrid then
screen:expect({
grid = [[
## grid 1
{9: }{10:2}{9:+ [No Name] }{3: + [No Name] }{5: }{9:X}|
[4:----------------------------------------]|*5
[3:----------------------------------------]|
## grid 2 (hidden)
hello |
{0:~ }|*5
## grid 3
|
## grid 4
^hello |
{0:~ }|*4
]],
})
else
screen:expect([[
{9: }{10:2}{9:+ [No Name] }{3: + [No Name] }{5: }{9:X}|
^hello |
{0:~ }|*4
|
]])
end
-- Importantly, want tabline redrawn and float's hl attribs to be correct here.
api.nvim_win_set_config(float, { win = 0 })
if multigrid then
screen:expect({
grid = [[
## grid 1
{9: + [No Name] }{3: }{11:2}{3:+ [No Name] }{5: }{9:X}|
[4:----------------------------------------]|*5
[3:----------------------------------------]|
## grid 2 (hidden)
hello |
{0:~ }|*5
## grid 3
|
## grid 4
^hello |
{0:~ }|*4
## grid 5
{1:hello }|
{2:~ }|*4
]],
float_pos = {
[5] = { 1002, 'NW', 1, 1, 1, true, 50, 1, 1, 1 },
},
})
else
screen:expect([[
{9: + [No Name] }{3: }{11:2}{3:+ [No Name] }{5: }{9:X}|
^h{1:hello } |
{0:~}{2:~ }{0: }|*4
|
]])
end
-- Autocommand runs within the first tabpage, but won't refresh grids when switching to it due
-- to switch_win_noblock having no_display set.
command(
('autocmd OptionSet rightleft ++once call nvim_win_set_config(%d, #{relative: "win", win: %d, row: 0, col: 0})'):format(
float,
tab1_win
)
)
api.nvim_set_option_value('rightleft', true, { win = tab1_win })
-- Stale grids should not have caused issues with removing the float's grid.
if multigrid then
screen:expect({
grid = [[
## grid 1
{9: }{10:2}{9:+ [No Name] }{3: + [No Name] }{5: }{9:X}|
[4:----------------------------------------]|*5
[3:----------------------------------------]|
## grid 2 (hidden)
hello |
{0:~ }|*5
## grid 3
|
## grid 4
^hello |
{0:~ }|*4
## grid 5 (hidden)
{1:hello }|
{2:~ }|*4
]],
})
else
screen:expect([[
{9: }{10:2}{9:+ [No Name] }{3: + [No Name] }{5: }{9:X}|
^hello |
{0:~ }|*4
|
]])
end
command('tabfirst')
if multigrid then
screen:expect({
grid = [[
## grid 1
{3: }{11:2}{3:+ [No Name] }{9: + [No Name] }{5: }{9:X}|
[2:----------------------------------------]|*5
[3:----------------------------------------]|
## grid 2
olle^h|
{0: ~}|*4
## grid 3
|
## grid 4 (hidden)
hello |
{0:~ }|*4
## grid 5
{1:hello }|
{2:~ }|*4
]],
float_pos = {
[5] = { 1002, 'NW', 2, 0, 0, true, 50, 1, 1, 0 },
},
})
else
screen:expect([[
{3: }{11:2}{3:+ [No Name] }{9: + [No Name] }{5: }{9:X}|
{1:hello } olle^h|
{2:~ }{0: ~}|*4
|
]])
end
-- Check tablines are redrawn even when moving floats between two non-current tabpages.
command('tabnew')
local tab3_win = api.nvim_get_current_win()
if multigrid then
screen:expect({
grid = [[
## grid 1
{9: }{10:2}{9:+ No Name] }{3: [No Name] }{9: + [No Name] }{5: }{9:X}|
[6:----------------------------------------]|*5
[3:----------------------------------------]|
## grid 2 (hidden)
olleh|
{0: ~}|*4
## grid 3
|
## grid 4 (hidden)
hello |
{0:~ }|*4
## grid 5 (hidden)
{1:hello }|
{2:~ }|*4
## grid 6
^ |
{0: ~}|*4
]],
})
else
screen:expect([[
{9: }{10:2}{9:+ No Name] }{3: [No Name] }{9: + [No Name] }{5: }{9:X}|
^ |
{0: ~}|*4
|
]])
end
api.nvim_win_set_config(float, { win = tab2_win })
if multigrid then
screen:expect({
grid = [[
## grid 1
{9: + [No Name] }{3: [No Name] }{9: }{10:2}{9:+ No Name] }{5: }{9:X}|
[6:----------------------------------------]|*5
[3:----------------------------------------]|
## grid 2 (hidden)
olleh|
{0: ~}|*4
## grid 3
|
## grid 4 (hidden)
hello |
{0:~ }|*4
## grid 5 (hidden)
{1:hello }|
{2:~ }|*4
## grid 6
^ |
{0: ~}|*4
]],
})
else
screen:expect([[
{9: + [No Name] }{3: [No Name] }{9: }{10:2}{9:+ No Name] }{5: }{9:X}|
^ |
{0: ~}|*4
|
]])
end
-- Try converting a split to a float, then moving it to another tabpage in one call.
command('set norightleft | new')
fn.setline(1, 'floaty mcfloatface')
api.nvim_win_set_config(0, { relative = 'editor', win = tab1_win, row = 3, col = 3, width = 15, height = 5 })
if multigrid then
screen:expect({
grid = [[
## grid 1
{9: }{10:2}{9:+ No Name] }{3: [No Name] }{9: }{10:2}{9:+ No Name] }{5: }{9:X}|
[6:----------------------------------------]|*5
[3:----------------------------------------]|
## grid 2 (hidden)
olleh|
{0: ~}|*4
## grid 3
|
## grid 4 (hidden)
hello |
{0:~ }|*4
## grid 5 (hidden)
{1:hello }|
{2:~ }|*4
## grid 6
^ |
{0:~ }|*4
## grid 7 (hidden)
floaty mcfloatface |
{0:~ }|
]],
})
else
screen:expect([[
{9: }{10:2}{9:+ No Name] }{3: [No Name] }{9: }{10:2}{9:+ No Name] }{5: }{9:X}|
^ |
{0:~ }|*4
|
]])
end
command('tabfirst')
if multigrid then
screen:expect({
grid = [[
## grid 1
{3: }{11:2}{3:+ No Name] }{9: [No Name] }{10:2}{9:+ No Name] }{5: }{9:X}|
[2:----------------------------------------]|*5
[3:----------------------------------------]|
## grid 2
olle^h|
{0: ~}|*4
## grid 3
|
## grid 4 (hidden)
hello |
{0:~ }|*4
## grid 5 (hidden)
{1:hello }|
{2:~ }|*4
## grid 6 (hidden)
|
{0:~ }|*4
## grid 7
{1:floaty mcfloatf}|
{1:ace }|
{2:~ }|*3
]],
float_pos = {
[7] = { 1004, 'NW', 1, 3, 3, true, 50, 1, 1, 3 },
},
})
else
screen:expect([[
{3: }{11:2}{3:+ No Name] }{9: [No Name] }{10:2}{9:+ No Name] }{5: }{9:X}|
{1:floaty mcfloatf} olle^h|
{0: }{1:ace }{0: ~}|
{0: }{2:~ }{0: ~}|*3
|
]])
end
-- Works when doing the same between two non-current tabpages.
local float2 = api.nvim_open_win(0, false, { split = 'below', win = tab3_win })
api.nvim_win_set_config(float2, { relative = 'win', win = tab2_win, row = 2, col = 7, width = 4, height = 4, border = 'single' })
if multigrid then
screen:expect({
grid = [[
## grid 1
{3: }{11:2}{3:+ No Name] }{9: [No Name] }{10:3}{9:+ No Name] }{5: }{9:X}|
[2:----------------------------------------]|*5
[3:----------------------------------------]|
## grid 2
olle^h|
{0: ~}|*4
## grid 3
|
## grid 4 (hidden)
hello |
{0:~ }|*4
## grid 5 (hidden)
{1:hello }|
{2:~ }|*4
## grid 6 (hidden)
|
{0:~ }|*4
## grid 7
{1:floaty mcfloatf}|
{1:ace }|
{2:~ }|*3
]],
float_pos = {
[7] = { 1004, 'NW', 1, 3, 3, true, 50, 1, 1, 3 },
},
})
else
screen:expect([[
{3: }{11:2}{3:+ No Name] }{9: [No Name] }{10:3}{9:+ No Name] }{5: }{9:X}|
{1:floaty mcfloatf} olle^h|
{0: }{1:ace }{0: ~}|
{0: }{2:~ }{0: ~}|*3
|
]])
end
command('tabnext')
if multigrid then
screen:expect({
grid = [[
## grid 1
{9: }{10:2}{9:+ No Name] }{3: [No Name] }{9: }{10:3}{9:+ No Name] }{5: }{9:X}|
[6:----------------------------------------]|*5
[3:----------------------------------------]|
## grid 2 (hidden)
olleh|
{0: ~}|*4
## grid 3
|
## grid 4 (hidden)
hello |
{0:~ }|*4
## grid 5 (hidden)
{1:hello }|
{2:~ }|*4
## grid 6
^ |
{0:~ }|*4
## grid 7 (hidden)
{1:floaty mcfloatf}|
{1:ace }|
{2:~ }|*3
]],
})
else
screen:expect([[
{9: }{10:2}{9:+ No Name] }{3: [No Name] }{9: }{10:3}{9:+ No Name] }{5: }{9:X}|
^ |
{0:~ }|*4
|
]])
end
command('tabnext')
if multigrid then
screen:expect({
grid = [[
## grid 1
{9: }{10:2}{9:+ No Name] [No Name] }{3: }{11:3}{3:+ No Name] }{5: }{9:X}|
[4:----------------------------------------]|*5
[3:----------------------------------------]|
## grid 2 (hidden)
olleh|
{0: ~}|*4
## grid 3
|
## grid 4
^hello |
{0:~ }|*4
## grid 5
{1:hello }|
{2:~ }|*4
## grid 6 (hidden)
|
{0:~ }|*4
## grid 7 (hidden)
{1:floaty mcfloatf}|
{1:ace }|
{2:~ }|*3
## grid 8
{5:┌────┐}|
{5:│}{1:lleh}{5:│}|
{5:│}{1: o}{5:│}|
{5:│}{2: ~}{5:│}|*2
{5:└────┘}|
]],
float_pos = {
[5] = { 1002, 'NW', 4, 0, 0, true, 50, 1, 1, 0 },
[8] = { 1005, 'NW', 4, 2, 7, true, 50, 2, 0, 7 },
},
})
else
screen:expect([[
{9: }{10:2}{9:+ No }{5:┌────┐}{9: [No Name] }{3: }{11:3}{3:+ No Name] }{5: }{9:X}|
{1:^hello }{5:│}{1:lleh}{5:│} |
{2:~ }{5:│}{1: o}{5:│}{0: }|
{2:~ }{5:│}{2: ~}{5:│}{0: }|*2
{2:~ }{5:└────┘}{0: }|
|
]])
end
-- Used relative=win on two floats relative to this window.
-- Split it to the right to ensure both follow.
command('topleft vsplit')
if multigrid then
screen:expect({
grid = [[
## grid 1
{9: }{10:2}{9:+ No Name] [No Name] }{3: }{11:4}{3:+ No Name] }{5: }{9:X}|
[9:--------------------]{5:│}[4:-------------------]|*4
{4:[No Name] [+] }{5:[No Name] [+] }|
[3:----------------------------------------]|
## grid 2 (hidden)
olleh|
{0: ~}|*4
## grid 3
|
## grid 4
hello |
{0:~ }|*3
## grid 5
{1:hello }|
{2:~ }|*4
## grid 6 (hidden)
|
{0:~ }|*4
## grid 7 (hidden)
{1:floaty mcfloatf}|
{1:ace }|
{2:~ }|*3
## grid 8
{5:┌────┐}|
{5:│}{1:lleh}{5:│}|
{5:│}{1: o}{5:│}|
{5:│}{2: ~}{5:│}|*2
{5:└────┘}|
## grid 9
^hello |
{0:~ }|*3
]],
float_pos = {
[5] = { 1002, 'NW', 4, 0, 0, true, 50, 1, 1, 21 },
[8] = { 1005, 'NW', 4, 2, 7, true, 50, 2, 0, 28 },
},
})
else
screen:expect([[
{9: }{10:2}{9:+ No Name] [No Name] }{3: }{11:4}{3:+ }{5:┌────┐}{3:e] }{5: }{9:X}|
^hello {5:│}{1:hello }{5:│}{1:lleh}{5:│} |
{0:~ }{5:│}{2:~ }{5:│}{1: o}{5:│}{0: }|
{0:~ }{5:│}{2:~ }{5:│}{2: ~}{5:│}{0: }|*2
{4:[No Name] [+] }{2:~ }{5:└────┘ }|
|
]])
end
end)
end
describe('with ext_multigrid and actual mouse grid', function()

View File

@@ -2346,8 +2346,7 @@ describe('ui/ext_messages', function()
screen:expect([[
|
{1:~ }{4:^ }{1: }|
{1:~ }|*21
{2:[No Name] }|
{1:~ }|*22
]])
end)

View File

@@ -1795,9 +1795,14 @@ local function fmt_ext_state(name, state)
elseif name == 'float_pos' then
local str = '{\n'
for k, v in pairs(state) do
str = str .. ' [' .. k .. '] = {' .. v[1]
for i = 2, #v do
str = str .. ', ' .. inspect(v[i])
str = str .. ' [' .. k .. '] = {'
if v.external then
str = str .. ' external = true '
else
str = str .. v[1]
for i = 2, #v do
str = str .. ', ' .. inspect(v[i])
end
end
str = str .. '};\n'
end