aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/api/win_config.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/api/win_config.c')
-rw-r--r--src/nvim/api/win_config.c630
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;