aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/api
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2023-11-29 22:39:54 +0000
committerJosh Rahm <joshuarahm@gmail.com>2023-11-29 22:39:54 +0000
commit21cb7d04c387e4198ca8098a884c78b56ffcf4c2 (patch)
tree84fe5690df1551f0bb2bdfe1a13aacd29ebc1de7 /src/nvim/api
parentd9c904f85a23a496df4eb6be42aa43f007b22d50 (diff)
parent4a8bf24ac690004aedf5540fa440e788459e5e34 (diff)
downloadrneovim-colorcolchar.tar.gz
rneovim-colorcolchar.tar.bz2
rneovim-colorcolchar.zip
Merge remote-tracking branch 'upstream/master' into colorcolcharcolorcolchar
Diffstat (limited to 'src/nvim/api')
-rw-r--r--src/nvim/api/autocmd.c602
-rw-r--r--src/nvim/api/autocmd.h9
-rw-r--r--src/nvim/api/buffer.c441
-rw-r--r--src/nvim/api/buffer.h12
-rw-r--r--src/nvim/api/command.c567
-rw-r--r--src/nvim/api/command.h11
-rw-r--r--src/nvim/api/deprecated.c288
-rw-r--r--src/nvim/api/deprecated.h8
-rw-r--r--src/nvim/api/extmark.c878
-rw-r--r--src/nvim/api/extmark.h20
-rw-r--r--src/nvim/api/keysets.lua229
-rw-r--r--src/nvim/api/keysets_defs.h315
-rw-r--r--src/nvim/api/options.c717
-rw-r--r--src/nvim/api/options.h12
-rw-r--r--src/nvim/api/private/converter.c30
-rw-r--r--src/nvim/api/private/converter.h9
-rw-r--r--src/nvim/api/private/defs.h25
-rw-r--r--src/nvim/api/private/dispatch.c3
-rw-r--r--src/nvim/api/private/dispatch.h12
-rw-r--r--src/nvim/api/private/helpers.c205
-rw-r--r--src/nvim/api/private/helpers.h66
-rw-r--r--src/nvim/api/private/validate.c76
-rw-r--r--src/nvim/api/private/validate.h96
-rw-r--r--src/nvim/api/tabpage.c4
-rw-r--r--src/nvim/api/tabpage.h6
-rw-r--r--src/nvim/api/ui.c236
-rw-r--r--src/nvim/api/ui.h14
-rw-r--r--src/nvim/api/ui_events.in.h11
-rw-r--r--src/nvim/api/vim.c688
-rw-r--r--src/nvim/api/vim.h9
-rw-r--r--src/nvim/api/vimscript.c195
-rw-r--r--src/nvim/api/vimscript.h9
-rw-r--r--src/nvim/api/win_config.c482
-rw-r--r--src/nvim/api/win_config.h9
-rw-r--r--src/nvim/api/window.c177
-rw-r--r--src/nvim/api/window.h7
36 files changed, 3664 insertions, 2814 deletions
diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
index 931363e199..08d9d8e117 100644
--- a/src/nvim/api/autocmd.c
+++ b/src/nvim/api/autocmd.c
@@ -1,27 +1,27 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
+#include <lauxlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "lauxlib.h"
+#include "klib/kvec.h"
#include "nvim/api/autocmd.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/api/private/validate.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/lua/executor.h"
#include "nvim/memory.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/autocmd.c.generated.h"
@@ -32,13 +32,11 @@
// Copy string or array of strings into an empty array.
// Get the event number, unless it is an error. Then goto `goto_name`.
#define GET_ONE_EVENT(event_nr, event_str, goto_name) \
- char *__next_ev; \
event_T event_nr = \
- event_name2nr(event_str.data.string.data, &__next_ev); \
- if (event_nr >= NUM_EVENTS) { \
- api_set_error(err, kErrorTypeValidation, "unexpected event"); \
+ event_name2nr_str(event_str.data.string); \
+ VALIDATE_S((event_nr < NUM_EVENTS), "event", event_str.data.string.data, { \
goto goto_name; \
- }
+ });
// ID for associating autocmds created via nvim_create_autocmd
// Used to delete autocmds from nvim_del_autocmd
@@ -47,19 +45,20 @@ static int64_t next_autocmd_id = 1;
/// Get all autocommands that match the corresponding {opts}.
///
/// These examples will get autocommands matching ALL the given criteria:
-/// <pre>lua
-/// -- Matches all criteria
-/// autocommands = vim.api.nvim_get_autocmds({
-/// group = "MyGroup",
-/// event = {"BufEnter", "BufWinEnter"},
-/// pattern = {"*.c", "*.h"}
-/// })
///
-/// -- All commands from one group
-/// autocommands = vim.api.nvim_get_autocmds({
-/// group = "MyGroup",
-/// })
-/// </pre>
+/// ```lua
+/// -- Matches all criteria
+/// autocommands = vim.api.nvim_get_autocmds({
+/// group = "MyGroup",
+/// event = {"BufEnter", "BufWinEnter"},
+/// pattern = {"*.c", "*.h"}
+/// })
+///
+/// -- All commands from one group
+/// autocommands = vim.api.nvim_get_autocmds({
+/// group = "MyGroup",
+/// })
+/// ```
///
/// NOTE: When multiple patterns or events are provided, it will find all the autocommands that
/// match any combination of them.
@@ -107,25 +106,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", opts->group.data.string.data, {
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.");
+ char *name = group == 0 ? NULL : augroup_name(group);
+ VALIDATE_INT(augroup_exists(name), "group", opts->group.data.integer, {
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->event.type != kObjectTypeNil) {
+ if (HAS_KEY(opts, get_autocmds, event)) {
check_event = true;
Object v = opts->event;
@@ -134,57 +132,49 @@ 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_EXP(false, "event", "String or Array", NULL, {
+ 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, get_autocmds, pattern) || !HAS_KEY(opts, get_autocmds, buffer)),
+ "%s", "Cannot use both 'pattern' and 'buffer'", {
goto cleanup;
- }
+ });
int pattern_filter_count = 0;
- if (opts->pattern.type != kObjectTypeNil) {
+ if (HAS_KEY(opts, get_autocmds, 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",
- AUCMD_MAX_PATTERNS);
+ VALIDATE((v.data.array.size <= AUCMD_MAX_PATTERNS),
+ "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;
+ });
}
}
@@ -194,34 +184,33 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
goto cleanup;
}
- snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle);
- ADD(buffers, CSTR_TO_OBJ((char *)pattern_buflocal));
+ snprintf(pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle);
+ 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)) {
goto cleanup;
}
- snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle);
- ADD(buffers, CSTR_TO_OBJ((char *)pattern_buflocal));
+ snprintf(pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle);
+ ADD(buffers, CSTR_TO_OBJ(pattern_buflocal));
+ });
+ } else if (HAS_KEY(opts, get_autocmds, buffer)) {
+ VALIDATE_EXP(false, "buffer", "Integer or Array", api_typename(opts->buffer.type), {
+ goto cleanup;
});
- } 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;
}
FOREACH_ITEM(buffers, bufnr, {
@@ -234,8 +223,12 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
continue;
}
- for (AutoPat *ap = au_get_autopat_for_event(event); ap != NULL; ap = ap->next) {
- if (ap->cmds == NULL) {
+ AutoCmdVec *acs = au_get_autocmds_for_event(event);
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ AutoPat *const ap = ac->pat;
+
+ if (ap == NULL) {
continue;
}
@@ -247,19 +240,16 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
// Skip 'pattern' from invalid patterns if passed.
if (pattern_filter_count > 0) {
bool passed = false;
- for (int i = 0; i < pattern_filter_count; i++) {
- assert(i < AUCMD_MAX_PATTERNS);
- assert(pattern_filters[i]);
+ for (int j = 0; j < pattern_filter_count; j++) {
+ assert(j < AUCMD_MAX_PATTERNS);
+ assert(pattern_filters[j]);
- char *pat = pattern_filters[i];
+ char *pat = pattern_filters[j];
int patlen = (int)strlen(pat);
if (aupat_is_buflocal(pat, patlen)) {
- aupat_normalize_buflocal_pat(pattern_buflocal,
- pat,
- patlen,
+ aupat_normalize_buflocal_pat(pattern_buflocal, pat, patlen,
aupat_get_buflocal_nr(pat, patlen));
-
pat = pattern_buflocal;
}
@@ -274,85 +264,71 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
}
}
- for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
- if (aucmd_exec_is_deleted(ac->exec)) {
- continue;
- }
+ Dictionary autocmd_info = ARRAY_DICT_INIT;
- Dictionary autocmd_info = ARRAY_DICT_INIT;
-
- if (ap->group != AUGROUP_DEFAULT) {
- PUT(autocmd_info, "group", INTEGER_OBJ(ap->group));
- PUT(autocmd_info, "group_name", CSTR_TO_OBJ(augroup_name(ap->group)));
- }
+ if (ap->group != AUGROUP_DEFAULT) {
+ PUT(autocmd_info, "group", INTEGER_OBJ(ap->group));
+ PUT(autocmd_info, "group_name", CSTR_TO_OBJ(augroup_name(ap->group)));
+ }
- if (ac->id > 0) {
- PUT(autocmd_info, "id", INTEGER_OBJ(ac->id));
- }
+ if (ac->id > 0) {
+ PUT(autocmd_info, "id", INTEGER_OBJ(ac->id));
+ }
- if (ac->desc != NULL) {
- PUT(autocmd_info, "desc", CSTR_TO_OBJ(ac->desc));
- }
+ if (ac->desc != NULL) {
+ PUT(autocmd_info, "desc", CSTR_TO_OBJ(ac->desc));
+ }
- if (ac->exec.type == CALLABLE_CB) {
- PUT(autocmd_info, "command", STRING_OBJ(STRING_INIT));
+ if (ac->exec.type == CALLABLE_CB) {
+ PUT(autocmd_info, "command", STRING_OBJ(STRING_INIT));
- Callback *cb = &ac->exec.callable.cb;
- switch (cb->type) {
- case kCallbackLua:
- if (nlua_ref_is_function(cb->data.luaref)) {
- PUT(autocmd_info, "callback", LUAREF_OBJ(api_new_luaref(cb->data.luaref)));
- }
- break;
- case kCallbackFuncref:
- case kCallbackPartial:
- PUT(autocmd_info, "callback", STRING_OBJ(cstr_as_string(callback_to_string(cb))));
- break;
- default:
- abort();
+ Callback *cb = &ac->exec.callable.cb;
+ switch (cb->type) {
+ case kCallbackLua:
+ if (nlua_ref_is_function(cb->data.luaref)) {
+ PUT(autocmd_info, "callback", LUAREF_OBJ(api_new_luaref(cb->data.luaref)));
}
- } else {
- PUT(autocmd_info,
- "command",
- STRING_OBJ(cstr_as_string(xstrdup(ac->exec.callable.cmd))));
+ break;
+ case kCallbackFuncref:
+ case kCallbackPartial:
+ PUT(autocmd_info, "callback", CSTR_AS_OBJ(callback_to_string(cb)));
+ break;
+ case kCallbackNone:
+ abort();
}
+ } else {
+ PUT(autocmd_info, "command", CSTR_TO_OBJ(ac->exec.callable.cmd));
+ }
- PUT(autocmd_info,
- "pattern",
- STRING_OBJ(cstr_to_string((char *)ap->pat)));
-
- PUT(autocmd_info,
- "event",
- STRING_OBJ(cstr_to_string((char *)event_nr2name(event))));
-
- PUT(autocmd_info, "once", BOOLEAN_OBJ(ac->once));
-
- if (ap->buflocal_nr) {
- PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(true));
- PUT(autocmd_info, "buffer", INTEGER_OBJ(ap->buflocal_nr));
- } else {
- PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(false));
- }
+ PUT(autocmd_info, "pattern", CSTR_TO_OBJ(ap->pat));
+ PUT(autocmd_info, "event", CSTR_TO_OBJ(event_nr2name(event)));
+ PUT(autocmd_info, "once", BOOLEAN_OBJ(ac->once));
- // TODO(sctx): It would be good to unify script_ctx to actually work with lua
- // right now it's just super weird, and never really gives you the info that
- // you would expect from this.
- //
- // I think we should be able to get the line number, filename, etc. from lua
- // when we're executing something, and it should be easy to then save that
- // info here.
- //
- // I think it's a big loss not getting line numbers of where options, autocmds,
- // etc. are set (just getting "Sourced (lua)" or something is not that helpful.
- //
- // Once we do that, we can put these into the autocmd_info, but I don't think it's
- // useful to do that at this time.
- //
- // PUT(autocmd_info, "sid", INTEGER_OBJ(ac->script_ctx.sc_sid));
- // PUT(autocmd_info, "lnum", INTEGER_OBJ(ac->script_ctx.sc_lnum));
-
- ADD(autocmd_list, DICTIONARY_OBJ(autocmd_info));
+ if (ap->buflocal_nr) {
+ PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(true));
+ PUT(autocmd_info, "buffer", INTEGER_OBJ(ap->buflocal_nr));
+ } else {
+ PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(false));
}
+
+ // TODO(sctx): It would be good to unify script_ctx to actually work with lua
+ // right now it's just super weird, and never really gives you the info that
+ // you would expect from this.
+ //
+ // I think we should be able to get the line number, filename, etc. from lua
+ // when we're executing something, and it should be easy to then save that
+ // info here.
+ //
+ // I think it's a big loss not getting line numbers of where options, autocmds,
+ // etc. are set (just getting "Sourced (lua)" or something is not that helpful.
+ //
+ // Once we do that, we can put these into the autocmd_info, but I don't think it's
+ // useful to do that at this time.
+ //
+ // PUT(autocmd_info, "sid", INTEGER_OBJ(ac->script_ctx.sc_sid));
+ // PUT(autocmd_info, "lnum", INTEGER_OBJ(ac->script_ctx.sc_lnum));
+
+ ADD(autocmd_list, DICTIONARY_OBJ(autocmd_info));
}
}
@@ -365,28 +341,31 @@ cleanup:
/// function _name_ string) or `command` (Ex command string).
///
/// Example using Lua callback:
-/// <pre>lua
-/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
-/// pattern = {"*.c", "*.h"},
-/// callback = function(ev)
-/// print(string.format('event fired: %s', vim.inspect(ev)))
-/// end
-/// })
-/// </pre>
+///
+/// ```lua
+/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
+/// pattern = {"*.c", "*.h"},
+/// callback = function(ev)
+/// print(string.format('event fired: %s', vim.inspect(ev)))
+/// end
+/// })
+/// ```
///
/// Example using an Ex command as the handler:
-/// <pre>lua
-/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
-/// pattern = {"*.c", "*.h"},
-/// command = "echo 'Entering a C or C++ file'",
-/// })
-/// </pre>
+///
+/// ```lua
+/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
+/// pattern = {"*.c", "*.h"},
+/// command = "echo 'Entering a C or C++ file'",
+/// })
+/// ```
///
/// Note: `pattern` is NOT automatically expanded (unlike with |:autocmd|), thus names like "$HOME"
/// and "~" must be expanded explicitly:
-/// <pre>lua
-/// pattern = vim.fn.expand("~") .. "/some/path/*.py"
-/// </pre>
+///
+/// ```lua
+/// pattern = vim.fn.expand("~") .. "/some/path/*.py"
+/// ```
///
/// @param event (string|array) Event(s) that will trigger the handler (`callback` or `command`).
/// @param opts Options dict:
@@ -404,7 +383,7 @@ cleanup:
/// - match: (string) expanded value of |<amatch>|
/// - buf: (number) expanded value of |<abuf>|
/// - file: (string) expanded value of |<afile>|
-/// - data: (any) arbitrary data passed to |nvim_exec_autocmds()|
+/// - data: (any) arbitrary data passed from |nvim_exec_autocmds()|
/// - command (string) optional: Vim command to execute on event. Cannot be used with
/// {callback}
/// - once (boolean) optional: defaults to false. Run the autocommand
@@ -421,10 +400,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 +409,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, create_autocmd, callback) || !HAS_KEY(opts, create_autocmd, 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, create_autocmd, 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,61 +435,50 @@ 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) {
- 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");
- goto cleanup;
- }
+ } else if (HAS_KEY(opts, create_autocmd, command)) {
+ aucmd.type = CALLABLE_EX;
+ aucmd.callable.cmd = string_to_cstr(opts->command);
} 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);
- bool is_nested = api_object_to_bool(opts->nested, "nested", false, err);
-
int au_group = get_augroup_from_object(opts->group, err);
if (au_group == AUGROUP_ERROR) {
goto cleanup;
}
- if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) {
+ bool has_buffer = HAS_KEY(opts, create_autocmd, buffer);
+
+ VALIDATE((!HAS_KEY(opts, create_autocmd, pattern) || !has_buffer),
+ "%s", "Cannot use both 'pattern' and 'buffer' for the same autocmd", {
+ goto cleanup;
+ });
+
+ if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, has_buffer, opts->buffer, err)) {
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");
- goto cleanup;
- }
+ if (HAS_KEY(opts, create_autocmd, desc)) {
+ desc = opts->desc.data;
}
if (patterns.size == 0) {
- ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("*")));
+ ADD(patterns, STATIC_CSTR_TO_OBJ("*"));
}
- 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, {
@@ -535,8 +494,8 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
pat.data.string.data,
(int)pat.data.string.size,
au_group,
- is_once,
- is_nested,
+ opts->once,
+ opts->nested,
desc,
aucmd);
});
@@ -556,25 +515,22 @@ cleanup:
return autocmd_id;
}
-/// Delete an autocommand by id.
+/// Deletes an autocommand by id.
///
-/// NOTE: Only autocommands created via the API have an id.
-/// @param id Integer The id returned by nvim_create_autocmd
-/// @see |nvim_create_autocmd()|
+/// @param id Integer Autocommand id returned by |nvim_create_autocmd()|
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");
}
}
-/// Clear all autocommands that match the corresponding {opts}. To delete
-/// a particular autocmd, see |nvim_del_autocmd()|.
+/// Clears all autocommands selected by {opts}. To delete autocmds see |nvim_del_autocmd()|.
+///
/// @param opts Parameters
/// - event: (string|table)
/// Examples:
@@ -610,25 +566,26 @@ 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'");
+ bool has_buffer = HAS_KEY(opts, clear_autocmds, buffer);
+
+ VALIDATE((!HAS_KEY(opts, clear_autocmds, pattern) || !has_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) {
goto cleanup;
}
- if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) {
+ if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, has_buffer, opts->buffer, err)) {
goto cleanup;
}
// When we create the autocmds, we want to say that they are all matched, so that's *
// but when we clear them, we want to say that we didn't pass a pattern, so that's NUL
if (patterns.size == 0) {
- ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("")));
+ ADD(patterns, STATIC_CSTR_TO_OBJ(""));
}
// If we didn't pass any events, that means clear all events.
@@ -636,7 +593,7 @@ void nvim_clear_autocmds(Dict(clear_autocmds) *opts, Error *err)
FOR_ALL_AUEVENTS(event) {
FOREACH_ITEM(patterns, pat_object, {
char *pat = pat_object.data.string.data;
- if (!clear_autocmd(event, (char *)pat, au_group, err)) {
+ if (!clear_autocmd(event, pat, au_group, err)) {
goto cleanup;
}
});
@@ -647,7 +604,7 @@ void nvim_clear_autocmds(Dict(clear_autocmds) *opts, Error *err)
FOREACH_ITEM(patterns, pat_object, {
char *pat = pat_object.data.string.data;
- if (!clear_autocmd(event_nr, (char *)pat, au_group, err)) {
+ if (!clear_autocmd(event_nr, pat, au_group, err)) {
goto cleanup;
}
});
@@ -662,11 +619,12 @@ cleanup:
/// Create or get an autocommand group |autocmd-groups|.
///
/// To get an existing group id, do:
-/// <pre>lua
-/// local id = vim.api.nvim_create_augroup("MyGroup", {
-/// clear = false
-/// })
-/// </pre>
+///
+/// ```lua
+/// local id = vim.api.nvim_create_augroup("MyGroup", {
+/// clear = false
+/// })
+/// ```
///
/// @param name String: The name of the group
/// @param opts Dictionary Parameters
@@ -691,7 +649,7 @@ Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augrou
if (clear_autocmds) {
FOR_ALL_AUEVENTS(event) {
- aupat_del_for_event_and_group(event, augroup);
+ aucmd_del_for_event_and_group(event, augroup);
}
}
});
@@ -711,11 +669,9 @@ Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augrou
void nvim_del_augroup_by_id(Integer id, Error *err)
FUNC_API_SINCE(9)
{
- TRY_WRAP({
- try_start();
- char *name = augroup_name((int)id);
+ TRY_WRAP(err, {
+ char *name = id == 0 ? NULL : augroup_name((int)id);
augroup_del(name, false);
- try_end(err);
});
}
@@ -728,10 +684,8 @@ void nvim_del_augroup_by_id(Integer id, Error *err)
void nvim_del_augroup_by_name(String name, Error *err)
FUNC_API_SINCE(9)
{
- TRY_WRAP({
- try_start();
+ TRY_WRAP(err, {
augroup_del(name.data, false);
- try_end(err);
});
}
@@ -772,62 +726,59 @@ 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);
+ char *name = au_group == 0 ? NULL : augroup_name(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) {
- 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);
+ bool has_buffer = false;
+ if (HAS_KEY(opts, exec_autocmds, buffer)) {
+ VALIDATE((!HAS_KEY(opts, exec_autocmds, pattern)),
+ "%s", "Cannot use both 'pattern' and 'buffer' for the same autocmd", {
goto cleanup;
- }
+ });
- buf = find_buffer_by_handle((Buffer)buf_obj.data.integer, err);
+ has_buffer = true;
+ buf = find_buffer_by_handle(opts->buffer, err);
if (ERROR_SET(err)) {
goto cleanup;
}
}
- if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) {
+ if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, has_buffer, opts->buffer, err)) {
goto cleanup;
}
if (patterns.size == 0) {
- ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("")));
+ ADD(patterns, STATIC_CSTR_TO_OBJ(""));
}
- if (opts->data.type != kObjectTypeNil) {
+ if (HAS_KEY(opts, exec_autocmds, data)) {
data = &opts->data;
}
- modeline = api_object_to_bool(opts->modeline, "modeline", true, err);
+ modeline = GET_BOOL_OR_TRUE(opts, exec_autocmds, modeline);
bool did_aucmd = false;
FOREACH_ITEM(event_array, event_str, {
GET_ONE_EVENT(event_nr, event_str, cleanup)
FOREACH_ITEM(patterns, pat, {
- char *fname = opts->buffer.type == kObjectTypeNil ? pat.data.string.data : NULL;
- did_aucmd |=
- apply_autocmds_group(event_nr, fname, NULL, true, au_group, buf, NULL, data);
+ char *fname = !has_buffer ? pat.data.string.data : NULL;
+ did_aucmd |= apply_autocmds_group(event_nr, fname, NULL, true, au_group, buf, NULL, data);
})
})
@@ -840,45 +791,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,88 +819,71 @@ 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);
+ char *name = au_group == 0 ? NULL : augroup_name(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;
+ });
}
}
-static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Object buffer,
- Error *err)
+static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, bool has_buffer,
+ Buffer buffer, Error *err)
{
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");
- return false;
- } else if (pattern.type != kObjectTypeNil) {
+ if (pattern.type != kObjectTypeNil) {
Object *v = &pattern;
if (v->type == kObjectTypeString) {
- char *pat = v->data.string.data;
+ const char *pat = v->data.string.data;
size_t patlen = aucmd_pattern_length(pat);
while (patlen) {
- ADD(*patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen)));
+ ADD(*patterns, STRING_OBJ(cbuf_to_string(pat, patlen)));
pat = aucmd_next_pattern(pat, patlen);
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;
}
Array array = v->data.array;
FOREACH_ITEM(array, entry, {
- char *pat = entry.data.string.data;
+ const char *pat = entry.data.string.data;
size_t patlen = aucmd_pattern_length(pat);
while (patlen) {
- ADD(*patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen)));
+ ADD(*patterns, STRING_OBJ(cbuf_to_string(pat, patlen)));
pat = aucmd_next_pattern(pat, patlen);
patlen = aucmd_pattern_length(pat);
}
})
} else {
- api_set_error(err,
- kErrorTypeValidation,
- "'pattern' must be a string or table");
- return false;
- }
- } else if (buffer.type != kObjectTypeNil) {
- if (buffer.type != kObjectTypeInteger && buffer.type != kObjectTypeBuffer) {
- api_set_error(err,
- kErrorTypeValidation,
- "'buffer' must be an integer");
- return false;
+ VALIDATE_EXP(false, "pattern", "String or Table", api_typename(v->type), {
+ return false;
+ });
}
-
- buf_T *buf = find_buffer_by_handle((Buffer)buffer.data.integer, err);
+ } else if (has_buffer) {
+ buf_T *buf = find_buffer_by_handle(buffer, err);
if (ERROR_SET(err)) {
return false;
}
snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle);
- ADD(*patterns, STRING_OBJ(cstr_to_string((char *)pattern_buflocal)));
+ ADD(*patterns, CSTR_TO_OBJ(pattern_buflocal));
}
return true;
diff --git a/src/nvim/api/autocmd.h b/src/nvim/api/autocmd.h
index f9432830d9..4ab3ddb943 100644
--- a/src/nvim/api/autocmd.h
+++ b/src/nvim/api/autocmd.h
@@ -1,11 +1,10 @@
-#ifndef NVIM_API_AUTOCMD_H
-#define NVIM_API_AUTOCMD_H
+#pragma once
-#include <stdint.h>
+#include <stdint.h> // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/autocmd.h.generated.h"
#endif
-#endif // NVIM_API_AUTOCMD_H
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index fe9e6077d6..0df231868d 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -1,10 +1,6 @@
-// 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
-
// Some of this code was adapted from 'if_py_both.h' from the original
// vim source
-#include <assert.h>
#include <lauxlib.h>
#include <stdbool.h>
#include <stddef.h>
@@ -14,9 +10,11 @@
#include "klib/kvec.h"
#include "lua.h"
#include "nvim/api/buffer.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/api/private/validate.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -26,6 +24,7 @@
#include "nvim/drawscreen.h"
#include "nvim/ex_cmds.h"
#include "nvim/extmark.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/lua/executor.h"
#include "nvim/mapping.h"
@@ -34,10 +33,11 @@
#include "nvim/memory.h"
#include "nvim/move.h"
#include "nvim/ops.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/pos_defs.h"
+#include "nvim/state_defs.h"
+#include "nvim/types_defs.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/buffer.c.generated.h"
@@ -47,7 +47,7 @@
///
/// \brief For more information on buffers, see |buffers|
///
-/// Unloaded Buffers:~
+/// Unloaded Buffers: ~
///
/// Buffers may be unloaded by the |:bunload| command or the buffer's
/// |'bufhidden'| option. When a buffer is unloaded its file contents are freed
@@ -83,12 +83,16 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err)
/// Activates buffer-update events on a channel, or as Lua callbacks.
///
/// Example (Lua): capture buffer updates in a global `events` variable
-/// (use "print(vim.inspect(events))" to see its contents):
-/// <pre>lua
-/// events = {}
-/// vim.api.nvim_buf_attach(0, false, {
-/// on_lines=function(...) table.insert(events, {...}) end})
-/// </pre>
+/// (use "vim.print(events)" to see its contents):
+///
+/// ```lua
+/// events = {}
+/// vim.api.nvim_buf_attach(0, false, {
+/// on_lines = function(...)
+/// table.insert(events, {...})
+/// end,
+/// })
+/// ```
///
/// @see |nvim_buf_detach()|
/// @see |api-buffer-updates-lua|
@@ -111,7 +115,7 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err)
/// - byte count of previous contents
/// - deleted_codepoints (if `utf_sizes` is true)
/// - deleted_codeunits (if `utf_sizes` is true)
-/// - on_bytes: lua callback invoked on change.
+/// - on_bytes: Lua callback invoked on change.
/// This callback receives more granular information about the
/// change compared to on_lines.
/// Return `true` to detach.
@@ -179,11 +183,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 +196,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, "'opts' key", k.data, {
goto error;
- }
+ });
}
return buf_updates_register(buf, channel_id, cb, send_buffer);
@@ -252,6 +251,9 @@ void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last, Error *e
if (!buf) {
return;
}
+ if (last < 0) {
+ last = buf->b_ml.ml_line_count;
+ }
redraw_buf_range_later(buf, (linenr_T)first + 1, (linenr_T)last);
}
@@ -297,10 +299,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 +326,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
@@ -371,7 +350,7 @@ static bool check_string_array(Array arr, bool disallow_nl, Error *err)
void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integer end,
Boolean strict_indexing, ArrayOf(String) replacement, Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -379,24 +358,27 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
return;
}
+ // load buffer first if it's not loaded
+ if (buf->b_ml.ml_mfp == NULL) {
+ if (!buf_ensure_loaded(buf)) {
+ api_set_error(err, kErrorTypeException, "Failed to load buffer");
+ return;
+ }
+ }
+
bool oob = false;
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;
}
@@ -415,27 +397,25 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
}
try_start();
- aco_save_T aco;
- aucmd_prepbuf(&aco, buf);
if (!MODIFIABLE(buf)) {
api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'");
goto end;
}
- if (u_save((linenr_T)(start - 1), (linenr_T)end) == FAIL) {
+ if (u_save_buf(buf, (linenr_T)(start - 1), (linenr_T)end) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to save undo information");
goto end;
}
- bcount_t deleted_bytes = get_region_bytecount(curbuf, (linenr_T)start, (linenr_T)end, 0, 0);
+ bcount_t deleted_bytes = get_region_bytecount(buf, (linenr_T)start, (linenr_T)end, 0, 0);
// If the size of the range is reducing (ie, new_len < old_len) we
// need to delete some old_len. We do this at the start, by
// repeatedly deleting line "start".
- size_t to_delete = (new_len < old_len) ? (size_t)(old_len - new_len) : 0;
+ size_t to_delete = (new_len < old_len) ? old_len - new_len : 0;
for (size_t i = 0; i < to_delete; i++) {
- if (ml_delete((linenr_T)start, false) == FAIL) {
+ if (ml_delete_buf(buf, (linenr_T)start, false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to delete line");
goto end;
}
@@ -453,12 +433,11 @@ 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) {
+ if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to replace line");
goto end;
}
@@ -473,12 +452,11 @@ 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) {
+ if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to insert line");
goto end;
}
@@ -493,20 +471,21 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
// Adjust marks. Invalidate any which lie in the
// changed range, and move any in the remainder of the buffer.
- // Only adjust marks if we managed to switch to a window that holds
- // the buffer, otherwise line numbers will be invalid.
- mark_adjust((linenr_T)start,
- (linenr_T)(end - 1),
- MAXLNUM,
- (linenr_T)extra,
- kExtmarkNOOP);
-
- extmark_splice(curbuf, (int)start - 1, 0, (int)(end - start), 0,
+ linenr_T adjust = end > start ? MAXLNUM : 0;
+ mark_adjust_buf(buf, (linenr_T)start, (linenr_T)(end - 1), adjust, (linenr_T)extra,
+ true, true, kExtmarkNOOP);
+
+ extmark_splice(buf, (int)start - 1, 0, (int)(end - start), 0,
deleted_bytes, (int)new_len, 0, inserted_bytes,
kExtmarkUndo);
- changed_lines((linenr_T)start, 0, (linenr_T)end, (linenr_T)extra, true);
- fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra);
+ changed_lines(buf, (linenr_T)start, 0, (linenr_T)end, (linenr_T)extra, true);
+
+ FOR_ALL_TAB_WINDOWS(tp, win) {
+ if (win->w_buffer == buf) {
+ fix_cursor(win, (linenr_T)start, (linenr_T)end, (linenr_T)extra);
+ }
+ }
end:
for (size_t i = 0; i < new_len; i++) {
@@ -514,7 +493,6 @@ end:
}
xfree(lines);
- aucmd_restbuf(&aco);
try_end(err);
}
@@ -533,7 +511,10 @@ end:
///
/// Prefer |nvim_buf_set_lines()| if you are only adding or deleting entire lines.
///
+/// Prefer |nvim_put()| if you want to insert text at the cursor position.
+///
/// @see |nvim_buf_set_lines()|
+/// @see |nvim_put()|
///
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
@@ -546,10 +527,11 @@ end:
void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, Integer start_col,
Integer end_row, Integer end_col, ArrayOf(String) replacement, Error *err)
FUNC_API_SINCE(7)
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
MAXSIZE_TEMP_ARRAY(scratch, 1);
if (replacement.size == 0) {
- ADD_C(scratch, STRING_OBJ(STATIC_CSTR_AS_STRING("")));
+ ADD_C(scratch, STATIC_CSTR_AS_OBJ(""));
replacement = scratch;
}
@@ -558,48 +540,54 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
return;
}
+ // load buffer first if it's not loaded
+ if (buf->b_ml.ml_mfp == NULL) {
+ if (!buf_ensure_loaded(buf)) {
+ api_set_error(err, kErrorTypeException, "Failed to load buffer");
+ return;
+ }
+ }
+
bool oob = false;
// 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;
// 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));
+ str_at_start = xstrdup(ml_get_buf(buf, (linenr_T)start_row));
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");
+ start_col = start_col < 0 ? (int64_t)len_at_start + start_col + 1 : start_col;
+ 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));
+ str_at_end = xstrdup(ml_get_buf(buf, (linenr_T)end_row));
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");
+ end_col = end_col < 0 ? (int64_t)len_at_end + end_col + 1 : end_col;
+ 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;
}
@@ -616,7 +604,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
for (int64_t i = 1; i < end_row - start_row; i++) {
int64_t lnum = start_row + i;
- const char *bufline = ml_get_buf(buf, (linenr_T)lnum, false);
+ const char *bufline = ml_get_buf(buf, (linenr_T)lnum);
old_byte += (bcount_t)(strlen(bufline)) + 1;
}
old_byte += (bcount_t)end_col + 1;
@@ -662,8 +650,6 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
}
try_start();
- aco_save_T aco;
- aucmd_prepbuf(&aco, buf);
if (!MODIFIABLE(buf)) {
api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'");
@@ -672,7 +658,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
// Small note about undo states: unlike set_lines, we want to save the
// undo state of one past the end_row, since end_row is inclusive.
- if (u_save((linenr_T)start_row - 1, (linenr_T)end_row + 1) == FAIL) {
+ if (u_save_buf(buf, (linenr_T)start_row - 1, (linenr_T)end_row + 1) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to save undo information");
goto end;
}
@@ -683,9 +669,9 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
// If the size of the range is reducing (ie, new_len < old_len) we
// need to delete some old_len. We do this at the start, by
// repeatedly deleting line "start".
- size_t to_delete = (new_len < old_len) ? (size_t)(old_len - new_len) : 0;
+ size_t to_delete = (new_len < old_len) ? old_len - new_len : 0;
for (size_t i = 0; i < to_delete; i++) {
- if (ml_delete((linenr_T)start_row, false) == FAIL) {
+ if (ml_delete_buf(buf, (linenr_T)start_row, false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to delete line");
goto end;
}
@@ -702,12 +688,11 @@ 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) {
+ if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to replace line");
goto end;
}
@@ -720,12 +705,11 @@ 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) {
+ if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to insert line");
goto end;
}
@@ -736,35 +720,39 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
extra++;
}
+ colnr_T col_extent = (colnr_T)(end_col
+ - ((end_row == start_row) ? start_col : 0));
+
// Adjust marks. Invalidate any which lie in the
// changed range, and move any in the remainder of the buffer.
- mark_adjust((linenr_T)start_row,
- (linenr_T)end_row,
- MAXLNUM,
- (linenr_T)extra,
- kExtmarkNOOP);
+ // Do not adjust any cursors. need to use column-aware logic (below)
+ linenr_T adjust = end_row >= start_row ? MAXLNUM : 0;
+ mark_adjust_buf(buf, (linenr_T)start_row, (linenr_T)end_row, adjust, (linenr_T)extra,
+ true, true, kExtmarkNOOP);
- colnr_T col_extent = (colnr_T)(end_col
- - ((end_row == start_row) ? start_col : 0));
extmark_splice(buf, (int)start_row - 1, (colnr_T)start_col,
(int)(end_row - start_row), col_extent, old_byte,
(int)new_len - 1, (colnr_T)last_item.size, new_byte,
kExtmarkUndo);
- changed_lines((linenr_T)start_row, 0, (linenr_T)end_row + 1, (linenr_T)extra, true);
+ changed_lines(buf, (linenr_T)start_row, 0, (linenr_T)end_row + 1, (linenr_T)extra, true);
- // adjust cursor like an extmark ( i e it was inside last_part_len)
- if (curwin->w_cursor.lnum == end_row && curwin->w_cursor.col > end_col) {
- curwin->w_cursor.col -= col_extent - (colnr_T)last_item.size;
+ FOR_ALL_TAB_WINDOWS(tp, win) {
+ if (win->w_buffer == buf) {
+ if (win->w_cursor.lnum >= start_row && win->w_cursor.lnum <= end_row) {
+ fix_cursor_cols(win, (linenr_T)start_row, (colnr_T)start_col, (linenr_T)end_row,
+ (colnr_T)end_col, (linenr_T)new_len, (colnr_T)last_item.size);
+ } else {
+ fix_cursor(win, (linenr_T)start_row, (linenr_T)end_row, (linenr_T)extra);
+ }
+ }
}
- fix_cursor((linenr_T)start_row, (linenr_T)end_row, (linenr_T)extra);
end:
for (size_t i = 0; i < new_len; i++) {
xfree(lines[i]);
}
xfree(lines);
- aucmd_restbuf(&aco);
try_end(err);
early_end:
@@ -800,10 +788,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 +807,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 +892,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);
}
@@ -1100,7 +1084,7 @@ Boolean nvim_buf_is_loaded(Buffer buffer)
/// - unload: Unloaded only, do not delete. See |:bunload|
void nvim_buf_delete(Buffer buffer, Dictionary opts, Error *err)
FUNC_API_SINCE(7)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -1118,8 +1102,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, "'opts' key", k.data, {
+ return;
+ });
}
}
@@ -1174,20 +1159,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,19 +1205,18 @@ 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);
return res;
}
-/// Returns a tuple (row,col) representing the position of the named mark. See
-/// |mark-motions|.
+/// Returns a `(row,col)` tuple representing the position of the named mark.
+/// "End of line" column position is returned as |v:maxcol| (big number).
+/// See |mark-motions|.
///
/// Marks are (1,0)-indexed. |api-indexing|
///
@@ -1257,21 +1237,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;
@@ -1295,15 +1272,15 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
/// Otherwise a temporary scratch window (called the "autocmd window" for
/// historical reasons) will be used.
///
-/// This is useful e.g. to call vimL functions that only work with the current
-/// buffer/window currently, like |termopen()|.
+/// This is useful e.g. to call Vimscript functions that only work with the
+/// current buffer/window currently, like |termopen()|.
///
/// @param buffer Buffer handle, or 0 for current buffer
-/// @param fun Function to call inside the buffer (currently lua callable
+/// @param fun Function to call inside the buffer (currently Lua callable
/// only)
/// @param[out] err Error details, if any
-/// @return Return value of function. NB: will deepcopy lua values
-/// currently, use upvalues to send lua references in and out.
+/// @return Return value of function. NB: will deepcopy Lua values
+/// currently, use upvalues to send Lua references in and out.
Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err)
FUNC_API_SINCE(7)
FUNC_API_LUA_ONLY
@@ -1362,42 +1339,92 @@ Dictionary nvim__buf_stats(Buffer buffer, Error *err)
// Check if deleting lines made the cursor position invalid.
// Changed lines from `lo` to `hi`; added `extra` lines (negative if deleted).
-static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra)
+static void fix_cursor(win_T *win, linenr_T lo, linenr_T hi, linenr_T extra)
{
- if (curwin->w_cursor.lnum >= lo) {
+ if (win->w_cursor.lnum >= lo) {
// Adjust cursor position if it's in/after the changed lines.
- if (curwin->w_cursor.lnum >= hi) {
- curwin->w_cursor.lnum += extra;
- check_cursor_col();
+ if (win->w_cursor.lnum >= hi) {
+ win->w_cursor.lnum += extra;
} else if (extra < 0) {
- check_cursor();
- } else {
- check_cursor_col();
+ check_cursor_lnum(win);
}
- changed_cline_bef_curs();
+ check_cursor_col_win(win);
+ changed_cline_bef_curs(win);
}
- invalidate_botline();
+ invalidate_botline(win);
}
-// Normalizes 0-based indexes to buffer line numbers
-static int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bool *oob)
+/// Fix cursor position after replacing text
+/// between (start_row, start_col) and (end_row, end_col).
+///
+/// win->w_cursor.lnum is assumed to be >= start_row and <= end_row.
+static void fix_cursor_cols(win_T *win, linenr_T start_row, colnr_T start_col, linenr_T end_row,
+ colnr_T end_col, linenr_T new_rows, colnr_T new_cols_at_end_row)
{
- assert(buf->b_ml.ml_line_count > 0);
- int64_t max_index = buf->b_ml.ml_line_count + (int)end_exclusive - 1;
- // Fix if < 0
- index = index < 0 ? max_index + index + 1 : index;
-
- // Check for oob
- if (index > max_index) {
- *oob = true;
- index = max_index;
- } else if (index < 0) {
- *oob = true;
- index = 0;
- }
- // Convert the index to a vim line number
- index++;
- return index;
+ colnr_T mode_col_adj = win == curwin && (State & MODE_INSERT) ? 0 : 1;
+
+ colnr_T end_row_change_start = new_rows == 1 ? start_col : 0;
+ colnr_T end_row_change_end = end_row_change_start + new_cols_at_end_row;
+
+ // check if cursor is after replaced range or not
+ if (win->w_cursor.lnum == end_row && win->w_cursor.col + mode_col_adj > end_col) {
+ // if cursor is after replaced range, it's shifted
+ // to keep it's position the same, relative to end_col
+
+ linenr_T old_rows = end_row - start_row + 1;
+ win->w_cursor.lnum += new_rows - old_rows;
+ win->w_cursor.col += end_row_change_end - end_col;
+ } else {
+ // if cursor is inside replaced range
+ // and the new range got smaller,
+ // it's shifted to keep it inside the new range
+ //
+ // if cursor is before range or range did not
+ // got smaller, position is not changed
+
+ colnr_T old_coladd = win->w_cursor.coladd;
+
+ // it's easier to work with a single value here.
+ // col and coladd are fixed by a later call
+ // to check_cursor_col_win when necessary
+ win->w_cursor.col += win->w_cursor.coladd;
+ win->w_cursor.coladd = 0;
+
+ linenr_T new_end_row = start_row + new_rows - 1;
+
+ // make sure cursor row is in the new row range
+ if (win->w_cursor.lnum > new_end_row) {
+ win->w_cursor.lnum = new_end_row;
+
+ // don't simply move cursor up, but to the end
+ // of new_end_row, if it's not at or after
+ // it already (in case virtualedit is active)
+ // column might be additionally adjusted below
+ // to keep it inside col range if needed
+ colnr_T len = (colnr_T)strlen(ml_get_buf(win->w_buffer, new_end_row));
+ if (win->w_cursor.col < len) {
+ win->w_cursor.col = len;
+ }
+ }
+
+ // if cursor is at the last row and
+ // it wasn't after eol before, move it exactly
+ // to end_row_change_end
+ if (win->w_cursor.lnum == new_end_row
+ && win->w_cursor.col > end_row_change_end && old_coladd == 0) {
+ win->w_cursor.col = end_row_change_end;
+
+ // make sure cursor is inside range, not after it,
+ // except when doing so would move it before new range
+ if (win->w_cursor.col - mode_col_adj >= end_row_change_start) {
+ win->w_cursor.col -= mode_col_adj;
+ }
+ }
+ }
+
+ check_cursor_col_win(win);
+ changed_cline_bef_curs(win);
+ invalidate_botline(win);
}
/// Initialise a string array either:
@@ -1481,7 +1508,7 @@ bool buf_collect_lines(buf_T *buf, size_t n, linenr_T start, int start_idx, bool
return false;
}
- char *bufstr = ml_get_buf(buf, lnum, false);
+ char *bufstr = ml_get_buf(buf, lnum);
push_linestr(lstate, l, bufstr, strlen(bufstr), start_idx + (int)i, replace_nl);
}
diff --git a/src/nvim/api/buffer.h b/src/nvim/api/buffer.h
index 0814da63cd..f3971c1d30 100644
--- a/src/nvim/api/buffer.h
+++ b/src/nvim/api/buffer.h
@@ -1,12 +1,12 @@
-#ifndef NVIM_API_BUFFER_H
-#define NVIM_API_BUFFER_H
+#pragma once
-#include <lauxlib.h>
+#include <lua.h> // IWYU pragma: keep
+#include <stdint.h> // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/buffer.h.generated.h"
#endif
-#endif // NVIM_API_BUFFER_H
diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c
index abd265f2cf..2a57ce9a19 100644
--- a/src/nvim/api/command.c
+++ b/src/nvim/api/command.c
@@ -1,36 +1,37 @@
-// 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 <inttypes.h>
+#include <lauxlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "klib/kvec.h"
-#include "lauxlib.h"
#include "nvim/api/command.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/api/private/validate.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
-#include "nvim/decoration.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/globals.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/ops.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/usercmd.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -99,16 +100,15 @@ 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;
CmdParseInfo cmdinfo;
char *cmdline = string_to_cstr(str);
- char *errormsg = NULL;
+ const char *errormsg = NULL;
if (!parse_cmdline(cmdline, &ea, &cmdinfo, &errormsg)) {
if (errormsg != NULL) {
@@ -127,7 +127,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
// otherwise split arguments by whitespace.
if (ea.argt & EX_NOSPC) {
if (*ea.arg != NUL) {
- ADD(args, STRING_OBJ(cstrn_to_string((char *)ea.arg, length)));
+ ADD(args, STRING_OBJ(cstrn_to_string(ea.arg, length)));
}
} else {
size_t end = 0;
@@ -153,9 +153,9 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
}
if (cmd != NULL) {
- PUT(result, "cmd", CSTR_TO_OBJ((char *)cmd->uc_name));
+ PUT(result, "cmd", CSTR_TO_OBJ(cmd->uc_name));
} else {
- PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx)));
+ PUT(result, "cmd", CSTR_TO_OBJ(get_command_name(NULL, ea.cmdidx)));
}
if (ea.argt & EX_RANGE) {
@@ -237,14 +237,14 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
break;
}
PUT(result, "addr", CSTR_TO_OBJ(addr));
- PUT(result, "nextcmd", CSTR_TO_OBJ((char *)ea.nextcmd));
+ PUT(result, "nextcmd", CSTR_TO_OBJ(ea.nextcmd));
Dictionary mods = ARRAY_DICT_INIT;
Dictionary filter = ARRAY_DICT_INIT;
PUT(filter, "pattern", cmdinfo.cmdmod.cmod_filter_pat
? CSTR_TO_OBJ(cmdinfo.cmdmod.cmod_filter_pat)
- : STRING_OBJ(STATIC_CSTR_TO_STRING("")));
+ : STATIC_CSTR_TO_OBJ(""));
PUT(filter, "force", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_filter_force));
PUT(mods, "filter", DICTIONARY_OBJ(filter));
@@ -305,9 +305,9 @@ end:
/// make their usage simpler with |vim.cmd()|. For example, instead of
/// `vim.cmd.bdelete{ count = 2 }`, you may do `vim.cmd.bdelete(2)`.
///
-/// On execution error: fails with VimL error, updates v:errmsg.
+/// On execution error: fails with Vimscript error, updates v:errmsg.
///
-/// @see |nvim_exec()|
+/// @see |nvim_exec2()|
/// @see |nvim_command()|
///
/// @param cmd Command to execute. Must be a Dictionary that can contain the same values as
@@ -340,32 +340,22 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
} \
} while (0)
-#define OBJ_TO_CMOD_FLAG(flag, value, default, varname) \
+#define VALIDATE_MOD(cond, mod_, name_) \
do { \
- if (api_object_to_bool(value, varname, default, err)) { \
- cmdinfo.cmdmod.cmod_flags |= (flag); \
- } \
- if (ERROR_SET(err)) { \
+ if (!(cond)) { \
+ api_set_error(err, kErrorTypeValidation, "Command cannot accept %s: %s", (mod_), (name_)); \
goto end; \
} \
} while (0)
-#define VALIDATION_ERROR(...) \
- do { \
- api_set_error(err, kErrorTypeValidation, __VA_ARGS__); \
- goto end; \
- } while (0)
-
- bool output;
- OBJ_TO_BOOL(output, opts->output, false, "'output'");
-
- // First, parse the command name and check if it exists and is valid.
- if (!HAS_KEY(cmd->cmd) || cmd->cmd.type != kObjectTypeString
- || cmd->cmd.data.string.data[0] == NUL) {
- VALIDATION_ERROR("'cmd' must be a non-empty String");
- }
+ VALIDATE_R(HAS_KEY(cmd, cmd, cmd), "cmd", {
+ goto end;
+ });
+ VALIDATE_EXP((cmd->cmd.data[0] != NUL), "cmd", "non-empty String", NULL, {
+ goto end;
+ });
- cmdname = string_to_cstr(cmd->cmd.data.string);
+ cmdname = string_to_cstr(cmd->cmd);
ea.cmd = cmdname;
char *p = find_ex_command(&ea, NULL);
@@ -382,12 +372,18 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
p = (ret && !aborting()) ? find_ex_command(&ea, NULL) : ea.cmd;
}
- if (p == NULL || ea.cmdidx == CMD_SIZE) {
- VALIDATION_ERROR("Command not found: %s", cmdname);
- }
- if (is_cmd_ni(ea.cmdidx)) {
- VALIDATION_ERROR("Command not implemented: %s", cmdname);
- }
+ VALIDATE((p != NULL && ea.cmdidx != CMD_SIZE), "Command not found: %s", cmdname, {
+ goto end;
+ });
+ VALIDATE(!is_cmd_ni(ea.cmdidx), "Command not implemented: %s", cmdname, {
+ goto end;
+ });
+ const char *fullname = IS_USER_CMDIDX(ea.cmdidx)
+ ? get_user_command_name(ea.useridx, ea.cmdidx)
+ : get_command_name(NULL, ea.cmdidx);
+ VALIDATE(strncmp(fullname, cmdname, strlen(cmdname)) == 0, "Invalid command: \"%s\"", cmdname, {
+ goto end;
+ });
// Get the command flags so that we can know what type of arguments the command uses.
// Not required for a user command since `find_ex_command` already deals with it in that case.
@@ -396,15 +392,11 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
}
// Parse command arguments since it's needed to get the command address type.
- if (HAS_KEY(cmd->args)) {
- if (cmd->args.type != kObjectTypeArray) {
- VALIDATION_ERROR("'args' must be an Array");
- }
-
+ if (HAS_KEY(cmd, cmd, args)) {
// Process all arguments. Convert non-String arguments to String and check if String arguments
// have non-whitespace characters.
- for (size_t i = 0; i < cmd->args.data.array.size; i++) {
- Object elem = cmd->args.data.array.items[i];
+ for (size_t i = 0; i < cmd->args.size; i++) {
+ Object elem = cmd->args.items[i];
char *data_str;
switch (elem.type) {
@@ -421,18 +413,19 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
snprintf(data_str, NUMBUFLEN, "%" PRId64, elem.data.integer);
break;
case kObjectTypeString:
- if (string_iswhite(elem.data.string)) {
- VALIDATION_ERROR("String command argument must have at least one non-whitespace "
- "character");
- }
- data_str = xstrndup(elem.data.string.data, elem.data.string.size);
+ VALIDATE_EXP(!string_iswhite(elem.data.string), "command arg", "non-whitespace", NULL, {
+ goto end;
+ });
+ data_str = string_to_cstr(elem.data.string);
break;
default:
- VALIDATION_ERROR("Invalid type for command argument");
+ VALIDATE_EXP(false, "command arg", "valid type", api_typename(elem.type), {
+ goto end;
+ });
break;
}
- ADD(args, STRING_OBJ(cstr_as_string(data_str)));
+ ADD(args, CSTR_AS_OBJ(data_str));
}
bool argc_valid;
@@ -456,32 +449,30 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
break;
}
- if (!argc_valid) {
- VALIDATION_ERROR("Incorrect number of arguments supplied");
- }
+ VALIDATE(argc_valid, "%s", "Wrong number of arguments", {
+ goto end;
+ });
}
// Simply pass the first argument (if it exists) as the arg pointer to `set_cmd_addr_type()`
// since it only ever checks the first argument.
set_cmd_addr_type(&ea, args.size > 0 ? args.items[0].data.string.data : NULL);
- if (HAS_KEY(cmd->range)) {
- if (!(ea.argt & EX_RANGE)) {
- VALIDATION_ERROR("Command cannot accept a range");
- } else if (cmd->range.type != kObjectTypeArray) {
- VALIDATION_ERROR("'range' must be an Array");
- } else if (cmd->range.data.array.size > 2) {
- VALIDATION_ERROR("'range' cannot contain more than two elements");
- }
+ if (HAS_KEY(cmd, cmd, range)) {
+ VALIDATE_MOD((ea.argt & EX_RANGE), "range", cmd->cmd.data);
+ VALIDATE_EXP((cmd->range.size <= 2), "range", "<=2 elements", NULL, {
+ goto end;
+ });
- Array range = cmd->range.data.array;
+ Array range = cmd->range;
ea.addr_count = (int)range.size;
for (size_t i = 0; i < range.size; i++) {
Object elem = range.items[i];
- if (elem.type != kObjectTypeInteger || elem.data.integer < 0) {
- VALIDATION_ERROR("'range' element must be a non-negative Integer");
- }
+ VALIDATE_EXP((elem.type == kObjectTypeInteger && elem.data.integer >= 0),
+ "range element", "non-negative Integer", NULL, {
+ goto end;
+ });
}
if (range.size > 0) {
@@ -489,9 +480,9 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
ea.line2 = (linenr_T)range.items[range.size - 1].data.integer;
}
- if (invalid_range(&ea) != NULL) {
- VALIDATION_ERROR("Invalid range provided");
- }
+ VALIDATE_S((invalid_range(&ea) == NULL), "range", "", {
+ goto end;
+ });
}
if (ea.addr_count == 0) {
if (ea.argt & EX_DFLALL) {
@@ -506,48 +497,42 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
}
}
- if (HAS_KEY(cmd->count)) {
- if (!(ea.argt & EX_COUNT)) {
- VALIDATION_ERROR("Command cannot accept a count");
- } else if (cmd->count.type != kObjectTypeInteger || cmd->count.data.integer < 0) {
- VALIDATION_ERROR("'count' must be a non-negative Integer");
- }
- set_cmd_count(&ea, cmd->count.data.integer, true);
+ if (HAS_KEY(cmd, cmd, count)) {
+ VALIDATE_MOD((ea.argt & EX_COUNT), "count", cmd->cmd.data);
+ VALIDATE_EXP((cmd->count >= 0), "count", "non-negative Integer", NULL, {
+ goto end;
+ });
+ set_cmd_count(&ea, (linenr_T)cmd->count, true);
}
- if (HAS_KEY(cmd->reg)) {
- if (!(ea.argt & EX_REGSTR)) {
- VALIDATION_ERROR("Command cannot accept a register");
- } else if (cmd->reg.type != kObjectTypeString || cmd->reg.data.string.size != 1) {
- VALIDATION_ERROR("'reg' must be a single character");
- }
- char regname = cmd->reg.data.string.data[0];
- if (regname == '=') {
- VALIDATION_ERROR("Cannot use register \"=");
- } else if (!valid_yank_reg(regname, ea.cmdidx != CMD_put && !IS_USER_CMDIDX(ea.cmdidx))) {
- VALIDATION_ERROR("Invalid register: \"%c", regname);
- }
+ if (HAS_KEY(cmd, cmd, reg)) {
+ VALIDATE_MOD((ea.argt & EX_REGSTR), "register", cmd->cmd.data);
+ VALIDATE_EXP((cmd->reg.size == 1),
+ "reg", "single character", cmd->reg.data, {
+ goto end;
+ });
+ char regname = cmd->reg.data[0];
+ VALIDATE((regname != '='), "%s", "Cannot use register \"=", {
+ goto end;
+ });
+ VALIDATE(valid_yank_reg(regname, ea.cmdidx != CMD_put && !IS_USER_CMDIDX(ea.cmdidx)),
+ "Invalid register: \"%c", regname, {
+ goto end;
+ });
ea.regname = (uint8_t)regname;
}
- OBJ_TO_BOOL(ea.forceit, cmd->bang, false, "'bang'");
- if (ea.forceit && !(ea.argt & EX_BANG)) {
- VALIDATION_ERROR("Command cannot accept a bang");
- }
-
- if (HAS_KEY(cmd->magic)) {
- if (cmd->magic.type != kObjectTypeDictionary) {
- VALIDATION_ERROR("'magic' must be a Dictionary");
- }
+ ea.forceit = cmd->bang;
+ VALIDATE_MOD((!ea.forceit || (ea.argt & EX_BANG)), "bang", cmd->cmd.data);
- Dict(cmd_magic) magic = { 0 };
- if (!api_dict_to_keydict(&magic, KeyDict_cmd_magic_get_field,
- cmd->magic.data.dictionary, err)) {
+ if (HAS_KEY(cmd, cmd, magic)) {
+ Dict(cmd_magic) magic[1] = { 0 };
+ if (!api_dict_to_keydict(magic, KeyDict_cmd_magic_get_field, cmd->magic, err)) {
goto end;
}
- OBJ_TO_BOOL(cmdinfo.magic.file, magic.file, ea.argt & EX_XFILE, "'magic.file'");
- OBJ_TO_BOOL(cmdinfo.magic.bar, magic.bar, ea.argt & EX_TRLBAR, "'magic.bar'");
+ cmdinfo.magic.file = HAS_KEY(magic, cmd_magic, file) ? magic->file : (ea.argt & EX_XFILE);
+ cmdinfo.magic.bar = HAS_KEY(magic, cmd_magic, bar) ? magic->bar : (ea.argt & EX_TRLBAR);
if (cmdinfo.magic.file) {
ea.argt |= EX_XFILE;
} else {
@@ -558,107 +543,90 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
cmdinfo.magic.bar = ea.argt & EX_TRLBAR;
}
- if (HAS_KEY(cmd->mods)) {
- if (cmd->mods.type != kObjectTypeDictionary) {
- VALIDATION_ERROR("'mods' must be a Dictionary");
- }
-
- Dict(cmd_mods) mods = { 0 };
- if (!api_dict_to_keydict(&mods, KeyDict_cmd_mods_get_field, cmd->mods.data.dictionary, err)) {
+ if (HAS_KEY(cmd, cmd, mods)) {
+ Dict(cmd_mods) mods[1] = { 0 };
+ if (!api_dict_to_keydict(mods, KeyDict_cmd_mods_get_field, cmd->mods, err)) {
goto end;
}
- if (HAS_KEY(mods.filter)) {
- if (mods.filter.type != kObjectTypeDictionary) {
- VALIDATION_ERROR("'mods.filter' must be a Dictionary");
- }
-
- Dict(cmd_mods_filter) filter = { 0 };
+ if (HAS_KEY(mods, cmd_mods, filter)) {
+ Dict(cmd_mods_filter) filter[1] = { 0 };
if (!api_dict_to_keydict(&filter, KeyDict_cmd_mods_filter_get_field,
- mods.filter.data.dictionary, err)) {
+ mods->filter, err)) {
goto end;
}
- if (HAS_KEY(filter.pattern)) {
- if (filter.pattern.type != kObjectTypeString) {
- VALIDATION_ERROR("'mods.filter.pattern' must be a String");
- }
-
- OBJ_TO_BOOL(cmdinfo.cmdmod.cmod_filter_force, filter.force, false, "'mods.filter.force'");
+ if (HAS_KEY(filter, cmd_mods_filter, pattern)) {
+ cmdinfo.cmdmod.cmod_filter_force = filter->force;
// "filter! // is not no-op, so add a filter if either the pattern is non-empty or if filter
// is inverted.
- if (*filter.pattern.data.string.data != NUL || cmdinfo.cmdmod.cmod_filter_force) {
- cmdinfo.cmdmod.cmod_filter_pat = string_to_cstr(filter.pattern.data.string);
+ if (*filter->pattern.data != NUL || cmdinfo.cmdmod.cmod_filter_force) {
+ cmdinfo.cmdmod.cmod_filter_pat = string_to_cstr(filter->pattern);
cmdinfo.cmdmod.cmod_filter_regmatch.regprog = vim_regcomp(cmdinfo.cmdmod.cmod_filter_pat,
RE_MAGIC);
}
}
}
- if (HAS_KEY(mods.tab)) {
- if (mods.tab.type != kObjectTypeInteger) {
- VALIDATION_ERROR("'mods.tab' must be an Integer");
- } else if ((int)mods.tab.data.integer >= 0) {
+ if (HAS_KEY(mods, cmd_mods, tab)) {
+ if ((int)mods->tab >= 0) {
// Silently ignore negative integers to allow mods.tab to be set to -1.
- cmdinfo.cmdmod.cmod_tab = (int)mods.tab.data.integer + 1;
+ cmdinfo.cmdmod.cmod_tab = (int)mods->tab + 1;
}
}
- if (HAS_KEY(mods.verbose)) {
- if (mods.verbose.type != kObjectTypeInteger) {
- VALIDATION_ERROR("'mods.verbose' must be an Integer");
- } else if ((int)mods.verbose.data.integer >= 0) {
+ if (HAS_KEY(mods, cmd_mods, verbose)) {
+ if ((int)mods->verbose >= 0) {
// Silently ignore negative integers to allow mods.verbose to be set to -1.
- cmdinfo.cmdmod.cmod_verbose = (int)mods.verbose.data.integer + 1;
+ cmdinfo.cmdmod.cmod_verbose = (int)mods->verbose + 1;
}
}
- bool vertical;
- OBJ_TO_BOOL(vertical, mods.vertical, false, "'mods.vertical'");
- cmdinfo.cmdmod.cmod_split |= (vertical ? WSP_VERT : 0);
+ cmdinfo.cmdmod.cmod_split |= (mods->vertical ? WSP_VERT : 0);
- bool horizontal;
- OBJ_TO_BOOL(horizontal, mods.horizontal, false, "'mods.horizontal'");
- cmdinfo.cmdmod.cmod_split |= (horizontal ? WSP_HOR : 0);
+ cmdinfo.cmdmod.cmod_split |= (mods->horizontal ? WSP_HOR : 0);
- if (HAS_KEY(mods.split)) {
- if (mods.split.type != kObjectTypeString) {
- VALIDATION_ERROR("'mods.split' must be a String");
- }
-
- if (*mods.split.data.string.data == NUL) {
+ if (HAS_KEY(mods, cmd_mods, split)) {
+ if (*mods->split.data == NUL) {
// Empty string, do nothing.
- } else if (strcmp(mods.split.data.string.data, "aboveleft") == 0
- || strcmp(mods.split.data.string.data, "leftabove") == 0) {
+ } else if (strcmp(mods->split.data, "aboveleft") == 0
+ || strcmp(mods->split.data, "leftabove") == 0) {
cmdinfo.cmdmod.cmod_split |= WSP_ABOVE;
- } else if (strcmp(mods.split.data.string.data, "belowright") == 0
- || strcmp(mods.split.data.string.data, "rightbelow") == 0) {
+ } else if (strcmp(mods->split.data, "belowright") == 0
+ || strcmp(mods->split.data, "rightbelow") == 0) {
cmdinfo.cmdmod.cmod_split |= WSP_BELOW;
- } else if (strcmp(mods.split.data.string.data, "topleft") == 0) {
+ } else if (strcmp(mods->split.data, "topleft") == 0) {
cmdinfo.cmdmod.cmod_split |= WSP_TOP;
- } else if (strcmp(mods.split.data.string.data, "botright") == 0) {
+ } else if (strcmp(mods->split.data, "botright") == 0) {
cmdinfo.cmdmod.cmod_split |= WSP_BOT;
} else {
- VALIDATION_ERROR("Invalid value for 'mods.split'");
+ VALIDATE_S(false, "mods.split", "", {
+ goto end;
+ });
}
}
- OBJ_TO_CMOD_FLAG(CMOD_SILENT, mods.silent, false, "'mods.silent'");
- OBJ_TO_CMOD_FLAG(CMOD_ERRSILENT, mods.emsg_silent, false, "'mods.emsg_silent'");
- OBJ_TO_CMOD_FLAG(CMOD_UNSILENT, mods.unsilent, false, "'mods.unsilent'");
- OBJ_TO_CMOD_FLAG(CMOD_SANDBOX, mods.sandbox, false, "'mods.sandbox'");
- OBJ_TO_CMOD_FLAG(CMOD_NOAUTOCMD, mods.noautocmd, false, "'mods.noautocmd'");
- OBJ_TO_CMOD_FLAG(CMOD_BROWSE, mods.browse, false, "'mods.browse'");
- OBJ_TO_CMOD_FLAG(CMOD_CONFIRM, mods.confirm, false, "'mods.confirm'");
- OBJ_TO_CMOD_FLAG(CMOD_HIDE, mods.hide, false, "'mods.hide'");
- OBJ_TO_CMOD_FLAG(CMOD_KEEPALT, mods.keepalt, false, "'mods.keepalt'");
- OBJ_TO_CMOD_FLAG(CMOD_KEEPJUMPS, mods.keepjumps, false, "'mods.keepjumps'");
- OBJ_TO_CMOD_FLAG(CMOD_KEEPMARKS, mods.keepmarks, false, "'mods.keepmarks'");
- OBJ_TO_CMOD_FLAG(CMOD_KEEPPATTERNS, mods.keeppatterns, false, "'mods.keeppatterns'");
- OBJ_TO_CMOD_FLAG(CMOD_LOCKMARKS, mods.lockmarks, false, "'mods.lockmarks'");
- OBJ_TO_CMOD_FLAG(CMOD_NOSWAPFILE, mods.noswapfile, false, "'mods.noswapfile'");
+#define OBJ_TO_CMOD_FLAG(flag, value) \
+ if (value) { \
+ cmdinfo.cmdmod.cmod_flags |= (flag); \
+ }
+
+ OBJ_TO_CMOD_FLAG(CMOD_SILENT, mods->silent);
+ OBJ_TO_CMOD_FLAG(CMOD_ERRSILENT, mods->emsg_silent);
+ OBJ_TO_CMOD_FLAG(CMOD_UNSILENT, mods->unsilent);
+ OBJ_TO_CMOD_FLAG(CMOD_SANDBOX, mods->sandbox);
+ OBJ_TO_CMOD_FLAG(CMOD_NOAUTOCMD, mods->noautocmd);
+ OBJ_TO_CMOD_FLAG(CMOD_BROWSE, mods->browse);
+ OBJ_TO_CMOD_FLAG(CMOD_CONFIRM, mods->confirm);
+ OBJ_TO_CMOD_FLAG(CMOD_HIDE, mods->hide);
+ OBJ_TO_CMOD_FLAG(CMOD_KEEPALT, mods->keepalt);
+ OBJ_TO_CMOD_FLAG(CMOD_KEEPJUMPS, mods->keepjumps);
+ OBJ_TO_CMOD_FLAG(CMOD_KEEPMARKS, mods->keepmarks);
+ OBJ_TO_CMOD_FLAG(CMOD_KEEPPATTERNS, mods->keeppatterns);
+ OBJ_TO_CMOD_FLAG(CMOD_LOCKMARKS, mods->lockmarks);
+ OBJ_TO_CMOD_FLAG(CMOD_NOSWAPFILE, mods->noswapfile);
if (cmdinfo.cmdmod.cmod_flags & CMOD_ERRSILENT) {
// CMOD_ERRSILENT must imply CMOD_SILENT, otherwise apply_cmdmod() and undo_cmdmod() won't
@@ -666,9 +634,10 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
cmdinfo.cmdmod.cmod_flags |= CMOD_SILENT;
}
- if ((cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX) && !(ea.argt & EX_SBOXOK)) {
- VALIDATION_ERROR("Command cannot be run in sandbox");
- }
+ VALIDATE(!((cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX) && !(ea.argt & EX_SBOXOK)),
+ "%s", "Command cannot be run in sandbox", {
+ goto end;
+ });
}
// Finally, build the command line string that will be stored inside ea.cmdlinep.
@@ -681,14 +650,13 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
garray_T * const save_capture_ga = capture_ga;
const int save_msg_col = msg_col;
- if (output) {
+ if (opts->output) {
ga_init(&capture_local, 1, 80);
capture_ga = &capture_local;
}
- TRY_WRAP({
- try_start();
- if (output) {
+ TRY_WRAP(err, {
+ if (opts->output) {
msg_silent++;
msg_col = 0; // prevent leading spaces
}
@@ -697,21 +665,19 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
execute_cmd(&ea, &cmdinfo, false);
});
- if (output) {
+ if (opts->output) {
capture_ga = save_capture_ga;
msg_silent = save_msg_silent;
// Put msg_col back where it was, since nothing should have been written.
msg_col = save_msg_col;
}
-
- try_end(err);
});
if (ERROR_SET(err)) {
goto clear_ga;
}
- if (output && capture_local.ga_len > 1) {
+ if (opts->output && capture_local.ga_len > 1) {
retv = (String){
.data = capture_local.ga_data,
.size = (size_t)capture_local.ga_len,
@@ -725,7 +691,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
goto end;
}
clear_ga:
- if (output) {
+ if (opts->output) {
ga_clear(&capture_local);
}
end:
@@ -739,7 +705,7 @@ end:
#undef OBJ_TO_BOOL
#undef OBJ_TO_CMOD_FLAG
-#undef VALIDATION_ERROR
+#undef VALIDATE_MOD
}
/// Check if a string contains only whitespace characters.
@@ -870,8 +836,8 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin
offset += eap->arglens[i];
}
// If there isn't an argument, make eap->arg point to end of cmdline.
- eap->arg = argc > 0 ? eap->args[0] :
- cmdline.items + cmdline.size - 1; // Subtract 1 to account for NUL
+ eap->arg = argc > 0 ? eap->args[0]
+ : cmdline.items + cmdline.size - 1; // Subtract 1 to account for NUL
// Finally, make cmdlinep point to the cmdline string.
*cmdlinep = cmdline.items;
@@ -888,18 +854,17 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin
}
}
-/// Create a new user command |user-commands|
+/// Creates a global |user-commands| command.
///
-/// {name} is the name of the new command. The name must begin with an uppercase letter.
-///
-/// {command} is the replacement text or Lua function to execute.
+/// For Lua usage see |lua-guide-commands-create|.
///
/// Example:
-/// <pre>vim
-/// :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {})
-/// :SayHello
-/// Hello world!
-/// </pre>
+///
+/// ```vim
+/// :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {'bang': v:true})
+/// :SayHello
+/// Hello world!
+/// ```
///
/// @param name Name of the new user command. Must begin with an uppercase letter.
/// @param command Replacement command to execute when this user command is executed. When called
@@ -909,6 +874,7 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin
/// - args: (string) The args passed to the command, if any |<args>|
/// - fargs: (table) The args split by unescaped whitespace (when more than one
/// argument is allowed), if any |<f-args>|
+/// - nargs: (string) Number of arguments |:command-nargs|
/// - bang: (boolean) "true" if the command was executed with a ! modifier |<bang>|
/// - line1: (number) The starting line of the command range |<line1>|
/// - line2: (number) The final line of the command range |<line2>|
@@ -918,20 +884,22 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin
/// - mods: (string) Command modifiers, if any |<mods>|
/// - smods: (table) Command modifiers in a structured format. Has the same
/// structure as the "mods" key of |nvim_parse_cmd()|.
-/// @param opts Optional command attributes. See |command-attributes| for more details. To use
-/// boolean attributes (such as |:command-bang| or |:command-bar|) set the value to
-/// "true". In addition to the string options listed in |:command-complete|, the
-/// "complete" key also accepts a Lua function which works like the "customlist"
-/// completion mode |:command-completion-customlist|. Additional parameters:
-/// - desc: (string) Used for listing the command when a Lua function is used for
-/// {command}.
-/// - force: (boolean, default true) Override any previous definition.
-/// - preview: (function) Preview callback for 'inccommand' |:command-preview|
+/// @param opts Optional |command-attributes|.
+/// - Set boolean attributes such as |:command-bang| or |:command-bar| to true (but
+/// not |:command-buffer|, use |nvim_buf_create_user_command()| instead).
+/// - "complete" |:command-complete| also accepts a Lua function which works like
+/// |:command-completion-customlist|.
+/// - Other parameters:
+/// - desc: (string) Used for listing the command when a Lua function is used for
+/// {command}.
+/// - force: (boolean, default true) Override any previous definition.
+/// - preview: (function) Preview callback for 'inccommand' |:command-preview|
/// @param[out] err Error details, if any.
-void nvim_create_user_command(String name, Object command, Dict(user_command) *opts, Error *err)
+void nvim_create_user_command(uint64_t channel_id, String name, Object command,
+ Dict(user_command) *opts, Error *err)
FUNC_API_SINCE(9)
{
- create_user_command(name, command, opts, 0, err);
+ create_user_command(channel_id, name, command, opts, 0, err);
}
/// Delete a user-defined command.
@@ -944,12 +912,12 @@ void nvim_del_user_command(String name, Error *err)
nvim_buf_del_user_command(-1, name, err);
}
-/// Create a new user command |user-commands| in the given buffer.
+/// Creates a buffer-local command |user-commands|.
///
/// @param buffer Buffer handle, or 0 for current buffer.
/// @param[out] err Error details, if any.
/// @see nvim_create_user_command
-void nvim_buf_create_user_command(Buffer buffer, String name, Object command,
+void nvim_buf_create_user_command(uint64_t channel_id, Buffer buffer, String name, Object command,
Dict(user_command) *opts, Error *err)
FUNC_API_SINCE(9)
{
@@ -960,7 +928,7 @@ void nvim_buf_create_user_command(Buffer buffer, String name, Object command,
buf_T *save_curbuf = curbuf;
curbuf = target_buf;
- create_user_command(name, command, opts, UC_BUFFER, err);
+ create_user_command(channel_id, name, command, opts, UC_BUFFER, err);
curbuf = save_curbuf;
}
@@ -998,36 +966,33 @@ 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,
- Error *err)
+void create_user_command(uint64_t channel_id, String name, Object command, Dict(user_command) *opts,
+ int flags, Error *err)
{
uint32_t argt = 0;
- long def = -1;
+ int64_t def = -1;
cmd_addr_T addr_type_arg = ADDR_NONE;
- int compl = EXPAND_NOTHING;
+ int context = EXPAND_NOTHING;
char *compl_arg = NULL;
const char *rep = NULL;
LuaRef luaref = LUA_NOREF;
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 start with uppercase)",
+ 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, user_command, range) || !HAS_KEY(opts, user_command, count)), "%s",
+ "Cannot use both 'range' and 'count'", {
goto err;
- }
+ });
if (opts->nargs.type == kObjectTypeInteger) {
switch (opts->nargs.data.integer) {
@@ -1038,14 +1003,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 +1023,20 @@ 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;
+ } else if (HAS_KEY(opts, user_command, nargs)) {
+ 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, user_command, complete) || argt),
+ "%s", "'complete' used without 'nargs'", {
goto err;
- }
+ });
if (opts->range.type == kObjectTypeBoolean) {
if (opts->range.data.boolean) {
@@ -1077,20 +1044,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;
+ } else if (HAS_KEY(opts, user_command, range)) {
+ VALIDATE_S(false, "range", "", {
+ goto err;
+ });
}
if (opts->count.type == kObjectTypeBoolean) {
@@ -1103,76 +1070,72 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
argt |= EX_COUNT | EX_ZEROR | EX_RANGE;
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;
+ } else if (HAS_KEY(opts, user_command, count)) {
+ 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, user_command, 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)) {
+ if (opts->bang) {
argt |= EX_BANG;
- } else if (ERROR_SET(err)) {
- goto err;
}
- if (api_object_to_bool(opts->bar, "bar", false, err)) {
+ if (opts->bar) {
argt |= EX_TRLBAR;
- } else if (ERROR_SET(err)) {
- goto err;
}
- if (api_object_to_bool(opts->register_, "register", false, err)) {
+ if (opts->register_) {
argt |= EX_REGSTR;
- } else if (ERROR_SET(err)) {
- goto err;
}
- if (api_object_to_bool(opts->keepscript, "keepscript", false, err)) {
+ if (opts->keepscript) {
argt |= EX_KEEPSCRIPT;
- } else if (ERROR_SET(err)) {
- goto err;
}
- bool force = api_object_to_bool(opts->force, "force", true, err);
+ bool force = GET_BOOL_OR_TRUE(opts, user_command, force);
if (ERROR_SET(err)) {
goto err;
}
if (opts->complete.type == kObjectTypeLuaRef) {
- compl = EXPAND_USER_LUA;
+ context = 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, &context, &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;
+ });
+ } else if (HAS_KEY(opts, user_command, complete)) {
+ VALIDATE_EXP(false, "complete", "Function or String", NULL, {
+ goto err;
+ });
}
- if (opts->preview.type == kObjectTypeLuaRef) {
+ if (HAS_KEY(opts, user_command, 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,15 +1151,18 @@ 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_EXP(false, "command", "Function or String", NULL, {
+ goto err;
+ });
}
- if (uc_add_command(name.data, name.size, rep, argt, def, flags, compl, compl_arg, compl_luaref,
- preview_luaref, addr_type_arg, luaref, force) != OK) {
- api_set_error(err, kErrorTypeException, "Failed to create user command");
- // Do not goto err, since uc_add_command now owns luaref, compl_luaref, and compl_arg
- }
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ if (uc_add_command(name.data, name.size, rep, argt, def, flags, context, compl_arg,
+ compl_luaref, preview_luaref, addr_type_arg, luaref, force) != OK) {
+ api_set_error(err, kErrorTypeException, "Failed to create user command");
+ // Do not goto err, since uc_add_command now owns luaref, compl_luaref, and compl_arg
+ }
+ });
return;
@@ -1209,6 +1175,8 @@ err:
///
/// Currently only |user-commands| are supported, not builtin Ex commands.
///
+/// @see |nvim_get_all_options_info()|
+///
/// @param opts Optional parameters. Currently only supports
/// {"builtin":false}
/// @param[out] err Error details, if any.
@@ -1231,13 +1199,12 @@ Dictionary nvim_buf_get_commands(Buffer buffer, Dict(get_commands) *opts, Error
FUNC_API_SINCE(4)
{
bool global = (buffer == -1);
- bool builtin = api_object_to_bool(opts->builtin, "builtin", false, err);
if (ERROR_SET(err)) {
return (Dictionary)ARRAY_DICT_INIT;
}
if (global) {
- if (builtin) {
+ if (opts->builtin) {
api_set_error(err, kErrorTypeValidation, "builtin=true not implemented");
return (Dictionary)ARRAY_DICT_INIT;
}
@@ -1245,7 +1212,7 @@ Dictionary nvim_buf_get_commands(Buffer buffer, Dict(get_commands) *opts, Error
}
buf_T *buf = find_buffer_by_handle(buffer, err);
- if (builtin || !buf) {
+ if (opts->builtin || !buf) {
return (Dictionary)ARRAY_DICT_INIT;
}
return commands_array(buf);
diff --git a/src/nvim/api/command.h b/src/nvim/api/command.h
index b1c9230551..1cccbfb4c7 100644
--- a/src/nvim/api/command.h
+++ b/src/nvim/api/command.h
@@ -1,11 +1,10 @@
-#ifndef NVIM_API_COMMAND_H
-#define NVIM_API_COMMAND_H
+#pragma once
-#include "nvim/api/private/defs.h"
-#include "nvim/decoration.h"
-#include "nvim/ex_cmds.h"
+#include <stdint.h> // IWYU pragma: keep
+
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/command.h.generated.h"
#endif
-#endif // NVIM_API_COMMAND_H
diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c
index 332e2b5fc3..2ec11236d7 100644
--- a/src/nvim/api/deprecated.c
+++ b/src/nvim/api/deprecated.c
@@ -1,36 +1,50 @@
-// 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 <stdbool.h>
#include <stdint.h>
-#include <stdlib.h>
+#include <string.h>
#include "nvim/api/buffer.h"
#include "nvim/api/deprecated.h"
#include "nvim/api/extmark.h"
+#include "nvim/api/keysets_defs.h"
+#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/api/vimscript.h"
#include "nvim/buffer_defs.h"
#include "nvim/decoration.h"
#include "nvim/extmark.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
+#include "nvim/highlight.h"
+#include "nvim/highlight_group.h"
#include "nvim/lua/executor.h"
#include "nvim/memory.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/option.h"
+#include "nvim/pos_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/deprecated.c.generated.h"
#endif
+/// @deprecated Use nvim_exec2() instead.
+/// @see nvim_exec2
+String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err)
+ FUNC_API_SINCE(7)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ Dict(exec_opts) opts = { .output = output };
+ return exec_impl(channel_id, src, &opts, err);
+}
+
/// @deprecated
-/// @see nvim_exec
+/// @see nvim_exec2
String nvim_command_output(uint64_t channel_id, String command, Error *err)
FUNC_API_SINCE(1)
FUNC_API_DEPRECATED_SINCE(7)
{
- return nvim_exec(channel_id, command, true, err);
+ Dict(exec_opts) opts = { .output = true };
+ return exec_impl(channel_id, command, &opts, err);
}
/// @deprecated Use nvim_exec_lua() instead.
@@ -140,25 +154,71 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A
return 0;
}
- Decoration *existing = decor_find_virttext(buf, (int)line, ns_id);
+ DecorVirtText *existing = decor_find_virttext(buf, (int)line, ns_id);
if (existing) {
- clear_virttext(&existing->virt_text);
- existing->virt_text = virt_text;
- existing->virt_text_width = width;
+ clear_virttext(&existing->data.virt_text);
+ existing->data.virt_text = virt_text;
+ existing->width = width;
return src_id;
}
- Decoration decor = DECORATION_INIT;
- decor.virt_text = virt_text;
- decor.virt_text_width = width;
- decor.priority = 0;
+ DecorVirtText *vt = xmalloc(sizeof *vt);
+ *vt = (DecorVirtText)DECOR_VIRT_TEXT_INIT;
+ vt->data.virt_text = virt_text;
+ vt->width = width;
+ vt->priority = 0;
- extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, &decor, true,
- false, kExtmarkNoUndo);
+ DecorInline decor = { .ext = true, .data.ext.vt = vt, .data.ext.sh_idx = DECOR_ID_INVALID };
+
+ extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, decor, 0, true,
+ false, false, false, NULL);
return src_id;
}
+/// Gets a highlight definition by id. |hlID()|
+///
+/// @deprecated use |nvim_get_hl()| instead
+///
+/// @param hl_id Highlight id as returned by |hlID()|
+/// @param rgb Export RGB colors
+/// @param[out] err Error details, if any
+/// @return Highlight definition map
+/// @see nvim_get_hl_by_name
+Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Arena *arena, Error *err)
+ FUNC_API_SINCE(3)
+ FUNC_API_DEPRECATED_SINCE(9)
+{
+ Dictionary dic = ARRAY_DICT_INIT;
+ 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);
+}
+
+/// Gets a highlight definition by name.
+///
+/// @deprecated use |nvim_get_hl()| instead
+///
+/// @param name Highlight group name
+/// @param rgb Export RGB colors
+/// @param[out] err Error details, if any
+/// @return Highlight definition map
+/// @see nvim_get_hl_by_id
+Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Arena *arena, Error *err)
+ FUNC_API_SINCE(3)
+ FUNC_API_DEPRECATED_SINCE(9)
+{
+ Dictionary result = ARRAY_DICT_INIT;
+ int id = syn_name2id(name.data);
+
+ VALIDATE_S((id != 0), "highlight name", name.data, {
+ return result;
+ });
+ return nvim_get_hl_by_id(id, rgb, arena, err);
+}
+
/// Inserts a sequence of lines to a buffer at a certain index
///
/// @deprecated use nvim_buf_set_lines(buffer, lnum, lnum, true, lines)
@@ -449,3 +509,195 @@ static int64_t convert_index(int64_t index)
{
return index < 0 ? index - 1 : index;
}
+
+/// Gets the option information for one option
+///
+/// @deprecated Use @ref nvim_get_option_info2 instead.
+///
+/// @param name Option name
+/// @param[out] err Error details, if any
+/// @return Option Information
+Dictionary nvim_get_option_info(String name, Error *err)
+ FUNC_API_SINCE(7)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ return get_vimoption(name, OPT_GLOBAL, curbuf, curwin, err);
+}
+
+/// Sets the global value of an option.
+///
+/// @deprecated
+/// @param channel_id
+/// @param name Option name
+/// @param value New option value
+/// @param[out] err Error details, if any
+void nvim_set_option(uint64_t channel_id, String name, Object value, Error *err)
+ FUNC_API_SINCE(1)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ set_option_to(channel_id, NULL, kOptReqGlobal, name, value, err);
+}
+
+/// Gets the global value of an option.
+///
+/// @deprecated
+/// @param name Option name
+/// @param[out] err Error details, if any
+/// @return Option value (global)
+Object nvim_get_option(String name, Arena *arena, Error *err)
+ FUNC_API_SINCE(1)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ return get_option_from(NULL, kOptReqGlobal, name, err);
+}
+
+/// Gets a buffer option value
+///
+/// @deprecated
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param name Option name
+/// @param[out] err Error details, if any
+/// @return Option value
+Object nvim_buf_get_option(Buffer buffer, String name, Arena *arena, Error *err)
+ FUNC_API_SINCE(1)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return (Object)OBJECT_INIT;
+ }
+
+ return get_option_from(buf, kOptReqBuf, name, err);
+}
+
+/// Sets a buffer option value. Passing `nil` as value deletes the option (only
+/// works if there's a global fallback)
+///
+/// @deprecated
+/// @param channel_id
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param name Option name
+/// @param value Option value
+/// @param[out] err Error details, if any
+void nvim_buf_set_option(uint64_t channel_id, Buffer buffer, String name, Object value, Error *err)
+ FUNC_API_SINCE(1)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return;
+ }
+
+ set_option_to(channel_id, buf, kOptReqBuf, name, value, err);
+}
+
+/// Gets a window option value
+///
+/// @deprecated
+/// @param window Window handle, or 0 for current window
+/// @param name Option name
+/// @param[out] err Error details, if any
+/// @return Option value
+Object nvim_win_get_option(Window window, String name, Arena *arena, Error *err)
+ FUNC_API_SINCE(1)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ win_T *win = find_window_by_handle(window, err);
+
+ if (!win) {
+ return (Object)OBJECT_INIT;
+ }
+
+ return get_option_from(win, kOptReqWin, name, err);
+}
+
+/// Sets a window option value. Passing `nil` as value deletes the option (only
+/// works if there's a global fallback)
+///
+/// @deprecated
+/// @param channel_id
+/// @param window Window handle, or 0 for current window
+/// @param name Option name
+/// @param value Option value
+/// @param[out] err Error details, if any
+void nvim_win_set_option(uint64_t channel_id, Window window, String name, Object value, Error *err)
+ FUNC_API_SINCE(1)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ win_T *win = find_window_by_handle(window, err);
+
+ if (!win) {
+ return;
+ }
+
+ set_option_to(channel_id, win, kOptReqWin, name, value, err);
+}
+
+/// Gets the value of a global or local (buffer, window) option.
+///
+/// @param[in] from Pointer to buffer or window for local option value.
+/// @param req_scope Requested option scope. See OptReqScope in option.h.
+/// @param name The option name.
+/// @param[out] err Details of an error that may have occurred.
+///
+/// @return the option value.
+static Object get_option_from(void *from, OptReqScope req_scope, String name, Error *err)
+{
+ VALIDATE_S(name.size > 0, "option name", "<empty>", {
+ return (Object)OBJECT_INIT;
+ });
+
+ OptVal value = get_option_value_strict(name.data, req_scope, from, err);
+ if (ERROR_SET(err)) {
+ return (Object)OBJECT_INIT;
+ }
+
+ VALIDATE_S(value.type != kOptValTypeNil, "option name", name.data, {
+ return (Object)OBJECT_INIT;
+ });
+
+ return optval_as_object(value);
+}
+
+/// Sets the value of a global or local (buffer, window) option.
+///
+/// @param[in] to Pointer to buffer or window for local option value.
+/// @param req_scope Requested option scope. See OptReqScope in option.h.
+/// @param name The option name.
+/// @param value New option value.
+/// @param[out] err Details of an error that may have occurred.
+static void set_option_to(uint64_t channel_id, void *to, OptReqScope req_scope, String name,
+ Object value, Error *err)
+{
+ VALIDATE_S(name.size > 0, "option name", "<empty>", {
+ return;
+ });
+
+ int flags = get_option_attrs(name.data);
+ VALIDATE_S(flags != 0, "option name", name.data, {
+ return;
+ });
+
+ bool error = false;
+ OptVal optval = object_as_optval(value, &error);
+
+ // Handle invalid option value type.
+ // Don't use `name` in the error message here, because `name` can be any String.
+ // No need to check if value type actually matches the types for the option, as set_option_value()
+ // already handles that.
+ VALIDATE_EXP(!error, "value", "valid option type", api_typename(value.type), {
+ return;
+ });
+
+ // For global-win-local options -> setlocal
+ // For win-local options -> setglobal and setlocal (opt_flags == 0)
+ const int opt_flags = (req_scope == kOptReqWin && !(flags & SOPT_GLOBAL))
+ ? 0
+ : (req_scope == kOptReqGlobal) ? OPT_GLOBAL : OPT_LOCAL;
+
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ set_option_value_for(name.data, optval, opt_flags, req_scope, to, err);
+ });
+}
diff --git a/src/nvim/api/deprecated.h b/src/nvim/api/deprecated.h
index 79095167e1..e20d8304e0 100644
--- a/src/nvim/api/deprecated.h
+++ b/src/nvim/api/deprecated.h
@@ -1,11 +1,9 @@
-#ifndef NVIM_API_DEPRECATED_H
-#define NVIM_API_DEPRECATED_H
+#pragma once
-#include <stdint.h>
+#include <stdint.h> // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/deprecated.h.generated.h"
#endif
-#endif // NVIM_API_DEPRECATED_H
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index ab3b3485e4..d71498d6ed 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -1,29 +1,31 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
+#include <lauxlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "klib/kvec.h"
-#include "lauxlib.h"
#include "nvim/api/extmark.h"
+#include "nvim/api/keysets_defs.h"
#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/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/decoration.h"
#include "nvim/decoration_provider.h"
#include "nvim/drawscreen.h"
#include "nvim/extmark.h"
+#include "nvim/func_attr.h"
+#include "nvim/grid.h"
#include "nvim/highlight_group.h"
+#include "nvim/marktree.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
-#include "nvim/pos.h"
-#include "nvim/strings.h"
-#include "nvim/vim.h"
+#include "nvim/pos_defs.h"
+#include "nvim/sign.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/extmark.c.generated.h"
@@ -32,12 +34,10 @@
void api_extmark_free_all_mem(void)
{
String name;
- handle_T id;
- map_foreach(&namespace_ids, name, id, {
- (void)id;
+ map_foreach_key(&namespace_ids, name, {
xfree(name.data);
})
- map_destroy(String, handle_T)(&namespace_ids);
+ map_destroy(String, &namespace_ids);
}
/// Creates a new namespace or gets an existing one. \*namespace\*
@@ -54,14 +54,14 @@ void api_extmark_free_all_mem(void)
Integer nvim_create_namespace(String name)
FUNC_API_SINCE(5)
{
- handle_T id = map_get(String, handle_T)(&namespace_ids, name);
+ handle_T id = map_get(String, int)(&namespace_ids, name);
if (id > 0) {
return id;
}
id = next_namespace_id++;
if (name.size > 0) {
String name_alloc = copy_string(name, NULL);
- map_put(String, handle_T)(&namespace_ids, name_alloc, id);
+ map_put(String, int)(&namespace_ids, name_alloc, id);
}
return (Integer)id;
}
@@ -83,7 +83,7 @@ Dictionary nvim_get_namespaces(void)
return retval;
}
-const char *describe_ns(NS ns_id)
+const char *describe_ns(NS ns_id, const char *unknown)
{
String name;
handle_T id;
@@ -92,7 +92,7 @@ const char *describe_ns(NS ns_id)
return name.data;
}
})
- return "(UNKNOWN PLUGIN)";
+ return unknown;
}
// Is the Namespace in use?
@@ -104,92 +104,73 @@ bool ns_initialized(uint32_t ns)
return ns < (uint32_t)next_namespace_id;
}
-static Array extmark_to_array(const ExtmarkInfo *extmark, bool id, bool add_dict)
+Array virt_text_to_array(VirtText vt, bool hl_name)
{
+ Array chunks = ARRAY_DICT_INIT;
+ Array hl_array = ARRAY_DICT_INIT;
+ for (size_t i = 0; i < kv_size(vt); i++) {
+ char *text = kv_A(vt, i).text;
+ int hl_id = kv_A(vt, i).hl_id;
+ if (text == NULL) {
+ if (hl_id > 0) {
+ ADD(hl_array, hl_group_name(hl_id, hl_name));
+ }
+ continue;
+ }
+ Array chunk = ARRAY_DICT_INIT;
+ ADD(chunk, CSTR_TO_OBJ(text));
+ if (hl_array.size > 0) {
+ if (hl_id > 0) {
+ ADD(hl_array, hl_group_name(hl_id, hl_name));
+ }
+ ADD(chunk, ARRAY_OBJ(hl_array));
+ hl_array = (Array)ARRAY_DICT_INIT;
+ } else if (hl_id > 0) {
+ ADD(chunk, hl_group_name(hl_id, hl_name));
+ }
+ ADD(chunks, ARRAY_OBJ(chunk));
+ }
+ assert(hl_array.size == 0);
+ return chunks;
+}
+
+static Array extmark_to_array(MTPair extmark, bool id, bool add_dict, bool hl_name)
+{
+ MTKey start = extmark.start;
Array rv = ARRAY_DICT_INIT;
if (id) {
- ADD(rv, INTEGER_OBJ((Integer)extmark->mark_id));
+ ADD(rv, INTEGER_OBJ((Integer)start.id));
}
- ADD(rv, INTEGER_OBJ(extmark->row));
- ADD(rv, INTEGER_OBJ(extmark->col));
+ ADD(rv, INTEGER_OBJ(start.pos.row));
+ ADD(rv, INTEGER_OBJ(start.pos.col));
if (add_dict) {
Dictionary dict = ARRAY_DICT_INIT;
- PUT(dict, "right_gravity", BOOLEAN_OBJ(extmark->right_gravity));
+ PUT(dict, "ns_id", INTEGER_OBJ((Integer)start.ns));
- if (extmark->end_row >= 0) {
- PUT(dict, "end_row", INTEGER_OBJ(extmark->end_row));
- PUT(dict, "end_col", INTEGER_OBJ(extmark->end_col));
- PUT(dict, "end_right_gravity", BOOLEAN_OBJ(extmark->end_right_gravity));
- }
+ PUT(dict, "right_gravity", BOOLEAN_OBJ(mt_right(start)));
- const Decoration *decor = &extmark->decor;
- if (decor->hl_id) {
- String name = cstr_to_string((const char *)syn_id2name(decor->hl_id));
- PUT(dict, "hl_group", STRING_OBJ(name));
- PUT(dict, "hl_eol", BOOLEAN_OBJ(decor->hl_eol));
- }
- if (decor->hl_mode) {
- PUT(dict, "hl_mode", STRING_OBJ(cstr_to_string(hl_mode_str[decor->hl_mode])));
+ if (extmark.end_pos.row >= 0) {
+ PUT(dict, "end_row", INTEGER_OBJ(extmark.end_pos.row));
+ PUT(dict, "end_col", INTEGER_OBJ(extmark.end_pos.col));
+ PUT(dict, "end_right_gravity", BOOLEAN_OBJ(extmark.end_right_gravity));
}
- if (kv_size(decor->virt_text)) {
- Array chunks = ARRAY_DICT_INIT;
- for (size_t i = 0; i < decor->virt_text.size; i++) {
- Array chunk = ARRAY_DICT_INIT;
- VirtTextChunk *vtc = &decor->virt_text.items[i];
- ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text)));
- if (vtc->hl_id > 0) {
- ADD(chunk,
- STRING_OBJ(cstr_to_string((const char *)syn_id2name(vtc->hl_id))));
- }
- ADD(chunks, ARRAY_OBJ(chunk));
- }
- PUT(dict, "virt_text", ARRAY_OBJ(chunks));
- PUT(dict, "virt_text_hide", BOOLEAN_OBJ(decor->virt_text_hide));
- if (decor->virt_text_pos == kVTWinCol) {
- PUT(dict, "virt_text_win_col", INTEGER_OBJ(decor->col));
- }
- PUT(dict, "virt_text_pos",
- STRING_OBJ(cstr_to_string(virt_text_pos_str[decor->virt_text_pos])));
+ if (mt_no_undo(start)) {
+ PUT(dict, "undo_restore", BOOLEAN_OBJ(false));
}
- if (decor->ui_watched) {
- PUT(dict, "ui_watched", BOOLEAN_OBJ(true));
+ if (mt_invalidate(start)) {
+ PUT(dict, "invalidate", BOOLEAN_OBJ(true));
}
-
- if (kv_size(decor->virt_lines)) {
- Array all_chunks = ARRAY_DICT_INIT;
- bool virt_lines_leftcol = false;
- for (size_t i = 0; i < decor->virt_lines.size; i++) {
- Array chunks = ARRAY_DICT_INIT;
- VirtText *vt = &decor->virt_lines.items[i].line;
- virt_lines_leftcol = decor->virt_lines.items[i].left_col;
- for (size_t j = 0; j < vt->size; j++) {
- Array chunk = ARRAY_DICT_INIT;
- VirtTextChunk *vtc = &vt->items[j];
- ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text)));
- if (vtc->hl_id > 0) {
- ADD(chunk,
- STRING_OBJ(cstr_to_string((const char *)syn_id2name(vtc->hl_id))));
- }
- ADD(chunks, ARRAY_OBJ(chunk));
- }
- ADD(all_chunks, ARRAY_OBJ(chunks));
- }
- PUT(dict, "virt_lines", ARRAY_OBJ(all_chunks));
- PUT(dict, "virt_lines_above", BOOLEAN_OBJ(decor->virt_lines_above));
- PUT(dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_leftcol));
+ if (mt_invalid(start)) {
+ PUT(dict, "invalid", BOOLEAN_OBJ(true));
}
- if (decor->hl_id || kv_size(decor->virt_text) || decor->ui_watched) {
- PUT(dict, "priority", INTEGER_OBJ(decor->priority));
- }
+ decor_to_dict_legacy(&dict, mt_decor(start), hl_name);
- if (dict.size) {
- ADD(rv, DICTIONARY_OBJ(dict));
- }
+ ADD(rv, DICTIONARY_OBJ(dict));
}
return rv;
@@ -202,6 +183,7 @@ static Array extmark_to_array(const ExtmarkInfo *extmark, bool id, bool add_dict
/// @param id Extmark id
/// @param opts Optional parameters. Keys:
/// - details: Whether to include the details dict
+/// - hl_name: Whether to include highlight group name instead of id, true if omitted
/// @param[out] err Error details, if any
/// @return 0-indexed (row, col) tuple or empty list () if extmark id was
/// absent
@@ -218,80 +200,92 @@ 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;
+ bool hl_name = true;
for (size_t i = 0; i < opts.size; i++) {
String k = opts.items[i].key;
Object *v = &opts.items[i].value;
if (strequal("details", k.data)) {
- if (v->type == kObjectTypeBoolean) {
- details = v->data.boolean;
- } else if (v->type == kObjectTypeInteger) {
- details = v->data.integer;
- } else {
- api_set_error(err, kErrorTypeValidation, "details is not an boolean");
+ details = api_object_to_bool(*v, "details", false, err);
+ if (ERROR_SET(err)) {
+ return rv;
+ }
+ } else if (strequal("hl_name", k.data)) {
+ hl_name = api_object_to_bool(*v, "hl_name", false, err);
+ if (ERROR_SET(err)) {
return rv;
}
} else {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
- return rv;
+ VALIDATE_S(false, "'opts' key", k.data, {
+ return rv;
+ });
}
}
- ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id);
- if (extmark.row < 0) {
+ MTPair extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id);
+ if (extmark.start.pos.row < 0) {
return rv;
}
- return extmark_to_array(&extmark, false, details);
+ return extmark_to_array(extmark, false, details, hl_name);
}
-/// Gets |extmarks| in "traversal order" from a |charwise| region defined by
-/// buffer positions (inclusive, 0-indexed |api-indexing|).
+/// Gets |extmarks| (including |signs|) in "traversal order" from a |charwise|
+/// region defined by buffer positions (inclusive, 0-indexed |api-indexing|).
///
/// Region can be given as (row,col) tuples, or valid extmark ids (whose
/// positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1)
/// respectively, thus the following are equivalent:
-/// <pre>lua
-/// vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {})
-/// vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {})
-/// </pre>
+///
+/// ```lua
+/// vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {})
+/// vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {})
+/// ```
///
/// If `end` is less than `start`, traversal works backwards. (Useful
/// with `limit`, to get the first marks prior to a given position.)
///
+/// Note: when using extmark ranges (marks with a end_row/end_col position)
+/// the `overlap` option might be useful. Otherwise only the start position
+/// of an extmark will be considered.
+///
/// Example:
-/// <pre>lua
-/// local a = vim.api
-/// local pos = a.nvim_win_get_cursor(0)
-/// local ns = a.nvim_create_namespace('my-plugin')
-/// -- Create new extmark at line 1, column 1.
-/// local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, {})
-/// -- Create new extmark at line 3, column 1.
-/// local m2 = a.nvim_buf_set_extmark(0, ns, 2, 0, {})
-/// -- Get extmarks only from line 3.
-/// local ms = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {})
-/// -- Get all marks in this buffer + namespace.
-/// local all = a.nvim_buf_get_extmarks(0, ns, 0, -1, {})
-/// print(vim.inspect(ms))
-/// </pre>
+///
+/// ```lua
+/// local api = vim.api
+/// local pos = api.nvim_win_get_cursor(0)
+/// local ns = api.nvim_create_namespace('my-plugin')
+/// -- Create new extmark at line 1, column 1.
+/// local m1 = api.nvim_buf_set_extmark(0, ns, 0, 0, {})
+/// -- Create new extmark at line 3, column 1.
+/// local m2 = api.nvim_buf_set_extmark(0, ns, 2, 0, {})
+/// -- Get extmarks only from line 3.
+/// local ms = api.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {})
+/// -- Get all marks in this buffer + namespace.
+/// local all = api.nvim_buf_get_extmarks(0, ns, 0, -1, {})
+/// vim.print(ms)
+/// ```
///
/// @param buffer Buffer handle, or 0 for current buffer
-/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param ns_id Namespace id from |nvim_create_namespace()| or -1 for all namespaces
/// @param start Start of range: a 0-indexed (row, col) or valid extmark id
/// (whose position defines the bound). |api-indexing|
/// @param end End of range (inclusive): a 0-indexed (row, col) or valid
/// extmark id (whose position defines the bound). |api-indexing|
/// @param opts Optional parameters. Keys:
/// - limit: Maximum number of marks to return
-/// - details Whether to include the details dict
+/// - details: Whether to include the details dict
+/// - hl_name: Whether to include highlight group name instead of id, true if omitted
+/// - overlap: Also include marks which overlap the range, even if
+/// their start position is less than `start`
+/// - type: Filter marks by type: "highlight", "sign", "virt_text" and "virt_lines"
/// @param[out] err Error details, if any
/// @return List of [extmark_id, row, col] tuples in "traversal order".
-Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object end, Dictionary opts,
- Error *err)
+Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object end,
+ Dict(get_extmarks) *opts, Error *err)
FUNC_API_SINCE(7)
{
Array rv = ARRAY_DICT_INIT;
@@ -301,38 +295,32 @@ 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_id == -1 || ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
return rv;
- }
-
- Integer limit = -1;
- bool details = false;
-
- for (size_t i = 0; i < opts.size; i++) {
- 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");
- return rv;
- }
- limit = v->data.integer;
- } else if (strequal("details", k.data)) {
- if (v->type == kObjectTypeBoolean) {
- details = v->data.boolean;
- } else if (v->type == kObjectTypeInteger) {
- details = v->data.integer;
- } else {
- api_set_error(err, kErrorTypeValidation, "details is not an boolean");
- return rv;
- }
+ });
+
+ bool details = opts->details;
+ bool hl_name = GET_BOOL_OR_TRUE(opts, get_extmarks, hl_name);
+
+ ExtmarkType type = kExtmarkNone;
+ if (HAS_KEY(opts, get_extmarks, type)) {
+ if (strequal(opts->type.data, "sign")) {
+ type = kExtmarkSign;
+ } else if (strequal(opts->type.data, "virt_text")) {
+ type = kExtmarkVirtText;
+ } else if (strequal(opts->type.data, "virt_lines")) {
+ type = kExtmarkVirtLines;
+ } else if (strequal(opts->type.data, "highlight")) {
+ type = kExtmarkHighlight;
} else {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
- return rv;
+ VALIDATE_EXP(false, "type", "sign, virt_text, virt_lines or highlight", opts->type.data, {
+ return rv;
+ });
}
}
+ Integer limit = HAS_KEY(opts, get_extmarks, limit) ? opts->limit : -1;
+
if (limit == 0) {
return rv;
} else if (limit < 0) {
@@ -357,11 +345,12 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
reverse = true;
}
- ExtmarkInfoArray marks = extmark_get(buf, (uint32_t)ns_id, l_row, l_col,
- u_row, u_col, (int64_t)limit, reverse);
+ // note: ns_id=-1 allowed, represented as UINT32_MAX
+ ExtmarkInfoArray marks = extmark_get(buf, (uint32_t)ns_id, l_row, l_col, u_row,
+ u_col, (int64_t)limit, reverse, type, opts->overlap);
for (size_t i = 0; i < kv_size(marks); i++) {
- ADD(rv, ARRAY_OBJ(extmark_to_array(&kv_A(marks, i), true, (bool)details)));
+ ADD(rv, ARRAY_OBJ(extmark_to_array(kv_A(marks, i), true, details, hl_name)));
}
kv_destroy(marks);
@@ -379,6 +368,11 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// Using the optional arguments, it is possible to use this to highlight
/// a range of text, and also to associate virtual text to the mark.
///
+/// If present, the position defined by `end_col` and `end_row` should be after
+/// the start position in order for the extmark to cover a range.
+/// An earlier end position is not an error, but then it behaves like an empty
+/// range (no highlighting).
+///
/// @param buffer Buffer handle, or 0 for current buffer
/// @param ns_id Namespace id from |nvim_create_namespace()|
/// @param line Line where to place the mark, 0-based. |api-indexing|
@@ -402,24 +396,28 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// either as a string or as an integer, the latter which
/// can be obtained using |nvim_get_hl_id_by_name()|.
/// - virt_text_pos : position of virtual text. Possible values:
-/// - "eol": right after eol character (default)
+/// - "eol": right after eol character (default).
/// - "overlay": display over the specified column, without
/// shifting the underlying text.
/// - "right_align": display right aligned in the window.
+/// - "inline": display at the specified column, and
+/// shift the buffer text to the right as needed.
/// - virt_text_win_col : position the virtual text at a fixed
/// window column (starting from the first
-/// text column)
+/// text column of the screen line) instead
+/// of "virt_text_pos".
/// - virt_text_hide : hide the virtual text when the background
-/// text is selected or hidden due to
-/// horizontal scroll 'nowrap'
+/// text is selected or hidden because of
+/// scrolling with 'nowrap' or 'smoothscroll'.
+/// Currently only affects "overlay" virt_text.
/// - hl_mode : control how highlights are combined with the
/// highlights of the text. Currently only affects
/// virt_text highlights, but might affect `hl_group`
/// in later versions.
-/// - "replace": only show the virt_text color. This is the
-/// default
-/// - "combine": combine with background text color
+/// - "replace": only show the virt_text color. This is the default.
+/// - "combine": combine with background text color.
/// - "blend": blend with background text color.
+/// Not supported for "inline" virt_text.
///
/// - virt_lines : virtual lines to add next to this mark
/// This should be an array over lines, where each line in
@@ -443,11 +441,17 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// buffer.
/// - right_gravity : boolean that indicates the direction
/// the extmark will be shifted in when new text is inserted
-/// (true for right, false for left). defaults to true.
+/// (true for right, false for left). Defaults to true.
/// - end_right_gravity : boolean that indicates the direction
/// the extmark end position (if it exists) will be shifted
/// in when new text is inserted (true for right, false
/// for left). Defaults to false.
+/// - undo_restore : Restore the exact position of the mark
+/// if text around the mark was deleted and then restored by undo.
+/// Defaults to true.
+/// - invalidate : boolean that indicates whether to hide the
+/// extmark if the entirety of its range is deleted. If
+/// "undo_restore" is false, the extmark is deleted instead.
/// - priority: a priority value for the highlight group or sign
/// attribute. For example treesitter highlighting uses a
/// value of 100.
@@ -493,286 +497,255 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
Dict(set_extmark) *opts, Error *err)
FUNC_API_SINCE(7)
{
- Decoration decor = DECORATION_INIT;
- bool has_decor = false;
+ DecorHighlightInline hl = DECOR_HIGHLIGHT_INLINE_INIT;
+ // TODO(bfredl): in principle signs with max one (1) hl group and max 4 bytes of text.
+ // should be a candidate for inlining as well.
+ DecorSignHighlight sign = DECOR_SIGN_HIGHLIGHT_INIT;
+ DecorVirtText virt_text = DECOR_VIRT_TEXT_INIT;
+ DecorVirtText virt_lines = DECOR_VIRT_LINES_INIT;
+ bool has_hl = false;
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
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;
+ if (HAS_KEY(opts, set_extmark, id)) {
+ VALIDATE_EXP((opts->id > 0), "id", "positive Integer", NULL, {
+ goto error;
+ });
+
+ id = (uint32_t)opts->id;
}
int line2 = -1;
+ bool did_end_line = false;
// 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");
+ if (HAS_KEY(opts, set_extmark, end_line)) {
+ VALIDATE(!HAS_KEY(opts, set_extmark, end_row),
+ "%s", "cannot use both 'end_row' and 'end_line'", {
goto error;
- }
- opts->end_row = opts->end_line;
- }
+ });
-#define OPTION_TO_BOOL(target, name, val) \
- target = api_object_to_bool(opts->name, #name, val, err); \
- if (ERROR_SET(err)) { \
- goto error; \
+ opts->end_row = opts->end_line;
+ did_end_line = true;
}
- bool strict = true;
- OPTION_TO_BOOL(strict, strict, true);
+ bool strict = GET_BOOL_OR_TRUE(opts, set_extmark, strict);
- if (opts->end_row.type == kObjectTypeInteger) {
- 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");
+ if (HAS_KEY(opts, set_extmark, end_row) || did_end_line) {
+ Integer val = opts->end_row;
+ 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) {
- Integer val = opts->end_col.data.integer;
- if (val < 0 || val > MAXCOL) {
- api_set_error(err, kErrorTypeValidation, "end_col value outside range");
+ if (HAS_KEY(opts, set_extmark, end_col)) {
+ Integer val = opts->end_col;
+ 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
+ // TODO(bfredl): keyset type alias for hl_group? (nil|int|string)
struct {
const char *name;
Object *opt;
int *dest;
} hls[] = {
- { "hl_group" , &opts->hl_group , &decor.hl_id },
- { "sign_hl_group" , &opts->sign_hl_group , &decor.sign_hl_id },
- { "number_hl_group" , &opts->number_hl_group , &decor.number_hl_id },
- { "line_hl_group" , &opts->line_hl_group , &decor.line_hl_id },
- { "cursorline_hl_group", &opts->cursorline_hl_group, &decor.cursorline_hl_id },
+ { "hl_group" , &opts->hl_group , &hl.hl_id },
+ { "sign_hl_group" , &opts->sign_hl_group , &sign.hl_id },
+ { "number_hl_group" , &opts->number_hl_group , &sign.number_hl_id },
+ { "line_hl_group" , &opts->line_hl_group , &sign.line_hl_id },
+ { "cursorline_hl_group", &opts->cursorline_hl_group, &sign.cursorline_hl_id },
{ NULL, NULL, NULL },
};
// uncrustify:on
for (int j = 0; hls[j].name && hls[j].dest; j++) {
- if (HAS_KEY(*hls[j].opt)) {
+ if (hls[j].opt->type != kObjectTypeNil) {
+ if (j > 0) {
+ sign.flags |= kSHIsSign;
+ } else {
+ has_hl = true;
+ }
*hls[j].dest = object_to_hl_id(*hls[j].opt, hls[j].name, err);
if (ERROR_SET(err)) {
goto error;
}
- has_decor = true;
}
}
- if (opts->conceal.type == kObjectTypeString) {
- String c = opts->conceal.data.string;
- decor.conceal = true;
- if (c.size) {
- decor.conceal_char = utf_ptr2char(c.data);
+ if (HAS_KEY(opts, set_extmark, conceal)) {
+ hl.flags |= kSHConceal;
+ has_hl = true;
+ String c = opts->conceal;
+ if (c.size > 0) {
+ int ch;
+ hl.conceal_char = utfc_ptr2schar_len(c.data, (int)c.size, &ch);
+ if (!hl.conceal_char || !vim_isprintc(ch)) {
+ api_set_error(err, kErrorTypeValidation, "conceal char has to be printable");
+ goto error;
+ }
}
- 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) {
- decor.virt_text = parse_virt_text(opts->virt_text.data.array, err,
- &decor.virt_text_width);
- has_decor = true;
+ if (HAS_KEY(opts, set_extmark, virt_text)) {
+ virt_text.data.virt_text = parse_virt_text(opts->virt_text, err, &virt_text.width);
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) {
- String str = opts->virt_text_pos.data.string;
+ if (HAS_KEY(opts, set_extmark, virt_text_pos)) {
+ String str = opts->virt_text_pos;
if (strequal("eol", str.data)) {
- decor.virt_text_pos = kVTEndOfLine;
+ virt_text.pos = kVPosEndOfLine;
} else if (strequal("overlay", str.data)) {
- decor.virt_text_pos = kVTOverlay;
+ virt_text.pos = kVPosOverlay;
} else if (strequal("right_align", str.data)) {
- decor.virt_text_pos = kVTRightAlign;
+ virt_text.pos = kVPosRightAlign;
+ } else if (strequal("inline", str.data)) {
+ virt_text.pos = kVPosInline;
} else {
- api_set_error(err, kErrorTypeValidation, "virt_text_pos: invalid value");
- goto error;
+ VALIDATE_S(false, "virt_text_pos", str.data, {
+ 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) {
- 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;
+ if (HAS_KEY(opts, set_extmark, virt_text_win_col)) {
+ virt_text.col = (int)opts->virt_text_win_col;
+ virt_text.pos = kVPosWinCol;
}
- OPTION_TO_BOOL(decor.virt_text_hide, virt_text_hide, false);
- OPTION_TO_BOOL(decor.hl_eol, hl_eol, false);
+ hl.flags |= opts->hl_eol ? kSHHlEol : 0;
+ virt_text.flags |= opts->virt_text_hide ? kVTHide : 0;
- if (opts->hl_mode.type == kObjectTypeString) {
- String str = opts->hl_mode.data.string;
+ if (HAS_KEY(opts, set_extmark, hl_mode)) {
+ String str = opts->hl_mode;
if (strequal("replace", str.data)) {
- decor.hl_mode = kHlModeReplace;
+ virt_text.hl_mode = kHlModeReplace;
} else if (strequal("combine", str.data)) {
- decor.hl_mode = kHlModeCombine;
+ virt_text.hl_mode = kHlModeCombine;
} else if (strequal("blend", str.data)) {
- decor.hl_mode = kHlModeBlend;
+ if (virt_text.pos == kVPosInline) {
+ VALIDATE(false, "%s", "cannot use 'blend' hl_mode with inline virtual text", {
+ goto error;
+ });
+ }
+ virt_text.hl_mode = kHlModeBlend;
} else {
- api_set_error(err, kErrorTypeValidation,
- "virt_text_pos: invalid value");
- goto error;
+ VALIDATE_S(false, "hl_mode", str.data, {
+ goto error;
+ });
}
- } else if (HAS_KEY(opts->hl_mode)) {
- api_set_error(err, kErrorTypeValidation, "hl_mode is not a String");
- goto error;
}
- bool virt_lines_leftcol = false;
- OPTION_TO_BOOL(virt_lines_leftcol, virt_lines_leftcol, false);
+ bool virt_lines_leftcol = opts->virt_lines_leftcol;
- if (opts->virt_lines.type == kObjectTypeArray) {
- Array a = opts->virt_lines.data.array;
+ if (HAS_KEY(opts, set_extmark, virt_lines)) {
+ Array a = opts->virt_lines;
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 }));
+ kv_push(virt_lines.data.virt_lines, ((struct virt_line){ jtem, virt_lines_leftcol }));
if (ERROR_SET(err)) {
goto error;
}
- 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) {
- Integer val = opts->priority.data.integer;
+ virt_lines.flags |= opts->virt_lines_above ? kVTLinesAbove : 0;
- if (val < 0 || val > UINT16_MAX) {
- api_set_error(err, kErrorTypeValidation, "priority is not a valid value");
+ if (HAS_KEY(opts, set_extmark, priority)) {
+ VALIDATE_RANGE((opts->priority >= 0 && opts->priority <= 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;
+ });
+ hl.priority = (DecorPriority)opts->priority;
+ sign.priority = (DecorPriority)opts->priority;
+ virt_text.priority = (DecorPriority)opts->priority;
+ virt_lines.priority = (DecorPriority)opts->priority;
}
- 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, set_extmark, sign_text)) {
+ sign.text.ptr = NULL;
+ VALIDATE_S(init_sign_text(NULL, &sign.text.ptr, opts->sign_text.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;
+ });
+ sign.flags |= kSHIsSign;
}
- bool right_gravity = true;
- OPTION_TO_BOOL(right_gravity, right_gravity, true);
+ bool right_gravity = GET_BOOL_OR_TRUE(opts, set_extmark, right_gravity);
// 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, set_extmark, 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);
+ });
size_t len = 0;
- bool ephemeral = false;
- OPTION_TO_BOOL(ephemeral, ephemeral, false);
-
- if (opts->spell.type == kObjectTypeNil) {
- decor.spell = kNone;
- } else {
- bool spell = false;
- OPTION_TO_BOOL(spell, spell, false);
- decor.spell = spell ? kTrue : kFalse;
- has_decor = true;
+ if (HAS_KEY(opts, set_extmark, spell)) {
+ hl.flags |= (opts->spell) ? kSHSpellOn : kSHSpellOff;
+ has_hl = true;
}
- OPTION_TO_BOOL(decor.ui_watched, ui_watched, false);
- if (decor.ui_watched) {
- has_decor = true;
+ if (opts->ui_watched) {
+ hl.flags |= kSHUIWatched;
+ if (virt_text.pos == kVPosOverlay) {
+ // TODO(bfredl): in a revised interface this should be the default.
+ hl.flags |= kSHUIWatchedOverlay;
+ }
+ has_hl = 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));
+ len = opts->ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line + 1));
}
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) {
if (line2 >= 0 && line2 < buf->b_ml.ml_line_count) {
- len = ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line2 + 1, false));
+ len = opts->ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line2 + 1));
} else if (line2 == buf->b_ml.ml_line_count) {
// We are trying to add an extmark past final newline
len = 0;
@@ -781,36 +754,96 @@ 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;
}
- // TODO(bfredl): synergize these two branches even more
- if (ephemeral && decor_state.buf == buf) {
- decor_add_ephemeral((int)line, (int)col, line2, col2, &decor, (uint64_t)ns_id, id);
+ if (opts->ephemeral && decor_state.win && decor_state.win->w_buffer == buf) {
+ int r = (int)line;
+ int c = (int)col;
+ if (line2 == -1) {
+ line2 = r;
+ col2 = c;
+ }
+
+ if (kv_size(virt_text.data.virt_text)) {
+ decor_range_add_virt(&decor_state, r, c, line2, col2, decor_put_vt(virt_text, NULL), true);
+ }
+ if (kv_size(virt_lines.data.virt_lines)) {
+ decor_range_add_virt(&decor_state, r, c, line2, col2, decor_put_vt(virt_lines, NULL), true);
+ }
+ if (has_hl) {
+ DecorSignHighlight sh = decor_sh_from_inline(hl);
+ decor_range_add_sh(&decor_state, r, c, line2, col2, &sh, true, (uint32_t)ns_id, id);
+ }
} else {
- if (ephemeral) {
+ if (opts->ephemeral) {
api_set_error(err, kErrorTypeException, "not yet implemented");
goto error;
}
+ uint16_t decor_flags = 0;
+
+ DecorVirtText *decor_alloc = NULL;
+ if (kv_size(virt_text.data.virt_text)) {
+ decor_alloc = decor_put_vt(virt_text, decor_alloc);
+ if (virt_text.pos == kVPosInline) {
+ decor_flags |= MT_FLAG_DECOR_VIRT_TEXT_INLINE;
+ }
+ }
+ if (kv_size(virt_lines.data.virt_lines)) {
+ decor_alloc = decor_put_vt(virt_lines, decor_alloc);
+ decor_flags |= MT_FLAG_DECOR_VIRT_LINES;
+ }
+
+ uint32_t decor_indexed = DECOR_ID_INVALID;
+ if (sign.flags & kSHIsSign) {
+ decor_indexed = decor_put_sh(sign);
+ if (sign.text.ptr != NULL) {
+ decor_flags |= MT_FLAG_DECOR_SIGNTEXT;
+ }
+ if (sign.number_hl_id || sign.line_hl_id || sign.cursorline_hl_id) {
+ decor_flags |= MT_FLAG_DECOR_SIGNHL;
+ }
+ }
+
+ DecorInline decor = DECOR_INLINE_INIT;
+ if (decor_alloc || decor_indexed != DECOR_ID_INVALID || schar_high(hl.conceal_char)) {
+ if (has_hl) {
+ DecorSignHighlight sh = decor_sh_from_inline(hl);
+ sh.next = decor_indexed;
+ decor_indexed = decor_put_sh(sh);
+ }
+ decor.ext = true;
+ decor.data.ext = (DecorExt){ .sh_idx = decor_indexed, .vt = decor_alloc };
+ } else {
+ decor.data.hl = hl;
+ }
+
+ if (has_hl) {
+ decor_flags |= MT_FLAG_DECOR_HL;
+ }
+
extmark_set(buf, (uint32_t)ns_id, &id, (int)line, (colnr_T)col, line2, col2,
- has_decor ? &decor : NULL, right_gravity, end_right_gravity,
- kExtmarkNoUndo);
+ decor, decor_flags, right_gravity, opts->end_right_gravity,
+ !GET_BOOL_OR_TRUE(opts, set_extmark, undo_restore),
+ opts->invalidate, err);
+ if (ERROR_SET(err)) {
+ decor_free(decor);
+ return 0;
+ }
}
return (Integer)id;
error:
- clear_virttext(&decor.virt_text);
- xfree(decor.sign_text);
+ clear_virttext(&virt_text.data.virt_text);
+ clear_virtlines(&virt_lines.data.virt_lines);
return 0;
}
@@ -829,12 +862,11 @@ 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);
+ return extmark_del_id(buf, (uint32_t)ns_id, (uint32_t)id);
}
uint32_t src2ns(Integer *src_id)
@@ -887,14 +919,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;
}
@@ -919,13 +950,11 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In
end_line++;
}
- Decoration decor = DECORATION_INIT;
- decor.hl_id = hl_id;
+ DecorInline decor = DECOR_INLINE_INIT;
+ decor.data.hl.hl_id = hl_id;
- extmark_set(buf, ns, NULL,
- (int)line, (colnr_T)col_start,
- end_line, (colnr_T)col_end,
- &decor, true, false, kExtmarkNoUndo);
+ extmark_set(buf, ns, NULL, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end,
+ decor, MT_FLAG_DECOR_HL, true, false, false, false, NULL);
return ns_id;
}
@@ -950,10 +979,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;
}
@@ -964,14 +993,14 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
/// Set or change decoration provider for a |namespace|
///
-/// This is a very general purpose interface for having lua callbacks
+/// This is a very general purpose interface for having Lua callbacks
/// being triggered during the redraw code.
///
/// The expected usage is to set |extmarks| for the currently
/// redrawn buffer. |nvim_buf_set_extmark()| can be called to add marks
/// on a per-window or per-lines basis. Use the `ephemeral` key to only
/// use the mark for the current screen redraw (the callback will be called
-/// again for the next redraw ).
+/// again for the next redraw).
///
/// Note: this function should not be called often. Rather, the callbacks
/// themselves can be used to throttle unneeded callbacks. the `on_start`
@@ -983,11 +1012,13 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
/// for the extmarks set/modified inside the callback anyway.
///
/// Note: doing anything other than setting extmarks is considered experimental.
-/// Doing things like changing options are not expliticly forbidden, but is
+/// Doing things like changing options are not explicitly forbidden, but is
/// likely to have unexpected consequences (such as 100% CPU consumption).
/// doing `vim.rpcnotify` should be OK, but `vim.rpcrequest` is quite dubious
/// for the moment.
///
+/// Note: It is not allowed to remove or update extmarks in 'on_line' callbacks.
+///
/// @param ns_id Namespace id from |nvim_create_namespace()|
/// @param opts Table of callbacks:
/// - on_start: called first on each screen redraw
@@ -996,7 +1027,8 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
/// window callbacks)
/// ["buf", bufnr, tick]
/// - on_win: called when starting to redraw a
-/// specific window.
+/// specific window. botline_guess is an approximation
+/// that does not exceed the last line number.
/// ["win", winid, bufnr, topline, botline_guess]
/// - on_line: called for each buffer line being redrawn.
/// (The interaction with fold lines is subject to change)
@@ -1015,7 +1047,7 @@ void nvim_set_decoration_provider(Integer ns_id, Dict(set_decoration_provider) *
struct {
const char *name;
- Object *source;
+ LuaRef *source;
LuaRef *dest;
} cbs[] = {
{ "on_start", &opts->on_start, &p->redraw_start },
@@ -1029,26 +1061,18 @@ void nvim_set_decoration_provider(Integer ns_id, Dict(set_decoration_provider) *
};
for (size_t i = 0; cbs[i].source && cbs[i].dest && cbs[i].name; i++) {
- Object *v = cbs[i].source;
- if (v->type == kObjectTypeNil) {
+ LuaRef *v = cbs[i].source;
+ if (*v <= 0) {
continue;
}
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation,
- "%s is not a function", cbs[i].name);
- goto error;
- }
- *(cbs[i].dest) = v->data.luaref;
- v->data.luaref = LUA_NOREF;
+ *(cbs[i].dest) = *v;
+ *v = LUA_NOREF;
}
p->active = true;
p->hl_valid++;
p->hl_cached = false;
- return;
-error:
- decor_provider_clear(p);
}
/// Gets the line and column of an |extmark|.
@@ -1075,75 +1099,40 @@ 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");
+ MTPair extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id);
+
+ VALIDATE_INT((extmark.start.pos.row >= 0), "mark id (not found)", id, {
return false;
- }
+ });
+ *row = extmark.start.pos.row;
+ *col = extmark.start.pos.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_EXP((pos.size == 2
+ && pos.items[0].type == kObjectTypeInteger
+ && pos.items[1].type == kObjectTypeInteger),
+ "mark position", "2 Integer items", NULL, {
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);
+ *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;
- }
-}
-// adapted from sign.c:sign_define_init_text.
-// TODO(lewis6991): Consider merging
-static int init_sign_text(char **sign_text, char *text)
-{
- char *s;
-
- char *endp = text + (int)strlen(text);
-
- // Count cells and check for non-printable chars
- int cells = 0;
- for (s = text; s < endp; s += utfc_ptr2len(s)) {
- if (!vim_isprintc(utf_ptr2char(s))) {
- break;
- }
- cells += utf_ptr2cells(s);
- }
- // Currently must be empty, one or two display cells
- if (s != endp || cells > 2) {
- return FAIL;
- }
- if (cells < 1) {
- return OK;
- }
-
- // Allocate one byte more if we need to pad up
- // with a space.
- size_t len = (size_t)(endp - text + ((cells == 1) ? 1 : 0));
- *sign_text = xstrnsave(text, len);
-
- if (cells == 1) {
- STRCPY(*sign_text + len - 1, " ");
+ VALIDATE_EXP(false, "mark position", "mark id Integer or 2-item Array", NULL, {
+ return false;
+ });
}
-
- return OK;
}
VirtText parse_virt_text(Array chunks, Error *err, int *width)
@@ -1151,17 +1140,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;
@@ -1176,8 +1162,7 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width)
goto free_exit;
}
if (j < arr.size - 1) {
- kv_push(virt_text, ((VirtTextChunk){ .text = NULL,
- .hl_id = hl_id }));
+ kv_push(virt_text, ((VirtTextChunk){ .text = NULL, .hl_id = hl_id }));
}
}
} else {
@@ -1194,10 +1179,23 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width)
kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
}
- *width = w;
+ if (width != NULL) {
+ *width = w;
+ }
return virt_text;
free_exit:
clear_virttext(&virt_text);
return virt_text;
}
+
+String nvim__buf_debug_extmarks(Buffer buffer, Boolean keys, Boolean dot, Error *err)
+ FUNC_API_SINCE(7)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return NULL_STRING;
+ }
+
+ return mt_inspect(buf->b_marktree, keys, dot);
+}
diff --git a/src/nvim/api/extmark.h b/src/nvim/api/extmark.h
index 0a627a889c..124feaabfb 100644
--- a/src/nvim/api/extmark.h
+++ b/src/nvim/api/extmark.h
@@ -1,17 +1,17 @@
-#ifndef NVIM_API_EXTMARK_H
-#define NVIM_API_EXTMARK_H
+#pragma once
-#include "nvim/api/private/defs.h"
-#include "nvim/decoration.h"
-#include "nvim/macros.h"
-#include "nvim/map.h"
+#include <stdint.h> // IWYU pragma: keep
+
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/decoration_defs.h" // IWYU pragma: keep
+#include "nvim/macros_defs.h"
#include "nvim/map_defs.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
-EXTERN Map(String, handle_T) namespace_ids INIT(= MAP_INIT);
-EXTERN handle_T next_namespace_id INIT(= 1);
+EXTERN Map(String, int) namespace_ids INIT( = MAP_INIT);
+EXTERN handle_T next_namespace_id INIT( = 1);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/extmark.h.generated.h"
#endif
-#endif // NVIM_API_EXTMARK_H
diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
deleted file mode 100644
index 30dcef6127..0000000000
--- a/src/nvim/api/keysets.lua
+++ /dev/null
@@ -1,229 +0,0 @@
-return {
- { 'context', {
- "types";
- }};
- { 'set_decoration_provider', {
- "on_start";
- "on_buf";
- "on_win";
- "on_line";
- "on_end";
- "_on_hl_def";
- "_on_spell_nav";
- }};
- { 'set_extmark', {
- "id";
- "end_line";
- "end_row";
- "end_col";
- "hl_group";
- "virt_text";
- "virt_text_pos";
- "virt_text_win_col";
- "virt_text_hide";
- "hl_eol";
- "hl_mode";
- "ephemeral";
- "priority";
- "right_gravity";
- "end_right_gravity";
- "virt_lines";
- "virt_lines_above";
- "virt_lines_leftcol";
- "strict";
- "sign_text";
- "sign_hl_group";
- "number_hl_group";
- "line_hl_group";
- "cursorline_hl_group";
- "conceal";
- "spell";
- "ui_watched";
- }};
- { 'keymap', {
- "noremap";
- "nowait";
- "silent";
- "script";
- "expr";
- "unique";
- "callback";
- "desc";
- "replace_keycodes";
- }};
- { 'get_commands', {
- "builtin";
- }};
- { 'user_command', {
- "addr";
- "bang";
- "bar";
- "complete";
- "count";
- "desc";
- "force";
- "keepscript";
- "nargs";
- "preview";
- "range";
- "register";
- }};
- { 'float_config', {
- "row";
- "col";
- "width";
- "height";
- "anchor";
- "relative";
- "win";
- "bufpos";
- "external";
- "focusable";
- "zindex";
- "border";
- "title";
- "title_pos";
- "style";
- "noautocmd";
- }};
- { 'runtime', {
- "is_lua";
- "do_source";
- }};
- { 'eval_statusline', {
- "winid";
- "maxwidth";
- "fillchar";
- "highlights";
- "use_winbar";
- "use_tabline";
- }};
- { 'option', {
- "scope";
- "win";
- "buf";
- }};
- { 'highlight', {
- "bold";
- "standout";
- "strikethrough";
- "underline";
- "undercurl";
- "underdouble";
- "underdotted";
- "underdashed";
- "italic";
- "reverse";
- "altfont";
- "nocombine";
- "default";
- "cterm";
- "foreground"; "fg";
- "background"; "bg";
- "ctermfg";
- "ctermbg";
- "special"; "sp";
- "link";
- "global_link";
- "fallback";
- "blend";
- "fg_indexed";
- "bg_indexed";
- }};
- { 'highlight_cterm', {
- "bold";
- "standout";
- "strikethrough";
- "underline";
- "undercurl";
- "underdouble";
- "underdotted";
- "underdashed";
- "italic";
- "reverse";
- "altfont";
- "nocombine";
- }};
- -- Autocmds
- { 'clear_autocmds', {
- "buffer";
- "event";
- "group";
- "pattern";
- }};
- { 'create_autocmd', {
- "buffer";
- "callback";
- "command";
- "desc";
- "group";
- "nested";
- "once";
- "pattern";
- }};
- { 'exec_autocmds', {
- "buffer";
- "group";
- "modeline";
- "pattern";
- "data";
- }};
- { 'get_autocmds', {
- "event";
- "group";
- "pattern";
- "buffer";
- }};
- { 'create_augroup', {
- "clear";
- }};
- { 'cmd', {
- "cmd";
- "range";
- "count";
- "reg";
- "bang";
- "args";
- "magic";
- "mods";
- "nargs";
- "addr";
- "nextcmd";
- }};
- { 'cmd_magic', {
- "file";
- "bar";
- }};
- { 'cmd_mods', {
- "silent";
- "emsg_silent";
- "unsilent";
- "filter";
- "sandbox";
- "noautocmd";
- "browse";
- "confirm";
- "hide";
- "horizontal";
- "keepalt";
- "keepjumps";
- "keepmarks";
- "keeppatterns";
- "lockmarks";
- "noswapfile";
- "tab";
- "verbose";
- "vertical";
- "split";
- }};
- { 'cmd_mods_filter', {
- "pattern";
- "force";
- }};
- { 'cmd_opts', {
- "output";
- }};
- { 'echo_opts', {
- "verbose";
- }};
-}
diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h
new file mode 100644
index 0000000000..e59eda5686
--- /dev/null
+++ b/src/nvim/api/keysets_defs.h
@@ -0,0 +1,315 @@
+#pragma once
+
+#include "nvim/api/private/defs.h"
+
+typedef struct {
+ OptionalKeys is_set__context_;
+ Array types;
+} Dict(context);
+
+typedef struct {
+ OptionalKeys is_set__set_decoration_provider_;
+ LuaRef on_start;
+ LuaRef on_buf;
+ LuaRef on_win;
+ LuaRef on_line;
+ LuaRef on_end;
+ LuaRef _on_hl_def;
+ LuaRef _on_spell_nav;
+} Dict(set_decoration_provider);
+
+typedef struct {
+ OptionalKeys is_set__set_extmark_;
+ Integer id;
+ Integer end_line;
+ Integer end_row;
+ Integer end_col;
+ Object hl_group;
+ Array virt_text;
+ String virt_text_pos;
+ Integer virt_text_win_col;
+ Boolean virt_text_hide;
+ Boolean hl_eol;
+ String hl_mode;
+ Boolean invalidate;
+ Boolean ephemeral;
+ Integer priority;
+ Boolean right_gravity;
+ Boolean end_right_gravity;
+ Array virt_lines;
+ Boolean virt_lines_above;
+ Boolean virt_lines_leftcol;
+ Boolean strict;
+ String sign_text;
+ Object sign_hl_group;
+ Object number_hl_group;
+ Object line_hl_group;
+ Object cursorline_hl_group;
+ String conceal;
+ Boolean spell;
+ Boolean ui_watched;
+ Boolean undo_restore;
+} Dict(set_extmark);
+
+typedef struct {
+ OptionalKeys is_set__get_extmarks_;
+ Integer limit;
+ Boolean details;
+ Boolean hl_name;
+ Boolean overlap;
+ String type;
+} Dict(get_extmarks);
+
+typedef struct {
+ OptionalKeys is_set__keymap_;
+ Boolean noremap;
+ Boolean nowait;
+ Boolean silent;
+ Boolean script;
+ Boolean expr;
+ Boolean unique;
+ LuaRef callback;
+ String desc;
+ Boolean replace_keycodes;
+} Dict(keymap);
+
+typedef struct {
+ Boolean builtin;
+} Dict(get_commands);
+
+typedef struct {
+ OptionalKeys is_set__user_command_;
+ Object addr;
+ Boolean bang;
+ Boolean bar;
+ Object complete;
+ Object count;
+ Object desc;
+ Boolean force;
+ Boolean keepscript;
+ Object nargs;
+ Object preview;
+ Object range;
+ Boolean register_;
+} Dict(user_command);
+
+typedef struct {
+ OptionalKeys is_set__float_config_;
+ Float row;
+ Float col;
+ Integer width;
+ Integer height;
+ String anchor;
+ String relative;
+ Window win;
+ Array bufpos;
+ Boolean external;
+ Boolean focusable;
+ Integer zindex;
+ Object border;
+ Object title;
+ String title_pos;
+ Object footer;
+ String footer_pos;
+ String style;
+ Boolean noautocmd;
+ Boolean fixed;
+ Boolean hide;
+} Dict(float_config);
+
+typedef struct {
+ Boolean is_lua;
+ Boolean do_source;
+} Dict(runtime);
+
+typedef struct {
+ OptionalKeys is_set__eval_statusline_;
+ Window winid;
+ Integer maxwidth;
+ String fillchar;
+ Boolean highlights;
+ Boolean use_winbar;
+ Boolean use_tabline;
+ Integer use_statuscol_lnum;
+} Dict(eval_statusline);
+
+typedef struct {
+ OptionalKeys is_set__option_;
+ String scope;
+ Window win;
+ Buffer buf;
+ String filetype;
+} Dict(option);
+
+typedef struct {
+ OptionalKeys is_set__highlight_;
+ Boolean bold;
+ Boolean standout;
+ Boolean strikethrough;
+ Boolean underline;
+ Boolean undercurl;
+ Boolean underdouble;
+ Boolean underdotted;
+ Boolean underdashed;
+ Boolean italic;
+ Boolean reverse;
+ Boolean altfont;
+ Boolean nocombine;
+ Boolean default_;
+ Object cterm;
+ Object foreground;
+ Object fg;
+ Object background;
+ Object bg;
+ Object ctermfg;
+ Object ctermbg;
+ Object special;
+ Object sp;
+ Object link;
+ Object global_link;
+ Boolean fallback;
+ Integer blend;
+ Boolean fg_indexed;
+ Boolean bg_indexed;
+ Boolean force;
+} Dict(highlight);
+
+typedef struct {
+ Boolean bold;
+ Boolean standout;
+ Boolean strikethrough;
+ Boolean underline;
+ Boolean undercurl;
+ Boolean underdouble;
+ Boolean underdotted;
+ Boolean underdashed;
+ Boolean italic;
+ Boolean reverse;
+ Boolean altfont;
+ Boolean nocombine;
+} Dict(highlight_cterm);
+
+typedef struct {
+ OptionalKeys is_set__get_highlight_;
+ Integer id;
+ String name;
+ Boolean link;
+ Boolean create;
+} Dict(get_highlight);
+
+typedef struct {
+ OptionalKeys is_set__get_ns_;
+ Window winid;
+} Dict(get_ns);
+
+typedef struct {
+ OptionalKeys is_set__win_text_height_;
+ Integer start_row;
+ Integer end_row;
+ Integer start_vcol;
+ Integer end_vcol;
+} Dict(win_text_height);
+
+typedef struct {
+ OptionalKeys is_set__clear_autocmds_;
+ Buffer buffer;
+ Object event;
+ Object group;
+ Object pattern;
+} Dict(clear_autocmds);
+
+typedef struct {
+ OptionalKeys is_set__create_autocmd_;
+ Buffer buffer;
+ Object callback;
+ String command;
+ String desc;
+ Object group;
+ Boolean nested;
+ Boolean once;
+ Object pattern;
+} Dict(create_autocmd);
+
+typedef struct {
+ OptionalKeys is_set__exec_autocmds_;
+ Buffer buffer;
+ Object group;
+ Boolean modeline;
+ Object pattern;
+ Object data;
+} Dict(exec_autocmds);
+
+typedef struct {
+ OptionalKeys is_set__get_autocmds_;
+ Object event;
+ Object group;
+ Object pattern;
+ Object buffer;
+} Dict(get_autocmds);
+
+typedef struct {
+ Object clear;
+} Dict(create_augroup);
+
+typedef struct {
+ OptionalKeys is_set__cmd_;
+ String cmd;
+ Array range;
+ Integer count;
+ String reg;
+ Boolean bang;
+ Array args;
+ Dictionary magic;
+ Dictionary mods;
+ Object nargs;
+ Object addr;
+ Object nextcmd;
+} Dict(cmd);
+
+typedef struct {
+ OptionalKeys is_set__cmd_magic_;
+ Boolean file;
+ Boolean bar;
+} Dict(cmd_magic);
+
+typedef struct {
+ OptionalKeys is_set__cmd_mods_;
+ Boolean silent;
+ Boolean emsg_silent;
+ Boolean unsilent;
+ Dictionary filter;
+ Boolean sandbox;
+ Boolean noautocmd;
+ Boolean browse;
+ Boolean confirm;
+ Boolean hide;
+ Boolean horizontal;
+ Boolean keepalt;
+ Boolean keepjumps;
+ Boolean keepmarks;
+ Boolean keeppatterns;
+ Boolean lockmarks;
+ Boolean noswapfile;
+ Integer tab;
+ Integer verbose;
+ Boolean vertical;
+ String split;
+} Dict(cmd_mods);
+
+typedef struct {
+ OptionalKeys is_set__cmd_mods_filter_;
+ String pattern;
+ Boolean force;
+} Dict(cmd_mods_filter);
+
+typedef struct {
+ Boolean output;
+} Dict(cmd_opts);
+
+typedef struct {
+ Boolean verbose;
+} Dict(echo_opts);
+
+typedef struct {
+ Boolean output;
+} Dict(exec_opts);
diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c
index bfcb99754f..c012a69c7b 100644
--- a/src/nvim/api/options.c
+++ b/src/nvim/api/options.c
@@ -1,80 +1,135 @@
-// 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 <inttypes.h>
-#include <limits.h>
+#include <assert.h>
#include <stdbool.h>
#include <string.h>
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/options.h"
#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/autocmd.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/buffer.h"
#include "nvim/eval/window.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/option.h"
-#include "nvim/vim.h"
+#include "nvim/option_vars.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/options.c.generated.h"
#endif
-static int validate_option_value_args(Dict(option) *opts, int *scope, int *opt_type, void **from,
+static int validate_option_value_args(Dict(option) *opts, char *name, int *scope,
+ OptReqScope *req_scope, void **from, char **filetype,
Error *err)
{
- if (opts->scope.type == kObjectTypeString) {
- if (!strcmp(opts->scope.data.string.data, "local")) {
+#define HAS_KEY_X(d, v) HAS_KEY(d, option, v)
+ if (HAS_KEY_X(opts, scope)) {
+ if (!strcmp(opts->scope.data, "local")) {
*scope = OPT_LOCAL;
- } else if (!strcmp(opts->scope.data.string.data, "global")) {
+ } else if (!strcmp(opts->scope.data, "global")) {
*scope = OPT_GLOBAL;
} else {
- api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'");
- return FAIL;
+ VALIDATE_EXP(false, "scope", "'local' or 'global'", NULL, {
+ return FAIL;
+ });
}
- } else if (HAS_KEY(opts->scope)) {
- api_set_error(err, kErrorTypeValidation, "invalid value for key: scope");
- return FAIL;
}
- *opt_type = SREQ_GLOBAL;
+ *req_scope = kOptReqGlobal;
+
+ if (filetype != NULL && HAS_KEY_X(opts, filetype)) {
+ *filetype = opts->filetype.data;
+ }
- if (opts->win.type == kObjectTypeInteger) {
- *opt_type = SREQ_WIN;
- *from = find_window_by_handle((int)opts->win.data.integer, err);
+ if (HAS_KEY_X(opts, win)) {
+ *req_scope = kOptReqWin;
+ *from = find_window_by_handle(opts->win, 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_X(opts, buf)) {
*scope = OPT_LOCAL;
- *opt_type = SREQ_BUF;
- *from = find_buffer_by_handle((int)opts->buf.data.integer, err);
+ *req_scope = kOptReqBuf;
+ *from = find_buffer_by_handle(opts->buf, 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_X(opts, filetype)
+ || !(HAS_KEY_X(opts, buf) || HAS_KEY_X(opts, scope) || HAS_KEY_X(opts, win))),
+ "%s", "cannot use 'filetype' with 'scope', 'buf' or 'win'", {
return FAIL;
- }
+ });
+
+ VALIDATE((!HAS_KEY_X(opts, scope) || !HAS_KEY_X(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_X(opts, win) || !HAS_KEY_X(opts, buf)),
+ "%s", "cannot use both 'buf' and 'win'", {
return FAIL;
+ });
+
+ int flags = get_option_attrs(name);
+ if (flags == 0) {
+ // hidden or unknown option
+ api_set_error(err, kErrorTypeValidation, "Unknown option '%s'", name);
+ } else if (*req_scope == kOptReqBuf || *req_scope == kOptReqWin) {
+ // if 'buf' or 'win' is passed, make sure the option supports it
+ int req_flags = *req_scope == kOptReqBuf ? SOPT_BUF : SOPT_WIN;
+ if (!(flags & req_flags)) {
+ char *tgt = *req_scope & kOptReqBuf ? "buf" : "win";
+ char *global = flags & SOPT_GLOBAL ? "global " : "";
+ char *req = flags & SOPT_BUF ? "buffer-local "
+ : flags & SOPT_WIN ? "window-local " : "";
+
+ api_set_error(err, kErrorTypeValidation, "'%s' cannot be passed for %s%soption '%s'",
+ tgt, global, req, name);
+ }
}
return OK;
+#undef HAS_KEY_X
+}
+
+/// Create a dummy buffer and run the FileType autocmd on it.
+static buf_T *do_ft_buf(char *filetype, aco_save_T *aco, Error *err)
+{
+ if (filetype == NULL) {
+ return NULL;
+ }
+
+ // Allocate a buffer without putting it in the buffer list.
+ buf_T *ftbuf = buflist_new(NULL, NULL, 1, BLN_DUMMY);
+ if (ftbuf == NULL) {
+ api_set_error(err, kErrorTypeException, "Could not create internal buffer");
+ return NULL;
+ }
+
+ // Set curwin/curbuf to buf and save a few things.
+ aucmd_prepbuf(aco, ftbuf);
+
+ TRY_WRAP(err, {
+ set_option_value("bufhidden", STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL);
+ set_option_value("buftype", STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL);
+ set_option_value("swapfile", BOOLEAN_OPTVAL(false), OPT_LOCAL);
+ set_option_value("modeline", BOOLEAN_OPTVAL(false), OPT_LOCAL); // 'nomodeline'
+
+ ftbuf->b_p_ft = xstrdup(filetype);
+ do_filetype_autocmd(ftbuf, false);
+ });
+
+ return ftbuf;
}
/// Gets the value of an option. The behavior of this function matches that of
@@ -89,53 +144,61 @@ static int validate_option_value_args(Dict(option) *opts, int *scope, int *opt_t
/// - win: |window-ID|. Used for getting window local options.
/// - buf: Buffer number. Used for getting buffer local options.
/// Implies {scope} is "local".
+/// - filetype: |filetype|. Used to get the default option for a
+/// specific filetype. Cannot be used with any other option.
+/// Note: this will trigger |ftplugin| and all |FileType|
+/// autocommands for the corresponding filetype.
/// @param[out] err Error details, if any
/// @return Option value
Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
FUNC_API_SINCE(9)
{
Object rv = OBJECT_INIT;
+ OptVal value = NIL_OPTVAL;
int scope = 0;
- int opt_type = SREQ_GLOBAL;
+ OptReqScope req_scope = kOptReqGlobal;
void *from = NULL;
- if (!validate_option_value_args(opts, &scope, &opt_type, &from, err)) {
- return rv;
+ char *filetype = NULL;
+
+ if (!validate_option_value_args(opts, name.data, &scope, &req_scope, &from, &filetype, err)) {
+ goto err;
}
- long numval = 0;
- char *stringval = NULL;
- getoption_T result = access_option_value_for(name.data, &numval, &stringval, scope, opt_type,
- from, true, err);
+ aco_save_T aco;
+
+ buf_T *ftbuf = do_ft_buf(filetype, &aco, err);
if (ERROR_SET(err)) {
- return rv;
+ goto err;
}
- switch (result) {
- case gov_string:
- rv = STRING_OBJ(cstr_as_string(stringval));
- break;
- case gov_number:
- rv = INTEGER_OBJ(numval);
- break;
- case gov_bool:
- switch (numval) {
- case 0:
- case 1:
- rv = BOOLEAN_OBJ(numval);
- break;
- default:
- // Boolean options that return something other than 0 or 1 should return nil. Currently this
- // only applies to 'autoread' which uses -1 as a local value to indicate "unset"
- rv = NIL;
- break;
- }
- break;
- default:
- api_set_error(err, kErrorTypeValidation, "unknown option '%s'", name.data);
- return rv;
+ if (ftbuf != NULL) {
+ assert(!from);
+ from = ftbuf;
+ }
+
+ bool hidden;
+ value = get_option_value_for(name.data, NULL, scope, &hidden, req_scope, from, err);
+
+ if (ftbuf != NULL) {
+ // restore curwin/curbuf and a few other things
+ aucmd_restbuf(&aco);
+
+ assert(curbuf != ftbuf); // safety check
+ wipe_buffer(ftbuf, false);
+ }
+
+ if (ERROR_SET(err)) {
+ goto err;
}
+ VALIDATE_S(!hidden && value.type != kOptValTypeNil, "option", name.data, {
+ goto err;
+ });
+
+ return optval_as_object(value);
+err:
+ optval_free(value);
return rv;
}
@@ -153,13 +216,14 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
/// - win: |window-ID|. Used for setting window local option.
/// - buf: Buffer number. Used for setting buffer local option.
/// @param[out] err Error details, if any
-void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error *err)
+void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict(option) *opts,
+ Error *err)
FUNC_API_SINCE(9)
{
int scope = 0;
- int opt_type = SREQ_GLOBAL;
+ OptReqScope req_scope = kOptReqGlobal;
void *to = NULL;
- if (!validate_option_value_args(opts, &scope, &opt_type, &to, err)) {
+ if (!validate_option_value_args(opts, name.data, &scope, &req_scope, &to, NULL, err)) {
return;
}
@@ -169,41 +233,35 @@ void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error
// - option is global or local to window (global-local)
//
// Then force scope to local since we don't want to change the global option
- if (opt_type == SREQ_WIN && scope == 0) {
- int flags = get_option_value_strict(name.data, NULL, NULL, opt_type, to);
+ if (req_scope == kOptReqWin && scope == 0) {
+ int flags = get_option_attrs(name.data);
if (flags & SOPT_GLOBAL) {
scope = OPT_LOCAL;
}
}
- long numval = 0;
- char *stringval = NULL;
+ bool error = false;
+ OptVal optval = object_as_optval(value, &error);
- switch (value.type) {
- case kObjectTypeInteger:
- numval = value.data.integer;
- break;
- case kObjectTypeBoolean:
- numval = value.data.boolean ? 1 : 0;
- break;
- case kObjectTypeString:
- stringval = value.data.string.data;
- break;
- case kObjectTypeNil:
- scope |= OPT_CLEAR;
- break;
- default:
- api_set_error(err, kErrorTypeValidation, "invalid value for option");
+ // Handle invalid option value type.
+ // Don't use `name` in the error message here, because `name` can be any String.
+ // No need to check if value type actually matches the types for the option, as set_option_value()
+ // already handles that.
+ VALIDATE_EXP(!error, "value", "valid option type", api_typename(value.type), {
return;
- }
+ });
- access_option_value_for(name.data, &numval, &stringval, scope, opt_type, to, false, err);
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ set_option_value_for(name.data, optval, scope, req_scope, to, err);
+ });
}
/// Gets the option information for all options.
///
/// The dictionary has the full option names as keys and option metadata
-/// dictionaries as detailed at |nvim_get_option_info()|.
+/// dictionaries as detailed at |nvim_get_option_info2()|.
+///
+/// @see |nvim_get_commands()|
///
/// @return dictionary of all options
Dictionary nvim_get_all_options_info(Error *err)
@@ -212,7 +270,7 @@ Dictionary nvim_get_all_options_info(Error *err)
return get_all_vimoptions();
}
-/// Gets the option information for one option
+/// Gets the option information for one option from arbitrary buffer or window
///
/// Resulting dictionary has keys:
/// - name: Name of the option (like 'filetype')
@@ -231,324 +289,289 @@ Dictionary nvim_get_all_options_info(Error *err)
/// - commalist: List of comma separated values
/// - flaglist: List of single char flags
///
+/// When {scope} is not provided, the last set information applies to the local
+/// value in the current buffer or window if it is available, otherwise the
+/// global value information is returned. This behavior can be disabled by
+/// explicitly specifying {scope} in the {opts} table.
///
-/// @param name Option name
+/// @param name Option name
+/// @param opts Optional parameters
+/// - scope: One of "global" or "local". Analogous to
+/// |:setglobal| and |:setlocal|, respectively.
+/// - win: |window-ID|. Used for getting window local options.
+/// - buf: Buffer number. Used for getting buffer local options.
+/// Implies {scope} is "local".
/// @param[out] err Error details, if any
/// @return Option Information
-Dictionary nvim_get_option_info(String name, Error *err)
- FUNC_API_SINCE(7)
+Dictionary nvim_get_option_info2(String name, Dict(option) *opts, Error *err)
+ FUNC_API_SINCE(11)
{
- return get_vimoption(name, err);
-}
-/// Sets the global value of an option.
-///
-/// @param channel_id
-/// @param name Option name
-/// @param value New option value
-/// @param[out] err Error details, if any
-void nvim_set_option(uint64_t channel_id, String name, Object value, Error *err)
- FUNC_API_SINCE(1)
-{
- set_option_to(channel_id, NULL, SREQ_GLOBAL, name, value, err);
-}
+ int scope = 0;
+ OptReqScope req_scope = kOptReqGlobal;
+ void *from = NULL;
+ if (!validate_option_value_args(opts, name.data, &scope, &req_scope, &from, NULL, err)) {
+ return (Dictionary)ARRAY_DICT_INIT;
+ }
-/// Gets the global value of an option.
-///
-/// @param name Option name
-/// @param[out] err Error details, if any
-/// @return Option value (global)
-Object nvim_get_option(String name, Arena *arena, Error *err)
- FUNC_API_SINCE(1)
-{
- return get_option_from(NULL, SREQ_GLOBAL, name, err);
+ buf_T *buf = (req_scope == kOptReqBuf) ? (buf_T *)from : curbuf;
+ win_T *win = (req_scope == kOptReqWin) ? (win_T *)from : curwin;
+
+ return get_vimoption(name, scope, buf, win, err);
}
-/// Gets a buffer option value
+/// Switch current context to get/set option value for window/buffer.
+///
+/// @param[out] ctx Current context. switchwin_T for window and aco_save_T for buffer.
+/// @param req_scope Requested option scope. See OptReqScope in option.h.
+/// @param[in] from Target buffer/window.
+/// @param[out] err Error message, if any.
///
-/// @param buffer Buffer handle, or 0 for current buffer
-/// @param name Option name
-/// @param[out] err Error details, if any
-/// @return Option value
-Object nvim_buf_get_option(Buffer buffer, String name, Arena *arena, Error *err)
- FUNC_API_SINCE(1)
+/// @return true if context was switched, false otherwise.
+static bool switch_option_context(void *const ctx, OptReqScope req_scope, void *const from,
+ Error *err)
{
- buf_T *buf = find_buffer_by_handle(buffer, err);
+ switch (req_scope) {
+ case kOptReqWin: {
+ win_T *const win = (win_T *)from;
+ switchwin_T *const switchwin = (switchwin_T *)ctx;
+
+ if (win == curwin) {
+ return false;
+ }
- if (!buf) {
- return (Object)OBJECT_INIT;
+ if (switch_win_noblock(switchwin, win, win_find_tabpage(win), true)
+ == FAIL) {
+ restore_win_noblock(switchwin, true);
+
+ if (try_end(err)) {
+ return false;
+ }
+ api_set_error(err, kErrorTypeException, "Problem while switching windows");
+ return false;
+ }
+ return true;
}
+ case kOptReqBuf: {
+ buf_T *const buf = (buf_T *)from;
+ aco_save_T *const aco = (aco_save_T *)ctx;
- return get_option_from(buf, SREQ_BUF, name, err);
+ if (buf == curbuf) {
+ return false;
+ }
+ aucmd_prepbuf(aco, buf);
+ return true;
+ }
+ case kOptReqGlobal:
+ return false;
+ }
+ UNREACHABLE;
}
-/// Sets a buffer option value. Passing `nil` as value deletes the option (only
-/// works if there's a global fallback)
-///
-/// @param channel_id
-/// @param buffer Buffer handle, or 0 for current buffer
-/// @param name Option name
-/// @param value Option value
-/// @param[out] err Error details, if any
-void nvim_buf_set_option(uint64_t channel_id, Buffer buffer, String name, Object value, Error *err)
- FUNC_API_SINCE(1)
+/// Restore context after getting/setting option for window/buffer. See switch_option_context() for
+/// params.
+static void restore_option_context(void *const ctx, OptReqScope req_scope)
{
- buf_T *buf = find_buffer_by_handle(buffer, err);
-
- if (!buf) {
- return;
+ switch (req_scope) {
+ case kOptReqWin:
+ restore_win_noblock((switchwin_T *)ctx, true);
+ break;
+ case kOptReqBuf:
+ aucmd_restbuf((aco_save_T *)ctx);
+ break;
+ case kOptReqGlobal:
+ break;
}
-
- set_option_to(channel_id, buf, SREQ_BUF, name, value, err);
}
-/// Gets a window option value
+/// Get attributes for an option.
///
-/// @param window Window handle, or 0 for current window
-/// @param name Option name
-/// @param[out] err Error details, if any
-/// @return Option value
-Object nvim_win_get_option(Window window, String name, Arena *arena, Error *err)
- FUNC_API_SINCE(1)
+/// @param name Option name.
+///
+/// @return Option attributes.
+/// 0 for hidden or unknown option.
+/// See SOPT_* in option_defs.h for other flags.
+int get_option_attrs(char *name)
{
- win_T *win = find_window_by_handle(window, err);
+ int opt_idx = findoption(name);
- if (!win) {
- return (Object)OBJECT_INIT;
+ if (opt_idx < 0) {
+ return 0;
}
- return get_option_from(win, SREQ_WIN, name, err);
-}
+ vimoption_T *opt = get_option(opt_idx);
-/// Sets a window option value. Passing `nil` as value deletes the option (only
-/// works if there's a global fallback)
-///
-/// @param channel_id
-/// @param window Window handle, or 0 for current window
-/// @param name Option name
-/// @param value Option value
-/// @param[out] err Error details, if any
-void nvim_win_set_option(uint64_t channel_id, Window window, String name, Object value, Error *err)
- FUNC_API_SINCE(1)
-{
- win_T *win = find_window_by_handle(window, err);
+ if (is_tty_option(opt->fullname)) {
+ return SOPT_STRING | SOPT_GLOBAL;
+ }
- if (!win) {
- return;
+ // Hidden option
+ if (opt->var == NULL) {
+ return 0;
}
- set_option_to(channel_id, win, SREQ_WIN, name, value, err);
-}
+ int attrs = 0;
-/// Gets the value of a global or local (buffer, window) option.
-///
-/// @param from If `type` is `SREQ_WIN` or `SREQ_BUF`, this must be a pointer
-/// to the window or buffer.
-/// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF`
-/// @param name The option name
-/// @param[out] err Details of an error that may have occurred
-/// @return the option value
-static Object get_option_from(void *from, int type, String name, Error *err)
-{
- Object rv = OBJECT_INIT;
+ if (opt->flags & P_BOOL) {
+ attrs |= SOPT_BOOL;
+ } else if (opt->flags & P_NUM) {
+ attrs |= SOPT_NUM;
+ } else if (opt->flags & P_STRING) {
+ attrs |= SOPT_STRING;
+ }
- if (name.size == 0) {
- api_set_error(err, kErrorTypeValidation, "Empty option name");
- 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);
- return rv;
- }
-
- if (flags & SOPT_BOOL) {
- rv.type = kObjectTypeBoolean;
- rv.data.boolean = numval ? true : false;
- } else if (flags & SOPT_NUM) {
- 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);
- }
- } else {
- api_set_error(err,
- kErrorTypeException,
- "Unknown type for option '%s'",
- name.data);
+ if (opt->indir == PV_NONE || (opt->indir & PV_BOTH)) {
+ attrs |= SOPT_GLOBAL;
+ }
+ if (opt->indir & PV_WIN) {
+ attrs |= SOPT_WIN;
+ } else if (opt->indir & PV_BUF) {
+ attrs |= SOPT_BUF;
}
- return rv;
+ return attrs;
}
-/// Sets the value of a global or local (buffer, window) option.
+/// Check if option has a value in the requested scope.
///
-/// @param to If `type` is `SREQ_WIN` or `SREQ_BUF`, this must be a pointer
-/// to the window or buffer.
-/// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF`
-/// @param name The option name
-/// @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)
+/// @param name Option name.
+/// @param req_scope Requested option scope. See OptReqScope in option.h.
+///
+/// @return true if option has a value in the requested scope, false otherwise.
+static bool option_has_scope(char *name, OptReqScope req_scope)
{
- if (name.size == 0) {
- api_set_error(err, kErrorTypeValidation, "Empty option name");
- return;
+ int opt_idx = findoption(name);
+
+ if (opt_idx < 0) {
+ return false;
}
- int flags = get_option_value_strict(name.data, NULL, NULL, type, to);
+ vimoption_T *opt = get_option(opt_idx);
- if (flags == 0) {
- api_set_error(err, kErrorTypeValidation, "Invalid option name '%s'",
- name.data);
- return;
+ // Hidden option.
+ if (opt->var == NULL) {
+ return false;
+ }
+ // TTY option.
+ if (is_tty_option(opt->fullname)) {
+ return req_scope == kOptReqGlobal;
}
- if (value.type == kObjectTypeNil) {
- if (type == SREQ_GLOBAL) {
- 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",
- name.data);
- return;
- } else {
- unset_global_local_option(name.data, to);
- return;
- }
+ switch (req_scope) {
+ case kOptReqGlobal:
+ return opt->var != VAR_WIN;
+ case kOptReqBuf:
+ return opt->indir & PV_BUF;
+ case kOptReqWin:
+ return opt->indir & PV_WIN;
}
+ UNREACHABLE;
+}
- long numval = 0;
- char *stringval = NULL;
+/// Get the option value in the requested scope.
+///
+/// @param name Option name.
+/// @param req_scope Requested option scope. See OptReqScope in option.h.
+/// @param[in] from Pointer to buffer or window for local option value.
+/// @param[out] err Error message, if any.
+///
+/// @return Option value in the requested scope. Returns a Nil option value if option is not found,
+/// hidden or if it isn't present in the requested scope. (i.e. has no global, window-local or
+/// buffer-local value depending on opt_scope).
+OptVal get_option_value_strict(char *name, OptReqScope req_scope, void *from, Error *err)
+{
+ OptVal retv = NIL_OPTVAL;
- if (flags & SOPT_BOOL) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(err,
- kErrorTypeValidation,
- "Option '%s' requires a Boolean value",
- name.data);
- return;
- }
+ if (!option_has_scope(name, req_scope)) {
+ return retv;
+ }
+ if (get_tty_option(name, &retv.data.string.data)) {
+ retv.type = kOptValTypeString;
+ return retv;
+ }
- 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);
- return;
- }
+ int opt_idx = findoption(name);
+ assert(opt_idx != 0); // option_has_scope() already verifies if option name is valid.
- 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);
- return;
- }
+ vimoption_T *opt = get_option(opt_idx);
+ switchwin_T switchwin;
+ aco_save_T aco;
+ void *ctx = req_scope == kOptReqWin ? (void *)&switchwin
+ : (req_scope == kOptReqBuf ? (void *)&aco : NULL);
+ bool switched = switch_option_context(ctx, req_scope, from, err);
+ if (ERROR_SET(err)) {
+ return retv;
+ }
- numval = (int)value.data.integer;
- } else {
- if (value.type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation,
- "Option '%s' requires a string value",
- name.data);
- return;
- }
+ char *varp = get_varp_scope(opt, req_scope == kOptReqGlobal ? OPT_GLOBAL : OPT_LOCAL);
+ retv = optval_from_varp(opt_idx, varp);
- stringval = value.data.string.data;
+ if (switched) {
+ restore_option_context(ctx, req_scope);
}
- // For global-win-local options -> setlocal
- // For win-local options -> setglobal and setlocal (opt_flags == 0)
- const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL)) ? 0 :
- (type == SREQ_GLOBAL) ? OPT_GLOBAL : OPT_LOCAL;
-
- WITH_SCRIPT_CONTEXT(channel_id, {
- access_option_value_for(name.data, &numval, &stringval, opt_flags, type, to, false, err);
- });
+ return retv;
}
-static getoption_T access_option_value(char *key, long *numval, char **stringval, int opt_flags,
- bool get, Error *err)
+/// Get option value for buffer / window.
+///
+/// @param[in] name Option name.
+/// @param[out] flagsp Set to the option flags (P_xxxx) (if not NULL).
+/// @param[in] scope Option scope (can be OPT_LOCAL, OPT_GLOBAL or a combination).
+/// @param[out] hidden Whether option is hidden.
+/// @param req_scope Requested option scope. See OptReqScope in option.h.
+/// @param[in] from Target buffer/window.
+/// @param[out] err Error message, if any.
+///
+/// @return Option value. Must be freed by caller.
+OptVal get_option_value_for(const char *const name, uint32_t *flagsp, int scope, bool *hidden,
+ const OptReqScope req_scope, void *const from, Error *err)
{
- if (get) {
- return get_option_value(key, numval, stringval, NULL, opt_flags);
- } else {
- char *errmsg;
- if ((errmsg = set_option_value(key, *numval, *stringval, opt_flags))) {
- if (try_end(err)) {
- return 0;
- }
+ switchwin_T switchwin;
+ aco_save_T aco;
+ void *ctx = req_scope == kOptReqWin ? (void *)&switchwin
+ : (req_scope == kOptReqBuf ? (void *)&aco : NULL);
- api_set_error(err, kErrorTypeException, "%s", errmsg);
- }
- return 0;
+ bool switched = switch_option_context(ctx, req_scope, from, err);
+ if (ERROR_SET(err)) {
+ return NIL_OPTVAL;
+ }
+
+ OptVal retv = get_option_value(name, flagsp, scope, hidden);
+
+ if (switched) {
+ restore_option_context(ctx, req_scope);
}
+
+ return retv;
}
-static getoption_T access_option_value_for(char *key, long *numval, char **stringval, int opt_flags,
- int opt_type, void *from, bool get, Error *err)
+/// Set option value for buffer / window.
+///
+/// @param[in] name Option name.
+/// @param[in] value Option value.
+/// @param[in] opt_flags Flags: OPT_LOCAL, OPT_GLOBAL, or 0 (both).
+/// @param req_scope Requested option scope. See OptReqScope in option.h.
+/// @param[in] from Target buffer/window.
+/// @param[out] err Error message, if any.
+void set_option_value_for(const char *const name, OptVal value, const int opt_flags,
+ const OptReqScope req_scope, void *const from, Error *err)
{
- bool need_switch = false;
switchwin_T switchwin;
aco_save_T aco;
- getoption_T result = 0;
-
- try_start();
- switch (opt_type) {
- case SREQ_WIN:
- need_switch = (win_T *)from != curwin;
- if (need_switch) {
- if (switch_win_noblock(&switchwin, (win_T *)from, win_find_tabpage((win_T *)from), true)
- == FAIL) {
- restore_win_noblock(&switchwin, true);
- if (try_end(err)) {
- return result;
- }
- api_set_error(err, kErrorTypeException, "Problem while switching windows");
- return result;
- }
- }
- result = access_option_value(key, numval, stringval, opt_flags, get, err);
- if (need_switch) {
- restore_win_noblock(&switchwin, true);
- }
- break;
- case SREQ_BUF:
- need_switch = (buf_T *)from != curbuf;
- if (need_switch) {
- aucmd_prepbuf(&aco, (buf_T *)from);
- }
- result = access_option_value(key, numval, stringval, opt_flags, get, err);
- if (need_switch) {
- aucmd_restbuf(&aco);
- }
- break;
- case SREQ_GLOBAL:
- result = access_option_value(key, numval, stringval, opt_flags, get, err);
- break;
- }
+ void *ctx = req_scope == kOptReqWin ? (void *)&switchwin
+ : (req_scope == kOptReqBuf ? (void *)&aco : NULL);
+ bool switched = switch_option_context(ctx, req_scope, from, err);
if (ERROR_SET(err)) {
- return result;
+ return;
}
- try_end(err);
+ const char *const errmsg = set_option_value(name, value, opt_flags);
+ if (errmsg) {
+ api_set_error(err, kErrorTypeException, "%s", errmsg);
+ }
- return result;
+ if (switched) {
+ restore_option_context(ctx, req_scope);
+ }
}
diff --git a/src/nvim/api/options.h b/src/nvim/api/options.h
index efbfec3a6c..c16c6088b3 100644
--- a/src/nvim/api/options.h
+++ b/src/nvim/api/options.h
@@ -1,9 +1,11 @@
-#ifndef NVIM_API_OPTIONS_H
-#define NVIM_API_OPTIONS_H
+#pragma once
+
+#include <stdint.h> // IWYU pragma: keep
+
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/option_defs.h" // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/options.h.generated.h"
#endif
-
-#endif // NVIM_API_OPTIONS_H
diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c
index 58ff552ab7..90023171e5 100644
--- a/src/nvim/api/private/converter.c
+++ b/src/nvim/api/private/converter.c
@@ -1,25 +1,21 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
-#include <stdlib.h>
#include "klib/kvec.h"
#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/assert.h"
+#include "nvim/assert_defs.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
-#include "nvim/garray.h"
+#include "nvim/func_attr.h"
#include "nvim/lua/executor.h"
#include "nvim/memory.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
/// Helper structure for vim_to_object
typedef struct {
@@ -49,9 +45,9 @@ typedef struct {
#define TYPVAL_ENCODE_CONV_STRING(tv, str, len) \
do { \
const size_t len_ = (size_t)(len); \
- const char *const str_ = (const char *)(str); \
+ const char *const str_ = (str); \
assert(len_ == 0 || str_ != NULL); \
- kvi_push(edata->stack, STRING_OBJ(cbuf_to_string((len_?str_:""), len_))); \
+ kvi_push(edata->stack, STRING_OBJ(cbuf_to_string((len_ ? str_ : ""), len_))); \
} while (0)
#define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING
@@ -204,6 +200,7 @@ static inline void typval_encode_dict_end(EncodedData *const edata)
#define TYPVAL_ENCODE_FIRST_ARG_TYPE EncodedData *const
#define TYPVAL_ENCODE_FIRST_ARG_NAME edata
#include "nvim/eval/typval_encode.c.h"
+
#undef TYPVAL_ENCODE_SCOPE
#undef TYPVAL_ENCODE_NAME
#undef TYPVAL_ENCODE_FIRST_ARG_TYPE
@@ -256,12 +253,14 @@ Object vim_to_object(typval_T *obj)
return ret;
}
-/// Converts from type Object to a VimL value.
+/// Converts from type Object to a Vimscript value.
///
/// @param obj Object to convert from.
/// @param tv Conversion result is placed here. On failure member v_type is
/// set to VAR_UNKNOWN (no allocation was made for this variable).
-/// returns true if conversion is successful, otherwise false.
+/// @param err Error object.
+///
+/// @returns true if conversion is successful, otherwise false.
bool object_to_vim(Object obj, typval_T *tv, Error *err)
{
tv->v_type = VAR_UNKNOWN;
@@ -275,7 +274,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
case kObjectTypeBoolean:
tv->v_type = VAR_BOOL;
- tv->vval.v_bool = obj.data.boolean? kBoolVarTrue: kBoolVarFalse;
+ tv->vval.v_bool = obj.data.boolean ? kBoolVarTrue : kBoolVarFalse;
break;
case kObjectTypeBuffer:
@@ -283,7 +282,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
case kObjectTypeTabpage:
case kObjectTypeInteger:
STATIC_ASSERT(sizeof(obj.data.integer) <= sizeof(varnumber_T),
- "Integer size must be <= VimL number size");
+ "Integer size must be <= Vimscript number size");
tv->v_type = VAR_NUMBER;
tv->vval.v_number = (varnumber_T)obj.data.integer;
break;
@@ -363,9 +362,6 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
tv->vval.v_string = xstrdup(name);
break;
}
-
- default:
- abort();
}
return true;
diff --git a/src/nvim/api/private/converter.h b/src/nvim/api/private/converter.h
index 80ee640295..fc82abf332 100644
--- a/src/nvim/api/private/converter.h
+++ b/src/nvim/api/private/converter.h
@@ -1,11 +1,8 @@
-#ifndef NVIM_API_PRIVATE_CONVERTER_H
-#define NVIM_API_PRIVATE_CONVERTER_H
+#pragma once
-#include "nvim/api/private/defs.h"
-#include "nvim/eval/typval.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/private/converter.h.generated.h"
#endif
-
-#endif // NVIM_API_PRIVATE_CONVERTER_H
diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h
index 8acbf0d9de..25c8377518 100644
--- a/src/nvim/api/private/defs.h
+++ b/src/nvim/api/private/defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_API_PRIVATE_DEFS_H
-#define NVIM_API_PRIVATE_DEFS_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
@@ -7,7 +6,7 @@
#include "klib/kvec.h"
#include "nvim/func_attr.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#define ARRAY_DICT_INIT KV_INITIAL_VALUE
#define STRING_INIT { .data = NULL, .size = 0 }
@@ -42,10 +41,10 @@ typedef enum {
/// Mask for all internal calls
#define INTERNAL_CALL_MASK (((uint64_t)1) << (sizeof(uint64_t) * 8 - 1))
-/// Internal call from VimL code
+/// Internal call from Vimscript code
#define VIML_INTERNAL_CALL INTERNAL_CALL_MASK
-/// Internal call from lua code
+/// Internal call from Lua code
#define LUA_INTERNAL_CALL (VIML_INTERNAL_CALL + 1)
static inline bool is_internal_call(uint64_t channel_id)
@@ -124,14 +123,18 @@ struct key_value_pair {
Object value;
};
-typedef Object *(*field_hash)(void *retval, const char *str, size_t len);
+typedef uint64_t OptionalKeys;
+
+// this is the prefix of all keysets with optional keys
+typedef struct {
+ OptionalKeys is_set_;
+} OptKeySet;
+
typedef struct {
char *str;
size_t ptr_off;
+ ObjectType type; // kObjectTypeNil == untyped
+ int opt_index;
} KeySetLink;
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "keysets_defs.generated.h"
-#endif
-
-#endif // NVIM_API_PRIVATE_DEFS_H
+typedef KeySetLink *(*FieldHashfn)(const char *str, size_t len);
diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c
index f427bba00e..53fcd148bd 100644
--- a/src/nvim/api/private/dispatch.c
+++ b/src/nvim/api/private/dispatch.c
@@ -1,6 +1,3 @@
-// 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 <stddef.h>
#include "nvim/api/private/defs.h"
diff --git a/src/nvim/api/private/dispatch.h b/src/nvim/api/private/dispatch.h
index 4ae61b2bfb..6a2c9eaf54 100644
--- a/src/nvim/api/private/dispatch.h
+++ b/src/nvim/api/private/dispatch.h
@@ -1,12 +1,11 @@
-#ifndef NVIM_API_PRIVATE_DISPATCH_H
-#define NVIM_API_PRIVATE_DISPATCH_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "nvim/api/private/defs.h"
-#include "nvim/memory.h"
-#include "nvim/types.h"
+#include "nvim/memory_defs.h"
+#include "nvim/types_defs.h"
typedef Object (*ApiDispatchWrapper)(uint64_t channel_id, Array args, Arena *arena, Error *error);
@@ -27,7 +26,6 @@ extern const MsgpackRpcRequestHandler method_handlers[];
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/private/dispatch.h.generated.h"
-# include "api/private/dispatch_wrappers.h.generated.h"
+# include "api/private/dispatch_wrappers.h.generated.h" // IWYU pragma: export
+# include "keysets_defs.generated.h"
#endif
-
-#endif // NVIM_API_PRIVATE_DISPATCH_H
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 519f2cc5bf..be39836a5b 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1,13 +1,10 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
-#include <inttypes.h>
#include <limits.h>
#include <msgpack/unpack.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -16,21 +13,26 @@
#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/api/private/validate.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
+#include "nvim/eval/vars.h"
#include "nvim/ex_eval.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
+#include "nvim/globals.h"
#include "nvim/highlight_group.h"
#include "nvim/lua/executor.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/mark.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/msgpack_rpc/helpers.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/version.h"
@@ -40,10 +42,10 @@
# include "api/private/ui_events_metadata.generated.h"
#endif
-/// Start block that may cause VimL exceptions while evaluating another code
+/// Start block that may cause Vimscript exceptions while evaluating another code
///
-/// Used when caller is supposed to be operating when other VimL code is being
-/// processed and that “other VimL code” must not be affected.
+/// Used when caller is supposed to be operating when other Vimscript code is being
+/// processed and that “other Vimscript code” must not be affected.
///
/// @param[out] tstate Location where try state should be saved.
void try_enter(TryState *const tstate)
@@ -234,8 +236,7 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva
// Delete the key
if (di == NULL) {
// Doesn't exist, fail
- api_set_error(err, kErrorTypeValidation, "Key not found: %s",
- key.data);
+ api_set_error(err, kErrorTypeValidation, "Key not found: %s", key.data);
} else {
// Notify watchers
if (watched) {
@@ -264,13 +265,23 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva
di = tv_dict_item_alloc_len(key.data, key.size);
tv_dict_add(dict, di);
} else {
- if (watched) {
- tv_copy(&di->di_tv, &oldtv);
- }
// Return the old value
if (retval) {
rv = vim_to_object(&di->di_tv);
}
+ bool type_error = false;
+ if (dict == &vimvardict
+ && !before_set_vvar(key.data, di, &tv, true, watched, &type_error)) {
+ tv_clear(&tv);
+ if (type_error) {
+ api_set_error(err, kErrorTypeValidation,
+ "Setting v:%s to value with wrong type", key.data);
+ }
+ return rv;
+ }
+ if (watched) {
+ tv_copy(&di->di_tv, &oldtv);
+ }
tv_clear(&di->di_tv);
}
@@ -478,6 +489,27 @@ Array string_to_array(const String input, bool crlf)
return ret;
}
+/// Normalizes 0-based indexes to buffer line numbers.
+int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bool *oob)
+{
+ assert(buf->b_ml.ml_line_count > 0);
+ int64_t max_index = buf->b_ml.ml_line_count + (int)end_exclusive - 1;
+ // A negative index counts from the bottom.
+ index = index < 0 ? max_index + index + 1 : index;
+
+ // Check for oob and clamp.
+ if (index > max_index) {
+ *oob = true;
+ index = max_index;
+ } else if (index < 0) {
+ *oob = true;
+ index = 0;
+ }
+ // Convert the index to a 1-based line number.
+ index++;
+ return index;
+}
+
/// Returns a substring of a buffer line
///
/// @param buf Buffer handle
@@ -495,7 +527,7 @@ String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col
return rv;
}
- char *bufstr = ml_get_buf(buf, (linenr_T)lnum, false);
+ char *bufstr = ml_get_buf(buf, (linenr_T)lnum);
size_t line_length = strlen(bufstr);
start_col = start_col < 0 ? (int64_t)line_length + start_col + 1 : start_col;
@@ -577,9 +609,6 @@ void api_free_object(Object value)
case kObjectTypeLuaRef:
api_free_luaref(value.data.luaref);
break;
-
- default:
- abort();
}
}
@@ -660,10 +689,10 @@ static void init_ui_event_metadata(Dictionary *metadata)
msgpack_unpacked_destroy(&unpacked);
PUT(*metadata, "ui_events", ui_events);
Array ui_options = ARRAY_DICT_INIT;
- ADD(ui_options, STRING_OBJ(cstr_to_string("rgb")));
+ ADD(ui_options, CSTR_TO_OBJ("rgb"));
for (UIExtension i = 0; i < kUIExtCount; i++) {
if (ui_ext_names[i][0] != '_') {
- ADD(ui_options, STRING_OBJ(cstr_to_string(ui_ext_names[i])));
+ ADD(ui_options, CSTR_TO_OBJ(ui_ext_names[i]));
}
}
PUT(*metadata, "ui_options", ARRAY_OBJ(ui_options));
@@ -692,17 +721,17 @@ static void init_type_metadata(Dictionary *metadata)
Dictionary buffer_metadata = ARRAY_DICT_INIT;
PUT(buffer_metadata, "id",
INTEGER_OBJ(kObjectTypeBuffer - EXT_OBJECT_TYPE_SHIFT));
- PUT(buffer_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_buf_")));
+ PUT(buffer_metadata, "prefix", CSTR_TO_OBJ("nvim_buf_"));
Dictionary window_metadata = ARRAY_DICT_INIT;
PUT(window_metadata, "id",
INTEGER_OBJ(kObjectTypeWindow - EXT_OBJECT_TYPE_SHIFT));
- PUT(window_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_win_")));
+ PUT(window_metadata, "prefix", CSTR_TO_OBJ("nvim_win_"));
Dictionary tabpage_metadata = ARRAY_DICT_INIT;
PUT(tabpage_metadata, "id",
INTEGER_OBJ(kObjectTypeTabpage - EXT_OBJECT_TYPE_SHIFT));
- PUT(tabpage_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_tabpage_")));
+ PUT(tabpage_metadata, "prefix", CSTR_TO_OBJ("nvim_tabpage_"));
PUT(types, "Buffer", DICTIONARY_OBJ(buffer_metadata));
PUT(types, "Window", DICTIONARY_OBJ(window_metadata));
@@ -767,10 +796,8 @@ Object copy_object(Object obj, Arena *arena)
case kObjectTypeLuaRef:
return LUAREF_OBJ(api_new_luaref(obj.data.luaref));
-
- default:
- abort();
}
+ UNREACHABLE;
}
void api_set_error(Error *err, ErrorType errType, const char *format, ...)
@@ -806,7 +833,7 @@ bool api_object_to_bool(Object obj, const char *what, bool nil_value, Error *err
} else if (obj.type == kObjectTypeInteger) {
return obj.data.integer; // C semantics: non-zero int is true
} else if (obj.type == kObjectTypeNil) {
- return nil_value; // caller decides what NIL (missing retval in lua) means
+ return nil_value; // caller decides what NIL (missing retval in Lua) means
} else {
api_set_error(err, kErrorTypeValidation, "%s is not a boolean", what);
return false;
@@ -821,12 +848,40 @@ 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";
+ }
+ UNREACHABLE;
+}
+
HlMessage parse_hl_msg(Array chunks, Error *err)
{
HlMessage hl_msg = KV_INITIAL_VALUE;
@@ -865,17 +920,84 @@ free_exit:
return (HlMessage)KV_INITIAL_VALUE;
}
-bool api_dict_to_keydict(void *rv, field_hash hashy, Dictionary dict, Error *err)
+// see also nlua_pop_keydict for the lua specific implementation
+bool api_dict_to_keydict(void *retval, FieldHashfn hashy, Dictionary dict, Error *err)
{
for (size_t i = 0; i < dict.size; i++) {
String k = dict.items[i].key;
- Object *field = hashy(rv, k.data, k.size);
+ KeySetLink *field = hashy(k.data, k.size);
if (!field) {
api_set_error(err, kErrorTypeValidation, "Invalid key: '%.*s'", (int)k.size, k.data);
return false;
}
- *field = dict.items[i].value;
+ if (field->opt_index >= 0) {
+ OptKeySet *ks = (OptKeySet *)retval;
+ ks->is_set_ |= (1ULL << field->opt_index);
+ }
+
+ char *mem = ((char *)retval + field->ptr_off);
+ Object *value = &dict.items[i].value;
+ if (field->type == kObjectTypeNil) {
+ *(Object *)mem = *value;
+ } else if (field->type == kObjectTypeInteger) {
+ VALIDATE_T(field->str, kObjectTypeInteger, value->type, {
+ return false;
+ });
+ *(Integer *)mem = value->data.integer;
+ } else if (field->type == kObjectTypeFloat) {
+ Float *val = (Float *)mem;
+ if (value->type == kObjectTypeInteger) {
+ *val = (Float)value->data.integer;
+ } else {
+ VALIDATE_T(field->str, kObjectTypeFloat, value->type, {
+ return false;
+ });
+ *val = value->data.floating;
+ }
+ } else if (field->type == kObjectTypeBoolean) {
+ // caller should check HAS_KEY to override the nil behavior, or GET_BOOL_OR_TRUE
+ // to directly use true when nil
+ *(Boolean *)mem = api_object_to_bool(*value, field->str, false, err);
+ if (ERROR_SET(err)) {
+ return false;
+ }
+ } else if (field->type == kObjectTypeString) {
+ VALIDATE_T(field->str, kObjectTypeString, value->type, {
+ return false;
+ });
+ *(String *)mem = value->data.string;
+ } else if (field->type == kObjectTypeArray) {
+ VALIDATE_T(field->str, kObjectTypeArray, value->type, {
+ return false;
+ });
+ *(Array *)mem = value->data.array;
+ } else if (field->type == kObjectTypeDictionary) {
+ Dictionary *val = (Dictionary *)mem;
+ // allow empty array as empty dict for lua (directly or via lua-client RPC)
+ if (value->type == kObjectTypeArray && value->data.array.size == 0) {
+ *val = (Dictionary)ARRAY_DICT_INIT;
+ } else if (value->type == kObjectTypeDictionary) {
+ *val = value->data.dictionary;
+ } else {
+ api_err_exp(err, field->str, api_typename(field->type), api_typename(value->type));
+ return false;
+ }
+ } else if (field->type == kObjectTypeBuffer || field->type == kObjectTypeWindow
+ || field->type == kObjectTypeTabpage) {
+ if (value->type == kObjectTypeInteger || value->type == field->type) {
+ *(handle_T *)mem = (handle_T)value->data.integer;
+ } else {
+ api_err_exp(err, field->str, api_typename(field->type), api_typename(value->type));
+ return false;
+ }
+ } else if (field->type == kObjectTypeLuaRef) {
+ api_set_error(err, kErrorTypeValidation, "Invalid key: '%.*s' is only allowed from Lua",
+ (int)k.size, k.data);
+ return false;
+ } else {
+ abort();
+ }
}
return true;
@@ -884,7 +1006,18 @@ bool api_dict_to_keydict(void *rv, field_hash hashy, Dictionary dict, Error *err
void api_free_keydict(void *dict, KeySetLink *table)
{
for (size_t i = 0; table[i].str; i++) {
- api_free_object(*(Object *)((char *)dict + table[i].ptr_off));
+ char *mem = ((char *)dict + table[i].ptr_off);
+ if (table[i].type == kObjectTypeNil) {
+ api_free_object(*(Object *)mem);
+ } else if (table[i].type == kObjectTypeString) {
+ api_free_string(*(String *)mem);
+ } else if (table[i].type == kObjectTypeArray) {
+ api_free_array(*(Array *)mem);
+ } else if (table[i].type == kObjectTypeDictionary) {
+ api_free_dictionary(*(Dictionary *)mem);
+ } else if (table[i].type == kObjectTypeLuaRef) {
+ api_free_luaref(*(LuaRef *)mem);
+ }
}
}
@@ -930,12 +1063,14 @@ bool set_mark(buf_T *buf, String name, Integer line, Integer col, Error *err)
}
/// Get default statusline highlight for window
-const char *get_default_stl_hl(win_T *wp, bool use_winbar)
+const char *get_default_stl_hl(win_T *wp, bool use_winbar, int stc_hl_id)
{
if (wp == NULL) {
return "TabLineFill";
} else if (use_winbar) {
return (wp == curwin) ? "WinBar" : "WinBarNC";
+ } else if (stc_hl_id > 0) {
+ return syn_id2name(stc_hl_id);
} else {
return (wp == curwin) ? "StatusLine" : "StatusLineNC";
}
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index ec97ba9ec6..e61dd5f992 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -1,26 +1,28 @@
-#ifndef NVIM_API_PRIVATE_HELPERS_H
-#define NVIM_API_PRIVATE_HELPERS_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/decoration.h"
+#include "nvim/eval/typval_defs.h"
#include "nvim/ex_eval_defs.h"
#include "nvim/getchar.h"
+#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
-#include "nvim/map.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
#include "nvim/memory.h"
-#include "nvim/vim.h"
+#include "nvim/message.h"
#define OBJECT_OBJ(o) o
#define BOOLEAN_OBJ(b) ((Object) { \
.type = kObjectTypeBoolean, \
.data.boolean = b })
-#define BOOL(b) BOOLEAN_OBJ(b)
#define INTEGER_OBJ(i) ((Object) { \
.type = kObjectTypeInteger, \
@@ -34,6 +36,7 @@
.type = kObjectTypeString, \
.data.string = s })
+#define CSTR_AS_OBJ(s) STRING_OBJ(cstr_as_string(s))
#define CSTR_TO_OBJ(s) STRING_OBJ(cstr_to_string(s))
#define BUFFER_OBJ(s) ((Object) { \
@@ -63,8 +66,9 @@
#define NIL ((Object)OBJECT_INIT)
#define NULL_STRING ((String)STRING_INIT)
-// currently treat key=vim.NIL as if the key was missing
-#define HAS_KEY(o) ((o).type != kObjectTypeNil)
+#define HAS_KEY(d, typ, key) (((d)->is_set__##typ##_ & (1 << KEYSET_OPTIDX_##typ##__##key)) != 0)
+
+#define GET_BOOL_OR_TRUE(d, typ, key) (HAS_KEY(d, typ, key) ? (d)->key : true)
#define PUT(dict, k, v) \
kv_push(dict, ((KeyValuePair) { .key = cstr_to_string(k), .value = v }))
@@ -72,8 +76,6 @@
#define PUT_C(dict, k, v) \
kv_push_c(dict, ((KeyValuePair) { .key = cstr_as_string(k), .value = v }))
-#define PUT_BOOL(dict, name, condition) PUT(dict, name, BOOLEAN_OBJ(condition));
-
#define ADD(array, item) \
kv_push(array, item)
@@ -94,7 +96,7 @@
#define cbuf_as_string(d, s) ((String) { .data = d, .size = s })
-#define STATIC_CSTR_AS_STRING(s) ((String) { .data = s, .size = sizeof(s) - 1 })
+#define STATIC_CSTR_AS_STRING(s) ((String) { .data = s, .size = sizeof("" s) - 1 })
/// Create a new String instance, putting data in allocated memory
///
@@ -103,6 +105,9 @@
.data = xmemdupz(s, sizeof(s) - 1), \
.size = sizeof(s) - 1 })
+#define STATIC_CSTR_AS_OBJ(s) STRING_OBJ(STATIC_CSTR_AS_STRING(s))
+#define STATIC_CSTR_TO_OBJ(s) STRING_OBJ(STATIC_CSTR_TO_STRING(s))
+
// Helpers used by the generated msgpack-rpc api wrappers
#define api_init_boolean
#define api_init_integer
@@ -122,18 +127,18 @@
#define api_free_window(value)
#define api_free_tabpage(value)
-EXTERN PMap(handle_T) buffer_handles INIT(= MAP_INIT);
-EXTERN PMap(handle_T) window_handles INIT(= MAP_INIT);
-EXTERN PMap(handle_T) tabpage_handles INIT(= MAP_INIT);
+EXTERN PMap(int) buffer_handles INIT( = MAP_INIT);
+EXTERN PMap(int) window_handles INIT( = MAP_INIT);
+EXTERN PMap(int) tabpage_handles INIT( = MAP_INIT);
-#define handle_get_buffer(h) pmap_get(handle_T)(&buffer_handles, (h))
-#define handle_get_window(h) pmap_get(handle_T)(&window_handles, (h))
-#define handle_get_tabpage(h) pmap_get(handle_T)(&tabpage_handles, (h))
+#define handle_get_buffer(h) pmap_get(int)(&buffer_handles, (h))
+#define handle_get_window(h) pmap_get(int)(&window_handles, (h))
+#define handle_get_tabpage(h) pmap_get(int)(&tabpage_handles, (h))
/// Structure used for saving state for :try
///
-/// Used when caller is supposed to be operating when other VimL code is being
-/// processed and that “other VimL code” must not be affected.
+/// Used when caller is supposed to be operating when other Vimscript code is being
+/// processed and that “other Vimscript code” must not be affected.
typedef struct {
except_T *current_exception;
msglist_T *private_msg_list;
@@ -149,14 +154,26 @@ typedef struct {
// which would otherwise be ignored. This pattern is from do_cmdline().
//
// TODO(bfredl): prepare error-handling at "top level" (nv_event).
-#define TRY_WRAP(code) \
+#define TRY_WRAP(err, code) \
do { \
msglist_T **saved_msg_list = msg_list; \
msglist_T *private_msg_list; \
msg_list = &private_msg_list; \
private_msg_list = NULL; \
- code \
- msg_list = saved_msg_list; /* Restore the exception context. */ \
+ try_start(); \
+ code; \
+ try_end(err); \
+ msg_list = saved_msg_list; /* Restore the exception context. */ \
+ } while (0)
+
+// Execute code with cursor position saved and restored and textlock active.
+#define TEXTLOCK_WRAP(code) \
+ do { \
+ const pos_T save_cursor = curwin->w_cursor; \
+ textlock++; \
+ code; \
+ textlock--; \
+ curwin->w_cursor = save_cursor; \
} while (0)
// Useful macro for executing some `code` for each item in an array.
@@ -169,18 +186,17 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/private/helpers.h.generated.h"
-# include "keysets.h.generated.h"
#endif
#define WITH_SCRIPT_CONTEXT(channel_id, code) \
do { \
const sctx_T save_current_sctx = current_sctx; \
+ const uint64_t save_channel_id = current_channel_id; \
current_sctx.sc_sid = \
(channel_id) == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; \
current_sctx.sc_lnum = 0; \
current_channel_id = channel_id; \
code; \
+ current_channel_id = save_channel_id; \
current_sctx = save_current_sctx; \
} while (0);
-
-#endif // NVIM_API_PRIVATE_HELPERS_H
diff --git a/src/nvim/api/private/validate.c b/src/nvim/api/private/validate.c
new file mode 100644
index 0000000000..e198c671eb
--- /dev/null
+++ b/src/nvim/api/private/validate.c
@@ -0,0 +1,76 @@
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/globals.h"
+
+/// Creates "Invalid …" message and sets it on `err`.
+void api_err_invalid(Error *err, const char *name, const char *val_s, int64_t val_n, bool quote_val)
+{
+ ErrorType errtype = kErrorTypeValidation;
+ // Treat `name` without whitespace as a parameter (surround in quotes).
+ // Treat `name` with whitespace as a description (no quotes).
+ char *has_space = strchr(name, ' ');
+
+ // No value.
+ if (val_s && val_s[0] == '\0') {
+ api_set_error(err, errtype, has_space ? "Invalid %s" : "Invalid '%s'", name);
+ return;
+ }
+
+ // Number value.
+ if (val_s == NULL) {
+ api_set_error(err, errtype, has_space ? "Invalid %s: %" PRId64 : "Invalid '%s': %" PRId64,
+ name, val_n);
+ return;
+ }
+
+ // String value.
+ if (has_space) {
+ api_set_error(err, errtype, quote_val ? "Invalid %s: '%s'" : "Invalid %s: %s", name, val_s);
+ } else {
+ api_set_error(err, errtype, quote_val ? "Invalid '%s': '%s'" : "Invalid '%s': %s", name, val_s);
+ }
+}
+
+/// Creates "Invalid …: expected …" message and sets it on `err`.
+void api_err_exp(Error *err, const char *name, const char *expected, const char *actual)
+{
+ ErrorType errtype = kErrorTypeValidation;
+ // Treat `name` without whitespace as a parameter (surround in quotes).
+ // Treat `name` with whitespace as a description (no quotes).
+ char *has_space = strchr(name, ' ');
+
+ if (!actual) {
+ api_set_error(err, errtype,
+ has_space ? "Invalid %s: expected %s" : "Invalid '%s': expected %s",
+ name, expected);
+ return;
+ }
+
+ api_set_error(err, errtype,
+ has_space ? "Invalid %s: expected %s, got %s" : "Invalid '%s': expected %s, got %s",
+ name, expected, actual);
+}
+
+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..d1c977cd6e
--- /dev/null
+++ b/src/nvim/api/private/validate.h
@@ -0,0 +1,96 @@
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/assert_defs.h"
+#include "nvim/macros_defs.h"
+
+#define VALIDATE(cond, fmt_, fmt_arg1, code) \
+ do { \
+ if (!(cond)) { \
+ api_set_error(err, kErrorTypeValidation, fmt_, fmt_arg1); \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE_INT(cond, name, val_, code) \
+ do { \
+ if (!(cond)) { \
+ api_err_invalid(err, name, NULL, val_, false); \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE_S(cond, name, val_, code) \
+ do { \
+ if (!(cond)) { \
+ api_err_invalid(err, name, val_, 0, true); \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE_EXP(cond, name, expected, actual, code) \
+ do { \
+ if (!(cond)) { \
+ api_err_exp(err, name, expected, actual); \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE_T(name, expected_t, actual_t, code) \
+ do { \
+ STATIC_ASSERT(expected_t != kObjectTypeDictionary, "use VALIDATE_T_DICT"); \
+ if (expected_t != actual_t) { \
+ api_err_exp(err, name, api_typename(expected_t), api_typename(actual_t)); \
+ code; \
+ } \
+ } while (0)
+
+/// Checks that `obj_` has type `expected_t`.
+#define VALIDATE_T2(obj_, expected_t, code) \
+ do { \
+ STATIC_ASSERT(expected_t != kObjectTypeDictionary, "use VALIDATE_T_DICT"); \
+ if ((obj_).type != expected_t) { \
+ api_err_exp(err, STR(obj_), api_typename(expected_t), api_typename((obj_).type)); \
+ code; \
+ } \
+ } while (0)
+
+/// Checks that `obj_` has Dict type. Also allows empty Array in a Lua context.
+#define VALIDATE_T_DICT(name, obj_, code) \
+ do { \
+ if ((obj_).type != kObjectTypeDictionary \
+ && !(channel_id == LUA_INTERNAL_CALL \
+ && (obj_).type == kObjectTypeArray \
+ && (obj_).data.array.size == 0)) { \
+ api_err_exp(err, name, api_typename(kObjectTypeDictionary), api_typename((obj_).type)); \
+ code; \
+ } \
+ } while (0)
+
+/// Checks that actual_t is either the correct handle type or a type erased handle (integer)
+#define VALIDATE_T_HANDLE(name, expected_t, actual_t, code) \
+ do { \
+ if (expected_t != actual_t && kObjectTypeInteger != actual_t) { \
+ api_err_exp(err, name, api_typename(expected_t), api_typename(actual_t)); \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE_RANGE(cond, name, code) \
+ do { \
+ if (!(cond)) { \
+ api_err_invalid(err, name, "out of range", 0, false); \
+ 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
diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c
index 21eb326c3b..c854a22477 100644
--- a/src/nvim/api/tabpage.c
+++ b/src/nvim/api/tabpage.c
@@ -1,6 +1,3 @@
-// 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 <stdbool.h>
#include <stdlib.h>
@@ -9,6 +6,7 @@
#include "nvim/api/tabpage.h"
#include "nvim/api/vim.h"
#include "nvim/buffer_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/memory.h"
#include "nvim/window.h"
diff --git a/src/nvim/api/tabpage.h b/src/nvim/api/tabpage.h
index 2689cf6ae6..2f19845bd9 100644
--- a/src/nvim/api/tabpage.h
+++ b/src/nvim/api/tabpage.h
@@ -1,9 +1,7 @@
-#ifndef NVIM_API_TABPAGE_H
-#define NVIM_API_TABPAGE_H
+#pragma once
-#include "nvim/api/private/defs.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/tabpage.h.generated.h"
#endif
-#endif // NVIM_API_TABPAGE_H
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index e67607a7e4..836a68546c 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -1,10 +1,8 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
#include <msgpack/pack.h>
#include <stdbool.h>
+#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
@@ -12,25 +10,27 @@
#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"
+#include "nvim/eval.h"
#include "nvim/event/loop.h"
#include "nvim/event/wstream.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/highlight.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/msgpack_rpc/helpers.h"
#include "nvim/option.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
#define BUF_POS(data) ((size_t)((data)->buf_wptr - (data)->buf))
@@ -72,6 +72,11 @@ static void mpack_uint(char **buf, uint32_t val)
}
}
+static void mpack_bool(char **buf, bool val)
+{
+ mpack_w(buf, 0xc2 | (val ? 1 : 0));
+}
+
static void mpack_array(char **buf, uint32_t len)
{
if (len < 0x10) {
@@ -110,23 +115,32 @@ void remote_ui_disconnect(uint64_t channel_id)
}
UIData *data = ui->data;
kv_destroy(data->call_buf);
- pmap_del(uint64_t)(&connected_uis, channel_id);
+ pmap_del(uint64_t)(&connected_uis, channel_id, NULL);
ui_detach_impl(ui, channel_id);
+
+ // Destroy `ui`.
+ XFREE_CLEAR(ui->term_name);
xfree(ui);
}
-/// Wait until ui has connected on stdio channel.
-void remote_ui_wait_for_attach(void)
+/// Wait until ui has connected on stdio channel if only_stdio
+/// is true, otherwise any channel.
+void remote_ui_wait_for_attach(bool only_stdio)
{
- Channel *channel = find_channel(CHAN_STDIO);
- if (!channel) {
- // this function should only be called in --embed mode, stdio channel
- // can be assumed.
- abort();
- }
+ if (only_stdio) {
+ Channel *channel = find_channel(CHAN_STDIO);
+ if (!channel) {
+ // this function should only be called in --embed mode, stdio channel
+ // can be assumed.
+ abort();
+ }
- LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1,
- pmap_has(uint64_t)(&connected_uis, CHAN_STDIO));
+ LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1,
+ map_has(uint64_t, &connected_uis, CHAN_STDIO));
+ } else {
+ LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, -1,
+ ui_active());
+ }
}
/// Activates UI events on the channel.
@@ -148,7 +162,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
Error *err)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
- if (pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI already attached to channel: %" PRId64, channel_id);
return;
@@ -162,15 +176,9 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
UI *ui = xcalloc(1, sizeof(UI));
ui->width = (int)width;
ui->height = (int)height;
- ui->pum_nlines = 0;
- ui->pum_pos = false;
- ui->pum_width = 0.0;
- ui->pum_height = 0.0;
ui->pum_row = -1.0;
ui->pum_col = -1.0;
ui->rgb = true;
- ui->override = false;
-
CLEAR_FIELD(ui->ui_ext);
for (size_t i = 0; i < options.size; i++) {
@@ -202,6 +210,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
data->flushed_events = false;
data->ncalls_pos = NULL;
data->ncalls = 0;
+ data->ncells_pending = 0;
data->buf_wptr = data->buf;
data->temp_buf = NULL;
data->wildmenu_active = false;
@@ -210,6 +219,8 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
pmap_put(uint64_t)(&connected_uis, channel_id, ui);
ui_attach_impl(ui, channel_id);
+
+ may_trigger_vim_suspend_resume(false);
}
/// @deprecated
@@ -226,12 +237,16 @@ void ui_attach(uint64_t channel_id, Integer width, Integer height, Boolean enabl
void nvim_ui_set_focus(uint64_t channel_id, Boolean gained, Error *error)
FUNC_API_SINCE(11) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(error, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
}
+ if (gained) {
+ may_trigger_vim_suspend_resume(false);
+ }
+
do_autocmd_focusgained((bool)gained);
}
@@ -244,7 +259,7 @@ void nvim_ui_set_focus(uint64_t channel_id, Boolean gained, Error *error)
void nvim_ui_detach(uint64_t channel_id, Error *err)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -252,14 +267,15 @@ void nvim_ui_detach(uint64_t channel_id, Error *err)
remote_ui_disconnect(channel_id);
}
-// TODO(bfredl): use me to detach a specifc ui from the server
+// TODO(bfredl): use me to detach a specific ui from the server
void remote_ui_stop(UI *ui)
-{}
+{
+}
void nvim_ui_try_resize(uint64_t channel_id, Integer width, Integer height, Error *err)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -280,7 +296,7 @@ void nvim_ui_try_resize(uint64_t channel_id, Integer width, Integer height, Erro
void nvim_ui_set_option(uint64_t channel_id, String name, Object value, Error *error)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(error, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -290,22 +306,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.
@@ -316,63 +330,53 @@ 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));
+ ui->term_name = 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");
- return;
- }
- set_tty_background(value.data.string.data);
+ ui->term_colors = (int)value.data.integer;
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;
+ ui->stdin_tty = 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;
+ ui->stdout_tty = value.data.boolean;
return;
}
@@ -382,17 +386,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
+ // There shouldn't be a reason for a 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) {
@@ -402,8 +404,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
@@ -420,7 +421,7 @@ void nvim_ui_try_resize_grid(uint64_t channel_id, Integer grid, Integer width, I
Error *err)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -442,7 +443,7 @@ void nvim_ui_try_resize_grid(uint64_t channel_id, Integer grid, Integer width, I
void nvim_ui_pum_set_height(uint64_t channel_id, Integer height, Error *err)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -483,7 +484,7 @@ void nvim_ui_pum_set_bounds(uint64_t channel_id, Float width, Float height, Floa
Error *err)
FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -511,6 +512,33 @@ void nvim_ui_pum_set_bounds(uint64_t channel_id, Float width, Float height, Floa
ui->pum_pos = true;
}
+/// Tells Nvim when a terminal event has occurred
+///
+/// The following terminal events are supported:
+///
+/// - "termresponse": The terminal sent an OSC or DCS response sequence to
+/// Nvim. The payload is the received response. Sets
+/// |v:termresponse| and fires |TermResponse|.
+///
+/// @param channel_id
+/// @param event Event name
+/// @param payload Event payload
+/// @param[out] err Error details, if any.
+void nvim_ui_term_event(uint64_t channel_id, String event, Object value, Error *err)
+ FUNC_API_SINCE(12) FUNC_API_REMOTE_ONLY
+{
+ if (strequal("termresponse", event.data)) {
+ if (value.type != kObjectTypeString) {
+ api_set_error(err, kErrorTypeValidation, "termresponse must be a string");
+ return;
+ }
+
+ const String termresponse = value.data.string;
+ set_vim_var_string(VV_TERMRESPONSE, termresponse.data, (ptrdiff_t)termresponse.size);
+ apply_autocmds_group(EVENT_TERMRESPONSE, NULL, NULL, false, AUGROUP_ALL, NULL, NULL, &value);
+ }
+}
+
static void flush_event(UIData *data)
{
if (data->cur_event) {
@@ -644,6 +672,8 @@ void remote_ui_grid_resize(UI *ui, Integer grid, Integer width, Integer height)
Array args = data->call_buf;
if (ui->ui_ext[kUILinegrid]) {
ADD_C(args, INTEGER_OBJ(grid));
+ } else {
+ data->client_col = -1; // force cursor update
}
ADD_C(args, INTEGER_OBJ(width));
ADD_C(args, INTEGER_OBJ(height));
@@ -731,8 +761,8 @@ void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAttrs cte
ADD_C(args, INTEGER_OBJ(id));
MAXSIZE_TEMP_DICT(rgb, HLATTRS_DICT_SIZE);
MAXSIZE_TEMP_DICT(cterm, HLATTRS_DICT_SIZE);
- hlattrs2dict(&rgb, rgb_attrs, true);
- hlattrs2dict(&cterm, rgb_attrs, false);
+ hlattrs2dict(&rgb, NULL, rgb_attrs, true, false);
+ hlattrs2dict(&cterm, NULL, rgb_attrs, false, false);
ADD_C(args, DICTIONARY_OBJ(rgb));
ADD_C(args, DICTIONARY_OBJ(cterm));
@@ -755,7 +785,7 @@ void remote_ui_highlight_set(UI *ui, int id)
}
data->hl_id = id;
MAXSIZE_TEMP_DICT(dict, HLATTRS_DICT_SIZE);
- hlattrs2dict(&dict, syn_attr2entry(id), ui->rgb);
+ hlattrs2dict(&dict, NULL, syn_attr2entry(id), ui->rgb, false);
ADD_C(args, DICTIONARY_OBJ(dict));
push_call(ui, "highlight_set", args);
}
@@ -798,7 +828,7 @@ void remote_ui_put(UI *ui, const char *cell)
UIData *data = ui->data;
data->client_col++;
Array args = data->call_buf;
- ADD_C(args, STRING_OBJ(cstr_as_string((char *)cell)));
+ ADD_C(args, CSTR_AS_OBJ((char *)cell));
push_call(ui, "put", args);
}
@@ -812,7 +842,7 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int
data->ncalls++;
char **buf = &data->buf_wptr;
- mpack_array(buf, 4);
+ mpack_array(buf, 5);
mpack_uint(buf, (uint32_t)grid);
mpack_uint(buf, (uint32_t)row);
mpack_uint(buf, (uint32_t)startcol);
@@ -822,21 +852,24 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int
size_t ncells = (size_t)(endcol - startcol);
int last_hl = -1;
uint32_t nelem = 0;
+ bool was_space = false;
for (size_t i = 0; i < ncells; i++) {
repeat++;
- if (i == ncells - 1 || attrs[i] != attrs[i + 1]
- || strcmp(chunk[i], chunk[i + 1]) != 0) {
- if (UI_BUF_SIZE - BUF_POS(data) < 2 * (1 + 2 + sizeof(schar_T) + 5 + 5)) {
+ if (i == ncells - 1 || attrs[i] != attrs[i + 1] || chunk[i] != chunk[i + 1]) {
+ if (UI_BUF_SIZE - BUF_POS(data) < 2 * (1 + 2 + sizeof(schar_T) + 5 + 5) + 1) {
// close to overflowing the redraw buffer. finish this event,
// flush, and start a new "grid_line" event at the current position.
// For simplicity leave place for the final "clear" element
// as well, hence the factor of 2 in the check.
mpack_w2(&lenpos, nelem);
+
+ // We only ever set the wrap field on the final "grid_line" event for the line.
+ mpack_bool(buf, false);
remote_ui_flush_buf(ui);
prepare_call(ui, "grid_line");
data->ncalls++;
- mpack_array(buf, 4);
+ mpack_array(buf, 5);
mpack_uint(buf, (uint32_t)grid);
mpack_uint(buf, (uint32_t)row);
mpack_uint(buf, (uint32_t)startcol + (uint32_t)i - repeat + 1);
@@ -847,31 +880,46 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int
uint32_t csize = (repeat > 1) ? 3 : ((attrs[i] != last_hl) ? 2 : 1);
nelem++;
mpack_array(buf, csize);
- mpack_str(buf, (const char *)chunk[i]);
+ char sc_buf[MAX_SCHAR_SIZE];
+ schar_get(sc_buf, chunk[i]);
+ mpack_str(buf, sc_buf);
if (csize >= 2) {
mpack_uint(buf, (uint32_t)attrs[i]);
if (csize >= 3) {
mpack_uint(buf, repeat);
}
}
+ data->ncells_pending += MIN(repeat, 2);
last_hl = attrs[i];
repeat = 0;
+ was_space = chunk[i] == schar_from_ascii(' ');
}
}
- if (endcol < clearcol) {
+ // If the last chunk was all spaces, add a clearing chunk even if there are
+ // no more cells to clear, so there is no ambiguity about what to clear.
+ if (endcol < clearcol || was_space) {
nelem++;
+ data->ncells_pending += 1;
mpack_array(buf, 3);
mpack_str(buf, " ");
mpack_uint(buf, (uint32_t)clearattr);
mpack_uint(buf, (uint32_t)(clearcol - endcol));
}
mpack_w2(&lenpos, nelem);
+ mpack_bool(buf, flags & kLineFlagWrap);
+
+ if (data->ncells_pending > 500) {
+ // pass off cells to UI to let it start processing them
+ remote_ui_flush_buf(ui);
+ }
} else {
for (int i = 0; i < endcol - startcol; i++) {
remote_ui_cursor_goto(ui, row, startcol + i);
remote_ui_highlight_set(ui, attrs[i]);
- remote_ui_put(ui, (const char *)chunk[i]);
- if (utf_ambiguous_width(utf_ptr2char((char *)chunk[i]))) {
+ char sc_buf[MAX_SCHAR_SIZE];
+ schar_get(sc_buf, chunk[i]);
+ remote_ui_put(ui, sc_buf);
+ if (utf_ambiguous_width(utf_ptr2char(sc_buf))) {
data->client_col = -1; // force cursor update
}
}
@@ -917,6 +965,8 @@ void remote_ui_flush_buf(UI *ui)
// we have sent events to the client, but possibly not yet the final "flush"
// event.
data->flushed_events = true;
+
+ data->ncells_pending = 0;
}
/// An intentional flush (vsync) when Nvim is finished redrawing the screen
@@ -945,7 +995,7 @@ static Array translate_contents(UI *ui, Array contents, Arena *arena)
int attr = (int)item.items[0].data.integer;
if (attr) {
Dictionary rgb_attrs = arena_dict(arena, HLATTRS_DICT_SIZE);
- hlattrs2dict(&rgb_attrs, syn_attr2entry(attr), ui->rgb);
+ hlattrs2dict(&rgb_attrs, NULL, syn_attr2entry(attr), ui->rgb, false);
ADD(new_item, DICTIONARY_OBJ(rgb_attrs));
} else {
ADD(new_item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT));
diff --git a/src/nvim/api/ui.h b/src/nvim/api/ui.h
index b3fe0fa2bb..26a91d0dbc 100644
--- a/src/nvim/api/ui.h
+++ b/src/nvim/api/ui.h
@@ -1,14 +1,14 @@
-#ifndef NVIM_API_UI_H
-#define NVIM_API_UI_H
+#pragma once
-#include <stdint.h>
+#include <stdint.h> // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
-#include "nvim/map.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/highlight_defs.h" // IWYU pragma: keep
+#include "nvim/map_defs.h"
+#include "nvim/types_defs.h" // IWYU pragma: keep
#include "nvim/ui.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/ui.h.generated.h"
-# include "ui_events_remote.h.generated.h"
+# include "ui_events_remote.h.generated.h" // IWYU pragma: export
#endif
-#endif // NVIM_API_UI_H
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index a08e8dbfeb..bda0c72423 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_API_UI_EVENTS_IN_H
-#define NVIM_API_UI_EVENTS_IN_H
+#pragma once
// This file is not compiled, just parsed for definitions
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -82,7 +81,7 @@ void grid_clear(Integer grid)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL;
void grid_cursor_goto(Integer grid, Integer row, Integer col)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL;
-void grid_line(Integer grid, Integer row, Integer col_start, Array data)
+void grid_line(Integer grid, Integer row, Integer col_start, Array data, Boolean wrap)
FUNC_API_SINCE(5) FUNC_API_REMOTE_ONLY FUNC_API_CLIENT_IMPL;
void grid_scroll(Integer grid, Integer top, Integer bot, Integer left, Integer right, Integer rows,
Integer cols)
@@ -114,7 +113,7 @@ void msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep_char)
FUNC_API_SINCE(6) FUNC_API_COMPOSITOR_IMPL FUNC_API_CLIENT_IGNORE;
void win_viewport(Integer grid, Window win, Integer topline, Integer botline, Integer curline,
- Integer curcol, Integer line_count)
+ Integer curcol, Integer line_count, Integer scroll_delta)
FUNC_API_SINCE(7) FUNC_API_CLIENT_IGNORE;
void win_extmark(Integer grid, Window win, Integer ns_id, Integer mark_id, Integer row, Integer col)
@@ -167,4 +166,6 @@ void msg_history_show(Array entries)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void msg_history_clear(void)
FUNC_API_SINCE(10) FUNC_API_REMOTE_ONLY;
-#endif // NVIM_API_UI_EVENTS_IN_H
+
+void error_exit(Integer status)
+ FUNC_API_SINCE(12);
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index a53b30dd8a..d631b10af9 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -1,8 +1,6 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
+#include <lauxlib.h>
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
@@ -11,25 +9,29 @@
#include <string.h>
#include "klib/kvec.h"
-#include "lauxlib.h"
#include "nvim/api/buffer.h"
#include "nvim/api/deprecated.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/converter.h"
#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/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/channel.h"
#include "nvim/context.h"
+#include "nvim/cursor.h"
+#include "nvim/decoration.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
+#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
@@ -38,7 +40,7 @@
#include "nvim/keycodes.h"
#include "nvim/log.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
@@ -47,24 +49,24 @@
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/msgpack_rpc/unpacker.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/input.h"
-#include "nvim/os/os_defs.h"
#include "nvim/os/process.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/runtime.h"
+#include "nvim/sign.h"
#include "nvim/state.h"
#include "nvim/statusline.h"
#include "nvim/strings.h"
#include "nvim/terminal.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#define LINE_BUFFER_MIN_SIZE 4096
@@ -73,44 +75,6 @@
# include "api/vim.c.generated.h"
#endif
-/// Gets a highlight definition by name.
-///
-/// @param name Highlight group name
-/// @param rgb Export RGB colors
-/// @param[out] err Error details, if any
-/// @return Highlight definition map
-/// @see nvim_get_hl_by_id
-Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Arena *arena, Error *err)
- FUNC_API_SINCE(3)
-{
- 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);
- return result;
- }
- return nvim_get_hl_by_id(id, rgb, arena, err);
-}
-
-/// Gets a highlight definition by id. |hlID()|
-/// @param hl_id Highlight id as returned by |hlID()|
-/// @param rgb Export RGB colors
-/// @param[out] err Error details, if any
-/// @return Highlight definition map
-/// @see nvim_get_hl_by_name
-Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Arena *arena, Error *err)
- 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);
- return dic;
- }
- int attrcode = syn_id2attr((int)hl_id);
- return hl_get_attr_by_id(attrcode, rgb, arena, err);
-}
-
/// Gets a highlight group by name
///
/// similar to |hlID()|, but allocates a new ID if not present.
@@ -120,12 +84,26 @@ Integer nvim_get_hl_id_by_name(String name)
return syn_check_group(name.data, name.size);
}
-Dictionary nvim__get_hl_defs(Integer ns_id, Arena *arena, Error *err)
+/// Gets all or specific highlight groups in a namespace.
+///
+/// @note When the `link` attribute is defined in the highlight definition
+/// map, other attributes will not be taking effect (see |:hi-link|).
+///
+/// @param ns_id Get highlight groups for namespace ns_id |nvim_get_namespaces()|.
+/// Use 0 to get global highlight groups |:highlight|.
+/// @param opts Options dict:
+/// - name: (string) Get a highlight definition by name.
+/// - id: (integer) Get a highlight definition by id.
+/// - link: (boolean, default true) Show linked group name instead of effective definition |:hi-link|.
+/// - create: (boolean, default true) When highlight group doesn't exist create it.
+///
+/// @param[out] err Error details, if any.
+/// @return Highlight groups as a map from group name to a highlight definition map as in |nvim_set_hl()|,
+/// or only a single highlight definition map if requested by name or id.
+Dictionary nvim_get_hl(Integer ns_id, Dict(get_highlight) *opts, Arena *arena, Error *err)
+ FUNC_API_SINCE(11)
{
- if (ns_id == 0) {
- return get_global_hl_defs(arena);
- }
- abort();
+ return ns_get_hl_defs((NS)ns_id, opts, arena, err);
}
/// Sets a highlight group.
@@ -140,8 +118,14 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Arena *arena, Error *err)
/// values of the Normal group. If the Normal group has not been defined,
/// using these values results in an error.
///
+///
+/// @note If `link` is used in combination with other attributes; only the
+/// `link` will take effect (see |:hi-link|).
+///
/// @param ns_id Namespace id for this highlight |nvim_create_namespace()|.
/// Use 0 to set a highlight group globally |:highlight|.
+/// Highlights from non-global namespaces are not active by default, use
+/// |nvim_set_hl_ns()| or |nvim_win_set_hl_ns()| to activate them.
/// @param name Highlight group name, e.g. "ErrorMsg"
/// @param val Highlight definition map, accepts the following keys:
/// - fg (or foreground): color name or "#RRGGBB", see note.
@@ -166,6 +150,7 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Arena *arena, Error *err)
/// - cterm: cterm attribute map, like |highlight-args|. If not set,
/// cterm attributes will match those from the attribute map
/// documented above.
+/// - force: if true force update the highlight group when it exists.
/// @param[out] err Error details, if any
///
// TODO(bfredl): val should take update vs reset flag
@@ -173,10 +158,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);
@@ -185,25 +169,47 @@ void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err)
}
}
-/// Set active namespace for highlights. This can be set for a single window,
-/// see |nvim_win_set_hl_ns()|.
+/// Gets the active highlight namespace.
+///
+/// @param opts Optional parameters
+/// - winid: (number) |window-ID| for retrieving a window's highlight
+/// namespace. A value of -1 is returned when |nvim_win_set_hl_ns()|
+/// has not been called for the window (or was called with a namespace
+/// of -1).
+/// @param[out] err Error details, if any
+/// @return Namespace id, or -1
+Integer nvim_get_hl_ns(Dict(get_ns) *opts, Error *err)
+ FUNC_API_SINCE(12)
+{
+ if (HAS_KEY(opts, get_ns, winid)) {
+ win_T *win = find_window_by_handle(opts->winid, err);
+ if (!win) {
+ return 0;
+ }
+ return win->w_ns_hl;
+ } else {
+ return ns_hl_global;
+ }
+}
+
+/// Set active namespace for highlights defined with |nvim_set_hl()|. This can be set for
+/// a single window, see |nvim_win_set_hl_ns()|.
///
/// @param ns_id the namespace to use
/// @param[out] err Error details, if any
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();
redraw_all_later(UPD_NOT_VALID);
}
-/// Set active namespace for highlights while redrawing.
+/// Set active namespace for highlights defined with |nvim_set_hl()| while redrawing.
///
/// This function meant to be called while redrawing, primarily from
/// |nvim_set_decoration_provider()| on_win and on_line callbacks, which
@@ -229,10 +235,11 @@ void nvim_set_hl_ns_fast(Integer ns_id, Error *err)
/// nvim_feedkeys().
///
/// Example:
-/// <pre>vim
-/// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true)
-/// :call nvim_feedkeys(key, 'n', v:false)
-/// </pre>
+///
+/// ```vim
+/// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true)
+/// :call nvim_feedkeys(key, 'n', v:false)
+/// ```
///
/// @param keys to be typed
/// @param mode behavior flags, see |feedkeys()|
@@ -323,6 +330,7 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_ks)
Integer nvim_input(String keys)
FUNC_API_SINCE(1) FUNC_API_FAST
{
+ may_trigger_vim_suspend_resume(false);
return (Integer)input_enqueue(keys);
}
@@ -352,6 +360,8 @@ void nvim_input_mouse(String button, String action, String modifier, Integer gri
Integer col, Error *err)
FUNC_API_SINCE(6) FUNC_API_FAST
{
+ may_trigger_vim_suspend_resume(false);
+
if (button.data == NULL || action.data == NULL) {
goto error;
}
@@ -403,11 +413,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;
}
@@ -448,7 +456,7 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, Bool
}
char *ptr = NULL;
- replace_termcodes(str.data, str.size, &ptr, flags, NULL, CPO_TO_CPO_FLAGS);
+ replace_termcodes(str.data, str.size, &ptr, 0, flags, NULL, CPO_TO_CPO_FLAGS);
return cstr_as_string(ptr);
}
@@ -500,15 +508,14 @@ 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);
}
-/// Gets the paths contained in 'runtimepath'.
+/// Gets the paths contained in |runtime-search-path|.
///
/// @return List of paths
ArrayOf(String) nvim_list_runtime_paths(Error *err)
@@ -542,20 +549,23 @@ ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Error *err)
int flags = DIP_DIRFILE | (all ? DIP_ALL : 0);
- TRY_WRAP({
- try_start();
+ TRY_WRAP(err, {
do_in_runtimepath((name.size ? name.data : ""), flags, find_runtime_cb, &rv);
- try_end(err);
});
return rv;
}
-static void find_runtime_cb(char *fname, void *cookie)
+static bool find_runtime_cb(int num_fnames, char **fnames, bool all, void *cookie)
{
Array *rv = (Array *)cookie;
- if (fname != NULL) {
- ADD(*rv, STRING_OBJ(cstr_to_string((char *)fname)));
+ for (int i = 0; i < num_fnames; i++) {
+ ADD(*rv, CSTR_TO_OBJ(fnames[i]));
+ if (!all) {
+ return true;
+ }
}
+
+ return num_fnames > 0;
}
String nvim__get_lib_dir(void)
@@ -567,28 +577,24 @@ String nvim__get_lib_dir(void)
///
/// @param pat pattern of files to search for
/// @param all whether to return all matches or only the first
-/// @param opts is_lua: only search lua subdirs
+/// @param opts is_lua: only search Lua subdirs
/// @return list of absolute paths to the found files
ArrayOf(String) nvim__get_runtime(Array pat, Boolean all, Dict(runtime) *opts, Error *err)
FUNC_API_SINCE(8)
FUNC_API_FAST
{
- 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(!opts->do_source || nlua_is_deferred_safe(), "%s", "'do_source' used in fast callback",
+ {});
if (ERROR_SET(err)) {
return (Array)ARRAY_DICT_INIT;
}
- ArrayOf(String) res = runtime_get_named(is_lua, pat, all);
+ ArrayOf(String) res = runtime_get_named(opts->is_lua, pat, all);
- if (source) {
+ if (opts->do_source) {
for (size_t i = 0; i < res.size; i++) {
String name = res.items[i].data.string;
- (void)do_source(name.data, false, DOSO_NONE);
+ (void)do_source(name.data, false, DOSO_NONE, NULL);
}
}
@@ -602,10 +608,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);
@@ -639,7 +644,7 @@ String nvim_get_current_line(Error *err)
/// @param[out] err Error details, if any
void nvim_set_current_line(String line, Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
buffer_set_line(curbuf->handle, curwin->w_cursor.lnum - 1, line, err);
}
@@ -649,7 +654,7 @@ void nvim_set_current_line(String line, Error *err)
/// @param[out] err Error details, if any
void nvim_del_current_line(Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
buffer_del_line(curbuf->handle, curwin->w_cursor.lnum - 1, err);
}
@@ -664,16 +669,15 @@ 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);
+ bool found = script_autoload(name.data, name.size, false) && !aborting();
+ VALIDATE(found, "Key not found: %s", 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((di != NULL), "Key not found: %s", name.data, {
return (Object)OBJECT_INIT;
- }
+ });
return vim_to_object(&di->di_tv);
}
@@ -738,15 +742,13 @@ void nvim_echo(Array chunks, Boolean history, Dict(echo_opts) *opts, Error *err)
goto error;
}
- bool verbose = api_object_to_bool(opts->verbose, "verbose", false, err);
-
- if (verbose) {
+ if (opts->verbose) {
verbose_enter();
}
msg_multiattr(hl_msg, history ? "echomsg" : "echo", history);
- if (verbose) {
+ if (opts->verbose) {
verbose_leave();
verbose_stop(); // flush now
}
@@ -767,7 +769,7 @@ error:
void nvim_out_write(String str)
FUNC_API_SINCE(1)
{
- write_msg(str, false);
+ write_msg(str, false, false);
}
/// Writes a message to the Vim error buffer. Does not append "\n", the
@@ -777,7 +779,7 @@ void nvim_out_write(String str)
void nvim_err_write(String str)
FUNC_API_SINCE(1)
{
- write_msg(str, true);
+ write_msg(str, true, false);
}
/// Writes a message to the Vim error buffer. Appends "\n", so the buffer is
@@ -788,8 +790,7 @@ void nvim_err_write(String str)
void nvim_err_writeln(String str)
FUNC_API_SINCE(1)
{
- nvim_err_write(str);
- nvim_err_write((String) { .data = "\n", .size = 1 });
+ write_msg(str, true, true);
}
/// Gets the current list of buffer handles
@@ -832,7 +833,7 @@ Buffer nvim_get_current_buf(void)
/// @param[out] err Error details, if any
void nvim_set_current_buf(Buffer buffer, Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -887,7 +888,7 @@ Window nvim_get_current_win(void)
/// @param[out] err Error details, if any
void nvim_set_current_win(Window window, Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK
{
win_T *win = find_window_by_handle(window, err);
@@ -918,7 +919,7 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err)
FUNC_API_SINCE(6)
{
try_start();
- buf_T *buf = buflist_new(NULL, NULL, (linenr_T)0,
+ buf_T *buf = buflist_new(NULL, NULL, 0,
BLN_NOOPT | BLN_NEW | (listed ? BLN_LISTED : 0));
try_end(err);
if (buf == NULL) {
@@ -936,14 +937,23 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err)
goto fail;
}
+ // Set last_changedtick to avoid triggering a TextChanged autocommand right
+ // after it was added.
+ buf->b_last_changedtick = buf_get_changedtick(buf);
+ buf->b_last_changedtick_i = buf_get_changedtick(buf);
+ buf->b_last_changedtick_pum = buf_get_changedtick(buf);
+
+ // Only strictly needed for scratch, but could just as well be consistent
+ // and do this now. buffer is created NOW, not when it latter first happen
+ // to reach a window or aucmd_prepbuf() ..
+ buf_copy_options(buf, BCO_ENTER | BCO_NOHELP);
+
if (scratch) {
- aco_save_T aco;
- aucmd_prepbuf(&aco, buf);
- set_option_value("bufhidden", 0L, "hide", OPT_LOCAL);
- set_option_value("buftype", 0L, "nofile", OPT_LOCAL);
- set_option_value("swapfile", 0L, NULL, OPT_LOCAL);
- set_option_value("modeline", 0L, NULL, OPT_LOCAL); // 'nomodeline'
- aucmd_restbuf(&aco);
+ set_string_option_direct_in_buf(buf, "bufhidden", -1, "hide", OPT_LOCAL, 0);
+ set_string_option_direct_in_buf(buf, "buftype", -1, "nofile", OPT_LOCAL, 0);
+ assert(buf->b_ml.ml_mfp->mf_fd < 0); // ml_open() should not have opened swapfile already
+ buf->b_p_swf = false;
+ buf->b_p_ml = false;
}
return buf->b_fnum;
@@ -970,7 +980,7 @@ fail:
///
/// @param buffer the buffer to use (expected to be empty)
/// @param opts Optional parameters.
-/// - on_input: lua callback for input sent, i e keypresses in terminal
+/// - on_input: Lua callback for input sent, i e keypresses in terminal
/// mode. Note: keypresses are sent raw as they would be to the pty
/// master end. For instance, a carriage return is sent
/// as a "\r", not as a "\n". |textlock| applies. It is possible
@@ -980,27 +990,31 @@ fail:
/// @return Channel id, or 0 on error
Integer nvim_open_term(Buffer buffer, DictionaryOf(LuaRef) opts, Error *err)
FUNC_API_SINCE(7)
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
return 0;
}
+ if (cmdwin_type != 0 && buf == curbuf) {
+ api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
+ return 0;
+ }
+
LuaRef cb = LUA_NOREF;
for (size_t i = 0; i < opts.size; i++) {
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, "'opts' key", k.data, {});
}
}
@@ -1016,9 +1030,12 @@ Integer nvim_open_term(Buffer buffer, DictionaryOf(LuaRef) opts, Error *err)
topts.write_cb = term_write;
topts.resize_cb = term_resize;
topts.close_cb = term_close;
- Terminal *term = terminal_open(buf, topts);
- terminal_check_size(term);
- chan->term = term;
+ channel_incref(chan);
+ terminal_open(&chan->term, buf, topts);
+ if (chan->term != NULL) {
+ terminal_check_size(chan->term);
+ }
+ channel_decref(chan);
return (Integer)chan->id;
}
@@ -1040,7 +1057,7 @@ static void term_write(char *buf, size_t size, void *data) // NOLINT(readabilit
static void term_resize(uint16_t width, uint16_t height, void *data)
{
- // TODO(bfredl): lua callback
+ // TODO(bfredl): Lua callback
}
static void term_close(void *data)
@@ -1075,9 +1092,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.
@@ -1117,7 +1132,7 @@ Tabpage nvim_get_current_tabpage(void)
/// @param[out] err Error details, if any
void nvim_set_current_tabpage(Tabpage tabpage, Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK
{
tabpage_T *tp = find_tab_by_handle(tabpage, err);
@@ -1159,15 +1174,14 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err)
/// - false: Client must cancel the paste.
Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err)
FUNC_API_SINCE(6)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
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.
@@ -1231,23 +1245,20 @@ theend:
/// @param[out] err Error details, if any
void nvim_put(ArrayOf(String) lines, String type, Boolean after, Boolean follow, Error *err)
FUNC_API_SINCE(6)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
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);
@@ -1255,14 +1266,12 @@ void nvim_put(ArrayOf(String) lines, String type, Boolean after, Boolean follow,
finish_yankreg_from_object(reg, false);
- TRY_WRAP({
- try_start();
+ TRY_WRAP(err, {
bool VIsual_was_active = VIsual_active;
msg_silent++; // Avoid "N more lines" message.
do_put(0, reg, after ? FORWARD : BACKWARD, 1, follow ? PUT_CURSEND : 0);
msg_silent--;
VIsual_active = VIsual_was_active;
- try_end(err);
});
cleanup:
@@ -1291,9 +1300,9 @@ void nvim_subscribe(uint64_t channel_id, String event)
void nvim_unsubscribe(uint64_t channel_id, String event)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
- size_t length = (event.size < METHOD_MAXLEN ?
- event.size :
- METHOD_MAXLEN);
+ size_t length = (event.size < METHOD_MAXLEN
+ ? event.size
+ : METHOD_MAXLEN);
char e[METHOD_MAXLEN + 1];
memcpy(e, event.data, length);
e[length] = NUL;
@@ -1304,10 +1313,11 @@ void nvim_unsubscribe(uint64_t channel_id, String event)
/// "#rrggbb" hexadecimal string.
///
/// Example:
-/// <pre>vim
-/// :echo nvim_get_color_by_name("Pink")
-/// :echo nvim_get_color_by_name("#cbcbcb")
-/// </pre>
+///
+/// ```vim
+/// :echo nvim_get_color_by_name("Pink")
+/// :echo nvim_get_color_by_name("#cbcbcb")
+/// ```
///
/// @param name Color name or "#rrggbb" string
/// @return 24-bit RGB value, or -1 for invalid argument.
@@ -1348,11 +1358,8 @@ Dictionary nvim_get_context(Dict(context) *opts, Error *err)
FUNC_API_SINCE(6)
{
Array types = ARRAY_DICT_INIT;
- if (opts->types.type == kObjectTypeArray) {
- 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;
+ if (HAS_KEY(opts, context, types)) {
+ types = opts->types;
}
int int_types = types.size > 0 ? 0 : kCtxAll;
@@ -1373,8 +1380,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;
+ });
}
}
}
@@ -1390,7 +1398,7 @@ Dictionary nvim_get_context(Dict(context) *opts, Error *err)
/// Sets the current editor state from the given |context| map.
///
/// @param dict |Context| map.
-Object nvim_load_context(Dictionary dict)
+Object nvim_load_context(Dictionary dict, Error *err)
FUNC_API_SINCE(6)
{
Context ctx = CONTEXT_INIT;
@@ -1398,8 +1406,8 @@ Object nvim_load_context(Dictionary dict)
int save_did_emsg = did_emsg;
did_emsg = false;
- ctx_from_dict(dict, &ctx);
- if (!did_emsg) {
+ ctx_from_dict(dict, &ctx, err);
+ if (!ERROR_SET(err)) {
ctx_restore(&ctx, kCtxAll);
}
@@ -1421,7 +1429,7 @@ Dictionary nvim_get_mode(void)
get_mode(modestr);
bool blocked = input_blocking();
- PUT(rv, "mode", STRING_OBJ(cstr_to_string(modestr)));
+ PUT(rv, "mode", CSTR_TO_OBJ(modestr));
PUT(rv, "blocking", BOOLEAN_OBJ(blocked));
return rv;
@@ -1446,29 +1454,31 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode)
/// Empty {rhs} is |<Nop>|. |keycodes| are replaced as usual.
///
/// Example:
-/// <pre>vim
-/// call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true})
-/// </pre>
+///
+/// ```vim
+/// call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true})
+/// ```
///
/// is equivalent to:
-/// <pre>vim
-/// nmap <nowait> <Space><NL> <Nop>
-/// </pre>
+///
+/// ```vim
+/// nmap <nowait> <Space><NL> <Nop>
+/// ```
///
/// @param channel_id
/// @param mode Mode short-name (map command prefix: "n", "i", "v", "x", …)
/// or "!" for |:map!|, or empty string for |:map|.
+/// "ia", "ca" or "!a" for abbreviation in Insert mode, Cmdline mode, or both, respectively
/// @param lhs Left-hand-side |{lhs}| of the mapping.
/// @param rhs Right-hand-side |{rhs}| of the mapping.
-/// @param opts Optional parameters map: keys are |:map-arguments|, values are booleans (default
-/// false). Accepts all |:map-arguments| as keys excluding |<buffer>| but including
-/// |:noremap| and "desc". Unknown key is an error.
-/// "desc" can be used to give a description to the mapping.
-/// When called from Lua, also accepts a "callback" key that takes a Lua function to
-/// call when the mapping is executed.
-/// When "expr" is true, "replace_keycodes" (boolean) can be used to replace keycodes
-/// in the resulting string (see |nvim_replace_termcodes()|), and a Lua callback
-/// returning `nil` is equivalent to returning an empty string.
+/// @param opts Optional parameters map: Accepts all |:map-arguments| as keys except |<buffer>|,
+/// values are booleans (default false). Also:
+/// - "noremap" disables |recursive_mapping|, like |:noremap|
+/// - "desc" human-readable description.
+/// - "callback" Lua function called in place of {rhs}.
+/// - "replace_keycodes" (boolean) When "expr" is true, replace keycodes in the
+/// resulting string (see |nvim_replace_termcodes()|). Returning nil from the Lua
+/// "callback" is equivalent to returning an empty string.
/// @param[out] err Error details, if any.
void nvim_set_keymap(uint64_t channel_id, String mode, String lhs, String rhs, Dict(keymap) *opts,
Error *err)
@@ -1527,7 +1537,10 @@ Array nvim_get_api_info(uint64_t channel_id, Arena *arena)
/// - "commit" hash or similar identifier of commit
/// @param type Must be one of the following values. Client libraries should
/// default to "remote" unless overridden by the user.
-/// - "remote" remote client connected to Nvim.
+/// - "remote" remote client connected "Nvim flavored" MessagePack-RPC (responses
+/// must be in reverse order of requests). |msgpack-rpc|
+/// - "msgpack-rpc" remote client connected to Nvim via fully MessagePack-RPC
+/// compliant protocol.
/// - "ui" gui frontend
/// - "embedder" application using Nvim as a component (for example,
/// IDE/editor implementing a vim mode).
@@ -1651,34 +1664,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_EXP((call.size == 2), "'calls' item", "2-item Array", NULL, {
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("call args", kObjectTypeArray, call.items[1].type, {
goto theend;
- }
+ });
Array args = call.items[1].data.array;
MsgpackRpcRequestHandler handler =
@@ -1726,34 +1725,44 @@ theend:
///
/// @param message Message to write
/// @param to_err true: message is an error (uses `emsg` instead of `msg`)
-static void write_msg(String message, bool to_err)
+/// @param writeln Append a trailing newline
+static void write_msg(String message, bool to_err, bool writeln)
{
static StringBuilder out_line_buf = KV_INITIAL_VALUE;
static StringBuilder err_line_buf = KV_INITIAL_VALUE;
+ StringBuilder *line_buf = to_err ? &err_line_buf : &out_line_buf;
-#define PUSH_CHAR(i, line_buf, msg) \
- if (kv_max(line_buf) == 0) { \
- kv_resize(line_buf, LINE_BUFFER_MIN_SIZE); \
+#define PUSH_CHAR(c) \
+ if (kv_max(*line_buf) == 0) { \
+ kv_resize(*line_buf, LINE_BUFFER_MIN_SIZE); \
} \
- if (message.data[i] == NL) { \
- kv_push(line_buf, NUL); \
- msg(line_buf.items); \
- kv_drop(line_buf, kv_size(line_buf)); \
- kv_resize(line_buf, LINE_BUFFER_MIN_SIZE); \
- continue; \
- } \
- kv_push(line_buf, message.data[i]);
+ if (c == NL) { \
+ kv_push(*line_buf, NUL); \
+ if (to_err) { \
+ emsg(line_buf->items); \
+ } else { \
+ msg(line_buf->items, 0); \
+ } \
+ if (msg_silent == 0) { \
+ msg_didout = true; \
+ } \
+ kv_drop(*line_buf, kv_size(*line_buf)); \
+ kv_resize(*line_buf, LINE_BUFFER_MIN_SIZE); \
+ } else if (c == NUL) { \
+ kv_push(*line_buf, NL); \
+ } else { \
+ kv_push(*line_buf, c); \
+ }
no_wait_return++;
for (uint32_t i = 0; i < message.size; i++) {
if (got_int) {
break;
}
- if (to_err) {
- PUSH_CHAR(i, err_line_buf, emsg);
- } else {
- PUSH_CHAR(i, out_line_buf, msg);
- }
+ PUSH_CHAR(message.data[i]);
+ }
+ if (writeln) {
+ PUSH_CHAR(NL);
}
no_wait_return--;
msg_end();
@@ -1850,10 +1859,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 +1900,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 +1945,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 +1968,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
@@ -1976,7 +1980,9 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Arena *arena, E
}
ret = arena_array(arena, 3);
size_t off = g->line_offset[(size_t)row] + (size_t)col;
- ADD_C(ret, STRING_OBJ(cstr_as_string((char *)g->chars[off])));
+ char *sc_buf = arena_alloc(arena, MAX_SCHAR_SIZE, false);
+ schar_get(sc_buf, g->chars[off]);
+ ADD_C(ret, CSTR_AS_OBJ(sc_buf));
int attr = g->attrs[off];
ADD_C(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, arena, err)));
// will not work first time
@@ -1992,6 +1998,14 @@ void nvim__screenshot(String path)
ui_call_screenshot(path);
}
+/// For testing. The condition in schar_cache_clear_if_full is hard to
+/// reach, so this function can be used to force a cache clear in a test.
+void nvim__invalidate_glyph_cache(void)
+{
+ schar_cache_clear();
+ must_redraw = UPD_CLEAR;
+}
+
Object nvim__unpack(String str, Error *err)
FUNC_API_FAST
{
@@ -2000,7 +2014,7 @@ Object nvim__unpack(String str, Error *err)
/// Deletes an uppercase/file named mark. See |mark-motions|.
///
-/// @note fails with error if a lowercase or buffer local named mark is used.
+/// @note Lowercase name (or other buffer-local mark) is an error.
/// @param name Mark name
/// @return true if the mark was deleted, else false.
/// @see |nvim_buf_del_mark()|
@@ -2009,29 +2023,26 @@ 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;
}
-/// Return a tuple (row, col, buffer, buffername) representing the position of
-/// the uppercase/file named mark. See |mark-motions|.
+/// Returns a `(row, col, buffer, buffername)` tuple representing the position
+/// of the uppercase/file named mark. "End of line" column position is returned
+/// as |v:maxcol| (big number). See |mark-motions|.
///
/// Marks are (1,0)-indexed. |api-indexing|
///
-/// @note fails with error if a lowercase or buffer local named mark is used.
+/// @note Lowercase name (or other buffer-local mark) is an error.
/// @param name Mark name
/// @param opts Optional parameters. Reserved for future use.
/// @return 4-tuple (row, col, buffer, buffername), (0, 0, 0, '') if the mark is
@@ -2043,16 +2054,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;
@@ -2092,7 +2100,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err)
ADD(rv, INTEGER_OBJ(row));
ADD(rv, INTEGER_OBJ(col));
ADD(rv, INTEGER_OBJ(bufnr));
- ADD(rv, STRING_OBJ(cstr_to_string(filename)));
+ ADD(rv, CSTR_TO_OBJ(filename));
if (allocated) {
xfree(filename);
@@ -2113,6 +2121,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err)
/// - use_winbar: (boolean) Evaluate winbar instead of statusline.
/// - use_tabline: (boolean) Evaluate tabline instead of statusline. When true, {winid}
/// is ignored. Mutually exclusive with {use_winbar}.
+/// - use_statuscol_lnum: (number) Evaluate statuscolumn for this line number instead of statusline.
///
/// @param[out] err Error details, if any.
/// @return Dictionary containing statusline information, with these keys:
@@ -2130,106 +2139,117 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
int maxwidth;
int fillchar = 0;
+ int statuscol_lnum = 0;
Window window = 0;
- bool use_winbar = false;
- bool use_tabline = false;
- bool highlights = false;
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");
- return result;
- }
-
- window = (Window)opts->winid.data.integer;
+ if (HAS_KEY(opts, eval_statusline, winid)) {
+ window = opts->winid;
}
- 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");
+ if (HAS_KEY(opts, eval_statusline, fillchar)) {
+ VALIDATE_EXP((*opts->fillchar.data != 0
+ && ((size_t)utf_ptr2len(opts->fillchar.data) == opts->fillchar.size)),
+ "fillchar", "single character", NULL, {
return result;
- }
- fillchar = utf_ptr2char(opts->fillchar.data.string.data);
+ });
+ fillchar = utf_ptr2char(opts->fillchar.data);
}
- if (HAS_KEY(opts->highlights)) {
- highlights = api_object_to_bool(opts->highlights, "highlights", false, err);
- if (ERROR_SET(err)) {
- return result;
- }
- }
- if (HAS_KEY(opts->use_winbar)) {
- use_winbar = api_object_to_bool(opts->use_winbar, "use_winbar", false, err);
+ int use_bools = (int)opts->use_winbar + (int)opts->use_tabline;
- if (ERROR_SET(err)) {
- return result;
- }
+ win_T *wp = opts->use_tabline ? curwin : find_window_by_handle(window, err);
+ if (wp == NULL) {
+ api_set_error(err, kErrorTypeException, "unknown winid %d", window);
+ return result;
}
- if (HAS_KEY(opts->use_tabline)) {
- use_tabline = api_object_to_bool(opts->use_tabline, "use_tabline", false, err);
- if (ERROR_SET(err)) {
+ if (HAS_KEY(opts, eval_statusline, use_statuscol_lnum)) {
+ statuscol_lnum = (int)opts->use_statuscol_lnum;
+ VALIDATE_RANGE(statuscol_lnum > 0 && statuscol_lnum <= wp->w_buffer->b_ml.ml_line_count,
+ "use_statuscol_lnum", {
return result;
- }
+ });
+ use_bools++;
}
- if (use_winbar && use_tabline) {
- api_set_error(err, kErrorTypeValidation, "use_winbar and use_tabline are mutually exclusive");
+ VALIDATE(use_bools <= 1, "%s",
+ "Can only use one of 'use_winbar', 'use_tabline' and 'use_statuscol_lnum'", {
return result;
- }
+ });
- win_T *wp, *ewp;
+ int stc_hl_id = 0;
+ statuscol_T statuscol = { 0 };
+ SignTextAttrs sattrs[SIGN_SHOW_MAX] = { 0 };
- if (use_tabline) {
- wp = NULL;
- ewp = curwin;
+ if (opts->use_tabline) {
fillchar = ' ';
} else {
- wp = find_window_by_handle(window, err);
- if (wp == NULL) {
- api_set_error(err, kErrorTypeException, "unknown winid %d", window);
- return result;
- }
- ewp = wp;
-
if (fillchar == 0) {
- if (use_winbar) {
+ if (opts->use_winbar) {
fillchar = wp->w_p_fcs_chars.wbr;
} else {
int attr;
fillchar = fillchar_status(&attr, wp);
}
}
- }
+ if (statuscol_lnum) {
+ int line_id = 0;
+ int cul_id = 0;
+ int num_id = 0;
+ linenr_T lnum = statuscol_lnum;
+ wp->w_scwidth = win_signcol_count(wp);
+ decor_redraw_signs(wp, wp->w_buffer, lnum - 1, sattrs, &line_id, &cul_id, &num_id);
+
+ statuscol.sattrs = sattrs;
+ statuscol.foldinfo = fold_info(wp, lnum);
+ wp->w_cursorline = win_cursorline_standout(wp) ? wp->w_cursor.lnum : 0;
+
+ if (wp->w_p_cul) {
+ if (statuscol.foldinfo.fi_level != 0 && statuscol.foldinfo.fi_lines > 0) {
+ wp->w_cursorline = statuscol.foldinfo.fi_lnum;
+ }
+ statuscol.use_cul = lnum == wp->w_cursorline && (wp->w_p_culopt_flags & CULOPT_NBR);
+ }
- if (HAS_KEY(opts->maxwidth)) {
- if (opts->maxwidth.type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation, "maxwidth must be an integer");
- return result;
+ statuscol.sign_cul_id = statuscol.use_cul ? cul_id : 0;
+ if (num_id) {
+ stc_hl_id = num_id;
+ } else if (statuscol.use_cul) {
+ stc_hl_id = HLF_CLN + 1;
+ } else if (wp->w_p_rnu) {
+ stc_hl_id = (lnum < wp->w_cursor.lnum ? HLF_LNA : HLF_LNB) + 1;
+ } else {
+ stc_hl_id = HLF_N + 1;
+ }
+
+ set_vim_var_nr(VV_LNUM, lnum);
+ set_vim_var_nr(VV_RELNUM, labs(get_cursor_rel_lnum(wp, lnum)));
+ set_vim_var_nr(VV_VIRTNUM, 0);
}
+ }
- maxwidth = (int)opts->maxwidth.data.integer;
+ if (HAS_KEY(opts, eval_statusline, maxwidth)) {
+ maxwidth = (int)opts->maxwidth;
} else {
- maxwidth = (use_tabline || (!use_winbar && global_stl_height() > 0)) ? Columns : wp->w_width;
+ maxwidth = statuscol_lnum ? win_col_off(wp)
+ : (opts->use_tabline
+ || (!opts->use_winbar
+ && global_stl_height() > 0)) ? Columns : wp->w_width;
}
char buf[MAXPATHL];
stl_hlrec_t *hltab;
- stl_hlrec_t **hltab_ptr = highlights ? &hltab : NULL;
// Temporarily reset 'cursorbind' to prevent side effects from moving the cursor away and back.
- int p_crb_save = ewp->w_p_crb;
- ewp->w_p_crb = false;
+ int p_crb_save = wp->w_p_crb;
+ wp->w_p_crb = false;
- int width = build_stl_str_hl(ewp,
+ int width = build_stl_str_hl(wp,
buf,
sizeof(buf),
str.data,
@@ -2237,25 +2257,25 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
0,
fillchar,
maxwidth,
- hltab_ptr,
+ opts->highlights ? &hltab : NULL,
NULL,
- NULL);
+ statuscol_lnum ? &statuscol : NULL);
PUT(result, "width", INTEGER_OBJ(width));
// Restore original value of 'cursorbind'
- ewp->w_p_crb = p_crb_save;
+ wp->w_p_crb = p_crb_save;
- if (highlights) {
+ if (opts->highlights) {
Array hl_values = ARRAY_DICT_INIT;
const char *grpname;
- char user_group[6];
+ char user_group[15]; // strlen("User") + strlen("2147483647") + NUL
// If first character doesn't have a defined highlight,
// add the default highlight at the beginning of the highlight list
if (hltab->start == NULL || (hltab->start - buf) != 0) {
Dictionary hl_info = ARRAY_DICT_INIT;
- grpname = get_default_stl_hl(wp, use_winbar);
+ grpname = get_default_stl_hl(opts->use_tabline ? NULL : wp, opts->use_winbar, stc_hl_id);
PUT(hl_info, "start", INTEGER_OBJ(0));
PUT(hl_info, "group", CSTR_TO_OBJ(grpname));
@@ -2266,10 +2286,10 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
for (stl_hlrec_t *sp = hltab; sp->start != NULL; sp++) {
Dictionary hl_info = ARRAY_DICT_INIT;
- PUT(hl_info, "start", INTEGER_OBJ((char *)sp->start - buf));
+ PUT(hl_info, "start", INTEGER_OBJ(sp->start - buf));
if (sp->userhl == 0) {
- grpname = get_default_stl_hl(wp, use_winbar);
+ grpname = get_default_stl_hl(opts->use_tabline ? NULL : wp, opts->use_winbar, stc_hl_id);
} else if (sp->userhl < 0) {
grpname = syn_id2name(-sp->userhl);
} else {
@@ -2281,7 +2301,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
}
PUT(result, "highlights", ARRAY_OBJ(hl_values));
}
- PUT(result, "str", CSTR_TO_OBJ((char *)buf));
+ PUT(result, "str", CSTR_TO_OBJ(buf));
return result;
}
diff --git a/src/nvim/api/vim.h b/src/nvim/api/vim.h
index de56c67665..b620158751 100644
--- a/src/nvim/api/vim.h
+++ b/src/nvim/api/vim.h
@@ -1,9 +1,10 @@
-#ifndef NVIM_API_VIM_H
-#define NVIM_API_VIM_H
+#pragma once
-#include "nvim/api/private/defs.h"
+#include <stdint.h> // IWYU pragma: keep
+
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/vim.h.generated.h"
#endif
-#endif // NVIM_API_VIM_H
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index af1b23b712..c75bf21572 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
@@ -8,23 +5,22 @@
#include <string.h>
#include "klib/kvec.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/vimscript.h"
-#include "nvim/ascii.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/ascii_defs.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/ex_docmd.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/globals.h"
#include "nvim/memory.h"
-#include "nvim/pos.h"
#include "nvim/runtime.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/viml/parser/expressions.h"
#include "nvim/viml/parser/parser.h"
@@ -38,39 +34,57 @@
/// Unlike |nvim_command()| this function supports heredocs, script-scope (s:),
/// etc.
///
-/// On execution error: fails with VimL error, updates v:errmsg.
+/// On execution error: fails with Vimscript error, updates v:errmsg.
///
/// @see |execute()|
/// @see |nvim_command()|
/// @see |nvim_cmd()|
///
/// @param src Vimscript code
-/// @param output Capture and return all (non-error, non-shell |:!|) output
+/// @param opts Optional parameters.
+/// - output: (boolean, default false) Whether to capture and return
+/// all (non-error, non-shell |:!|) output.
/// @param[out] err Error details (Vim error), if any
-/// @return Output (non-error, non-shell |:!|) if `output` is true,
-/// else empty string.
-String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err)
- FUNC_API_SINCE(7)
+/// @return Dictionary containing information about execution, with these keys:
+/// - output: (string|nil) Output if `opts.output` is true.
+Dictionary nvim_exec2(uint64_t channel_id, String src, Dict(exec_opts) *opts, Error *err)
+ FUNC_API_SINCE(11)
+{
+ Dictionary result = ARRAY_DICT_INIT;
+
+ String output = exec_impl(channel_id, src, opts, err);
+ if (ERROR_SET(err)) {
+ return result;
+ }
+
+ if (opts->output) {
+ PUT(result, "output", STRING_OBJ(output));
+ }
+
+ return result;
+}
+
+String exec_impl(uint64_t channel_id, String src, Dict(exec_opts) *opts, Error *err)
{
const int save_msg_silent = msg_silent;
garray_T *const save_capture_ga = capture_ga;
const int save_msg_col = msg_col;
garray_T capture_local;
- if (output) {
+ if (opts->output) {
ga_init(&capture_local, 1, 80);
capture_ga = &capture_local;
}
try_start();
- if (output) {
+ if (opts->output) {
msg_silent++;
msg_col = 0; // prevent leading spaces
}
const sctx_T save_current_sctx = api_set_sctx(channel_id);
- do_source_str(src.data, "nvim_exec()");
- if (output) {
+ do_source_str(src.data, "nvim_exec2()");
+ if (opts->output) {
capture_ga = save_capture_ga;
msg_silent = save_msg_silent;
// Put msg_col back where it was, since nothing should have been written.
@@ -84,7 +98,7 @@ String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err)
goto theend;
}
- if (output && capture_local.ga_len > 1) {
+ if (opts->output && capture_local.ga_len > 1) {
String s = (String){
.data = capture_local.ga_data,
.size = (size_t)capture_local.ga_len,
@@ -98,7 +112,7 @@ String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err)
return s; // Caller will free the memory.
}
theend:
- if (output) {
+ if (opts->output) {
ga_clear(&capture_local);
}
return (String)STRING_INIT;
@@ -106,10 +120,10 @@ theend:
/// Executes an Ex command.
///
-/// On execution error: fails with VimL error, updates v:errmsg.
+/// On execution error: fails with Vimscript error, updates v:errmsg.
///
-/// Prefer using |nvim_cmd()| or |nvim_exec()| over this. To evaluate multiple lines of Vim script
-/// or an Ex command directly, use |nvim_exec()|. To construct an Ex command using a structured
+/// Prefer using |nvim_cmd()| or |nvim_exec2()| over this. To evaluate multiple lines of Vim script
+/// or an Ex command directly, use |nvim_exec2()|. To construct an Ex command using a structured
/// format and then execute it, use |nvim_cmd()|. To modify an Ex command before evaluating it, use
/// |nvim_parse_cmd()| in conjunction with |nvim_cmd()|.
///
@@ -123,12 +137,12 @@ void nvim_command(String command, Error *err)
try_end(err);
}
-/// Evaluates a VimL |expression|.
+/// Evaluates a Vimscript |expression|.
/// Dictionaries and Lists are recursively expanded.
///
-/// On execution error: fails with VimL error, updates v:errmsg.
+/// On execution error: fails with Vimscript error, updates v:errmsg.
///
-/// @param expr VimL expression string
+/// @param expr Vimscript expression string
/// @param[out] err Error details, if any
/// @return Evaluation result or expanded object
Object nvim_eval(String expr, Error *err)
@@ -137,39 +151,42 @@ Object nvim_eval(String expr, Error *err)
static int recursive = 0; // recursion depth
Object rv = OBJECT_INIT;
- TRY_WRAP({
- // Initialize `force_abort` and `suppress_errthrow` at the top level.
- if (!recursive) {
- force_abort = false;
- suppress_errthrow = false;
- did_throw = false;
- // `did_emsg` is set by emsg(), which cancels execution.
- did_emsg = false;
- }
- recursive++;
- try_start();
+ // Initialize `force_abort` and `suppress_errthrow` at the top level.
+ if (!recursive) {
+ force_abort = false;
+ suppress_errthrow = false;
+ did_throw = false;
+ // `did_emsg` is set by emsg(), which cancels execution.
+ did_emsg = false;
+ }
- typval_T rettv;
- int ok = eval0(expr.data, &rettv, NULL, true);
+ recursive++;
- if (!try_end(err)) {
- if (ok == FAIL) {
- // Should never happen, try_end() should get the error. #8371
- api_set_error(err, kErrorTypeException,
- "Failed to evaluate expression: '%.*s'", 256, expr.data);
- } else {
- rv = vim_to_object(&rettv);
- }
- }
+ typval_T rettv;
+ int ok;
- tv_clear(&rettv);
- recursive--;
+ TRY_WRAP(err, {
+ ok = eval0(expr.data, &rettv, NULL, &EVALARG_EVALUATE);
+ clear_evalarg(&EVALARG_EVALUATE, NULL);
});
+ if (!ERROR_SET(err)) {
+ if (ok == FAIL) {
+ // Should never happen, try_end() (in TRY_WRAP) should get the error. #8371
+ api_set_error(err, kErrorTypeException,
+ "Failed to evaluate expression: '%.*s'", 256, expr.data);
+ } else {
+ rv = vim_to_object(&rettv);
+ }
+ }
+
+ tv_clear(&rettv);
+ recursive--;
+
return rv;
}
-/// Calls a VimL function.
+/// Calls a Vimscript function.
///
/// @param fn Function name
/// @param args Function arguments
@@ -196,34 +213,37 @@ static Object _call_function(String fn, Array args, dict_T *self, Error *err)
}
}
- TRY_WRAP({
- // Initialize `force_abort` and `suppress_errthrow` at the top level.
- if (!recursive) {
- force_abort = false;
- suppress_errthrow = false;
- did_throw = false;
- // `did_emsg` is set by emsg(), which cancels execution.
- did_emsg = false;
- }
- recursive++;
- try_start();
- typval_T rettv;
- funcexe_T funcexe = FUNCEXE_INIT;
- funcexe.fe_firstline = curwin->w_cursor.lnum;
- funcexe.fe_lastline = curwin->w_cursor.lnum;
- funcexe.fe_evaluate = true;
- funcexe.fe_selfdict = self;
+ // Initialize `force_abort` and `suppress_errthrow` at the top level.
+ if (!recursive) {
+ force_abort = false;
+ suppress_errthrow = false;
+ did_throw = false;
+ // `did_emsg` is set by emsg(), which cancels execution.
+ did_emsg = false;
+ }
+ recursive++;
+
+ typval_T rettv;
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.fe_firstline = curwin->w_cursor.lnum;
+ funcexe.fe_lastline = curwin->w_cursor.lnum;
+ funcexe.fe_evaluate = true;
+ funcexe.fe_selfdict = self;
+
+ TRY_WRAP(err, {
// call_func() retval is deceptive, ignore it. Instead we set `msg_list`
// (see above) to capture abort-causing non-exception errors.
(void)call_func(fn.data, (int)fn.size, &rettv, (int)args.size,
vim_args, &funcexe);
- if (!try_end(err)) {
- rv = vim_to_object(&rettv);
- }
- tv_clear(&rettv);
- recursive--;
});
+ if (!ERROR_SET(err)) {
+ rv = vim_to_object(&rettv);
+ }
+
+ tv_clear(&rettv);
+ recursive--;
+
free_vim_args:
while (i > 0) {
tv_clear(&vim_args[--i]);
@@ -232,9 +252,9 @@ free_vim_args:
return rv;
}
-/// Calls a VimL function with the given arguments.
+/// Calls a Vimscript function with the given arguments.
///
-/// On execution error: fails with VimL error, updates v:errmsg.
+/// On execution error: fails with Vimscript error, updates v:errmsg.
///
/// @param fn Function to call
/// @param args Function arguments packed in an Array
@@ -246,12 +266,12 @@ Object nvim_call_function(String fn, Array args, Error *err)
return _call_function(fn, args, NULL, err);
}
-/// Calls a VimL |Dictionary-function| with the given arguments.
+/// Calls a Vimscript |Dictionary-function| with the given arguments.
///
-/// On execution error: fails with VimL error, updates v:errmsg.
+/// On execution error: fails with Vimscript error, updates v:errmsg.
///
-/// @param dict Dictionary, or String evaluating to a VimL |self| dict
-/// @param fn Name of the function defined on the VimL dict
+/// @param dict Dictionary, or String evaluating to a Vimscript |self| dict
+/// @param fn Name of the function defined on the Vimscript dict
/// @param args Function arguments packed in an Array
/// @param[out] err Error details, if any
/// @return Result of the function call
@@ -265,10 +285,11 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err)
switch (dict.type) {
case kObjectTypeString:
try_start();
- if (eval0(dict.data.string.data, &rettv, NULL, true) == FAIL) {
+ if (eval0(dict.data.string.data, &rettv, NULL, &EVALARG_EVALUATE) == FAIL) {
api_set_error(err, kErrorTypeException,
"Failed to evaluate dict expression");
}
+ clear_evalarg(&EVALARG_EVALUATE, NULL);
if (try_end(err)) {
return rv;
}
@@ -336,7 +357,7 @@ typedef struct {
typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack;
/// @endcond
-/// Parse a VimL expression.
+/// Parse a Vimscript expression.
///
/// @param[in] expr Expression to parse. Always treated as a single line.
/// @param[in] flags Flags:
@@ -375,7 +396,7 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack;
/// stringified without "kExprNode" prefix.
/// - "start": a pair [line, column] describing where node is "started"
/// where "line" is always 0 (will not be 0 if you will be
-/// using nvim_parse_viml() on e.g. ":let", but that is not
+/// using this API on e.g. ":let", but that is not
/// present yet). Both elements are Integers.
/// - "len": “length” of the node. This and "start" are there for
/// debugging purposes primary (debugging parser and providing
@@ -475,7 +496,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E
};
err_dict.items[0] = (KeyValuePair) {
.key = STATIC_CSTR_TO_STRING("message"),
- .value = STRING_OBJ(cstr_to_string(east.err.msg)),
+ .value = CSTR_TO_OBJ(east.err.msg),
};
if (east.err.arg == NULL) {
err_dict.items[1] = (KeyValuePair) {
@@ -512,7 +533,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E
chunk_arr.items[0] = INTEGER_OBJ((Integer)chunk.start.line);
chunk_arr.items[1] = INTEGER_OBJ((Integer)chunk.start.col);
chunk_arr.items[2] = INTEGER_OBJ((Integer)chunk.end_col);
- chunk_arr.items[3] = STRING_OBJ(cstr_to_string(chunk.group));
+ chunk_arr.items[3] = CSTR_TO_OBJ(chunk.group);
hl.items[i] = ARRAY_OBJ(chunk_arr);
}
ret.items[ret.size++] = (KeyValuePair) {
@@ -589,7 +610,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E
kv_drop(ast_conv_stack, 1);
ret_node->items[ret_node->size++] = (KeyValuePair) {
.key = STATIC_CSTR_TO_STRING("type"),
- .value = STRING_OBJ(cstr_to_string(east_node_type_tab[node->type])),
+ .value = CSTR_TO_OBJ(east_node_type_tab[node->type]),
};
Array start_array = {
.items = xmalloc(2 * sizeof(start_array.items[0])),
@@ -674,11 +695,11 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E
case kExprNodeComparison:
ret_node->items[ret_node->size++] = (KeyValuePair) {
.key = STATIC_CSTR_TO_STRING("cmp_type"),
- .value = STRING_OBJ(cstr_to_string(eltkn_cmp_type_tab[node->data.cmp.type])),
+ .value = CSTR_TO_OBJ(eltkn_cmp_type_tab[node->data.cmp.type]),
};
ret_node->items[ret_node->size++] = (KeyValuePair) {
.key = STATIC_CSTR_TO_STRING("ccs_strategy"),
- .value = STRING_OBJ(cstr_to_string(ccs_tab[node->data.cmp.ccs])),
+ .value = CSTR_TO_OBJ(ccs_tab[node->data.cmp.ccs]),
};
ret_node->items[ret_node->size++] = (KeyValuePair) {
.key = STATIC_CSTR_TO_STRING("invert"),
diff --git a/src/nvim/api/vimscript.h b/src/nvim/api/vimscript.h
index be808b6b25..c315e932e9 100644
--- a/src/nvim/api/vimscript.h
+++ b/src/nvim/api/vimscript.h
@@ -1,9 +1,10 @@
-#ifndef NVIM_API_VIMSCRIPT_H
-#define NVIM_API_VIMSCRIPT_H
+#pragma once
-#include "nvim/api/private/defs.h"
+#include <stdint.h> // IWYU pragma: keep
+
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/vimscript.h.generated.h"
#endif
-#endif // NVIM_API_VIMSCRIPT_H
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
index 0ffeac1bff..4e23717dc6 100644
--- a/src/nvim/api/win_config.c
+++ b/src/nvim/api/win_config.c
@@ -1,30 +1,32 @@
-// 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 <stdbool.h>
#include <string.h>
#include "klib/kvec.h"
#include "nvim/api/extmark.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/win_config.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
#include "nvim/decoration.h"
#include "nvim/drawscreen.h"
-#include "nvim/extmark_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
+#include "nvim/grid.h"
#include "nvim/highlight_group.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/option.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
+#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/ui.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/win_config.c.generated.h"
@@ -55,16 +57,19 @@
/// this should not be used to specify arbitrary WM screen positions.
///
/// Example (Lua): window-relative float
-/// <pre>lua
-/// vim.api.nvim_open_win(0, false,
-/// {relative='win', row=3, col=3, width=12, height=3})
-/// </pre>
+///
+/// ```lua
+/// vim.api.nvim_open_win(0, false,
+/// {relative='win', row=3, col=3, width=12, height=3})
+/// ```
///
/// Example (Lua): buffer-relative float (travels as buffer is scrolled)
-/// <pre>lua
-/// vim.api.nvim_open_win(0, false,
-/// {relative='win', width=12, height=3, bufpos={100,10}})
-/// </pre>
+///
+/// ```lua
+/// vim.api.nvim_open_win(0, false,
+/// {relative='win', width=12, height=3, bufpos={100,10}})
+/// })
+/// ```
///
/// @param buffer Buffer to display, or 0 for current buffer
/// @param enter Enter the window (make it the current window)
@@ -108,8 +113,8 @@
/// The default value for floats are 50. In general, values below 100 are
/// recommended, unless there is a good reason to overshadow builtin
/// elements.
-/// - style: Configure the appearance of the window. Currently only takes
-/// one non-empty value:
+/// - style: (optional) Configure the appearance of the window. Currently
+/// only supports one value:
/// - "minimal" Nvim will display the window with many UI options
/// disabled. This is useful when displaying a temporary
/// float where the text should not be edited. Disables
@@ -129,7 +134,7 @@
/// - "solid": Adds padding by a single whitespace cell.
/// - "shadow": A drop shadow effect by blending with the background.
/// - If it is an array, it should have a length of eight or any divisor of
-/// eight. The array will specifify the eight chars building up the border
+/// eight. The array will specify the eight chars building up the border
/// in a clockwise fashion starting with the top-left corner. As an
/// example, the double box style could be specified as
/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ].
@@ -144,22 +149,41 @@
/// By default, `FloatBorder` highlight is used, which links to `WinSeparator`
/// when not defined. It could also be specified by character:
/// [ ["+", "MyCorner"], ["x", "MyBorder"] ].
-/// - title: Title (optional) in window border, String or list.
-/// List is [text, highlight] tuples. if is string the default
-/// highlight group is `FloatTitle`.
-/// - title_pos: Title position must set with title option.
-/// value can be of `left` `center` `right` default is left.
+/// - title: Title (optional) in window border, string or list.
+/// List should consist of `[text, highlight]` tuples.
+/// If string, the default highlight group is `FloatTitle`.
+/// - title_pos: Title position. Must be set with `title` option.
+/// Value can be one of "left", "center", or "right".
+/// Default is `"left"`.
+/// - footer: Footer (optional) in window border, string or list.
+/// List should consist of `[text, highlight]` tuples.
+/// If string, the default highlight group is `FloatFooter`.
+/// - footer_pos: Footer position. Must be set with `footer` option.
+/// Value can be one of "left", "center", or "right".
+/// Default is `"left"`.
/// - noautocmd: If true then no buffer-related autocommand events such as
/// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from
/// calling this function.
+/// - fixed: If true when anchor is NW or SW, the float window
+/// would be kept fixed even if the window would be truncated.
+/// - hide: If true the floating window will be hidden.
///
/// @param[out] err Error details, if any
///
/// @return Window handle, or 0 on error
Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, Error *err)
FUNC_API_SINCE(6)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return 0;
+ }
+ if (cmdwin_type != 0 && (enter || buf == curbuf)) {
+ api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
+ return 0;
+ }
+
FloatConfig fconfig = FLOAT_CONFIG_INIT;
if (!parse_float_config(config, &fconfig, false, true, err)) {
return 0;
@@ -173,7 +197,11 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, E
}
// autocmds in win_enter or win_set_buf below may close the window
if (win_valid(wp) && buffer > 0) {
- win_set_buf(wp->handle, buffer, fconfig.noautocmd, err);
+ Boolean noautocmd = !enter || fconfig.noautocmd;
+ win_set_buf(wp, buf, noautocmd, err);
+ if (!fconfig.noautocmd) {
+ apply_autocmds(EVENT_WINNEW, NULL, NULL, false, buf);
+ }
}
if (!win_valid(wp)) {
api_set_error(err, kErrorTypeException, "Window was closed immediately");
@@ -222,12 +250,56 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err)
win_config_float(win, fconfig);
win->w_pos_changed = true;
}
- if (fconfig.style == kWinStyleMinimal) {
- win_set_minimal_style(win);
- didset_window_options(win, true);
+ if (HAS_KEY(config, float_config, style)) {
+ if (fconfig.style == kWinStyleMinimal) {
+ win_set_minimal_style(win);
+ didset_window_options(win, true);
+ }
}
}
+static Dictionary config_put_bordertext(Dictionary config, FloatConfig *fconfig,
+ BorderTextType bordertext_type)
+{
+ VirtText vt;
+ AlignTextPos align;
+ char *field_name;
+ char *field_pos_name;
+ switch (bordertext_type) {
+ case kBorderTextTitle:
+ vt = fconfig->title_chunks;
+ align = fconfig->title_pos;
+ field_name = "title";
+ field_pos_name = "title_pos";
+ break;
+ case kBorderTextFooter:
+ vt = fconfig->footer_chunks;
+ align = fconfig->footer_pos;
+ field_name = "footer";
+ field_pos_name = "footer_pos";
+ break;
+ }
+
+ Array bordertext = virt_text_to_array(vt, true);
+ PUT(config, field_name, ARRAY_OBJ(bordertext));
+
+ char *pos;
+ switch (align) {
+ case kAlignLeft:
+ pos = "left";
+ break;
+ case kAlignCenter:
+ pos = "center";
+ break;
+ case kAlignRight:
+ pos = "right";
+ break;
+ }
+ PUT(config, field_pos_name, CSTR_TO_OBJ(pos));
+
+ return config;
+}
+
/// Gets window configuration.
///
/// The returned value may be given to |nvim_open_win()|.
@@ -251,6 +323,7 @@ Dictionary nvim_win_get_config(Window window, Error *err)
PUT(rv, "focusable", BOOLEAN_OBJ(config->focusable));
PUT(rv, "external", BOOLEAN_OBJ(config->external));
+ PUT(rv, "hide", BOOLEAN_OBJ(config->hide));
if (wp->w_floating) {
PUT(rv, "width", INTEGER_OBJ(config->width));
@@ -265,7 +338,7 @@ Dictionary nvim_win_get_config(Window window, Error *err)
PUT(rv, "bufpos", ARRAY_OBJ(pos));
}
}
- PUT(rv, "anchor", STRING_OBJ(cstr_to_string(float_anchor_str[config->anchor])));
+ PUT(rv, "anchor", CSTR_TO_OBJ(float_anchor_str[config->anchor]));
PUT(rv, "row", FLOAT_OBJ(config->row));
PUT(rv, "col", FLOAT_OBJ(config->col));
PUT(rv, "zindex", INTEGER_OBJ(config->zindex));
@@ -275,13 +348,13 @@ Dictionary nvim_win_get_config(Window window, Error *err)
for (size_t i = 0; i < 8; i++) {
Array tuple = ARRAY_DICT_INIT;
- String s = cstrn_to_string((const char *)config->border_chars[i], sizeof(schar_T));
+ String s = cstrn_to_string(config->border_chars[i], MAX_SCHAR_SIZE);
int hi_id = config->border_hl_ids[i];
char *hi_name = syn_id2name(hi_id);
if (hi_name[0]) {
ADD(tuple, STRING_OBJ(s));
- ADD(tuple, STRING_OBJ(cstr_to_string((const char *)hi_name)));
+ ADD(tuple, CSTR_TO_OBJ(hi_name));
ADD(border, ARRAY_OBJ(tuple));
} else {
ADD(border, STRING_OBJ(s));
@@ -289,34 +362,17 @@ Dictionary nvim_win_get_config(Window window, Error *err)
}
PUT(rv, "border", ARRAY_OBJ(border));
if (config->title) {
- Array titles = ARRAY_DICT_INIT;
- VirtText title_datas = config->title_chunks;
- for (size_t i = 0; i < title_datas.size; i++) {
- Array tuple = ARRAY_DICT_INIT;
- ADD(tuple, CSTR_TO_OBJ((const char *)title_datas.items[i].text));
- if (title_datas.items[i].hl_id > 0) {
- ADD(tuple,
- STRING_OBJ(cstr_to_string((const char *)syn_id2name(title_datas.items[i].hl_id))));
- }
- ADD(titles, ARRAY_OBJ(tuple));
- }
- PUT(rv, "title", ARRAY_OBJ(titles));
- char *title_pos;
- if (config->title_pos == kAlignLeft) {
- title_pos = "left";
- } else if (config->title_pos == kAlignCenter) {
- title_pos = "center";
- } else {
- title_pos = "right";
- }
- PUT(rv, "title_pos", CSTR_TO_OBJ(title_pos));
+ rv = config_put_bordertext(rv, config, kBorderTextTitle);
+ }
+ if (config->footer) {
+ rv = config_put_bordertext(rv, config, kBorderTextFooter);
}
}
}
const char *rel = (wp->w_floating && !config->external
? float_relative_str[config->relative] : "");
- PUT(rv, "relative", STRING_OBJ(cstr_to_string(rel)));
+ PUT(rv, "relative", CSTR_TO_OBJ(rel));
return rv;
}
@@ -370,68 +426,91 @@ static bool parse_float_bufpos(Array bufpos, lpos_T *out)
return true;
}
-static void parse_border_title(Object title, Object title_pos, FloatConfig *fconfig, Error *err)
+static void parse_bordertext(Object bordertext, BorderTextType bordertext_type,
+ FloatConfig *fconfig, Error *err)
{
- if (!parse_title_pos(title_pos, fconfig, err)) {
- return;
- }
-
- if (title.type == kObjectTypeString) {
- if (title.data.string.size == 0) {
- fconfig->title = false;
+ bool *is_present;
+ VirtText *chunks;
+ int *width;
+ int default_hl_id;
+ switch (bordertext_type) {
+ case kBorderTextTitle:
+ is_present = &fconfig->title;
+ chunks = &fconfig->title_chunks;
+ width = &fconfig->title_width;
+ default_hl_id = syn_check_group(S_LEN("FloatTitle"));
+ break;
+ case kBorderTextFooter:
+ is_present = &fconfig->footer;
+ chunks = &fconfig->footer_chunks;
+ width = &fconfig->footer_width;
+ default_hl_id = syn_check_group(S_LEN("FloatFooter"));
+ break;
+ }
+
+ if (bordertext.type == kObjectTypeString) {
+ if (bordertext.data.string.size == 0) {
+ *is_present = false;
return;
}
- int hl_id = syn_check_group(S_LEN("FloatTitle"));
- kv_push(fconfig->title_chunks, ((VirtTextChunk){ .text = xstrdup(title.data.string.data),
- .hl_id = hl_id }));
- fconfig->title_width = (int)mb_string2cells(title.data.string.data);
- fconfig->title = true;
+ kv_push(*chunks, ((VirtTextChunk){ .text = xstrdup(bordertext.data.string.data),
+ .hl_id = default_hl_id }));
+ *width = (int)mb_string2cells(bordertext.data.string.data);
+ *is_present = true;
return;
}
- if (title.type != kObjectTypeArray) {
+ if (bordertext.type != kObjectTypeArray) {
api_set_error(err, kErrorTypeValidation, "title must be string or array");
return;
}
- if (title.data.array.size == 0) {
+ if (bordertext.data.array.size == 0) {
api_set_error(err, kErrorTypeValidation, "title cannot be an empty array");
return;
}
- fconfig->title_width = 0;
- fconfig->title_chunks = parse_virt_text(title.data.array, err, &fconfig->title_width);
+ *width = 0;
+ *chunks = parse_virt_text(bordertext.data.array, err, width);
- fconfig->title = true;
+ *is_present = true;
}
-static bool parse_title_pos(Object title_pos, FloatConfig *fconfig, Error *err)
+static bool parse_bordertext_pos(String bordertext_pos, BorderTextType bordertext_type,
+ FloatConfig *fconfig, Error *err)
{
- if (!HAS_KEY(title_pos)) {
- fconfig->title_pos = kAlignLeft;
+ AlignTextPos *align;
+ switch (bordertext_type) {
+ case kBorderTextTitle:
+ align = &fconfig->title_pos;
+ break;
+ case kBorderTextFooter:
+ align = &fconfig->footer_pos;
+ break;
+ }
+
+ if (bordertext_pos.size == 0) {
+ *align = kAlignLeft;
return true;
}
- if (title_pos.type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation, "title_pos must be string");
- return false;
- }
-
- if (title_pos.data.string.size == 0) {
- fconfig->title_pos = kAlignLeft;
- return true;
- }
-
- char *pos = title_pos.data.string.data;
+ char *pos = bordertext_pos.data;
if (strequal(pos, "left")) {
- fconfig->title_pos = kAlignLeft;
+ *align = kAlignLeft;
} else if (strequal(pos, "center")) {
- fconfig->title_pos = kAlignCenter;
+ *align = kAlignCenter;
} else if (strequal(pos, "right")) {
- fconfig->title_pos = kAlignRight;
+ *align = kAlignRight;
} else {
- api_set_error(err, kErrorTypeValidation, "invalid title_pos value");
+ switch (bordertext_type) {
+ case kBorderTextTitle:
+ api_set_error(err, kErrorTypeValidation, "invalid title_pos value");
+ break;
+ case kBorderTextFooter:
+ api_set_error(err, kErrorTypeValidation, "invalid footer_pos value");
+ break;
+ }
return false;
}
return true;
@@ -441,7 +520,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
{
struct {
const char *name;
- schar_T chars[8];
+ char chars[8][MAX_SCHAR_SIZE];
bool shadow_color;
} defaults[] = {
{ "double", { "╔", "═", "╗", "║", "╝", "═", "╚", "║" }, false },
@@ -452,7 +531,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
{ NULL, { { NUL } }, false },
};
- schar_T *chars = fconfig->border_chars;
+ char (*chars)[MAX_SCHAR_SIZE] = fconfig->border_chars;
int *hl_ids = fconfig->border_hl_ids;
fconfig->border = true;
@@ -521,8 +600,9 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
String str = style.data.string;
if (str.size == 0 || strequal(str.data, "none")) {
fconfig->border = false;
- // title does not work with border equal none
+ // border text does not work with border equal none
fconfig->title = false;
+ fconfig->footer = false;
return;
}
for (size_t i = 0; defaults[i].name; i++) {
@@ -549,110 +629,90 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, bool reconf,
bool new_win, Error *err)
{
+#define HAS_KEY_X(d, key) HAS_KEY(d, float_config, key)
bool has_relative = false, relative_is_win = false;
- if (config->relative.type == kObjectTypeString) {
- // ignore empty string, to match nvim_win_get_config
- if (config->relative.data.string.size > 0) {
- if (!parse_float_relative(config->relative.data.string, &fconfig->relative)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value of 'relative' key");
- return false;
- }
+ // ignore empty string, to match nvim_win_get_config
+ if (HAS_KEY_X(config, relative) && config->relative.size > 0) {
+ if (!parse_float_relative(config->relative, &fconfig->relative)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value of 'relative' key");
+ return false;
+ }
- if (!(HAS_KEY(config->row) && HAS_KEY(config->col)) && !HAS_KEY(config->bufpos)) {
- api_set_error(err, kErrorTypeValidation,
- "'relative' requires 'row'/'col' or 'bufpos'");
- return false;
- }
+ if (!(HAS_KEY_X(config, row) && HAS_KEY_X(config, col)) && !HAS_KEY_X(config, bufpos)) {
+ api_set_error(err, kErrorTypeValidation,
+ "'relative' requires 'row'/'col' or 'bufpos'");
+ return false;
+ }
- has_relative = true;
- fconfig->external = false;
- if (fconfig->relative == kFloatRelativeWindow) {
- relative_is_win = true;
- fconfig->bufpos.lnum = -1;
- }
+ has_relative = true;
+ fconfig->external = false;
+ if (fconfig->relative == kFloatRelativeWindow) {
+ relative_is_win = true;
+ fconfig->bufpos.lnum = -1;
}
- } else if (HAS_KEY(config->relative)) {
- api_set_error(err, kErrorTypeValidation, "'relative' key must be String");
- return false;
}
- if (config->anchor.type == kObjectTypeString) {
- if (!parse_float_anchor(config->anchor.data.string, &fconfig->anchor)) {
+ if (HAS_KEY_X(config, anchor)) {
+ if (!parse_float_anchor(config->anchor, &fconfig->anchor)) {
api_set_error(err, kErrorTypeValidation, "Invalid value of 'anchor' key");
return false;
}
- } else if (HAS_KEY(config->anchor)) {
- api_set_error(err, kErrorTypeValidation, "'anchor' key must be String");
- return false;
}
- if (HAS_KEY(config->row)) {
+ if (HAS_KEY_X(config, row)) {
if (!has_relative) {
api_set_error(err, kErrorTypeValidation, "non-float cannot have 'row'");
return false;
- } else if (config->row.type == kObjectTypeInteger) {
- fconfig->row = (double)config->row.data.integer;
- } else if (config->row.type == kObjectTypeFloat) {
- fconfig->row = config->row.data.floating;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "'row' key must be Integer or Float");
- return false;
}
+ fconfig->row = config->row;
}
- if (HAS_KEY(config->col)) {
+ if (HAS_KEY_X(config, col)) {
if (!has_relative) {
api_set_error(err, kErrorTypeValidation, "non-float cannot have 'col'");
return false;
- } else if (config->col.type == kObjectTypeInteger) {
- fconfig->col = (double)config->col.data.integer;
- } else if (config->col.type == kObjectTypeFloat) {
- fconfig->col = config->col.data.floating;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "'col' key must be Integer or Float");
- return false;
}
+ fconfig->col = config->col;
}
- if (HAS_KEY(config->bufpos)) {
+ if (HAS_KEY_X(config, bufpos)) {
if (!has_relative) {
api_set_error(err, kErrorTypeValidation, "non-float cannot have 'bufpos'");
return false;
- } else if (config->bufpos.type == kObjectTypeArray) {
- if (!parse_float_bufpos(config->bufpos.data.array, &fconfig->bufpos)) {
+ } else {
+ if (!parse_float_bufpos(config->bufpos, &fconfig->bufpos)) {
api_set_error(err, kErrorTypeValidation, "Invalid value of 'bufpos' key");
return false;
}
- if (!HAS_KEY(config->row)) {
+ if (!HAS_KEY_X(config, row)) {
fconfig->row = (fconfig->anchor & kFloatAnchorSouth) ? 0 : 1;
}
- if (!HAS_KEY(config->col)) {
+ if (!HAS_KEY_X(config, col)) {
fconfig->col = 0;
}
- } else {
- api_set_error(err, kErrorTypeValidation, "'bufpos' key must be Array");
- return false;
}
}
- if (config->width.type == kObjectTypeInteger && config->width.data.integer > 0) {
- fconfig->width = (int)config->width.data.integer;
- } else if (HAS_KEY(config->width)) {
- api_set_error(err, kErrorTypeValidation, "'width' key must be a positive Integer");
- return false;
+ if (HAS_KEY_X(config, width)) {
+ if (config->width > 0) {
+ fconfig->width = (int)config->width;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "'width' key must be a positive Integer");
+ return false;
+ }
} else if (!reconf) {
api_set_error(err, kErrorTypeValidation, "Must specify 'width'");
return false;
}
- if (config->height.type == kObjectTypeInteger && config->height.data.integer > 0) {
- fconfig->height = (int)config->height.data.integer;
- } else if (HAS_KEY(config->height)) {
- api_set_error(err, kErrorTypeValidation, "'height' key must be a positive Integer");
- return false;
+ if (HAS_KEY_X(config, height)) {
+ if (config->height > 0) {
+ fconfig->height = (int)config->height;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "'height' key must be a positive Integer");
+ return false;
+ }
} else if (!reconf) {
api_set_error(err, kErrorTypeValidation, "Must specify 'height'");
return false;
@@ -660,26 +720,20 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
if (relative_is_win) {
fconfig->window = curwin->handle;
- if (config->win.type == kObjectTypeInteger || config->win.type == kObjectTypeWindow) {
- if (config->win.data.integer > 0) {
- fconfig->window = (Window)config->win.data.integer;
+ if (HAS_KEY_X(config, win)) {
+ if (config->win > 0) {
+ fconfig->window = config->win;
}
- } else if (HAS_KEY(config->win)) {
- api_set_error(err, kErrorTypeValidation, "'win' key must be Integer or Window");
- return false;
}
} else {
- if (HAS_KEY(config->win)) {
+ if (HAS_KEY_X(config, win)) {
api_set_error(err, kErrorTypeValidation, "'win' key is only valid with relative='win'");
return false;
}
}
- if (HAS_KEY(config->external)) {
- fconfig->external = api_object_to_bool(config->external, "'external' key", false, err);
- if (ERROR_SET(err)) {
- return false;
- }
+ if (HAS_KEY_X(config, external)) {
+ fconfig->external = config->external;
if (has_relative && fconfig->external) {
api_set_error(err, kErrorTypeValidation,
"Only one of 'relative' and 'external' must be used");
@@ -698,30 +752,22 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
return false;
}
- if (HAS_KEY(config->focusable)) {
- fconfig->focusable = api_object_to_bool(config->focusable, "'focusable' key", false, err);
- if (ERROR_SET(err)) {
- return false;
- }
- }
-
- if (config->zindex.type == kObjectTypeInteger && config->zindex.data.integer > 0) {
- fconfig->zindex = (int)config->zindex.data.integer;
- } else if (HAS_KEY(config->zindex)) {
- api_set_error(err, kErrorTypeValidation, "'zindex' key must be a positive Integer");
- return false;
+ if (HAS_KEY_X(config, focusable)) {
+ fconfig->focusable = config->focusable;
}
- if (HAS_KEY(config->title_pos)) {
- if (!HAS_KEY(config->title)) {
- api_set_error(err, kErrorTypeException, "title_pos requires title to be set");
+ if (HAS_KEY_X(config, zindex)) {
+ if (config->zindex > 0) {
+ fconfig->zindex = (int)config->zindex;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "'zindex' key must be a positive Integer");
return false;
}
}
- if (HAS_KEY(config->title)) {
+ if (HAS_KEY_X(config, title)) {
// title only work with border
- if (!HAS_KEY(config->border) && !fconfig->border) {
+ if (!HAS_KEY_X(config, border) && !fconfig->border) {
api_set_error(err, kErrorTypeException, "title requires border to be set");
return false;
}
@@ -729,42 +775,84 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
if (fconfig->title) {
clear_virttext(&fconfig->title_chunks);
}
- parse_border_title(config->title, config->title_pos, fconfig, err);
+
+ parse_bordertext(config->title, kBorderTextTitle, fconfig, err);
+ if (ERROR_SET(err)) {
+ return false;
+ }
+
+ // handles unset 'title_pos' same as empty string
+ if (!parse_bordertext_pos(config->title_pos, kBorderTextTitle, fconfig, err)) {
+ return false;
+ }
+ } else {
+ if (HAS_KEY_X(config, title_pos)) {
+ api_set_error(err, kErrorTypeException, "title_pos requires title to be set");
+ return false;
+ }
+ }
+
+ if (HAS_KEY_X(config, footer)) {
+ // footer only work with border
+ if (!HAS_KEY_X(config, border) && !fconfig->border) {
+ api_set_error(err, kErrorTypeException, "footer requires border to be set");
+ return false;
+ }
+
+ if (fconfig->footer) {
+ clear_virttext(&fconfig->footer_chunks);
+ }
+
+ parse_bordertext(config->footer, kBorderTextFooter, fconfig, err);
if (ERROR_SET(err)) {
return false;
}
+
+ // handles unset 'footer_pos' same as empty string
+ if (!parse_bordertext_pos(config->footer_pos, kBorderTextFooter, fconfig, err)) {
+ return false;
+ }
+ } else {
+ if (HAS_KEY_X(config, footer_pos)) {
+ api_set_error(err, kErrorTypeException, "footer_pos requires footer to be set");
+ return false;
+ }
}
- if (HAS_KEY(config->border)) {
+ if (HAS_KEY_X(config, border)) {
parse_border_style(config->border, fconfig, err);
if (ERROR_SET(err)) {
return false;
}
}
- if (config->style.type == kObjectTypeString) {
- if (config->style.data.string.data[0] == NUL) {
+ if (HAS_KEY_X(config, style)) {
+ if (config->style.data[0] == NUL) {
fconfig->style = kWinStyleUnused;
- } else if (striequal(config->style.data.string.data, "minimal")) {
+ } else if (striequal(config->style.data, "minimal")) {
fconfig->style = kWinStyleMinimal;
} else {
api_set_error(err, kErrorTypeValidation, "Invalid value of 'style' key");
+ return false;
}
- } else if (HAS_KEY(config->style)) {
- api_set_error(err, kErrorTypeValidation, "'style' key must be String");
- return false;
}
- if (HAS_KEY(config->noautocmd)) {
+ if (HAS_KEY_X(config, noautocmd)) {
if (!new_win) {
api_set_error(err, kErrorTypeValidation, "Invalid key: 'noautocmd'");
return false;
}
- fconfig->noautocmd = api_object_to_bool(config->noautocmd, "'noautocmd' key", false, err);
- if (ERROR_SET(err)) {
- return false;
- }
+ fconfig->noautocmd = config->noautocmd;
+ }
+
+ if (HAS_KEY_X(config, fixed)) {
+ fconfig->fixed = config->fixed;
+ }
+
+ if (HAS_KEY_X(config, hide)) {
+ fconfig->hide = config->hide;
}
return true;
+#undef HAS_KEY_X
}
diff --git a/src/nvim/api/win_config.h b/src/nvim/api/win_config.h
index d3e5ede5e9..6df8ed13fa 100644
--- a/src/nvim/api/win_config.h
+++ b/src/nvim/api/win_config.h
@@ -1,9 +1,10 @@
-#ifndef NVIM_API_WIN_CONFIG_H
-#define NVIM_API_WIN_CONFIG_H
+#pragma once
-#include "nvim/api/private/defs.h"
+#include <stdint.h> // IWYU pragma: keep
+
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/win_config.h.generated.h"
#endif
-#endif // NVIM_API_WIN_CONFIG_H
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index e2c234ab29..de5b40940f 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -1,27 +1,30 @@
-// 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 <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
+#include "nvim/api/keysets_defs.h"
#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/window.h"
-#include "nvim/ascii.h"
+#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
#include "nvim/eval/window.h"
#include "nvim/ex_docmd.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/lua/executor.h"
-#include "nvim/memline_defs.h"
+#include "nvim/memory.h"
+#include "nvim/message.h"
#include "nvim/move.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/plines.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
#include "nvim/window.h"
/// Gets the current buffer in a window
@@ -48,15 +51,26 @@ Buffer nvim_win_get_buf(Window window, Error *err)
/// @param[out] err Error details, if any
void nvim_win_set_buf(Window window, Buffer buffer, Error *err)
FUNC_API_SINCE(5)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
- win_set_buf(window, buffer, false, err);
+ win_T *win = find_window_by_handle(window, err);
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!win || !buf) {
+ return;
+ }
+ if (cmdwin_type != 0 && (win == curwin || win == cmdwin_old_curwin || buf == curbuf)) {
+ api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
+ return;
+ }
+ win_set_buf(win, buf, false, err);
}
/// Gets the (1,0)-indexed, buffer-relative cursor position for a given window
/// (different windows showing the same buffer have independent cursor
/// positions). |api-indexing|
///
+/// @see |getcurpos()|
+///
/// @param window Window handle, or 0 for current window
/// @param[out] err Error details, if any
/// @return (row, col) tuple
@@ -167,13 +181,8 @@ void nvim_win_set_height(Window window, Integer height, Error *err)
return;
}
- win_T *savewin = curwin;
- curwin = win;
- curbuf = curwin->w_buffer;
try_start();
- win_setheight((int)height);
- curwin = savewin;
- curbuf = curwin->w_buffer;
+ win_setheight_win((int)height, win);
try_end(err);
}
@@ -214,13 +223,8 @@ void nvim_win_set_width(Window window, Integer width, Error *err)
return;
}
- win_T *savewin = curwin;
- curwin = win;
- curbuf = curwin->w_buffer;
try_start();
- win_setwidth((int)width);
- curwin = savewin;
- curbuf = curwin->w_buffer;
+ win_setwidth_win((int)width, win);
try_end(err);
}
@@ -359,10 +363,10 @@ Boolean nvim_win_is_valid(Window window)
/// @param[out] err Error details, if any
void nvim_win_hide(Window window, Error *err)
FUNC_API_SINCE(7)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
win_T *win = find_window_by_handle(window, err);
- if (!win) {
+ if (!win || !can_close_in_cmdwin(win, err)) {
return;
}
@@ -391,19 +395,10 @@ void nvim_win_hide(Window window, Error *err)
/// @param[out] err Error details, if any
void nvim_win_close(Window window, Boolean force, Error *err)
FUNC_API_SINCE(6)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
win_T *win = find_window_by_handle(window, err);
- if (!win) {
- return;
- }
-
- if (cmdwin_type != 0) {
- if (win == curwin) {
- cmdwin_result = Ctrl_C;
- } else {
- api_set_error(err, kErrorTypeException, "%s", _(e_cmdwin));
- }
+ if (!win || !can_close_in_cmdwin(win, err)) {
return;
}
@@ -420,11 +415,11 @@ void nvim_win_close(Window window, Boolean force, Error *err)
/// @see |nvim_buf_call()|
///
/// @param window Window handle, or 0 for current window
-/// @param fun Function to call inside the window (currently lua callable
+/// @param fun Function to call inside the window (currently Lua callable
/// only)
/// @param[out] err Error details, if any
-/// @return Return value of function. NB: will deepcopy lua values
-/// currently, use upvalues to send lua references in and out.
+/// @return Return value of function. NB: will deepcopy Lua values
+/// currently, use upvalues to send Lua references in and out.
Object nvim_win_call(Window window, LuaRef fun, Error *err)
FUNC_API_SINCE(7)
FUNC_API_LUA_ONLY
@@ -445,8 +440,9 @@ Object nvim_win_call(Window window, LuaRef fun, Error *err)
return res;
}
-/// Set highlight namespace for a window. This will use highlights defined in
-/// this namespace, but fall back to global highlights (ns=0) when missing.
+/// Set highlight namespace for a window. This will use highlights defined with
+/// |nvim_set_hl()| for this namespace, but fall back to global highlights (ns=0) when
+/// missing.
///
/// This takes precedence over the 'winhighlight' option.
///
@@ -469,3 +465,106 @@ void nvim_win_set_hl_ns(Window window, Integer ns_id, Error *err)
win->w_hl_needs_update = true;
redraw_later(win, UPD_NOT_VALID);
}
+
+/// Computes the number of screen lines occupied by a range of text in a given window.
+/// Works for off-screen text and takes folds into account.
+///
+/// Diff filler or virtual lines above a line are counted as a part of that line,
+/// unless the line is on "start_row" and "start_vcol" is specified.
+///
+/// Diff filler or virtual lines below the last buffer line are counted in the result
+/// when "end_row" is omitted.
+///
+/// Line indexing is similar to |nvim_buf_get_text()|.
+///
+/// @param window Window handle, or 0 for current window.
+/// @param opts Optional parameters:
+/// - start_row: Starting line index, 0-based inclusive.
+/// When omitted start at the very top.
+/// - end_row: Ending line index, 0-based inclusive.
+/// When omitted end at the very bottom.
+/// - start_vcol: Starting virtual column index on "start_row",
+/// 0-based inclusive, rounded down to full screen lines.
+/// When omitted include the whole line.
+/// - end_vcol: Ending virtual column index on "end_row",
+/// 0-based exclusive, rounded up to full screen lines.
+/// When omitted include the whole line.
+/// @return Dictionary containing text height information, with these keys:
+/// - all: The total number of screen lines occupied by the range.
+/// - fill: The number of diff filler or virtual lines among them.
+///
+/// @see |virtcol()| for text width.
+Dictionary nvim_win_text_height(Window window, Dict(win_text_height) *opts, Arena *arena,
+ Error *err)
+ FUNC_API_SINCE(12)
+{
+ Dictionary rv = arena_dict(arena, 2);
+
+ win_T *const win = find_window_by_handle(window, err);
+ if (!win) {
+ return rv;
+ }
+ buf_T *const buf = win->w_buffer;
+ const linenr_T line_count = buf->b_ml.ml_line_count;
+
+ linenr_T start_lnum = 1;
+ linenr_T end_lnum = line_count;
+ int64_t start_vcol = -1;
+ int64_t end_vcol = -1;
+
+ bool oob = false;
+
+ if (HAS_KEY(opts, win_text_height, start_row)) {
+ start_lnum = (linenr_T)normalize_index(buf, opts->start_row, false, &oob);
+ }
+
+ if (HAS_KEY(opts, win_text_height, end_row)) {
+ end_lnum = (linenr_T)normalize_index(buf, opts->end_row, false, &oob);
+ }
+
+ VALIDATE(!oob, "%s", "Line index out of bounds", {
+ return rv;
+ });
+ VALIDATE((start_lnum <= end_lnum), "%s", "'start_row' is higher than 'end_row'", {
+ return rv;
+ });
+
+ if (HAS_KEY(opts, win_text_height, start_vcol)) {
+ VALIDATE(HAS_KEY(opts, win_text_height, start_row),
+ "%s", "'start_vcol' specified without 'start_row'", {
+ return rv;
+ });
+ start_vcol = opts->start_vcol;
+ VALIDATE_RANGE((start_vcol >= 0 && start_vcol <= MAXCOL), "start_vcol", {
+ return rv;
+ });
+ }
+
+ if (HAS_KEY(opts, win_text_height, end_vcol)) {
+ VALIDATE(HAS_KEY(opts, win_text_height, end_row),
+ "%s", "'end_vcol' specified without 'end_row'", {
+ return rv;
+ });
+ end_vcol = opts->end_vcol;
+ VALIDATE_RANGE((end_vcol >= 0 && end_vcol <= MAXCOL), "end_vcol", {
+ return rv;
+ });
+ }
+
+ if (start_lnum == end_lnum && start_vcol >= 0 && end_vcol >= 0) {
+ VALIDATE((start_vcol <= end_vcol), "%s", "'start_vcol' is higher than 'end_vcol'", {
+ return rv;
+ });
+ }
+
+ int64_t fill = 0;
+ int64_t all = win_text_height(win, start_lnum, start_vcol, end_lnum, end_vcol, &fill);
+ if (!HAS_KEY(opts, win_text_height, end_row)) {
+ const int64_t end_fill = win_get_fill(win, line_count + 1);
+ fill += end_fill;
+ all += end_fill;
+ }
+ PUT_C(rv, "all", INTEGER_OBJ(all));
+ PUT_C(rv, "fill", INTEGER_OBJ(fill));
+ return rv;
+}
diff --git a/src/nvim/api/window.h b/src/nvim/api/window.h
index 0f36c12a9f..a5c9f86225 100644
--- a/src/nvim/api/window.h
+++ b/src/nvim/api/window.h
@@ -1,9 +1,8 @@
-#ifndef NVIM_API_WINDOW_H
-#define NVIM_API_WINDOW_H
+#pragma once
-#include "nvim/api/private/defs.h"
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/window.h.generated.h"
#endif
-#endif // NVIM_API_WINDOW_H