aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWill Hopkins <willothyh@gmail.com>2024-01-31 19:43:35 -0800
committerGitHub <noreply@github.com>2024-02-01 11:43:35 +0800
commit6bba4becedaea5a330c0c9d9427fb495e8092dac (patch)
tree989ada1e26430843b6b855e8189afc29b2b69267
parent8fa67fdae539a13cf78621ab5a628da1fd745be2 (diff)
downloadrneovim-6bba4becedaea5a330c0c9d9427fb495e8092dac.tar.gz
rneovim-6bba4becedaea5a330c0c9d9427fb495e8092dac.tar.bz2
rneovim-6bba4becedaea5a330c0c9d9427fb495e8092dac.zip
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.
-rw-r--r--runtime/doc/api.txt38
-rw-r--r--runtime/doc/news.txt3
-rw-r--r--runtime/lua/vim/_meta/api.lua39
-rw-r--r--runtime/lua/vim/_meta/api_keysets.lua2
-rw-r--r--src/nvim/api/keysets_defs.h2
-rw-r--r--src/nvim/api/win_config.c454
-rw-r--r--src/nvim/buffer_defs.h10
-rw-r--r--src/nvim/window.c67
-rw-r--r--src/nvim/window.h19
-rw-r--r--test/functional/api/window_spec.lua579
-rw-r--r--test/functional/ui/float_spec.lua57
11 files changed, 1129 insertions, 141 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index 1831b78e9e..89d2860ad2 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -3093,18 +3093,28 @@ nvim_win_text_height({window}, {*opts}) *nvim_win_text_height()*
Win_Config Functions *api-win_config*
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
- 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 with
- multigrid GUIs, and are displayed as separate top-level 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 with multigrid GUIs, and are displayed as separate top-level
+ windows.
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
@@ -3127,6 +3137,13 @@ nvim_open_win({buffer}, {enter}, {*config}) *nvim_open_win()*
{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: ~
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.
• "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)
@@ -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
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".
Return: ~
Window handle, or 0 on error
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 3051b81189..36b42e28e3 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -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
|: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*
diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua
index bedc218626..77a29cb4c1 100644
--- a/runtime/lua/vim/_meta/api.lua
+++ b/runtime/lua/vim/_meta/api.lua
@@ -1483,15 +1483,24 @@ function vim.api.nvim_notify(msg, log_level, opts) end
--- @return integer
function vim.api.nvim_open_term(buffer, opts) end
---- Open a new window.
---- 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 with
---- multigrid GUIs, and are displayed as separate top-level windows.
+--- 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.
+--- 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 with multigrid GUIs, and are displayed as separate top-level
+--- windows.
--- 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
--- 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}})
--- ```
---
+--- 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 enter boolean Enter the window (make it the current window)
--- @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.
--- • "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)
@@ -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
--- 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".
--- @return integer
function vim.api.nvim_open_win(buffer, enter, config) end
diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua
index c9ef4e9b4f..1b6c6811a2 100644
--- a/runtime/lua/vim/_meta/api_keysets.lua
+++ b/runtime/lua/vim/_meta/api_keysets.lua
@@ -118,10 +118,12 @@ error('Cannot require a meta file')
--- @field height? integer
--- @field anchor? string
--- @field relative? string
+--- @field split? string
--- @field win? integer
--- @field bufpos? any[]
--- @field external? boolean
--- @field focusable? boolean
+--- @field vertical? boolean
--- @field zindex? integer
--- @field border? any
--- @field title? any
diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h
index 85896c1fa7..2f1b38d1e5 100644
--- a/src/nvim/api/keysets_defs.h
+++ b/src/nvim/api/keysets_defs.h
@@ -115,10 +115,12 @@ typedef struct {
Integer height;
String anchor;
String relative;
+ String split;
Window win;
Array bufpos;
Boolean external;
Boolean focusable;
+ Boolean vertical;
Integer zindex;
Object border;
Object title;
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
index 6df86683c1..5089367114 100644
--- a/src/nvim/api/win_config.c
+++ b/src/nvim/api/win_config.c
@@ -7,6 +7,7 @@
#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"
@@ -15,6 +16,8 @@
#include "nvim/decoration.h"
#include "nvim/decoration_defs.h"
#include "nvim/drawscreen.h"
+#include "nvim/eval/window.h"
+#include "nvim/extmark_defs.h"
#include "nvim/globals.h"
#include "nvim/grid_defs.h"
#include "nvim/highlight_group.h"
@@ -22,12 +25,15 @@
#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"
@@ -35,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
@@ -45,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
@@ -73,6 +88,15 @@
/// {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 enter Enter the window (make it the current window)
/// @param config Map defining the window configuration. Keys:
@@ -82,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
@@ -169,13 +194,14 @@
/// - 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
+ FUNC_API_SINCE(6) FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
buf_T *buf = find_buffer_by_handle(buffer, err);
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)) {
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) {
+ 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;
}
@@ -217,6 +288,36 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, E
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
/// (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) {
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
- FloatConfig fconfig = new_float ? FLOAT_CONFIG_INIT : win->w_float_config;
+ FloatConfig fconfig = win->w_float_config;
+
+ 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, !new_float, false, err)) {
+ 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 (!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 {
win_config_float(win, fconfig);
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
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;
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);
}
}
+ } 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
- ? float_relative_str[config->relative] : "");
- PUT(rv, "relative", CSTR_TO_OBJ(rel));
+ if (wp->w_floating && !config->external) {
+ PUT(rv, "relative", CSTR_TO_OBJ(float_relative_str[config->relative]));
+ } else {
+ PUT(rv, "relative", CSTR_TO_OBJ(""));
+ }
return rv;
}
@@ -419,10 +707,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;
}
@@ -529,7 +833,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, FloatConfig *fconfig, Error *err)
{
struct {
const char *name;
@@ -544,7 +848,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;
@@ -553,8 +857,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++) {
@@ -584,10 +887,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);
@@ -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[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;
@@ -634,8 +934,7 @@ 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);
}
}
@@ -643,17 +942,16 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
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) {
+ bool has_relative = false, relative_is_win = false, is_split = false;
+ if (HAS_KEY_X(config, relative) && !striequal(config->relative.data, "")) {
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;
}
@@ -663,6 +961,32 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
relative_is_win = true;
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)) {
@@ -673,7 +997,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;
}
@@ -681,7 +1005,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;
}
@@ -689,7 +1013,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 {
@@ -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");
return false;
}
- } else if (!reconf) {
+ } else if (!reconf && !is_split) {
api_set_error(err, kErrorTypeValidation, "Must specify 'width'");
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");
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;
}
}
@@ -753,23 +1078,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 {
@@ -779,6 +1101,10 @@ 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");
@@ -802,6 +1128,10 @@ 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");
@@ -825,6 +1155,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;
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 24f97dd92c..ea014c3918 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -889,6 +889,14 @@ typedef enum {
kFloatRelativeMouse = 3,
} 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 {
kWinStyleUnused = 0,
kWinStyleMinimal, /// Minimal UI: no number column, eob markers, etc
@@ -914,6 +922,7 @@ typedef struct {
FloatRelative relative;
bool external;
bool focusable;
+ WinSplit split;
int zindex;
WinStyle style;
bool border;
@@ -939,6 +948,7 @@ typedef struct {
.row = 0, .col = 0, .anchor = 0, \
.relative = 0, .external = false, \
.focusable = true, \
+ .split = 0, \
.zindex = kZIndexFloatDefault, \
.style = kWinStyleUnused, \
.noautocmd = false, \
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 66169bcb74..d07ae22c31 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -713,7 +713,7 @@ void win_set_buf(win_T *win, buf_T *buf, bool noautocmd, Error *err)
}
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,
kErrorTypeException,
"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.
validate_cursor();
- restore_win_noblock(&switchwin, false);
+ restore_win_noblock(&switchwin, true);
if (noautocmd) {
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_BOT: open window at the bottom-right of the screen (quickfix window).
// 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
int win_split(int size, int flags)
@@ -956,20 +957,20 @@ int win_split(int size, int flags)
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 not NULL: insert this window at the far
/// top/left/right/bottom.
-/// @return FAIL for failure, OK otherwise
-int win_split_ins(int size, int flags, win_T *new_wp, int dir)
+/// @return NULL for failure, or pointer to new window
+win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir)
{
win_T *wp = new_wp;
// aucmd_win[] should always remain floating
if (new_wp != NULL && is_aucmd_win(new_wp)) {
- return FAIL;
+ return NULL;
}
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 new_size = size;
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
if (one_nonfloat() && p_ls == 1 && oldwin->w_status_height == 0) {
if (oldwin->w_height <= p_wmh && new_in_layout) {
emsg(_(e_noroom));
- return FAIL;
+ return NULL;
}
need_status = STATUS_HEIGHT;
}
bool do_equal = false;
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;
- if (flags & WSP_VERT) {
+ if (vertical) {
// Check if we are able to split the current window and compute its
// width.
// 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 available;
- if (flags & (WSP_BOT | WSP_TOP)) {
+ if (toplevel) {
minwidth = frame_minwidth(topframe, NOWIN);
available = topframe->fr_width;
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) {
emsg(_(e_noroom));
- return FAIL;
+ return NULL;
}
if (new_size == 0) {
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 available;
- if (flags & (WSP_BOT | WSP_TOP)) {
+ if (toplevel) {
minheight = frame_minheight(topframe, NOWIN) + need_status;
available = topframe->fr_height;
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) {
emsg(_(e_noroom));
- return FAIL;
+ return NULL;
}
oldwin_height = oldwin->w_height;
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_BELOW)
|| (!(flags & WSP_ABOVE)
- && ((flags & WSP_VERT) ? p_spr : p_sb)))) {
+ && (vertical ? p_spr : p_sb)))) {
// new window below/right of current one
if (new_wp == NULL) {
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 (wp == NULL) {
- return FAIL;
+ return NULL;
}
new_frame(wp);
@@ -1218,9 +1221,9 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
frame_T *curfrp;
// Reorganise the tree of frames to insert the new window.
- if (flags & (WSP_TOP | WSP_BOT)) {
- if ((topframe->fr_layout == FR_COL && (flags & WSP_VERT) == 0)
- || (topframe->fr_layout == FR_ROW && (flags & WSP_VERT) != 0)) {
+ if (toplevel) {
+ if ((topframe->fr_layout == FR_COL && !vertical)
+ || (topframe->fr_layout == FR_ROW && vertical)) {
curfrp = topframe->fr_child;
if (flags & WSP_BOT) {
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;
} else if (flags & WSP_ABOVE) {
before = true;
- } else if (flags & WSP_VERT) {
+ } else if (vertical) {
before = !p_spr;
} else {
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;
- if (flags & WSP_VERT) {
+ if (vertical) {
wp->w_p_scr = curwin->w_p_scr;
if (need_status) {
win_new_height(oldwin, oldwin->w_height - 1);
oldwin->w_status_height = need_status;
}
- if (flags & (WSP_TOP | WSP_BOT)) {
+ if (toplevel) {
// set height and row of new window to full height
wp->w_winrow = tabline_height();
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;
oldwin->w_vsep_width = 1;
}
- if (flags & (WSP_TOP | WSP_BOT)) {
+ if (toplevel) {
if (flags & WSP_BOT) {
frame_add_vsep(curfrp);
}
@@ -1338,7 +1341,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
} else {
const bool is_stl_global = global_stl_height() > 0;
// width and column of new window is same as current window
- if (flags & (WSP_TOP | WSP_BOT)) {
+ if (toplevel) {
wp->w_wincol = 0;
win_new_width(wp, Columns);
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;
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;
if (is_stl_global) {
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);
}
- if (flags & (WSP_TOP | WSP_BOT)) {
+ if (toplevel) {
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.
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)) {
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
- win_enter_ext(wp, WEE_TRIGGER_NEW_AUTOCMDS | WEE_TRIGGER_ENTER_AUTOCMDS
- | WEE_TRIGGER_LEAVE_AUTOCMDS);
- if (flags & WSP_VERT) {
+ if (!(flags & WSP_NOENTER)) {
+ // make the new window the current window
+ win_enter_ext(wp, WEE_TRIGGER_NEW_AUTOCMDS | WEE_TRIGGER_ENTER_AUTOCMDS
+ | WEE_TRIGGER_LEAVE_AUTOCMDS);
+ }
+ if (vertical) {
p_wiw = i;
} else {
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;
}
- return OK;
+ return wp;
}
// Initialize window "newp" from window "oldp".
diff --git a/src/nvim/window.h b/src/nvim/window.h
index 3c88965324..d20b799e20 100644
--- a/src/nvim/window.h
+++ b/src/nvim/window.h
@@ -21,15 +21,16 @@ enum {
/// arguments for win_split()
enum {
- WSP_ROOM = 0x01, ///< require enough room
- WSP_VERT = 0x02, ///< split/equalize vertically
- WSP_HOR = 0x04, ///< equalize horizontally
- WSP_TOP = 0x08, ///< window at top-left of shell
- WSP_BOT = 0x10, ///< window at bottom-right of shell
- WSP_HELP = 0x20, ///< creating the help window
- WSP_BELOW = 0x40, ///< put new window below/right
- WSP_ABOVE = 0x80, ///< put new window above/left
- WSP_NEWLOC = 0x100, ///< don't copy location list
+ WSP_ROOM = 0x01, ///< require enough room
+ WSP_VERT = 0x02, ///< split/equalize vertically
+ WSP_HOR = 0x04, ///< equalize horizontally
+ WSP_TOP = 0x08, ///< window at top-left of shell
+ WSP_BOT = 0x10, ///< window at bottom-right of shell
+ WSP_HELP = 0x20, ///< creating the help window
+ WSP_BELOW = 0x40, ///< put new window below/right
+ WSP_ABOVE = 0x80, ///< put new window above/left
+ WSP_NEWLOC = 0x100, ///< don't copy location list
+ WSP_NOENTER = 0x200, ///< don't enter the new window
};
enum {
diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua
index aadf59eb5a..097a546ef2 100644
--- a/test/functional/api/window_spec.lua
+++ b/test/functional/api/window_spec.lua
@@ -1232,6 +1232,437 @@ describe('API/win', function()
)
eq(wins_before, api.nvim_list_wins())
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)
describe('get_config', function()
@@ -1292,6 +1723,154 @@ describe('API/win', function()
eq(title, cfg.title)
eq(footer, cfg.footer)
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)
describe('set_config', function()
diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua
index b05e7aceda..397c1f6132 100644
--- a/test/functional/ui/float_spec.lua
+++ b/test/functional/ui/float_spec.lua
@@ -104,14 +104,20 @@ describe('float window', function()
end)
it('open with WinNew autocmd', function()
- local res = exec_lua([[
- local triggerd = false
+ local new_triggered_before_enter, new_curwin, win = unpack(exec_lua([[
+ local enter_triggered = false
+ local new_triggered_before_enter = false
+ local new_curwin
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', {
- callback = function(opt)
- if opt.buf == buf then
- triggerd = true
- end
+ callback = function()
+ new_triggered_before_enter = not enter_triggered
+ new_curwin = vim.api.nvim_get_current_win()
end
})
local opts = {
@@ -120,10 +126,11 @@ describe('float window', function()
width = 1, height = 1,
noautocmd = false,
}
- vim.api.nvim_open_win(buf, true, opts)
- return triggerd
- ]])
- eq(true, res)
+ local win = vim.api.nvim_open_win(buf, true, opts)
+ return {new_triggered_before_enter, new_curwin, win}
+ ]]))
+ eq(true, new_triggered_before_enter)
+ eq(win, new_curwin)
end)
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}
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
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()
local buf = api.nvim_create_buf(false,false)
eq("Invalid key: 'bork'",
- pcall_err(api.nvim_open_win,buf, false, {width=20,height=2,bork=true}))
- eq("'win' key is only valid with relative='win'",
- 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,bork=true}))
+ 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}))
+ 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",
- 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",
- 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",
- 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'",
- 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",
- 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",
- 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",
- 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'",
- 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'",
- 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)
it('can be placed relative window or cursor', function()