aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/api.txt2
-rw-r--r--runtime/doc/develop.txt2
-rw-r--r--src/nvim/api/deprecated.c14
-rw-r--r--src/nvim/api/private/dispatch.c1
-rw-r--r--src/nvim/api/private/helpers.c355
-rw-r--r--src/nvim/api/ui.c3
-rw-r--r--src/nvim/api/vim.c154
-rw-r--r--src/nvim/api/win_config.c639
-rw-r--r--src/nvim/api/win_config.h11
-rw-r--r--src/nvim/api/window.c111
-rw-r--r--src/nvim/generators/gen_api_dispatch.lua14
-rw-r--r--src/nvim/mouse.c7
-rw-r--r--test/functional/ui/float_spec.lua127
-rw-r--r--test/functional/ui/mouse_spec.lua63
14 files changed, 873 insertions, 630 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index df345e4981..a8b4bc7dd2 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -1251,7 +1251,7 @@ nvim_open_term({buffer}, {opts}) *nvim_open_term()*
For instance, for a floating display, first create an empty
buffer using |nvim_create_buf()|, then display it using
|nvim_open_win()|, and then call this function. Then
- |nvim_chan_send()| cal be called immediately to process
+ |nvim_chan_send()| can be called immediately to process
sequences in a virtual terminal having the intended size.
Parameters: ~
diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt
index 14f35acce3..7127c74134 100644
--- a/runtime/doc/develop.txt
+++ b/runtime/doc/develop.txt
@@ -166,7 +166,7 @@ Docstring format:
- Use `<pre>` for code samples.
Example: the help for |nvim_open_win()| is generated from a docstring defined
-in src/nvim/api/vim.c like this: >
+in src/nvim/api/win_config.c like this: >
/// Opens a new window.
/// ...
diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c
index 332fc0ba96..907d09e5b7 100644
--- a/src/nvim/api/deprecated.c
+++ b/src/nvim/api/deprecated.c
@@ -165,6 +165,7 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A
/// @param lines Array of lines
/// @param[out] err Error details, if any
void buffer_insert(Buffer buffer, Integer lnum, ArrayOf(String) lines, Error *err)
+ FUNC_API_DEPRECATED_SINCE(1)
{
// "lnum" will be the index of the line after inserting,
// no matter if it is negative or not
@@ -184,6 +185,7 @@ void buffer_insert(Buffer buffer, Integer lnum, ArrayOf(String) lines, Error *er
/// @param[out] err Error details, if any
/// @return Line string
String buffer_get_line(Buffer buffer, Integer index, Error *err)
+ FUNC_API_DEPRECATED_SINCE(1)
{
String rv = { .size = 0 };
@@ -212,6 +214,7 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)
/// @param line Contents of the new line
/// @param[out] err Error details, if any
void buffer_set_line(Buffer buffer, Integer index, String line, Error *err)
+ FUNC_API_DEPRECATED_SINCE(1)
{
Object l = STRING_OBJ(line);
Array array = { .items = &l, .size = 1 };
@@ -230,6 +233,7 @@ void buffer_set_line(Buffer buffer, Integer index, String line, Error *err)
/// @param index line index
/// @param[out] err Error details, if any
void buffer_del_line(Buffer buffer, Integer index, Error *err)
+ FUNC_API_DEPRECATED_SINCE(1)
{
Array array = ARRAY_DICT_INIT;
index = convert_index(index);
@@ -255,6 +259,7 @@ ArrayOf(String) buffer_get_line_slice(Buffer buffer,
Boolean include_start,
Boolean include_end,
Error *err)
+ FUNC_API_DEPRECATED_SINCE(1)
{
start = convert_index(start) + !include_start;
end = convert_index(end) + include_end;
@@ -278,6 +283,7 @@ ArrayOf(String) buffer_get_line_slice(Buffer buffer,
/// @param[out] err Error details, if any
void buffer_set_line_slice(Buffer buffer, Integer start, Integer end, Boolean include_start,
Boolean include_end, ArrayOf(String) replacement, Error *err)
+ FUNC_API_DEPRECATED_SINCE(1)
{
start = convert_index(start) + !include_start;
end = convert_index(end) + include_end;
@@ -298,6 +304,7 @@ void buffer_set_line_slice(Buffer buffer, Integer start, Integer end, Boolean in
/// @warning It may return nil if there was no previous value
/// or if previous value was `v:null`.
Object buffer_set_var(Buffer buffer, String name, Object value, Error *err)
+ FUNC_API_DEPRECATED_SINCE(1)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -317,6 +324,7 @@ Object buffer_set_var(Buffer buffer, String name, Object value, Error *err)
/// @param[out] err Error details, if any
/// @return Old value
Object buffer_del_var(Buffer buffer, String name, Error *err)
+ FUNC_API_DEPRECATED_SINCE(1)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -340,6 +348,7 @@ Object buffer_del_var(Buffer buffer, String name, Error *err)
/// @warning It may return nil if there was no previous value
/// or if previous value was `v:null`.
Object window_set_var(Window window, String name, Object value, Error *err)
+ FUNC_API_DEPRECATED_SINCE(1)
{
win_T *win = find_window_by_handle(window, err);
@@ -359,6 +368,7 @@ Object window_set_var(Window window, String name, Object value, Error *err)
/// @param[out] err Error details, if any
/// @return Old value
Object window_del_var(Window window, String name, Error *err)
+ FUNC_API_DEPRECATED_SINCE(1)
{
win_T *win = find_window_by_handle(window, err);
@@ -382,6 +392,7 @@ Object window_del_var(Window window, String name, Error *err)
/// @warning It may return nil if there was no previous value
/// or if previous value was `v:null`.
Object tabpage_set_var(Tabpage tabpage, String name, Object value, Error *err)
+ FUNC_API_DEPRECATED_SINCE(1)
{
tabpage_T *tab = find_tab_by_handle(tabpage, err);
@@ -401,6 +412,7 @@ Object tabpage_set_var(Tabpage tabpage, String name, Object value, Error *err)
/// @param[out] err Error details, if any
/// @return Old value
Object tabpage_del_var(Tabpage tabpage, String name, Error *err)
+ FUNC_API_DEPRECATED_SINCE(1)
{
tabpage_T *tab = find_tab_by_handle(tabpage, err);
@@ -417,6 +429,7 @@ Object tabpage_del_var(Tabpage tabpage, String name, Error *err)
/// OR if previous value was `v:null`.
/// @return Old value or nil if there was no previous value.
Object vim_set_var(String name, Object value, Error *err)
+ FUNC_API_DEPRECATED_SINCE(1)
{
return dict_set_var(&globvardict, name, value, false, true, err);
}
@@ -424,6 +437,7 @@ Object vim_set_var(String name, Object value, Error *err)
/// @deprecated
/// @see nvim_del_var
Object vim_del_var(String name, Error *err)
+ FUNC_API_DEPRECATED_SINCE(1)
{
return dict_set_var(&globvardict, name, NIL, true, true, err);
}
diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c
index 5e93ccce17..ee0cdc4c07 100644
--- a/src/nvim/api/private/dispatch.c
+++ b/src/nvim/api/private/dispatch.c
@@ -15,6 +15,7 @@
#include "nvim/api/ui.h"
#include "nvim/api/vim.h"
#include "nvim/api/window.h"
+#include "nvim/api/win_config.h"
#include "nvim/log.h"
#include "nvim/map.h"
#include "nvim/msgpack_rpc/helpers.h"
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 8bd76b0917..7b70c183c3 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1648,361 +1648,6 @@ const char *describe_ns(NS ns_id)
return "(UNKNOWN PLUGIN)";
}
-static bool parse_float_anchor(String anchor, FloatAnchor *out)
-{
- if (anchor.size == 0) {
- *out = (FloatAnchor)0;
- }
- char *str = anchor.data;
- if (striequal(str, "NW")) {
- *out = 0; // NW is the default
- } else if (striequal(str, "NE")) {
- *out = kFloatAnchorEast;
- } else if (striequal(str, "SW")) {
- *out = kFloatAnchorSouth;
- } else if (striequal(str, "SE")) {
- *out = kFloatAnchorSouth | kFloatAnchorEast;
- } else {
- return false;
- }
- return true;
-}
-
-static bool parse_float_relative(String relative, FloatRelative *out)
-{
- char *str = relative.data;
- if (striequal(str, "editor")) {
- *out = kFloatRelativeEditor;
- } else if (striequal(str, "win")) {
- *out = kFloatRelativeWindow;
- } else if (striequal(str, "cursor")) {
- *out = kFloatRelativeCursor;
- } else {
- return false;
- }
- return true;
-}
-
-static bool parse_float_bufpos(Array bufpos, lpos_T *out)
-{
- if (bufpos.size != 2
- || bufpos.items[0].type != kObjectTypeInteger
- || bufpos.items[1].type != kObjectTypeInteger) {
- return false;
- }
- out->lnum = bufpos.items[0].data.integer;
- out->col = (colnr_T)bufpos.items[1].data.integer;
- return true;
-}
-
-static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
-{
- struct {
- const char *name;
- schar_T chars[8];
- bool shadow_color;
- } defaults[] = {
- { "double", { "╔", "═", "╗", "║", "╝", "═", "╚", "║" }, false },
- { "single", { "┌", "─", "┐", "│", "┘", "─", "└", "│" }, false },
- { "shadow", { "", "", " ", " ", " ", " ", " ", "" }, true },
- { "rounded", { "╭", "─", "╮", "│", "╯", "─", "╰", "│" }, false },
- { "solid", { " ", " ", " ", " ", " ", " ", " ", " " }, false },
- { NULL, { { NUL } }, false },
- };
-
- schar_T *chars = fconfig->border_chars;
- int *hl_ids = fconfig->border_hl_ids;
-
- fconfig->border = true;
-
- if (style.type == kObjectTypeArray) {
- 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");
- return;
- }
- for (size_t i = 0; i < size; i++) {
- Object iytem = arr.items[i];
- String string;
- int hl_id = 0;
- if (iytem.type == kObjectTypeArray) {
- Array iarr = iytem.data.array;
- if (!iarr.size || iarr.size > 2) {
- api_set_error(err, kErrorTypeValidation, "invalid border char");
- return;
- }
- if (iarr.items[0].type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation, "invalid border char");
- return;
- }
- string = iarr.items[0].data.string;
- if (iarr.size == 2) {
- hl_id = object_to_hl_id(iarr.items[1], "border char highlight", err);
- if (ERROR_SET(err)) {
- return;
- }
- }
- } else if (iytem.type == kObjectTypeString) {
- string = iytem.data.string;
- } else {
- api_set_error(err, kErrorTypeValidation, "invalid border char");
- return;
- }
- if (string.size
- && mb_string2cells_len((char_u *)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);
- if (len) {
- memcpy(chars[i], string.data, len);
- }
- chars[i][len] = NUL;
- hl_ids[i] = hl_id;
- }
- while (size < 8) {
- memcpy(chars+size, chars, sizeof(*chars) * size);
- memcpy(hl_ids+size, hl_ids, sizeof(*hl_ids) * size);
- size <<= 1;
- }
- if ((chars[7][0] && chars[1][0] && !chars[0][0])
- || (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");
- }
- } else if (style.type == kObjectTypeString) {
- String str = style.data.string;
- if (str.size == 0 || strequal(str.data, "none")) {
- fconfig->border = false;
- return;
- }
- for (size_t i = 0; defaults[i].name; i++) {
- if (strequal(str.data, defaults[i].name)) {
- memcpy(chars, defaults[i].chars, sizeof(defaults[i].chars));
- memset(hl_ids, 0, 8 * sizeof(*hl_ids));
- if (defaults[i].shadow_color) {
- int hl_blend = SYN_GROUP_STATIC("FloatShadow");
- int hl_through = SYN_GROUP_STATIC("FloatShadowThrough");
- hl_ids[2] = hl_through;
- hl_ids[3] = hl_blend;
- hl_ids[4] = hl_blend;
- hl_ids[5] = hl_blend;
- hl_ids[6] = hl_through;
- }
- return;
- }
- }
- api_set_error(err, kErrorTypeValidation,
- "invalid border style \"%s\"", str.data);
- }
-}
-
-bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, bool reconf, bool new_win,
- Error *err)
-{
- bool has_relative = false, relative_is_win = false;
- if (config->relative.type == kObjectTypeString) {
- // ignore empty string, to match nvim_win_get_config
- if (config->relative.data.string.size > 0) {
- if (!parse_float_relative(config->relative.data.string, &fconfig->relative)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value of 'relative' key");
- return false;
- }
-
- if (!(HAS_KEY(config->row) && HAS_KEY(config->col)) && !HAS_KEY(config->bufpos)) {
- api_set_error(err, kErrorTypeValidation,
- "'relative' requires 'row'/'col' or 'bufpos'");
- return false;
- }
-
- has_relative = true;
- fconfig->external = false;
- if (fconfig->relative == kFloatRelativeWindow) {
- relative_is_win = true;
- fconfig->bufpos.lnum = -1;
- }
- }
- } else if (HAS_KEY(config->relative)) {
- api_set_error(err, kErrorTypeValidation, "'relative' key must be String");
- return false;
- }
-
- if (config->anchor.type == kObjectTypeString) {
- if (!parse_float_anchor(config->anchor.data.string, &fconfig->anchor)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value of 'anchor' key");
- return false;
- }
- } else if (HAS_KEY(config->anchor)) {
- api_set_error(err, kErrorTypeValidation, "'anchor' key must be String");
- return false;
- }
-
- if (HAS_KEY(config->row)) {
- if (!has_relative) {
- api_set_error(err, kErrorTypeValidation, "non-float cannot have 'row'");
- return false;
- } else if (config->row.type == kObjectTypeInteger) {
- fconfig->row = (double)config->row.data.integer;
- } else if (config->row.type == kObjectTypeFloat) {
- fconfig->row = config->row.data.floating;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "'row' key must be Integer or Float");
- return false;
- }
- }
-
- if (HAS_KEY(config->col)) {
- if (!has_relative) {
- api_set_error(err, kErrorTypeValidation, "non-float cannot have 'col'");
- return false;
- } else if (config->col.type == kObjectTypeInteger) {
- fconfig->col = (double)config->col.data.integer;
- } else if (config->col.type == kObjectTypeFloat) {
- fconfig->col = config->col.data.floating;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "'col' key must be Integer or Float");
- return false;
- }
- }
-
- if (HAS_KEY(config->bufpos)) {
- if (!has_relative) {
- api_set_error(err, kErrorTypeValidation, "non-float cannot have 'bufpos'");
- return false;
- } else if (config->bufpos.type == kObjectTypeArray) {
- if (!parse_float_bufpos(config->bufpos.data.array, &fconfig->bufpos)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value of 'bufpos' key");
- return false;
- }
-
- if (!HAS_KEY(config->row)) {
- fconfig->row = (fconfig->anchor & kFloatAnchorSouth) ? 0 : 1;
- }
- if (!HAS_KEY(config->col)) {
- fconfig->col = 0;
- }
- } else {
- api_set_error(err, kErrorTypeValidation, "'bufpos' key must be Array");
- return false;
- }
- }
-
- if (config->width.type == kObjectTypeInteger && config->width.data.integer > 0) {
- fconfig->width = (int)config->width.data.integer;
- } else if (HAS_KEY(config->width)) {
- api_set_error(err, kErrorTypeValidation, "'width' key must be a positive Integer");
- return false;
- } else if (!reconf) {
- api_set_error(err, kErrorTypeValidation, "Must specify 'width'");
- return false;
- }
-
- if (config->height.type == kObjectTypeInteger && config->height.data.integer > 0) {
- fconfig->height = (int)config->height.data.integer;
- } else if (HAS_KEY(config->height)) {
- api_set_error(err, kErrorTypeValidation, "'height' key must be a positive Integer");
- return false;
- } else if (!reconf) {
- api_set_error(err, kErrorTypeValidation, "Must specify 'height'");
- return false;
- }
-
- if (relative_is_win) {
- fconfig->window = curwin->handle;
- if (config->win.type == kObjectTypeInteger || config->win.type == kObjectTypeWindow) {
- if (config->win.data.integer > 0) {
- fconfig->window = (Window)config->win.data.integer;
- }
- } else if (HAS_KEY(config->win)) {
- api_set_error(err, kErrorTypeValidation, "'win' key must be Integer or Window");
- return false;
- }
- } else {
- if (HAS_KEY(config->win)) {
- api_set_error(err, kErrorTypeValidation, "'win' key is only valid with relative='win'");
- return false;
- }
- }
-
- if (HAS_KEY(config->external)) {
- fconfig->external = api_object_to_bool(config->external, "'external' key", false, err);
- if (ERROR_SET(err)) {
- return false;
- }
- if (has_relative && fconfig->external) {
- api_set_error(err, kErrorTypeValidation,
- "Only one of 'relative' and 'external' must be used");
- return false;
- }
- if (fconfig->external && !ui_has(kUIMultigrid)) {
- 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(config->focusable)) {
- fconfig->focusable = api_object_to_bool(config->focusable, "'focusable' key", false, err);
- if (ERROR_SET(err)) {
- return false;
- }
- }
-
- if (config->zindex.type == kObjectTypeInteger && config->zindex.data.integer > 0) {
- fconfig->zindex = (int)config->zindex.data.integer;
- } else if (HAS_KEY(config->zindex)) {
- api_set_error(err, kErrorTypeValidation, "'zindex' key must be a positive Integer");
- return false;
- }
-
- if (HAS_KEY(config->border)) {
- parse_border_style(config->border, fconfig, err);
- if (ERROR_SET(err)) {
- return false;
- }
- }
-
- if (config->style.type == kObjectTypeString) {
- if (config->style.data.string.data[0] == NUL) {
- fconfig->style = kWinStyleUnused;
- } else if (striequal(config->style.data.string.data, "minimal")) {
- fconfig->style = kWinStyleMinimal;
- } else {
- api_set_error(err, kErrorTypeValidation, "Invalid value of 'style' key");
- }
- } else if (HAS_KEY(config->style)) {
- api_set_error(err, kErrorTypeValidation, "'style' key must be String");
- return false;
- }
-
- if (HAS_KEY(config->noautocmd)) {
- if (!new_win) {
- api_set_error(err, kErrorTypeValidation, "Invalid key: 'noautocmd'");
- return false;
- }
- fconfig->noautocmd = api_object_to_bool(config->noautocmd, "'noautocmd' key", false, err);
- if (ERROR_SET(err)) {
- return false;
- }
- }
-
- return true;
-}
-
bool api_dict_to_keydict(void *rv, field_hash hashy, Dictionary dict, Error *err)
{
for (size_t i = 0; i < dict.size; i++) {
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index 9b200dcba2..d86aecc318 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -40,7 +40,6 @@ typedef struct {
static PMap(uint64_t) connected_uis = MAP_INIT;
void remote_ui_disconnect(uint64_t channel_id)
- FUNC_API_NOEXPORT
{
UI *ui = pmap_get(uint64_t)(&connected_uis, channel_id);
if (!ui) {
@@ -57,7 +56,6 @@ void remote_ui_disconnect(uint64_t channel_id)
/// Wait until ui has connected on stdio channel.
void remote_ui_wait_for_attach(void)
- FUNC_API_NOEXPORT
{
Channel *channel = find_channel(CHAN_STDIO);
if (!channel) {
@@ -172,6 +170,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
/// @deprecated
void ui_attach(uint64_t channel_id, Integer width, Integer height, Boolean enable_rgb, Error *err)
+ FUNC_API_DEPRECATED_SINCE(1)
{
Dictionary opts = ARRAY_DICT_INIT;
PUT(opts, "rgb", BOOLEAN_OBJ(enable_rgb));
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index f80d605d9a..45fca8b1c5 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -59,7 +59,6 @@
#endif
void api_vim_free_all_mem(void)
- FUNC_API_NOEXPORT
{
String name;
handle_T id;
@@ -264,7 +263,6 @@ void nvim__set_hl_ns(Integer ns_id, Error *err)
}
static void on_redraw_event(void **argv)
- FUNC_API_NOEXPORT
{
redraw_all_later(NOT_VALID);
}
@@ -1221,7 +1219,7 @@ fail:
/// buffer in a configured window before calling this. For instance, for a
/// floating display, first create an empty buffer using |nvim_create_buf()|,
/// then display it using |nvim_open_win()|, and then call this function.
-/// Then |nvim_chan_send()| cal be called immediately to process sequences
+/// Then |nvim_chan_send()| can be called immediately to process sequences
/// in a virtual terminal having the intended size.
///
/// @param buffer the buffer to use (expected to be empty)
@@ -1305,156 +1303,6 @@ void nvim_chan_send(Integer chan, String data, Error *err)
}
}
-/// 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.
-///
-/// 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.
-///
-/// 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
-/// (used by non-multigrid UIs) will always round down to nearest integer.
-///
-/// Out-of-bounds values, and configurations that make the float not fit inside
-/// the main editor, are allowed. The builtin implementation truncates values
-/// so floats are fully within the main screen grid. External GUIs
-/// could let floats hover outside of the main window like a tooltip, but
-/// this should not be used to specify arbitrary WM screen positions.
-///
-/// Example (Lua): window-relative float
-/// <pre>
-/// vim.api.nvim_open_win(0, false,
-/// {relative='win', row=3, col=3, width=12, height=3})
-/// </pre>
-///
-/// Example (Lua): buffer-relative float (travels as buffer is scrolled)
-/// <pre>
-/// vim.api.nvim_open_win(0, false,
-/// {relative='win', width=12, height=3, bufpos={100,10}})
-/// </pre>
-///
-/// @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:
-/// - `relative`: Sets the window layout to "floating", placed at (row,col)
-/// coordinates relative to:
-/// - "editor" The global editor grid
-/// - "win" Window given by the `win` field, or current window.
-/// - "cursor" Cursor position in current window.
-/// - `win`: |window-ID| for relative="win".
-/// - `anchor`: Decides which corner of the float to place at (row,col):
-/// - "NW" northwest (default)
-/// - "NE" northeast
-/// - "SW" southwest
-/// - "SE" southeast
-/// - `width`: Window width (in character cells). Minimum of 1.
-/// - `height`: Window height (in character cells). Minimum of 1.
-/// - `bufpos`: Places float relative to buffer text (only when
-/// relative="win"). Takes a tuple of zero-indexed [line, column].
-/// `row` and `col` if given are applied relative to this
-/// position, else they default to:
-/// - `row=1` and `col=0` if `anchor` is "NW" or "NE"
-/// - `row=0` and `col=0` if `anchor` is "SW" or "SE"
-/// (thus like a tooltip near the buffer text).
-/// - `row`: Row position in units of "screen cell height", may be fractional.
-/// - `col`: Column position in units of "screen cell width", may be
-/// fractional.
-/// - `focusable`: Enable focus by user actions (wincmds, mouse events).
-/// Defaults to true. Non-focusable windows can be entered by
-/// |nvim_set_current_win()|.
-/// - `external`: GUI should display the window as an external
-/// top-level window. Currently accepts no other positioning
-/// configuration together with this.
-/// - `zindex`: Stacking order. floats with higher `zindex` go on top on
-/// floats with lower indices. Must be larger than zero. The
-/// following screen elements have hard-coded z-indices:
-/// - 100: insert completion popupmenu
-/// - 200: message scrollback
-/// - 250: cmdline completion popupmenu (when wildoptions+=pum)
-/// The default value for floats are 50. In general, values below 100 are
-/// recommended, unless there is a good reason to overshadow builtin
-/// elements.
-/// - `style`: Configure the appearance of the window. Currently only takes
-/// one non-empty value:
-/// - "minimal" Nvim will display the window with many UI options
-/// disabled. This is useful when displaying a temporary
-/// float where the text should not be edited. Disables
-/// 'number', 'relativenumber', 'cursorline', 'cursorcolumn',
-/// 'foldcolumn', 'spell' and 'list' options. 'signcolumn'
-/// is changed to `auto` and 'colorcolumn' is cleared. The
-/// end-of-buffer region is hidden by setting `eob` flag of
-/// 'fillchars' to a space char, and clearing the
-/// |EndOfBuffer| region in 'winhighlight'.
-/// - `border`: Style of (optional) window border. This can either be a string
-/// or an array. The string values are
-/// - "none": No border (default).
-/// - "single": A single line box.
-/// - "double": A double line box.
-/// - "rounded": Like "single", but with rounded corners ("╭" etc.).
-/// - "solid": Adds padding by a single whitespace cell.
-/// - "shadow": A drop shadow effect by blending with the background.
-/// - If it is an array, it should have a length of eight or any divisor of
-/// eight. The array will specifify the eight chars building up the border
-/// in a clockwise fashion starting with the top-left corner. As an
-/// example, the double box style could be specified as
-/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ].
-/// If the number of chars are less than eight, they will be repeated. Thus
-/// an ASCII border could be specified as
-/// [ "/", "-", "\\", "|" ],
-/// or all chars the same as
-/// [ "x" ].
-/// An empty string can be used to turn off a specific border, for instance,
-/// [ "", "", "", ">", "", "", "", "<" ]
-/// will only make vertical borders but not horizontal ones.
-/// By default, `FloatBorder` highlight is used, which links to `VertSplit`
-/// when not defined. It could also be specified by character:
-/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ].
-/// - `noautocmd`: If true then no buffer-related autocommand events such as
-/// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from
-/// calling this function.
-///
-/// @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_CHECK_TEXTLOCK
-{
- FloatConfig fconfig = FLOAT_CONFIG_INIT;
- if (!parse_float_config(config, &fconfig, false, true, err)) {
- return 0;
- }
- win_T *wp = win_new_float(NULL, fconfig, err);
- if (!wp) {
- return 0;
- }
- if (enter) {
- win_enter(wp, false);
- }
- // autocmds in win_enter or win_set_buf below may close the window
- if (win_valid(wp) && buffer > 0) {
- win_set_buf(wp->handle, buffer, fconfig.noautocmd, err);
- }
- if (!win_valid(wp)) {
- api_set_error(err, kErrorTypeException, "Window was closed immediately");
- return 0;
- }
-
- if (fconfig.style == kWinStyleMinimal) {
- win_set_minimal_style(wp);
- didset_window_options(wp);
- }
- return wp->handle;
-}
-
/// Gets the current list of tabpage handles.
///
/// @return List of tabpage handles
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
new file mode 100644
index 0000000000..d82e7e8a03
--- /dev/null
+++ b/src/nvim/api/win_config.c
@@ -0,0 +1,639 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/api/win_config.h"
+#include "nvim/ascii.h"
+#include "nvim/option.h"
+#include "nvim/screen.h"
+#include "nvim/strings.h"
+#include "nvim/syntax.h"
+#include "nvim/ui.h"
+#include "nvim/window.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/win_config.c.generated.h"
+#endif
+
+
+/// 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.
+///
+/// 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.
+///
+/// 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
+/// (used by non-multigrid UIs) will always round down to nearest integer.
+///
+/// Out-of-bounds values, and configurations that make the float not fit inside
+/// the main editor, are allowed. The builtin implementation truncates values
+/// so floats are fully within the main screen grid. External GUIs
+/// could let floats hover outside of the main window like a tooltip, but
+/// this should not be used to specify arbitrary WM screen positions.
+///
+/// Example (Lua): window-relative float
+/// <pre>
+/// vim.api.nvim_open_win(0, false,
+/// {relative='win', row=3, col=3, width=12, height=3})
+/// </pre>
+///
+/// Example (Lua): buffer-relative float (travels as buffer is scrolled)
+/// <pre>
+/// vim.api.nvim_open_win(0, false,
+/// {relative='win', width=12, height=3, bufpos={100,10}})
+/// </pre>
+///
+/// @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:
+/// - `relative`: Sets the window layout to "floating", placed at (row,col)
+/// coordinates relative to:
+/// - "editor" The global editor grid
+/// - "win" Window given by the `win` field, or current window.
+/// - "cursor" Cursor position in current window.
+/// - `win`: |window-ID| for relative="win".
+/// - `anchor`: Decides which corner of the float to place at (row,col):
+/// - "NW" northwest (default)
+/// - "NE" northeast
+/// - "SW" southwest
+/// - "SE" southeast
+/// - `width`: Window width (in character cells). Minimum of 1.
+/// - `height`: Window height (in character cells). Minimum of 1.
+/// - `bufpos`: Places float relative to buffer text (only when
+/// relative="win"). Takes a tuple of zero-indexed [line, column].
+/// `row` and `col` if given are applied relative to this
+/// position, else they default to:
+/// - `row=1` and `col=0` if `anchor` is "NW" or "NE"
+/// - `row=0` and `col=0` if `anchor` is "SW" or "SE"
+/// (thus like a tooltip near the buffer text).
+/// - `row`: Row position in units of "screen cell height", may be fractional.
+/// - `col`: Column position in units of "screen cell width", may be
+/// fractional.
+/// - `focusable`: Enable focus by user actions (wincmds, mouse events).
+/// Defaults to true. Non-focusable windows can be entered by
+/// |nvim_set_current_win()|.
+/// - `external`: GUI should display the window as an external
+/// top-level window. Currently accepts no other positioning
+/// configuration together with this.
+/// - `zindex`: Stacking order. floats with higher `zindex` go on top on
+/// floats with lower indices. Must be larger than zero. The
+/// following screen elements have hard-coded z-indices:
+/// - 100: insert completion popupmenu
+/// - 200: message scrollback
+/// - 250: cmdline completion popupmenu (when wildoptions+=pum)
+/// The default value for floats are 50. In general, values below 100 are
+/// recommended, unless there is a good reason to overshadow builtin
+/// elements.
+/// - `style`: Configure the appearance of the window. Currently only takes
+/// one non-empty value:
+/// - "minimal" Nvim will display the window with many UI options
+/// disabled. This is useful when displaying a temporary
+/// float where the text should not be edited. Disables
+/// 'number', 'relativenumber', 'cursorline', 'cursorcolumn',
+/// 'foldcolumn', 'spell' and 'list' options. 'signcolumn'
+/// is changed to `auto` and 'colorcolumn' is cleared. The
+/// end-of-buffer region is hidden by setting `eob` flag of
+/// 'fillchars' to a space char, and clearing the
+/// |EndOfBuffer| region in 'winhighlight'.
+/// - `border`: Style of (optional) window border. This can either be a string
+/// or an array. The string values are
+/// - "none": No border (default).
+/// - "single": A single line box.
+/// - "double": A double line box.
+/// - "rounded": Like "single", but with rounded corners ("╭" etc.).
+/// - "solid": Adds padding by a single whitespace cell.
+/// - "shadow": A drop shadow effect by blending with the background.
+/// - If it is an array, it should have a length of eight or any divisor of
+/// eight. The array will specifify the eight chars building up the border
+/// in a clockwise fashion starting with the top-left corner. As an
+/// example, the double box style could be specified as
+/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ].
+/// If the number of chars are less than eight, they will be repeated. Thus
+/// an ASCII border could be specified as
+/// [ "/", "-", "\\", "|" ],
+/// or all chars the same as
+/// [ "x" ].
+/// An empty string can be used to turn off a specific border, for instance,
+/// [ "", "", "", ">", "", "", "", "<" ]
+/// will only make vertical borders but not horizontal ones.
+/// By default, `FloatBorder` highlight is used, which links to `VertSplit`
+/// when not defined. It could also be specified by character:
+/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ].
+/// - `noautocmd`: If true then no buffer-related autocommand events such as
+/// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from
+/// calling this function.
+///
+/// @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_CHECK_TEXTLOCK
+{
+ FloatConfig fconfig = FLOAT_CONFIG_INIT;
+ if (!parse_float_config(config, &fconfig, false, true, err)) {
+ return 0;
+ }
+ win_T *wp = win_new_float(NULL, fconfig, err);
+ if (!wp) {
+ return 0;
+ }
+ if (enter) {
+ win_enter(wp, false);
+ }
+ // autocmds in win_enter or win_set_buf below may close the window
+ if (win_valid(wp) && buffer > 0) {
+ win_set_buf(wp->handle, buffer, fconfig.noautocmd, err);
+ }
+ if (!win_valid(wp)) {
+ api_set_error(err, kErrorTypeException, "Window was closed immediately");
+ return 0;
+ }
+
+ if (fconfig.style == kWinStyleMinimal) {
+ win_set_minimal_style(wp);
+ didset_window_options(wp);
+ }
+ return wp->handle;
+}
+
+/// Configures window layout. Currently only for floating and external windows
+/// (including changing a split window to those layouts).
+///
+/// When reconfiguring a floating window, absent option keys will not be
+/// changed. `row`/`col` and `relative` must be reconfigured together.
+///
+/// @see |nvim_open_win()|
+///
+/// @param window Window handle, or 0 for current window
+/// @param config Map defining the window configuration,
+/// see |nvim_open_win()|
+/// @param[out] err Error details, if any
+void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err)
+ FUNC_API_SINCE(6)
+{
+ win_T *win = find_window_by_handle(window, err);
+ if (!win) {
+ return;
+ }
+ bool new_float = !win->w_floating;
+ // reuse old values, if not overridden
+ FloatConfig fconfig = new_float ? FLOAT_CONFIG_INIT : win->w_float_config;
+
+ if (!parse_float_config(config, &fconfig, !new_float, false, err)) {
+ return;
+ }
+ if (new_float) {
+ if (!win_new_float(win, fconfig, err)) {
+ return;
+ }
+ redraw_later(win, NOT_VALID);
+ } else {
+ win_config_float(win, fconfig);
+ win->w_pos_changed = true;
+ }
+ if (fconfig.style == kWinStyleMinimal) {
+ win_set_minimal_style(win);
+ didset_window_options(win);
+ }
+}
+
+/// Gets window configuration.
+///
+/// The returned value may be given to |nvim_open_win()|.
+///
+/// `relative` is empty for normal windows.
+///
+/// @param window Window handle, or 0 for current window
+/// @param[out] err Error details, if any
+/// @return Map defining the window configuration, see |nvim_open_win()|
+Dictionary nvim_win_get_config(Window window, Error *err)
+ FUNC_API_SINCE(6)
+{
+ Dictionary rv = ARRAY_DICT_INIT;
+
+ win_T *wp = find_window_by_handle(window, err);
+ if (!wp) {
+ return rv;
+ }
+
+ FloatConfig *config = &wp->w_float_config;
+
+ PUT(rv, "focusable", BOOLEAN_OBJ(config->focusable));
+ PUT(rv, "external", BOOLEAN_OBJ(config->external));
+
+ if (wp->w_floating) {
+ PUT(rv, "width", INTEGER_OBJ(config->width));
+ PUT(rv, "height", INTEGER_OBJ(config->height));
+ if (!config->external) {
+ if (config->relative == kFloatRelativeWindow) {
+ PUT(rv, "win", INTEGER_OBJ(config->window));
+ if (config->bufpos.lnum >= 0) {
+ Array pos = ARRAY_DICT_INIT;
+ ADD(pos, INTEGER_OBJ(config->bufpos.lnum));
+ ADD(pos, INTEGER_OBJ(config->bufpos.col));
+ PUT(rv, "bufpos", ARRAY_OBJ(pos));
+ }
+ }
+ PUT(rv, "anchor", STRING_OBJ(cstr_to_string(float_anchor_str[config->anchor])));
+ PUT(rv, "row", FLOAT_OBJ(config->row));
+ PUT(rv, "col", FLOAT_OBJ(config->col));
+ PUT(rv, "zindex", INTEGER_OBJ(config->zindex));
+ }
+ if (config->border) {
+ Array border = ARRAY_DICT_INIT;
+ for (size_t i = 0; i < 8; i++) {
+ Array tuple = ARRAY_DICT_INIT;
+
+ String s = cstrn_to_string((const char *)config->border_chars[i], sizeof(schar_T));
+
+ int hi_id = config->border_hl_ids[i];
+ char_u *hi_name = syn_id2name(hi_id);
+ if (hi_name[0]) {
+ ADD(tuple, STRING_OBJ(s));
+ ADD(tuple, STRING_OBJ(cstr_to_string((const char *)hi_name)));
+ ADD(border, ARRAY_OBJ(tuple));
+ } else {
+ ADD(border, STRING_OBJ(s));
+ }
+ }
+ PUT(rv, "border", ARRAY_OBJ(border));
+ }
+ }
+
+ const char *rel = (wp->w_floating && !config->external
+ ? float_relative_str[config->relative] : "");
+ PUT(rv, "relative", STRING_OBJ(cstr_to_string(rel)));
+
+ return rv;
+}
+
+static bool parse_float_anchor(String anchor, FloatAnchor *out)
+{
+ if (anchor.size == 0) {
+ *out = (FloatAnchor)0;
+ }
+ char *str = anchor.data;
+ if (striequal(str, "NW")) {
+ *out = 0; // NW is the default
+ } else if (striequal(str, "NE")) {
+ *out = kFloatAnchorEast;
+ } else if (striequal(str, "SW")) {
+ *out = kFloatAnchorSouth;
+ } else if (striequal(str, "SE")) {
+ *out = kFloatAnchorSouth | kFloatAnchorEast;
+ } else {
+ return false;
+ }
+ return true;
+}
+
+static bool parse_float_relative(String relative, FloatRelative *out)
+{
+ char *str = relative.data;
+ if (striequal(str, "editor")) {
+ *out = kFloatRelativeEditor;
+ } else if (striequal(str, "win")) {
+ *out = kFloatRelativeWindow;
+ } else if (striequal(str, "cursor")) {
+ *out = kFloatRelativeCursor;
+ } else {
+ return false;
+ }
+ return true;
+}
+
+static bool parse_float_bufpos(Array bufpos, lpos_T *out)
+{
+ if (bufpos.size != 2
+ || bufpos.items[0].type != kObjectTypeInteger
+ || bufpos.items[1].type != kObjectTypeInteger) {
+ return false;
+ }
+ out->lnum = bufpos.items[0].data.integer;
+ out->col = (colnr_T)bufpos.items[1].data.integer;
+ return true;
+}
+
+static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
+{
+ struct {
+ const char *name;
+ schar_T chars[8];
+ bool shadow_color;
+ } defaults[] = {
+ { "double", { "╔", "═", "╗", "║", "╝", "═", "╚", "║" }, false },
+ { "single", { "┌", "─", "┐", "│", "┘", "─", "└", "│" }, false },
+ { "shadow", { "", "", " ", " ", " ", " ", " ", "" }, true },
+ { "rounded", { "╭", "─", "╮", "│", "╯", "─", "╰", "│" }, false },
+ { "solid", { " ", " ", " ", " ", " ", " ", " ", " " }, false },
+ { NULL, { { NUL } }, false },
+ };
+
+ schar_T *chars = fconfig->border_chars;
+ int *hl_ids = fconfig->border_hl_ids;
+
+ fconfig->border = true;
+
+ if (style.type == kObjectTypeArray) {
+ 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");
+ return;
+ }
+ for (size_t i = 0; i < size; i++) {
+ Object iytem = arr.items[i];
+ String string;
+ int hl_id = 0;
+ if (iytem.type == kObjectTypeArray) {
+ Array iarr = iytem.data.array;
+ if (!iarr.size || iarr.size > 2) {
+ api_set_error(err, kErrorTypeValidation, "invalid border char");
+ return;
+ }
+ if (iarr.items[0].type != kObjectTypeString) {
+ api_set_error(err, kErrorTypeValidation, "invalid border char");
+ return;
+ }
+ string = iarr.items[0].data.string;
+ if (iarr.size == 2) {
+ hl_id = object_to_hl_id(iarr.items[1], "border char highlight", err);
+ if (ERROR_SET(err)) {
+ return;
+ }
+ }
+ } else if (iytem.type == kObjectTypeString) {
+ string = iytem.data.string;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "invalid border char");
+ return;
+ }
+ if (string.size
+ && mb_string2cells_len((char_u *)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);
+ if (len) {
+ memcpy(chars[i], string.data, len);
+ }
+ chars[i][len] = NUL;
+ hl_ids[i] = hl_id;
+ }
+ while (size < 8) {
+ memcpy(chars+size, chars, sizeof(*chars) * size);
+ memcpy(hl_ids+size, hl_ids, sizeof(*hl_ids) * size);
+ size <<= 1;
+ }
+ if ((chars[7][0] && chars[1][0] && !chars[0][0])
+ || (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");
+ }
+ } else if (style.type == kObjectTypeString) {
+ String str = style.data.string;
+ if (str.size == 0 || strequal(str.data, "none")) {
+ fconfig->border = false;
+ return;
+ }
+ for (size_t i = 0; defaults[i].name; i++) {
+ if (strequal(str.data, defaults[i].name)) {
+ memcpy(chars, defaults[i].chars, sizeof(defaults[i].chars));
+ memset(hl_ids, 0, 8 * sizeof(*hl_ids));
+ if (defaults[i].shadow_color) {
+ int hl_blend = SYN_GROUP_STATIC("FloatShadow");
+ int hl_through = SYN_GROUP_STATIC("FloatShadowThrough");
+ hl_ids[2] = hl_through;
+ hl_ids[3] = hl_blend;
+ hl_ids[4] = hl_blend;
+ hl_ids[5] = hl_blend;
+ hl_ids[6] = hl_through;
+ }
+ return;
+ }
+ }
+ api_set_error(err, kErrorTypeValidation,
+ "invalid border style \"%s\"", str.data);
+ }
+}
+
+static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, bool reconf,
+ bool new_win, Error *err)
+{
+ bool has_relative = false, relative_is_win = false;
+ if (config->relative.type == kObjectTypeString) {
+ // ignore empty string, to match nvim_win_get_config
+ if (config->relative.data.string.size > 0) {
+ if (!parse_float_relative(config->relative.data.string, &fconfig->relative)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value of 'relative' key");
+ return false;
+ }
+
+ if (!(HAS_KEY(config->row) && HAS_KEY(config->col)) && !HAS_KEY(config->bufpos)) {
+ api_set_error(err, kErrorTypeValidation,
+ "'relative' requires 'row'/'col' or 'bufpos'");
+ return false;
+ }
+
+ has_relative = true;
+ fconfig->external = false;
+ if (fconfig->relative == kFloatRelativeWindow) {
+ relative_is_win = true;
+ fconfig->bufpos.lnum = -1;
+ }
+ }
+ } else if (HAS_KEY(config->relative)) {
+ api_set_error(err, kErrorTypeValidation, "'relative' key must be String");
+ return false;
+ }
+
+ if (config->anchor.type == kObjectTypeString) {
+ if (!parse_float_anchor(config->anchor.data.string, &fconfig->anchor)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value of 'anchor' key");
+ return false;
+ }
+ } else if (HAS_KEY(config->anchor)) {
+ api_set_error(err, kErrorTypeValidation, "'anchor' key must be String");
+ return false;
+ }
+
+ if (HAS_KEY(config->row)) {
+ if (!has_relative) {
+ api_set_error(err, kErrorTypeValidation, "non-float cannot have 'row'");
+ return false;
+ } else if (config->row.type == kObjectTypeInteger) {
+ fconfig->row = (double)config->row.data.integer;
+ } else if (config->row.type == kObjectTypeFloat) {
+ fconfig->row = config->row.data.floating;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "'row' key must be Integer or Float");
+ return false;
+ }
+ }
+
+ if (HAS_KEY(config->col)) {
+ if (!has_relative) {
+ api_set_error(err, kErrorTypeValidation, "non-float cannot have 'col'");
+ return false;
+ } else if (config->col.type == kObjectTypeInteger) {
+ fconfig->col = (double)config->col.data.integer;
+ } else if (config->col.type == kObjectTypeFloat) {
+ fconfig->col = config->col.data.floating;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "'col' key must be Integer or Float");
+ return false;
+ }
+ }
+
+ if (HAS_KEY(config->bufpos)) {
+ if (!has_relative) {
+ api_set_error(err, kErrorTypeValidation, "non-float cannot have 'bufpos'");
+ return false;
+ } else if (config->bufpos.type == kObjectTypeArray) {
+ if (!parse_float_bufpos(config->bufpos.data.array, &fconfig->bufpos)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value of 'bufpos' key");
+ return false;
+ }
+
+ if (!HAS_KEY(config->row)) {
+ fconfig->row = (fconfig->anchor & kFloatAnchorSouth) ? 0 : 1;
+ }
+ if (!HAS_KEY(config->col)) {
+ fconfig->col = 0;
+ }
+ } else {
+ api_set_error(err, kErrorTypeValidation, "'bufpos' key must be Array");
+ return false;
+ }
+ }
+
+ if (config->width.type == kObjectTypeInteger && config->width.data.integer > 0) {
+ fconfig->width = (int)config->width.data.integer;
+ } else if (HAS_KEY(config->width)) {
+ api_set_error(err, kErrorTypeValidation, "'width' key must be a positive Integer");
+ return false;
+ } else if (!reconf) {
+ api_set_error(err, kErrorTypeValidation, "Must specify 'width'");
+ return false;
+ }
+
+ if (config->height.type == kObjectTypeInteger && config->height.data.integer > 0) {
+ fconfig->height = (int)config->height.data.integer;
+ } else if (HAS_KEY(config->height)) {
+ api_set_error(err, kErrorTypeValidation, "'height' key must be a positive Integer");
+ return false;
+ } else if (!reconf) {
+ api_set_error(err, kErrorTypeValidation, "Must specify 'height'");
+ return false;
+ }
+
+ if (relative_is_win) {
+ fconfig->window = curwin->handle;
+ if (config->win.type == kObjectTypeInteger || config->win.type == kObjectTypeWindow) {
+ if (config->win.data.integer > 0) {
+ fconfig->window = (Window)config->win.data.integer;
+ }
+ } else if (HAS_KEY(config->win)) {
+ api_set_error(err, kErrorTypeValidation, "'win' key must be Integer or Window");
+ return false;
+ }
+ } else {
+ if (HAS_KEY(config->win)) {
+ api_set_error(err, kErrorTypeValidation, "'win' key is only valid with relative='win'");
+ return false;
+ }
+ }
+
+ if (HAS_KEY(config->external)) {
+ fconfig->external = api_object_to_bool(config->external, "'external' key", false, err);
+ if (ERROR_SET(err)) {
+ return false;
+ }
+ if (has_relative && fconfig->external) {
+ api_set_error(err, kErrorTypeValidation,
+ "Only one of 'relative' and 'external' must be used");
+ return false;
+ }
+ if (fconfig->external && !ui_has(kUIMultigrid)) {
+ 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(config->focusable)) {
+ fconfig->focusable = api_object_to_bool(config->focusable, "'focusable' key", false, err);
+ if (ERROR_SET(err)) {
+ return false;
+ }
+ }
+
+ if (config->zindex.type == kObjectTypeInteger && config->zindex.data.integer > 0) {
+ fconfig->zindex = (int)config->zindex.data.integer;
+ } else if (HAS_KEY(config->zindex)) {
+ api_set_error(err, kErrorTypeValidation, "'zindex' key must be a positive Integer");
+ return false;
+ }
+
+ if (HAS_KEY(config->border)) {
+ parse_border_style(config->border, fconfig, err);
+ if (ERROR_SET(err)) {
+ return false;
+ }
+ }
+
+ if (config->style.type == kObjectTypeString) {
+ if (config->style.data.string.data[0] == NUL) {
+ fconfig->style = kWinStyleUnused;
+ } else if (striequal(config->style.data.string.data, "minimal")) {
+ fconfig->style = kWinStyleMinimal;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "Invalid value of 'style' key");
+ }
+ } else if (HAS_KEY(config->style)) {
+ api_set_error(err, kErrorTypeValidation, "'style' key must be String");
+ return false;
+ }
+
+ if (HAS_KEY(config->noautocmd)) {
+ if (!new_win) {
+ api_set_error(err, kErrorTypeValidation, "Invalid key: 'noautocmd'");
+ return false;
+ }
+ fconfig->noautocmd = api_object_to_bool(config->noautocmd, "'noautocmd' key", false, err);
+ if (ERROR_SET(err)) {
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/src/nvim/api/win_config.h b/src/nvim/api/win_config.h
new file mode 100644
index 0000000000..9271c35f23
--- /dev/null
+++ b/src/nvim/api/win_config.h
@@ -0,0 +1,11 @@
+#ifndef NVIM_API_WIN_CONFIG_H
+#define NVIM_API_WIN_CONFIG_H
+
+#include <stdint.h>
+
+#include "nvim/api/private/defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/win_config.h.generated.h"
+#endif
+#endif // NVIM_API_WIN_CONFIG_H
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index 95eae1af2d..6e68c057dc 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -373,117 +373,6 @@ Boolean nvim_win_is_valid(Window window)
}
-/// Configures window layout. Currently only for floating and external windows
-/// (including changing a split window to those layouts).
-///
-/// When reconfiguring a floating window, absent option keys will not be
-/// changed. `row`/`col` and `relative` must be reconfigured together.
-///
-/// @see |nvim_open_win()|
-///
-/// @param window Window handle, or 0 for current window
-/// @param config Map defining the window configuration,
-/// see |nvim_open_win()|
-/// @param[out] err Error details, if any
-void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err)
- FUNC_API_SINCE(6)
-{
- win_T *win = find_window_by_handle(window, err);
- if (!win) {
- return;
- }
- bool new_float = !win->w_floating;
- // reuse old values, if not overridden
- FloatConfig fconfig = new_float ? FLOAT_CONFIG_INIT : win->w_float_config;
-
- if (!parse_float_config(config, &fconfig, !new_float, false, err)) {
- return;
- }
- if (new_float) {
- if (!win_new_float(win, fconfig, err)) {
- return;
- }
- redraw_later(win, NOT_VALID);
- } else {
- win_config_float(win, fconfig);
- win->w_pos_changed = true;
- }
- if (fconfig.style == kWinStyleMinimal) {
- win_set_minimal_style(win);
- didset_window_options(win);
- }
-}
-
-/// Gets window configuration.
-///
-/// The returned value may be given to |nvim_open_win()|.
-///
-/// `relative` is empty for normal windows.
-///
-/// @param window Window handle, or 0 for current window
-/// @param[out] err Error details, if any
-/// @return Map defining the window configuration, see |nvim_open_win()|
-Dictionary nvim_win_get_config(Window window, Error *err)
- FUNC_API_SINCE(6)
-{
- Dictionary rv = ARRAY_DICT_INIT;
-
- win_T *wp = find_window_by_handle(window, err);
- if (!wp) {
- return rv;
- }
-
- FloatConfig *config = &wp->w_float_config;
-
- PUT(rv, "focusable", BOOLEAN_OBJ(config->focusable));
- PUT(rv, "external", BOOLEAN_OBJ(config->external));
-
- if (wp->w_floating) {
- PUT(rv, "width", INTEGER_OBJ(config->width));
- PUT(rv, "height", INTEGER_OBJ(config->height));
- if (!config->external) {
- if (config->relative == kFloatRelativeWindow) {
- PUT(rv, "win", INTEGER_OBJ(config->window));
- if (config->bufpos.lnum >= 0) {
- Array pos = ARRAY_DICT_INIT;
- ADD(pos, INTEGER_OBJ(config->bufpos.lnum));
- ADD(pos, INTEGER_OBJ(config->bufpos.col));
- PUT(rv, "bufpos", ARRAY_OBJ(pos));
- }
- }
- PUT(rv, "anchor", STRING_OBJ(cstr_to_string(float_anchor_str[config->anchor])));
- PUT(rv, "row", FLOAT_OBJ(config->row));
- PUT(rv, "col", FLOAT_OBJ(config->col));
- PUT(rv, "zindex", INTEGER_OBJ(config->zindex));
- }
- if (config->border) {
- Array border = ARRAY_DICT_INIT;
- for (size_t i = 0; i < 8; i++) {
- Array tuple = ARRAY_DICT_INIT;
-
- String s = cstrn_to_string((const char *)config->border_chars[i], sizeof(schar_T));
-
- int hi_id = config->border_hl_ids[i];
- char_u *hi_name = syn_id2name(hi_id);
- if (hi_name[0]) {
- ADD(tuple, STRING_OBJ(s));
- ADD(tuple, STRING_OBJ(cstr_to_string((const char *)hi_name)));
- ADD(border, ARRAY_OBJ(tuple));
- } else {
- ADD(border, STRING_OBJ(s));
- }
- }
- PUT(rv, "border", ARRAY_OBJ(border));
- }
- }
-
- const char *rel = (wp->w_floating && !config->external
- ? float_relative_str[config->relative] : "");
- PUT(rv, "relative", STRING_OBJ(cstr_to_string(rel)));
-
- return rv;
-}
-
/// Closes the window and hide the buffer it contains (like |:hide| with a
/// |window-ID|).
///
diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua
index 6ed2e5dab8..e38a0af0ec 100644
--- a/src/nvim/generators/gen_api_dispatch.lua
+++ b/src/nvim/generators/gen_api_dispatch.lua
@@ -33,6 +33,10 @@ local function_names = {}
local c_grammar = require('generators.c_grammar')
+local function startswith(String,Start)
+ return string.sub(String,1,string.len(Start))==Start
+end
+
-- read each input file, parse and append to the api metadata
for i = 6, #arg do
local full_path = arg[i]
@@ -47,7 +51,8 @@ for i = 6, #arg do
local tmp = c_grammar.grammar:match(input:read('*all'))
for j = 1, #tmp do
local fn = tmp[j]
- if not fn.noexport then
+ local public = startswith(fn.name, "nvim_") or fn.deprecated_since
+ if public and not fn.noexport then
functions[#functions + 1] = tmp[j]
function_names[fn.name] = true
if #fn.parameters ~= 0 and fn.parameters[1][2] == 'channel_id' then
@@ -76,10 +81,6 @@ local function shallowcopy(orig)
return copy
end
-local function startswith(String,Start)
- return string.sub(String,1,string.len(Start))==Start
-end
-
-- Export functions under older deprecated names.
-- These will be removed eventually.
local deprecated_aliases = require("api.dispatch_deprecated")
@@ -108,9 +109,10 @@ for _,f in ipairs(shallowcopy(functions)) do
f.lua = f.lua_only or not f.remote_only
f.eval = (not f.lua_only) and (not f.remote_only)
else
+ f.deprecated_since = tonumber(f.deprecated_since)
+ assert(f.deprecated_since == 1)
f.remote = true
f.since = 0
- f.deprecated_since = 1
end
f.method = ismethod
local newname = deprecated_aliases[f.name]
diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c
index cf463fd40a..cd9f5fc83e 100644
--- a/src/nvim/mouse.c
+++ b/src/nvim/mouse.c
@@ -236,6 +236,11 @@ retnomove:
redraw_curbuf_later(INVERTED); // delete the inversion
}
+ if (grid == 0) {
+ row -= curwin->w_grid_alloc.comp_row+curwin->w_grid.row_offset;
+ col -= curwin->w_grid_alloc.comp_col+curwin->w_grid.col_offset;
+ }
+
// When clicking beyond the end of the window, scroll the screen.
// Scroll by however many rows outside the window we are.
if (row < 0) {
@@ -476,7 +481,7 @@ win_T *mouse_find_win(int *gridp, int *rowp, int *colp)
static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp)
{
if (*gridp == msg_grid.handle) {
- // rowp += msg_grid_pos; // PVS: dead store #11612
+ *rowp += msg_grid_pos;
*gridp = DEFAULT_GRID_HANDLE;
} else if (*gridp > 1) {
win_T *wp = get_win_by_grid_handle(*gridp);
diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua
index e57c63bb0f..6c2c4b398a 100644
--- a/test/functional/ui/float_spec.lua
+++ b/test/functional/ui/float_spec.lua
@@ -46,6 +46,7 @@ describe('float window', function()
[24] = {foreground = Screen.colors.Black, background = Screen.colors.Grey80};
[25] = {blend = 100, background = Screen.colors.Gray0};
[26] = {blend = 80, background = Screen.colors.Gray0};
+ [27] = {background = Screen.colors.LightGray};
}
it('behavior', function()
@@ -6174,6 +6175,132 @@ describe('float window', function()
end)
end)
+ it("left drag changes visual selection in float window", function()
+ local buf = meths.create_buf(false,false)
+ meths.buf_set_lines(buf, 0, -1, true, {'foo', 'bar'})
+ meths.open_win(buf, false, {relative='editor', width=20, height=3, row=2, col=5})
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 5
+ {1:foo }|
+ {1:bar }|
+ {2:~ }|
+ ]], float_pos={
+ [5] = {{id = 1002}, "NW", 1, 2, 5, true, 50};
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 2};
+ }}
+ meths.input_mouse('left', 'press', '', 5, 0, 0)
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 5
+ {1:^foo }|
+ {1:bar }|
+ {2:~ }|
+ ]], float_pos={
+ [5] = {{id = 1002}, "NW", 1, 2, 5, true, 50};
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 2};
+ }}
+ meths.input_mouse('left', 'drag', '', 5, 1, 2)
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ {3:-- VISUAL --} |
+ ## grid 5
+ {27:foo}{1: }|
+ {27:ba}{1:^r }|
+ {2:~ }|
+ ]], float_pos={
+ [5] = {{id = 1002}, "NW", 1, 2, 5, true, 50};
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 1, curcol = 2, linecount = 2};
+ }}
+ else
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }{1:foo }{0: }|
+ {0:~ }{1:bar }{0: }|
+ {0:~ }{2:~ }{0: }|
+ {0:~ }|
+ |
+ ]]}
+
+ meths.input_mouse('left', 'press', '', 0, 2, 5)
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }{1:^foo }{0: }|
+ {0:~ }{1:bar }{0: }|
+ {0:~ }{2:~ }{0: }|
+ {0:~ }|
+ |
+ ]]}
+
+ meths.input_mouse('left', 'drag', '', 0, 3, 7)
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }{27:foo}{1: }{0: }|
+ {0:~ }{27:ba}{1:^r }{0: }|
+ {0:~ }{2:~ }{0: }|
+ {0:~ }|
+ {3:-- VISUAL --} |
+ ]]}
+ end
+ end)
+
it("'winblend' option", function()
screen:try_resize(50,9)
screen:set_default_attr_ids({
diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua
index d3fe38ef52..baacef358f 100644
--- a/test/functional/ui/mouse_spec.lua
+++ b/test/functional/ui/mouse_spec.lua
@@ -585,6 +585,69 @@ describe('ui/mouse/input', function()
]])
end)
+ it('left drag changes visual selection in split layout', function()
+ screen:try_resize(53,14)
+ command('set mouse=a')
+ command('vsplit')
+ command('wincmd l')
+ command('below split')
+ command('enew')
+ feed('ifoo\nbar<esc>')
+
+ screen:expect{grid=[[
+ testing {4:│}testing |
+ mouse {4:│}mouse |
+ support and selection {4:│}support and selection |
+ {0:~ }{4:│}{0:~ }|
+ {0:~ }{4:│}{0:~ }|
+ {0:~ }{4:│[No Name] [+] }|
+ {0:~ }{4:│}foo{0:$} |
+ {0:~ }{4:│}ba^r{0:$} |
+ {0:~ }{4:│}{0:~ }|
+ {0:~ }{4:│}{0:~ }|
+ {0:~ }{4:│}{0:~ }|
+ {0:~ }{4:│}{0:~ }|
+ {4:[No Name] [+] }{5:[No Name] [+] }|
+ |
+ ]]}
+
+ meths.input_mouse('left', 'press', '', 0, 6, 27)
+ screen:expect{grid=[[
+ testing {4:│}testing |
+ mouse {4:│}mouse |
+ support and selection {4:│}support and selection |
+ {0:~ }{4:│}{0:~ }|
+ {0:~ }{4:│}{0:~ }|
+ {0:~ }{4:│[No Name] [+] }|
+ {0:~ }{4:│}^foo{0:$} |
+ {0:~ }{4:│}bar{0:$} |
+ {0:~ }{4:│}{0:~ }|
+ {0:~ }{4:│}{0:~ }|
+ {0:~ }{4:│}{0:~ }|
+ {0:~ }{4:│}{0:~ }|
+ {4:[No Name] [+] }{5:[No Name] [+] }|
+ |
+ ]]}
+ meths.input_mouse('left', 'drag', '', 0, 7, 30)
+
+ screen:expect{grid=[[
+ testing {4:│}testing |
+ mouse {4:│}mouse |
+ support and selection {4:│}support and selection |
+ {0:~ }{4:│}{0:~ }|
+ {0:~ }{4:│}{0:~ }|
+ {0:~ }{4:│[No Name] [+] }|
+ {0:~ }{4:│}{1:foo}{3:$} |
+ {0:~ }{4:│}{1:bar}{0:^$} |
+ {0:~ }{4:│}{0:~ }|
+ {0:~ }{4:│}{0:~ }|
+ {0:~ }{4:│}{0:~ }|
+ {0:~ }{4:│}{0:~ }|
+ {4:[No Name] [+] }{5:[No Name] [+] }|
+ {2:-- VISUAL --} |
+ ]]}
+ end)
+
it('two clicks will select the word and enter VISUAL', function()
feed('<LeftMouse><2,2><LeftMouse><2,2>')
screen:expect([[