diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2024-05-24 19:18:11 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2024-05-24 19:18:11 +0000 |
commit | ff7ed8f586589d620a806c3758fac4a47a8e7e15 (patch) | |
tree | 729bbcb92231538fa61dab6c3d890b025484b7f5 /src/nvim/api/win_config.c | |
parent | 376914f419eb08fdf4c1a63a77e1f035898a0f10 (diff) | |
parent | 28c04948a1c887a1cc0cb64de79fa32631700466 (diff) | |
download | rneovim-ff7ed8f586589d620a806c3758fac4a47a8e7e15.tar.gz rneovim-ff7ed8f586589d620a806c3758fac4a47a8e7e15.tar.bz2 rneovim-ff7ed8f586589d620a806c3758fac4a47a8e7e15.zip |
Merge remote-tracking branch 'upstream/master' into mix_20240309
Diffstat (limited to 'src/nvim/api/win_config.c')
-rw-r--r-- | src/nvim/api/win_config.c | 363 |
1 files changed, 240 insertions, 123 deletions
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 543c7b8113..3a9986a7d1 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,8 @@ /// - 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 all autocommands are blocked for the duration of +/// the call. /// - 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. @@ -224,25 +224,33 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err } WinConfig fconfig = WIN_CONFIG_INIT; - if (!parse_float_config(config, &fconfig, false, true, err)) { + if (!parse_win_config(NULL, config, &fconfig, false, err)) { return 0; } bool is_split = HAS_KEY_X(config, split) || HAS_KEY_X(config, vertical); + Window rv = 0; + if (fconfig.noautocmd) { + block_autocmds(); + } win_T *wp = NULL; tabpage_T *tp = curtab; + win_T *parent = NULL; + if (config->win != -1) { + parent = find_window_by_handle(fconfig.window, err); + if (!parent) { + // find_window_by_handle has already set the error + goto cleanup; + } else if (is_split && parent->w_floating) { + api_set_error(err, kErrorTypeException, "Cannot split a floating window"); + goto cleanup; + } + tp = win_find_tabpage(parent); + } if (is_split) { - win_T *parent = NULL; - if (config->win != -1) { - parent = find_window_by_handle(fconfig.window, err); - if (!parent) { - // find_window_by_handle has already set the error - return 0; - } else if (parent->w_floating) { - api_set_error(err, kErrorTypeException, "Cannot split a floating window"); - return 0; - } + if (!check_split_disallowed_err(parent ? parent : curwin, err)) { + goto cleanup; // error already set } if (HAS_KEY_X(config, vertical) && !HAS_KEY_X(config, split)) { @@ -254,18 +262,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, { + int size = (flags & WSP_VERT) ? fconfig.width : fconfig.height; + if (parent == NULL || parent == curwin) { + wp = win_split_ins(size, flags, NULL, 0, NULL); + } else { + 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(size, flags, NULL, 0, NULL); + restore_win(&switchwin, true); + } + }); if (wp) { wp->w_config = fconfig; } @@ -273,30 +283,64 @@ 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"); - return 0; + if (!ERROR_SET(err)) { + api_set_error(err, kErrorTypeException, "Failed to create window"); + } + goto cleanup; } - switchwin_T switchwin; - if (switch_win_noblock(&switchwin, wp, tp, true) == OK) { - apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf); + + // 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); + if (!fconfig.noautocmd) { + switchwin_T switchwin; + 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, 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; + goto cleanup; } if (fconfig.style == kWinStyleMinimal) { win_set_minimal_style(wp); didset_window_options(wp, true); } - return wp->handle; + rv = wp->handle; + +cleanup: + if (fconfig.noautocmd) { + unblock_autocmds(); + } + return rv; #undef HAS_KEY_X } @@ -330,11 +374,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()| /// @@ -350,6 +394,7 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) if (!win) { return; } + tabpage_T *win_tp = win_find_tabpage(win); bool was_split = !win->w_floating; bool has_split = HAS_KEY_X(config, split); @@ -361,26 +406,31 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) && !(HAS_KEY_X(config, external) ? config->external : fconfig.external) && (has_split || has_vertical || was_split); - if (!parse_float_config(config, &fconfig, !was_split || to_split, false, err)) { + if (!parse_win_config(win, config, &fconfig, !was_split || to_split, err)) { return; } + win_T *parent = NULL; + if (config->win != -1) { + parent = find_window_by_handle(fconfig.window, err); + if (!parent) { + return; + } else if (to_split && parent->w_floating) { + api_set_error(err, kErrorTypeException, "Cannot split a floating window"); + return; + } + + // Prevent autocmd window from being moved into another tabpage + if (is_aucmd_win(win) && win_find_tabpage(win) != win_find_tabpage(parent)) { + api_set_error(err, kErrorTypeException, "Cannot move autocmd win to another tabpage"); + 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) { - win_T *parent = NULL; - if (config->win != -1) { - parent = find_window_by_handle(fconfig.window, err); - if (!parent) { - return; - } else if (parent->w_floating) { - api_set_error(err, kErrorTypeException, "Cannot split a floating window"); - return; - } - } - WinSplit old_split = win_split_dir(win); if (has_vertical && !has_split) { if (config->vertical) { @@ -413,17 +463,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 +551,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); - } - // 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); + altwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr); } - 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; @@ -947,8 +1038,19 @@ static void parse_border_style(Object style, WinConfig *fconfig, Error *err) } } -static bool parse_float_config(Dict(win_config) *config, WinConfig *fconfig, bool reconf, - bool new_win, Error *err) +static void generate_api_error(win_T *wp, const char *attribute, Error *err) +{ + if (wp->w_floating) { + api_set_error(err, kErrorTypeValidation, + "Missing 'relative' field when reconfiguring floating window %d", + wp->handle); + } else { + api_set_error(err, kErrorTypeValidation, "non-float cannot have '%s'", attribute); + } +} + +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; @@ -973,7 +1075,7 @@ static bool parse_float_config(Dict(win_config) *config, WinConfig *fconfig, boo } else if (!config->external) { if (HAS_KEY_X(config, vertical) || HAS_KEY_X(config, split)) { is_split = true; - } else if (new_win) { + } else if (wp == NULL) { // new win api_set_error(err, kErrorTypeValidation, "Must specify 'relative' or 'external' when creating a float"); return false; @@ -1007,7 +1109,7 @@ static bool parse_float_config(Dict(win_config) *config, WinConfig *fconfig, boo if (HAS_KEY_X(config, row)) { if (!has_relative || is_split) { - api_set_error(err, kErrorTypeValidation, "non-float cannot have 'row'"); + generate_api_error(wp, "row", err); return false; } fconfig->row = config->row; @@ -1015,7 +1117,7 @@ static bool parse_float_config(Dict(win_config) *config, WinConfig *fconfig, boo if (HAS_KEY_X(config, col)) { if (!has_relative || is_split) { - api_set_error(err, kErrorTypeValidation, "non-float cannot have 'col'"); + generate_api_error(wp, "col", err); return false; } fconfig->col = config->col; @@ -1023,7 +1125,7 @@ static bool parse_float_config(Dict(win_config) *config, WinConfig *fconfig, boo if (HAS_KEY_X(config, bufpos)) { if (!has_relative || is_split) { - api_set_error(err, kErrorTypeValidation, "non-float cannot have 'bufpos'"); + generate_api_error(wp, "bufpos", err); return false; } else { if (!parse_float_bufpos(config->bufpos, &fconfig->bufpos)) { @@ -1065,17 +1167,32 @@ static bool parse_float_config(Dict(win_config) *config, WinConfig *fconfig, boo } 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) { + return false; + } + + if (target_win == wp) { + api_set_error(err, kErrorTypeException, "floating window cannot be relative to itself"); + return false; + } + } fconfig->window = curwin->handle; if (HAS_KEY_X(config, win)) { if (config->win > 0) { 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; } } @@ -1186,8 +1303,8 @@ static bool parse_float_config(Dict(win_config) *config, WinConfig *fconfig, boo } if (HAS_KEY_X(config, noautocmd)) { - if (!new_win) { - api_set_error(err, kErrorTypeValidation, "Invalid key: 'noautocmd'"); + if (wp) { + api_set_error(err, kErrorTypeValidation, "'noautocmd' cannot be used with existing windows"); return false; } fconfig->noautocmd = config->noautocmd; |