fix(api): use standard error messages

This commit is contained in:
Justin M. Keyes
2026-03-14 17:56:19 +01:00
parent ca67ba3b66
commit 680d25e5b3
23 changed files with 401 additions and 312 deletions

View File

@@ -4193,8 +4193,8 @@ nvim_win_set_buf({window}, {buffer}) *nvim_win_set_buf()*
• {buffer} (`integer`) Buffer id
nvim_win_set_cursor({window}, {pos}) *nvim_win_set_cursor()*
Sets the (1,0)-indexed cursor position in the window. |api-indexing| This
scrolls the window even if it is not the current one.
Sets the (1,0)-indexed cursor position (byte offset) in the window.
|api-indexing| This scrolls the window even if it is not the current one.
Attributes: ~
Since: 0.1.0

View File

@@ -2524,7 +2524,7 @@ function vim.api.nvim_win_set_buf(window, buffer) end
--- @param config vim.api.keyset.win_config Map defining the window configuration, see [nvim_open_win()]
function vim.api.nvim_win_set_config(window, config) end
--- Sets the (1,0)-indexed cursor position in the window. `api-indexing`
--- Sets the (1,0)-indexed cursor position (byte offset) in the window. `api-indexing`
--- This scrolls the window even if it is not the current one.
---
--- @param window integer `window-ID`, or 0 for current window

View File

@@ -193,11 +193,10 @@ ArrayOf(DictAs(get_autocmds__ret)) nvim_get_autocmds(Dict(get_autocmds) *opts, A
buffers = arena_array(arena, 1);
ADD_C(buffers, STRING_OBJ(pat));
} else if (opts->buffer.type == kObjectTypeArray) {
if (opts->buffer.data.array.size > AUCMD_MAX_PATTERNS) {
api_set_error(err, kErrorTypeValidation, "Too many buffers (maximum of %d)",
AUCMD_MAX_PATTERNS);
VALIDATE((opts->buffer.data.array.size <= AUCMD_MAX_PATTERNS),
"Too many buffers (maximum of %d)", AUCMD_MAX_PATTERNS, {
goto cleanup;
}
});
buffers = arena_array(arena, kv_size(opts->buffer.data.array));
FOREACH_ITEM(opts->buffer.data.array, bufnr, {

View File

@@ -60,10 +60,9 @@ void nvim_ui_term_event(uint64_t channel_id, String event, Object value, Error *
FUNC_API_SINCE(12) FUNC_API_REMOTE_ONLY
{
if (strequal("termresponse", event.data)) {
if (value.type != kObjectTypeString) {
api_set_error(err, kErrorTypeValidation, "termresponse must be a string");
VALIDATE_T("termresponse", kObjectTypeString, value.type, {
return;
}
});
const String termresponse = value.data.string;
set_vim_var_string(VV_TERMRESPONSE, termresponse.data, (ptrdiff_t)termresponse.size);

View File

@@ -140,10 +140,9 @@ Object dict_get_value(dict_T *dict, String key, Arena *arena, Error *err)
{
dictitem_T *const di = tv_dict_find(dict, key.data, (ptrdiff_t)key.size);
if (di == NULL) {
api_set_error(err, kErrorTypeValidation, "Key not found: %s", key.data);
VALIDATE(di != NULL, "Key not found: %s", key.data, {
return (Object)OBJECT_INIT;
}
});
return vim_to_object(&di->di_tv, arena, true);
}
@@ -269,9 +268,9 @@ buf_T *find_buffer_by_handle(Buffer buffer, Error *err)
buf_T *rv = handle_get_buffer(buffer);
if (!rv) {
api_set_error(err, kErrorTypeValidation, "Invalid buffer id: %d", buffer);
}
VALIDATE_INT(rv, "buffer id", buffer, {
return NULL;
});
return rv;
}
@@ -284,9 +283,9 @@ win_T *find_window_by_handle(Window window, Error *err)
win_T *rv = handle_get_window(window);
if (!rv) {
api_set_error(err, kErrorTypeValidation, "Invalid window id: %d", window);
}
VALIDATE_INT(rv, "window id", window, {
return NULL;
});
return rv;
}
@@ -299,9 +298,9 @@ tabpage_T *find_tab_by_handle(Tabpage tabpage, Error *err)
tabpage_T *rv = handle_get_tabpage(tabpage);
if (!rv) {
api_set_error(err, kErrorTypeValidation, "Invalid tabpage id: %d", tabpage);
}
VALIDATE_INT(rv, "tabpage id", tabpage, {
return NULL;
});
return rv;
}
@@ -480,10 +479,9 @@ String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col
{
String rv = STRING_INIT;
if (lnum >= MAXLNUM) {
api_set_error(err, kErrorTypeValidation, "Line index is too high");
VALIDATE_RANGE((lnum < MAXLNUM), "line index", {
return rv;
}
});
char *bufstr = ml_get_buf(buf, (linenr_T)lnum);
colnr_T line_length = ml_get_buf_len(buf, (linenr_T)lnum);
@@ -720,7 +718,7 @@ bool api_object_to_bool(Object obj, const char *what, bool nil_value, Error *err
} else if (obj.type == kObjectTypeNil) {
return nil_value; // caller decides what NIL (missing retval in Lua) means
} else {
api_set_error(err, kErrorTypeValidation, "%s is not a boolean", what);
VALIDATE_EXP(false, what, "boolean", NULL, {});
return false;
}
}
@@ -734,7 +732,7 @@ int object_to_hl_id(Object obj, const char *what, Error *err)
int id = (int)obj.data.integer;
return (1 <= id && id <= highlight_num_groups()) ? id : 0;
} else {
api_set_error(err, kErrorTypeValidation, "Invalid hl_group: %s", what);
VALIDATE_S(false, "hl_group", what, {});
return 0;
}
}
@@ -1010,14 +1008,12 @@ bool set_mark(buf_T *buf, String name, Integer line, Integer col, Error *err)
col = 0;
deleting = true;
} else {
if (col > MAXCOL) {
api_set_error(err, kErrorTypeValidation, "Column value outside range");
VALIDATE_RANGE(!(col > MAXCOL), "column", {
return res;
}
if (line < 1 || line > buf->b_ml.ml_line_count) {
api_set_error(err, kErrorTypeValidation, "Line value outside range");
});
VALIDATE_RANGE(!(line < 1 || line > buf->b_ml.ml_line_count), "line", {
return res;
}
});
}
assert(INT32_MIN <= line && line <= INT32_MAX);
pos_T pos = { (linenr_T)line, (int)col, 0 };

View File

@@ -57,6 +57,31 @@ void api_err_exp(Error *err, const char *name, const char *expected, const char
name, expected, actual);
}
/// Creates "Required: …" message and sets it on `err`.
void api_err_required(Error *err, const char *name)
{
ErrorType errtype = kErrorTypeValidation;
// Treat `name` without whitespace as a parameter (surround in quotes).
// Treat `name` with whitespace as a description (no quotes).
const char *has_space = strchr(name, ' ');
api_set_error(err, errtype, has_space ? "Required: %s" : "Required: '%s'", name);
}
/// Creates "Conflict: … not allowed with …" message and sets it on `err`.
void api_err_conflict(Error *err, const char *name, const char *name2)
{
ErrorType errtype = kErrorTypeValidation;
// Treat `name` without whitespace as a parameter (surround in quotes).
// Treat `name` with whitespace as a description (no quotes).
const char *has_space2 = strchr(name2, ' ');
api_set_error(err, errtype, has_space2
? "Conflict: '%s' not allowed with %s"
: "Conflict: '%s' not allowed with '%s'",
name, name2);
}
bool check_string_array(Array arr, char *name, bool disallow_nl, Error *err)
{
snprintf(IObuff, sizeof(IObuff), "'%s' item", name);

View File

@@ -16,6 +16,23 @@
} \
} while (0)
#define VALIDATE_R(cond, name, code) \
do { \
if (!(cond)) { \
api_err_required(err, name); \
code; \
} \
} while (0)
/// Shows a "not allowed with" message, for mutually-exclusive args.
#define VALIDATE_CON(cond, name, name2, code) \
do { \
if (!(cond)) { \
api_err_conflict(err, name, name2); \
code; \
} \
} while (0)
#define VALIDATE_INT(cond, name, val_, code) \
do { \
if (!(cond)) { \
@@ -88,7 +105,4 @@
} \
} while (0)
#define VALIDATE_R(cond, name, code) \
VALIDATE(cond, "Required: '%s'", name, code);
#include "api/private/validate.h.generated.h"

View File

@@ -447,7 +447,9 @@ static void ui_set_option(RemoteUI *ui, bool init, String name, Object value, Er
}
}
api_set_error(err, kErrorTypeValidation, "No such UI option: %s", name.data);
VALIDATE_S(false, "UI option", name.data, {
return;
});
}
/// Tell Nvim to resize a grid. Triggers a grid_resize event with the requested
@@ -495,8 +497,7 @@ void nvim_ui_pum_set_height(uint64_t channel_id, Integer height, Error *err)
}
if (!ui->ui_ext[kUIPopupmenu]) {
api_set_error(err, kErrorTypeValidation,
"It must support the ext_popupmenu option");
api_set_error(err, kErrorTypeValidation, "UI must support the ext_popupmenu option");
return;
}

View File

@@ -184,7 +184,7 @@ void nvim_set_hl(uint64_t channel_id, Integer ns_id, String name, Dict(highlight
// Setting URLs directly through highlight attributes is not supported
if (HAS_KEY(val, highlight, url)) {
api_set_error(err, kErrorTypeValidation, "Invalid Key: 'url'");
api_set_error(err, kErrorTypeValidation, "Invalid key: 'url'");
return;
}

View File

@@ -10,6 +10,7 @@
#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/private/validate.h"
#include "nvim/api/vimscript.h"
#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
@@ -303,8 +304,9 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Arena *arena,
object_to_vim(dict, &rettv, err);
break;
default:
api_set_error(err, kErrorTypeValidation, "dict argument type must be String or Dict");
return rv;
VALIDATE_EXP(false, "dict argument", "String or Dict", NULL, {
return rv;
});
}
dict_T *self_dict = rettv.vval.v_dict;
if (rettv.v_type != VAR_DICT || !self_dict) {
@@ -314,29 +316,26 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Arena *arena,
if (fn.data && fn.size > 0 && dict.type != kObjectTypeDict) {
dictitem_T *const di = tv_dict_find(self_dict, fn.data, (ptrdiff_t)fn.size);
if (di == NULL) {
api_set_error(err, kErrorTypeValidation, "Not found: %s", fn.data);
VALIDATE(di != NULL, "Not found: %s", fn.data, {
goto end;
}
});
if (di->di_tv.v_type == VAR_PARTIAL) {
api_set_error(err, kErrorTypeValidation,
"partial function not supported");
goto end;
}
if (di->di_tv.v_type != VAR_FUNC) {
api_set_error(err, kErrorTypeValidation, "Not a function: %s", fn.data);
VALIDATE((di->di_tv.v_type == VAR_FUNC), "Not a function: %s", fn.data, {
goto end;
}
});
fn = (String) {
.data = di->di_tv.vval.v_string,
.size = strlen(di->di_tv.vval.v_string),
};
}
if (!fn.data || fn.size < 1) {
api_set_error(err, kErrorTypeValidation, "Invalid (empty) function name");
VALIDATE((fn.data && fn.size >= 1), "Invalid function name: %s", "(empty)", {
goto end;
}
});
rv = _call_function(fn, args, self_dict, arena, err);
end:

View File

@@ -8,6 +8,7 @@
#include "nvim/api/private/defs.h"
#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/private/validate.h"
#include "nvim/api/win_config.h"
#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
@@ -1012,15 +1013,15 @@ static bool parse_float_bufpos(Array bufpos, lpos_T *out)
static void parse_bordertext(Object bordertext, BorderTextType bordertext_type, WinConfig *fconfig,
Error *err)
{
if (bordertext.type != kObjectTypeString && bordertext.type != kObjectTypeArray) {
api_set_error(err, kErrorTypeValidation, "title/footer must be string or array");
VALIDATE_EXP(!(bordertext.type != kObjectTypeString && bordertext.type != kObjectTypeArray),
"title/footer", "String or Array", api_typename(bordertext.type), {
return;
}
});
if (bordertext.type == kObjectTypeArray && bordertext.data.array.size == 0) {
api_set_error(err, kErrorTypeValidation, "title/footer cannot be an empty array");
VALIDATE_EXP(!(bordertext.type == kObjectTypeArray && bordertext.data.array.size == 0),
"title/footer", "non-empty Array", NULL, {
return;
}
});
bool *is_present;
VirtText *chunks;
@@ -1086,15 +1087,9 @@ static bool parse_bordertext_pos(win_T *wp, String bordertext_pos, BorderTextTyp
} else if (strequal(pos, "right")) {
*align = kAlignRight;
} else {
switch (bordertext_type) {
case kBorderTextTitle:
api_set_error(err, kErrorTypeValidation, "invalid title_pos value");
break;
case kBorderTextFooter:
api_set_error(err, kErrorTypeValidation, "invalid footer_pos value");
break;
}
return false;
VALIDATE_S(false, (bordertext_type == kBorderTextTitle ? "title_pos" : "footer_pos"), pos, {
return false;
});
}
return true;
}
@@ -1123,24 +1118,22 @@ void parse_border_style(Object style, WinConfig *fconfig, Error *err)
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");
VALIDATE_EXP(!(!size || size > 8 || (size & (size - 1))),
"border", "1, 2, 4, or 8 chars", NULL, {
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");
VALIDATE_EXP(!(!iarr.size || iarr.size > 2), "border", "1 or 2-item Array", NULL, {
return;
}
if (iarr.items[0].type != kObjectTypeString) {
api_set_error(err, kErrorTypeValidation, "invalid border char");
});
VALIDATE_EXP(iarr.items[0].type == kObjectTypeString, "border", "Array of Strings", NULL, {
return;
}
});
string = iarr.items[0].data.string;
if (iarr.size == 2) {
hl_id = object_to_hl_id(iarr.items[1], "border char highlight", err);
@@ -1151,13 +1144,14 @@ void parse_border_style(Object style, WinConfig *fconfig, Error *err)
} else if (iytem.type == kObjectTypeString) {
string = iytem.data.string;
} else {
api_set_error(err, kErrorTypeValidation, "invalid border char");
return;
VALIDATE_EXP(false, "border", "String or Array", api_typename(iytem.type), {
return;
});
}
if (string.size && mb_string2cells_len(string.data, string.size) > 1) {
api_set_error(err, kErrorTypeValidation, "border chars must be one cell");
VALIDATE_EXP(!(string.size && mb_string2cells_len(string.data, string.size) > 1),
"border", "only one-cell chars", NULL, {
return;
}
});
size_t len = MIN(string.size, sizeof(*chars) - 1);
if (len) {
memcpy(chars[i], string.data, len);
@@ -1170,12 +1164,13 @@ void parse_border_style(Object style, WinConfig *fconfig, Error *err)
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");
}
VALIDATE_EXP(!((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])), "border",
"corner char between edge chars", NULL, {
return;
});
} else if (style.type == kObjectTypeString) {
String str = style.data.string;
if (str.size == 0 || strequal(str.data, "none")) {
@@ -1201,7 +1196,9 @@ void parse_border_style(Object style, WinConfig *fconfig, Error *err)
return;
}
}
api_set_error(err, kErrorTypeValidation, "invalid border style \"%s\"", str.data);
VALIDATE_S(false, "border", str.data, {
return;
});
}
}
@@ -1209,10 +1206,10 @@ static void generate_api_error(win_T *wp, const char *attribute, Error *err)
{
if (wp != NULL && wp->w_floating) {
api_set_error(err, kErrorTypeValidation,
"Missing 'relative' field when reconfiguring floating window %d",
"Required: 'relative' when reconfiguring floating window %d",
wp->handle);
} else {
api_set_error(err, kErrorTypeValidation, "non-float cannot have '%s'", attribute);
VALIDATE_CON(false, attribute, "non-float window", {});
}
}
@@ -1267,16 +1264,15 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
{
bool has_relative = false, relative_is_win = false, is_split = false;
if (config->relative.size > 0) {
if (!parse_float_relative(config->relative, &fconfig->relative)) {
api_set_error(err, kErrorTypeValidation, "Invalid value of 'relative' key");
VALIDATE_S(parse_float_relative(config->relative, &fconfig->relative),
"relative", config->relative.data, {
goto fail;
}
});
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'");
VALIDATE_R(!(config->relative.size > 0 && !(HAS_KEY_X(config, row) && HAS_KEY_X(config, col))
&& !HAS_KEY_X(config, bufpos)), "'relative' requires 'row'/'col' or 'bufpos'", {
goto fail;
}
});
has_relative = true;
fconfig->external = false;
@@ -1289,35 +1285,34 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
is_split = true;
fconfig->external = false;
} else if (wp == NULL) { // new win
api_set_error(err, kErrorTypeValidation,
"Must specify 'relative' or 'external' when creating a float");
goto fail;
VALIDATE_R(false, "'relative' or 'external' when creating a float", {
goto fail;
});
}
}
if (HAS_KEY_X(config, vertical)) {
if (!is_split) {
api_set_error(err, kErrorTypeValidation, "floating windows cannot have 'vertical'");
goto fail;
}
}
VALIDATE_CON(!(HAS_KEY_X(config, vertical) && !is_split), "vertical", "floating windows", {
goto fail;
});
VALIDATE_CON(!(HAS_KEY_X(config, split) && !is_split), "split", "floating windows", {
goto fail;
});
if (HAS_KEY_X(config, split)) {
if (!is_split) {
api_set_error(err, kErrorTypeValidation, "floating windows cannot have 'split'");
VALIDATE_CON(is_split, "split", "floating windows", {
goto fail;
}
if (!parse_config_split(config->split, &fconfig->split)) {
api_set_error(err, kErrorTypeValidation, "Invalid value of 'split' key");
});
VALIDATE_S(parse_config_split(config->split, &fconfig->split), "split", config->split.data, {
goto fail;
}
});
}
if (HAS_KEY_X(config, anchor)) {
if (!parse_float_anchor(config->anchor, &fconfig->anchor)) {
api_set_error(err, kErrorTypeValidation, "Invalid value of 'anchor' key");
VALIDATE_S(parse_float_anchor(config->anchor, &fconfig->anchor),
"anchor", config->anchor.data, {
goto fail;
}
});
}
if (HAS_KEY_X(config, row)) {
@@ -1341,10 +1336,10 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
generate_api_error(wp, "bufpos", err);
goto fail;
} else {
if (!parse_float_bufpos(config->bufpos, &fconfig->bufpos)) {
api_set_error(err, kErrorTypeValidation, "Invalid value of 'bufpos' key");
VALIDATE_EXP(parse_float_bufpos(config->bufpos, &fconfig->bufpos),
"bufpos", "[row, col] array", NULL, {
goto fail;
}
});
if (!HAS_KEY_X(config, row)) {
fconfig->row = (fconfig->anchor & kFloatAnchorSouth) ? 0 : 1;
@@ -1356,46 +1351,42 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
}
if (HAS_KEY_X(config, width)) {
if (config->width > 0) {
fconfig->width = (int)config->width;
} else {
api_set_error(err, kErrorTypeValidation, "'width' key must be a positive Integer");
VALIDATE_EXP((config->width > 0), "width", "positive Integer", NULL, {
goto fail;
}
});
fconfig->width = (int)config->width;
} else if (!reconf && !is_split) {
api_set_error(err, kErrorTypeValidation, "Must specify 'width'");
goto fail;
VALIDATE_R(false, "width", {
goto fail;
});
}
if (HAS_KEY_X(config, height)) {
if (config->height > 0) {
fconfig->height = (int)config->height;
} else {
api_set_error(err, kErrorTypeValidation, "'height' key must be a positive Integer");
VALIDATE_EXP((config->height > 0), "height", "positive Integer", NULL, {
goto fail;
}
});
fconfig->height = (int)config->height;
} else if (!reconf && !is_split) {
api_set_error(err, kErrorTypeValidation, "Must specify 'height'");
goto fail;
VALIDATE_R(false, "height", {
goto fail;
});
}
if (HAS_KEY_X(config, external)) {
fconfig->external = config->external;
if (has_relative && fconfig->external) {
api_set_error(err, kErrorTypeValidation,
"Only one of 'relative' and 'external' must be used");
VALIDATE_CON(!(has_relative && fconfig->external), "relative", "external", {
goto fail;
}
});
if (fconfig->external && !ui_has(kUIMultigrid)) {
api_set_error(err, kErrorTypeValidation, "UI doesn't support external windows");
goto fail;
}
}
if (HAS_KEY_X(config, win) && fconfig->external) {
api_set_error(err, kErrorTypeValidation, "external window cannot have 'win'");
VALIDATE_CON(!(HAS_KEY_X(config, win) && fconfig->external), "win", "external window", {
goto fail;
}
});
if (relative_is_win || (HAS_KEY_X(config, win) && !is_split && wp && wp->w_floating
&& fconfig->relative == kFloatRelativeWindow)) {
// When relative=win is given, missing win field means win=0.
@@ -1411,11 +1402,11 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
} else {
// Handle is not validated here, as win_config_split can accept negative values.
if (HAS_KEY_X(config, win)) {
if (!is_split && !has_relative && (!wp || !wp->w_floating)) {
api_set_error(err, kErrorTypeValidation,
"non-float with 'win' requires at least 'split' or 'vertical'");
VALIDATE_R(!(!is_split && !has_relative && (!wp || !wp->w_floating)),
"non-float with 'win' requires 'split' or 'vertical'", {
goto fail;
}
});
fconfig->window = config->win;
}
// Resolve, but skip validating. E.g: win_config_split accepts negative "win".
@@ -1434,23 +1425,19 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
}
if (HAS_KEY_X(config, zindex)) {
if (is_split) {
api_set_error(err, kErrorTypeValidation, "non-float cannot have 'zindex'");
VALIDATE_CON(!is_split, "zindex", "non-float window", {
goto fail;
}
if (config->zindex > 0) {
fconfig->zindex = (int)config->zindex;
} else {
api_set_error(err, kErrorTypeValidation, "'zindex' key must be a positive Integer");
});
VALIDATE_EXP((config->zindex > 0), "zindex", "positive Integer", NULL, {
goto fail;
}
});
fconfig->zindex = (int)config->zindex;
}
if (HAS_KEY_X(config, title)) {
if (is_split) {
api_set_error(err, kErrorTypeValidation, "non-float cannot have 'title'");
VALIDATE_CON(!is_split, "title", "non-float window", {
goto fail;
}
});
parse_bordertext(config->title, kBorderTextTitle, fconfig, err);
if (ERROR_SET(err)) {
@@ -1462,17 +1449,15 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
goto fail;
}
} else {
if (HAS_KEY_X(config, title_pos)) {
api_set_error(err, kErrorTypeException, "title_pos requires title to be set");
VALIDATE_R(!HAS_KEY_X(config, title_pos), "'title' requires 'title_pos'", {
goto fail;
}
});
}
if (HAS_KEY_X(config, footer)) {
if (is_split) {
api_set_error(err, kErrorTypeValidation, "non-float cannot have 'footer'");
VALIDATE_CON(!is_split, "footer", "non-float window", {
goto fail;
}
});
parse_bordertext(config->footer, kBorderTextFooter, fconfig, err);
if (ERROR_SET(err)) {
@@ -1484,18 +1469,16 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
goto fail;
}
} else {
if (HAS_KEY_X(config, footer_pos)) {
api_set_error(err, kErrorTypeException, "footer_pos requires footer to be set");
VALIDATE_R(!HAS_KEY_X(config, footer_pos), "'footer' requires 'footer_pos'", {
goto fail;
}
});
}
Object border_style = OBJECT_INIT;
if (HAS_KEY_X(config, border)) {
if (is_split) {
api_set_error(err, kErrorTypeValidation, "non-float cannot have 'border'");
VALIDATE_CON(!is_split, "border", "non-float window", {
goto fail;
}
});
border_style = config->border;
if (border_style.type != kObjectTypeNil) {
parse_border_style(border_style, fconfig, err);
@@ -1514,15 +1497,15 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
} else if (striequal(config->style.data, "minimal")) {
fconfig->style = kWinStyleMinimal;
} else {
api_set_error(err, kErrorTypeValidation, "Invalid value of 'style' key");
goto fail;
VALIDATE_S(false, "style", config->style.data, {
goto fail;
});
}
}
if (HAS_KEY_X(config, noautocmd)) {
if (wp && config->noautocmd != fconfig->noautocmd) {
api_set_error(err, kErrorTypeValidation,
"'noautocmd' cannot be changed with existing windows");
api_set_error(err, kErrorTypeValidation, "'noautocmd' cannot be changed on existing window");
goto fail;
}
fconfig->noautocmd = config->noautocmd;

View File

@@ -92,7 +92,7 @@ ArrayOf(Integer, 2) nvim_win_get_cursor(Window window, Arena *arena, Error *err)
return rv;
}
/// Sets the (1,0)-indexed cursor position in the window. |api-indexing|
/// Sets the (1,0)-indexed cursor position (byte offset) in the window. |api-indexing|
/// This scrolls the window even if it is not the current one.
///
/// @param window |window-ID|, or 0 for current window
@@ -107,26 +107,21 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err)
return;
}
if (pos.size != 2 || pos.items[0].type != kObjectTypeInteger
|| pos.items[1].type != kObjectTypeInteger) {
api_set_error(err,
kErrorTypeValidation,
"Argument \"pos\" must be a [row, col] array");
VALIDATE_EXP(!(pos.size != 2 || pos.items[0].type != kObjectTypeInteger
|| pos.items[1].type != kObjectTypeInteger), "pos", "[row, col] array", NULL, {
return;
}
});
int64_t row = pos.items[0].data.integer;
int64_t col = pos.items[1].data.integer;
if (row <= 0 || row > win->w_buffer->b_ml.ml_line_count) {
api_set_error(err, kErrorTypeValidation, "Cursor position outside buffer");
VALIDATE_RANGE(!(row <= 0 || row > win->w_buffer->b_ml.ml_line_count), "cursor line", {
return;
}
});
if (col > MAXCOL || col < 0) {
api_set_error(err, kErrorTypeValidation, "Column value outside range");
VALIDATE_RANGE(!(col > MAXCOL || col < 0), "cursor column", {
return;
}
});
win->w_cursor.lnum = (linenr_T)row;
win->w_cursor.col = (colnr_T)col;
@@ -450,9 +445,9 @@ void nvim_win_set_hl_ns(Window window, Integer ns_id, Error *err)
}
// -1 is allowed as inherit global namespace
if (ns_id < -1) {
api_set_error(err, kErrorTypeValidation, "no such namespace");
}
VALIDATE_S((ns_id >= -1), "namespace", "", {
return;
});
win->w_ns_hl = (NS)ns_id;
win->w_hl_needs_update = true;

View File

@@ -119,6 +119,11 @@ end
describe('API: buffer events:', function()
before_each(clear)
it('validation', function()
local b = editoriginal(false)
eq("Invalid key: 'builtin'", pcall_err(api.nvim_buf_attach, b, false, { builtin = 'asfd' }))
end)
it('when lines are added', function()
local b, tick = editoriginal(true)
@@ -798,11 +803,6 @@ describe('API: buffer events:', function()
expectn('nvim_buf_changedtick_event', { b, tick })
end)
it('returns a proper error on nonempty options dict', function()
local b = editoriginal(false)
eq("Invalid key: 'builtin'", pcall_err(api.nvim_buf_attach, b, false, { builtin = 'asfd' }))
end)
it('nvim_buf_attach returns response after delay #8634', function()
sleep(250)
-- response

View File

@@ -143,7 +143,7 @@ describe('API/extmarks', function()
)
-- No memory leak with virt_text, virt_lines, sign_text
eq(
'right_gravity is not a boolean',
"Invalid 'right_gravity': expected boolean",
pcall_err(set_extmark, ns, marks[2], 0, 0, {
virt_text = { { 'foo', 'Normal' } },
virt_lines = { { { 'bar', 'Normal' } } },

View File

@@ -116,6 +116,9 @@ describe('API: set highlight', function()
"Invalid 'blend': expected Integer, got Array",
pcall_err(api.nvim_set_hl, 0, 'Test_hl3', { fg = '#FF00FF', blend = {} })
)
-- 'url' is rejected. #38162
eq("Invalid key: 'url'", pcall_err(api.nvim_set_hl, 0, 'Test', { url = 'https://example.com' }))
assert_alive()
end)
it('can set gui highlight', function()
@@ -254,10 +257,6 @@ describe('API: set highlight', function()
)
assert_alive()
end)
it("'url' is rejected with an error #38162", function()
eq("Invalid Key: 'url'", pcall_err(api.nvim_set_hl, 0, 'Test', { url = 'https://example.com' }))
assert_alive()
end)
end)
describe('API: get highlight', function()

View File

@@ -649,7 +649,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
eq('Invalid mode shortname: "xnoremap"', pcall_err(api.nvim_del_keymap, 'xnoremap', 'lhs'))
end)
it('error on invalid optnames', function()
it('validation', function()
eq(
"Invalid key: 'silentt'",
pcall_err(api.nvim_set_keymap, 'n', 'lhs', 'rhs', { silentt = true })
@@ -659,16 +659,13 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
"Invalid key: 'nowaiT'",
pcall_err(api.nvim_set_keymap, 'n', 'lhs', 'rhs', { nowaiT = false })
)
end)
it('error on <buffer> option key', function()
-- <buffer> option key
eq(
"Invalid key: 'buffer'",
pcall_err(api.nvim_set_keymap, 'n', 'lhs', 'rhs', { buffer = true })
)
end)
it('error when "replace_keycodes" is used without "expr"', function()
eq(
'"replace_keycodes" requires "expr"',
pcall_err(api.nvim_set_keymap, 'n', 'lhs', 'rhs', { replace_keycodes = true })
@@ -681,7 +678,10 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
it('throws an error when given non-boolean value for ' .. opt, function()
local opts = {}
opts[opt] = 'fooo'
eq(opt .. ' is not a boolean', pcall_err(api.nvim_set_keymap, 'n', 'lhs', 'rhs', opts))
eq(
("Invalid '%s': expected boolean"):format(opt),
pcall_err(api.nvim_set_keymap, 'n', 'lhs', 'rhs', opts)
)
end)
end

View File

@@ -26,7 +26,7 @@ describe('nvim_ui_attach()', function()
end)
it('validation', function()
eq('No such UI option: foo', pcall_err(api.nvim_ui_attach, 80, 24, { foo = { 'foo' } }))
eq("Invalid UI option: 'foo'", pcall_err(api.nvim_ui_attach, 80, 24, { foo = { 'foo' } }))
eq(
"Invalid 'ext_linegrid': expected Boolean, got Array",

View File

@@ -729,7 +729,7 @@ describe('API', function()
pcall_err(request, 'nvim_call_dict_function', "{ 'f': '' }", 'f', { 1, 2 })
)
eq(
'dict argument type must be String or Dict',
'Invalid dict argument: expected String or Dict',
pcall_err(request, 'nvim_call_dict_function', 42, 'f', { 1, 2 })
)
eq(
@@ -738,7 +738,7 @@ describe('API', function()
)
eq('dict not found', pcall_err(request, 'nvim_call_dict_function', '42', 'f', { 1, 2 }))
eq(
'Invalid (empty) function name',
'Invalid function name: (empty)',
pcall_err(request, 'nvim_call_dict_function', "{ 'f': '' }", '', { 1, 2 })
)
end)
@@ -3791,7 +3791,7 @@ describe('API', function()
'Invalid chunk: expected Array with 1 or 2 Strings',
pcall_err(api.nvim_echo, { { '', '', '' } }, 1, {})
)
eq('Invalid hl_group: text highlight', pcall_err(api.nvim_echo, { { '', false } }, 1, {}))
eq("Invalid 'hl_group': 'text highlight'", pcall_err(api.nvim_echo, { { '', false } }, 1, {}))
end)
it('should clear cmdline message before echo', function()

View File

@@ -2163,7 +2163,7 @@ describe('API/win', function()
it('no memory leak with valid title and invalid footer', function()
eq(
'title/footer must be string or array',
"Invalid 'title/footer': expected String or Array, got Integer",
pcall_err(api.nvim_open_win, 0, false, {
relative = 'editor',
row = 10,
@@ -2179,7 +2179,7 @@ describe('API/win', function()
it('no memory leak with invalid title and valid footer', function()
eq(
'title/footer must be string or array',
"Invalid 'title/footer': expected String or Array, got Integer",
pcall_err(api.nvim_open_win, 0, false, {
relative = 'editor',
row = 10,
@@ -2523,11 +2523,11 @@ describe('API/win', function()
eq('below', config.split)
eq(
"non-float with 'win' requires at least 'split' or 'vertical'",
"Required: non-float with 'win' requires 'split' or 'vertical'",
pcall_err(api.nvim_win_set_config, 0, { win = 0 })
)
eq(
"non-float with 'win' requires at least 'split' or 'vertical'",
"Required: non-float with 'win' requires 'split' or 'vertical'",
pcall_err(api.nvim_win_set_config, 0, { win = 0, relative = '' })
)
@@ -3310,7 +3310,7 @@ describe('API/win', function()
eq(true, pcall(api.nvim_win_set_config, win, cfg))
cfg.noautocmd = false
eq(
"'noautocmd' cannot be changed with existing windows",
"'noautocmd' cannot be changed on existing window",
pcall_err(api.nvim_win_set_config, win, cfg)
)
end)
@@ -3596,13 +3596,13 @@ describe('API/win', function()
border = 'single',
})
eq(
'title/footer must be string or array',
"Invalid 'title/footer': expected String or Array, got Integer",
pcall_err(api.nvim_win_set_config, win, { title = 0 })
)
command('redraw!')
assert_alive()
eq(
'title/footer cannot be an empty array',
"Invalid 'title/footer': expected non-empty Array",
pcall_err(api.nvim_win_set_config, win, { title = {} })
)
command('redraw!')
@@ -3620,13 +3620,13 @@ describe('API/win', function()
border = 'single',
})
eq(
'title/footer must be string or array',
"Invalid 'title/footer': expected String or Array, got Integer",
pcall_err(api.nvim_win_set_config, win, { footer = 0 })
)
command('redraw!')
assert_alive()
eq(
'title/footer cannot be an empty array',
"Invalid 'title/footer': expected non-empty Array",
pcall_err(api.nvim_win_set_config, win, { footer = {} })
)
command('redraw!')
@@ -3651,7 +3651,7 @@ describe('API/win', function()
it('with valid title and invalid footer', function()
eq(
'title/footer must be string or array',
"Invalid 'title/footer': expected String or Array, got Integer",
pcall_err(api.nvim_win_set_config, win, {
title = { { 'NEW_TITLE' } },
footer = 0,
@@ -3664,7 +3664,7 @@ describe('API/win', function()
it('with invalid title and valid footer', function()
eq(
'title/footer must be string or array',
"Invalid 'title/footer': expected String or Array, got Integer",
pcall_err(api.nvim_win_set_config, win, {
title = 0,
footer = { { 'NEW_FOOTER' } },

View File

@@ -44,7 +44,7 @@ describe('luaeval(vim.api.…)', function()
it('transforms API error from nvim_win_set_cursor into lua error', function()
eq(
{ false, 'Argument "pos" must be a [row, col] array' },
{ false, "Invalid 'pos': expected [row, col] array" },
fn.luaeval('{pcall(vim.api.nvim_win_set_cursor, 0, {1, 2, 3})}')
)
-- Used to produce a memory leak due to a bug in nvim_win_set_cursor
@@ -58,7 +58,7 @@ describe('luaeval(vim.api.…)', function()
'transforms API error from nvim_win_set_cursor + same array as in first test into lua error',
function()
eq(
{ false, 'Argument "pos" must be a [row, col] array' },
{ false, "Invalid 'pos': expected [row, col] array" },
fn.luaeval('{pcall(vim.api.nvim_win_set_cursor, 0, {"b\\na"})}')
)
end

View File

@@ -2465,7 +2465,7 @@ describe('extmark decorations', function()
]])
eq(
'Invalid hl_group: hl_group item',
"Invalid 'hl_group': 'hl_group item'",
pcall_err(api.nvim_buf_set_extmark, 0, ns, 0, 0, { end_row = 1, hl_group = { 'Group1', 'Group2', { 'fail' } }, hl_eol = true })
)
end)

View File

@@ -44,6 +44,169 @@ describe('float window', function()
eq(1000, fn.win_getid())
end)
it('validation', 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("Required: 'relative' or 'external' when creating a float", pcall_err(api.nvim_open_win, buf, false, { win = 0 }))
eq(
"Conflict: 'vertical' not allowed with floating windows",
pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 2, relative = 'editor', row = 0, col = 0, vertical = true })
)
eq(
"Conflict: 'split' not allowed with floating windows",
pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 2, relative = 'editor', row = 0, col = 0, split = 'left' })
)
eq(
"Conflict: 'relative' not allowed with 'external'",
pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 2, relative = 'editor', row = 0, col = 0, external = true })
)
eq(
"Invalid 'relative': 'shell'",
pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 2, relative = 'shell', row = 0, col = 0 })
)
eq(
"Invalid 'anchor': 'bottom'",
pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 2, relative = 'editor', row = 0, col = 0, anchor = 'bottom' })
)
eq(
"Required: 'relative' requires 'row'/'col' or 'bufpos'",
pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 2, relative = 'editor' })
)
eq(
"Invalid 'width': expected positive Integer",
pcall_err(api.nvim_open_win, buf, false, { width = -1, height = 2, relative = 'editor', row = 0, col = 0 })
)
eq(
"Invalid 'height': expected positive Integer",
pcall_err(api.nvim_open_win, buf, false, { width = 20, height = -1, relative = 'editor', row = 0, col = 0 })
)
eq(
"Invalid 'height': expected positive Integer",
pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 0, relative = 'editor', row = 0, col = 0 })
)
eq("Required: 'width'", pcall_err(api.nvim_open_win, buf, false, { relative = 'editor', row = 0, col = 0 }))
eq("Required: 'height'", pcall_err(api.nvim_open_win, buf, false, { relative = 'editor', row = 0, col = 0, width = 2 }))
eq("Invalid 'split': 'up'", pcall_err(api.nvim_open_win, buf, false, { split = 'up' }))
eq(
"Invalid 'bufpos': expected [row, col] array",
pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 2, relative = 'editor', row = 0, col = 0, bufpos = { 0 } })
)
eq(
"Invalid 'zindex': expected positive Integer",
pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 2, relative = 'editor', row = 0, col = 0, zindex = 0 })
)
eq(
"Invalid 'zindex': expected positive Integer",
pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 2, relative = 'editor', row = 0, col = 0, zindex = -1 })
)
eq(
"Invalid 'style': 'bogus'",
pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 2, relative = 'editor', row = 0, col = 0, style = 'bogus' })
)
eq(
"Invalid 'border': 'bogus'",
pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 2, relative = 'editor', row = 0, col = 0, border = 'bogus' })
)
eq(
"Invalid 'border': expected 1, 2, 4, or 8 chars",
pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 2, relative = 'editor', row = 0, col = 0, border = { '', '', '' } })
)
eq(
"Invalid 'border': expected 1 or 2-item Array",
pcall_err(
api.nvim_open_win,
buf,
false,
{ width = 20, height = 2, relative = 'editor', row = 0, col = 0, border = { { 'a', 'b', 'c' } } }
)
)
eq(
"Invalid 'border': expected Array of Strings",
pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 2, relative = 'editor', row = 0, col = 0, border = { { 1 } } })
)
eq(
"Invalid 'border': expected String or Array, got Integer",
pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 2, relative = 'editor', row = 0, col = 0, border = { 42 } })
)
eq(
"Invalid 'border': expected only one-cell chars",
pcall_err(api.nvim_open_win, buf, false, { width = 20, height = 2, relative = 'editor', row = 0, col = 0, border = { 'aa' } })
)
eq(
"Invalid 'border': expected corner char between edge chars",
pcall_err(
api.nvim_open_win,
buf,
false,
{ width = 20, height = 2, relative = 'editor', row = 0, col = 0, border = { '', '-', '', '|', '', '-', '', '|' } }
)
)
-- title_pos/footer_pos validation
eq(
"Invalid 'title_pos': 'bogus'",
pcall_err(
api.nvim_open_win,
buf,
false,
{ width = 20, height = 2, relative = 'editor', row = 0, col = 0, border = 'single', title = 'T', title_pos = 'bogus' }
)
)
eq(
"Invalid 'footer_pos': 'bogus'",
pcall_err(
api.nvim_open_win,
buf,
false,
{ width = 20, height = 2, relative = 'editor', row = 0, col = 0, border = 'single', footer = 'F', footer_pos = 'bogus' }
)
)
eq(
"Required: 'footer' requires 'footer_pos'",
pcall_err(
api.nvim_open_win,
buf,
false,
{ relative = 'editor', width = 9, height = 2, row = 2, col = 5, border = 'single', footer_pos = 'left' }
)
)
eq(
"Required: 'title' requires 'title_pos'",
pcall_err(
api.nvim_open_win,
buf,
false,
{ relative = 'editor', width = 9, height = 2, row = 2, col = 5, border = 'single', title_pos = 'left' }
)
)
end)
it('validation: split window', function()
local buf = api.nvim_create_buf(false, true)
eq("Conflict: 'zindex' not allowed with non-float window", pcall_err(api.nvim_open_win, buf, false, { split = 'left', zindex = 100 }))
eq("Conflict: 'title' not allowed with non-float window", pcall_err(api.nvim_open_win, buf, false, { split = 'left', title = 'T' }))
eq("Conflict: 'footer' not allowed with non-float window", pcall_err(api.nvim_open_win, buf, false, { split = 'left', footer = 'F' }))
eq(
"Conflict: 'border' not allowed with non-float window",
pcall_err(api.nvim_open_win, buf, false, { split = 'left', border = 'single' })
)
eq("Conflict: 'row' not allowed with non-float window", pcall_err(api.nvim_open_win, buf, true, { split = 'right', row = 10 }))
eq("Conflict: 'col' not allowed with non-float window", pcall_err(api.nvim_open_win, buf, true, { split = 'right', col = 10 }))
eq(
"Conflict: 'bufpos' not allowed with non-float window",
pcall_err(api.nvim_open_win, buf, true, { split = 'right', bufpos = { 0, 0 } })
)
local winid = api.nvim_open_win(buf, true, { split = 'right' })
eq("Conflict: 'row' not allowed with non-float window", pcall_err(api.nvim_win_set_config, winid, { split = 'right', row = 10 }))
eq("Conflict: 'col' not allowed with non-float window", pcall_err(api.nvim_win_set_config, winid, { split = 'right', col = 10 }))
eq(
"Conflict: 'bufpos' not allowed with non-float window",
pcall_err(api.nvim_win_set_config, winid, { split = 'right', bufpos = { 0, 0 } })
)
end)
it('win_execute() should work', function()
local buf = api.nvim_create_buf(false, false)
api.nvim_buf_set_lines(buf, 0, -1, true, { 'the floatwin', 'abc', 'def' })
@@ -221,23 +384,12 @@ describe('float window', function()
eq({ 14, 12 }, { pos[1], pos[2] })
end)
it('error message when invalid field specified for split', function()
local bufnr = api.nvim_create_buf(false, true)
eq("non-float cannot have 'row'", pcall_err(api.nvim_open_win, bufnr, true, { split = 'right', row = 10 }))
eq("non-float cannot have 'col'", pcall_err(api.nvim_open_win, bufnr, true, { split = 'right', col = 10 }))
eq("non-float cannot have 'bufpos'", pcall_err(api.nvim_open_win, bufnr, true, { split = 'right', bufpos = { 0, 0 } }))
local winid = api.nvim_open_win(bufnr, true, { split = 'right' })
eq("non-float cannot have 'row'", pcall_err(api.nvim_win_set_config, winid, { split = 'right', row = 10 }))
eq("non-float cannot have 'col'", pcall_err(api.nvim_win_set_config, winid, { split = 'right', col = 10 }))
eq("non-float cannot have 'bufpos'", pcall_err(api.nvim_win_set_config, winid, { split = 'right', bufpos = { 0, 0 } }))
end)
it('error message when reconfig missing relative field', function()
it('error when reconfig missing relative field', function()
local bufnr = api.nvim_create_buf(false, true)
local opts = { width = 10, height = 10, col = 5, row = 5, relative = 'editor', style = 'minimal' }
local winid = api.nvim_open_win(bufnr, true, opts)
eq(
"Missing 'relative' field when reconfiguring floating window 1001",
"Required: 'relative' when reconfiguring floating window 1001",
pcall_err(api.nvim_win_set_config, winid, { width = 3, height = 3, row = 10, col = 10 })
)
end)
@@ -2093,23 +2245,7 @@ describe('float window', function()
end
end)
it('validates title title_pos', function()
local buf = api.nvim_create_buf(false, false)
eq(
'title_pos requires title to be set',
pcall_err(api.nvim_open_win, buf, false, {
relative = 'editor',
width = 9,
height = 2,
row = 2,
col = 5,
border = 'single',
title_pos = 'left',
})
)
end)
it('validate title_pos in nvim_win_get_config', function()
it('nvim_win_get_config.title_pos', function()
local title_pos = exec_lua([[
local bufnr = vim.api.nvim_create_buf(false, false)
local opts = {
@@ -2130,23 +2266,7 @@ describe('float window', function()
eq('center', title_pos)
end)
it('validates footer footer_pos', function()
local buf = api.nvim_create_buf(false, false)
eq(
'footer_pos requires footer to be set',
pcall_err(api.nvim_open_win, buf, false, {
relative = 'editor',
width = 9,
height = 2,
row = 2,
col = 5,
border = 'single',
footer_pos = 'left',
})
)
end)
it('validate footer_pos in nvim_win_get_config', function()
it('nvim_win_get_config.footer_pos', function()
local footer_pos = exec_lua([[
local bufnr = vim.api.nvim_create_buf(false, false)
local opts = {
@@ -3779,56 +3899,15 @@ describe('float window', function()
end)
end)
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("Must specify 'relative' or 'external' when creating a float", pcall_err(api.nvim_open_win, buf, false, { 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 })
)
eq(
"Invalid value of 'relative' key",
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' })
)
eq(
"'relative' requires 'row'/'col' or 'bufpos'",
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 })
)
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 })
)
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 })
)
eq("Must specify 'width'", 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 }))
it('validation: multigrid', function()
if multigrid then
local buf = api.nvim_create_buf(false, false)
eq(
"external window cannot have 'win'",
"Conflict: 'win' not allowed with external window",
pcall_err(api.nvim_open_win, buf, false, { external = true, win = 0, width = 10, height = 10 })
)
api.nvim_open_win(buf, true, { external = true, width = 10, height = 10 })
eq("external window cannot have 'win'", pcall_err(api.nvim_win_set_config, 0, { win = 0 }))
eq("Conflict: 'win' not allowed with external window", pcall_err(api.nvim_win_set_config, 0, { win = 0 }))
-- OK to include "win" if external window is also reconfigured to a normal float.
api.nvim_win_set_config(0, { relative = 'editor', win = 0, row = 0, col = 0, width = 5, height = 5 })
eq('editor', api.nvim_win_get_config(0).relative)

View File

@@ -535,7 +535,7 @@ describe('ui/ext_popupmenu', function()
it('an error occurs when ext_popupmenu is false', function()
api.nvim_ui_pum_set_height(1)
screen:set_option('ext_popupmenu', false)
eq('It must support the ext_popupmenu option', pcall_err(api.nvim_ui_pum_set_height, 1))
eq('UI must support the ext_popupmenu option', pcall_err(api.nvim_ui_pum_set_height, 1))
end)
end)