diff options
Diffstat (limited to 'src/nvim/api/win_config.c')
-rw-r--r-- | src/nvim/api/win_config.c | 630 |
1 files changed, 489 insertions, 141 deletions
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 4e23717dc6..543c7b8113 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -7,24 +7,33 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/tabpage.h" #include "nvim/api/win_config.h" #include "nvim/ascii_defs.h" #include "nvim/autocmd.h" +#include "nvim/autocmd_defs.h" #include "nvim/buffer_defs.h" #include "nvim/decoration.h" +#include "nvim/decoration_defs.h" #include "nvim/drawscreen.h" -#include "nvim/func_attr.h" +#include "nvim/eval/window.h" +#include "nvim/extmark_defs.h" #include "nvim/globals.h" -#include "nvim/grid.h" +#include "nvim/grid_defs.h" #include "nvim/highlight_group.h" #include "nvim/macros_defs.h" #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/option.h" +#include "nvim/option_vars.h" #include "nvim/pos_defs.h" #include "nvim/strings.h" #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" #include "nvim/winfloat.h" @@ -32,9 +41,9 @@ # include "api/win_config.c.generated.h" #endif -/// Open a new window. +/// Opens a new split window, or a floating window if `relative` is specified, +/// or an external window (managed by the UI) if `external` is specified. /// -/// Currently this is used to open floating and external windows. /// Floats are windows that are drawn above the split layout, at some anchor /// position in some other window. Floats can be drawn internally or by external /// GUI with the |ui-multigrid| extension. External windows are only supported @@ -42,8 +51,17 @@ /// /// For a general overview of floats, see |api-floatwin|. /// -/// Exactly one of `external` and `relative` must be specified. The `width` and -/// `height` of the new window must be specified. +/// The `width` and `height` of the new window must be specified when opening +/// a floating window, but are optional for normal windows. +/// +/// If `relative` and `external` are omitted, a normal "split" window is created. +/// The `win` property determines which window will be split. If no `win` is +/// provided or `win == 0`, a window will be created adjacent to the current window. +/// If -1 is provided, a top-level split will be created. `vertical` and `split` are +/// only valid for normal windows, and are used to control split direction. For `vertical`, +/// the exact direction is determined by |'splitright'| and |'splitbelow'|. +/// Split windows cannot have `bufpos`/`row`/`col`/`border`/`title`/`footer` +/// properties. /// /// With relative=editor (row=0,col=0) refers to the top-left corner of the /// screen-grid and (row=Lines-1,col=Columns-1) refers to the bottom-right @@ -68,6 +86,14 @@ /// ```lua /// vim.api.nvim_open_win(0, false, /// {relative='win', width=12, height=3, bufpos={100,10}}) +/// ``` +/// +/// Example (Lua): vertical split left of the current window +/// +/// ```lua +/// vim.api.nvim_open_win(0, false, { +/// split = 'left', +/// win = 0 /// }) /// ``` /// @@ -80,7 +106,8 @@ /// - "win" Window given by the `win` field, or current window. /// - "cursor" Cursor position in current window. /// - "mouse" Mouse position -/// - win: |window-ID| for relative="win". +/// - win: |window-ID| window to split, or relative window when creating a +/// float (relative="win"). /// - anchor: Decides which corner of the float to place at (row,col): /// - "NW" northwest (default) /// - "NE" northeast @@ -89,12 +116,12 @@ /// - width: Window width (in character cells). Minimum of 1. /// - height: Window height (in character cells). Minimum of 1. /// - bufpos: Places float relative to buffer text (only when -/// relative="win"). Takes a tuple of zero-indexed [line, column]. -/// `row` and `col` if given are applied relative to this -/// position, else they default to: -/// - `row=1` and `col=0` if `anchor` is "NW" or "NE" -/// - `row=0` and `col=0` if `anchor` is "SW" or "SE" -/// (thus like a tooltip near the buffer text). +/// relative="win"). Takes a tuple of zero-indexed `[line, column]`. +/// `row` and `col` if given are applied relative to this +/// position, else they default to: +/// - `row=1` and `col=0` if `anchor` is "NW" or "NE" +/// - `row=0` and `col=0` if `anchor` is "SW" or "SE" +/// (thus like a tooltip near the buffer text). /// - row: Row position in units of "screen cell height", may be fractional. /// - col: Column position in units of "screen cell width", may be /// fractional. @@ -126,7 +153,7 @@ /// 'fillchars' to a space char, and clearing the /// |hl-EndOfBuffer| region in 'winhighlight'. /// - border: Style of (optional) window border. This can either be a string -/// or an array. The string values are +/// or an array. The string values are /// - "none": No border (default). /// - "single": A single line box. /// - "double": A double line box. @@ -134,21 +161,31 @@ /// - "solid": Adds padding by a single whitespace cell. /// - "shadow": A drop shadow effect by blending with the background. /// - If it is an array, it should have a length of eight or any divisor of -/// eight. The array will specify the eight chars building up the border -/// in a clockwise fashion starting with the top-left corner. As an -/// example, the double box style could be specified as +/// eight. The array will specify the eight chars building up the border +/// in a clockwise fashion starting with the top-left corner. As an +/// example, the double box style could be specified as: +/// ``` /// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ]. -/// If the number of chars are less than eight, they will be repeated. Thus -/// an ASCII border could be specified as +/// ``` +/// If the number of chars are less than eight, they will be repeated. Thus +/// an ASCII border could be specified as +/// ``` /// [ "/", "-", \"\\\\\", "|" ], -/// or all chars the same as +/// ``` +/// or all chars the same as +/// ``` /// [ "x" ]. +/// ``` /// An empty string can be used to turn off a specific border, for instance, +/// ``` /// [ "", "", "", ">", "", "", "", "<" ] +/// ``` /// will only make vertical borders but not horizontal ones. /// By default, `FloatBorder` highlight is used, which links to `WinSeparator` /// when not defined. It could also be specified by character: +/// ``` /// [ ["+", "MyCorner"], ["x", "MyBorder"] ]. +/// ``` /// - title: Title (optional) in window border, string or list. /// List should consist of `[text, highlight]` tuples. /// If string, the default highlight group is `FloatTitle`. @@ -167,43 +204,90 @@ /// - 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. +/// - vertical: Split vertically |:vertical|. +/// - split: Split direction: "left", "right", "above", "below". /// /// @param[out] err Error details, if any /// /// @return Window handle, or 0 on error -Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, Error *err) - FUNC_API_SINCE(6) - FUNC_API_TEXTLOCK_ALLOW_CMDWIN +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; } - if (cmdwin_type != 0 && (enter || buf == curbuf)) { + if ((cmdwin_type != 0 && enter) || buf == cmdwin_buf) { api_set_error(err, kErrorTypeException, "%s", e_cmdwin); return 0; } - FloatConfig fconfig = FLOAT_CONFIG_INIT; + WinConfig fconfig = WIN_CONFIG_INIT; if (!parse_float_config(config, &fconfig, false, true, err)) { return 0; } - win_T *wp = win_new_float(NULL, false, fconfig, err); + + bool is_split = HAS_KEY_X(config, split) || HAS_KEY_X(config, vertical); + + win_T *wp = NULL; + tabpage_T *tp = curtab; + 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 (HAS_KEY_X(config, vertical) && !HAS_KEY_X(config, split)) { + if (config->vertical) { + fconfig.split = p_spr ? kWinSplitRight : kWinSplitLeft; + } else { + fconfig.split = p_sb ? kWinSplitBelow : kWinSplitAbove; + } + } + 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); + } + if (wp) { + wp->w_config = fconfig; + } + } else { + wp = win_new_float(NULL, false, fconfig, err); + } if (!wp) { + api_set_error(err, kErrorTypeException, "Failed to create window"); return 0; } + switchwin_T switchwin; + if (switch_win_noblock(&switchwin, wp, tp, true) == OK) { + apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf); + } + restore_win_noblock(&switchwin, true); if (enter) { - win_enter(wp, false); + goto_tabpage_win(tp, wp); } - // autocmds in win_enter or win_set_buf below may close the window - if (win_valid(wp) && buffer > 0) { - Boolean noautocmd = !enter || fconfig.noautocmd; - win_set_buf(wp, buf, noautocmd, err); - if (!fconfig.noautocmd) { - apply_autocmds(EVENT_WINNEW, NULL, NULL, false, buf); - } + if (win_valid_any_tab(wp) && buf != wp->w_buffer) { + win_set_buf(wp, buf, !enter || fconfig.noautocmd, err); } - if (!win_valid(wp)) { + if (!win_valid_any_tab(wp)) { api_set_error(err, kErrorTypeException, "Window was closed immediately"); return 0; } @@ -213,6 +297,37 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, E didset_window_options(wp, true); } return wp->handle; +#undef HAS_KEY_X +} + +static WinSplit win_split_dir(win_T *win) +{ + if (win->w_frame == NULL || win->w_frame->fr_parent == NULL) { + return kWinSplitLeft; + } + + char layout = win->w_frame->fr_parent->fr_layout; + if (layout == FR_COL) { + return win->w_frame->fr_next ? kWinSplitAbove : kWinSplitBelow; + } else { + return win->w_frame->fr_next ? kWinSplitLeft : kWinSplitRight; + } +} + +static int win_split_flags(WinSplit split, bool toplevel) +{ + int flags = 0; + if (split == kWinSplitAbove || split == kWinSplitBelow) { + flags |= WSP_HOR; + } else { + flags |= WSP_VERT; + } + if (split == kWinSplitAbove || split == kWinSplitLeft) { + flags |= toplevel ? WSP_TOP : WSP_ABOVE; + } else { + flags |= toplevel ? WSP_BOT : WSP_BELOW; + } + return flags; } /// Configures window layout. Currently only for floating and external windows @@ -227,61 +342,231 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, E /// @param config Map defining the window configuration, /// see |nvim_open_win()| /// @param[out] err Error details, if any -void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err) +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; } - bool new_float = !win->w_floating; + tabpage_T *win_tp = win_find_tabpage(win); + bool was_split = !win->w_floating; + bool has_split = HAS_KEY_X(config, split); + bool has_vertical = HAS_KEY_X(config, vertical); // reuse old values, if not overridden - FloatConfig fconfig = new_float ? FLOAT_CONFIG_INIT : win->w_float_config; + WinConfig fconfig = win->w_config; - if (!parse_float_config(config, &fconfig, !new_float, false, err)) { + bool to_split = config->relative.size == 0 + && !(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)) { return; } - if (new_float) { + 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) { + if (old_split == kWinSplitRight || p_spr) { + fconfig.split = kWinSplitRight; + } else { + fconfig.split = kWinSplitLeft; + } + } else { + if (old_split == kWinSplitBelow || p_sb) { + fconfig.split = kWinSplitBelow; + } else { + fconfig.split = kWinSplitAbove; + } + } + } + win->w_config = fconfig; + + // If there's no "vertical" or "split" set, or if "split" is unchanged, + // then we can just change the size of the window. + if ((!has_vertical && !has_split) + || (was_split && !HAS_KEY_X(config, win) && old_split == fconfig.split)) { + 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; + } + + if (was_split) { + win_T *new_curwin = NULL; + + // 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; + } 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) { + n_frames++; + } + + win_T *neighbor = NULL; + + if (n_frames > 2) { + // There are three or more windows in the frame, we need to split a neighboring window. + frame_T *frame = win->w_frame->fr_parent; + + if (frame->fr_parent) { + // ┌──────────────┐ + // │ A │ + // ├────┬────┬────┤ + // │ B │ C │ D │ + // └────┴────┴────┘ + // || + // \/ + // ┌───────────────────┐ + // │ A │ + // ├─────────┬─────────┤ + // │ │ C │ + // │ B ├─────────┤ + // │ │ D │ + // └─────────┴─────────┘ + if (fconfig.split == kWinSplitAbove || fconfig.split == kWinSplitLeft) { + neighbor = win->w_next; + } else { + neighbor = win->w_prev; + } + } + // 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); + } 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; + } 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; + } + // 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); + } + 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; + } + + int flags = win_split_flags(fconfig.split, parent == NULL); + + 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; + + 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; + } + // 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; + } + } + 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; } - if (HAS_KEY(config, float_config, style)) { + if (HAS_KEY_X(config, style)) { if (fconfig.style == kWinStyleMinimal) { win_set_minimal_style(win); didset_window_options(win, true); } } +#undef HAS_KEY_X } -static Dictionary config_put_bordertext(Dictionary config, FloatConfig *fconfig, - BorderTextType bordertext_type) +#define PUT_KEY_X(d, key, value) PUT_KEY(d, win_config, key, value) +static void config_put_bordertext(Dict(win_config) *config, WinConfig *fconfig, + BorderTextType bordertext_type, Arena *arena) { VirtText vt; AlignTextPos align; - char *field_name; - char *field_pos_name; switch (bordertext_type) { case kBorderTextTitle: vt = fconfig->title_chunks; align = fconfig->title_pos; - field_name = "title"; - field_pos_name = "title_pos"; break; case kBorderTextFooter: vt = fconfig->footer_chunks; align = fconfig->footer_pos; - field_name = "footer"; - field_pos_name = "footer_pos"; break; } - Array bordertext = virt_text_to_array(vt, true); - PUT(config, field_name, ARRAY_OBJ(bordertext)); + Array bordertext = virt_text_to_array(vt, true, arena); char *pos; switch (align) { @@ -295,9 +580,16 @@ static Dictionary config_put_bordertext(Dictionary config, FloatConfig *fconfig, pos = "right"; break; } - PUT(config, field_pos_name, CSTR_TO_OBJ(pos)); - return config; + switch (bordertext_type) { + case kBorderTextTitle: + PUT_KEY_X(*config, title, ARRAY_OBJ(bordertext)); + PUT_KEY_X(*config, title_pos, cstr_as_string(pos)); + break; + case kBorderTextFooter: + PUT_KEY_X(*config, footer, ARRAY_OBJ(bordertext)); + PUT_KEY_X(*config, footer_pos, cstr_as_string(pos)); + } } /// Gets window configuration. @@ -309,70 +601,80 @@ static Dictionary config_put_bordertext(Dictionary config, FloatConfig *fconfig, /// @param window Window handle, or 0 for current window /// @param[out] err Error details, if any /// @return Map defining the window configuration, see |nvim_open_win()| -Dictionary nvim_win_get_config(Window window, Error *err) +Dict(win_config) nvim_win_get_config(Window window, Arena *arena, Error *err) FUNC_API_SINCE(6) { - Dictionary rv = ARRAY_DICT_INIT; + /// Keep in sync with FloatRelative in buffer_defs.h + static const char *const float_relative_str[] = { "editor", "win", "cursor", "mouse" }; + + /// Keep in sync with WinSplit in buffer_defs.h + static const char *const win_split_str[] = { "left", "right", "above", "below" }; + + Dict(win_config) rv = KEYDICT_INIT; win_T *wp = find_window_by_handle(window, err); if (!wp) { return rv; } - FloatConfig *config = &wp->w_float_config; + WinConfig *config = &wp->w_config; - PUT(rv, "focusable", BOOLEAN_OBJ(config->focusable)); - PUT(rv, "external", BOOLEAN_OBJ(config->external)); - PUT(rv, "hide", BOOLEAN_OBJ(config->hide)); + PUT_KEY_X(rv, focusable, config->focusable); + PUT_KEY_X(rv, external, config->external); + PUT_KEY_X(rv, hide, config->hide); if (wp->w_floating) { - PUT(rv, "width", INTEGER_OBJ(config->width)); - PUT(rv, "height", INTEGER_OBJ(config->height)); + PUT_KEY_X(rv, width, config->width); + PUT_KEY_X(rv, height, config->height); if (!config->external) { if (config->relative == kFloatRelativeWindow) { - PUT(rv, "win", INTEGER_OBJ(config->window)); + PUT_KEY_X(rv, win, config->window); if (config->bufpos.lnum >= 0) { - Array pos = ARRAY_DICT_INIT; - ADD(pos, INTEGER_OBJ(config->bufpos.lnum)); - ADD(pos, INTEGER_OBJ(config->bufpos.col)); - PUT(rv, "bufpos", ARRAY_OBJ(pos)); + Array pos = arena_array(arena, 2); + ADD_C(pos, INTEGER_OBJ(config->bufpos.lnum)); + ADD_C(pos, INTEGER_OBJ(config->bufpos.col)); + PUT_KEY_X(rv, bufpos, pos); } } - PUT(rv, "anchor", CSTR_TO_OBJ(float_anchor_str[config->anchor])); - PUT(rv, "row", FLOAT_OBJ(config->row)); - PUT(rv, "col", FLOAT_OBJ(config->col)); - PUT(rv, "zindex", INTEGER_OBJ(config->zindex)); + PUT_KEY_X(rv, anchor, cstr_as_string(float_anchor_str[config->anchor])); + PUT_KEY_X(rv, row, config->row); + PUT_KEY_X(rv, col, config->col); + PUT_KEY_X(rv, zindex, config->zindex); } if (config->border) { - Array border = ARRAY_DICT_INIT; + Array border = arena_array(arena, 8); for (size_t i = 0; i < 8; i++) { - Array tuple = ARRAY_DICT_INIT; - - String s = cstrn_to_string(config->border_chars[i], MAX_SCHAR_SIZE); + String s = cstrn_as_string(config->border_chars[i], MAX_SCHAR_SIZE); int hi_id = config->border_hl_ids[i]; char *hi_name = syn_id2name(hi_id); if (hi_name[0]) { - ADD(tuple, STRING_OBJ(s)); - ADD(tuple, CSTR_TO_OBJ(hi_name)); - ADD(border, ARRAY_OBJ(tuple)); + Array tuple = arena_array(arena, 2); + ADD_C(tuple, STRING_OBJ(s)); + ADD_C(tuple, CSTR_AS_OBJ(hi_name)); + ADD_C(border, ARRAY_OBJ(tuple)); } else { - ADD(border, STRING_OBJ(s)); + ADD_C(border, STRING_OBJ(s)); } } - PUT(rv, "border", ARRAY_OBJ(border)); + PUT_KEY_X(rv, border, ARRAY_OBJ(border)); if (config->title) { - rv = config_put_bordertext(rv, config, kBorderTextTitle); + config_put_bordertext(&rv, config, kBorderTextTitle, arena); } if (config->footer) { - rv = config_put_bordertext(rv, config, kBorderTextFooter); + config_put_bordertext(&rv, config, kBorderTextFooter, arena); } } + } else if (!config->external) { + PUT_KEY_X(rv, width, wp->w_width); + PUT_KEY_X(rv, height, wp->w_height); + WinSplit split = win_split_dir(wp); + PUT_KEY_X(rv, split, cstr_as_string(win_split_str[split])); } const char *rel = (wp->w_floating && !config->external ? float_relative_str[config->relative] : ""); - PUT(rv, "relative", CSTR_TO_OBJ(rel)); + PUT_KEY_X(rv, relative, cstr_as_string(rel)); return rv; } @@ -414,10 +716,26 @@ static bool parse_float_relative(String relative, FloatRelative *out) return true; } +static bool parse_config_split(String split, WinSplit *out) +{ + char *str = split.data; + if (striequal(str, "left")) { + *out = kWinSplitLeft; + } else if (striequal(str, "right")) { + *out = kWinSplitRight; + } else if (striequal(str, "above")) { + *out = kWinSplitAbove; + } else if (striequal(str, "below")) { + *out = kWinSplitBelow; + } else { + return false; + } + return true; +} + static bool parse_float_bufpos(Array bufpos, lpos_T *out) { - if (bufpos.size != 2 - || bufpos.items[0].type != kObjectTypeInteger + if (bufpos.size != 2 || bufpos.items[0].type != kObjectTypeInteger || bufpos.items[1].type != kObjectTypeInteger) { return false; } @@ -426,21 +744,39 @@ static bool parse_float_bufpos(Array bufpos, lpos_T *out) return true; } -static void parse_bordertext(Object bordertext, BorderTextType bordertext_type, - FloatConfig *fconfig, Error *err) +static void parse_bordertext(Object bordertext, BorderTextType bordertext_type, WinConfig *fconfig, + Error *err) { + if (bordertext.type != kObjectTypeString && bordertext.type != kObjectTypeArray) { + api_set_error(err, kErrorTypeValidation, "title/footer must be string or array"); + return; + } + + if (bordertext.type == kObjectTypeArray && bordertext.data.array.size == 0) { + api_set_error(err, kErrorTypeValidation, "title/footer cannot be an empty array"); + return; + } + bool *is_present; VirtText *chunks; int *width; int default_hl_id; switch (bordertext_type) { case kBorderTextTitle: + if (fconfig->title) { + clear_virttext(&fconfig->title_chunks); + } + is_present = &fconfig->title; chunks = &fconfig->title_chunks; width = &fconfig->title_width; default_hl_id = syn_check_group(S_LEN("FloatTitle")); break; case kBorderTextFooter: + if (fconfig->footer) { + clear_virttext(&fconfig->footer_chunks); + } + is_present = &fconfig->footer; chunks = &fconfig->footer_chunks; width = &fconfig->footer_width; @@ -460,16 +796,6 @@ static void parse_bordertext(Object bordertext, BorderTextType bordertext_type, return; } - if (bordertext.type != kObjectTypeArray) { - api_set_error(err, kErrorTypeValidation, "title must be string or array"); - return; - } - - if (bordertext.data.array.size == 0) { - api_set_error(err, kErrorTypeValidation, "title cannot be an empty array"); - return; - } - *width = 0; *chunks = parse_virt_text(bordertext.data.array, err, width); @@ -477,7 +803,7 @@ static void parse_bordertext(Object bordertext, BorderTextType bordertext_type, } static bool parse_bordertext_pos(String bordertext_pos, BorderTextType bordertext_type, - FloatConfig *fconfig, Error *err) + WinConfig *fconfig, Error *err) { AlignTextPos *align; switch (bordertext_type) { @@ -516,7 +842,7 @@ static bool parse_bordertext_pos(String bordertext_pos, BorderTextType bordertex return true; } -static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) +static void parse_border_style(Object style, WinConfig *fconfig, Error *err) { struct { const char *name; @@ -531,7 +857,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) { NULL, { { NUL } }, false }, }; - char (*chars)[MAX_SCHAR_SIZE] = fconfig->border_chars; + char(*chars)[MAX_SCHAR_SIZE] = fconfig->border_chars; int *hl_ids = fconfig->border_hl_ids; fconfig->border = true; @@ -540,8 +866,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) Array arr = style.data.array; size_t size = arr.size; if (!size || size > 8 || (size & (size - 1))) { - api_set_error(err, kErrorTypeValidation, - "invalid number of border chars"); + api_set_error(err, kErrorTypeValidation, "invalid number of border chars"); return; } for (size_t i = 0; i < size; i++) { @@ -571,10 +896,8 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) api_set_error(err, kErrorTypeValidation, "invalid border char"); return; } - if (string.size - && mb_string2cells_len(string.data, string.size) > 1) { - api_set_error(err, kErrorTypeValidation, - "border chars must be one cell"); + if (string.size && mb_string2cells_len(string.data, string.size) > 1) { + api_set_error(err, kErrorTypeValidation, "border chars must be one cell"); return; } size_t len = MIN(string.size, sizeof(*chars) - 1); @@ -593,8 +916,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) || (chars[1][0] && chars[3][0] && !chars[2][0]) || (chars[3][0] && chars[5][0] && !chars[4][0]) || (chars[5][0] && chars[7][0] && !chars[6][0])) { - api_set_error(err, kErrorTypeValidation, - "corner between used edges must be specified"); + api_set_error(err, kErrorTypeValidation, "corner between used edges must be specified"); } } else if (style.type == kObjectTypeString) { String str = style.data.string; @@ -621,26 +943,24 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) return; } } - api_set_error(err, kErrorTypeValidation, - "invalid border style \"%s\"", str.data); + api_set_error(err, kErrorTypeValidation, "invalid border style \"%s\"", str.data); } } -static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, bool reconf, +static bool parse_float_config(Dict(win_config) *config, WinConfig *fconfig, bool reconf, bool new_win, Error *err) { -#define HAS_KEY_X(d, key) HAS_KEY(d, float_config, key) - bool has_relative = false, relative_is_win = false; - // ignore empty string, to match nvim_win_get_config - if (HAS_KEY_X(config, relative) && config->relative.size > 0) { +#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)) { api_set_error(err, kErrorTypeValidation, "Invalid value of 'relative' key"); return false; } - if (!(HAS_KEY_X(config, row) && HAS_KEY_X(config, col)) && !HAS_KEY_X(config, bufpos)) { - api_set_error(err, kErrorTypeValidation, - "'relative' requires 'row'/'col' or 'bufpos'"); + if (config->relative.size > 0 && !(HAS_KEY_X(config, row) && HAS_KEY_X(config, col)) + && !HAS_KEY_X(config, bufpos)) { + api_set_error(err, kErrorTypeValidation, "'relative' requires 'row'/'col' or 'bufpos'"); return false; } @@ -650,6 +970,32 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, relative_is_win = true; fconfig->bufpos.lnum = -1; } + } else if (!config->external) { + if (HAS_KEY_X(config, vertical) || HAS_KEY_X(config, split)) { + is_split = true; + } else if (new_win) { + api_set_error(err, kErrorTypeValidation, + "Must specify 'relative' or 'external' when creating a float"); + return false; + } + } + + if (HAS_KEY_X(config, vertical)) { + if (!is_split) { + api_set_error(err, kErrorTypeValidation, "floating windows cannot have 'vertical'"); + return false; + } + } + + if (HAS_KEY_X(config, split)) { + if (!is_split) { + api_set_error(err, kErrorTypeValidation, "floating windows cannot have 'split'"); + return false; + } + if (!parse_config_split(config->split, &fconfig->split)) { + api_set_error(err, kErrorTypeValidation, "Invalid value of 'split' key"); + return false; + } } if (HAS_KEY_X(config, anchor)) { @@ -660,7 +1006,7 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, } if (HAS_KEY_X(config, row)) { - if (!has_relative) { + if (!has_relative || is_split) { api_set_error(err, kErrorTypeValidation, "non-float cannot have 'row'"); return false; } @@ -668,7 +1014,7 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, } if (HAS_KEY_X(config, col)) { - if (!has_relative) { + if (!has_relative || is_split) { api_set_error(err, kErrorTypeValidation, "non-float cannot have 'col'"); return false; } @@ -676,7 +1022,7 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, } if (HAS_KEY_X(config, bufpos)) { - if (!has_relative) { + if (!has_relative || is_split) { api_set_error(err, kErrorTypeValidation, "non-float cannot have 'bufpos'"); return false; } else { @@ -701,7 +1047,7 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, api_set_error(err, kErrorTypeValidation, "'width' key must be a positive Integer"); return false; } - } else if (!reconf) { + } else if (!reconf && !is_split) { api_set_error(err, kErrorTypeValidation, "Must specify 'width'"); return false; } @@ -713,21 +1059,22 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, api_set_error(err, kErrorTypeValidation, "'height' key must be a positive Integer"); return false; } - } else if (!reconf) { + } else if (!reconf && !is_split) { api_set_error(err, kErrorTypeValidation, "Must specify 'height'"); return false; } - if (relative_is_win) { + if (relative_is_win || is_split) { fconfig->window = curwin->handle; if (HAS_KEY_X(config, win)) { if (config->win > 0) { fconfig->window = config->win; } } - } else { + } else if (has_relative) { if (HAS_KEY_X(config, win)) { - api_set_error(err, kErrorTypeValidation, "'win' key is only valid with relative='win'"); + api_set_error(err, kErrorTypeValidation, + "'win' key is only valid with relative='win' and relative=''"); return false; } } @@ -740,23 +1087,20 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, return false; } if (fconfig->external && !ui_has(kUIMultigrid)) { - api_set_error(err, kErrorTypeValidation, - "UI doesn't support external windows"); + api_set_error(err, kErrorTypeValidation, "UI doesn't support external windows"); return false; } } - if (!reconf && (!has_relative && !fconfig->external)) { - api_set_error(err, kErrorTypeValidation, - "One of 'relative' and 'external' must be used"); - return false; - } - if (HAS_KEY_X(config, focusable)) { fconfig->focusable = config->focusable; } if (HAS_KEY_X(config, zindex)) { + if (is_split) { + api_set_error(err, kErrorTypeValidation, "non-float cannot have 'zindex'"); + return false; + } if (config->zindex > 0) { fconfig->zindex = (int)config->zindex; } else { @@ -766,16 +1110,16 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, } if (HAS_KEY_X(config, title)) { + if (is_split) { + api_set_error(err, kErrorTypeValidation, "non-float cannot have 'title'"); + return false; + } // title only work with border if (!HAS_KEY_X(config, border) && !fconfig->border) { api_set_error(err, kErrorTypeException, "title requires border to be set"); return false; } - if (fconfig->title) { - clear_virttext(&fconfig->title_chunks); - } - parse_bordertext(config->title, kBorderTextTitle, fconfig, err); if (ERROR_SET(err)) { return false; @@ -793,16 +1137,16 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, } if (HAS_KEY_X(config, footer)) { + if (is_split) { + api_set_error(err, kErrorTypeValidation, "non-float cannot have 'footer'"); + return false; + } // footer only work with border if (!HAS_KEY_X(config, border) && !fconfig->border) { api_set_error(err, kErrorTypeException, "footer requires border to be set"); return false; } - if (fconfig->footer) { - clear_virttext(&fconfig->footer_chunks); - } - parse_bordertext(config->footer, kBorderTextFooter, fconfig, err); if (ERROR_SET(err)) { return false; @@ -820,6 +1164,10 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, } if (HAS_KEY_X(config, border)) { + if (is_split) { + api_set_error(err, kErrorTypeValidation, "non-float cannot have 'border'"); + return false; + } parse_border_style(config->border, fconfig, err); if (ERROR_SET(err)) { return false; |