fix(api): improve external window validation

Problem: "win" is allowed in external window configs in some cases. External
window converted to normal float can't move tabpages in one nvim_win_set_config
call. External window can't be turned into a normal split.

Solution: disallow setting "win" for external windows. Allow external window to
move tabpages, which turns it non-external. Allow external window to be turned
into a (non-external) split.

parse_win_config has more validation issues from not considering the window's
existing config enough (not from this PR). For example, zindex can be set for an
existing split if "split"/"vertical" isn't given, despite intending for that to
be an error. Plus the logic is confusing.

It could do with a refactor at some point...
This commit is contained in:
Sean Dewar
2026-03-13 11:52:01 +00:00
parent 853eea859f
commit 3115e3d0d1
3 changed files with 49 additions and 47 deletions

View File

@@ -416,10 +416,6 @@ static bool win_can_move_tp(win_T *wp, tabpage_T *tp, Error *err)
api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
return false;
}
if (wp->w_config.external) {
api_set_error(err, kErrorTypeException, "Cannot move external window to another tabpage");
return false;
}
return true;
}
@@ -772,7 +768,7 @@ 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)) {
@@ -1289,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");
@@ -1380,6 +1377,23 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
goto fail;
}
if (HAS_KEY_X(config, external)) {
fconfig->external = config->external;
if (has_relative && fconfig->external) {
api_set_error(err, kErrorTypeValidation,
"Only one of 'relative' and 'external' must be used");
goto fail;
}
if (fconfig->external && !ui_has(kUIMultigrid)) {
api_set_error(err, kErrorTypeValidation, "UI doesn't support external windows");
goto fail;
}
}
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.
@@ -1408,19 +1422,6 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
}
}
if (HAS_KEY_X(config, external)) {
fconfig->external = config->external;
if (has_relative && fconfig->external) {
api_set_error(err, kErrorTypeValidation,
"Only one of 'relative' and 'external' must be used");
goto fail;
}
if (fconfig->external && !ui_has(kUIMultigrid)) {
api_set_error(err, kErrorTypeValidation, "UI doesn't support external windows");
goto fail;
}
}
if (HAS_KEY_X(config, focusable)) {
fconfig->focusable = config->focusable;
fconfig->mouse = config->focusable;