refactor(api): cleanup, more comments, more tests, news

- Factor out logic to keep nvim_win_set_config clean.
- Clean up a few things, remove redundant logic, reflow some lines.
- Add some more comments where appropriate.
- Don't consider negative "win", as that's only relevant for splits.
- Add more test coverage.
- Add news.txt entry.
This commit is contained in:
Sean Dewar
2026-03-10 09:05:27 +00:00
parent e2a0748cb2
commit e5240b35c3
3 changed files with 174 additions and 113 deletions

View File

@@ -40,6 +40,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,
@@ -198,7 +200,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;
@@ -349,7 +350,6 @@ cleanup:
unblock_autocmds();
}
return rv;
#undef HAS_KEY_X
}
static WinSplit win_split_dir(win_T *win)
@@ -382,28 +382,31 @@ static int win_split_flags(WinSplit split, bool toplevel)
return flags;
}
static bool can_move(win_T *wp, bool switch_tab, Error *err)
/// Checks if window `wp` can be moved to tabpage `tp`.
static bool win_can_move_tp(win_T *wp, Error *err)
FUNC_ATTR_NONNULL_ALL
{
if (is_aucmd_win(wp) && switch_tab) {
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) && switch_tab) {
if (wp == cmdwin_win || wp == cmdwin_old_curwin) {
api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
return false;
}
if (wp->w_floating && wp->w_config.external) {
api_set_error(err, kErrorTypeException, "%s", "Cannot move external floating window");
if (wp->w_config.external) {
api_set_error(err, kErrorTypeException, "Cannot move external window to another tabpage");
return false;
}
return true;
}
static bool win_config_split(win_T *win, Dict(win_config) *config, WinConfig *fconfig, Error *err)
/// 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);
@@ -442,8 +445,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 (!can_move(win, win_tp != parent_tp, err)) {
return false;
if (win_tp != parent_tp && !win_can_move_tp(win, err)) {
return false; // error already set
}
}
@@ -472,8 +475,7 @@ static bool win_config_split(win_T *win, Dict(win_config) *config, WinConfig *fc
// 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);
@@ -621,7 +623,92 @@ 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);
}
if (win_tp != parent_tp) {
if (!win_can_move_tp(win, err)) {
return false; // error already set
}
if (curwin == win) {
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 false;
}
win_tp = win_find_tabpage(win);
parent_tp = win_find_tabpage(parent);
bool restore_curwin = false;
if (!win_tp || !parent_tp) {
api_set_error(err, kErrorTypeException, "Target windows were closed");
restore_curwin = true;
} else if (!win->w_floating) {
api_set_error(err, kErrorTypeException, "Window %d was made non-floating", win->handle);
restore_curwin = true;
} else if (win_tp != parent_tp && !win_can_move_tp(win, err)) {
restore_curwin = true; // error already set
}
if (restore_curwin) {
// As `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 (win_valid(win)) {
win_goto(win);
}
return false;
}
}
// Check again, in case autocommands above moved windows to the same tabpage.
if (win_tp != parent_tp) {
win_remove(win, win_tp == curtab ? NULL : win_tp);
win_T *after;
if (parent_tp == curtab) {
after = lastwin_nofloating();
} else {
after = parent_tp->tp_lastwin;
while (after->w_floating) {
after = after->w_prev;
}
}
win_append(after, win, parent_tp == curtab ? NULL : parent_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 = win_float_find_altwin(win, win_tp);
}
if (parent_tp != curtab) {
if (ui_has(kUIMultigrid)) {
ui_call_win_hide(win->w_grid_alloc.handle);
}
ui_comp_remove_grid(&win->w_grid_alloc);
} else {
win->w_hl_needs_update = true;
}
}
}
win_config_float(win, *fconfig);
return true;
}
/// Reconfigures the layout and properties of a window.
@@ -644,7 +731,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;
@@ -665,24 +751,6 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err)
return;
}
win_T *parent = NULL;
tabpage_T *parent_tp = NULL;
tabpage_T *win_tp = win_find_tabpage(win);
bool curwin_moving_tp = false;
if (config->win == 0) {
parent = curwin;
parent_tp = curtab;
} else if (config->win > 0) {
parent = find_window_by_handle(fconfig.window, err);
if (!parent) {
return;
}
parent_tp = win_find_tabpage(parent);
if (!parent_tp) {
return;
}
}
if (was_split && !to_split) {
if (!win_new_float(win, false, fconfig, err)) {
return;
@@ -693,65 +761,10 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err)
return;
}
} else {
if (win->w_floating && HAS_KEY_X(config, win) && parent && parent_tp != win_tp) {
if (!can_move(win, win_tp != parent_tp, err)) {
return;
}
if (win == curwin) {
curwin_moving_tp = true;
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);
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->w_floating) {
api_set_error(err, kErrorTypeException, "Window %d was made non-floating",
win->handle);
goto restore_curwin;
}
if (!can_move(win, win_tp != parent_tp, err)) {
goto restore_curwin;
}
}
if (win_tp != parent_tp) {
win_remove(win, win_tp == curtab ? NULL : win_tp);
win_T *target_after;
if (parent_tp == curtab) {
target_after = lastwin_nofloating();
} else {
target_after = parent_tp->tp_lastwin;
while (target_after->w_floating) {
target_after = target_after->w_prev;
}
}
win_append(target_after, win, parent_tp == curtab ? NULL : parent_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 = win_float_find_altwin(win, win_tp);
}
if (parent_tp != curtab) {
if (ui_has(kUIMultigrid)) {
ui_call_win_hide(win->w_grid_alloc.handle);
}
ui_comp_remove_grid(&win->w_grid_alloc);
} else {
win->w_hl_needs_update = true;
}
}
assert(!was_split);
if (!win_config_float_tp(win, config, &fconfig, err)) {
return;
}
win_config_float(win, fconfig);
}
if (fconfig.style == kWinStyleMinimal && old_style != fconfig.style) {
@@ -764,15 +777,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;
}
return;
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);
}
#undef HAS_KEY_X
}
#define PUT_KEY_X(d, key, value) PUT_KEY(d, win_config, key, value)
@@ -1241,7 +1245,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)) {
@@ -1519,5 +1522,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
}