feat(api): make nvim_open_win support non-floating windows (#25550)

Adds support to `nvim_open_win` and `nvim_win_set_config` for creating
and manipulating split (non-floating) windows.
This commit is contained in:
Will Hopkins 2024-01-31 19:43:35 -08:00 committed by GitHub
parent 8fa67fdae5
commit 6bba4beced
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 1129 additions and 141 deletions

View File

@ -3093,18 +3093,28 @@ nvim_win_text_height({window}, {*opts}) *nvim_win_text_height()*
Win_Config Functions *api-win_config* Win_Config Functions *api-win_config*
nvim_open_win({buffer}, {enter}, {*config}) *nvim_open_win()* nvim_open_win({buffer}, {enter}, {*config}) *nvim_open_win()*
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 Floats are windows that are drawn above the split layout, at some anchor
windows that are drawn above the split layout, at some anchor position in position in some other window. Floats can be drawn internally or by
some other window. Floats can be drawn internally or by external GUI with external GUI with the |ui-multigrid| extension. External windows are only
the |ui-multigrid| extension. External windows are only supported with supported with multigrid GUIs, and are displayed as separate top-level
multigrid GUIs, and are displayed as separate top-level windows. windows.
For a general overview of floats, see |api-floatwin|. For a general overview of floats, see |api-floatwin|.
Exactly one of `external` and `relative` must be specified. The `width` The `width` and `height` of the new window must be specified when opening
and `height` of the new window must be specified. 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 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 screen-grid and (row=Lines-1,col=Columns-1) refers to the bottom-right
@ -3127,6 +3137,13 @@ nvim_open_win({buffer}, {enter}, {*config}) *nvim_open_win()*
{relative='win', width=12, height=3, bufpos={100,10}}) {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
})
<
Attributes: ~ Attributes: ~
not allowed when |textlock| is active not allowed when |textlock| is active
@ -3142,7 +3159,8 @@ nvim_open_win({buffer}, {enter}, {*config}) *nvim_open_win()*
• "cursor" Cursor position in current window. • "cursor" Cursor position in current window.
• "mouse" Mouse position • "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 • anchor: Decides which corner of the float to place at
(row,col): (row,col):
• "NW" northwest (default) • "NW" northwest (default)
@ -3239,6 +3257,8 @@ nvim_open_win({buffer}, {enter}, {*config}) *nvim_open_win()*
• fixed: If true when anchor is NW or SW, the float window • fixed: If true when anchor is NW or SW, the float window
would be kept fixed even if the window would be truncated. would be kept fixed even if the window would be truncated.
• hide: If true the floating window will be hidden. • hide: If true the floating window will be hidden.
• vertical: Split vertically |:vertical|.
• split: Split direction: "left", "right", "above", "below".
Return: ~ Return: ~
Window handle, or 0 on error Window handle, or 0 on error

View File

@ -406,6 +406,9 @@ The following changes to existing APIs or features add new behavior.
• |:checkhealth| buffer can now be opened in a split window using modifiers like • |:checkhealth| buffer can now be opened in a split window using modifiers like
|:vertical|, |:horizontal| and |:botright|. |:vertical|, |:horizontal| and |:botright|.
• |nvim_open_win()| and |nvim_win_set_config()| now support opening normal (split)
windows, and moving floating windows into split windows.
============================================================================== ==============================================================================
REMOVED FEATURES *news-removed* REMOVED FEATURES *news-removed*

View File

@ -1483,15 +1483,24 @@ function vim.api.nvim_notify(msg, log_level, opts) end
--- @return integer --- @return integer
function vim.api.nvim_open_term(buffer, opts) end function vim.api.nvim_open_term(buffer, opts) end
--- Open a new window. --- Opens a new split window, or a floating window if `relative` is specified,
--- Currently this is used to open floating and external windows. Floats are --- or an external window (managed by the UI) if `external` is specified.
--- windows that are drawn above the split layout, at some anchor position in --- Floats are windows that are drawn above the split layout, at some anchor
--- some other window. Floats can be drawn internally or by external GUI with --- position in some other window. Floats can be drawn internally or by
--- the `ui-multigrid` extension. External windows are only supported with --- external GUI with the `ui-multigrid` extension. External windows are only
--- multigrid GUIs, and are displayed as separate top-level windows. --- supported with multigrid GUIs, and are displayed as separate top-level
--- windows.
--- For a general overview of floats, see `api-floatwin`. --- For a general overview of floats, see `api-floatwin`.
--- Exactly one of `external` and `relative` must be specified. The `width` --- The `width` and `height` of the new window must be specified when opening
--- and `height` of the new window must be specified. --- 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 --- 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 --- screen-grid and (row=Lines-1,col=Columns-1) refers to the bottom-right
--- corner. Fractional values are allowed, but the builtin implementation --- corner. Fractional values are allowed, but the builtin implementation
@ -1515,6 +1524,15 @@ function vim.api.nvim_open_term(buffer, opts) end
--- {relative='win', width=12, height=3, bufpos={100,10}}) --- {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
--- })
--- ```
---
--- @param buffer integer Buffer to display, or 0 for current buffer --- @param buffer integer Buffer to display, or 0 for current buffer
--- @param enter boolean Enter the window (make it the current window) --- @param enter boolean Enter the window (make it the current window)
--- @param config vim.api.keyset.float_config Map defining the window configuration. Keys: --- @param config vim.api.keyset.float_config Map defining the window configuration. Keys:
@ -1526,7 +1544,8 @@ function vim.api.nvim_open_term(buffer, opts) end
--- • "cursor" Cursor position in current window. --- • "cursor" Cursor position in current window.
--- • "mouse" Mouse position --- • "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 --- • anchor: Decides which corner of the float to place at
--- (row,col): --- (row,col):
--- • "NW" northwest (default) --- • "NW" northwest (default)
@ -1623,6 +1642,8 @@ function vim.api.nvim_open_term(buffer, opts) end
--- • fixed: If true when anchor is NW or SW, the float window --- • fixed: If true when anchor is NW or SW, the float window
--- would be kept fixed even if the window would be truncated. --- would be kept fixed even if the window would be truncated.
--- • hide: If true the floating window will be hidden. --- • hide: If true the floating window will be hidden.
--- • vertical: Split vertically `:vertical`.
--- • split: Split direction: "left", "right", "above", "below".
--- @return integer --- @return integer
function vim.api.nvim_open_win(buffer, enter, config) end function vim.api.nvim_open_win(buffer, enter, config) end

View File

@ -118,10 +118,12 @@ error('Cannot require a meta file')
--- @field height? integer --- @field height? integer
--- @field anchor? string --- @field anchor? string
--- @field relative? string --- @field relative? string
--- @field split? string
--- @field win? integer --- @field win? integer
--- @field bufpos? any[] --- @field bufpos? any[]
--- @field external? boolean --- @field external? boolean
--- @field focusable? boolean --- @field focusable? boolean
--- @field vertical? boolean
--- @field zindex? integer --- @field zindex? integer
--- @field border? any --- @field border? any
--- @field title? any --- @field title? any

View File

@ -115,10 +115,12 @@ typedef struct {
Integer height; Integer height;
String anchor; String anchor;
String relative; String relative;
String split;
Window win; Window win;
Array bufpos; Array bufpos;
Boolean external; Boolean external;
Boolean focusable; Boolean focusable;
Boolean vertical;
Integer zindex; Integer zindex;
Object border; Object border;
Object title; Object title;

View File

@ -7,6 +7,7 @@
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
#include "nvim/api/private/dispatch.h" #include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/api/tabpage.h"
#include "nvim/api/win_config.h" #include "nvim/api/win_config.h"
#include "nvim/ascii_defs.h" #include "nvim/ascii_defs.h"
#include "nvim/autocmd.h" #include "nvim/autocmd.h"
@ -15,6 +16,8 @@
#include "nvim/decoration.h" #include "nvim/decoration.h"
#include "nvim/decoration_defs.h" #include "nvim/decoration_defs.h"
#include "nvim/drawscreen.h" #include "nvim/drawscreen.h"
#include "nvim/eval/window.h"
#include "nvim/extmark_defs.h"
#include "nvim/globals.h" #include "nvim/globals.h"
#include "nvim/grid_defs.h" #include "nvim/grid_defs.h"
#include "nvim/highlight_group.h" #include "nvim/highlight_group.h"
@ -22,12 +25,15 @@
#include "nvim/mbyte.h" #include "nvim/mbyte.h"
#include "nvim/memory.h" #include "nvim/memory.h"
#include "nvim/option.h" #include "nvim/option.h"
#include "nvim/option_vars.h"
#include "nvim/pos_defs.h" #include "nvim/pos_defs.h"
#include "nvim/strings.h" #include "nvim/strings.h"
#include "nvim/syntax.h" #include "nvim/syntax.h"
#include "nvim/types_defs.h" #include "nvim/types_defs.h"
#include "nvim/ui.h" #include "nvim/ui.h"
#include "nvim/ui_compositor.h"
#include "nvim/ui_defs.h" #include "nvim/ui_defs.h"
#include "nvim/vim_defs.h"
#include "nvim/window.h" #include "nvim/window.h"
#include "nvim/winfloat.h" #include "nvim/winfloat.h"
@ -35,9 +41,9 @@
# include "api/win_config.c.generated.h" # include "api/win_config.c.generated.h"
#endif #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 /// 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 /// position in some other window. Floats can be drawn internally or by external
/// GUI with the |ui-multigrid| extension. External windows are only supported /// GUI with the |ui-multigrid| extension. External windows are only supported
@ -45,8 +51,17 @@
/// ///
/// For a general overview of floats, see |api-floatwin|. /// For a general overview of floats, see |api-floatwin|.
/// ///
/// Exactly one of `external` and `relative` must be specified. The `width` and /// The `width` and `height` of the new window must be specified when opening
/// `height` of the new window must be specified. /// 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 /// 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 /// screen-grid and (row=Lines-1,col=Columns-1) refers to the bottom-right
@ -73,6 +88,15 @@
/// {relative='win', width=12, height=3, bufpos={100,10}}) /// {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
/// })
/// ```
///
/// @param buffer Buffer to display, or 0 for current buffer /// @param buffer Buffer to display, or 0 for current buffer
/// @param enter Enter the window (make it the current window) /// @param enter Enter the window (make it the current window)
/// @param config Map defining the window configuration. Keys: /// @param config Map defining the window configuration. Keys:
@ -82,7 +106,8 @@
/// - "win" Window given by the `win` field, or current window. /// - "win" Window given by the `win` field, or current window.
/// - "cursor" Cursor position in current window. /// - "cursor" Cursor position in current window.
/// - "mouse" Mouse position /// - "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): /// - anchor: Decides which corner of the float to place at (row,col):
/// - "NW" northwest (default) /// - "NW" northwest (default)
/// - "NE" northeast /// - "NE" northeast
@ -169,13 +194,14 @@
/// - fixed: If true when anchor is NW or SW, the float window /// - fixed: If true when anchor is NW or SW, the float window
/// would be kept fixed even if the window would be truncated. /// would be kept fixed even if the window would be truncated.
/// - hide: If true the floating window will be hidden. /// - 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 /// @param[out] err Error details, if any
/// ///
/// @return Window handle, or 0 on error /// @return Window handle, or 0 on error
Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, Error *err) Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, Error *err)
FUNC_API_SINCE(6) FUNC_API_SINCE(6) FUNC_API_TEXTLOCK_ALLOW_CMDWIN
FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{ {
buf_T *buf = find_buffer_by_handle(buffer, err); buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) { if (!buf) {
@ -190,22 +216,67 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, E
if (!parse_float_config(config, &fconfig, false, true, err)) { if (!parse_float_config(config, &fconfig, false, true, err)) {
return 0; return 0;
} }
win_T *wp = win_new_float(NULL, false, fconfig, err);
bool is_split = HAS_KEY(config, float_config, split) || HAS_KEY(config, float_config, vertical);
win_T *wp = NULL;
tabpage_T *tp = curtab;
if (is_split) {
win_T *parent = NULL;
if (!HAS_KEY(config, float_config, win) || 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(config, float_config, vertical) && !HAS_KEY(config, float_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_float_config = fconfig;
}
} else {
wp = win_new_float(NULL, false, fconfig, err);
}
if (!wp) { if (!wp) {
api_set_error(err, kErrorTypeException, "Failed to create window");
return 0; 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) { 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_any_tab(wp) && buf != wp->w_buffer) {
if (win_valid(wp) && buffer > 0) { win_set_buf(wp, buf, !enter || fconfig.noautocmd, err);
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(wp)) { if (!win_valid_any_tab(wp)) {
api_set_error(err, kErrorTypeException, "Window was closed immediately"); api_set_error(err, kErrorTypeException, "Window was closed immediately");
return 0; return 0;
} }
@ -217,6 +288,36 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, E
return wp->handle; return wp->handle;
} }
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 /// Configures window layout. Currently only for floating and external windows
/// (including changing a split window to those layouts). /// (including changing a split window to those layouts).
/// ///
@ -236,18 +337,195 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err)
if (!win) { if (!win) {
return; 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(config, float_config, split);
bool has_vertical = HAS_KEY(config, float_config, vertical);
// reuse old values, if not overridden // reuse old values, if not overridden
FloatConfig fconfig = new_float ? FLOAT_CONFIG_INIT : win->w_float_config; FloatConfig fconfig = win->w_float_config;
if (!parse_float_config(config, &fconfig, !new_float, false, err)) { bool to_split = (!HAS_KEY(config, float_config, relative) || striequal(config->relative.data, ""))
&& ((!HAS_KEY(config, float_config, external) && !fconfig.external)
|| !config->external)
&& (has_split || has_vertical || was_split);
if (!parse_float_config(config, &fconfig, !was_split || to_split, false, err)) {
return; return;
} }
if (new_float) { if (was_split && !to_split) {
if (!win_new_float(win, false, fconfig, err)) { if (!win_new_float(win, false, fconfig, err)) {
return; return;
} }
redraw_later(win, UPD_NOT_VALID); redraw_later(win, UPD_NOT_VALID);
} else if (to_split) {
win_T *parent = NULL;
if (!HAS_KEY(config, float_config, win) || 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_float_config = fconfig;
// If there's no vertical or split set, or if the split is the same as the old split,
// then we can just change the size of the window.
if ((!has_vertical && !has_split)
|| (was_split
&& !HAS_KEY(config, float_config,
win) && ((!has_split && !has_vertical) || old_split == fconfig.split))) {
if (HAS_KEY(config, float_config, width)) {
win_setwidth_win(fconfig.width, win);
}
if (HAS_KEY(config, float_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_float_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(config, float_config, width)) {
win_setwidth_win(fconfig.width, win);
}
if (HAS_KEY(config, float_config, height)) {
win_setheight_win(fconfig.height, win);
}
redraw_later(win, UPD_NOT_VALID);
return;
} else { } else {
win_config_float(win, fconfig); win_config_float(win, fconfig);
win->w_pos_changed = true; win->w_pos_changed = true;
@ -317,6 +595,9 @@ Dictionary nvim_win_get_config(Window window, Error *err)
/// Keep in sync with FloatRelative in buffer_defs.h /// Keep in sync with FloatRelative in buffer_defs.h
static const char *const float_relative_str[] = { "editor", "win", "cursor", "mouse" }; 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" };
Dictionary rv = ARRAY_DICT_INIT; Dictionary rv = ARRAY_DICT_INIT;
win_T *wp = find_window_by_handle(window, err); win_T *wp = find_window_by_handle(window, err);
@ -373,11 +654,18 @@ Dictionary nvim_win_get_config(Window window, Error *err)
rv = config_put_bordertext(rv, config, kBorderTextFooter); rv = config_put_bordertext(rv, config, kBorderTextFooter);
} }
} }
} else if (!config->external) {
PUT(rv, "width", INTEGER_OBJ(wp->w_width));
PUT(rv, "height", INTEGER_OBJ(wp->w_height));
WinSplit split = win_split_dir(wp);
PUT(rv, "split", CSTR_TO_OBJ(win_split_str[split]));
} }
const char *rel = (wp->w_floating && !config->external if (wp->w_floating && !config->external) {
? float_relative_str[config->relative] : ""); PUT(rv, "relative", CSTR_TO_OBJ(float_relative_str[config->relative]));
PUT(rv, "relative", CSTR_TO_OBJ(rel)); } else {
PUT(rv, "relative", CSTR_TO_OBJ(""));
}
return rv; return rv;
} }
@ -419,10 +707,26 @@ static bool parse_float_relative(String relative, FloatRelative *out)
return true; 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) static bool parse_float_bufpos(Array bufpos, lpos_T *out)
{ {
if (bufpos.size != 2 if (bufpos.size != 2 || bufpos.items[0].type != kObjectTypeInteger
|| bufpos.items[0].type != kObjectTypeInteger
|| bufpos.items[1].type != kObjectTypeInteger) { || bufpos.items[1].type != kObjectTypeInteger) {
return false; return false;
} }
@ -529,7 +833,7 @@ static bool parse_bordertext_pos(String bordertext_pos, BorderTextType bordertex
return true; return true;
} }
static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
{ {
struct { struct {
const char *name; const char *name;
@ -544,7 +848,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
{ NULL, { { NUL } }, false }, { 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; int *hl_ids = fconfig->border_hl_ids;
fconfig->border = true; fconfig->border = true;
@ -553,8 +857,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
Array arr = style.data.array; Array arr = style.data.array;
size_t size = arr.size; size_t size = arr.size;
if (!size || size > 8 || (size & (size - 1))) { if (!size || size > 8 || (size & (size - 1))) {
api_set_error(err, kErrorTypeValidation, api_set_error(err, kErrorTypeValidation, "invalid number of border chars");
"invalid number of border chars");
return; return;
} }
for (size_t i = 0; i < size; i++) { for (size_t i = 0; i < size; i++) {
@ -584,10 +887,8 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
api_set_error(err, kErrorTypeValidation, "invalid border char"); api_set_error(err, kErrorTypeValidation, "invalid border char");
return; return;
} }
if (string.size if (string.size && mb_string2cells_len(string.data, string.size) > 1) {
&& mb_string2cells_len(string.data, string.size) > 1) { api_set_error(err, kErrorTypeValidation, "border chars must be one cell");
api_set_error(err, kErrorTypeValidation,
"border chars must be one cell");
return; return;
} }
size_t len = MIN(string.size, sizeof(*chars) - 1); size_t len = MIN(string.size, sizeof(*chars) - 1);
@ -606,8 +907,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
|| (chars[1][0] && chars[3][0] && !chars[2][0]) || (chars[1][0] && chars[3][0] && !chars[2][0])
|| (chars[3][0] && chars[5][0] && !chars[4][0]) || (chars[3][0] && chars[5][0] && !chars[4][0])
|| (chars[5][0] && chars[7][0] && !chars[6][0])) { || (chars[5][0] && chars[7][0] && !chars[6][0])) {
api_set_error(err, kErrorTypeValidation, api_set_error(err, kErrorTypeValidation, "corner between used edges must be specified");
"corner between used edges must be specified");
} }
} else if (style.type == kObjectTypeString) { } else if (style.type == kObjectTypeString) {
String str = style.data.string; String str = style.data.string;
@ -634,8 +934,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
return; return;
} }
} }
api_set_error(err, kErrorTypeValidation, api_set_error(err, kErrorTypeValidation, "invalid border style \"%s\"", str.data);
"invalid border style \"%s\"", str.data);
} }
} }
@ -643,17 +942,16 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
bool new_win, Error *err) bool new_win, Error *err)
{ {
#define HAS_KEY_X(d, key) HAS_KEY(d, float_config, key) #define HAS_KEY_X(d, key) HAS_KEY(d, float_config, key)
bool has_relative = false, relative_is_win = false; bool has_relative = false, relative_is_win = false, is_split = false;
// ignore empty string, to match nvim_win_get_config if (HAS_KEY_X(config, relative) && !striequal(config->relative.data, "")) {
if (HAS_KEY_X(config, relative) && config->relative.size > 0) {
if (!parse_float_relative(config->relative, &fconfig->relative)) { if (!parse_float_relative(config->relative, &fconfig->relative)) {
api_set_error(err, kErrorTypeValidation, "Invalid value of 'relative' key"); api_set_error(err, kErrorTypeValidation, "Invalid value of 'relative' key");
return false; return false;
} }
if (!(HAS_KEY_X(config, row) && HAS_KEY_X(config, col)) && !HAS_KEY_X(config, bufpos)) { if (config->relative.size > 0 && !(HAS_KEY_X(config, row) && HAS_KEY_X(config, col))
api_set_error(err, kErrorTypeValidation, && !HAS_KEY_X(config, bufpos)) {
"'relative' requires 'row'/'col' or 'bufpos'"); api_set_error(err, kErrorTypeValidation, "'relative' requires 'row'/'col' or 'bufpos'");
return false; return false;
} }
@ -663,6 +961,32 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
relative_is_win = true; relative_is_win = true;
fconfig->bufpos.lnum = -1; fconfig->bufpos.lnum = -1;
} }
} else if (!HAS_KEY_X(config, external) || !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)) { if (HAS_KEY_X(config, anchor)) {
@ -673,7 +997,7 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
} }
if (HAS_KEY_X(config, row)) { if (HAS_KEY_X(config, row)) {
if (!has_relative) { if (!has_relative || is_split) {
api_set_error(err, kErrorTypeValidation, "non-float cannot have 'row'"); api_set_error(err, kErrorTypeValidation, "non-float cannot have 'row'");
return false; return false;
} }
@ -681,7 +1005,7 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
} }
if (HAS_KEY_X(config, col)) { if (HAS_KEY_X(config, col)) {
if (!has_relative) { if (!has_relative || is_split) {
api_set_error(err, kErrorTypeValidation, "non-float cannot have 'col'"); api_set_error(err, kErrorTypeValidation, "non-float cannot have 'col'");
return false; return false;
} }
@ -689,7 +1013,7 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
} }
if (HAS_KEY_X(config, bufpos)) { if (HAS_KEY_X(config, bufpos)) {
if (!has_relative) { if (!has_relative || is_split) {
api_set_error(err, kErrorTypeValidation, "non-float cannot have 'bufpos'"); api_set_error(err, kErrorTypeValidation, "non-float cannot have 'bufpos'");
return false; return false;
} else { } else {
@ -714,7 +1038,7 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
api_set_error(err, kErrorTypeValidation, "'width' key must be a positive Integer"); api_set_error(err, kErrorTypeValidation, "'width' key must be a positive Integer");
return false; return false;
} }
} else if (!reconf) { } else if (!reconf && !is_split) {
api_set_error(err, kErrorTypeValidation, "Must specify 'width'"); api_set_error(err, kErrorTypeValidation, "Must specify 'width'");
return false; return false;
} }
@ -726,21 +1050,22 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
api_set_error(err, kErrorTypeValidation, "'height' key must be a positive Integer"); api_set_error(err, kErrorTypeValidation, "'height' key must be a positive Integer");
return false; return false;
} }
} else if (!reconf) { } else if (!reconf && !is_split) {
api_set_error(err, kErrorTypeValidation, "Must specify 'height'"); api_set_error(err, kErrorTypeValidation, "Must specify 'height'");
return false; return false;
} }
if (relative_is_win) { if (relative_is_win || is_split) {
fconfig->window = curwin->handle; fconfig->window = curwin->handle;
if (HAS_KEY_X(config, win)) { if (HAS_KEY_X(config, win)) {
if (config->win > 0) { if (config->win > 0) {
fconfig->window = config->win; fconfig->window = config->win;
} }
} }
} else { } else if (has_relative) {
if (HAS_KEY_X(config, win)) { 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; return false;
} }
} }
@ -753,23 +1078,20 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
return false; return false;
} }
if (fconfig->external && !ui_has(kUIMultigrid)) { if (fconfig->external && !ui_has(kUIMultigrid)) {
api_set_error(err, kErrorTypeValidation, api_set_error(err, kErrorTypeValidation, "UI doesn't support external windows");
"UI doesn't support external windows");
return false; 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)) { if (HAS_KEY_X(config, focusable)) {
fconfig->focusable = config->focusable; fconfig->focusable = config->focusable;
} }
if (HAS_KEY_X(config, zindex)) { 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) { if (config->zindex > 0) {
fconfig->zindex = (int)config->zindex; fconfig->zindex = (int)config->zindex;
} else { } else {
@ -779,6 +1101,10 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
} }
if (HAS_KEY_X(config, title)) { 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 // title only work with border
if (!HAS_KEY_X(config, border) && !fconfig->border) { if (!HAS_KEY_X(config, border) && !fconfig->border) {
api_set_error(err, kErrorTypeException, "title requires border to be set"); api_set_error(err, kErrorTypeException, "title requires border to be set");
@ -802,6 +1128,10 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
} }
if (HAS_KEY_X(config, footer)) { 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 // footer only work with border
if (!HAS_KEY_X(config, border) && !fconfig->border) { if (!HAS_KEY_X(config, border) && !fconfig->border) {
api_set_error(err, kErrorTypeException, "footer requires border to be set"); api_set_error(err, kErrorTypeException, "footer requires border to be set");
@ -825,6 +1155,10 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
} }
if (HAS_KEY_X(config, border)) { 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); parse_border_style(config->border, fconfig, err);
if (ERROR_SET(err)) { if (ERROR_SET(err)) {
return false; return false;

View File

@ -889,6 +889,14 @@ typedef enum {
kFloatRelativeMouse = 3, kFloatRelativeMouse = 3,
} FloatRelative; } FloatRelative;
/// Keep in sync with win_split_str[] in nvim_win_get_config() (api/win_config.c)
typedef enum {
kWinSplitLeft = 0,
kWinSplitRight = 1,
kWinSplitAbove = 2,
kWinSplitBelow = 3,
} WinSplit;
typedef enum { typedef enum {
kWinStyleUnused = 0, kWinStyleUnused = 0,
kWinStyleMinimal, /// Minimal UI: no number column, eob markers, etc kWinStyleMinimal, /// Minimal UI: no number column, eob markers, etc
@ -914,6 +922,7 @@ typedef struct {
FloatRelative relative; FloatRelative relative;
bool external; bool external;
bool focusable; bool focusable;
WinSplit split;
int zindex; int zindex;
WinStyle style; WinStyle style;
bool border; bool border;
@ -939,6 +948,7 @@ typedef struct {
.row = 0, .col = 0, .anchor = 0, \ .row = 0, .col = 0, .anchor = 0, \
.relative = 0, .external = false, \ .relative = 0, .external = false, \
.focusable = true, \ .focusable = true, \
.split = 0, \
.zindex = kZIndexFloatDefault, \ .zindex = kZIndexFloatDefault, \
.style = kWinStyleUnused, \ .style = kWinStyleUnused, \
.noautocmd = false, \ .noautocmd = false, \

View File

@ -713,7 +713,7 @@ void win_set_buf(win_T *win, buf_T *buf, bool noautocmd, Error *err)
} }
switchwin_T switchwin; switchwin_T switchwin;
if (switch_win_noblock(&switchwin, win, tab, false) == FAIL) { if (switch_win_noblock(&switchwin, win, tab, true) == FAIL) {
api_set_error(err, api_set_error(err,
kErrorTypeException, kErrorTypeException,
"Failed to switch to window %d", "Failed to switch to window %d",
@ -733,7 +733,7 @@ void win_set_buf(win_T *win, buf_T *buf, bool noautocmd, Error *err)
// So do it now. // So do it now.
validate_cursor(); validate_cursor();
restore_win_noblock(&switchwin, false); restore_win_noblock(&switchwin, true);
if (noautocmd) { if (noautocmd) {
unblock_autocmds(); unblock_autocmds();
} }
@ -928,6 +928,7 @@ static int check_split_disallowed(void)
// WSP_TOP: open window at the top-left of the screen (help window). // WSP_TOP: open window at the top-left of the screen (help window).
// WSP_BOT: open window at the bottom-right of the screen (quickfix window). // WSP_BOT: open window at the bottom-right of the screen (quickfix window).
// WSP_HELP: creating the help window, keep layout snapshot // WSP_HELP: creating the help window, keep layout snapshot
// WSP_NOENTER: do not enter the new window or trigger WinNew autocommands
// //
// return FAIL for failure, OK otherwise // return FAIL for failure, OK otherwise
int win_split(int size, int flags) int win_split(int size, int flags)
@ -956,20 +957,20 @@ int win_split(int size, int flags)
clear_snapshot(curtab, SNAP_HELP_IDX); clear_snapshot(curtab, SNAP_HELP_IDX);
} }
return win_split_ins(size, flags, NULL, 0); return win_split_ins(size, flags, NULL, 0) == NULL ? FAIL : OK;
} }
/// When "new_wp" is NULL: split the current window in two. /// When "new_wp" is NULL: split the current window in two.
/// When "new_wp" is not NULL: insert this window at the far /// When "new_wp" is not NULL: insert this window at the far
/// top/left/right/bottom. /// top/left/right/bottom.
/// @return FAIL for failure, OK otherwise /// @return NULL for failure, or pointer to new window
int win_split_ins(int size, int flags, win_T *new_wp, int dir) win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir)
{ {
win_T *wp = new_wp; win_T *wp = new_wp;
// aucmd_win[] should always remain floating // aucmd_win[] should always remain floating
if (new_wp != NULL && is_aucmd_win(new_wp)) { if (new_wp != NULL && is_aucmd_win(new_wp)) {
return FAIL; return NULL;
} }
win_T *oldwin; win_T *oldwin;
@ -985,22 +986,24 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
int need_status = 0; int need_status = 0;
int new_size = size; int new_size = size;
bool new_in_layout = (new_wp == NULL || new_wp->w_floating); bool new_in_layout = (new_wp == NULL || new_wp->w_floating);
bool vertical = flags & WSP_VERT;
bool toplevel = flags & (WSP_TOP | WSP_BOT);
// add a status line when p_ls == 1 and splitting the first window // add a status line when p_ls == 1 and splitting the first window
if (one_nonfloat() && p_ls == 1 && oldwin->w_status_height == 0) { if (one_nonfloat() && p_ls == 1 && oldwin->w_status_height == 0) {
if (oldwin->w_height <= p_wmh && new_in_layout) { if (oldwin->w_height <= p_wmh && new_in_layout) {
emsg(_(e_noroom)); emsg(_(e_noroom));
return FAIL; return NULL;
} }
need_status = STATUS_HEIGHT; need_status = STATUS_HEIGHT;
} }
bool do_equal = false; bool do_equal = false;
int oldwin_height = 0; int oldwin_height = 0;
const int layout = flags & WSP_VERT ? FR_ROW : FR_COL; const int layout = vertical ? FR_ROW : FR_COL;
bool did_set_fraction = false; bool did_set_fraction = false;
if (flags & WSP_VERT) { if (vertical) {
// Check if we are able to split the current window and compute its // Check if we are able to split the current window and compute its
// width. // width.
// Current window requires at least 1 space. // Current window requires at least 1 space.
@ -1011,7 +1014,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
} }
int minwidth; int minwidth;
int available; int available;
if (flags & (WSP_BOT | WSP_TOP)) { if (toplevel) {
minwidth = frame_minwidth(topframe, NOWIN); minwidth = frame_minwidth(topframe, NOWIN);
available = topframe->fr_width; available = topframe->fr_width;
needed += minwidth; needed += minwidth;
@ -1039,7 +1042,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
} }
if (available < needed && new_in_layout) { if (available < needed && new_in_layout) {
emsg(_(e_noroom)); emsg(_(e_noroom));
return FAIL; return NULL;
} }
if (new_size == 0) { if (new_size == 0) {
new_size = oldwin->w_width / 2; new_size = oldwin->w_width / 2;
@ -1092,7 +1095,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
} }
int minheight; int minheight;
int available; int available;
if (flags & (WSP_BOT | WSP_TOP)) { if (toplevel) {
minheight = frame_minheight(topframe, NOWIN) + need_status; minheight = frame_minheight(topframe, NOWIN) + need_status;
available = topframe->fr_height; available = topframe->fr_height;
needed += minheight; needed += minheight;
@ -1119,7 +1122,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
} }
if (available < needed && new_in_layout) { if (available < needed && new_in_layout) {
emsg(_(e_noroom)); emsg(_(e_noroom));
return FAIL; return NULL;
} }
oldwin_height = oldwin->w_height; oldwin_height = oldwin->w_height;
if (need_status) { if (need_status) {
@ -1182,7 +1185,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
&& ((flags & WSP_BOT) && ((flags & WSP_BOT)
|| (flags & WSP_BELOW) || (flags & WSP_BELOW)
|| (!(flags & WSP_ABOVE) || (!(flags & WSP_ABOVE)
&& ((flags & WSP_VERT) ? p_spr : p_sb)))) { && (vertical ? p_spr : p_sb)))) {
// new window below/right of current one // new window below/right of current one
if (new_wp == NULL) { if (new_wp == NULL) {
wp = win_alloc(oldwin, false); wp = win_alloc(oldwin, false);
@ -1199,7 +1202,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
if (new_wp == NULL) { if (new_wp == NULL) {
if (wp == NULL) { if (wp == NULL) {
return FAIL; return NULL;
} }
new_frame(wp); new_frame(wp);
@ -1218,9 +1221,9 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
frame_T *curfrp; frame_T *curfrp;
// Reorganise the tree of frames to insert the new window. // Reorganise the tree of frames to insert the new window.
if (flags & (WSP_TOP | WSP_BOT)) { if (toplevel) {
if ((topframe->fr_layout == FR_COL && (flags & WSP_VERT) == 0) if ((topframe->fr_layout == FR_COL && !vertical)
|| (topframe->fr_layout == FR_ROW && (flags & WSP_VERT) != 0)) { || (topframe->fr_layout == FR_ROW && vertical)) {
curfrp = topframe->fr_child; curfrp = topframe->fr_child;
if (flags & WSP_BOT) { if (flags & WSP_BOT) {
while (curfrp->fr_next != NULL) { while (curfrp->fr_next != NULL) {
@ -1237,7 +1240,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
before = false; before = false;
} else if (flags & WSP_ABOVE) { } else if (flags & WSP_ABOVE) {
before = true; before = true;
} else if (flags & WSP_VERT) { } else if (vertical) {
before = !p_spr; before = !p_spr;
} else { } else {
before = !p_sb; before = !p_sb;
@ -1285,14 +1288,14 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
} }
wp->w_fraction = oldwin->w_fraction; wp->w_fraction = oldwin->w_fraction;
if (flags & WSP_VERT) { if (vertical) {
wp->w_p_scr = curwin->w_p_scr; wp->w_p_scr = curwin->w_p_scr;
if (need_status) { if (need_status) {
win_new_height(oldwin, oldwin->w_height - 1); win_new_height(oldwin, oldwin->w_height - 1);
oldwin->w_status_height = need_status; oldwin->w_status_height = need_status;
} }
if (flags & (WSP_TOP | WSP_BOT)) { if (toplevel) {
// set height and row of new window to full height // set height and row of new window to full height
wp->w_winrow = tabline_height(); wp->w_winrow = tabline_height();
win_new_height(wp, curfrp->fr_height - (p_ls == 1 || p_ls == 2)); win_new_height(wp, curfrp->fr_height - (p_ls == 1 || p_ls == 2));
@ -1316,7 +1319,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
wp->w_vsep_width = oldwin->w_vsep_width; wp->w_vsep_width = oldwin->w_vsep_width;
oldwin->w_vsep_width = 1; oldwin->w_vsep_width = 1;
} }
if (flags & (WSP_TOP | WSP_BOT)) { if (toplevel) {
if (flags & WSP_BOT) { if (flags & WSP_BOT) {
frame_add_vsep(curfrp); frame_add_vsep(curfrp);
} }
@ -1338,7 +1341,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
} else { } else {
const bool is_stl_global = global_stl_height() > 0; const bool is_stl_global = global_stl_height() > 0;
// width and column of new window is same as current window // width and column of new window is same as current window
if (flags & (WSP_TOP | WSP_BOT)) { if (toplevel) {
wp->w_wincol = 0; wp->w_wincol = 0;
win_new_width(wp, Columns); win_new_width(wp, Columns);
wp->w_vsep_width = 0; wp->w_vsep_width = 0;
@ -1359,7 +1362,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
wp->w_hsep_height = oldwin->w_hsep_height; wp->w_hsep_height = oldwin->w_hsep_height;
oldwin->w_hsep_height = is_stl_global ? 1 : 0; oldwin->w_hsep_height = is_stl_global ? 1 : 0;
} }
if (flags & (WSP_TOP | WSP_BOT)) { if (toplevel) {
int new_fr_height = curfrp->fr_height - new_size; int new_fr_height = curfrp->fr_height - new_size;
if (is_stl_global) { if (is_stl_global) {
if (flags & WSP_BOT) { if (flags & WSP_BOT) {
@ -1405,7 +1408,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
frame_fix_height(oldwin); frame_fix_height(oldwin);
} }
if (flags & (WSP_TOP | WSP_BOT)) { if (toplevel) {
win_comp_pos(); win_comp_pos();
} }
@ -1426,7 +1429,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
// equalize the window sizes. // equalize the window sizes.
if (do_equal || dir != 0) { if (do_equal || dir != 0) {
win_equal(wp, true, (flags & WSP_VERT) ? (dir == 'v' ? 'b' : 'h') : (dir == 'h' ? 'b' : 'v')); win_equal(wp, true, vertical ? (dir == 'v' ? 'b' : 'h') : (dir == 'h' ? 'b' : 'v'));
} else if (!is_aucmd_win(wp)) { } else if (!is_aucmd_win(wp)) {
win_fix_scroll(false); win_fix_scroll(false);
} }
@ -1447,10 +1450,12 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
} }
} }
// make the new window the current window if (!(flags & WSP_NOENTER)) {
win_enter_ext(wp, WEE_TRIGGER_NEW_AUTOCMDS | WEE_TRIGGER_ENTER_AUTOCMDS // make the new window the current window
| WEE_TRIGGER_LEAVE_AUTOCMDS); win_enter_ext(wp, WEE_TRIGGER_NEW_AUTOCMDS | WEE_TRIGGER_ENTER_AUTOCMDS
if (flags & WSP_VERT) { | WEE_TRIGGER_LEAVE_AUTOCMDS);
}
if (vertical) {
p_wiw = i; p_wiw = i;
} else { } else {
p_wh = i; p_wh = i;
@ -1461,7 +1466,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
oldwin->w_pos_changed = true; oldwin->w_pos_changed = true;
} }
return OK; return wp;
} }
// Initialize window "newp" from window "oldp". // Initialize window "newp" from window "oldp".

View File

@ -21,15 +21,16 @@ enum {
/// arguments for win_split() /// arguments for win_split()
enum { enum {
WSP_ROOM = 0x01, ///< require enough room WSP_ROOM = 0x01, ///< require enough room
WSP_VERT = 0x02, ///< split/equalize vertically WSP_VERT = 0x02, ///< split/equalize vertically
WSP_HOR = 0x04, ///< equalize horizontally WSP_HOR = 0x04, ///< equalize horizontally
WSP_TOP = 0x08, ///< window at top-left of shell WSP_TOP = 0x08, ///< window at top-left of shell
WSP_BOT = 0x10, ///< window at bottom-right of shell WSP_BOT = 0x10, ///< window at bottom-right of shell
WSP_HELP = 0x20, ///< creating the help window WSP_HELP = 0x20, ///< creating the help window
WSP_BELOW = 0x40, ///< put new window below/right WSP_BELOW = 0x40, ///< put new window below/right
WSP_ABOVE = 0x80, ///< put new window above/left WSP_ABOVE = 0x80, ///< put new window above/left
WSP_NEWLOC = 0x100, ///< don't copy location list WSP_NEWLOC = 0x100, ///< don't copy location list
WSP_NOENTER = 0x200, ///< don't enter the new window
}; };
enum { enum {

View File

@ -1232,6 +1232,437 @@ describe('API/win', function()
) )
eq(wins_before, api.nvim_list_wins()) eq(wins_before, api.nvim_list_wins())
end) end)
it('creates a split window', function()
local win = api.nvim_open_win(0, true, {
vertical = false,
})
eq('', api.nvim_win_get_config(win).relative)
end)
it('creates split windows in the correct direction', function()
local initial_win = api.nvim_get_current_win()
local win = api.nvim_open_win(0, true, {
vertical = true,
})
eq('', api.nvim_win_get_config(win).relative)
local layout = fn.winlayout()
eq({
'row',
{
{ 'leaf', win },
{ 'leaf', initial_win },
},
}, layout)
end)
it("respects the 'split' option", function()
local initial_win = api.nvim_get_current_win()
local win = api.nvim_open_win(0, true, {
split = 'below',
})
eq('', api.nvim_win_get_config(win).relative)
local layout = fn.winlayout()
eq({
'col',
{
{ 'leaf', initial_win },
{ 'leaf', win },
},
}, layout)
end)
it(
"doesn't change tp_curwin when splitting window in non-current tab with enter=false",
function()
local tab1 = api.nvim_get_current_tabpage()
local tab1_win = api.nvim_get_current_win()
helpers.command('tabnew')
local tab2 = api.nvim_get_current_tabpage()
local tab2_win = api.nvim_get_current_win()
eq({ tab1_win, tab2_win }, api.nvim_list_wins())
eq({ tab1, tab2 }, api.nvim_list_tabpages())
api.nvim_set_current_tabpage(tab1)
eq(tab1_win, api.nvim_get_current_win())
local tab2_prevwin = fn.tabpagewinnr(tab2, '#')
-- split in tab2 whine in tab2, with enter = false
local tab2_win2 = api.nvim_open_win(api.nvim_create_buf(false, true), false, {
win = tab2_win,
split = 'right',
})
eq(tab1_win, api.nvim_get_current_win()) -- we should still be in the first tp
eq(tab1_win, api.nvim_tabpage_get_win(tab1))
eq(tab2_win, api.nvim_tabpage_get_win(tab2)) -- tab2's tp_curwin should not have changed
eq(tab2_prevwin, fn.tabpagewinnr(tab2, '#')) -- tab2's tp_prevwin should not have changed
eq({ tab1_win, tab2_win, tab2_win2 }, api.nvim_list_wins())
eq({ tab2_win, tab2_win2 }, api.nvim_tabpage_list_wins(tab2))
end
)
it('creates splits in the correct location', function()
local first_win = api.nvim_get_current_win()
-- specifying window 0 should create a split next to the current window
local win = api.nvim_open_win(0, true, {
vertical = false,
})
local layout = fn.winlayout()
eq({
'col',
{
{ 'leaf', win },
{ 'leaf', first_win },
},
}, layout)
-- not specifying a window should create a top-level split
local win2 = api.nvim_open_win(0, true, {
split = 'left',
win = -1,
})
layout = fn.winlayout()
eq({
'row',
{
{ 'leaf', win2 },
{
'col',
{
{ 'leaf', win },
{ 'leaf', first_win },
},
},
},
}, layout)
-- specifying a window should create a split next to that window
local win3 = api.nvim_open_win(0, true, {
win = win,
vertical = false,
})
layout = fn.winlayout()
eq({
'row',
{
{ 'leaf', win2 },
{
'col',
{
{ 'leaf', win3 },
{ 'leaf', win },
{ 'leaf', first_win },
},
},
},
}, layout)
end)
end)
describe('set_config', function()
it('moves a split into a float', function()
local win = api.nvim_open_win(0, true, {
vertical = false,
})
eq('', api.nvim_win_get_config(win).relative)
api.nvim_win_set_config(win, {
relative = 'editor',
row = 5,
col = 5,
width = 5,
height = 5,
})
eq('editor', api.nvim_win_get_config(win).relative)
end)
it('throws error when attempting to move the last window', function()
local err = pcall_err(api.nvim_win_set_config, 0, {
vertical = false,
})
eq('Cannot move last window', err)
end)
it('passing retval of get_config results in no-op', function()
-- simple split layout
local win = api.nvim_open_win(0, true, {
split = 'left',
})
local layout = fn.winlayout()
local config = api.nvim_win_get_config(win)
api.nvim_win_set_config(win, config)
eq(layout, fn.winlayout())
-- nested split layout
local win2 = api.nvim_open_win(0, true, {
vertical = true,
})
local win3 = api.nvim_open_win(0, true, {
win = win2,
vertical = false,
})
layout = fn.winlayout()
config = api.nvim_win_get_config(win2)
api.nvim_win_set_config(win2, config)
eq(layout, fn.winlayout())
config = api.nvim_win_get_config(win3)
api.nvim_win_set_config(win3, config)
eq(layout, fn.winlayout())
end)
it('moves a float into a split', function()
local layout = fn.winlayout()
eq('leaf', layout[1])
local win = api.nvim_open_win(0, true, {
relative = 'editor',
row = 5,
col = 5,
width = 5,
height = 5,
})
api.nvim_win_set_config(win, {
split = 'below',
win = -1,
})
eq('', api.nvim_win_get_config(win).relative)
layout = fn.winlayout()
eq('col', layout[1])
eq(2, #layout[2])
eq(win, layout[2][2][2])
end)
it('respects the "split" option', function()
local layout = fn.winlayout()
eq('leaf', layout[1])
local first_win = layout[2]
local win = api.nvim_open_win(0, true, {
relative = 'editor',
row = 5,
col = 5,
width = 5,
height = 5,
})
api.nvim_win_set_config(win, {
split = 'right',
win = first_win,
})
layout = fn.winlayout()
eq('row', layout[1])
eq(2, #layout[2])
eq(win, layout[2][2][2])
local config = api.nvim_win_get_config(win)
eq('', config.relative)
eq('right', config.split)
api.nvim_win_set_config(win, {
split = 'below',
win = first_win,
})
layout = fn.winlayout()
eq('col', layout[1])
eq(2, #layout[2])
eq(win, layout[2][2][2])
config = api.nvim_win_get_config(win)
eq('', config.relative)
eq('below', config.split)
end)
it('creates top-level splits', function()
local win = api.nvim_open_win(0, true, {
vertical = false,
})
local win2 = api.nvim_open_win(0, true, {
vertical = true,
win = -1,
})
local layout = fn.winlayout()
eq('row', layout[1])
eq(2, #layout[2])
eq(win2, layout[2][1][2])
api.nvim_win_set_config(win, {
split = 'below',
win = -1,
})
layout = fn.winlayout()
eq('col', layout[1])
eq(2, #layout[2])
eq('row', layout[2][1][1])
eq(win, layout[2][2][2])
end)
it('moves splits to other tabpages', function()
local curtab = api.nvim_get_current_tabpage()
local win = api.nvim_open_win(0, false, { split = 'left' })
command('tabnew')
local tabnr = api.nvim_get_current_tabpage()
command('tabprev') -- return to the initial tab
api.nvim_win_set_config(win, {
split = 'right',
win = api.nvim_tabpage_get_win(tabnr),
})
eq(tabnr, api.nvim_win_get_tabpage(win))
-- we are changing the config, the current tabpage should not change
eq(curtab, api.nvim_get_current_tabpage())
command('tabnext') -- switch to the new tabpage so we can get the layout
local layout = fn.winlayout()
eq({
'row',
{
{ 'leaf', api.nvim_tabpage_get_win(tabnr) },
{ 'leaf', win },
},
}, layout)
end)
it('correctly moves curwin when moving curwin to a different tabpage', function()
local curtab = api.nvim_get_current_tabpage()
command('tabnew')
local tab2 = api.nvim_get_current_tabpage()
local tab2_win = api.nvim_get_current_win()
command('tabprev') -- return to the initial tab
local neighbor = api.nvim_get_current_win()
-- create and enter a new split
local win = api.nvim_open_win(0, true, {
vertical = false,
})
eq(curtab, api.nvim_win_get_tabpage(win))
eq({ win, neighbor }, api.nvim_tabpage_list_wins(curtab))
-- move the current win to a different tabpage
api.nvim_win_set_config(win, {
split = 'right',
win = api.nvim_tabpage_get_win(tab2),
})
eq(curtab, api.nvim_get_current_tabpage())
-- win should have moved to tab2
eq(tab2, api.nvim_win_get_tabpage(win))
-- tp_curwin of tab2 should not have changed
eq(tab2_win, api.nvim_tabpage_get_win(tab2))
-- win lists should be correct
eq({ tab2_win, win }, api.nvim_tabpage_list_wins(tab2))
eq({ neighbor }, api.nvim_tabpage_list_wins(curtab))
-- current win should have moved to neighboring win
eq(neighbor, api.nvim_tabpage_get_win(curtab))
end)
it('splits windows in non-current tabpage', function()
local curtab = api.nvim_get_current_tabpage()
command('tabnew')
local tabnr = api.nvim_get_current_tabpage()
command('tabprev') -- return to the initial tab
local win = api.nvim_open_win(0, false, {
vertical = false,
win = api.nvim_tabpage_get_win(tabnr),
})
eq(tabnr, api.nvim_win_get_tabpage(win))
-- since enter = false, the current tabpage should not change
eq(curtab, api.nvim_get_current_tabpage())
end)
it('moves the current split window', function()
local initial_win = api.nvim_get_current_win()
local win = api.nvim_open_win(0, true, {
vertical = true,
})
local win2 = api.nvim_open_win(0, true, {
vertical = true,
})
api.nvim_set_current_win(win)
eq({
'row',
{
{ 'leaf', win2 },
{ 'leaf', win },
{ 'leaf', initial_win },
},
}, fn.winlayout())
api.nvim_win_set_config(0, {
vertical = false,
win = 0,
})
eq(win, api.nvim_get_current_win())
eq({
'col',
{
{ 'leaf', win },
{
'row',
{
{ 'leaf', win2 },
{ 'leaf', initial_win },
},
},
},
}, fn.winlayout())
api.nvim_set_current_win(win2)
local win3 = api.nvim_open_win(0, true, {
vertical = true,
})
eq(win3, api.nvim_get_current_win())
eq({
'col',
{
{ 'leaf', win },
{
'row',
{
{ 'leaf', win3 },
{ 'leaf', win2 },
{ 'leaf', initial_win },
},
},
},
}, fn.winlayout())
api.nvim_win_set_config(0, {
vertical = false,
win = 0,
})
eq(win3, api.nvim_get_current_win())
eq({
'col',
{
{ 'leaf', win },
{
'row',
{
{
'col',
{
{ 'leaf', win3 },
{ 'leaf', win2 },
},
},
{ 'leaf', initial_win },
},
},
},
}, fn.winlayout())
end)
end) end)
describe('get_config', function() describe('get_config', function()
@ -1292,6 +1723,154 @@ describe('API/win', function()
eq(title, cfg.title) eq(title, cfg.title)
eq(footer, cfg.footer) eq(footer, cfg.footer)
end) end)
it('includes split for normal windows', function()
local win = api.nvim_open_win(0, true, {
vertical = true,
win = -1,
})
eq('left', api.nvim_win_get_config(win).split)
api.nvim_win_set_config(win, {
vertical = false,
win = -1,
})
eq('above', api.nvim_win_get_config(win).split)
api.nvim_win_set_config(win, {
split = 'below',
win = -1,
})
eq('below', api.nvim_win_get_config(win).split)
end)
it('includes split when splitting with ex commands', function()
local win = api.nvim_get_current_win()
eq('left', api.nvim_win_get_config(win).split)
command('vsplit')
local win2 = api.nvim_get_current_win()
-- initial window now be marked as right split
-- since it was split with a vertical split
-- and 'splitright' is false by default
eq('right', api.nvim_win_get_config(win).split)
eq('left', api.nvim_win_get_config(win2).split)
api.nvim_set_option_value('splitbelow', true, {
scope = 'global',
})
api.nvim_win_close(win, true)
command('split')
local win3 = api.nvim_get_current_win()
eq('below', api.nvim_win_get_config(win3).split)
end)
it("includes the correct 'split' option in complex layouts", function()
local initial_win = api.nvim_get_current_win()
local win = api.nvim_open_win(0, false, {
split = 'right',
win = -1,
})
local win2 = api.nvim_open_win(0, false, {
split = 'below',
win = win,
})
api.nvim_win_set_config(win2, {
width = 50,
})
api.nvim_win_set_config(win, {
split = 'left',
win = -1,
})
local win3 = api.nvim_open_win(0, false, {
split = 'above',
win = -1,
})
local float = api.nvim_open_win(0, false, {
relative = 'editor',
width = 40,
height = 20,
col = 20,
row = 10,
})
api.nvim_win_set_config(float, {
split = 'right',
win = -1,
})
local layout = fn.winlayout()
eq({
'row',
{
{
'col',
{
{ 'leaf', win3 },
{
'row',
{
{ 'leaf', win },
{ 'leaf', initial_win },
{ 'leaf', win2 },
},
},
},
},
{
'leaf',
float,
},
},
}, layout)
eq('above', api.nvim_win_get_config(win3).split)
eq('left', api.nvim_win_get_config(win).split)
eq('left', api.nvim_win_get_config(initial_win).split)
eq('right', api.nvim_win_get_config(win2).split)
eq('right', api.nvim_win_get_config(float).split)
end)
end)
describe('set_config', function()
it('no crash with invalid title', function()
local win = api.nvim_open_win(0, true, {
width = 10,
height = 10,
relative = 'editor',
row = 10,
col = 10,
title = { { 'test' } },
border = 'single',
})
eq(
'title/footer cannot be an empty array',
pcall_err(api.nvim_win_set_config, win, { title = {} })
)
command('redraw!')
assert_alive()
end)
it('no crash with invalid footer', function()
local win = api.nvim_open_win(0, true, {
width = 10,
height = 10,
relative = 'editor',
row = 10,
col = 10,
footer = { { 'test' } },
border = 'single',
})
eq(
'title/footer cannot be an empty array',
pcall_err(api.nvim_win_set_config, win, { footer = {} })
)
command('redraw!')
assert_alive()
end)
end) end)
describe('set_config', function() describe('set_config', function()

View File

@ -104,14 +104,20 @@ describe('float window', function()
end) end)
it('open with WinNew autocmd', function() it('open with WinNew autocmd', function()
local res = exec_lua([[ local new_triggered_before_enter, new_curwin, win = unpack(exec_lua([[
local triggerd = false local enter_triggered = false
local new_triggered_before_enter = false
local new_curwin
local buf = vim.api.nvim_create_buf(true, true) local buf = vim.api.nvim_create_buf(true, true)
vim.api.nvim_create_autocmd('WinEnter', {
callback = function()
enter_triggered = true
end
})
vim.api.nvim_create_autocmd('WinNew', { vim.api.nvim_create_autocmd('WinNew', {
callback = function(opt) callback = function()
if opt.buf == buf then new_triggered_before_enter = not enter_triggered
triggerd = true new_curwin = vim.api.nvim_get_current_win()
end
end end
}) })
local opts = { local opts = {
@ -120,10 +126,11 @@ describe('float window', function()
width = 1, height = 1, width = 1, height = 1,
noautocmd = false, noautocmd = false,
} }
vim.api.nvim_open_win(buf, true, opts) local win = vim.api.nvim_open_win(buf, true, opts)
return triggerd return {new_triggered_before_enter, new_curwin, win}
]]) ]]))
eq(true, res) eq(true, new_triggered_before_enter)
eq(win, new_curwin)
end) end)
it('opened with correct height', function() it('opened with correct height', function()
@ -1095,7 +1102,7 @@ describe('float window', function()
local expected = {anchor='NW', col=5, external=false, focusable=true, height=2, relative='editor', row=3, width=20, zindex=60, hide=false} local expected = {anchor='NW', col=5, external=false, focusable=true, height=2, relative='editor', row=3, width=20, zindex=60, hide=false}
eq(expected, api.nvim_win_get_config(win)) eq(expected, api.nvim_win_get_config(win))
eq({relative='', external=false, focusable=true, hide=false}, api.nvim_win_get_config(0)) eq({external=false, focusable=true, hide=false, relative='',split="left",width=40,height=6}, api.nvim_win_get_config(0))
if multigrid then if multigrid then
api.nvim_win_set_config(win, {external=true, width=10, height=1}) api.nvim_win_set_config(win, {external=true, width=10, height=1})
@ -2878,27 +2885,31 @@ describe('float window', function()
it('API has proper error messages', function() it('API has proper error messages', function()
local buf = api.nvim_create_buf(false,false) local buf = api.nvim_create_buf(false,false)
eq("Invalid key: 'bork'", eq("Invalid key: 'bork'",
pcall_err(api.nvim_open_win,buf, false, {width=20,height=2,bork=true})) pcall_err(api.nvim_open_win, buf, false, {width=20,height=2,bork=true}))
eq("'win' key is only valid with relative='win'", eq("'win' key is only valid with relative='win' and relative=''",
pcall_err(api.nvim_open_win,buf, false, {width=20,height=2,relative='editor',row=0,col=0,win=0})) pcall_err(api.nvim_open_win, buf, false, {width=20,height=2,relative='editor',row=0,col=0,win=0}))
eq("floating windows cannot have 'vertical'",
pcall_err(api.nvim_open_win, buf, false, {width=20,height=2,relative='editor',row=0,col=0,vertical=true}))
eq("floating windows cannot have 'split'",
pcall_err(api.nvim_open_win, buf, false, {width=20,height=2,relative='editor',row=0,col=0,split="left"}))
eq("Only one of 'relative' and 'external' must be used", eq("Only one of 'relative' and 'external' must be used",
pcall_err(api.nvim_open_win,buf, false, {width=20,height=2,relative='editor',row=0,col=0,external=true})) pcall_err(api.nvim_open_win, buf, false, {width=20,height=2,relative='editor',row=0,col=0,external=true}))
eq("Invalid value of 'relative' key", eq("Invalid value of 'relative' key",
pcall_err(api.nvim_open_win,buf, false, {width=20,height=2,relative='shell',row=0,col=0})) pcall_err(api.nvim_open_win, buf, false, {width=20,height=2,relative='shell',row=0,col=0}))
eq("Invalid value of 'anchor' key", eq("Invalid value of 'anchor' key",
pcall_err(api.nvim_open_win,buf, false, {width=20,height=2,relative='editor',row=0,col=0,anchor='bottom'})) pcall_err(api.nvim_open_win, buf, false, {width=20,height=2,relative='editor',row=0,col=0,anchor='bottom'}))
eq("'relative' requires 'row'/'col' or 'bufpos'", eq("'relative' requires 'row'/'col' or 'bufpos'",
pcall_err(api.nvim_open_win,buf, false, {width=20,height=2,relative='editor'})) pcall_err(api.nvim_open_win, buf, false, {width=20,height=2,relative='editor'}))
eq("'width' key must be a positive Integer", eq("'width' key must be a positive Integer",
pcall_err(api.nvim_open_win,buf, false, {width=-1,height=2,relative='editor', row=0, col=0})) pcall_err(api.nvim_open_win, buf, false, {width=-1,height=2,relative='editor', row=0, col=0}))
eq("'height' key must be a positive Integer", eq("'height' key must be a positive Integer",
pcall_err(api.nvim_open_win,buf, false, {width=20,height=-1,relative='editor', row=0, col=0})) pcall_err(api.nvim_open_win, buf, false, {width=20,height=-1,relative='editor', row=0, col=0}))
eq("'height' key must be a positive Integer", eq("'height' key must be a positive Integer",
pcall_err(api.nvim_open_win,buf, false, {width=20,height=0,relative='editor', row=0, col=0})) pcall_err(api.nvim_open_win, buf, false, {width=20,height=0,relative='editor', row=0, col=0}))
eq("Must specify 'width'", eq("Must specify 'width'",
pcall_err(api.nvim_open_win,buf, false, {relative='editor', row=0, col=0})) pcall_err(api.nvim_open_win, buf, false, {relative='editor', row=0, col=0}))
eq("Must specify 'height'", eq("Must specify 'height'",
pcall_err(api.nvim_open_win,buf, false, {relative='editor', row=0, col=0, width=2})) pcall_err(api.nvim_open_win, buf, false, {relative='editor', row=0, col=0, width=2}))
end) end)
it('can be placed relative window or cursor', function() it('can be placed relative window or cursor', function()