diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index f2275befc4..108283c0e7 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -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 diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index f8ba22ee6c..3057cd8998 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -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 diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index ecbb8fd74c..f75076f66a 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -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, { diff --git a/src/nvim/api/events.c b/src/nvim/api/events.c index 2df152795b..a1cd21ed67 100644 --- a/src/nvim/api/events.c +++ b/src/nvim/api/events.c @@ -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); diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 4f45e2ccc4..9cf2c396c2 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -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 }; diff --git a/src/nvim/api/private/validate.c b/src/nvim/api/private/validate.c index 82ffa9707c..7e0262e2e0 100644 --- a/src/nvim/api/private/validate.c +++ b/src/nvim/api/private/validate.c @@ -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); diff --git a/src/nvim/api/private/validate.h b/src/nvim/api/private/validate.h index cf3785e9f3..be32ef4ede 100644 --- a/src/nvim/api/private/validate.h +++ b/src/nvim/api/private/validate.h @@ -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" diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 10abe24a68..136ba6c94e 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -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; } diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index a28fca5d3d..63c01bf519 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -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; } diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index cf1092fa02..b4f5efec23 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -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: diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index b7235ab5cb..b46bb76bb6 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -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; diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index bacd2fb67c..4d72eba7e4 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -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; diff --git a/test/functional/api/buffer_updates_spec.lua b/test/functional/api/buffer_updates_spec.lua index 09a81f2ea1..64c60b4684 100644 --- a/test/functional/api/buffer_updates_spec.lua +++ b/test/functional/api/buffer_updates_spec.lua @@ -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 diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index 0dc3cc7bec..3245eba80c 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -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' } } }, diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index 9cd6b3125a..bc7e80377e 100644 --- a/test/functional/api/highlight_spec.lua +++ b/test/functional/api/highlight_spec.lua @@ -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() diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua index 5ec0261a9b..7cb752ade1 100644 --- a/test/functional/api/keymap_spec.lua +++ b/test/functional/api/keymap_spec.lua @@ -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 option key', function() + -- 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 diff --git a/test/functional/api/ui_spec.lua b/test/functional/api/ui_spec.lua index 79a991d458..c456cd4bb6 100644 --- a/test/functional/api/ui_spec.lua +++ b/test/functional/api/ui_spec.lua @@ -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", diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 92a7c8af25..77d86d7353 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -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() diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index bd7189e084..daf0ae8ce1 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -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' } }, diff --git a/test/functional/lua/api_spec.lua b/test/functional/lua/api_spec.lua index 1712fa2a5d..3bc66de0e6 100644 --- a/test/functional/lua/api_spec.lua +++ b/test/functional/lua/api_spec.lua @@ -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 diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 421808e745..911969d14c 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -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) diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index c9453e2b32..e5bf766000 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -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) diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 9b1323d03e..148b1716ff 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -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)