aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/nvim/api/autocmd.c267
-rw-r--r--src/nvim/api/buffer.c166
-rw-r--r--src/nvim/api/command.c119
-rw-r--r--src/nvim/api/extmark.c305
-rw-r--r--src/nvim/api/options.c142
-rw-r--r--src/nvim/api/private/helpers.c33
-rw-r--r--src/nvim/api/private/validate.c28
-rw-r--r--src/nvim/api/private/validate.h68
-rw-r--r--src/nvim/api/ui.c66
-rw-r--r--src/nvim/api/vim.c212
-rw-r--r--src/nvim/buffer_defs.h50
-rw-r--r--src/nvim/drawline.c51
-rw-r--r--src/nvim/fold.h11
-rw-r--r--src/nvim/fold_defs.h17
-rw-r--r--src/nvim/highlight.c60
-rw-r--r--src/nvim/option.c9
-rw-r--r--src/nvim/statusline.c49
-rw-r--r--src/nvim/statusline_defs.h53
-rw-r--r--src/nvim/ui.c9
-rw-r--r--test/functional/api/autocmd_spec.lua116
-rw-r--r--test/functional/api/buffer_spec.lua20
-rw-r--r--test/functional/api/buffer_updates_spec.lua2
-rw-r--r--test/functional/api/command_spec.lua2
-rw-r--r--test/functional/api/extmark_spec.lua14
-rw-r--r--test/functional/api/highlight_spec.lua67
-rw-r--r--test/functional/api/proc_spec.lua4
-rw-r--r--test/functional/api/ui_spec.lua26
-rw-r--r--test/functional/api/vim_spec.lua56
-rw-r--r--test/functional/lua/api_spec.lua2
-rw-r--r--test/functional/lua/vim_spec.lua4
-rw-r--r--test/functional/ui/bufhl_spec.lua4
-rw-r--r--test/functional/ui/screen.lua2
-rw-r--r--test/functional/ui/statuscolumn_spec.lua2
33 files changed, 1005 insertions, 1031 deletions
diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
index f801c716e5..36d3e04f54 100644
--- a/src/nvim/api/autocmd.c
+++ b/src/nvim/api/autocmd.c
@@ -12,6 +12,7 @@
#include "nvim/api/autocmd.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/ascii.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
@@ -107,25 +108,24 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
break;
case kObjectTypeString:
group = augroup_find(opts->group.data.string.data);
- if (group < 0) {
- api_set_error(err, kErrorTypeValidation, "invalid augroup passed.");
+ VALIDATE_S((group >= 0), "group", "", {
goto cleanup;
- }
+ });
break;
case kObjectTypeInteger:
group = (int)opts->group.data.integer;
char *name = augroup_name(group);
- if (!augroup_exists(name)) {
- api_set_error(err, kErrorTypeValidation, "invalid augroup passed.");
+ VALIDATE_S(augroup_exists(name), "group", "", {
goto cleanup;
- }
+ });
break;
default:
- api_set_error(err, kErrorTypeValidation, "group must be a string or an integer.");
- goto cleanup;
+ VALIDATE_S(false, "group (must be string or integer)", "", {
+ goto cleanup;
+ });
}
- if (opts->event.type != kObjectTypeNil) {
+ if (HAS_KEY(opts->event)) {
check_event = true;
Object v = opts->event;
@@ -134,57 +134,50 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
event_set[event_nr] = true;
} else if (v.type == kObjectTypeArray) {
FOREACH_ITEM(v.data.array, event_v, {
- if (event_v.type != kObjectTypeString) {
- api_set_error(err,
- kErrorTypeValidation,
- "Every event must be a string in 'event'");
+ VALIDATE_T("event item", kObjectTypeString, event_v.type, {
goto cleanup;
- }
+ });
GET_ONE_EVENT(event_nr, event_v, cleanup);
event_set[event_nr] = true;
})
} else {
- api_set_error(err,
- kErrorTypeValidation,
- "Not a valid 'event' value. Must be a string or an array");
- goto cleanup;
+ VALIDATE_S(false, "event (must be String or Array)", "", {
+ goto cleanup;
+ });
}
}
- if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) {
- api_set_error(err, kErrorTypeValidation,
- "Cannot use both 'pattern' and 'buffer'");
+ VALIDATE((!HAS_KEY(opts->pattern) || !HAS_KEY(opts->buffer)),
+ "%s", "Cannot use both 'pattern' and 'buffer'", {
goto cleanup;
- }
+ });
int pattern_filter_count = 0;
- if (opts->pattern.type != kObjectTypeNil) {
+ if (HAS_KEY(opts->pattern)) {
Object v = opts->pattern;
if (v.type == kObjectTypeString) {
pattern_filters[pattern_filter_count] = v.data.string.data;
pattern_filter_count += 1;
} else if (v.type == kObjectTypeArray) {
if (v.data.array.size > AUCMD_MAX_PATTERNS) {
- api_set_error(err, kErrorTypeValidation,
- "Too many patterns. Please limit yourself to %d or fewer",
+ api_set_error(err, kErrorTypeValidation, "Too many patterns (maximum of %d)",
AUCMD_MAX_PATTERNS);
goto cleanup;
}
FOREACH_ITEM(v.data.array, item, {
- if (item.type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'pattern': must be a string");
+ VALIDATE_T("pattern", kObjectTypeString, item.type, {
goto cleanup;
- }
+ });
pattern_filters[pattern_filter_count] = item.data.string.data;
pattern_filter_count += 1;
});
} else {
- api_set_error(err, kErrorTypeValidation,
- "Not a valid 'pattern' value. Must be a string or an array");
- goto cleanup;
+ VALIDATE_EXP(false, "pattern", "String or Array", api_typename(v.type), {
+ goto cleanup;
+ });
}
}
@@ -198,17 +191,16 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
ADD(buffers, CSTR_TO_OBJ(pattern_buflocal));
} else if (opts->buffer.type == kObjectTypeArray) {
if (opts->buffer.data.array.size > AUCMD_MAX_PATTERNS) {
- api_set_error(err,
- kErrorTypeValidation,
- "Too many buffers. Please limit yourself to %d or fewer", AUCMD_MAX_PATTERNS);
+ api_set_error(err, kErrorTypeValidation, "Too many buffers (maximum of %d)",
+ AUCMD_MAX_PATTERNS);
goto cleanup;
}
FOREACH_ITEM(opts->buffer.data.array, bufnr, {
- if (bufnr.type != kObjectTypeInteger && bufnr.type != kObjectTypeBuffer) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'buffer': must be an integer");
+ VALIDATE_EXP((bufnr.type == kObjectTypeInteger || bufnr.type == kObjectTypeBuffer),
+ "buffer", "Integer", api_typename(bufnr.type), {
goto cleanup;
- }
+ });
buf_T *buf = find_buffer_by_handle((Buffer)bufnr.data.integer, err);
if (ERROR_SET(err)) {
@@ -218,10 +210,10 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
snprintf(pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle);
ADD(buffers, CSTR_TO_OBJ(pattern_buflocal));
});
- } else if (opts->buffer.type != kObjectTypeNil) {
- api_set_error(err, kErrorTypeValidation,
- "Invalid value for 'buffer': must be an integer or array of integers");
- goto cleanup;
+ } else if (HAS_KEY(opts->buffer)) {
+ VALIDATE_EXP(false, "buffer", "Integer or Array", api_typename(opts->buffer.type), {
+ goto cleanup;
+ });
}
FOREACH_ITEM(buffers, bufnr, {
@@ -421,10 +413,8 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
{
int64_t autocmd_id = -1;
char *desc = NULL;
-
Array patterns = ARRAY_DICT_INIT;
Array event_array = ARRAY_DICT_INIT;
-
AucmdExecutable aucmd = AUCMD_EXECUTABLE_INIT;
Callback cb = CALLBACK_NONE;
@@ -432,30 +422,23 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
goto cleanup;
}
- if (opts->callback.type != kObjectTypeNil && opts->command.type != kObjectTypeNil) {
- api_set_error(err, kErrorTypeValidation, "specify either 'callback' or 'command', not both");
+ VALIDATE((!HAS_KEY(opts->callback) || !HAS_KEY(opts->command)),
+ "%s", "Cannot use both 'callback' and 'command'", {
goto cleanup;
- } else if (opts->callback.type != kObjectTypeNil) {
- // TODO(tjdevries): It's possible we could accept callable tables,
- // but we don't do that many other places, so for the moment let's
- // not do that.
+ });
+
+ if (HAS_KEY(opts->callback)) {
+ // NOTE: We could accept callable tables, but that isn't common in the API.
Object *callback = &opts->callback;
switch (callback->type) {
case kObjectTypeLuaRef:
- if (callback->data.luaref == LUA_NOREF) {
- api_set_error(err,
- kErrorTypeValidation,
- "must pass an actual value");
+ VALIDATE_S((callback->data.luaref != LUA_NOREF), "callback", "<no value>", {
goto cleanup;
- }
-
- if (!nlua_ref_is_function(callback->data.luaref)) {
- api_set_error(err,
- kErrorTypeValidation,
- "must pass a function for callback");
+ });
+ VALIDATE_S(nlua_ref_is_function(callback->data.luaref), "callback", "<not a function>", {
goto cleanup;
- }
+ });
cb.type = kCallbackLua;
cb.data.luaref = api_new_luaref(callback->data.luaref);
@@ -465,28 +448,25 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
cb.data.funcref = string_to_cstr(callback->data.string);
break;
default:
- api_set_error(err,
- kErrorTypeException,
- "'callback' must be a lua function or name of vim function");
- goto cleanup;
+ VALIDATE_EXP(false, "callback", "Lua function or Vim function name",
+ api_typename(callback->type), {
+ goto cleanup;
+ });
}
aucmd.type = CALLABLE_CB;
aucmd.callable.cb = cb;
- } else if (opts->command.type != kObjectTypeNil) {
+ } else if (HAS_KEY(opts->command)) {
Object *command = &opts->command;
- if (command->type == kObjectTypeString) {
- aucmd.type = CALLABLE_EX;
- aucmd.callable.cmd = string_to_cstr(command->data.string);
- } else {
- api_set_error(err,
- kErrorTypeValidation,
- "'command' must be a string");
+ VALIDATE_T("command", kObjectTypeString, command->type, {
goto cleanup;
- }
+ });
+ aucmd.type = CALLABLE_EX;
+ aucmd.callable.cmd = string_to_cstr(command->data.string);
} else {
- api_set_error(err, kErrorTypeValidation, "must pass one of: 'command', 'callback'");
- goto cleanup;
+ VALIDATE(false, "%s", "Required: 'command' or 'callback'", {
+ goto cleanup;
+ });
}
bool is_once = api_object_to_bool(opts->once, "once", false, err);
@@ -501,25 +481,20 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
goto cleanup;
}
- if (opts->desc.type != kObjectTypeNil) {
- if (opts->desc.type == kObjectTypeString) {
- desc = opts->desc.data.string.data;
- } else {
- api_set_error(err,
- kErrorTypeValidation,
- "'desc' must be a string");
+ if (HAS_KEY(opts->desc)) {
+ VALIDATE_T("desc", kObjectTypeString, opts->desc.type, {
goto cleanup;
- }
+ });
+ desc = opts->desc.data.string.data;
}
if (patterns.size == 0) {
ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("*")));
}
- if (event_array.size == 0) {
- api_set_error(err, kErrorTypeValidation, "'event' is a required key");
+ VALIDATE_R((event_array.size > 0), "event", {
goto cleanup;
- }
+ });
autocmd_id = next_autocmd_id++;
FOREACH_ITEM(event_array, event_str, {
@@ -564,10 +539,9 @@ cleanup:
void nvim_del_autocmd(Integer id, Error *err)
FUNC_API_SINCE(9)
{
- if (id <= 0) {
- api_set_error(err, kErrorTypeException, "Invalid autocmd id");
+ VALIDATE_INT((id > 0), "autocmd id", id, {
return;
- }
+ });
if (!autocmd_delete_id(id)) {
api_set_error(err, kErrorTypeException, "Failed to delete autocmd");
}
@@ -610,11 +584,10 @@ void nvim_clear_autocmds(Dict(clear_autocmds) *opts, Error *err)
goto cleanup;
}
- if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) {
- api_set_error(err, kErrorTypeValidation,
- "Cannot use both 'pattern' and 'buffer'");
+ VALIDATE((!HAS_KEY(opts->pattern) || !HAS_KEY(opts->buffer)),
+ "%s", "Cannot use both 'pattern' and 'buffer'", {
goto cleanup;
- }
+ });
int au_group = get_augroup_from_object(opts->group, err);
if (au_group == AUGROUP_ERROR) {
@@ -772,32 +745,29 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err)
break;
case kObjectTypeString:
au_group = augroup_find(opts->group.data.string.data);
- if (au_group == AUGROUP_ERROR) {
- api_set_error(err,
- kErrorTypeValidation,
- "invalid augroup: %s", opts->group.data.string.data);
+ VALIDATE_S((au_group != AUGROUP_ERROR), "group", opts->group.data.string.data, {
goto cleanup;
- }
+ });
break;
case kObjectTypeInteger:
au_group = (int)opts->group.data.integer;
char *name = augroup_name(au_group);
- if (!augroup_exists(name)) {
- api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group);
+ VALIDATE_INT(augroup_exists(name), "group", (int64_t)au_group, {
goto cleanup;
- }
+ });
break;
default:
- api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer.");
- goto cleanup;
+ VALIDATE_EXP(false, "group", "String or Integer", api_typename(opts->group.type), {
+ goto cleanup;
+ });
}
- if (opts->buffer.type != kObjectTypeNil) {
+ if (HAS_KEY(opts->buffer)) {
Object buf_obj = opts->buffer;
- if (buf_obj.type != kObjectTypeInteger && buf_obj.type != kObjectTypeBuffer) {
- api_set_error(err, kErrorTypeException, "invalid buffer: %d", buf_obj.type);
+ VALIDATE_EXP((buf_obj.type == kObjectTypeInteger || buf_obj.type == kObjectTypeBuffer),
+ "buffer", "Integer", api_typename(buf_obj.type), {
goto cleanup;
- }
+ });
buf = find_buffer_by_handle((Buffer)buf_obj.data.integer, err);
@@ -814,7 +784,7 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err)
ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("")));
}
- if (opts->data.type != kObjectTypeNil) {
+ if (HAS_KEY(opts->data)) {
data = &opts->data;
}
@@ -825,7 +795,7 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err)
GET_ONE_EVENT(event_nr, event_str, cleanup)
FOREACH_ITEM(patterns, pat, {
- char *fname = opts->buffer.type == kObjectTypeNil ? pat.data.string.data : NULL;
+ char *fname = !HAS_KEY(opts->buffer) ? pat.data.string.data : NULL;
did_aucmd |=
apply_autocmds_group(event_nr, fname, NULL, true, au_group, buf, NULL, data);
})
@@ -840,45 +810,19 @@ cleanup:
api_free_array(patterns);
}
-static bool check_autocmd_string_array(Array arr, char *k, Error *err)
-{
- FOREACH_ITEM(arr, entry, {
- if (entry.type != kObjectTypeString) {
- api_set_error(err,
- kErrorTypeValidation,
- "All entries in '%s' must be strings",
- k);
- return false;
- }
-
- // Disallow newlines in the middle of the line.
- const String l = entry.data.string;
- if (memchr(l.data, NL, l.size)) {
- api_set_error(err, kErrorTypeValidation,
- "String cannot contain newlines");
- return false;
- }
- })
- return true;
-}
-
static bool unpack_string_or_array(Array *array, Object *v, char *k, bool required, Error *err)
{
if (v->type == kObjectTypeString) {
ADD(*array, copy_object(*v, NULL));
} else if (v->type == kObjectTypeArray) {
- if (!check_autocmd_string_array(v->data.array, k, err)) {
+ if (!check_string_array(v->data.array, k, true, err)) {
return false;
}
*array = copy_array(v->data.array, NULL);
} else {
- if (required) {
- api_set_error(err,
- kErrorTypeValidation,
- "'%s' must be an array or a string.",
- k);
+ VALIDATE_EXP(!required, k, "Array or String", api_typename(v->type), {
return false;
- }
+ });
}
return true;
@@ -894,27 +838,22 @@ static int get_augroup_from_object(Object group, Error *err)
return AUGROUP_DEFAULT;
case kObjectTypeString:
au_group = augroup_find(group.data.string.data);
- if (au_group == AUGROUP_ERROR) {
- api_set_error(err,
- kErrorTypeValidation,
- "invalid augroup: %s", group.data.string.data);
-
+ VALIDATE_S((au_group != AUGROUP_ERROR), "group", group.data.string.data, {
return AUGROUP_ERROR;
- }
+ });
return au_group;
case kObjectTypeInteger:
au_group = (int)group.data.integer;
char *name = augroup_name(au_group);
- if (!augroup_exists(name)) {
- api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group);
+ VALIDATE_INT(augroup_exists(name), "group", (int64_t)au_group, {
return AUGROUP_ERROR;
- }
-
+ });
return au_group;
default:
- api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer.");
- return AUGROUP_ERROR;
+ VALIDATE_EXP(false, "group", "String or Integer", api_typename(group.type), {
+ return AUGROUP_ERROR;
+ });
}
}
@@ -923,11 +862,12 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob
{
const char pattern_buflocal[BUFLOCAL_PAT_LEN];
- if (pattern.type != kObjectTypeNil && buffer.type != kObjectTypeNil) {
- api_set_error(err, kErrorTypeValidation,
- "cannot pass both: 'pattern' and 'buffer' for the same autocmd");
+ VALIDATE((!HAS_KEY(pattern) || !HAS_KEY(buffer)),
+ "%s", "Cannot use both 'pattern' and 'buffer' for the same autocmd", {
return false;
- } else if (pattern.type != kObjectTypeNil) {
+ });
+
+ if (HAS_KEY(pattern)) {
Object *v = &pattern;
if (v->type == kObjectTypeString) {
@@ -940,7 +880,7 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob
patlen = aucmd_pattern_length(pat);
}
} else if (v->type == kObjectTypeArray) {
- if (!check_autocmd_string_array(v->data.array, "pattern", err)) {
+ if (!check_string_array(v->data.array, "pattern", true, err)) {
return false;
}
@@ -956,18 +896,15 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob
}
})
} else {
- api_set_error(err,
- kErrorTypeValidation,
- "'pattern' must be a string or table");
- return false;
+ VALIDATE_EXP(false, "pattern", "String or Table", api_typename(v->type), {
+ return false;
+ });
}
- } else if (buffer.type != kObjectTypeNil) {
- if (buffer.type != kObjectTypeInteger && buffer.type != kObjectTypeBuffer) {
- api_set_error(err,
- kErrorTypeValidation,
- "'buffer' must be an integer");
+ } else if (HAS_KEY(buffer)) {
+ VALIDATE_EXP((buffer.type == kObjectTypeInteger || buffer.type == kObjectTypeBuffer),
+ "buffer", "Integer", api_typename(buffer.type), {
return false;
- }
+ });
buf_T *buf = find_buffer_by_handle((Buffer)buffer.data.integer, err);
if (ERROR_SET(err)) {
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index fe9e6077d6..9c2d14d30f 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -16,6 +16,7 @@
#include "nvim/api/buffer.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/ascii.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
@@ -179,11 +180,9 @@ Boolean nvim_buf_attach(uint64_t channel_id, Buffer buffer, Boolean send_buffer,
if (is_lua) {
for (size_t j = 0; cbs[j].name; j++) {
if (strequal(cbs[j].name, k.data)) {
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation,
- "%s is not a function", cbs[j].name);
+ VALIDATE_T(cbs[j].name, kObjectTypeLuaRef, v->type, {
goto error;
- }
+ });
*(cbs[j].dest) = v->data.luaref;
v->data.luaref = LUA_NOREF;
key_used = true;
@@ -194,26 +193,23 @@ Boolean nvim_buf_attach(uint64_t channel_id, Buffer buffer, Boolean send_buffer,
if (key_used) {
continue;
} else if (strequal("utf_sizes", k.data)) {
- if (v->type != kObjectTypeBoolean) {
- api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean");
+ VALIDATE_T("utf_sizes", kObjectTypeBoolean, v->type, {
goto error;
- }
+ });
cb.utf_sizes = v->data.boolean;
key_used = true;
} else if (strequal("preview", k.data)) {
- if (v->type != kObjectTypeBoolean) {
- api_set_error(err, kErrorTypeValidation, "preview must be boolean");
+ VALIDATE_T("preview", kObjectTypeBoolean, v->type, {
goto error;
- }
+ });
cb.preview = v->data.boolean;
key_used = true;
}
}
- if (!key_used) {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ VALIDATE_S(key_used, "key", k.data, {
goto error;
- }
+ });
}
return buf_updates_register(buf, channel_id, cb, send_buffer);
@@ -297,10 +293,9 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id,
start = normalize_index(buf, start, true, &oob);
end = normalize_index(buf, end, true, &oob);
- if (strict_indexing && oob) {
- api_set_error(err, kErrorTypeValidation, "Index out of bounds");
+ VALIDATE((!strict_indexing || !oob), "%s", "Index out of bounds", {
return rv;
- }
+ });
if (start >= end) {
// Return 0-length array
@@ -325,28 +320,6 @@ end:
return rv;
}
-static bool check_string_array(Array arr, bool disallow_nl, Error *err)
-{
- for (size_t i = 0; i < arr.size; i++) {
- if (arr.items[i].type != kObjectTypeString) {
- api_set_error(err,
- kErrorTypeValidation,
- "All items in the replacement array must be strings");
- return false;
- }
- // Disallow newlines in the middle of the line.
- if (disallow_nl) {
- const String l = arr.items[i].data.string;
- if (memchr(l.data, NL, l.size)) {
- api_set_error(err, kErrorTypeValidation,
- "String cannot contain newlines");
- return false;
- }
- }
- }
- return true;
-}
-
/// Sets (replaces) a line-range in the buffer.
///
/// Indexing is zero-based, end-exclusive. Negative indices are interpreted
@@ -383,20 +356,15 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
start = normalize_index(buf, start, true, &oob);
end = normalize_index(buf, end, true, &oob);
- if (strict_indexing && oob) {
- api_set_error(err, kErrorTypeValidation, "Index out of bounds");
+ VALIDATE((!strict_indexing || !oob), "%s", "Index out of bounds", {
return;
- }
-
- if (start > end) {
- api_set_error(err,
- kErrorTypeValidation,
- "Argument \"start\" is higher than \"end\"");
+ });
+ VALIDATE((start <= end), "%s", "'start' is higher than 'end'", {
return;
- }
+ });
bool disallow_nl = (channel_id != VIML_INTERNAL_CALL);
- if (!check_string_array(replacement, disallow_nl, err)) {
+ if (!check_string_array(replacement, "replacement string", disallow_nl, err)) {
return;
}
@@ -453,10 +421,9 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
for (size_t i = 0; i < to_replace; i++) {
int64_t lnum = start + (int64_t)i;
- if (lnum >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Index value is too high");
+ VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", {
goto end;
- }
+ });
if (ml_replace((linenr_T)lnum, lines[i], false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to replace line");
@@ -473,10 +440,9 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
for (size_t i = to_replace; i < new_len; i++) {
int64_t lnum = start + (int64_t)i - 1;
- if (lnum >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Index value is too high");
+ VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", {
goto end;
- }
+ });
if (ml_append((linenr_T)lnum, lines[i], 0, false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to insert line");
@@ -563,16 +529,14 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
// check range is ordered and everything!
// start_row, end_row within buffer len (except add text past the end?)
start_row = normalize_index(buf, start_row, false, &oob);
- if (oob) {
- api_set_error(err, kErrorTypeValidation, "start_row out of bounds");
+ VALIDATE_RANGE((!oob), "start_row", {
return;
- }
+ });
end_row = normalize_index(buf, end_row, false, &oob);
- if (oob) {
- api_set_error(err, kErrorTypeValidation, "end_row out of bounds");
+ VALIDATE_RANGE((!oob), "end_row", {
return;
- }
+ });
char *str_at_start = NULL;
char *str_at_end = NULL;
@@ -580,26 +544,24 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
// Another call to ml_get_buf() may free the line, so make a copy.
str_at_start = xstrdup(ml_get_buf(buf, (linenr_T)start_row, false));
size_t len_at_start = strlen(str_at_start);
- if (start_col < 0 || (size_t)start_col > len_at_start) {
- api_set_error(err, kErrorTypeValidation, "start_col out of bounds");
+ VALIDATE_RANGE((start_col >= 0 && (size_t)start_col <= len_at_start), "start_col", {
goto early_end;
- }
+ });
// Another call to ml_get_buf() may free the line, so make a copy.
str_at_end = xstrdup(ml_get_buf(buf, (linenr_T)end_row, false));
size_t len_at_end = strlen(str_at_end);
- if (end_col < 0 || (size_t)end_col > len_at_end) {
- api_set_error(err, kErrorTypeValidation, "end_col out of bounds");
+ VALIDATE_RANGE((end_col >= 0 && (size_t)end_col <= len_at_end), "end_col", {
goto early_end;
- }
+ });
- if (start_row > end_row || (end_row == start_row && start_col > end_col)) {
- api_set_error(err, kErrorTypeValidation, "start is higher than end");
+ VALIDATE((start_row <= end_row && !(end_row == start_row && start_col > end_col)),
+ "%s", "'start' is higher than 'end'", {
goto early_end;
- }
+ });
bool disallow_nl = (channel_id != VIML_INTERNAL_CALL);
- if (!check_string_array(replacement, disallow_nl, err)) {
+ if (!check_string_array(replacement, "replacement string", disallow_nl, err)) {
goto early_end;
}
@@ -702,10 +664,9 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
for (size_t i = 0; i < to_replace; i++) {
int64_t lnum = start_row + (int64_t)i;
- if (lnum >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Index value is too high");
+ VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", {
goto end;
- }
+ });
if (ml_replace((linenr_T)lnum, lines[i], false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to replace line");
@@ -720,10 +681,9 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
for (size_t i = to_replace; i < new_len; i++) {
int64_t lnum = start_row + (int64_t)i - 1;
- if (lnum >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Index value is too high");
+ VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", {
goto end;
- }
+ });
if (ml_append((linenr_T)lnum, lines[i], 0, false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to insert line");
@@ -800,10 +760,9 @@ ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer,
{
Array rv = ARRAY_DICT_INIT;
- if (opts.size > 0) {
- api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ VALIDATE((opts.size == 0), "%s", "opts dict isn't empty", {
return rv;
- }
+ });
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -820,18 +779,16 @@ ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer,
start_row = normalize_index(buf, start_row, false, &oob);
end_row = normalize_index(buf, end_row, false, &oob);
- if (oob) {
- api_set_error(err, kErrorTypeValidation, "Index out of bounds");
+ VALIDATE((!oob), "%s", "Index out of bounds", {
return rv;
- }
+ });
// nvim_buf_get_lines doesn't care if the start row is greater than the end
// row (it will just return an empty array), but nvim_buf_get_text does in
// order to maintain symmetry with nvim_buf_set_text.
- if (start_row > end_row) {
- api_set_error(err, kErrorTypeValidation, "start is higher than end");
+ VALIDATE((start_row <= end_row), "%s", "'start' is higher than 'end'", {
return rv;
- }
+ });
bool replace_nl = (channel_id != VIML_INTERNAL_CALL);
@@ -907,10 +864,9 @@ Integer nvim_buf_get_offset(Buffer buffer, Integer index, Error *err)
return -1;
}
- if (index < 0 || index > buf->b_ml.ml_line_count) {
- api_set_error(err, kErrorTypeValidation, "Index out of bounds");
+ VALIDATE((index >= 0 && index <= buf->b_ml.ml_line_count), "%s", "Index out of bounds", {
return 0;
- }
+ });
return ml_find_line_or_offset(buf, (int)index + 1, NULL, true);
}
@@ -1118,8 +1074,9 @@ void nvim_buf_delete(Buffer buffer, Dictionary opts, Error *err)
} else if (strequal("unload", k.data)) {
unload = api_object_to_bool(v, "unload", false, err);
} else {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
- return;
+ VALIDATE_S(false, "key", k.data, {
+ return;
+ });
}
}
@@ -1174,20 +1131,16 @@ Boolean nvim_buf_del_mark(Buffer buffer, String name, Error *err)
return res;
}
- if (name.size != 1) {
- api_set_error(err, kErrorTypeValidation,
- "Mark name must be a single character");
+ VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, {
return res;
- }
+ });
fmark_T *fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, *name.data);
// fm is NULL when there's no mark with the given name
- if (fm == NULL) {
- api_set_error(err, kErrorTypeValidation, "Invalid mark name: '%c'",
- *name.data);
+ VALIDATE_S((fm != NULL), "mark name", name.data, {
return res;
- }
+ });
// mark.lnum is 0 when the mark is not valid in the buffer, or is not set.
if (fm->mark.lnum != 0 && fm->fnum == buf->handle) {
@@ -1224,11 +1177,9 @@ Boolean nvim_buf_set_mark(Buffer buffer, String name, Integer line, Integer col,
return res;
}
- if (name.size != 1) {
- api_set_error(err, kErrorTypeValidation,
- "Mark name must be a single character");
+ VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, {
return res;
- }
+ });
res = set_mark(buf, name, line, col, err);
@@ -1257,21 +1208,18 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
return rv;
}
- if (name.size != 1) {
- api_set_error(err, kErrorTypeValidation,
- "Mark name must be a single character");
+ VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, {
return rv;
- }
+ });
fmark_T *fm;
pos_T pos;
char mark = *name.data;
fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, mark);
- if (fm == NULL) {
- api_set_error(err, kErrorTypeValidation, "Invalid mark name");
+ VALIDATE_S((fm != NULL), "mark name", name.data, {
return rv;
- }
+ });
// (0, 0) uppercase/file mark set in another buffer.
if (fm->fnum != buf->handle) {
pos.lnum = 0;
diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c
index f09a00e5c2..cae3927dfd 100644
--- a/src/nvim/api/command.c
+++ b/src/nvim/api/command.c
@@ -11,6 +11,7 @@
#include "nvim/api/command.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/ascii.h"
#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
@@ -99,10 +100,9 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
{
Dictionary result = ARRAY_DICT_INIT;
- if (opts.size > 0) {
- api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ VALIDATE((opts.size == 0), "%s", "opts dict isn't empty", {
return result;
- }
+ });
// Parse command line
exarg_T ea;
@@ -998,7 +998,7 @@ void nvim_buf_del_user_command(Buffer buffer, String name, Error *err)
}
}
- api_set_error(err, kErrorTypeException, "No such user-defined command: %s", name.data);
+ api_set_error(err, kErrorTypeException, "Invalid command (not found): %s", name.data);
}
void create_user_command(String name, Object command, Dict(user_command) *opts, int flags,
@@ -1014,20 +1014,17 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
LuaRef compl_luaref = LUA_NOREF;
LuaRef preview_luaref = LUA_NOREF;
- if (!uc_validate_name(name.data)) {
- api_set_error(err, kErrorTypeValidation, "Invalid command name");
+ VALIDATE_S(uc_validate_name(name.data), "command name", name.data, {
goto err;
- }
-
- if (mb_islower(name.data[0])) {
- api_set_error(err, kErrorTypeValidation, "'name' must begin with an uppercase letter");
+ });
+ VALIDATE_S(!mb_islower(name.data[0]), "command name (must begin with an uppercase letter)",
+ name.data, {
goto err;
- }
-
- if (HAS_KEY(opts->range) && HAS_KEY(opts->count)) {
- api_set_error(err, kErrorTypeValidation, "'range' and 'count' are mutually exclusive");
+ });
+ VALIDATE((!HAS_KEY(opts->range) || !HAS_KEY(opts->count)), "%s",
+ "Cannot use both 'range' and 'count'", {
goto err;
- }
+ });
if (opts->nargs.type == kObjectTypeInteger) {
switch (opts->nargs.data.integer) {
@@ -1038,14 +1035,14 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
argt |= EX_EXTRA | EX_NOSPC | EX_NEEDARG;
break;
default:
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
- goto err;
+ VALIDATE_INT(false, "nargs", (int64_t)opts->nargs.data.integer, {
+ goto err;
+ });
}
} else if (opts->nargs.type == kObjectTypeString) {
- if (opts->nargs.data.string.size > 1) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
+ VALIDATE_S((opts->nargs.data.string.size <= 1), "nargs", opts->nargs.data.string.data, {
goto err;
- }
+ });
switch (opts->nargs.data.string.data[0]) {
case '*':
@@ -1058,18 +1055,19 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
argt |= EX_EXTRA | EX_NEEDARG;
break;
default:
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
- goto err;
+ VALIDATE_S(false, "nargs", opts->nargs.data.string.data, {
+ goto err;
+ });
}
} else if (HAS_KEY(opts->nargs)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
- goto err;
+ VALIDATE_S(false, "nargs", "", {
+ goto err;
+ });
}
- if (HAS_KEY(opts->complete) && !argt) {
- api_set_error(err, kErrorTypeValidation, "'complete' used without 'nargs'");
+ VALIDATE((!HAS_KEY(opts->complete) || argt), "%s", "'complete' used without 'nargs'", {
goto err;
- }
+ });
if (opts->range.type == kObjectTypeBoolean) {
if (opts->range.data.boolean) {
@@ -1077,20 +1075,20 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
addr_type_arg = ADDR_LINES;
}
} else if (opts->range.type == kObjectTypeString) {
- if (opts->range.data.string.data[0] == '%' && opts->range.data.string.size == 1) {
- argt |= EX_RANGE | EX_DFLALL;
- addr_type_arg = ADDR_LINES;
- } else {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'");
+ VALIDATE_S((opts->range.data.string.data[0] == '%' && opts->range.data.string.size == 1),
+ "range", "", {
goto err;
- }
+ });
+ argt |= EX_RANGE | EX_DFLALL;
+ addr_type_arg = ADDR_LINES;
} else if (opts->range.type == kObjectTypeInteger) {
argt |= EX_RANGE | EX_ZEROR;
def = opts->range.data.integer;
addr_type_arg = ADDR_LINES;
} else if (HAS_KEY(opts->range)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'");
- goto err;
+ VALIDATE_S(false, "range", "", {
+ goto err;
+ });
}
if (opts->count.type == kObjectTypeBoolean) {
@@ -1104,23 +1102,25 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
addr_type_arg = ADDR_OTHER;
def = opts->count.data.integer;
} else if (HAS_KEY(opts->count)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'count'");
- goto err;
+ VALIDATE_S(false, "count", "", {
+ goto err;
+ });
}
- if (opts->addr.type == kObjectTypeString) {
- if (parse_addr_type_arg(opts->addr.data.string.data, (int)opts->addr.data.string.size,
- &addr_type_arg) != OK) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'");
+ if (HAS_KEY(opts->addr)) {
+ VALIDATE_T("addr", kObjectTypeString, opts->addr.type, {
goto err;
- }
+ });
+
+ VALIDATE_S(OK == parse_addr_type_arg(opts->addr.data.string.data,
+ (int)opts->addr.data.string.size, &addr_type_arg), "addr",
+ opts->addr.data.string.data, {
+ goto err;
+ });
if (addr_type_arg != ADDR_LINES) {
argt |= EX_ZEROR;
}
- } else if (HAS_KEY(opts->addr)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'");
- goto err;
}
if (api_object_to_bool(opts->bang, "bang", false, err)) {
@@ -1156,23 +1156,25 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
compl = EXPAND_USER_LUA;
compl_luaref = api_new_luaref(opts->complete.data.luaref);
} else if (opts->complete.type == kObjectTypeString) {
- if (parse_compl_arg(opts->complete.data.string.data,
- (int)opts->complete.data.string.size, &compl, &argt,
- &compl_arg) != OK) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'");
+ VALIDATE_S(OK == parse_compl_arg(opts->complete.data.string.data,
+ (int)opts->complete.data.string.size, &compl, &argt,
+ &compl_arg),
+ "complete", opts->complete.data.string.data, {
goto err;
- }
+ });
} else if (HAS_KEY(opts->complete)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'");
- goto err;
+ VALIDATE(false, "%s", "Invalid complete: expected Function or String", {
+ goto err;
+ });
}
- if (opts->preview.type == kObjectTypeLuaRef) {
+ if (HAS_KEY(opts->preview)) {
+ VALIDATE_T("preview", kObjectTypeLuaRef, opts->preview.type, {
+ goto err;
+ });
+
argt |= EX_PREVIEW;
preview_luaref = api_new_luaref(opts->preview.data.luaref);
- } else if (HAS_KEY(opts->preview)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'preview'");
- goto err;
}
switch (command.type) {
@@ -1188,8 +1190,9 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
rep = command.data.string.data;
break;
default:
- api_set_error(err, kErrorTypeValidation, "'command' must be a string or Lua function");
- goto err;
+ VALIDATE(false, "%s", "Invalid command: expected Function or String", {
+ goto err;
+ });
}
if (uc_add_command(name.data, name.size, rep, argt, def, flags, compl, compl_arg, compl_luaref,
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index ab3b3485e4..15f759eddf 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -11,6 +11,7 @@
#include "nvim/api/extmark.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/decoration.h"
@@ -218,10 +219,9 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
return rv;
}
- if (!ns_initialized((uint32_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
return rv;
- }
+ });
bool details = false;
for (size_t i = 0; i < opts.size; i++) {
@@ -233,12 +233,14 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
} else if (v->type == kObjectTypeInteger) {
details = v->data.integer;
} else {
- api_set_error(err, kErrorTypeValidation, "details is not an boolean");
- return rv;
+ VALIDATE_EXP(false, "details", "Boolean or Integer", api_typename(v->type), {
+ return rv;
+ });
}
} else {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
- return rv;
+ VALIDATE_S(false, "key", k.data, {
+ return rv;
+ });
}
}
@@ -301,10 +303,9 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
return rv;
}
- if (!ns_initialized((uint32_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
return rv;
- }
+ });
Integer limit = -1;
bool details = false;
@@ -313,10 +314,9 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
String k = opts.items[i].key;
Object *v = &opts.items[i].value;
if (strequal("limit", k.data)) {
- if (v->type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation, "limit is not an integer");
+ VALIDATE_T("limit", kObjectTypeInteger, v->type, {
return rv;
- }
+ });
limit = v->data.integer;
} else if (strequal("details", k.data)) {
if (v->type == kObjectTypeBoolean) {
@@ -324,12 +324,14 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
} else if (v->type == kObjectTypeInteger) {
details = v->data.integer;
} else {
- api_set_error(err, kErrorTypeValidation, "details is not an boolean");
- return rv;
+ VALIDATE_EXP(false, "details", "Boolean or Integer", api_typename(v->type), {
+ return rv;
+ });
}
} else {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
- return rv;
+ VALIDATE_S(false, "key", k.data, {
+ return rv;
+ });
}
}
@@ -501,27 +503,26 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
goto error;
}
- if (!ns_initialized((uint32_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
goto error;
- }
+ });
uint32_t id = 0;
if (opts->id.type == kObjectTypeInteger && opts->id.data.integer > 0) {
id = (uint32_t)opts->id.data.integer;
} else if (HAS_KEY(opts->id)) {
- api_set_error(err, kErrorTypeValidation, "id is not a positive integer");
- goto error;
+ VALIDATE_S(false, "id (must be positive integer)", "", {
+ goto error;
+ });
}
int line2 = -1;
// For backward compatibility we support "end_line" as an alias for "end_row"
if (HAS_KEY(opts->end_line)) {
- if (HAS_KEY(opts->end_row)) {
- api_set_error(err, kErrorTypeValidation, "cannot use both end_row and end_line");
+ VALIDATE(!HAS_KEY(opts->end_row), "%s", "cannot use both 'end_row' and 'end_line'", {
goto error;
- }
+ });
opts->end_row = opts->end_line;
}
@@ -534,31 +535,29 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
bool strict = true;
OPTION_TO_BOOL(strict, strict, true);
- if (opts->end_row.type == kObjectTypeInteger) {
+ if (HAS_KEY(opts->end_row)) {
+ VALIDATE_T("end_row", kObjectTypeInteger, opts->end_row.type, {
+ goto error;
+ });
+
Integer val = opts->end_row.data.integer;
- if (val < 0 || (val > buf->b_ml.ml_line_count && strict)) {
- api_set_error(err, kErrorTypeValidation, "end_row value outside range");
+ VALIDATE_RANGE((val >= 0 && !(val > buf->b_ml.ml_line_count && strict)), "end_row", {
goto error;
- } else {
- line2 = (int)val;
- }
- } else if (HAS_KEY(opts->end_row)) {
- api_set_error(err, kErrorTypeValidation, "end_row is not an integer");
- goto error;
+ });
+ line2 = (int)val;
}
colnr_T col2 = -1;
- if (opts->end_col.type == kObjectTypeInteger) {
+ if (HAS_KEY(opts->end_col)) {
+ VALIDATE_T("end_col", kObjectTypeInteger, opts->end_col.type, {
+ goto error;
+ });
+
Integer val = opts->end_col.data.integer;
- if (val < 0 || val > MAXCOL) {
- api_set_error(err, kErrorTypeValidation, "end_col value outside range");
+ VALIDATE_RANGE((val >= 0 && val <= MAXCOL), "end_col", {
goto error;
- } else {
- col2 = (int)val;
- }
- } else if (HAS_KEY(opts->end_col)) {
- api_set_error(err, kErrorTypeValidation, "end_col is not an integer");
- goto error;
+ });
+ col2 = (int)val;
}
// uncrustify:off
@@ -588,31 +587,37 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
}
}
- if (opts->conceal.type == kObjectTypeString) {
+ if (HAS_KEY(opts->conceal)) {
+ VALIDATE_T("conceal", kObjectTypeString, opts->conceal.type, {
+ goto error;
+ });
+
String c = opts->conceal.data.string;
decor.conceal = true;
if (c.size) {
decor.conceal_char = utf_ptr2char(c.data);
}
has_decor = true;
- } else if (HAS_KEY(opts->conceal)) {
- api_set_error(err, kErrorTypeValidation, "conceal is not a String");
- goto error;
}
- if (opts->virt_text.type == kObjectTypeArray) {
+ if (HAS_KEY(opts->virt_text)) {
+ VALIDATE_T("virt_text", kObjectTypeArray, opts->virt_text.type, {
+ goto error;
+ });
+
decor.virt_text = parse_virt_text(opts->virt_text.data.array, err,
&decor.virt_text_width);
has_decor = true;
if (ERROR_SET(err)) {
goto error;
}
- } else if (HAS_KEY(opts->virt_text)) {
- api_set_error(err, kErrorTypeValidation, "virt_text is not an Array");
- goto error;
}
- if (opts->virt_text_pos.type == kObjectTypeString) {
+ if (HAS_KEY(opts->virt_text_pos)) {
+ VALIDATE_T("virt_text_pos", kObjectTypeString, opts->virt_text_pos.type, {
+ goto error;
+ });
+
String str = opts->virt_text_pos.data.string;
if (strequal("eol", str.data)) {
decor.virt_text_pos = kVTEndOfLine;
@@ -621,21 +626,19 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
} else if (strequal("right_align", str.data)) {
decor.virt_text_pos = kVTRightAlign;
} else {
- api_set_error(err, kErrorTypeValidation, "virt_text_pos: invalid value");
- goto error;
+ VALIDATE_S(false, "virt_text_pos", "", {
+ goto error;
+ });
}
- } else if (HAS_KEY(opts->virt_text_pos)) {
- api_set_error(err, kErrorTypeValidation, "virt_text_pos is not a String");
- goto error;
}
- if (opts->virt_text_win_col.type == kObjectTypeInteger) {
+ if (HAS_KEY(opts->virt_text_win_col)) {
+ VALIDATE_T("virt_text_win_col", kObjectTypeInteger, opts->virt_text_win_col.type, {
+ goto error;
+ });
+
decor.col = (int)opts->virt_text_win_col.data.integer;
decor.virt_text_pos = kVTWinCol;
- } else if (HAS_KEY(opts->virt_text_win_col)) {
- api_set_error(err, kErrorTypeValidation,
- "virt_text_win_col is not a Number of the correct size");
- goto error;
}
OPTION_TO_BOOL(decor.virt_text_hide, virt_text_hide, false);
@@ -650,25 +653,29 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
} else if (strequal("blend", str.data)) {
decor.hl_mode = kHlModeBlend;
} else {
- api_set_error(err, kErrorTypeValidation,
- "virt_text_pos: invalid value");
- goto error;
+ VALIDATE_S(false, "virt_text_pos", "", {
+ goto error;
+ });
}
} else if (HAS_KEY(opts->hl_mode)) {
- api_set_error(err, kErrorTypeValidation, "hl_mode is not a String");
- goto error;
+ VALIDATE_T("hl_mode", kObjectTypeString, opts->hl_mode.type, {
+ goto error;
+ });
}
bool virt_lines_leftcol = false;
OPTION_TO_BOOL(virt_lines_leftcol, virt_lines_leftcol, false);
- if (opts->virt_lines.type == kObjectTypeArray) {
+ if (HAS_KEY(opts->virt_lines)) {
+ VALIDATE_T("virt_lines", kObjectTypeArray, opts->virt_lines.type, {
+ goto error;
+ });
+
Array a = opts->virt_lines.data.array;
for (size_t j = 0; j < a.size; j++) {
- if (a.items[j].type != kObjectTypeArray) {
- api_set_error(err, kErrorTypeValidation, "virt_text_line item is not an Array");
+ VALIDATE_T("virt_text_line", kObjectTypeArray, a.items[j].type, {
goto error;
- }
+ });
int dummig;
VirtText jtem = parse_virt_text(a.items[j].data.array, err, &dummig);
kv_push(decor.virt_lines, ((struct virt_line){ jtem, virt_lines_leftcol }));
@@ -677,36 +684,33 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
}
has_decor = true;
}
- } else if (HAS_KEY(opts->virt_lines)) {
- api_set_error(err, kErrorTypeValidation, "virt_lines is not an Array");
- goto error;
}
OPTION_TO_BOOL(decor.virt_lines_above, virt_lines_above, false);
- if (opts->priority.type == kObjectTypeInteger) {
+ if (HAS_KEY(opts->priority)) {
+ VALIDATE_T("priority", kObjectTypeInteger, opts->priority.type, {
+ goto error;
+ });
+
Integer val = opts->priority.data.integer;
- if (val < 0 || val > UINT16_MAX) {
- api_set_error(err, kErrorTypeValidation, "priority is not a valid value");
+ VALIDATE_RANGE((val >= 0 && val <= UINT16_MAX), "priority", {
goto error;
- }
+ });
decor.priority = (DecorPriority)val;
- } else if (HAS_KEY(opts->priority)) {
- api_set_error(err, kErrorTypeValidation, "priority is not a Number of the correct size");
- goto error;
}
- if (opts->sign_text.type == kObjectTypeString) {
- if (!init_sign_text(&decor.sign_text,
- opts->sign_text.data.string.data)) {
- api_set_error(err, kErrorTypeValidation, "sign_text is not a valid value");
+ if (HAS_KEY(opts->sign_text)) {
+ VALIDATE_T("sign_text", kObjectTypeString, opts->sign_text.type, {
goto error;
- }
+ });
+
+ VALIDATE_S(init_sign_text(&decor.sign_text, opts->sign_text.data.string.data),
+ "sign_text", "", {
+ goto error;
+ });
has_decor = true;
- } else if (HAS_KEY(opts->sign_text)) {
- api_set_error(err, kErrorTypeValidation, "sign_text is not a String");
- goto error;
}
bool right_gravity = true;
@@ -714,11 +718,10 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
// Only error out if they try to set end_right_gravity without
// setting end_col or end_row
- if (line2 == -1 && col2 == -1 && HAS_KEY(opts->end_right_gravity)) {
- api_set_error(err, kErrorTypeValidation,
- "cannot set end_right_gravity without setting end_row or end_col");
+ VALIDATE(!(line2 == -1 && col2 == -1 && HAS_KEY(opts->end_right_gravity)),
+ "%s", "cannot set end_right_gravity without end_row or end_col", {
goto error;
- }
+ });
bool end_right_gravity = false;
OPTION_TO_BOOL(end_right_gravity, end_right_gravity, false);
@@ -728,7 +731,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
bool ephemeral = false;
OPTION_TO_BOOL(ephemeral, ephemeral, false);
- if (opts->spell.type == kObjectTypeNil) {
+ if (!HAS_KEY(opts->spell)) {
decor.spell = kNone;
} else {
bool spell = false;
@@ -742,16 +745,15 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
has_decor = true;
}
- if (line < 0) {
- api_set_error(err, kErrorTypeValidation, "line value outside range");
+ VALIDATE_RANGE((line >= 0), "line", {
goto error;
- } else if (line > buf->b_ml.ml_line_count) {
- if (strict) {
- api_set_error(err, kErrorTypeValidation, "line value outside range");
+ });
+
+ if (line > buf->b_ml.ml_line_count) {
+ VALIDATE_RANGE(!strict, "line", {
goto error;
- } else {
- line = buf->b_ml.ml_line_count;
- }
+ });
+ line = buf->b_ml.ml_line_count;
} else if (line < buf->b_ml.ml_line_count) {
len = ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line + 1, false));
}
@@ -759,15 +761,14 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
if (col == -1) {
col = (Integer)len;
} else if (col > (Integer)len) {
- if (strict) {
- api_set_error(err, kErrorTypeValidation, "col value outside range");
+ VALIDATE_RANGE(!strict, "col", {
goto error;
- } else {
- col = (Integer)len;
- }
+ });
+ col = (Integer)len;
} else if (col < -1) {
- api_set_error(err, kErrorTypeValidation, "col value outside range");
- goto error;
+ VALIDATE_RANGE(false, "col", {
+ goto error;
+ });
}
if (col2 >= 0) {
@@ -781,12 +782,10 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
line2 = (int)line;
}
if (col2 > (Integer)len) {
- if (strict) {
- api_set_error(err, kErrorTypeValidation, "end_col value outside range");
+ VALIDATE_RANGE(!strict, "end_col", {
goto error;
- } else {
- col2 = (int)len;
- }
+ });
+ col2 = (int)len;
}
} else if (line2 >= 0) {
col2 = 0;
@@ -829,10 +828,9 @@ Boolean nvim_buf_del_extmark(Buffer buffer, Integer ns_id, Integer id, Error *er
if (!buf) {
return false;
}
- if (!ns_initialized((uint32_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
return false;
- }
+ });
return extmark_del(buf, (uint32_t)ns_id, (uint32_t)id);
}
@@ -887,14 +885,13 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In
return 0;
}
- if (line < 0 || line >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Line number outside range");
+ VALIDATE_RANGE((line >= 0 && line < MAXLNUM), "line number", {
return 0;
- }
- if (col_start < 0 || col_start > MAXCOL) {
- api_set_error(err, kErrorTypeValidation, "Column value outside range");
+ });
+ VALIDATE_RANGE((col_start >= 0 && col_start <= MAXCOL), "column", {
return 0;
- }
+ });
+
if (col_end < 0 || col_end > MAXCOL) {
col_end = MAXCOL;
}
@@ -950,10 +947,10 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
return;
}
- if (line_start < 0 || line_start >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Line number outside range");
+ VALIDATE_RANGE((line_start >= 0 && line_start < MAXLNUM), "line number", {
return;
- }
+ });
+
if (line_end < 0 || line_end > MAXLNUM) {
line_end = MAXLNUM;
}
@@ -1034,11 +1031,10 @@ void nvim_set_decoration_provider(Integer ns_id, Dict(set_decoration_provider) *
continue;
}
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation,
- "%s is not a function", cbs[i].name);
+ VALIDATE_T(cbs[i].name, kObjectTypeLuaRef, v->type, {
goto error;
- }
+ });
+
*(cbs[i].dest) = v->data.luaref;
v->data.luaref = LUA_NOREF;
}
@@ -1075,39 +1071,39 @@ static bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, in
*col = MAXCOL;
return true;
} else if (id < 0) {
- api_set_error(err, kErrorTypeValidation, "Mark id must be positive");
- return false;
+ VALIDATE_INT(false, "mark id", id, {
+ return false;
+ });
}
ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id);
- if (extmark.row >= 0) {
- *row = extmark.row;
- *col = extmark.col;
- return true;
- } else {
- api_set_error(err, kErrorTypeValidation, "No mark with requested id");
+
+ VALIDATE_INT((extmark.row >= 0), "mark id (not found)", id, {
return false;
- }
+ });
+ *row = extmark.row;
+ *col = extmark.col;
+ return true;
// Check if it is a position
} else if (obj.type == kObjectTypeArray) {
Array pos = obj.data.array;
- if (pos.size != 2
- || pos.items[0].type != kObjectTypeInteger
- || pos.items[1].type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation,
- "Position must have 2 integer elements");
+ VALIDATE((pos.size == 2
+ && pos.items[0].type == kObjectTypeInteger
+ && pos.items[1].type == kObjectTypeInteger),
+ "%s", "Invalid position: expected 2 Integer items", {
return false;
- }
+ });
+
Integer pos_row = pos.items[0].data.integer;
Integer pos_col = pos.items[1].data.integer;
*row = (int)(pos_row >= 0 ? pos_row : MAXLNUM);
*col = (colnr_T)(pos_col >= 0 ? pos_col : MAXCOL);
return true;
} else {
- api_set_error(err, kErrorTypeValidation,
- "Position must be a mark id Integer or position Array");
- return false;
+ VALIDATE(false, "%s", "Invalid position: expected mark id Integer or 2-item Array", {
+ return false;
+ });
}
}
// adapted from sign.c:sign_define_init_text.
@@ -1151,17 +1147,14 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width)
VirtText virt_text = KV_INITIAL_VALUE;
int w = 0;
for (size_t i = 0; i < chunks.size; i++) {
- if (chunks.items[i].type != kObjectTypeArray) {
- api_set_error(err, kErrorTypeValidation, "Chunk is not an array");
+ VALIDATE_T("chunk", kObjectTypeArray, chunks.items[i].type, {
goto free_exit;
- }
+ });
Array chunk = chunks.items[i].data.array;
- if (chunk.size == 0 || chunk.size > 2
- || chunk.items[0].type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation,
- "Chunk is not an array with one or two strings");
+ VALIDATE((chunk.size > 0 && chunk.size <= 2 && chunk.items[0].type == kObjectTypeString),
+ "%s", "Invalid chunk: expected Array with 1 or 2 Strings", {
goto free_exit;
- }
+ });
String str = chunk.items[0].data.string;
diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c
index 2a54c3b132..40720c31c7 100644
--- a/src/nvim/api/options.c
+++ b/src/nvim/api/options.c
@@ -9,6 +9,7 @@
#include "nvim/api/options.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
#include "nvim/eval/window.h"
@@ -25,54 +26,56 @@
static int validate_option_value_args(Dict(option) *opts, int *scope, int *opt_type, void **from,
Error *err)
{
- if (opts->scope.type == kObjectTypeString) {
+ if (HAS_KEY(opts->scope)) {
+ VALIDATE_T("scope", kObjectTypeString, opts->scope.type, {
+ return FAIL;
+ });
+
if (!strcmp(opts->scope.data.string.data, "local")) {
*scope = OPT_LOCAL;
} else if (!strcmp(opts->scope.data.string.data, "global")) {
*scope = OPT_GLOBAL;
} else {
- api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'");
- return FAIL;
+ VALIDATE(false, "%s", "Invalid scope: expected 'local' or 'global'", {
+ return FAIL;
+ });
}
- } else if (HAS_KEY(opts->scope)) {
- api_set_error(err, kErrorTypeValidation, "invalid value for key: scope");
- return FAIL;
}
*opt_type = SREQ_GLOBAL;
- if (opts->win.type == kObjectTypeInteger) {
+ if (HAS_KEY(opts->win)) {
+ VALIDATE_T("win", kObjectTypeInteger, opts->win.type, {
+ return FAIL;
+ });
+
*opt_type = SREQ_WIN;
*from = find_window_by_handle((int)opts->win.data.integer, err);
if (ERROR_SET(err)) {
return FAIL;
}
- } else if (HAS_KEY(opts->win)) {
- api_set_error(err, kErrorTypeValidation, "invalid value for key: win");
- return FAIL;
}
- if (opts->buf.type == kObjectTypeInteger) {
+ if (HAS_KEY(opts->buf)) {
+ VALIDATE_T("buf", kObjectTypeInteger, opts->buf.type, {
+ return FAIL;
+ });
+
*scope = OPT_LOCAL;
*opt_type = SREQ_BUF;
*from = find_buffer_by_handle((int)opts->buf.data.integer, err);
if (ERROR_SET(err)) {
return FAIL;
}
- } else if (HAS_KEY(opts->buf)) {
- api_set_error(err, kErrorTypeValidation, "invalid value for key: buf");
- return FAIL;
}
- if (HAS_KEY(opts->scope) && HAS_KEY(opts->buf)) {
- api_set_error(err, kErrorTypeValidation, "scope and buf cannot be used together");
+ VALIDATE((!HAS_KEY(opts->scope) || !HAS_KEY(opts->buf)), "%s",
+ "cannot use both 'scope' and 'buf'", {
return FAIL;
- }
-
- if (HAS_KEY(opts->win) && HAS_KEY(opts->buf)) {
- api_set_error(err, kErrorTypeValidation, "buf and win cannot be used together");
+ });
+ VALIDATE((!HAS_KEY(opts->win) || !HAS_KEY(opts->buf)), "%s", "cannot use both 'buf' and 'win'", {
return FAIL;
- }
+ });
return OK;
}
@@ -132,8 +135,9 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
}
break;
default:
- api_set_error(err, kErrorTypeValidation, "unknown option '%s'", name.data);
- return rv;
+ VALIDATE_S(false, "option", name.data, {
+ return rv;
+ });
}
return rv;
@@ -193,8 +197,9 @@ void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error
scope |= OPT_CLEAR;
break;
default:
- api_set_error(err, kErrorTypeValidation, "invalid value for option");
- return;
+ VALIDATE_EXP(false, "option type", "Integer, Boolean, or String", api_typename(value.type), {
+ return;
+ });
}
access_option_value_for(name.data, &numval, &stringval, scope, opt_type, to, false, err);
@@ -351,21 +356,18 @@ static Object get_option_from(void *from, int type, String name, Error *err)
{
Object rv = OBJECT_INIT;
- if (name.size == 0) {
- api_set_error(err, kErrorTypeValidation, "Empty option name");
+ VALIDATE_S(name.size > 0, "option name", "<empty>", {
return rv;
- }
+ });
// Return values
int64_t numval;
char *stringval = NULL;
- int flags = get_option_value_strict(name.data, &numval, &stringval, type, from);
- if (!flags) {
- api_set_error(err, kErrorTypeValidation, "Invalid option name: '%s'",
- name.data);
+ int flags = get_option_value_strict(name.data, &numval, &stringval, type, from);
+ VALIDATE_S(flags != 0, "option name", name.data, {
return rv;
- }
+ });
if (flags & SOPT_BOOL) {
rv.type = kObjectTypeBoolean;
@@ -374,20 +376,15 @@ static Object get_option_from(void *from, int type, String name, Error *err)
rv.type = kObjectTypeInteger;
rv.data.integer = numval;
} else if (flags & SOPT_STRING) {
- if (stringval) {
- rv.type = kObjectTypeString;
- rv.data.string.data = stringval;
- rv.data.string.size = strlen(stringval);
- } else {
- api_set_error(err, kErrorTypeException,
- "Failed to get value for option '%s'",
- name.data);
+ if (!stringval) {
+ api_set_error(err, kErrorTypeException, "Failed to get option '%s'", name.data);
+ return rv;
}
+ rv.type = kObjectTypeString;
+ rv.data.string.data = stringval;
+ rv.data.string.size = strlen(stringval);
} else {
- api_set_error(err,
- kErrorTypeException,
- "Unknown type for option '%s'",
- name.data);
+ api_set_error(err, kErrorTypeException, "Unknown type for option '%s'", name.data);
}
return rv;
@@ -402,29 +399,22 @@ static Object get_option_from(void *from, int type, String name, Error *err)
/// @param[out] err Details of an error that may have occurred
void set_option_to(uint64_t channel_id, void *to, int type, String name, Object value, Error *err)
{
- if (name.size == 0) {
- api_set_error(err, kErrorTypeValidation, "Empty option name");
+ VALIDATE_S(name.size > 0, "option name", "<empty>", {
return;
- }
+ });
int flags = get_option_value_strict(name.data, NULL, NULL, type, to);
-
- if (flags == 0) {
- api_set_error(err, kErrorTypeValidation, "Invalid option name '%s'",
- name.data);
+ VALIDATE_S(flags != 0, "option name", name.data, {
return;
- }
+ });
if (value.type == kObjectTypeNil) {
if (type == SREQ_GLOBAL) {
- api_set_error(err, kErrorTypeException, "Cannot unset option '%s'",
- name.data);
+ api_set_error(err, kErrorTypeException, "Cannot unset option '%s'", name.data);
return;
} else if (!(flags & SOPT_GLOBAL)) {
- api_set_error(err,
- kErrorTypeException,
- "Cannot unset option '%s' "
- "because it doesn't have a global value",
+ api_set_error(err, kErrorTypeException,
+ "Cannot unset option '%s' because it doesn't have a global value",
name.data);
return;
} else {
@@ -437,39 +427,23 @@ void set_option_to(uint64_t channel_id, void *to, int type, String name, Object
char *stringval = NULL;
if (flags & SOPT_BOOL) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(err,
- kErrorTypeValidation,
- "Option '%s' requires a Boolean value",
- name.data);
+ VALIDATE(value.type == kObjectTypeBoolean, "Option '%s' value must be Boolean", name.data, {
return;
- }
-
+ });
numval = value.data.boolean;
} else if (flags & SOPT_NUM) {
- if (value.type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation,
- "Option '%s' requires an integer value",
- name.data);
+ VALIDATE(value.type == kObjectTypeInteger, "Option '%s' value must be Integer", name.data, {
return;
- }
-
- if (value.data.integer > INT_MAX || value.data.integer < INT_MIN) {
- api_set_error(err, kErrorTypeValidation,
- "Value for option '%s' is out of range",
- name.data);
+ });
+ VALIDATE((value.data.integer <= INT_MAX && value.data.integer >= INT_MIN),
+ "Option '%s' value is out of range", name.data, {
return;
- }
-
+ });
numval = (int)value.data.integer;
} else {
- if (value.type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation,
- "Option '%s' requires a string value",
- name.data);
+ VALIDATE(value.type == kObjectTypeString, "Option '%s' value must be String", name.data, {
return;
- }
-
+ });
stringval = value.data.string.data;
}
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 519f2cc5bf..c996e19eb9 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -821,12 +821,41 @@ int object_to_hl_id(Object obj, const char *what, Error *err)
} else if (obj.type == kObjectTypeInteger) {
return MAX((int)obj.data.integer, 0);
} else {
- api_set_error(err, kErrorTypeValidation,
- "%s is not a valid highlight", what);
+ api_set_error(err, kErrorTypeValidation, "Invalid highlight: %s", what);
return 0;
}
}
+char *api_typename(ObjectType t)
+{
+ switch (t) {
+ case kObjectTypeNil:
+ return "nil";
+ case kObjectTypeBoolean:
+ return "Boolean";
+ case kObjectTypeInteger:
+ return "Integer";
+ case kObjectTypeFloat:
+ return "Float";
+ case kObjectTypeString:
+ return "String";
+ case kObjectTypeArray:
+ return "Array";
+ case kObjectTypeDictionary:
+ return "Dict";
+ case kObjectTypeLuaRef:
+ return "Function";
+ case kObjectTypeBuffer:
+ return "Buffer";
+ case kObjectTypeWindow:
+ return "Window";
+ case kObjectTypeTabpage:
+ return "Tabpage";
+ default:
+ abort();
+ }
+}
+
HlMessage parse_hl_msg(Array chunks, Error *err)
{
HlMessage hl_msg = KV_INITIAL_VALUE;
diff --git a/src/nvim/api/private/validate.c b/src/nvim/api/private/validate.c
new file mode 100644
index 0000000000..41c9472a39
--- /dev/null
+++ b/src/nvim/api/private/validate.c
@@ -0,0 +1,28 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/private/validate.c.generated.h"
+#endif
+
+bool check_string_array(Array arr, char *name, bool disallow_nl, Error *err)
+{
+ snprintf(IObuff, sizeof(IObuff), "'%s' item", name);
+ for (size_t i = 0; i < arr.size; i++) {
+ VALIDATE_T(IObuff, kObjectTypeString, arr.items[i].type, {
+ return false;
+ });
+ // Disallow newlines in the middle of the line.
+ if (disallow_nl) {
+ const String l = arr.items[i].data.string;
+ VALIDATE(!memchr(l.data, NL, l.size), "'%s' item contains newlines", name, {
+ return false;
+ });
+ }
+ }
+ return true;
+}
diff --git a/src/nvim/api/private/validate.h b/src/nvim/api/private/validate.h
new file mode 100644
index 0000000000..4a1b99408e
--- /dev/null
+++ b/src/nvim/api/private/validate.h
@@ -0,0 +1,68 @@
+#ifndef NVIM_API_PRIVATE_VALIDATE_H
+#define NVIM_API_PRIVATE_VALIDATE_H
+
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+
+#define VALIDATE_INT(cond, name, val_, code) \
+ do { \
+ if (!(cond)) { \
+ api_set_error(err, kErrorTypeValidation, "Invalid " name ": %" PRId64, val_); \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE_S(cond, name, val_, code) \
+ do { \
+ if (!(cond)) { \
+ if (strequal(val_, "")) { \
+ api_set_error(err, kErrorTypeValidation, "Invalid " name); \
+ } else { \
+ api_set_error(err, kErrorTypeValidation, "Invalid " name ": '%s'", val_); \
+ } \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE_EXP(cond, name, expected, actual, code) \
+ do { \
+ if (!(cond)) { \
+ api_set_error(err, kErrorTypeValidation, "Invalid %s: expected %s, got %s", \
+ name, expected, actual); \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE_T(name, expected_t, actual_t, code) \
+ do { \
+ if (expected_t != actual_t) { \
+ api_set_error(err, kErrorTypeValidation, "Invalid %s: expected %s, got %s", \
+ name, api_typename(expected_t), api_typename(actual_t)); \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE(cond, fmt_, fmt_arg1, code) \
+ do { \
+ if (!(cond)) { \
+ api_set_error(err, kErrorTypeValidation, fmt_, fmt_arg1); \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE_RANGE(cond, name, code) \
+ do { \
+ if (!(cond)) { \
+ api_set_error(err, kErrorTypeValidation, "Invalid '%s': out of range", name); \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE_R(cond, name, code) \
+ VALIDATE(cond, "Required: '%s'", name, code);
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/private/validate.h.generated.h"
+#endif
+
+#endif // NVIM_API_PRIVATE_VALIDATE_H
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index 8f5465db77..61f2c881b3 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -12,6 +12,7 @@
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/api/ui.h"
#include "nvim/autocmd.h"
#include "nvim/channel.h"
@@ -291,22 +292,20 @@ void nvim_ui_set_option(uint64_t channel_id, String name, Object value, Error *e
ui_set_option(ui, false, name, value, error);
}
-static void ui_set_option(UI *ui, bool init, String name, Object value, Error *error)
+static void ui_set_option(UI *ui, bool init, String name, Object value, Error *err)
{
if (strequal(name.data, "override")) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(error, kErrorTypeValidation, "override must be a Boolean");
+ VALIDATE_T("override", kObjectTypeBoolean, value.type, {
return;
- }
+ });
ui->override = value.data.boolean;
return;
}
if (strequal(name.data, "rgb")) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(error, kErrorTypeValidation, "rgb must be a Boolean");
+ VALIDATE_T("rgb", kObjectTypeBoolean, value.type, {
return;
- }
+ });
ui->rgb = value.data.boolean;
// A little drastic, but only takes effect for legacy uis. For linegrid UI
// only changes metadata for nvim_list_uis(), no refresh needed.
@@ -317,62 +316,56 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e
}
if (strequal(name.data, "term_name")) {
- if (value.type != kObjectTypeString) {
- api_set_error(error, kErrorTypeValidation, "term_name must be a String");
+ VALIDATE_T("term_name", kObjectTypeString, value.type, {
return;
- }
+ });
set_tty_option("term", string_to_cstr(value.data.string));
return;
}
if (strequal(name.data, "term_colors")) {
- if (value.type != kObjectTypeInteger) {
- api_set_error(error, kErrorTypeValidation, "term_colors must be a Integer");
+ VALIDATE_T("term_colors", kObjectTypeInteger, value.type, {
return;
- }
+ });
t_colors = (int)value.data.integer;
return;
}
if (strequal(name.data, "term_background")) {
- if (value.type != kObjectTypeString) {
- api_set_error(error, kErrorTypeValidation, "term_background must be a String");
+ VALIDATE_T("term_background", kObjectTypeString, value.type, {
return;
- }
+ });
set_tty_background(value.data.string.data);
return;
}
if (strequal(name.data, "stdin_fd")) {
- if (value.type != kObjectTypeInteger || value.data.integer < 0) {
- api_set_error(error, kErrorTypeValidation, "stdin_fd must be a non-negative Integer");
+ VALIDATE_T("stdin_fd", kObjectTypeInteger, value.type, {
return;
- }
-
- if (starting != NO_SCREEN) {
- api_set_error(error, kErrorTypeValidation,
- "stdin_fd can only be used with first attached ui");
+ });
+ VALIDATE_INT((value.data.integer >= 0), "stdin_fd", value.data.integer, {
return;
- }
+ });
+ VALIDATE((starting == NO_SCREEN), "%s", "stdin_fd can only be used with first attached UI", {
+ return;
+ });
stdin_fd = (int)value.data.integer;
return;
}
if (strequal(name.data, "stdin_tty")) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(error, kErrorTypeValidation, "stdin_tty must be a Boolean");
+ VALIDATE_T("stdin_tty", kObjectTypeBoolean, value.type, {
return;
- }
+ });
stdin_isatty = value.data.boolean;
return;
}
if (strequal(name.data, "stdout_tty")) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(error, kErrorTypeValidation, "stdout_tty must be a Boolean");
+ VALIDATE_T("stdout_tty", kObjectTypeBoolean, value.type, {
return;
- }
+ });
stdout_isatty = value.data.boolean;
return;
}
@@ -383,17 +376,15 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e
for (UIExtension i = 0; i < kUIExtCount; i++) {
if (strequal(name.data, ui_ext_names[i])
|| (i == kUIPopupmenu && is_popupmenu)) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(error, kErrorTypeValidation, "%s must be a Boolean",
- name.data);
+ VALIDATE_EXP((value.type == kObjectTypeBoolean), name.data, "Boolean",
+ api_typename(value.type), {
return;
- }
+ });
bool boolval = value.data.boolean;
if (!init && i == kUILinegrid && boolval != ui->ui_ext[i]) {
// There shouldn't be a reason for an UI to do this ever
// so explicitly don't support this.
- api_set_error(error, kErrorTypeValidation,
- "ext_linegrid option cannot be changed");
+ api_set_error(err, kErrorTypeValidation, "ext_linegrid option cannot be changed");
}
ui->ui_ext[i] = boolval;
if (!init) {
@@ -403,8 +394,7 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e
}
}
- api_set_error(error, kErrorTypeValidation, "No such UI option: %s",
- name.data);
+ api_set_error(err, kErrorTypeValidation, "No such UI option: %s", name.data);
}
/// Tell Nvim to resize a grid. Triggers a grid_resize event with the requested
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 12ab40a687..50683ac403 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -18,6 +18,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/vim.h"
#include "nvim/ascii.h"
#include "nvim/autocmd.h"
@@ -86,10 +87,9 @@ Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Arena *arena, Error *er
Dictionary result = ARRAY_DICT_INIT;
int id = syn_name2id(name.data);
- if (id == 0) {
- api_set_error(err, kErrorTypeException, "Invalid highlight name: %s", name.data);
+ VALIDATE_S((id != 0), "highlight name", name.data, {
return result;
- }
+ });
return nvim_get_hl_by_id(id, rgb, arena, err);
}
@@ -103,10 +103,9 @@ Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Arena *arena, Error *er
FUNC_API_SINCE(3)
{
Dictionary dic = ARRAY_DICT_INIT;
- if (syn_get_final_id((int)hl_id) == 0) {
- api_set_error(err, kErrorTypeException, "Invalid highlight id: %" PRId64, hl_id);
+ VALIDATE_INT((syn_get_final_id((int)hl_id) != 0), "highlight id", hl_id, {
return dic;
- }
+ });
int attrcode = syn_id2attr((int)hl_id);
return hl_get_attr_by_id(attrcode, rgb, arena, err);
}
@@ -173,10 +172,9 @@ void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err)
FUNC_API_SINCE(7)
{
int hl_id = syn_check_group(name.data, name.size);
- if (hl_id == 0) {
- api_set_error(err, kErrorTypeException, "Invalid highlight name: %s", name.data);
+ VALIDATE_S((hl_id != 0), "highlight name", name.data, {
return;
- }
+ });
int link_id = -1;
HlAttrs attrs = dict2hlattrs(val, true, &link_id, err);
@@ -193,10 +191,9 @@ void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err)
void nvim_set_hl_ns(Integer ns_id, Error *err)
FUNC_API_SINCE(10)
{
- if (ns_id < 0) {
- api_set_error(err, kErrorTypeValidation, "no such namespace");
+ VALIDATE_INT((ns_id >= 0), "namespace", ns_id, {
return;
- }
+ });
ns_hl_global = (NS)ns_id;
hl_check_ns();
@@ -403,11 +400,9 @@ void nvim_input_mouse(String button, String action, String modifier, Integer gri
continue;
}
int mod = name_to_mod_mask(byte);
- if (mod == 0) {
- api_set_error(err, kErrorTypeValidation,
- "invalid modifier %c", byte);
+ VALIDATE((mod != 0), "Invalid modifier: %c", byte, {
return;
- }
+ });
modmask |= mod;
}
@@ -500,10 +495,9 @@ Object nvim_notify(String msg, Integer log_level, Dictionary opts, Error *err)
Integer nvim_strwidth(String text, Error *err)
FUNC_API_SINCE(1)
{
- if (text.size > INT_MAX) {
- api_set_error(err, kErrorTypeValidation, "String is too long");
+ VALIDATE_S((text.size <= INT_MAX), "text length", "(too long)", {
return 0;
- }
+ });
return (Integer)mb_string2cells(text.data);
}
@@ -575,10 +569,7 @@ ArrayOf(String) nvim__get_runtime(Array pat, Boolean all, Dict(runtime) *opts, E
{
bool is_lua = api_object_to_bool(opts->is_lua, "is_lua", false, err);
bool source = api_object_to_bool(opts->do_source, "do_source", false, err);
- if (source && !nlua_is_deferred_safe()) {
- api_set_error(err, kErrorTypeValidation, "'do_source' cannot be used in fast callback");
- }
-
+ VALIDATE((!source || nlua_is_deferred_safe()), "%s", "'do_source' used in fast callback", {});
if (ERROR_SET(err)) {
return (Array)ARRAY_DICT_INIT;
}
@@ -602,10 +593,9 @@ ArrayOf(String) nvim__get_runtime(Array pat, Boolean all, Dict(runtime) *opts, E
void nvim_set_current_dir(String dir, Error *err)
FUNC_API_SINCE(1)
{
- if (dir.size >= MAXPATHL) {
- api_set_error(err, kErrorTypeValidation, "Directory name is too long");
+ VALIDATE_S((dir.size < MAXPATHL), "directory name", "(too long)", {
return;
- }
+ });
char string[MAXPATHL];
memcpy(string, dir.data, dir.size);
@@ -664,16 +654,14 @@ Object nvim_get_var(String name, Error *err)
{
dictitem_T *di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size);
if (di == NULL) { // try to autoload script
- if (!script_autoload(name.data, name.size, false) || aborting()) {
- api_set_error(err, kErrorTypeValidation, "Key not found: %s", name.data);
+ VALIDATE_S((script_autoload(name.data, name.size, false) && !aborting()), "key", name.data, {
return (Object)OBJECT_INIT;
- }
+ });
di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size);
}
- if (di == NULL) {
- api_set_error(err, kErrorTypeValidation, "Key not found: %s", name.data);
+ VALIDATE_S((di != NULL), "key (not found)", name.data, {
return (Object)OBJECT_INIT;
- }
+ });
return vim_to_object(&di->di_tv);
}
@@ -991,16 +979,14 @@ Integer nvim_open_term(Buffer buffer, DictionaryOf(LuaRef) opts, Error *err)
String k = opts.items[i].key;
Object *v = &opts.items[i].value;
if (strequal("on_input", k.data)) {
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation,
- "%s is not a function", "on_input");
+ VALIDATE_T("on_input", kObjectTypeLuaRef, v->type, {
return 0;
- }
+ });
cb = v->data.luaref;
v->data.luaref = LUA_NOREF;
break;
} else {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ VALIDATE_S(false, "key", k.data, {});
}
}
@@ -1075,9 +1061,7 @@ void nvim_chan_send(Integer chan, String data, Error *err)
channel_send((uint64_t)chan, data.data, data.size,
false, &error);
- if (error) {
- api_set_error(err, kErrorTypeValidation, "%s", error);
- }
+ VALIDATE(!error, "%s", error, {});
}
/// Gets the current list of tabpage handles.
@@ -1164,10 +1148,9 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err)
static bool draining = false;
bool cancel = false;
- if (phase < -1 || phase > 3) {
- api_set_error(err, kErrorTypeValidation, "Invalid phase: %" PRId64, phase);
+ VALIDATE_INT((phase >= -1 && phase <= 3), "phase", phase, {
return false;
- }
+ });
Array args = ARRAY_DICT_INIT;
Object rv = OBJECT_INIT;
if (phase == -1 || phase == 1) { // Start of paste-stream.
@@ -1234,20 +1217,17 @@ void nvim_put(ArrayOf(String) lines, String type, Boolean after, Boolean follow,
FUNC_API_CHECK_TEXTLOCK
{
yankreg_T *reg = xcalloc(1, sizeof(yankreg_T));
- if (!prepare_yankreg_from_object(reg, type, lines.size)) {
- api_set_error(err, kErrorTypeValidation, "Invalid type: '%s'", type.data);
+ VALIDATE_S((prepare_yankreg_from_object(reg, type, lines.size)), "type", type.data, {
goto cleanup;
- }
+ });
if (lines.size == 0) {
goto cleanup; // Nothing to do.
}
for (size_t i = 0; i < lines.size; i++) {
- if (lines.items[i].type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation,
- "Invalid lines (expected array of strings)");
+ VALIDATE_T("line", kObjectTypeString, lines.items[i].type, {
goto cleanup;
- }
+ });
String line = lines.items[i].data.string;
reg->y_array[i] = xmemdupz(line.data, line.size);
memchrsub(reg->y_array[i], NUL, NL, line.size);
@@ -1348,11 +1328,11 @@ Dictionary nvim_get_context(Dict(context) *opts, Error *err)
FUNC_API_SINCE(6)
{
Array types = ARRAY_DICT_INIT;
- if (opts->types.type == kObjectTypeArray) {
+ if (HAS_KEY(opts->types)) {
+ VALIDATE_T("types", kObjectTypeArray, opts->types.type, {
+ return (Dictionary)ARRAY_DICT_INIT;
+ });
types = opts->types.data.array;
- } else if (opts->types.type != kObjectTypeNil) {
- api_set_error(err, kErrorTypeValidation, "invalid value for key: types");
- return (Dictionary)ARRAY_DICT_INIT;
}
int int_types = types.size > 0 ? 0 : kCtxAll;
@@ -1373,8 +1353,9 @@ Dictionary nvim_get_context(Dict(context) *opts, Error *err)
} else if (strequal(s, "funcs")) {
int_types |= kCtxFuncs;
} else {
- api_set_error(err, kErrorTypeValidation, "unexpected type: %s", s);
- return (Dictionary)ARRAY_DICT_INIT;
+ VALIDATE_S(false, "type", s, {
+ return (Dictionary)ARRAY_DICT_INIT;
+ });
}
}
}
@@ -1651,34 +1632,20 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Arena *arena, Error *er
size_t i; // also used for freeing the variables
for (i = 0; i < calls.size; i++) {
- if (calls.items[i].type != kObjectTypeArray) {
- api_set_error(err,
- kErrorTypeValidation,
- "Items in calls array must be arrays");
+ VALIDATE_T("calls item", kObjectTypeArray, calls.items[i].type, {
goto theend;
- }
+ });
Array call = calls.items[i].data.array;
- if (call.size != 2) {
- api_set_error(err,
- kErrorTypeValidation,
- "Items in calls array must be arrays of size 2");
+ VALIDATE((call.size == 2), "%s", "calls item must be a 2-item Array", {
goto theend;
- }
-
- if (call.items[0].type != kObjectTypeString) {
- api_set_error(err,
- kErrorTypeValidation,
- "Name must be String");
+ });
+ VALIDATE_T("name", kObjectTypeString, call.items[0].type, {
goto theend;
- }
+ });
String name = call.items[0].data.string;
-
- if (call.items[1].type != kObjectTypeArray) {
- api_set_error(err,
- kErrorTypeValidation,
- "Args must be Array");
+ VALIDATE_T("args", kObjectTypeArray, call.items[1].type, {
goto theend;
- }
+ });
Array args = call.items[1].data.array;
MsgpackRpcRequestHandler handler =
@@ -1850,10 +1817,9 @@ Array nvim_get_proc_children(Integer pid, Error *err)
Array rvobj = ARRAY_DICT_INIT;
int *proc_list = NULL;
- if (pid <= 0 || pid > INT_MAX) {
- api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid);
+ VALIDATE_INT((pid > 0 && pid <= INT_MAX), "pid", pid, {
goto end;
- }
+ });
size_t proc_count;
int rv = os_proc_children((int)pid, &proc_list, &proc_count);
@@ -1892,10 +1858,10 @@ Object nvim_get_proc(Integer pid, Error *err)
rvobj.data.dictionary = (Dictionary)ARRAY_DICT_INIT;
rvobj.type = kObjectTypeDictionary;
- if (pid <= 0 || pid > INT_MAX) {
- api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid);
+ VALIDATE_INT((pid > 0 && pid <= INT_MAX), "pid", pid, {
return NIL;
- }
+ });
+
#ifdef MSWIN
rvobj.data.dictionary = os_proc_info((int)pid);
if (rvobj.data.dictionary.size == 0) { // Process not found.
@@ -1937,10 +1903,9 @@ void nvim_select_popupmenu_item(Integer item, Boolean insert, Boolean finish, Di
Error *err)
FUNC_API_SINCE(6)
{
- if (opts.size > 0) {
- api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ VALIDATE((opts.size == 0), "%s", "opts dict isn't empty", {
return;
- }
+ });
if (finish) {
insert = true;
@@ -1961,13 +1926,10 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Arena *arena, E
g = &pum_grid;
} else if (grid > 1) {
win_T *wp = get_win_by_grid_handle((handle_T)grid);
- if (wp != NULL && wp->w_grid_alloc.chars != NULL) {
- g = &wp->w_grid_alloc;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "No grid with the given handle");
+ VALIDATE_INT((wp != NULL && wp->w_grid_alloc.chars != NULL), "grid handle", grid, {
return ret;
- }
+ });
+ g = &wp->w_grid_alloc;
}
if (row < 0 || row >= g->rows
@@ -2009,20 +1971,16 @@ Boolean nvim_del_mark(String name, Error *err)
FUNC_API_SINCE(8)
{
bool res = false;
- if (name.size != 1) {
- api_set_error(err, kErrorTypeValidation,
- "Mark name must be a single character");
+ VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, {
return res;
- }
+ });
// Only allow file/uppercase marks
// TODO(muniter): Refactor this ASCII_ISUPPER macro to a proper function
- if (ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data)) {
- res = set_mark(NULL, name, 0, 0, err);
- } else {
- api_set_error(err, kErrorTypeValidation,
- "Only file/uppercase marks allowed, invalid mark name: '%c'",
- *name.data);
- }
+ VALIDATE_S((ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data)),
+ "mark name (must be file/uppercase)", name.data, {
+ return res;
+ });
+ res = set_mark(NULL, name, 0, 0, err);
return res;
}
@@ -2043,16 +2001,13 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err)
{
Array rv = ARRAY_DICT_INIT;
- if (name.size != 1) {
- api_set_error(err, kErrorTypeValidation,
- "Mark name must be a single character");
+ VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, {
return rv;
- } else if (!(ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data))) {
- api_set_error(err, kErrorTypeValidation,
- "Only file/uppercase marks allowed, invalid mark name: '%c'",
- *name.data);
+ });
+ VALIDATE_S((ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data)),
+ "mark name (must be file/uppercase)", name.data, {
return rv;
- }
+ });
xfmark_T *mark = mark_get_global(false, *name.data); // false avoids loading the mark buffer
pos_T pos = mark->fmark.mark;
@@ -2137,27 +2092,28 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
if (str.size < 2 || memcmp(str.data, "%!", 2) != 0) {
const char *const errmsg = check_stl_option(str.data);
- if (errmsg) {
- api_set_error(err, kErrorTypeValidation, "%s", errmsg);
+ VALIDATE(!errmsg, "%s", errmsg, {
return result;
- }
+ });
}
if (HAS_KEY(opts->winid)) {
- if (opts->winid.type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation, "winid must be an integer");
+ VALIDATE_T("winid", kObjectTypeInteger, opts->winid.type, {
return result;
- }
+ });
window = (Window)opts->winid.data.integer;
}
if (HAS_KEY(opts->fillchar)) {
- if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size == 0
- || ((size_t)utf_ptr2len(opts->fillchar.data.string.data)
- != opts->fillchar.data.string.size)) {
- api_set_error(err, kErrorTypeValidation, "fillchar must be a single character");
+ VALIDATE_T("fillchar", kObjectTypeString, opts->fillchar.type, {
return result;
- }
+ });
+ VALIDATE((opts->fillchar.data.string.size != 0
+ && ((size_t)utf_ptr2len(opts->fillchar.data.string.data)
+ == opts->fillchar.data.string.size)),
+ "%s", "Invalid fillchar: expected single character", {
+ return result;
+ });
fillchar = utf_ptr2char(opts->fillchar.data.string.data);
}
if (HAS_KEY(opts->highlights)) {
@@ -2181,10 +2137,9 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
return result;
}
}
- if (use_winbar && use_tabline) {
- api_set_error(err, kErrorTypeValidation, "use_winbar and use_tabline are mutually exclusive");
+ VALIDATE(!(use_winbar && use_tabline), "%s", "Cannot use both 'use_winbar' and 'use_tabline'", {
return result;
- }
+ });
win_T *wp, *ewp;
@@ -2211,10 +2166,9 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
}
if (HAS_KEY(opts->maxwidth)) {
- if (opts->maxwidth.type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation, "maxwidth must be an integer");
+ VALIDATE_T("maxwidth", kObjectTypeInteger, opts->maxwidth.type, {
return result;
- }
+ });
maxwidth = (int)opts->maxwidth.data.integer;
} else {
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index cace4b1364..05901893b9 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -337,36 +337,6 @@ struct mapblock {
bool m_replace_keycodes; // replace keycodes in result of expression
};
-/// Used for highlighting in the status line.
-typedef struct stl_hlrec stl_hlrec_t;
-struct stl_hlrec {
- char *start;
- int userhl; // 0: no HL, 1-9: User HL, < 0 for syn ID
-};
-
-/// Used for building the status line.
-typedef struct stl_item stl_item_t;
-struct stl_item {
- // Where the item starts in the status line output buffer
- char *start;
- // Function to run for ClickFunc items.
- char *cmd;
- // The minimum width of the item
- int minwid;
- // The maximum width of the item
- int maxwid;
- enum {
- Normal,
- Empty,
- Group,
- Separate,
- Highlight,
- TabPage,
- ClickFunc,
- Trunc,
- } type;
-};
-
// values for b_syn_spell: what to do with toplevel text
#define SYNSPL_DEFAULT 0 // spell check if @Spell not defined
#define SYNSPL_TOP 1 // spell check toplevel text
@@ -1420,26 +1390,6 @@ struct window_S {
size_t w_statuscol_click_defs_size;
};
-/// Struct to hold info for 'statuscolumn'
-typedef struct statuscol statuscol_T;
-
-struct statuscol {
- int width; ///< width of the status column
- int cur_attr; ///< current attributes in text
- int num_attr; ///< attributes used for line number
- int fold_attr; ///< attributes used for fold column
- int sign_attr[SIGN_SHOW_MAX + 1]; ///< attributes used for signs
- int truncate; ///< truncated width
- bool draw; ///< draw statuscolumn or not
- char fold_text[9 * 4 + 1]; ///< text in fold column (%C)
- char *sign_text[SIGN_SHOW_MAX + 1]; ///< text in sign column (%s)
- char text[MAXPATHL]; ///< text in status column
- char *textp; ///< current position in text
- char *text_end; ///< end of text (the NUL byte)
- stl_hlrec_t *hlrec; ///< highlight groups
- stl_hlrec_t *hlrecp; ///< current highlight group
-};
-
/// Macros defined in Vim, but not in Neovim
// uncrustify:off
#define CHANGEDTICK(buf) \
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c
index 3bd55dbe09..3cedb04bcb 100644
--- a/src/nvim/drawline.c
+++ b/src/nvim/drawline.c
@@ -404,37 +404,10 @@ static int get_sign_attrs(buf_T *buf, linenr_T lnum, SignTextAttrs *sattrs, int
/// the start of the buffer line "lnum" and once for the wrapped lines.
///
/// @param[out] stcp Status column attributes
-static void get_statuscol_str(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines,
- int cul_attr, int sign_num_attr, int sign_cul_attr, statuscol_T *stcp,
- foldinfo_T foldinfo, SignTextAttrs *sattrs)
+static void get_statuscol_str(win_T *wp, linenr_T lnum, int virtnum, statuscol_T *stcp)
{
- long relnum = -1;
- bool use_cul = use_cursor_line_sign(wp, lnum);
- int virtnum = row - startrow - filler_lines;
-
- // When called the first time for line "lnum" set num_attr
- if (stcp->num_attr == 0) {
- stcp->num_attr = sign_num_attr ? sign_num_attr
- : get_line_number_attr(wp, lnum, row, startrow, filler_lines);
- }
- // When called for the first non-filler row of line "lnum" set num v:vars and fold column
- if (virtnum == 0) {
- relnum = labs(get_cursor_rel_lnum(wp, lnum));
- if (compute_foldcolumn(wp, 0)) {
- size_t n = fill_foldcolumn(stcp->fold_text, wp, foldinfo, lnum);
- stcp->fold_text[n] = NUL;
- stcp->fold_attr = win_hl_attr(wp, use_cul ? HLF_CLF : HLF_FC);
- }
- }
- // Make sure to clear->set->clear sign column for filler->first->wrapped lines
- int i = 0;
- for (; i < wp->w_scwidth; i++) {
- SignTextAttrs *sattr = virtnum ? NULL : sign_get_attr(i, sattrs, wp->w_scwidth);
- stcp->sign_text[i] = sattr && sattr->text ? sattr->text : " ";
- stcp->sign_attr[i] = sattr ? (use_cul && sign_cul_attr ? sign_cul_attr : sattr->hl_attr_id)
- : win_hl_attr(wp, use_cul ? HLF_CLS : HLF_SC);
- }
- stcp->sign_text[i] = NULL;
+ // When called for the first non-filler row of line "lnum" set num v:vars
+ long relnum = virtnum == 0 ? labs(get_cursor_rel_lnum(wp, lnum)) : -1;
// When a buffer's line count has changed, make a best estimate for the full
// width of the status column by building with "w_nrwidth_line_count". Add
@@ -442,14 +415,12 @@ static void get_statuscol_str(win_T *wp, linenr_T lnum, int row, int startrow, i
if (wp->w_statuscol_line_count != wp->w_nrwidth_line_count) {
wp->w_statuscol_line_count = wp->w_nrwidth_line_count;
set_vim_var_nr(VV_VIRTNUM, 0);
- build_statuscol_str(wp, wp->w_nrwidth_line_count, 0, stcp->width,
- ' ', stcp->text, &stcp->hlrec, stcp);
+ build_statuscol_str(wp, wp->w_nrwidth_line_count, 0, stcp);
stcp->width += stcp->truncate;
}
set_vim_var_nr(VV_VIRTNUM, virtnum);
- int width = build_statuscol_str(wp, lnum, relnum, stcp->width,
- ' ', stcp->text, &stcp->hlrec, stcp);
+ int width = build_statuscol_str(wp, lnum, relnum, stcp);
// Force a redraw in case of error or when truncated
if (*wp->w_p_stc == NUL || (stcp->truncate > 0 && wp->w_nrwidth < MAX_NUMBERWIDTH)) {
if (stcp->truncate) { // Avoid truncating 'statuscolumn'
@@ -496,8 +467,7 @@ static void get_statuscol_display_info(statuscol_T *stcp, LineDrawState *draw_st
if (stcp->textp + *n_extrap < stcp->text_end) {
int hl = stcp->hlrecp->userhl;
stcp->textp = stcp->hlrecp->start;
- stcp->cur_attr = hl < 0 ? syn_id2attr(-stcp->hlrecp->userhl)
- : hl > 0 ? hl : stcp->num_attr;
+ stcp->cur_attr = hl < 0 ? syn_id2attr(-hl) : hl > 0 ? hl : stcp->num_attr;
stcp->hlrecp++;
*draw_state = WL_STC - 1;
}
@@ -1208,7 +1178,13 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
if (*wp->w_p_stc != NUL) {
// Draw the 'statuscolumn' if option is set.
statuscol.draw = true;
+ statuscol.sattrs = sattrs;
+ statuscol.foldinfo = foldinfo;
statuscol.width = win_col_off(wp);
+ statuscol.use_cul = use_cursor_line_sign(wp, lnum);
+ statuscol.sign_cul_attr = statuscol.use_cul ? sign_cul_attr : 0;
+ statuscol.num_attr = sign_num_attr ? sign_num_attr
+ : get_line_number_attr(wp, lnum, row, startrow, filler_lines);
}
int sign_idx = 0;
@@ -1354,8 +1330,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// Draw the 'statuscolumn' if option is set.
if (statuscol.draw) {
if (statuscol.textp == NULL) {
- get_statuscol_str(wp, lnum, row, startrow, filler_lines, cul_attr,
- sign_num_attr, sign_cul_attr, &statuscol, foldinfo, sattrs);
+ get_statuscol_str(wp, lnum, row - startrow - filler_lines, &statuscol);
if (wp->w_redr_statuscol) {
break;
}
diff --git a/src/nvim/fold.h b/src/nvim/fold.h
index ac1e8c9419..cf44cf14c3 100644
--- a/src/nvim/fold.h
+++ b/src/nvim/fold.h
@@ -9,17 +9,6 @@
#include "nvim/pos.h"
#include "nvim/types.h"
-// Info used to pass info about a fold from the fold-detection code to the
-// code that displays the foldcolumn.
-typedef struct foldinfo {
- linenr_T fi_lnum; // line number where fold starts
- int fi_level; // level of the fold; when this is zero the
- // other fields are invalid
- int fi_low_level; // lowest fold level that starts in the same
- // line
- linenr_T fi_lines;
-} foldinfo_T;
-
EXTERN int disable_fold_update INIT(= 0);
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/fold_defs.h b/src/nvim/fold_defs.h
new file mode 100644
index 0000000000..c528d25348
--- /dev/null
+++ b/src/nvim/fold_defs.h
@@ -0,0 +1,17 @@
+#ifndef NVIM_FOLD_DEFS_H
+#define NVIM_FOLD_DEFS_H
+
+#include "nvim/pos.h"
+
+// Info used to pass info about a fold from the fold-detection code to the
+// code that displays the foldcolumn.
+typedef struct foldinfo {
+ linenr_T fi_lnum; // line number where fold starts
+ int fi_level; // level of the fold; when this is zero the
+ // other fields are invalid
+ int fi_low_level; // lowest fold level that starts in the same
+ // line
+ linenr_T fi_lines;
+} foldinfo_T;
+
+#endif // NVIM_FOLD_DEFS_H
diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c
index 5936e4ff2b..5e53bf273f 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -12,6 +12,7 @@
#include "lauxlib.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/api/ui.h"
#include "nvim/decoration_provider.h"
#include "nvim/drawscreen.h"
@@ -971,35 +972,33 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
return hlattrs;
}
- if (dict->blend.type == kObjectTypeInteger) {
+ if (HAS_KEY(dict->blend)) {
+ VALIDATE_T("blend", kObjectTypeInteger, dict->blend.type, {
+ return hlattrs;
+ });
+
Integer blend0 = dict->blend.data.integer;
- if (blend0 < 0 || blend0 > 100) {
- api_set_error(err, kErrorTypeValidation, "'blend' is not between 0 to 100");
- } else {
- blend = (int)blend0;
- }
- } else if (HAS_KEY(dict->blend)) {
- api_set_error(err, kErrorTypeValidation, "'blend' must be an integer");
- }
- if (ERROR_SET(err)) {
- return hlattrs;
+ VALIDATE_RANGE((blend0 >= 0 && blend0 <= 100), "blend", {
+ return hlattrs;
+ });
+ blend = (int)blend0;
}
if (HAS_KEY(dict->link) || HAS_KEY(dict->global_link)) {
- if (link_id) {
- if (HAS_KEY(dict->global_link)) {
- *link_id = object_to_hl_id(dict->global_link, "link", err);
- mask |= HL_GLOBAL;
- } else {
- *link_id = object_to_hl_id(dict->link, "link", err);
- }
-
- if (ERROR_SET(err)) {
- return hlattrs;
- }
- } else {
+ if (!link_id) {
api_set_error(err, kErrorTypeValidation, "Invalid Key: '%s'",
HAS_KEY(dict->global_link) ? "global_link" : "link");
+ return hlattrs;
+ }
+ if (HAS_KEY(dict->global_link)) {
+ *link_id = object_to_hl_id(dict->global_link, "link", err);
+ mask |= HL_GLOBAL;
+ } else {
+ *link_id = object_to_hl_id(dict->link, "link", err);
+ }
+
+ if (ERROR_SET(err)) {
+ return hlattrs;
}
}
@@ -1026,7 +1025,9 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
// TODO(clason): handle via gen_api_dispatch
cterm_mask_provided = true;
} else if (HAS_KEY(dict->cterm)) {
- api_set_error(err, kErrorTypeValidation, "'cterm' must be a Dictionary.");
+ VALIDATE_T("cterm", kObjectTypeDictionary, dict->cterm.type, {
+ return hlattrs;
+ });
}
#undef CHECK_FLAG
@@ -1083,13 +1084,14 @@ int object_to_color(Object val, char *key, bool rgb, Error *err)
} else {
color = name_to_ctermcolor(str.data);
}
- if (color < 0) {
- api_set_error(err, kErrorTypeValidation, "'%s' is not a valid color", str.data);
- }
+ VALIDATE_S((color >= 0), "highlight color", str.data, {
+ return color;
+ });
return color;
} else {
- api_set_error(err, kErrorTypeValidation, "'%s' must be string or integer", key);
- return 0;
+ VALIDATE(false, "Invalid %s: expected String or Integer", key, {
+ return 0;
+ });
}
}
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 5ece5e4e5d..069304d9e1 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -33,6 +33,7 @@
#include "nvim/api/extmark.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/ascii.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
@@ -2177,9 +2178,6 @@ static char *set_bool_option(const int opt_idx, char *const varp, const int valu
if (curwin->w_p_spell) {
errmsg = did_set_spelllang(curwin);
}
- } else if (((int *)varp == &curwin->w_p_nu || (int *)varp == &curwin->w_p_rnu)
- && *curwin->w_p_stc != NUL) { // '(relative)number' + 'statuscolumn'
- curwin->w_nrwidth_line_count = 0;
}
if ((int *)varp == &curwin->w_p_arab) {
@@ -5624,10 +5622,9 @@ long get_sidescrolloff_value(win_T *wp)
Dictionary get_vimoption(String name, Error *err)
{
int opt_idx = findoption_len((const char *)name.data, name.size);
- if (opt_idx < 0) {
- api_set_error(err, kErrorTypeValidation, "no such option: '%s'", name.data);
+ VALIDATE_S(opt_idx >= 0, "option (not found)", name.data, {
return (Dictionary)ARRAY_DICT_INIT;
- }
+ });
return vimoption2dict(&options[opt_idx]);
}
diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c
index c880de5060..de91bf334c 100644
--- a/src/nvim/statusline.c
+++ b/src/nvim/statusline.c
@@ -37,7 +37,7 @@
#include "nvim/path.h"
#include "nvim/pos.h"
#include "nvim/screen.h"
-#include "nvim/sign_defs.h"
+#include "nvim/sign.h"
#include "nvim/statusline.h"
#include "nvim/strings.h"
#include "nvim/types.h"
@@ -871,11 +871,8 @@ void draw_tabline(void)
/// Build the 'statuscolumn' string for line "lnum". When "relnum" == -1,
/// the v:lnum and v:relnum variables don't have to be updated.
///
-/// @param hlrec HL attributes (can be NULL)
-/// @param stcp Status column attributes (can be NULL)
/// @return The width of the built status column string for line "lnum"
-int build_statuscol_str(win_T *wp, linenr_T lnum, long relnum, int maxwidth, int fillchar,
- char *buf, stl_hlrec_t **hlrec, statuscol_T *stcp)
+int build_statuscol_str(win_T *wp, linenr_T lnum, long relnum, statuscol_T *stcp)
{
bool fillclick = relnum >= 0 && lnum == wp->w_topline;
@@ -886,8 +883,8 @@ int build_statuscol_str(win_T *wp, linenr_T lnum, long relnum, int maxwidth, int
StlClickRecord *clickrec;
char *stc = xstrdup(wp->w_p_stc);
- int width = build_stl_str_hl(wp, buf, MAXPATHL, stc, "statuscolumn", OPT_LOCAL, fillchar,
- maxwidth, hlrec, fillclick ? &clickrec : NULL, stcp);
+ int width = build_stl_str_hl(wp, stcp->text, MAXPATHL, stc, "statuscolumn", OPT_LOCAL, ' ',
+ stcp->width, &stcp->hlrec, fillclick ? &clickrec : NULL, stcp);
xfree(stc);
// Only update click definitions once per window per redraw
@@ -895,7 +892,7 @@ int build_statuscol_str(win_T *wp, linenr_T lnum, long relnum, int maxwidth, int
stl_clear_click_defs(wp->w_statuscol_click_defs, wp->w_statuscol_click_defs_size);
wp->w_statuscol_click_defs = stl_alloc_click_defs(wp->w_statuscol_click_defs, width,
&wp->w_statuscol_click_defs_size);
- stl_fill_click_defs(wp->w_statuscol_click_defs, clickrec, buf, width, false);
+ stl_fill_click_defs(wp->w_statuscol_click_defs, clickrec, stcp->text, width, false);
}
return width;
@@ -1634,20 +1631,40 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
if (stcp == NULL) {
break;
}
-
bool fold = opt == STL_FOLDCOL;
+ int width = fold ? (compute_foldcolumn(wp, 0) > 0) : wp->w_scwidth;
+
+ if (width == 0) {
+ break;
+ }
+
+ char *p;
+ if (fold) {
+ size_t n = fill_foldcolumn(out_p, wp, stcp->foldinfo, (linenr_T)get_vim_var_nr(VV_LNUM));
+ stl_items[curitem].minwid = win_hl_attr(wp, stcp->use_cul ? HLF_CLF : HLF_FC);
+ p = out_p;
+ p[n] = NUL;
+ }
+
*buf_tmp = NUL;
- for (int i = 0; i <= SIGN_SHOW_MAX; i++) {
- char *p = fold ? stcp->fold_text : stcp->sign_text[i];
- if ((!p || !*p) && *buf_tmp == NUL) {
- break;
+ varnumber_T virtnum = get_vim_var_nr(VV_VIRTNUM);
+ for (int i = 0; i <= width; i++) {
+ if (i == width) {
+ if (*buf_tmp == NUL) {
+ break;
+ }
+ stl_items[curitem].minwid = 0;
+ } else if (!fold) {
+ SignTextAttrs *sattr = virtnum ? NULL : sign_get_attr(i, stcp->sattrs, wp->w_scwidth);
+ p = sattr && sattr->text ? sattr->text : " ";
+ stl_items[curitem].minwid = sattr ? stcp->sign_cul_attr ? stcp->sign_cul_attr
+ : sattr->hl_attr_id
+ : win_hl_attr(wp, stcp->use_cul ? HLF_CLS : HLF_SC);
}
stl_items[curitem].type = Highlight;
stl_items[curitem].start = out_p + strlen(buf_tmp);
- stl_items[curitem].minwid = !p || (fold && i) ? 0 : fold ? stcp->fold_attr
- : stcp->sign_attr[i];
curitem++;
- if (!p || (fold && i)) {
+ if (i == width) {
str = buf_tmp;
break;
}
diff --git a/src/nvim/statusline_defs.h b/src/nvim/statusline_defs.h
index eac9dfd690..6835d62cdd 100644
--- a/src/nvim/statusline_defs.h
+++ b/src/nvim/statusline_defs.h
@@ -3,7 +3,10 @@
#include <stddef.h>
+#include "nvim/fold_defs.h"
#include "nvim/macros.h"
+#include "nvim/os/os_defs.h"
+#include "nvim/sign_defs.h"
/// Status line click definition
typedef struct {
@@ -23,4 +26,54 @@ typedef struct {
const char *start; ///< Location where region starts.
} StlClickRecord;
+/// Used for highlighting in the status line.
+typedef struct stl_hlrec stl_hlrec_t;
+struct stl_hlrec {
+ char *start;
+ int userhl; // 0: no HL, 1-9: User HL, < 0 for syn ID
+};
+
+/// Used for building the status line.
+typedef struct stl_item stl_item_t;
+struct stl_item {
+ // Where the item starts in the status line output buffer
+ char *start;
+ // Function to run for ClickFunc items.
+ char *cmd;
+ // The minimum width of the item
+ int minwid;
+ // The maximum width of the item
+ int maxwid;
+ enum {
+ Normal,
+ Empty,
+ Group,
+ Separate,
+ Highlight,
+ TabPage,
+ ClickFunc,
+ Trunc,
+ } type;
+};
+
+/// Struct to hold info for 'statuscolumn'
+typedef struct statuscol statuscol_T;
+
+struct statuscol {
+ int width; ///< width of the status column
+ int cur_attr; ///< current attributes in text
+ int num_attr; ///< attributes used for line number
+ int sign_cul_attr; ///< cursorline sign attr
+ int truncate; ///< truncated width
+ bool draw; ///< whether to draw the statuscolumn
+ bool use_cul; ///< whether to use cursorline attrs
+ char text[MAXPATHL]; ///< text in status column
+ char *textp; ///< current position in text
+ char *text_end; ///< end of text (the NUL byte)
+ stl_hlrec_t *hlrec; ///< highlight groups
+ stl_hlrec_t *hlrecp; ///< current highlight group
+ foldinfo_T foldinfo; ///< fold information
+ SignTextAttrs *sattrs; ///< sign attributes
+};
+
#endif // NVIM_STATUSLINE_DEFS_H
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index 8172a46773..1693595ce8 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -11,6 +11,7 @@
#include "klib/kvec.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/api/ui.h"
#include "nvim/ascii.h"
#include "nvim/autocmd.h"
@@ -608,7 +609,7 @@ Array ui_array(void)
return all_uis;
}
-void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error)
+void ui_grid_resize(handle_T grid_handle, int width, int height, Error *err)
{
if (grid_handle == DEFAULT_GRID_HANDLE) {
screen_resize(width, height);
@@ -616,11 +617,9 @@ void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error)
}
win_T *wp = get_win_by_grid_handle(grid_handle);
- if (wp == NULL) {
- api_set_error(error, kErrorTypeValidation,
- "No window with the given handle");
+ VALIDATE_INT((wp != NULL), "window handle", (int64_t)grid_handle, {
return;
- }
+ });
if (wp->w_floating) {
if (width != wp->w_width || height != wp->w_height) {
diff --git a/test/functional/api/autocmd_spec.lua b/test/functional/api/autocmd_spec.lua
index 22a1311ee9..f10f18174e 100644
--- a/test/functional/api/autocmd_spec.lua
+++ b/test/functional/api/autocmd_spec.lua
@@ -14,14 +14,31 @@ before_each(clear)
describe('autocmd api', function()
describe('nvim_create_autocmd', function()
- it('"command" and "callback" are mutually exclusive', function()
- local rv = pcall_err(meths.create_autocmd, "BufReadPost", {
- pattern = "*.py,*.pyi",
+ it('validation', function()
+ eq("Cannot use both 'callback' and 'command'", pcall_err(meths.create_autocmd, 'BufReadPost', {
+ pattern = '*.py,*.pyi',
command = "echo 'Should Have Errored",
- callback = "NotAllowed",
- })
-
- eq("specify either 'callback' or 'command', not both", rv)
+ callback = 'NotAllowed',
+ }))
+ eq("Cannot use both 'pattern' and 'buffer' for the same autocmd", pcall_err(meths.create_autocmd, 'FileType', {
+ command = 'let g:called = g:called + 1',
+ buffer = 0,
+ pattern = '*.py',
+ }))
+ eq("Required: 'event'", pcall_err(meths.create_autocmd, {}, {
+ command = 'ls',
+ }))
+ eq("Required: 'command' or 'callback'", pcall_err(meths.create_autocmd, 'FileType', {
+ }))
+ eq('Invalid desc: expected String, got Integer', pcall_err(meths.create_autocmd, 'FileType', {
+ command = 'ls',
+ desc = 42,
+ }))
+ eq('Invalid callback: expected Lua function or Vim function name, got Integer', pcall_err(meths.create_autocmd, 'FileType', {
+ callback = 0,
+ }))
+ eq("Invalid 'event' item: expected String, got Array", pcall_err(meths.create_autocmd,
+ {'FileType', {}}, {}))
end)
it('doesnt leak when you use ++once', function()
@@ -59,18 +76,8 @@ describe('autocmd api', function()
eq(1, meths.get_var('called'))
end)
- it('does not allow passing buffer and patterns', function()
- local rv = pcall_err(meths.create_autocmd, "Filetype", {
- command = "let g:called = g:called + 1",
- buffer = 0,
- pattern = "*.py",
- })
-
- eq("cannot pass both: 'pattern' and 'buffer' for the same autocmd", rv)
- end)
-
it('does not allow passing invalid buffers', function()
- local ok, msg = pcall(meths.create_autocmd, "Filetype", {
+ local ok, msg = pcall(meths.create_autocmd, 'FileType', {
command = "let g:called = g:called + 1",
buffer = -1,
})
@@ -295,7 +302,7 @@ describe('autocmd api', function()
describe('nvim_get_autocmds', function()
describe('events', function()
- it('should return one autocmd when there is only one for an event', function()
+ it('returns one autocmd when there is only one for an event', function()
command [[au! InsertEnter]]
command [[au InsertEnter * :echo "1"]]
@@ -303,7 +310,7 @@ describe('autocmd api', function()
eq(1, #aus)
end)
- it('should return two autocmds when there are two for an event', function()
+ it('returns two autocmds when there are two for an event', function()
command [[au! InsertEnter]]
command [[au InsertEnter * :echo "1"]]
command [[au InsertEnter * :echo "2"]]
@@ -312,7 +319,7 @@ describe('autocmd api', function()
eq(2, #aus)
end)
- it('should return the same thing if you use string or list', function()
+ it('returns the same thing if you use string or list', function()
command [[au! InsertEnter]]
command [[au InsertEnter * :echo "1"]]
command [[au InsertEnter * :echo "2"]]
@@ -322,7 +329,7 @@ describe('autocmd api', function()
eq(string_aus, array_aus)
end)
- it('should return two autocmds when there are two for an event', function()
+ it('returns two autocmds when there are two for an event', function()
command [[au! InsertEnter]]
command [[au! InsertLeave]]
command [[au InsertEnter * :echo "1"]]
@@ -332,7 +339,7 @@ describe('autocmd api', function()
eq(2, #aus)
end)
- it('should return different IDs for different autocmds', function()
+ it('returns different IDs for different autocmds', function()
command [[au! InsertEnter]]
command [[au! InsertLeave]]
command [[au InsertEnter * :echo "1"]]
@@ -356,7 +363,7 @@ describe('autocmd api', function()
eq(first, new_aus[1])
end)
- it('should return event name', function()
+ it('returns event name', function()
command [[au! InsertEnter]]
command [[au InsertEnter * :echo "1"]]
@@ -364,7 +371,7 @@ describe('autocmd api', function()
eq({ { buflocal = false, command = ':echo "1"', event = "InsertEnter", once = false, pattern = "*" } }, aus)
end)
- it('should work with buffer numbers', function()
+ it('works with buffer numbers', function()
command [[new]]
command [[au! InsertEnter]]
command [[au InsertEnter <buffer=1> :echo "1"]]
@@ -407,8 +414,8 @@ describe('autocmd api', function()
pattern = "<buffer=2>",
}}, aus)
- eq("Invalid value for 'buffer': must be an integer or array of integers", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = "foo" }))
- eq("Invalid value for 'buffer': must be an integer", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = { "foo", 42 } }))
+ eq("Invalid buffer: expected Integer or Array, got String", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = "foo" }))
+ eq("Invalid buffer: expected Integer, got String", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = { "foo", 42 } }))
eq("Invalid buffer id: 42", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = { 42 } }))
local bufs = {}
@@ -416,10 +423,10 @@ describe('autocmd api', function()
table.insert(bufs, meths.create_buf(true, false))
end
- eq("Too many buffers. Please limit yourself to 256 or fewer", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = bufs }))
+ eq("Too many buffers (maximum of 256)", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = bufs }))
end)
- it('should return autocmds when group is specified by id', function()
+ it('returns autocmds when group is specified by id', function()
local auid = meths.create_augroup("nvim_test_augroup", { clear = true })
meths.create_autocmd("FileType", { group = auid, command = 'echo "1"' })
meths.create_autocmd("FileType", { group = auid, command = 'echo "2"' })
@@ -431,7 +438,7 @@ describe('autocmd api', function()
eq(0, #aus2)
end)
- it('should return autocmds when group is specified by name', function()
+ it('returns autocmds when group is specified by name', function()
local auname = "nvim_test_augroup"
meths.create_augroup(auname, { clear = true })
meths.create_autocmd("FileType", { group = auname, command = 'echo "1"' })
@@ -531,7 +538,7 @@ describe('autocmd api', function()
command [[augroup END]]
end)
- it('should return all groups if no group is specified', function()
+ it('returns all groups if no group is specified', function()
local aus = meths.get_autocmds { event = "InsertEnter" }
if #aus ~= 4 then
eq({}, aus)
@@ -540,7 +547,7 @@ describe('autocmd api', function()
eq(4, #aus)
end)
- it('should return only the group specified', function()
+ it('returns only the group specified', function()
local aus = meths.get_autocmds {
event = "InsertEnter",
group = "GroupOne",
@@ -551,7 +558,7 @@ describe('autocmd api', function()
eq("GroupOne", aus[1].group_name)
end)
- it('should return only the group specified, multiple values', function()
+ it('returns only the group specified, multiple values', function()
local aus = meths.get_autocmds {
event = "InsertEnter",
group = "GroupTwo",
@@ -578,7 +585,7 @@ describe('autocmd api', function()
]], {}))
eq(false, success)
- matches('invalid augroup: NotDefined', code)
+ matches("Invalid group: 'NotDefined'", code)
end)
it('raises error for undefined augroup id', function()
@@ -596,7 +603,7 @@ describe('autocmd api', function()
]], {}))
eq(false, success)
- matches('invalid augroup: 1', code)
+ matches('Invalid group: 1', code)
end)
it('raises error for invalid group type', function()
@@ -611,7 +618,7 @@ describe('autocmd api', function()
]], {}))
eq(false, success)
- matches("'group' must be a string or an integer", code)
+ matches("Invalid group: expected String or Integer, got Boolean", code)
end)
it('raises error for invalid pattern array', function()
@@ -625,7 +632,7 @@ describe('autocmd api', function()
]], {}))
eq(false, success)
- matches("All entries in 'pattern' must be strings", code)
+ matches("Invalid 'pattern' item: expected String, got Array", code)
end)
end)
@@ -640,7 +647,7 @@ describe('autocmd api', function()
command [[au InsertEnter <buffer> :echo "Buffer"]]
end)
- it('should should return for literal match', function()
+ it('returns for literal match', function()
local aus = meths.get_autocmds {
event = "InsertEnter",
pattern = "*"
@@ -650,7 +657,7 @@ describe('autocmd api', function()
eq([[:echo "No Group"]], aus[1].command)
end)
- it('should return for multiple matches', function()
+ it('returns for multiple matches', function()
-- vim.api.nvim_get_autocmds
local aus = meths.get_autocmds {
event = "InsertEnter",
@@ -687,6 +694,23 @@ describe('autocmd api', function()
end)
describe('nvim_exec_autocmds', function()
+ it('validation', function()
+ eq('Invalid group: 9997999', pcall_err(meths.exec_autocmds, 'FileType', {
+ group = 9997999,
+ }))
+ eq("Invalid group: 'bogus'", pcall_err(meths.exec_autocmds, 'FileType', {
+ group = 'bogus',
+ }))
+ eq('Invalid group: expected String or Integer, got Array', pcall_err(meths.exec_autocmds, 'FileType', {
+ group = {},
+ }))
+ eq('Invalid buffer: expected Integer, got Array', pcall_err(meths.exec_autocmds, 'FileType', {
+ buffer = {},
+ }))
+ eq("Invalid 'event' item: expected String, got Array", pcall_err(meths.exec_autocmds,
+ {'FileType', {}}, {}))
+ end)
+
it("can trigger builtin autocmds", function()
meths.set_var("autocmd_executed", false)
@@ -1036,7 +1060,7 @@ describe('autocmd api', function()
local augroup = "WillBeDeleted"
meths.create_augroup(augroup, { clear = true })
- meths.create_autocmd({"Filetype"}, {
+ meths.create_autocmd({"FileType"}, {
pattern = "*",
command = "echo 'does not matter'",
})
@@ -1055,7 +1079,7 @@ describe('autocmd api', function()
meths.set_var("value_set", false)
meths.create_augroup(augroup, { clear = true })
- meths.create_autocmd("Filetype", {
+ meths.create_autocmd("FileType", {
pattern = "<buffer>",
command = "let g:value_set = v:true",
})
@@ -1171,6 +1195,16 @@ describe('autocmd api', function()
end)
describe('nvim_clear_autocmds', function()
+ it('validation', function()
+ eq("Cannot use both 'pattern' and 'buffer'", pcall_err(meths.clear_autocmds, {
+ pattern = '*',
+ buffer = 42,
+ }))
+ eq("Invalid 'event' item: expected String, got Array", pcall_err(meths.clear_autocmds, {
+ event = {'FileType', {}}
+ }))
+ end)
+
it('should clear based on event + pattern', function()
command('autocmd InsertEnter *.py :echo "Python can be cool sometimes"')
command('autocmd InsertEnter *.txt :echo "Text Files Are Cool"')
diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua
index 6b13729994..d454765edb 100644
--- a/test/functional/api/buffer_spec.lua
+++ b/test/functional/api/buffer_spec.lua
@@ -193,7 +193,7 @@ describe('api/buf', function()
it('fails correctly when input is not valid', function()
eq(1, api.curbufmeths.get_number())
- eq([[String cannot contain newlines]],
+ eq([['replacement string' item contains newlines]],
pcall_err(bufmeths.set_lines, 1, 1, 2, false, {'b\na'}))
end)
@@ -553,20 +553,20 @@ describe('api/buf', function()
insert([[
hello foo!
text]])
- eq('start_row out of bounds', pcall_err(set_text, 2, 0, 3, 0, {}))
- eq('start_row out of bounds', pcall_err(set_text, -3, 0, 0, 0, {}))
- eq('end_row out of bounds', pcall_err(set_text, 0, 0, 2, 0, {}))
- eq('end_row out of bounds', pcall_err(set_text, 0, 0, -3, 0, {}))
- eq('start_col out of bounds', pcall_err(set_text, 1, 5, 1, 5, {}))
- eq('end_col out of bounds', pcall_err(set_text, 1, 0, 1, 5, {}))
+ eq("Invalid 'start_row': out of range", pcall_err(set_text, 2, 0, 3, 0, {}))
+ eq("Invalid 'start_row': out of range", pcall_err(set_text, -3, 0, 0, 0, {}))
+ eq("Invalid 'end_row': out of range", pcall_err(set_text, 0, 0, 2, 0, {}))
+ eq("Invalid 'end_row': out of range", pcall_err(set_text, 0, 0, -3, 0, {}))
+ eq("Invalid 'start_col': out of range", pcall_err(set_text, 1, 5, 1, 5, {}))
+ eq("Invalid 'end_col': out of range", pcall_err(set_text, 1, 0, 1, 5, {}))
end)
it('errors when start is greater than end', function()
insert([[
hello foo!
text]])
- eq('start is higher than end', pcall_err(set_text, 1, 0, 0, 0, {}))
- eq('start is higher than end', pcall_err(set_text, 0, 1, 0, 0, {}))
+ eq("'start' is higher than 'end'", pcall_err(set_text, 1, 0, 0, 0, {}))
+ eq("'start' is higher than 'end'", pcall_err(set_text, 0, 1, 0, 0, {}))
end)
it('no heap-use-after-free when called consecutively #19643', function()
@@ -611,7 +611,7 @@ describe('api/buf', function()
end)
it('errors when start is greater than end', function()
- eq('start is higher than end', pcall_err(get_text, 1, 0, 0, 0, {}))
+ eq("'start' is higher than 'end'", pcall_err(get_text, 1, 0, 0, 0, {}))
eq('start_col must be less than end_col', pcall_err(get_text, 0, 1, 0, 0, {}))
end)
end)
diff --git a/test/functional/api/buffer_updates_spec.lua b/test/functional/api/buffer_updates_spec.lua
index d5f06c8f1f..1510d31945 100644
--- a/test/functional/api/buffer_updates_spec.lua
+++ b/test/functional/api/buffer_updates_spec.lua
@@ -762,7 +762,7 @@ describe('API: buffer events:', function()
it('returns a proper error on nonempty options dict', function()
clear()
local b = editoriginal(false)
- eq("unexpected key: builtin", pcall_err(buffer, 'attach', b, false, {builtin="asfd"}))
+ eq("Invalid key: 'builtin'", pcall_err(buffer, 'attach', b, false, {builtin="asfd"}))
end)
it('nvim_buf_attach returns response after delay #8634', function()
diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua
index d0fb26edc7..189f9d4f98 100644
--- a/test/functional/api/command_spec.lua
+++ b/test/functional/api/command_spec.lua
@@ -543,7 +543,7 @@ describe('nvim_create_user_command', function()
end)
it('does not allow invalid command names', function()
- matches("'name' must begin with an uppercase letter", pcall_err(exec_lua, [[
+ matches("Invalid command name %(must begin with an uppercase letter%): 'test'", pcall_err(exec_lua, [[
vim.api.nvim_create_user_command('test', 'echo "hi"', {})
]]))
diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua
index 9902826c72..fab1557204 100644
--- a/test/functional/api/extmark_spec.lua
+++ b/test/functional/api/extmark_spec.lua
@@ -106,7 +106,7 @@ describe('API/extmarks', function()
end_col = 0,
end_row = 1
})
- eq("end_col value outside range",
+ eq("Invalid 'end_col': out of range",
pcall_err(set_extmark, ns, marks[2], 0, 0, { end_col = 1, end_row = 1 }))
end)
@@ -1344,10 +1344,10 @@ describe('API/extmarks', function()
it('throws consistent error codes', function()
local ns_invalid = ns2 + 1
- eq("Invalid ns_id", pcall_err(set_extmark, ns_invalid, marks[1], positions[1][1], positions[1][2]))
- eq("Invalid ns_id", pcall_err(curbufmeths.del_extmark, ns_invalid, marks[1]))
- eq("Invalid ns_id", pcall_err(get_extmarks, ns_invalid, positions[1], positions[2]))
- eq("Invalid ns_id", pcall_err(get_extmark_by_id, ns_invalid, marks[1]))
+ eq("Invalid ns_id: 3", pcall_err(set_extmark, ns_invalid, marks[1], positions[1][1], positions[1][2]))
+ eq("Invalid ns_id: 3", pcall_err(curbufmeths.del_extmark, ns_invalid, marks[1]))
+ eq("Invalid ns_id: 3", pcall_err(get_extmarks, ns_invalid, positions[1], positions[2]))
+ eq("Invalid ns_id: 3", pcall_err(get_extmark_by_id, ns_invalid, marks[1]))
end)
it('when col = line-length, set the mark on eol', function()
@@ -1362,13 +1362,13 @@ describe('API/extmarks', function()
it('when col = line-length, set the mark on eol', function()
local invalid_col = init_text:len() + 1
- eq("col value outside range", pcall_err(set_extmark, ns, marks[1], 0, invalid_col))
+ eq("Invalid 'col': out of range", pcall_err(set_extmark, ns, marks[1], 0, invalid_col))
end)
it('fails when line > line_count', function()
local invalid_col = init_text:len() + 1
local invalid_lnum = 3
- eq('line value outside range', pcall_err(set_extmark, ns, marks[1], invalid_lnum, invalid_col))
+ eq("Invalid 'line': out of range", pcall_err(set_extmark, ns, marks[1], invalid_lnum, invalid_col))
eq({}, get_extmark_by_id(ns, marks[1]))
end)
diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua
index 7f044977cb..73551de42e 100644
--- a/test/functional/api/highlight_spec.lua
+++ b/test/functional/api/highlight_spec.lua
@@ -57,9 +57,7 @@ describe('API: highlight',function()
eq(expected_rgb, nvim("get_hl_by_id", hl_id, true))
-- Test invalid id.
- local err, emsg = pcall(meths.get_hl_by_id, 30000, false)
- eq(false, err)
- eq('Invalid highlight id: 30000', string.match(emsg, 'Invalid.*'))
+ eq('Invalid highlight id: 30000', pcall_err(meths.get_hl_by_id, 30000, false))
-- Test all highlight properties.
command('hi NewHighlight gui=underline,bold,italic,reverse,strikethrough,altfont,nocombine')
@@ -70,22 +68,14 @@ describe('API: highlight',function()
eq(expected_undercurl, nvim("get_hl_by_id", hl_id, true))
-- Test nil argument.
- err, emsg = pcall(meths.get_hl_by_id, { nil }, false)
- eq(false, err)
eq('Wrong type for argument 1 when calling nvim_get_hl_by_id, expecting Integer',
- string.match(emsg, 'Wrong.*'))
+ pcall_err(meths.get_hl_by_id, { nil }, false))
-- Test 0 argument.
- err, emsg = pcall(meths.get_hl_by_id, 0, false)
- eq(false, err)
- eq('Invalid highlight id: 0',
- string.match(emsg, 'Invalid.*'))
+ eq('Invalid highlight id: 0', pcall_err(meths.get_hl_by_id, 0, false))
-- Test -1 argument.
- err, emsg = pcall(meths.get_hl_by_id, -1, false)
- eq(false, err)
- eq('Invalid highlight id: -1',
- string.match(emsg, 'Invalid.*'))
+ eq('Invalid highlight id: -1', pcall_err(meths.get_hl_by_id, -1, false))
-- Test highlight group without ctermbg value.
command('hi Normal ctermfg=red ctermbg=yellow')
@@ -119,22 +109,16 @@ describe('API: highlight',function()
eq(expected_normal, nvim("get_hl_by_name", 'Normal', true))
-- Test invalid name.
- local err, emsg = pcall(meths.get_hl_by_name , 'unknown_highlight', false)
- eq(false, err)
- eq('Invalid highlight name: unknown_highlight',
- string.match(emsg, 'Invalid.*'))
+ eq("Invalid highlight name: 'unknown_highlight'",
+ pcall_err(meths.get_hl_by_name , 'unknown_highlight', false))
-- Test nil argument.
- err, emsg = pcall(meths.get_hl_by_name , { nil }, false)
- eq(false, err)
eq('Wrong type for argument 1 when calling nvim_get_hl_by_name, expecting String',
- string.match(emsg, 'Wrong.*'))
+ pcall_err(meths.get_hl_by_name , { nil }, false))
-- Test empty string argument.
- err, emsg = pcall(meths.get_hl_by_name , '', false)
- eq(false, err)
- eq('Invalid highlight name: ',
- string.match(emsg, 'Invalid.*'))
+ eq('Invalid highlight name',
+ pcall_err(meths.get_hl_by_name , '', false))
-- Test "standout" attribute. #8054
eq({ underline = true, },
@@ -155,7 +139,7 @@ describe('API: highlight',function()
it('nvim_get_hl_id_by_name', function()
-- precondition: use a hl group that does not yet exist
- eq('Invalid highlight name: Shrubbery', pcall_err(meths.get_hl_by_name, "Shrubbery", true))
+ eq("Invalid highlight name: 'Shrubbery'", pcall_err(meths.get_hl_by_name, "Shrubbery", true))
eq(0, funcs.hlID("Shrubbery"))
local hl_id = meths.get_hl_id_by_name("Shrubbery")
@@ -253,25 +237,32 @@ describe("API: set highlight", function()
before_each(clear)
- it ("can set gui highlight", function()
+ it('validation', function()
+ eq("Invalid 'blend': out of range",
+ pcall_err(meths.set_hl, 0, 'Test_hl3', {fg='#FF00FF', blend=999}))
+ eq("Invalid blend: expected Integer, got Array",
+ pcall_err(meths.set_hl, 0, 'Test_hl3', {fg='#FF00FF', blend={}}))
+ end)
+
+ it("can set gui highlight", function()
local ns = get_ns()
meths.set_hl(ns, 'Test_hl', highlight1)
eq(highlight1, meths.get_hl_by_name('Test_hl', true))
end)
- it ("can set cterm highlight", function()
+ it("can set cterm highlight", function()
local ns = get_ns()
meths.set_hl(ns, 'Test_hl', highlight2_config)
eq(highlight2_result, meths.get_hl_by_name('Test_hl', false))
end)
- it ("can set empty cterm attr", function()
+ it("can set empty cterm attr", function()
local ns = get_ns()
meths.set_hl(ns, 'Test_hl', { cterm = {} })
eq({}, meths.get_hl_by_name('Test_hl', false))
end)
- it ("cterm attr defaults to gui attr", function()
+ it("cterm attr defaults to gui attr", function()
local ns = get_ns()
meths.set_hl(ns, 'Test_hl', highlight1)
eq({
@@ -280,14 +271,14 @@ describe("API: set highlight", function()
}, meths.get_hl_by_name('Test_hl', false))
end)
- it ("can overwrite attr for cterm", function()
+ it("can overwrite attr for cterm", function()
local ns = get_ns()
meths.set_hl(ns, 'Test_hl', highlight3_config)
eq(highlight3_result_gui, meths.get_hl_by_name('Test_hl', true))
eq(highlight3_result_cterm, meths.get_hl_by_name('Test_hl', false))
end)
- it ("can set a highlight in the global namespace", function()
+ it("can set a highlight in the global namespace", function()
meths.set_hl(0, 'Test_hl', highlight2_config)
eq('Test_hl xxx cterm=underline,reverse ctermfg=8 ctermbg=15 gui=underline,reverse',
exec_capture('highlight Test_hl'))
@@ -307,7 +298,7 @@ describe("API: set highlight", function()
exec_capture('highlight Test_hl3'))
end)
- it ("can modify a highlight in the global namespace", function()
+ it("can modify a highlight in the global namespace", function()
meths.set_hl(0, 'Test_hl3', { bg = 'red', fg = 'blue'})
eq('Test_hl3 xxx guifg=Blue guibg=Red',
exec_capture('highlight Test_hl3'))
@@ -328,17 +319,17 @@ describe("API: set highlight", function()
eq('Test_hl3 xxx ctermbg=9',
exec_capture('highlight Test_hl3'))
- eq("'redd' is not a valid color",
+ eq("Invalid highlight color: 'redd'",
pcall_err(meths.set_hl, 0, 'Test_hl3', {fg='redd'}))
- eq("'bleu' is not a valid color",
+ eq("Invalid highlight color: 'bleu'",
pcall_err(meths.set_hl, 0, 'Test_hl3', {ctermfg='bleu'}))
meths.set_hl(0, 'Test_hl3', {fg='#FF00FF'})
eq('Test_hl3 xxx guifg=#ff00ff',
exec_capture('highlight Test_hl3'))
- eq("'#FF00FF' is not a valid color",
+ eq("Invalid highlight color: '#FF00FF'",
pcall_err(meths.set_hl, 0, 'Test_hl3', {ctermfg='#FF00FF'}))
for _, fg_val in ipairs{ nil, 'NONE', 'nOnE', '', -1 } do
@@ -353,14 +344,14 @@ describe("API: set highlight", function()
end)
- it ("correctly sets 'Normal' internal properties", function()
+ it("correctly sets 'Normal' internal properties", function()
-- Normal has some special handling internally. #18024
meths.set_hl(0, 'Normal', {fg='#000083', bg='#0000F3'})
eq({foreground = 131, background = 243}, nvim("get_hl_by_name", 'Normal', true))
end)
it('does not segfault on invalid group name #20009', function()
- eq('Invalid highlight name: foo bar', pcall_err(meths.set_hl, 0, 'foo bar', {bold = true}))
+ eq("Invalid highlight name: 'foo bar'", pcall_err(meths.set_hl, 0, 'foo bar', {bold = true}))
assert_alive()
end)
end)
diff --git a/test/functional/api/proc_spec.lua b/test/functional/api/proc_spec.lua
index 2028a8fba5..315653af14 100644
--- a/test/functional/api/proc_spec.lua
+++ b/test/functional/api/proc_spec.lua
@@ -42,7 +42,7 @@ describe('API', function()
end)
end)
- it('validates input', function()
+ it('validation', function()
local status, rv = pcall(request, "nvim_get_proc_children", -1)
eq(false, status)
eq("Invalid pid: -1", string.match(rv, "Invalid.*"))
@@ -68,7 +68,7 @@ describe('API', function()
neq(pid, pinfo.ppid)
end)
- it('validates input', function()
+ it('validation', function()
local status, rv = pcall(request, "nvim_get_proc", -1)
eq(false, status)
eq("Invalid pid: -1", string.match(rv, "Invalid.*"))
diff --git a/test/functional/api/ui_spec.lua b/test/functional/api/ui_spec.lua
index 279cd1856d..5654ef31ef 100644
--- a/test/functional/api/ui_spec.lua
+++ b/test/functional/api/ui_spec.lua
@@ -11,17 +11,37 @@ describe('nvim_ui_attach()', function()
before_each(function()
clear()
end)
+
it('handles very large width/height #2180', function()
local screen = Screen.new(999, 999)
screen:attach()
eq(999, eval('&lines'))
eq(999, eval('&columns'))
end)
- it('invalid option returns error', function()
+
+ it('validation', function()
eq('No such UI option: foo',
pcall_err(meths.ui_attach, 80, 24, { foo={'foo'} }))
- end)
- it('validates channel arg', function()
+
+ eq('Invalid ext_linegrid: expected Boolean, got Array',
+ pcall_err(meths.ui_attach, 80, 24, { ext_linegrid={} }))
+ eq('Invalid override: expected Boolean, got Array',
+ pcall_err(meths.ui_attach, 80, 24, { override={} }))
+ eq('Invalid rgb: expected Boolean, got Array',
+ pcall_err(meths.ui_attach, 80, 24, { rgb={} }))
+ eq('Invalid term_name: expected String, got Boolean',
+ pcall_err(meths.ui_attach, 80, 24, { term_name=true }))
+ eq('Invalid term_colors: expected Integer, got Boolean',
+ pcall_err(meths.ui_attach, 80, 24, { term_colors=true }))
+ eq('Invalid term_background: expected String, got Boolean',
+ pcall_err(meths.ui_attach, 80, 24, { term_background=true }))
+ eq('Invalid stdin_fd: expected Integer, got String',
+ pcall_err(meths.ui_attach, 80, 24, { stdin_fd='foo' }))
+ eq('Invalid stdin_tty: expected Boolean, got String',
+ pcall_err(meths.ui_attach, 80, 24, { stdin_tty='foo' }))
+ eq('Invalid stdout_tty: expected Boolean, got String',
+ pcall_err(meths.ui_attach, 80, 24, { stdout_tty='foo' }))
+
eq('UI not attached to channel: 1',
pcall_err(request, 'nvim_ui_try_resize', 40, 10))
eq('UI not attached to channel: 1',
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index fc550f5861..469d8e8b6a 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -1155,7 +1155,7 @@ describe('API', function()
describe('nvim_put', function()
it('validates args', function()
- eq('Invalid lines (expected array of strings)',
+ eq("Invalid line: expected String, got Integer",
pcall_err(request, 'nvim_put', {42}, 'l', false, false))
eq("Invalid type: 'x'",
pcall_err(request, 'nvim_put', {'foo'}, 'x', false, false))
@@ -1410,6 +1410,15 @@ describe('API', function()
ok(not nvim('get_option_value', 'equalalways', {}))
end)
+ it('validation', function()
+ eq("Invalid scope: expected 'local' or 'global'",
+ pcall_err(nvim, 'get_option_value', 'scrolloff', {scope = 'bogus'}))
+ eq("Invalid scope: expected 'local' or 'global'",
+ pcall_err(nvim, 'set_option_value', 'scrolloff', 1, {scope = 'bogus'}))
+ eq("Invalid scope: expected String, got Integer",
+ pcall_err(nvim, 'get_option_value', 'scrolloff', {scope = 42}))
+ end)
+
it('can get local values when global value is set', function()
eq(0, nvim('get_option_value', 'scrolloff', {}))
eq(-1, nvim('get_option_value', 'scrolloff', {scope = 'local'}))
@@ -1780,9 +1789,9 @@ describe('API', function()
it('validates args', function()
eq("Invalid key: 'blah'",
pcall_err(nvim, 'get_context', {blah={}}))
- eq('invalid value for key: types',
+ eq("Invalid types: expected Array, got Integer",
pcall_err(nvim, 'get_context', {types=42}))
- eq('unexpected type: zub',
+ eq("Invalid type: 'zub'",
pcall_err(nvim, 'get_context', {types={'jumps', 'zub', 'zam',}}))
end)
it('returns map of current editor state', function()
@@ -2223,15 +2232,14 @@ describe('API', function()
eq(5, meths.get_var('avar'))
end)
- it('throws error on malformed arguments', function()
+ it('validation', function()
local req = {
{'nvim_set_var', {'avar', 1}},
{'nvim_set_var'},
{'nvim_set_var', {'avar', 2}},
}
- local status, err = pcall(meths.call_atomic, req)
- eq(false, status)
- ok(err:match('Items in calls array must be arrays of size 2') ~= nil)
+ eq('calls item must be a 2-item Array',
+ pcall_err(meths.call_atomic, req))
-- call before was done, but not after
eq(1, meths.get_var('avar'))
@@ -2239,18 +2247,16 @@ describe('API', function()
{ 'nvim_set_var', { 'bvar', { 2, 3 } } },
12,
}
- status, err = pcall(meths.call_atomic, req)
- eq(false, status)
- ok(err:match('Items in calls array must be arrays') ~= nil)
+ eq("Invalid calls item: expected Array, got Integer",
+ pcall_err(meths.call_atomic, req))
eq({2,3}, meths.get_var('bvar'))
req = {
{'nvim_set_current_line', 'little line'},
{'nvim_set_var', {'avar', 3}},
}
- status, err = pcall(meths.call_atomic, req)
- eq(false, status)
- ok(err:match('Args must be Array') ~= nil)
+ eq("Invalid args: expected Array, got String",
+ pcall_err(meths.call_atomic, req))
-- call before was done, but not after
eq(1, meths.get_var('avar'))
eq({''}, meths.buf_get_lines(0, 0, -1, true))
@@ -2750,7 +2756,7 @@ describe('API', function()
describe('nvim_get_option_info', function()
it('should error for unknown options', function()
- eq("no such option: 'bogus'", pcall_err(meths.get_option_info, 'bogus'))
+ eq("Invalid option (not found): 'bogus'", pcall_err(meths.get_option_info, 'bogus'))
end)
it('should return the same options for short and long name', function()
@@ -3031,10 +3037,10 @@ describe('API', function()
eq(true, meths.del_mark('F'))
eq({0, 0}, meths.buf_get_mark(buf, 'F'))
end)
- it('fails when invalid marks are used', function()
- eq(false, pcall(meths.del_mark, 'f'))
- eq(false, pcall(meths.del_mark, '!'))
- eq(false, pcall(meths.del_mark, 'fail'))
+ it('validation', function()
+ eq("Invalid mark name (must be file/uppercase): 'f'", pcall_err(meths.del_mark, 'f'))
+ eq("Invalid mark name (must be file/uppercase): '!'", pcall_err(meths.del_mark, '!'))
+ eq("Invalid mark name (must be a single char): 'fail'", pcall_err(meths.del_mark, 'fail'))
end)
end)
describe('nvim_get_mark', function()
@@ -3048,10 +3054,10 @@ describe('API', function()
assert(string.find(mark[4], "mybuf$"))
eq({2, 2, buf.id, mark[4]}, mark)
end)
- it('fails when invalid marks are used', function()
- eq(false, pcall(meths.del_mark, 'f'))
- eq(false, pcall(meths.del_mark, '!'))
- eq(false, pcall(meths.del_mark, 'fail'))
+ it('validation', function()
+ eq("Invalid mark name (must be file/uppercase): 'f'", pcall_err(meths.get_mark, 'f', {}))
+ eq("Invalid mark name (must be file/uppercase): '!'", pcall_err(meths.get_mark, '!', {}))
+ eq("Invalid mark name (must be a single char): 'fail'", pcall_err(meths.get_mark, 'fail', {}))
end)
it('returns the expected when mark is not set', function()
eq(true, meths.del_mark('A'))
@@ -3113,15 +3119,15 @@ describe('API', function()
meths.eval_statusline('a%=b', { fillchar = '\031', maxwidth = 5 }))
end)
it('rejects multiple-character fillchar', function()
- eq('fillchar must be a single character',
+ eq('Invalid fillchar: expected single character',
pcall_err(meths.eval_statusline, '', { fillchar = 'aa' }))
end)
it('rejects empty string fillchar', function()
- eq('fillchar must be a single character',
+ eq('Invalid fillchar: expected single character',
pcall_err(meths.eval_statusline, '', { fillchar = '' }))
end)
it('rejects non-string fillchar', function()
- eq('fillchar must be a single character',
+ eq("Invalid fillchar: expected String, got Integer",
pcall_err(meths.eval_statusline, '', { fillchar = 1 }))
end)
it('rejects invalid string', function()
diff --git a/test/functional/lua/api_spec.lua b/test/functional/lua/api_spec.lua
index 03832978a4..dc6452dd62 100644
--- a/test/functional/lua/api_spec.lua
+++ b/test/functional/lua/api_spec.lua
@@ -33,7 +33,7 @@ describe('luaeval(vim.api.…)', function()
describe('with errors', function()
it('transforms API error from nvim_buf_set_lines into lua error', function()
funcs.setline(1, {"abc", "def", "a\nb", "ttt"})
- eq({false, 'String cannot contain newlines'},
+ eq({false, "'replacement string' item contains newlines"},
funcs.luaeval('{pcall(vim.api.nvim_buf_set_lines, 1, 1, 2, false, {"b\\na"})}'))
end)
diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua
index 867f366d06..b43e5b28db 100644
--- a/test/functional/lua/vim_spec.lua
+++ b/test/functional/lua/vim_spec.lua
@@ -1458,7 +1458,7 @@ describe('lua stdlib', function()
]]
eq('', funcs.luaeval "vim.bo.filetype")
eq(true, funcs.luaeval "vim.bo[BUF].modifiable")
- matches("no such option: 'nosuchopt'$",
+ matches("Invalid option %(not found%): 'nosuchopt'$",
pcall_err(exec_lua, 'return vim.bo.nosuchopt'))
matches("Expected lua string$",
pcall_err(exec_lua, 'return vim.bo[0][0].autoread'))
@@ -1479,7 +1479,7 @@ describe('lua stdlib', function()
eq(0, funcs.luaeval "vim.wo.cole")
eq(0, funcs.luaeval "vim.wo[0].cole")
eq(0, funcs.luaeval "vim.wo[1001].cole")
- matches("no such option: 'notanopt'$",
+ matches("Invalid option %(not found%): 'notanopt'$",
pcall_err(exec_lua, 'return vim.wo.notanopt'))
matches("Expected lua string$",
pcall_err(exec_lua, 'return vim.wo[0][0].list'))
diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua
index 46bfae8de2..76baf2cddb 100644
--- a/test/functional/ui/bufhl_spec.lua
+++ b/test/functional/ui/bufhl_spec.lua
@@ -751,8 +751,8 @@ describe('Buffer highlighting', function()
it('validates contents', function()
-- this used to leak memory
- eq('Chunk is not an array', pcall_err(set_virtual_text, id1, 0, {"texty"}, {}))
- eq('Chunk is not an array', pcall_err(set_virtual_text, id1, 0, {{"very"}, "texty"}, {}))
+ eq('Invalid chunk: expected Array, got String', pcall_err(set_virtual_text, id1, 0, {"texty"}, {}))
+ eq('Invalid chunk: expected Array, got String', pcall_err(set_virtual_text, id1, 0, {{"very"}, "texty"}, {}))
end)
it('can be retrieved', function()
diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua
index 3b9cce0e6f..a059fb5b89 100644
--- a/test/functional/ui/screen.lua
+++ b/test/functional/ui/screen.lua
@@ -214,7 +214,7 @@ function Screen:attach(options, session)
-- simplify test code by doing the same.
self._options.rgb = true
end
- if self._options.ext_multigrid or self._options.ext_float then
+ if self._options.ext_multigrid then
self._options.ext_linegrid = true
end
end
diff --git a/test/functional/ui/statuscolumn_spec.lua b/test/functional/ui/statuscolumn_spec.lua
index 3233e6cd19..287686cf37 100644
--- a/test/functional/ui/statuscolumn_spec.lua
+++ b/test/functional/ui/statuscolumn_spec.lua
@@ -456,7 +456,7 @@ describe('statuscolumn', function()
14 aaaaa |
|
]])
- command('set stc=') -- also for the default sign column
+ command('set stc=') -- also for the default fold column
screen:expect_unchanged()
-- 'statuscolumn' is not too wide with custom (bogus) fold column
command([[set stc=%{foldlevel(v:lnum)>0?repeat('-',foldlevel(v:lnum)):''}%=%l\ ]])