aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/api
diff options
context:
space:
mode:
authorJosh Rahm <rahm@google.com>2022-07-18 19:37:18 +0000
committerJosh Rahm <rahm@google.com>2022-07-18 19:37:18 +0000
commit308e1940dcd64aa6c344c403d4f9e0dda58d9c5c (patch)
tree35fe43e01755e0f312650667004487a44d6b7941 /src/nvim/api
parent96a00c7c588b2f38a2424aeeb4ea3581d370bf2d (diff)
parente8c94697bcbe23a5c7b07c292b90a6b70aadfa87 (diff)
downloadrneovim-308e1940dcd64aa6c344c403d4f9e0dda58d9c5c.tar.gz
rneovim-308e1940dcd64aa6c344c403d4f9e0dda58d9c5c.tar.bz2
rneovim-308e1940dcd64aa6c344c403d4f9e0dda58d9c5c.zip
Merge remote-tracking branch 'upstream/master' into rahm
Diffstat (limited to 'src/nvim/api')
-rw-r--r--src/nvim/api/autocmd.c1016
-rw-r--r--src/nvim/api/autocmd.h11
-rw-r--r--src/nvim/api/buffer.c407
-rw-r--r--src/nvim/api/command.c1189
-rw-r--r--src/nvim/api/command.h11
-rw-r--r--src/nvim/api/deprecated.c27
-rw-r--r--src/nvim/api/extmark.c460
-rw-r--r--src/nvim/api/extmark.h1
-rw-r--r--src/nvim/api/keysets.lua130
-rw-r--r--src/nvim/api/options.c554
-rw-r--r--src/nvim/api/options.h9
-rw-r--r--src/nvim/api/private/converter.c24
-rw-r--r--src/nvim/api/private/defs.h15
-rw-r--r--src/nvim/api/private/dispatch.c49
-rw-r--r--src/nvim/api/private/dispatch.h1
-rw-r--r--src/nvim/api/private/helpers.c833
-rw-r--r--src/nvim/api/private/helpers.h32
-rw-r--r--src/nvim/api/tabpage.c7
-rw-r--r--src/nvim/api/ui.c556
-rw-r--r--src/nvim/api/ui.h1
-rw-r--r--src/nvim/api/ui_events.in.h12
-rw-r--r--src/nvim/api/vim.c586
-rw-r--r--src/nvim/api/vimscript.c42
-rw-r--r--src/nvim/api/win_config.c37
-rw-r--r--src/nvim/api/window.c55
25 files changed, 4400 insertions, 1665 deletions
diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
new file mode 100644
index 0000000000..bf6402f938
--- /dev/null
+++ b/src/nvim/api/autocmd.c
@@ -0,0 +1,1016 @@
+// 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 <stdio.h>
+
+#include "lauxlib.h"
+#include "nvim/api/autocmd.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/ascii.h"
+#include "nvim/buffer.h"
+#include "nvim/eval/typval.h"
+#include "nvim/fileio.h"
+#include "nvim/lua/executor.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/autocmd.c.generated.h"
+#endif
+
+#define AUCMD_MAX_PATTERNS 256
+
+// 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"); \
+ goto goto_name; \
+ }
+
+// ID for associating autocmds created via nvim_create_autocmd
+// Used to delete autocmds from nvim_del_autocmd
+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>
+/// -- 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>
+///
+/// NOTE: When multiple patterns or events are provided, it will find all the autocommands that
+/// match any combination of them.
+///
+/// @param opts Dictionary with at least one of the following:
+/// - group (string|integer): the autocommand group name or id to match against.
+/// - event (string|array): event or events to match against |autocmd-events|.
+/// - pattern (string|array): pattern or patterns to match against |autocmd-pattern|.
+/// @return Array of autocommands matching the criteria, with each item
+/// containing the following fields:
+/// - id (number): the autocommand id (only when defined with the API).
+/// - group (integer): the autocommand group id.
+/// - group_name (string): the autocommand group name.
+/// - desc (string): the autocommand description.
+/// - event (string): the autocommand event.
+/// - command (string): the autocommand command. Note: this will be empty if a callback is set.
+/// - callback (function|string|nil): Lua function or name of a Vim script function
+/// which is executed when this autocommand is triggered.
+/// - once (boolean): whether the autocommand is only run once.
+/// - pattern (string): the autocommand pattern.
+/// If the autocommand is buffer local |autocmd-buffer-local|:
+/// - buflocal (boolean): true if the autocommand is buffer local.
+/// - buffer (number): the buffer number.
+Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
+ FUNC_API_SINCE(9)
+{
+ // TODO(tjdevries): Would be cool to add nvim_get_autocmds({ id = ... })
+
+ Array autocmd_list = ARRAY_DICT_INIT;
+ char *pattern_filters[AUCMD_MAX_PATTERNS];
+ char pattern_buflocal[BUFLOCAL_PAT_LEN];
+
+ Array buffers = ARRAY_DICT_INIT;
+
+ bool event_set[NUM_EVENTS] = { false };
+ bool check_event = false;
+
+ int group = 0;
+
+ switch (opts->group.type) {
+ case kObjectTypeNil:
+ break;
+ case kObjectTypeString:
+ group = augroup_find(opts->group.data.string.data);
+ if (group < 0) {
+ api_set_error(err, kErrorTypeValidation, "invalid augroup passed.");
+ 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.");
+ goto cleanup;
+ }
+ break;
+ default:
+ api_set_error(err, kErrorTypeValidation, "group must be a string or an integer.");
+ goto cleanup;
+ }
+
+ if (opts->event.type != kObjectTypeNil) {
+ check_event = true;
+
+ Object v = opts->event;
+ if (v.type == kObjectTypeString) {
+ GET_ONE_EVENT(event_nr, v, cleanup);
+ 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'");
+ 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;
+ }
+ }
+
+ if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) {
+ api_set_error(err, kErrorTypeValidation,
+ "Cannot use both 'pattern' and 'buffer'");
+ goto cleanup;
+ }
+
+ int pattern_filter_count = 0;
+ if (opts->pattern.type != kObjectTypeNil) {
+ 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);
+ 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");
+ 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;
+ }
+ }
+
+ if (opts->buffer.type == kObjectTypeInteger || opts->buffer.type == kObjectTypeBuffer) {
+ buf_T *buf = find_buffer_by_handle((Buffer)opts->buffer.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));
+ } 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);
+ 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");
+ 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));
+ });
+ } 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, {
+ pattern_filters[pattern_filter_count] = bufnr.data.string.data;
+ pattern_filter_count += 1;
+ });
+
+ FOR_ALL_AUEVENTS(event) {
+ if (check_event && !event_set[event]) {
+ continue;
+ }
+
+ for (AutoPat *ap = au_get_autopat_for_event(event); ap != NULL; ap = ap->next) {
+ if (ap->cmds == NULL) {
+ continue;
+ }
+
+ // Skip autocmds from invalid groups if passed.
+ if (group != 0 && ap->group != group) {
+ continue;
+ }
+
+ // 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]);
+
+ char *pat = pattern_filters[i];
+ int patlen = (int)STRLEN(pat);
+
+ if (aupat_is_buflocal(pat, patlen)) {
+ aupat_normalize_buflocal_pat(pattern_buflocal,
+ pat,
+ patlen,
+ aupat_get_buflocal_nr(pat, patlen));
+
+ pat = pattern_buflocal;
+ }
+
+ if (strequal(ap->pat, pat)) {
+ passed = true;
+ break;
+ }
+ }
+
+ if (!passed) {
+ continue;
+ }
+ }
+
+ for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
+ if (aucmd_exec_is_deleted(ac->exec)) {
+ continue;
+ }
+
+ 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 (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->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();
+ }
+ } else {
+ PUT(autocmd_info,
+ "command",
+ STRING_OBJ(cstr_as_string(xstrdup(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));
+ }
+
+ // 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));
+ }
+ }
+ }
+
+cleanup:
+ api_free_array(buffers);
+ return autocmd_list;
+}
+
+/// Create an |autocommand|
+///
+/// The API allows for two (mutually exclusive) types of actions to be executed when the autocommand
+/// triggers: a callback function (Lua or Vimscript), or a command (like regular autocommands).
+///
+/// Example using callback:
+/// <pre>
+/// -- Lua function
+/// local myluafun = function() print("This buffer enters") end
+///
+/// -- Vimscript function name (as a string)
+/// local myvimfun = "g:MyVimFunction"
+///
+/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
+/// pattern = {"*.c", "*.h"},
+/// callback = myluafun, -- Or myvimfun
+/// })
+/// </pre>
+///
+/// Lua functions receive a table with information about the autocmd event as an argument. To use
+/// a function which itself accepts another (optional) parameter, wrap the function
+/// in a lambda:
+///
+/// <pre>
+/// -- Lua function with an optional parameter.
+/// -- The autocmd callback would pass a table as argument but this
+/// -- function expects number|nil
+/// local myluafun = function(bufnr) bufnr = bufnr or vim.api.nvim_get_current_buf() end
+///
+/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
+/// pattern = {"*.c", "*.h"},
+/// callback = function() myluafun() end,
+/// })
+/// </pre>
+///
+/// Example using command:
+/// <pre>
+/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
+/// pattern = {"*.c", "*.h"},
+/// command = "echo 'Entering a C or C++ file'",
+/// })
+/// </pre>
+///
+/// Example values for pattern:
+/// <pre>
+/// pattern = "*.py"
+/// pattern = { "*.py", "*.pyi" }
+/// </pre>
+///
+/// Example values for event:
+/// <pre>
+/// "BufWritePre"
+/// {"CursorHold", "BufWritePre", "BufWritePost"}
+/// </pre>
+///
+/// @param event (string|array) The event or events to register this autocommand
+/// @param opts Dictionary of autocommand options:
+/// - group (string|integer) optional: the autocommand group name or
+/// id to match against.
+/// - pattern (string|array) optional: pattern or patterns to match
+/// against |autocmd-pattern|.
+/// - buffer (integer) optional: buffer number for buffer local autocommands
+/// |autocmd-buflocal|. Cannot be used with {pattern}.
+/// - desc (string) optional: description of the autocommand.
+/// - callback (function|string) optional: if a string, the name of a Vimscript function
+/// to call when this autocommand is triggered. Otherwise, a Lua function which is
+/// called when this autocommand is triggered. Cannot be used with {command}. Lua
+/// callbacks can return true to delete the autocommand; in addition, they accept a
+/// single table argument with the following keys:
+/// - id: (number) the autocommand id
+/// - event: (string) the name of the event that triggered the autocommand
+/// |autocmd-events|
+/// - group: (number|nil) the autocommand group id, if it exists
+/// - match: (string) the expanded value of |<amatch>|
+/// - buf: (number) the expanded value of |<abuf>|
+/// - file: (string) the expanded value of |<afile>|
+/// - data: (any) arbitrary data passed to |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
+/// only once |autocmd-once|.
+/// - nested (boolean) optional: defaults to false. Run nested
+/// autocommands |autocmd-nested|.
+///
+/// @return Integer id of the created autocommand.
+/// @see |autocommand|
+/// @see |nvim_del_autocmd()|
+Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autocmd) *opts,
+ Error *err)
+ FUNC_API_SINCE(9)
+{
+ 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;
+
+ if (!unpack_string_or_array(&event_array, &event, "event", true, err)) {
+ goto cleanup;
+ }
+
+ if (opts->callback.type != kObjectTypeNil && opts->command.type != kObjectTypeNil) {
+ api_set_error(err, kErrorTypeValidation,
+ "cannot pass both: 'callback' and 'command' for the same autocmd");
+ 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.
+
+ 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");
+ goto cleanup;
+ }
+
+ if (!nlua_ref_is_function(callback->data.luaref)) {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "must pass a function for callback");
+ goto cleanup;
+ }
+
+ cb.type = kCallbackLua;
+ cb.data.luaref = api_new_luaref(callback->data.luaref);
+ break;
+ case kObjectTypeString:
+ cb.type = kCallbackFuncref;
+ 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;
+ }
+
+ 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 {
+ api_set_error(err, kErrorTypeValidation, "must pass one of: 'command', '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)) {
+ 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 (patterns.size == 0) {
+ ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("*")));
+ }
+
+ if (event_array.size == 0) {
+ api_set_error(err, kErrorTypeValidation, "'event' is a required key");
+ goto cleanup;
+ }
+
+ autocmd_id = next_autocmd_id++;
+ FOREACH_ITEM(event_array, event_str, {
+ GET_ONE_EVENT(event_nr, event_str, cleanup);
+
+ int retval;
+
+ FOREACH_ITEM(patterns, pat, {
+ // See: TODO(sctx)
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ retval = autocmd_register(autocmd_id,
+ event_nr,
+ pat.data.string.data,
+ (int)pat.data.string.size,
+ au_group,
+ is_once,
+ is_nested,
+ desc,
+ aucmd);
+ });
+
+ if (retval == FAIL) {
+ api_set_error(err, kErrorTypeException, "Failed to set autocmd");
+ goto cleanup;
+ }
+ })
+ });
+
+cleanup:
+ aucmd_exec_free(&aucmd);
+ api_free_array(event_array);
+ api_free_array(patterns);
+
+ return autocmd_id;
+}
+
+/// Delete 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()|
+void nvim_del_autocmd(Integer id, Error *err)
+ FUNC_API_SINCE(9)
+{
+ if (id <= 0) {
+ api_set_error(err, kErrorTypeException, "Invalid autocmd 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|.
+/// @param opts Parameters
+/// - event: (string|table)
+/// Examples:
+/// - event: "pat1"
+/// - event: { "pat1" }
+/// - event: { "pat1", "pat2", "pat3" }
+/// - pattern: (string|table)
+/// - pattern or patterns to match exactly.
+/// - For example, if you have `*.py` as that pattern for the autocmd,
+/// you must pass `*.py` exactly to clear it. `test.py` will not
+/// match the pattern.
+/// - defaults to clearing all patterns.
+/// - NOTE: Cannot be used with {buffer}
+/// - buffer: (bufnr)
+/// - clear only |autocmd-buflocal| autocommands.
+/// - NOTE: Cannot be used with {pattern}
+/// - group: (string|int) The augroup name or id.
+/// - NOTE: If not passed, will only delete autocmds *not* in any group.
+///
+void nvim_clear_autocmds(Dict(clear_autocmds) *opts, Error *err)
+ FUNC_API_SINCE(9)
+{
+ // TODO(tjdevries): Future improvements:
+ // - once: (boolean) - Only clear autocmds with once. See |autocmd-once|
+ // - nested: (boolean) - Only clear autocmds with nested. See |autocmd-nested|
+ // - group: Allow passing "*" or true or something like that to force doing all
+ // autocmds, regardless of their group.
+
+ Array patterns = ARRAY_DICT_INIT;
+ Array event_array = ARRAY_DICT_INIT;
+
+ if (!unpack_string_or_array(&event_array, &opts->event, "event", false, err)) {
+ goto cleanup;
+ }
+
+ if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) {
+ api_set_error(err, kErrorTypeValidation,
+ "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)) {
+ 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("")));
+ }
+
+ // If we didn't pass any events, that means clear all events.
+ if (event_array.size == 0) {
+ 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)) {
+ goto cleanup;
+ }
+ });
+ }
+ } else {
+ FOREACH_ITEM(event_array, event_str, {
+ GET_ONE_EVENT(event_nr, event_str, cleanup);
+
+ FOREACH_ITEM(patterns, pat_object, {
+ char *pat = pat_object.data.string.data;
+ if (!clear_autocmd(event_nr, (char *)pat, au_group, err)) {
+ goto cleanup;
+ }
+ });
+ });
+ }
+
+cleanup:
+ api_free_array(event_array);
+ api_free_array(patterns);
+}
+
+/// Create or get an autocommand group |autocmd-groups|.
+///
+/// To get an existing group id, do:
+/// <pre>
+/// local id = vim.api.nvim_create_augroup("MyGroup", {
+/// clear = false
+/// })
+/// </pre>
+///
+/// @param name String: The name of the group
+/// @param opts Dictionary Parameters
+/// - clear (bool) optional: defaults to true. Clear existing
+/// commands if the group already exists |autocmd-groups|.
+/// @return Integer id of the created group.
+/// @see |autocmd-groups|
+Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augroup) *opts,
+ Error *err)
+ FUNC_API_SINCE(9)
+{
+ char *augroup_name = name.data;
+ bool clear_autocmds = api_object_to_bool(opts->clear, "clear", true, err);
+
+ int augroup = -1;
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ augroup = augroup_add(augroup_name);
+ if (augroup == AUGROUP_ERROR) {
+ api_set_error(err, kErrorTypeException, "Failed to set augroup");
+ return -1;
+ }
+
+ if (clear_autocmds) {
+ FOR_ALL_AUEVENTS(event) {
+ aupat_del_for_event_and_group(event, augroup);
+ }
+ }
+ });
+
+ return augroup;
+}
+
+/// Delete an autocommand group by id.
+///
+/// To get a group id one can use |nvim_get_autocmds()|.
+///
+/// NOTE: behavior differs from |augroup-delete|. When deleting a group, autocommands contained in
+/// this group will also be deleted and cleared. This group will no longer exist.
+/// @param id Integer The id of the group.
+/// @see |nvim_del_augroup_by_name()|
+/// @see |nvim_create_augroup()|
+void nvim_del_augroup_by_id(Integer id, Error *err)
+ FUNC_API_SINCE(9)
+{
+ TRY_WRAP({
+ try_start();
+ char *name = augroup_name((int)id);
+ augroup_del(name, false);
+ try_end(err);
+ });
+}
+
+/// Delete an autocommand group by name.
+///
+/// NOTE: behavior differs from |augroup-delete|. When deleting a group, autocommands contained in
+/// this group will also be deleted and cleared. This group will no longer exist.
+/// @param name String The name of the group.
+/// @see |autocommand-groups|
+void nvim_del_augroup_by_name(String name, Error *err)
+ FUNC_API_SINCE(9)
+{
+ TRY_WRAP({
+ try_start();
+ augroup_del(name.data, false);
+ try_end(err);
+ });
+}
+
+/// Execute all autocommands for {event} that match the corresponding
+/// {opts} |autocmd-execute|.
+/// @param event (String|Array) The event or events to execute
+/// @param opts Dictionary of autocommand options:
+/// - group (string|integer) optional: the autocommand group name or
+/// id to match against. |autocmd-groups|.
+/// - pattern (string|array) optional: defaults to "*" |autocmd-pattern|. Cannot be used
+/// with {buffer}.
+/// - buffer (integer) optional: buffer number |autocmd-buflocal|. Cannot be used with
+/// {pattern}.
+/// - modeline (bool) optional: defaults to true. Process the
+/// modeline after the autocommands |<nomodeline>|.
+/// - data (any): arbitrary data to send to the autocommand callback. See
+/// |nvim_create_autocmd()| for details.
+/// @see |:doautocmd|
+void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err)
+ FUNC_API_SINCE(9)
+{
+ int au_group = AUGROUP_ALL;
+ bool modeline = true;
+
+ buf_T *buf = curbuf;
+
+ Array patterns = ARRAY_DICT_INIT;
+ Array event_array = ARRAY_DICT_INIT;
+
+ Object *data = NULL;
+
+ if (!unpack_string_or_array(&event_array, &event, "event", true, err)) {
+ goto cleanup;
+ }
+
+ switch (opts->group.type) {
+ case kObjectTypeNil:
+ 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);
+ 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);
+ goto cleanup;
+ }
+ break;
+ default:
+ api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer.");
+ 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);
+ goto cleanup;
+ }
+
+ buf = find_buffer_by_handle((Buffer)buf_obj.data.integer, err);
+
+ if (ERROR_SET(err)) {
+ goto cleanup;
+ }
+ }
+
+ if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) {
+ goto cleanup;
+ }
+
+ if (patterns.size == 0) {
+ ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("")));
+ }
+
+ if (opts->data.type != kObjectTypeNil) {
+ data = &opts->data;
+ }
+
+ modeline = api_object_to_bool(opts->modeline, "modeline", true, err);
+
+ 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);
+ })
+ })
+
+ if (did_aucmd && modeline) {
+ do_modelines(0);
+ }
+
+cleanup:
+ api_free_array(event_array);
+ 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));
+ } else if (v->type == kObjectTypeArray) {
+ if (!check_autocmd_string_array(v->data.array, k, err)) {
+ return false;
+ }
+ *array = copy_array(v->data.array);
+ } else {
+ if (required) {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "'%s' must be an array or a string.",
+ k);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// Returns AUGROUP_ERROR if there was a problem with {group}
+static int get_augroup_from_object(Object group, Error *err)
+{
+ int au_group = AUGROUP_ERROR;
+
+ switch (group.type) {
+ case kObjectTypeNil:
+ 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);
+
+ 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);
+ return AUGROUP_ERROR;
+ }
+
+ return au_group;
+ default:
+ api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer.");
+ return AUGROUP_ERROR;
+ }
+}
+
+static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Object 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) {
+ Object *v = &pattern;
+
+ if (v->type == kObjectTypeString) {
+ 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)));
+
+ pat = aucmd_next_pattern(pat, patlen);
+ patlen = aucmd_pattern_length(pat);
+ }
+ } else if (v->type == kObjectTypeArray) {
+ if (!check_autocmd_string_array(*patterns, "pattern", err)) {
+ return false;
+ }
+
+ Array array = v->data.array;
+ FOREACH_ITEM(array, entry, {
+ 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)));
+
+ 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;
+ }
+
+ buf_T *buf = find_buffer_by_handle((Buffer)buffer.data.integer, 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)));
+ }
+
+ return true;
+}
+
+static bool clear_autocmd(event_T event, char *pat, int au_group, Error *err)
+{
+ if (do_autocmd_event(event, pat, false, false, "", true, au_group) == FAIL) {
+ api_set_error(err, kErrorTypeException, "Failed to clear autocmd");
+ return false;
+ }
+
+ return true;
+}
+
+#undef GET_ONE_EVENT
diff --git a/src/nvim/api/autocmd.h b/src/nvim/api/autocmd.h
new file mode 100644
index 0000000000..f9432830d9
--- /dev/null
+++ b/src/nvim/api/autocmd.h
@@ -0,0 +1,11 @@
+#ifndef NVIM_API_AUTOCMD_H
+#define NVIM_API_AUTOCMD_H
+
+#include <stdint.h>
+
+#include "nvim/api/private/defs.h"
+
+#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 2d5403d4b8..806b649ce6 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -22,6 +22,7 @@
#include "nvim/ex_docmd.h"
#include "nvim/extmark.h"
#include "nvim/lua/executor.h"
+#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -35,7 +36,6 @@
# include "api/buffer.c.generated.h"
#endif
-
/// \defgroup api-buffer
///
/// \brief For more information on buffers, see |buffers|
@@ -51,8 +51,7 @@
/// You can use |nvim_buf_is_loaded()| or |nvim_buf_line_count()| to check
/// whether a buffer is loaded.
-
-/// Gets the buffer line count
+/// Returns the number of lines in the given buffer.
///
/// @param buffer Buffer handle, or 0 for current buffer
/// @param[out] err Error details, if any
@@ -133,7 +132,7 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err)
/// - buffer handle
/// - on_reload: Lua callback invoked on reload. The entire buffer
/// content should be considered changed. Args:
-/// - the string "detach"
+/// - the string "reload"
/// - buffer handle
/// - utf_sizes: include UTF-32 and UTF-16 size of the replaced
/// region, as args to `on_lines`.
@@ -247,7 +246,7 @@ void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last, Error *e
return;
}
- redraw_buf_range_later(buf, (linenr_T)first+1, (linenr_T)last);
+ redraw_buf_range_later(buf, (linenr_T)first + 1, (linenr_T)last);
}
/// Gets a line-range from the buffer.
@@ -262,7 +261,7 @@ void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last, Error *e
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
/// @param start First line index
-/// @param end Last line index (exclusive)
+/// @param end Last line index, exclusive
/// @param strict_indexing Whether out-of-bounds should be an error.
/// @param[out] err Error details, if any
/// @return Array of lines, or empty array for unloaded buffer.
@@ -287,8 +286,8 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id,
}
bool oob = false;
- start = normalize_index(buf, start, &oob);
- end = normalize_index(buf, end, &oob);
+ 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");
@@ -358,7 +357,7 @@ static bool check_string_array(Array arr, bool disallow_nl, Error *err)
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
/// @param start First line index
-/// @param end Last line index (exclusive)
+/// @param end Last line index, exclusive
/// @param strict_indexing Whether out-of-bounds should be an error.
/// @param replacement Array of lines to use as replacement
/// @param[out] err Error details, if any
@@ -374,15 +373,14 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
}
bool oob = false;
- start = normalize_index(buf, start, &oob);
- end = normalize_index(buf, end, &oob);
+ 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");
return;
}
-
if (start > end) {
api_set_error(err,
kErrorTypeValidation,
@@ -423,7 +421,7 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
goto end;
}
- bcount_t deleted_bytes = get_region_bytecount(curbuf, start, end, 0, 0);
+ bcount_t deleted_bytes = get_region_bytecount(curbuf, (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
@@ -453,7 +451,7 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
goto end;
}
- if (ml_replace((linenr_T)lnum, (char_u *)lines[i], false) == FAIL) {
+ if (ml_replace((linenr_T)lnum, lines[i], false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to replace line");
goto end;
}
@@ -473,7 +471,7 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
goto end;
}
- if (ml_append((linenr_T)lnum, (char_u *)lines[i], 0, false) == FAIL) {
+ if (ml_append((linenr_T)lnum, lines[i], 0, false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to insert line");
goto end;
}
@@ -493,14 +491,14 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
mark_adjust((linenr_T)start,
(linenr_T)(end - 1),
MAXLNUM,
- (long)extra,
+ (linenr_T)extra,
kExtmarkNOOP);
- extmark_splice(curbuf, (int)start-1, 0, (int)(end-start), 0,
+ extmark_splice(curbuf, (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, (long)extra, true);
+ changed_lines((linenr_T)start, 0, (linenr_T)end, (linenr_T)extra, true);
fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra);
end:
@@ -515,24 +513,25 @@ end:
/// Sets (replaces) a range in the buffer
///
-/// This is recommended over nvim_buf_set_lines when only modifying parts of a
-/// line, as extmarks will be preserved on non-modified parts of the touched
+/// This is recommended over |nvim_buf_set_lines()| when only modifying parts of
+/// a line, as extmarks will be preserved on non-modified parts of the touched
/// lines.
///
-/// Indexing is zero-based and end-exclusive.
+/// Indexing is zero-based. Row indices are end-inclusive, and column indices
+/// are end-exclusive.
///
-/// To insert text at a given index, set `start` and `end` ranges to the same
-/// index. To delete a range, set `replacement` to an array containing
-/// an empty string, or simply an empty array.
+/// To insert text at a given `(row, column)` location, use `start_row = end_row
+/// = row` and `start_col = end_col = col`. To delete the text in a range, use
+/// `replacement = {}`.
///
-/// Prefer nvim_buf_set_lines when adding or deleting entire lines only.
+/// Prefer |nvim_buf_set_lines()| if you are only adding or deleting entire lines.
///
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
/// @param start_row First line index
-/// @param start_column First column
-/// @param end_row Last line index
-/// @param end_column Last column
+/// @param start_col Starting column (byte offset) on first line
+/// @param end_row Last line index, inclusive
+/// @param end_col Ending column (byte offset) on last line, exclusive
/// @param replacement Array of lines to use as replacement
/// @param[out] err Error details, if any
void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, Integer start_col,
@@ -554,25 +553,25 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
// check range is ordered and everything!
// start_row, end_row within buffer len (except add text past the end?)
- start_row = normalize_index(buf, start_row, &oob);
- if (oob || start_row == buf->b_ml.ml_line_count + 1) {
+ start_row = normalize_index(buf, start_row, false, &oob);
+ if (oob) {
api_set_error(err, kErrorTypeValidation, "start_row out of bounds");
return;
}
- end_row = normalize_index(buf, end_row, &oob);
- if (oob || end_row == buf->b_ml.ml_line_count + 1) {
+ end_row = normalize_index(buf, end_row, false, &oob);
+ if (oob) {
api_set_error(err, kErrorTypeValidation, "end_row out of bounds");
return;
}
- char *str_at_start = (char *)ml_get_buf(buf, start_row, false);
+ char *str_at_start = (char *)ml_get_buf(buf, (linenr_T)start_row, false);
if (start_col < 0 || (size_t)start_col > strlen(str_at_start)) {
api_set_error(err, kErrorTypeValidation, "start_col out of bounds");
return;
}
- char *str_at_end = (char *)ml_get_buf(buf, end_row, false);
+ char *str_at_end = (char *)ml_get_buf(buf, (linenr_T)end_row, false);
size_t len_at_end = strlen(str_at_end);
if (end_col < 0 || (size_t)end_col > len_at_end) {
api_set_error(err, kErrorTypeValidation, "end_col out of bounds");
@@ -598,21 +597,20 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
if (start_row == end_row) {
old_byte = (bcount_t)end_col - start_col;
} else {
- const char *bufline;
old_byte += (bcount_t)strlen(str_at_start) - start_col;
for (int64_t i = 1; i < end_row - start_row; i++) {
int64_t lnum = start_row + i;
- bufline = (char *)ml_get_buf(buf, lnum, false);
- old_byte += (bcount_t)(strlen(bufline))+1;
+ const char *bufline = (char *)ml_get_buf(buf, (linenr_T)lnum, false);
+ old_byte += (bcount_t)(strlen(bufline)) + 1;
}
- old_byte += (bcount_t)end_col+1;
+ old_byte += (bcount_t)end_col + 1;
}
String first_item = replacement.items[0].data.string;
- String last_item = replacement.items[replacement.size-1].data.string;
+ String last_item = replacement.items[replacement.size - 1].data.string;
- size_t firstlen = (size_t)start_col+first_item.size;
+ size_t firstlen = (size_t)start_col + first_item.size;
size_t last_part_len = strlen(str_at_end) - (size_t)end_col;
if (replacement.size == 1) {
firstlen += last_part_len;
@@ -620,32 +618,32 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
char *first = xmallocz(firstlen);
char *last = NULL;
memcpy(first, str_at_start, (size_t)start_col);
- memcpy(first+start_col, first_item.data, first_item.size);
- memchrsub(first+start_col, NUL, NL, first_item.size);
+ memcpy(first + start_col, first_item.data, first_item.size);
+ memchrsub(first + start_col, NUL, NL, first_item.size);
if (replacement.size == 1) {
- memcpy(first+start_col+first_item.size, str_at_end+end_col, last_part_len);
+ memcpy(first + start_col + first_item.size, str_at_end + end_col, last_part_len);
} else {
- last = xmallocz(last_item.size+last_part_len);
+ last = xmallocz(last_item.size + last_part_len);
memcpy(last, last_item.data, last_item.size);
memchrsub(last, NUL, NL, last_item.size);
- memcpy(last+last_item.size, str_at_end+end_col, last_part_len);
+ memcpy(last + last_item.size, str_at_end + end_col, last_part_len);
}
char **lines = xcalloc(new_len, sizeof(char *));
lines[0] = first;
new_byte += (bcount_t)(first_item.size);
- for (size_t i = 1; i < new_len-1; i++) {
+ for (size_t i = 1; i < new_len - 1; i++) {
const String l = replacement.items[i].data.string;
// Fill lines[i] with l's contents. Convert NULs to newlines as required by
// NL-used-for-NUL.
lines[i] = xmemdupz(l.data, l.size);
memchrsub(lines[i], NUL, NL, l.size);
- new_byte += (bcount_t)(l.size)+1;
+ new_byte += (bcount_t)(l.size) + 1;
}
if (replacement.size > 1) {
- lines[replacement.size-1] = last;
- new_byte += (bcount_t)(last_item.size)+1;
+ lines[replacement.size - 1] = last;
+ new_byte += (bcount_t)(last_item.size) + 1;
}
try_start();
@@ -665,7 +663,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
}
ptrdiff_t extra = 0; // lines added to text, can be negative
- size_t old_len = (size_t)(end_row-start_row+1);
+ size_t old_len = (size_t)(end_row - start_row + 1);
// 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
@@ -694,7 +692,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
goto end;
}
- if (ml_replace((linenr_T)lnum, (char_u *)lines[i], false) == FAIL) {
+ if (ml_replace((linenr_T)lnum, lines[i], false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to replace line");
goto end;
}
@@ -712,7 +710,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
goto end;
}
- if (ml_append((linenr_T)lnum, (char_u *)lines[i], 0, false) == FAIL) {
+ if (ml_append((linenr_T)lnum, lines[i], 0, false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to insert line");
goto end;
}
@@ -728,19 +726,17 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
mark_adjust((linenr_T)start_row,
(linenr_T)end_row,
MAXLNUM,
- (long)extra,
+ (linenr_T)extra,
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,
+ 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,
- (long)extra, true);
+ changed_lines((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) {
@@ -757,6 +753,109 @@ end:
try_end(err);
}
+/// Gets a range from the buffer.
+///
+/// This differs from |nvim_buf_get_lines()| in that it allows retrieving only
+/// portions of a line.
+///
+/// Indexing is zero-based. Row indices are end-inclusive, and column indices
+/// are end-exclusive.
+///
+/// Prefer |nvim_buf_get_lines()| when retrieving entire lines.
+///
+/// @param channel_id
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param start_row First line index
+/// @param start_col Starting column (byte offset) on first line
+/// @param end_row Last line index, inclusive
+/// @param end_col Ending column (byte offset) on last line, exclusive
+/// @param opts Optional parameters. Currently unused.
+/// @param[out] err Error details, if any
+/// @return Array of lines, or empty array for unloaded buffer.
+ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer,
+ Integer start_row, Integer start_col,
+ Integer end_row, Integer end_col,
+ Dictionary opts, Error *err)
+ FUNC_API_SINCE(9)
+{
+ Array rv = ARRAY_DICT_INIT;
+
+ if (opts.size > 0) {
+ api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ return rv;
+ }
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return rv;
+ }
+
+ // return sentinel value if the buffer isn't loaded
+ if (buf->b_ml.ml_mfp == NULL) {
+ return rv;
+ }
+
+ bool oob = false;
+ 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");
+ 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");
+ return rv;
+ }
+
+ bool replace_nl = (channel_id != VIML_INTERNAL_CALL);
+
+ if (start_row == end_row) {
+ String line = buf_get_text(buf, start_row, start_col, end_col, replace_nl, err);
+ if (ERROR_SET(err)) {
+ return rv;
+ }
+
+ ADD(rv, STRING_OBJ(line));
+ return rv;
+ }
+
+ rv.size = (size_t)(end_row - start_row) + 1;
+ rv.items = xcalloc(rv.size, sizeof(Object));
+
+ rv.items[0] = STRING_OBJ(buf_get_text(buf, start_row, start_col, MAXCOL - 1, replace_nl, err));
+ if (ERROR_SET(err)) {
+ goto end;
+ }
+
+ if (rv.size > 2) {
+ Array tmp = ARRAY_DICT_INIT;
+ tmp.items = &rv.items[1];
+ if (!buf_collect_lines(buf, rv.size - 2, start_row + 1, replace_nl, &tmp, err)) {
+ goto end;
+ }
+ }
+
+ rv.items[rv.size - 1] = STRING_OBJ(buf_get_text(buf, end_row, 0, end_col, replace_nl, err));
+ if (ERROR_SET(err)) {
+ goto end;
+ }
+
+end:
+ if (ERROR_SET(err)) {
+ api_free_array(rv);
+ rv.size = 0;
+ rv.items = NULL;
+ }
+
+ return rv;
+}
+
/// Returns the byte offset of a line (0-indexed). |api-indexing|
///
/// Line 1 (index=0) has offset 0. UTF-8 bytes are counted. EOL is one byte.
@@ -789,7 +888,7 @@ Integer nvim_buf_get_offset(Buffer buffer, Integer index, Error *err)
return 0;
}
- return ml_find_line_or_offset(buf, (int)index+1, NULL, true);
+ return ml_find_line_or_offset(buf, (int)index + 1, NULL, true);
}
/// Gets a buffer-scoped (b:) variable.
@@ -852,11 +951,11 @@ ArrayOf(Dictionary) nvim_buf_get_keymap(uint64_t channel_id, Buffer buffer, Stri
/// @see |nvim_set_keymap()|
///
/// @param buffer Buffer handle, or 0 for current buffer
-void nvim_buf_set_keymap(Buffer buffer, String mode, String lhs, String rhs, Dict(keymap) *opts,
- Error *err)
+void nvim_buf_set_keymap(uint64_t channel_id, Buffer buffer, String mode, String lhs, String rhs,
+ Dict(keymap) *opts, Error *err)
FUNC_API_SINCE(6)
{
- modify_keymap(buffer, false, mode, lhs, rhs, opts, err);
+ modify_keymap(channel_id, buffer, false, mode, lhs, rhs, opts, err);
}
/// Unmaps a buffer-local |mapping| for the given mode.
@@ -864,42 +963,11 @@ void nvim_buf_set_keymap(Buffer buffer, String mode, String lhs, String rhs, Dic
/// @see |nvim_del_keymap()|
///
/// @param buffer Buffer handle, or 0 for current buffer
-void nvim_buf_del_keymap(Buffer buffer, String mode, String lhs, Error *err)
+void nvim_buf_del_keymap(uint64_t channel_id, Buffer buffer, String mode, String lhs, Error *err)
FUNC_API_SINCE(6)
{
String rhs = { .data = "", .size = 0 };
- modify_keymap(buffer, true, mode, lhs, rhs, NULL, err);
-}
-
-/// Gets a map of buffer-local |user-commands|.
-///
-/// @param buffer Buffer handle, or 0 for current buffer
-/// @param opts Optional parameters. Currently not used.
-/// @param[out] err Error details, if any.
-///
-/// @returns Map of maps describing commands.
-Dictionary nvim_buf_get_commands(Buffer buffer, Dict(get_commands) *opts, Error *err)
- 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) {
- api_set_error(err, kErrorTypeValidation, "builtin=true not implemented");
- return (Dictionary)ARRAY_DICT_INIT;
- }
- return commands_array(NULL);
- }
-
- buf_T *buf = find_buffer_by_handle(buffer, err);
- if (builtin || !buf) {
- return (Dictionary)ARRAY_DICT_INIT;
- }
- return commands_array(buf);
+ modify_keymap(channel_id, buffer, true, mode, lhs, rhs, NULL, err);
}
/// Sets a buffer-scoped (b:) variable
@@ -937,45 +1005,6 @@ void nvim_buf_del_var(Buffer buffer, String name, Error *err)
dict_set_var(buf->b_vars, name, NIL, true, false, err);
}
-
-/// Gets a buffer option value
-///
-/// @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, Error *err)
- FUNC_API_SINCE(1)
-{
- buf_T *buf = find_buffer_by_handle(buffer, err);
-
- if (!buf) {
- return (Object)OBJECT_INIT;
- }
-
- return get_option_from(buf, SREQ_BUF, name, err);
-}
-
-/// 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)
-{
- buf_T *buf = find_buffer_by_handle(buffer, err);
-
- if (!buf) {
- return;
- }
-
- set_option_to(channel_id, buf, SREQ_BUF, name, value, err);
-}
-
/// Gets the full file name for the buffer
///
/// @param buffer Buffer handle, or 0 for current buffer
@@ -1013,7 +1042,7 @@ void nvim_buf_set_name(Buffer buffer, String name, Error *err)
// Using aucmd_*: autocommands will be executed by rename_buffer
aco_save_T aco;
aucmd_prepbuf(&aco, buf);
- int ren_ret = rename_buffer((char_u *)name.data);
+ int ren_ret = rename_buffer(name.data);
aucmd_restbuf(&aco);
if (try_end(err)) {
@@ -1127,17 +1156,17 @@ Boolean nvim_buf_del_mark(Buffer buffer, String name, Error *err)
return res;
}
- pos_T *pos = getmark_buf(buf, *name.data, false);
+ fmark_T *fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, *name.data);
- // pos point to NULL when there's no mark with name
- if (pos == NULL) {
+ // 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);
return res;
}
- // pos->lnum is 0 when the mark is not valid in the buffer, or is not set.
- if (pos->lnum != 0) {
+ // 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) {
// since the mark belongs to the buffer delete it.
res = set_mark(buf, name, 0, 0, err);
}
@@ -1210,31 +1239,29 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
return rv;
}
- pos_T *posp;
+ fmark_T *fm;
+ pos_T pos;
char mark = *name.data;
- try_start();
- bufref_T save_buf;
- switch_buffer(&save_buf, buf);
- posp = getmark(mark, false);
- restore_buffer(&save_buf);
-
- if (try_end(err)) {
- return rv;
- }
-
- if (posp == NULL) {
+ fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, mark);
+ if (fm == NULL) {
api_set_error(err, kErrorTypeValidation, "Invalid mark name");
return rv;
}
+ // (0, 0) uppercase/file mark set in another buffer.
+ if (fm->fnum != buf->handle) {
+ pos.lnum = 0;
+ pos.col = 0;
+ } else {
+ pos = fm->mark;
+ }
- ADD(rv, INTEGER_OBJ(posp->lnum));
- ADD(rv, INTEGER_OBJ(posp->col));
+ ADD(rv, INTEGER_OBJ(pos.lnum));
+ ADD(rv, INTEGER_OBJ(pos.col));
return rv;
}
-
/// call a function with buffer as temporary current buffer
///
/// This temporarily switches current buffer to "buffer".
@@ -1273,63 +1300,6 @@ Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err)
return res;
}
-/// Create a new user command |user-commands| in the given buffer.
-///
-/// @param buffer Buffer handle, or 0 for current buffer.
-/// @param[out] err Error details, if any.
-/// @see nvim_add_user_command
-void nvim_buf_add_user_command(Buffer buffer, String name, Object command,
- Dict(user_command) *opts, Error *err)
- FUNC_API_SINCE(9)
-{
- buf_T *target_buf = find_buffer_by_handle(buffer, err);
- if (ERROR_SET(err)) {
- return;
- }
-
- buf_T *save_curbuf = curbuf;
- curbuf = target_buf;
- add_user_command(name, command, opts, UC_BUFFER, err);
- curbuf = save_curbuf;
-}
-
-/// Delete a buffer-local user-defined command.
-///
-/// Only commands created with |:command-buffer| or
-/// |nvim_buf_add_user_command()| can be deleted with this function.
-///
-/// @param buffer Buffer handle, or 0 for current buffer.
-/// @param name Name of the command to delete.
-/// @param[out] err Error details, if any.
-void nvim_buf_del_user_command(Buffer buffer, String name, Error *err)
- FUNC_API_SINCE(9)
-{
- garray_T *gap;
- if (buffer == -1) {
- gap = &ucmds;
- } else {
- buf_T *buf = find_buffer_by_handle(buffer, err);
- gap = &buf->b_ucmds;
- }
-
- for (int i = 0; i < gap->ga_len; i++) {
- ucmd_T *cmd = USER_CMD_GA(gap, i);
- if (!STRCMP(name.data, cmd->uc_name)) {
- free_ucmd(cmd);
-
- gap->ga_len -= 1;
-
- if (i < gap->ga_len) {
- memmove(cmd, cmd + 1, (size_t)(gap->ga_len - i) * sizeof(ucmd_T));
- }
-
- return;
- }
- }
-
- api_set_error(err, kErrorTypeException, "No such user-defined command: %s", name.data);
-}
-
Dictionary nvim__buf_stats(Buffer buffer, Error *err)
{
Dictionary rv = ARRAY_DICT_INIT;
@@ -1386,16 +1356,17 @@ static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra)
}
// Normalizes 0-based indexes to buffer line numbers
-static int64_t normalize_index(buf_T *buf, int64_t index, bool *oob)
+static int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bool *oob)
{
- int64_t line_count = buf->b_ml.ml_line_count;
+ 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 ? line_count + index +1 : index;
+ index = index < 0 ? max_index + index + 1 : index;
// Check for oob
- if (index > line_count) {
+ if (index > max_index) {
*oob = true;
- index = line_count;
+ index = max_index;
} else if (index < 0) {
*oob = true;
index = 0;
diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c
new file mode 100644
index 0000000000..4c2404a0d8
--- /dev/null
+++ b/src/nvim/api/command.c
@@ -0,0 +1,1189 @@
+// 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 "nvim/api/command.h"
+#include "nvim/api/private/converter.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/autocmd.h"
+#include "nvim/ex_docmd.h"
+#include "nvim/lua/executor.h"
+#include "nvim/ops.h"
+#include "nvim/regexp.h"
+#include "nvim/window.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/command.c.generated.h"
+#endif
+
+/// Parse command line.
+///
+/// Doesn't check the validity of command arguments.
+///
+/// @param str Command line string to parse. Cannot contain "\n".
+/// @param opts Optional parameters. Reserved for future use.
+/// @param[out] err Error details, if any.
+/// @return Dictionary containing command information, with these keys:
+/// - cmd: (string) Command name.
+/// - range: (array) Command <range>. Can have 0-2 elements depending on how many items the
+/// range contains. Has no elements if command doesn't accept a range or if
+/// no range was specified, one element if only a single range item was
+/// specified and two elements if both range items were specified.
+/// - count: (number) Any |<count>| that was supplied to the command. -1 if command cannot
+/// take a count.
+/// - reg: (number) The optional command |<register>|, if specified. Empty string if not
+/// specified or if command cannot take a register.
+/// - bang: (boolean) Whether command contains a |<bang>| (!) modifier.
+/// - args: (array) Command arguments.
+/// - addr: (string) Value of |:command-addr|. Uses short name.
+/// - nargs: (string) Value of |:command-nargs|.
+/// - nextcmd: (string) Next command if there are multiple commands separated by a |:bar|.
+/// Empty if there isn't a next command.
+/// - magic: (dictionary) Which characters have special meaning in the command arguments.
+/// - file: (boolean) The command expands filenames. Which means characters such as "%",
+/// "#" and wildcards are expanded.
+/// - bar: (boolean) The "|" character is treated as a command separator and the double
+/// quote character (\") is treated as the start of a comment.
+/// - mods: (dictionary) |:command-modifiers|.
+/// - filter: (dictionary) |:filter|.
+/// - pattern: (string) Filter pattern. Empty string if there is no filter.
+/// - force: (boolean) Whether filter is inverted or not.
+/// - silent: (boolean) |:silent|.
+/// - emsg_silent: (boolean) |:silent!|.
+/// - unsilent: (boolean) |:unsilent|.
+/// - sandbox: (boolean) |:sandbox|.
+/// - noautocmd: (boolean) |:noautocmd|.
+/// - browse: (boolean) |:browse|.
+/// - confirm: (boolean) |:confirm|.
+/// - hide: (boolean) |:hide|.
+/// - keepalt: (boolean) |:keepalt|.
+/// - keepjumps: (boolean) |:keepjumps|.
+/// - keepmarks: (boolean) |:keepmarks|.
+/// - keeppatterns: (boolean) |:keeppatterns|.
+/// - lockmarks: (boolean) |:lockmarks|.
+/// - noswapfile: (boolean) |:noswapfile|.
+/// - tab: (integer) |:tab|.
+/// - verbose: (integer) |:verbose|. -1 when omitted.
+/// - vertical: (boolean) |:vertical|.
+/// - split: (string) Split modifier string, is an empty string when there's no split
+/// modifier. If there is a split modifier it can be one of:
+/// - "aboveleft": |:aboveleft|.
+/// - "belowright": |:belowright|.
+/// - "topleft": |:topleft|.
+/// - "botright": |:botright|.
+Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
+ FUNC_API_SINCE(10) FUNC_API_FAST
+{
+ Dictionary result = ARRAY_DICT_INIT;
+
+ if (opts.size > 0) {
+ api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ return result;
+ }
+
+ // Parse command line
+ exarg_T ea;
+ CmdParseInfo cmdinfo;
+ char *cmdline = string_to_cstr(str);
+ char *errormsg = NULL;
+
+ if (!parse_cmdline(cmdline, &ea, &cmdinfo, &errormsg)) {
+ if (errormsg != NULL) {
+ api_set_error(err, kErrorTypeException, "Error while parsing command line: %s", errormsg);
+ } else {
+ api_set_error(err, kErrorTypeException, "Error while parsing command line");
+ }
+ goto end;
+ }
+
+ // Parse arguments
+ Array args = ARRAY_DICT_INIT;
+ size_t length = STRLEN(ea.arg);
+
+ // For nargs = 1 or '?', pass the entire argument list as a single argument,
+ // 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)));
+ }
+ } else {
+ size_t end = 0;
+ size_t len = 0;
+ char *buf = xcalloc(length, sizeof(char));
+ bool done = false;
+
+ while (!done) {
+ done = uc_split_args_iter(ea.arg, length, &end, buf, &len);
+ if (len > 0) {
+ ADD(args, STRING_OBJ(cstrn_to_string(buf, len)));
+ }
+ }
+
+ xfree(buf);
+ }
+
+ ucmd_T *cmd = NULL;
+ if (ea.cmdidx == CMD_USER) {
+ cmd = USER_CMD(ea.useridx);
+ } else if (ea.cmdidx == CMD_USER_BUF) {
+ cmd = USER_CMD_GA(&curbuf->b_ucmds, ea.useridx);
+ }
+
+ if (cmd != NULL) {
+ PUT(result, "cmd", CSTR_TO_OBJ((char *)cmd->uc_name));
+ } else {
+ PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx)));
+ }
+
+ if ((ea.argt & EX_RANGE) && ea.addr_count > 0) {
+ Array range = ARRAY_DICT_INIT;
+ if (ea.addr_count > 1) {
+ ADD(range, INTEGER_OBJ(ea.line1));
+ }
+ ADD(range, INTEGER_OBJ(ea.line2));
+ PUT(result, "range", ARRAY_OBJ(range));
+ } else {
+ PUT(result, "range", ARRAY_OBJ(ARRAY_DICT_INIT));
+ }
+
+ if (ea.argt & EX_COUNT) {
+ if (ea.addr_count > 0) {
+ PUT(result, "count", INTEGER_OBJ(ea.line2));
+ } else if (cmd != NULL) {
+ PUT(result, "count", INTEGER_OBJ(cmd->uc_def));
+ } else {
+ PUT(result, "count", INTEGER_OBJ(0));
+ }
+ } else {
+ PUT(result, "count", INTEGER_OBJ(-1));
+ }
+
+ char reg[2];
+ reg[0] = (char)ea.regname;
+ reg[1] = '\0';
+ PUT(result, "reg", CSTR_TO_OBJ(reg));
+
+ PUT(result, "bang", BOOLEAN_OBJ(ea.forceit));
+ PUT(result, "args", ARRAY_OBJ(args));
+
+ char nargs[2];
+ if (ea.argt & EX_EXTRA) {
+ if (ea.argt & EX_NOSPC) {
+ if (ea.argt & EX_NEEDARG) {
+ nargs[0] = '1';
+ } else {
+ nargs[0] = '?';
+ }
+ } else if (ea.argt & EX_NEEDARG) {
+ nargs[0] = '+';
+ } else {
+ nargs[0] = '*';
+ }
+ } else {
+ nargs[0] = '0';
+ }
+ nargs[1] = '\0';
+ PUT(result, "nargs", CSTR_TO_OBJ(nargs));
+
+ const char *addr;
+ switch (ea.addr_type) {
+ case ADDR_LINES:
+ addr = "line";
+ break;
+ case ADDR_ARGUMENTS:
+ addr = "arg";
+ break;
+ case ADDR_BUFFERS:
+ addr = "buf";
+ break;
+ case ADDR_LOADED_BUFFERS:
+ addr = "load";
+ break;
+ case ADDR_WINDOWS:
+ addr = "win";
+ break;
+ case ADDR_TABS:
+ addr = "tab";
+ break;
+ case ADDR_QUICKFIX:
+ addr = "qf";
+ break;
+ case ADDR_NONE:
+ addr = "none";
+ break;
+ default:
+ addr = "?";
+ break;
+ }
+ PUT(result, "addr", CSTR_TO_OBJ(addr));
+ PUT(result, "nextcmd", CSTR_TO_OBJ((char *)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("")));
+ PUT(filter, "force", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_filter_force));
+ PUT(mods, "filter", DICTIONARY_OBJ(filter));
+
+ PUT(mods, "silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SILENT));
+ PUT(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_ERRSILENT));
+ PUT(mods, "unsilent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_UNSILENT));
+ PUT(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX));
+ PUT(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_NOAUTOCMD));
+ PUT(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.cmod_tab));
+ PUT(mods, "verbose", INTEGER_OBJ(cmdinfo.cmdmod.cmod_verbose - 1));
+ PUT(mods, "browse", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_BROWSE));
+ PUT(mods, "confirm", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_CONFIRM));
+ PUT(mods, "hide", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_HIDE));
+ PUT(mods, "keepalt", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPALT));
+ PUT(mods, "keepjumps", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPJUMPS));
+ PUT(mods, "keepmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPMARKS));
+ PUT(mods, "keeppatterns", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPPATTERNS));
+ PUT(mods, "lockmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_LOCKMARKS));
+ PUT(mods, "noswapfile", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_NOSWAPFILE));
+ PUT(mods, "vertical", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_split & WSP_VERT));
+
+ const char *split;
+ if (cmdinfo.cmdmod.cmod_split & WSP_BOT) {
+ split = "botright";
+ } else if (cmdinfo.cmdmod.cmod_split & WSP_TOP) {
+ split = "topleft";
+ } else if (cmdinfo.cmdmod.cmod_split & WSP_BELOW) {
+ split = "belowright";
+ } else if (cmdinfo.cmdmod.cmod_split & WSP_ABOVE) {
+ split = "aboveleft";
+ } else {
+ split = "";
+ }
+ PUT(mods, "split", CSTR_TO_OBJ(split));
+
+ PUT(result, "mods", DICTIONARY_OBJ(mods));
+
+ Dictionary magic = ARRAY_DICT_INIT;
+ PUT(magic, "file", BOOLEAN_OBJ(cmdinfo.magic.file));
+ PUT(magic, "bar", BOOLEAN_OBJ(cmdinfo.magic.bar));
+ PUT(result, "magic", DICTIONARY_OBJ(magic));
+
+ undo_cmdmod(&cmdinfo.cmdmod);
+end:
+ xfree(cmdline);
+ return result;
+}
+
+/// Executes an Ex command.
+///
+/// Unlike |nvim_command()| this command takes a structured Dictionary instead of a String. This
+/// allows for easier construction and manipulation of an Ex command. This also allows for things
+/// such as having spaces inside a command argument, expanding filenames in a command that otherwise
+/// doesn't expand filenames, etc.
+///
+/// On execution error: fails with VimL error, updates v:errmsg.
+///
+/// @see |nvim_exec()|
+/// @see |nvim_command()|
+///
+/// @param cmd Command to execute. Must be a Dictionary that can contain the same values as
+/// the return value of |nvim_parse_cmd()| except "addr", "nargs" and "nextcmd"
+/// which are ignored if provided. All values except for "cmd" are optional.
+/// @param opts Optional parameters.
+/// - output: (boolean, default false) Whether to return command output.
+/// @param[out] err Error details, if any.
+/// @return Command output (non-error, non-shell |:!|) if `output` is true, else empty string.
+String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error *err)
+ FUNC_API_SINCE(10)
+{
+ exarg_T ea;
+ memset(&ea, 0, sizeof(ea));
+
+ CmdParseInfo cmdinfo;
+ memset(&cmdinfo, 0, sizeof(cmdinfo));
+
+ char *cmdline = NULL;
+ char *cmdname = NULL;
+ char **args = NULL;
+ size_t argc = 0;
+
+ String retv = (String)STRING_INIT;
+
+#define OBJ_TO_BOOL(var, value, default, varname) \
+ do { \
+ (var) = api_object_to_bool(value, varname, default, err); \
+ if (ERROR_SET(err)) { \
+ goto end; \
+ } \
+ } while (0)
+
+#define OBJ_TO_CMOD_FLAG(flag, value, default, varname) \
+ do { \
+ if (api_object_to_bool(value, varname, default, err)) { \
+ cmdinfo.cmdmod.cmod_flags |= (flag); \
+ } \
+ if (ERROR_SET(err)) { \
+ 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");
+ }
+
+ cmdname = string_to_cstr(cmd->cmd.data.string);
+ ea.cmd = cmdname;
+
+ char *p = find_ex_command(&ea, NULL);
+
+ // If this looks like an undefined user command and there are CmdUndefined
+ // autocommands defined, trigger the matching autocommands.
+ if (p != NULL && ea.cmdidx == CMD_SIZE && ASCII_ISUPPER(*ea.cmd)
+ && has_event(EVENT_CMDUNDEFINED)) {
+ p = xstrdup(cmdname);
+ int ret = apply_autocmds(EVENT_CMDUNDEFINED, p, p, true, NULL);
+ xfree(p);
+ // If the autocommands did something and didn't cause an error, try
+ // finding the command again.
+ 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);
+ }
+
+ // 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.
+ if (!IS_USER_CMDIDX(ea.cmdidx)) {
+ ea.argt = get_cmd_argt(ea.cmdidx);
+ }
+
+ // 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");
+ }
+ // Check if every argument is valid
+ for (size_t i = 0; i < cmd->args.data.array.size; i++) {
+ Object elem = cmd->args.data.array.items[i];
+ if (elem.type != kObjectTypeString) {
+ VALIDATION_ERROR("Command argument must be a String");
+ } else if (string_iswhite(elem.data.string)) {
+ VALIDATION_ERROR("Command argument must have non-whitespace characters");
+ }
+ }
+
+ argc = cmd->args.data.array.size;
+ bool argc_valid;
+
+ // Check if correct number of arguments is used.
+ switch (ea.argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) {
+ case EX_EXTRA | EX_NOSPC | EX_NEEDARG:
+ argc_valid = argc == 1;
+ break;
+ case EX_EXTRA | EX_NOSPC:
+ argc_valid = argc <= 1;
+ break;
+ case EX_EXTRA | EX_NEEDARG:
+ argc_valid = argc >= 1;
+ break;
+ case EX_EXTRA:
+ argc_valid = true;
+ break;
+ default:
+ argc_valid = argc == 0;
+ break;
+ }
+
+ if (!argc_valid) {
+ argc = 0; // Ensure that args array isn't erroneously freed at the end.
+ VALIDATION_ERROR("Incorrect number of arguments supplied");
+ }
+
+ if (argc != 0) {
+ args = xcalloc(argc, sizeof(char *));
+
+ for (size_t i = 0; i < argc; i++) {
+ args[i] = string_to_cstr(cmd->args.data.array.items[i].data.string);
+ }
+ }
+ }
+
+ // 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, argc > 0 ? args[0] : 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");
+ }
+
+ Array range = cmd->range.data.array;
+ 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");
+ }
+ }
+
+ if (range.size > 0) {
+ ea.line1 = (linenr_T)range.items[0].data.integer;
+ ea.line2 = (linenr_T)range.items[range.size - 1].data.integer;
+ }
+
+ if (invalid_range(&ea) != NULL) {
+ VALIDATION_ERROR("Invalid range provided");
+ }
+ }
+ if (ea.addr_count == 0) {
+ if (ea.argt & EX_DFLALL) {
+ set_cmd_dflall_range(&ea); // Default range for range=%
+ } else {
+ ea.line1 = ea.line2 = get_cmd_default_range(&ea); // Default range.
+
+ if (ea.addr_type == ADDR_OTHER) {
+ // Default is 1, not cursor.
+ ea.line2 = 1;
+ }
+ }
+ }
+
+ 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->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);
+ }
+ 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");
+ }
+
+ Dict(cmd_magic) magic = { 0 };
+ if (!api_dict_to_keydict(&magic, KeyDict_cmd_magic_get_field,
+ cmd->magic.data.dictionary, 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'");
+ } else {
+ cmdinfo.magic.file = ea.argt & EX_XFILE;
+ 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)) {
+ 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 (!api_dict_to_keydict(&filter, KeyDict_cmd_mods_filter_get_field,
+ mods.filter.data.dictionary, 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'");
+
+ // "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);
+ 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 || mods.tab.data.integer < 0) {
+ VALIDATION_ERROR("'mods.tab' must be a non-negative Integer");
+ }
+ cmdinfo.cmdmod.cmod_tab = (int)mods.tab.data.integer + 1;
+ }
+
+ if (HAS_KEY(mods.verbose)) {
+ if (mods.verbose.type != kObjectTypeInteger) {
+ VALIDATION_ERROR("'mods.verbose' must be a Integer");
+ } else if ((int)mods.verbose.data.integer >= 0) {
+ // Silently ignore negative integers to allow mods.verbose to be set to -1.
+ cmdinfo.cmdmod.cmod_verbose = (int)mods.verbose.data.integer + 1;
+ }
+ }
+
+ bool vertical;
+ OBJ_TO_BOOL(vertical, mods.vertical, false, "'mods.vertical'");
+ cmdinfo.cmdmod.cmod_split |= (vertical ? WSP_VERT : 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) {
+ // Empty string, do nothing.
+ } else if (STRCMP(mods.split.data.string.data, "aboveleft") == 0
+ || STRCMP(mods.split.data.string.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) {
+ cmdinfo.cmdmod.cmod_split |= WSP_BELOW;
+ } else if (STRCMP(mods.split.data.string.data, "topleft") == 0) {
+ cmdinfo.cmdmod.cmod_split |= WSP_TOP;
+ } else if (STRCMP(mods.split.data.string.data, "botright") == 0) {
+ cmdinfo.cmdmod.cmod_split |= WSP_BOT;
+ } else {
+ VALIDATION_ERROR("Invalid value for 'mods.split'");
+ }
+ }
+
+ 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.silent, 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'");
+
+ if ((cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX) && !(ea.argt & EX_SBOXOK)) {
+ VALIDATION_ERROR("Command cannot be run in sandbox");
+ }
+ }
+
+ // Finally, build the command line string that will be stored inside ea.cmdlinep.
+ // This also sets the values of ea.cmd, ea.arg, ea.args and ea.arglens.
+ build_cmdline_str(&cmdline, &ea, &cmdinfo, args, argc);
+ ea.cmdlinep = &cmdline;
+
+ garray_T capture_local;
+ const int save_msg_silent = msg_silent;
+ garray_T * const save_capture_ga = capture_ga;
+
+ if (output) {
+ ga_init(&capture_local, 1, 80);
+ capture_ga = &capture_local;
+ }
+
+ TRY_WRAP({
+ try_start();
+ if (output) {
+ msg_silent++;
+ }
+
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ execute_cmd(&ea, &cmdinfo, false);
+ });
+
+ if (output) {
+ capture_ga = save_capture_ga;
+ msg_silent = save_msg_silent;
+ }
+
+ try_end(err);
+ });
+
+ if (ERROR_SET(err)) {
+ goto clear_ga;
+ }
+
+ if (output && capture_local.ga_len > 1) {
+ retv = (String){
+ .data = capture_local.ga_data,
+ .size = (size_t)capture_local.ga_len,
+ };
+ // redir usually (except :echon) prepends a newline.
+ if (retv.data[0] == '\n') {
+ memmove(retv.data, retv.data + 1, retv.size - 1);
+ retv.data[retv.size - 1] = '\0';
+ retv.size = retv.size - 1;
+ }
+ goto end;
+ }
+clear_ga:
+ if (output) {
+ ga_clear(&capture_local);
+ }
+end:
+ xfree(cmdline);
+ xfree(cmdname);
+ xfree(ea.args);
+ xfree(ea.arglens);
+ for (size_t i = 0; i < argc; i++) {
+ xfree(args[i]);
+ }
+ xfree(args);
+
+ return retv;
+
+#undef OBJ_TO_BOOL
+#undef OBJ_TO_CMOD_FLAG
+#undef VALIDATION_ERROR
+}
+
+/// Check if a string contains only whitespace characters.
+static bool string_iswhite(String str)
+{
+ for (size_t i = 0; i < str.size; i++) {
+ if (!ascii_iswhite(str.data[i])) {
+ // Found a non-whitespace character
+ return false;
+ } else if (str.data[i] == NUL) {
+ // Terminate at first occurrence of a NUL character
+ break;
+ }
+ }
+ return true;
+}
+
+/// Build cmdline string for command, used by `nvim_cmd()`.
+///
+/// @return OK or FAIL.
+static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdinfo, char **args,
+ size_t argc)
+{
+ StringBuilder cmdline = KV_INITIAL_VALUE;
+
+ // Add command modifiers
+ if (cmdinfo->cmdmod.cmod_tab != 0) {
+ kv_printf(cmdline, "%dtab ", cmdinfo->cmdmod.cmod_tab - 1);
+ }
+ if (cmdinfo->cmdmod.cmod_verbose > 0) {
+ kv_printf(cmdline, "%dverbose ", cmdinfo->cmdmod.cmod_verbose - 1);
+ }
+
+ if (cmdinfo->cmdmod.cmod_flags & CMOD_ERRSILENT) {
+ kv_concat(cmdline, "silent! ");
+ } else if (cmdinfo->cmdmod.cmod_flags & CMOD_SILENT) {
+ kv_concat(cmdline, "silent ");
+ }
+
+ if (cmdinfo->cmdmod.cmod_flags & CMOD_UNSILENT) {
+ kv_concat(cmdline, "unsilent ");
+ }
+
+ switch (cmdinfo->cmdmod.cmod_split & (WSP_ABOVE | WSP_BELOW | WSP_TOP | WSP_BOT)) {
+ case WSP_ABOVE:
+ kv_concat(cmdline, "aboveleft ");
+ break;
+ case WSP_BELOW:
+ kv_concat(cmdline, "belowright ");
+ break;
+ case WSP_TOP:
+ kv_concat(cmdline, "topleft ");
+ break;
+ case WSP_BOT:
+ kv_concat(cmdline, "botright ");
+ break;
+ default:
+ break;
+ }
+
+#define CMDLINE_APPEND_IF(cond, str) \
+ do { \
+ if (cond) { \
+ kv_concat(cmdline, str); \
+ } \
+ } while (0)
+
+ CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_split & WSP_VERT, "vertical ");
+ CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_SANDBOX, "sandbox ");
+ CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_NOAUTOCMD, "noautocmd ");
+ CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_BROWSE, "browse ");
+ CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_CONFIRM, "confirm ");
+ CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_HIDE, "hide ");
+ CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_KEEPALT, "keepalt ");
+ CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_KEEPJUMPS, "keepjumps ");
+ CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_KEEPMARKS, "keepmarks ");
+ CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_KEEPPATTERNS, "keeppatterns ");
+ CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_LOCKMARKS, "lockmarks ");
+ CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_NOSWAPFILE, "noswapfile ");
+#undef CMDLINE_APPEND_IF
+
+ // Command range / count.
+ if (eap->argt & EX_RANGE) {
+ if (eap->addr_count == 1) {
+ kv_printf(cmdline, "%" PRIdLINENR, eap->line2);
+ } else if (eap->addr_count > 1) {
+ kv_printf(cmdline, "%" PRIdLINENR ",%" PRIdLINENR, eap->line1, eap->line2);
+ eap->addr_count = 2; // Make sure address count is not greater than 2
+ }
+ }
+
+ // Keep the index of the position where command name starts, so eap->cmd can point to it.
+ size_t cmdname_idx = cmdline.size;
+ kv_printf(cmdline, "%s", eap->cmd);
+
+ // Command bang.
+ if (eap->argt & EX_BANG && eap->forceit) {
+ kv_printf(cmdline, "!");
+ }
+
+ // Command register.
+ if (eap->argt & EX_REGSTR && eap->regname) {
+ kv_printf(cmdline, " %c", eap->regname);
+ }
+
+ // Iterate through each argument and store the starting index and length of each argument
+ size_t *argidx = xcalloc(argc, sizeof(size_t));
+ eap->argc = argc;
+ eap->arglens = xcalloc(argc, sizeof(size_t));
+ for (size_t i = 0; i < argc; i++) {
+ argidx[i] = cmdline.size + 1; // add 1 to account for the space.
+ eap->arglens[i] = STRLEN(args[i]);
+ kv_printf(cmdline, " %s", args[i]);
+ }
+
+ // Now that all the arguments are appended, use the command index and argument indices to set the
+ // values of eap->cmd, eap->arg and eap->args.
+ eap->cmd = cmdline.items + cmdname_idx;
+ eap->args = xcalloc(argc, sizeof(char *));
+ for (size_t i = 0; i < argc; i++) {
+ eap->args[i] = cmdline.items + argidx[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;
+
+ // Finally, make cmdlinep point to the cmdline string.
+ *cmdlinep = cmdline.items;
+ xfree(argidx);
+
+ // Replace, :make and :grep with 'makeprg' and 'grepprg'.
+ char *p = replace_makeprg(eap, eap->arg, cmdlinep);
+ if (p != eap->arg) {
+ // If replace_makeprg modified the cmdline string, correct the argument pointers.
+ assert(argc == 1);
+ eap->arg = p;
+ eap->args[0] = p;
+ }
+}
+
+/// Create a new user command |user-commands|
+///
+/// {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.
+///
+/// Example:
+/// <pre>
+/// :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {})
+/// :SayHello
+/// Hello world!
+/// </pre>
+///
+/// @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
+/// from Lua, the command can also be a Lua function. The function is called with a
+/// single table argument that contains the following keys:
+/// - 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>|
+/// - 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>|
+/// - range: (number) The number of items in the command range: 0, 1, or 2 |<range>|
+/// - count: (number) Any count supplied |<count>|
+/// - reg: (string) The optional register, if specified |<reg>|
+/// - 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[out] err Error details, if any.
+void nvim_create_user_command(String name, Object command, Dict(user_command) *opts, Error *err)
+ FUNC_API_SINCE(9)
+{
+ create_user_command(name, command, opts, 0, err);
+}
+
+/// Delete a user-defined command.
+///
+/// @param name Name of the command to delete.
+/// @param[out] err Error details, if any.
+void nvim_del_user_command(String name, Error *err)
+ FUNC_API_SINCE(9)
+{
+ nvim_buf_del_user_command(-1, name, err);
+}
+
+/// Create a new user command |user-commands| in the given buffer.
+///
+/// @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,
+ Dict(user_command) *opts, Error *err)
+ FUNC_API_SINCE(9)
+{
+ buf_T *target_buf = find_buffer_by_handle(buffer, err);
+ if (ERROR_SET(err)) {
+ return;
+ }
+
+ buf_T *save_curbuf = curbuf;
+ curbuf = target_buf;
+ create_user_command(name, command, opts, UC_BUFFER, err);
+ curbuf = save_curbuf;
+}
+
+/// Delete a buffer-local user-defined command.
+///
+/// Only commands created with |:command-buffer| or
+/// |nvim_buf_create_user_command()| can be deleted with this function.
+///
+/// @param buffer Buffer handle, or 0 for current buffer.
+/// @param name Name of the command to delete.
+/// @param[out] err Error details, if any.
+void nvim_buf_del_user_command(Buffer buffer, String name, Error *err)
+ FUNC_API_SINCE(9)
+{
+ garray_T *gap;
+ if (buffer == -1) {
+ gap = &ucmds;
+ } else {
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ gap = &buf->b_ucmds;
+ }
+
+ for (int i = 0; i < gap->ga_len; i++) {
+ ucmd_T *cmd = USER_CMD_GA(gap, i);
+ if (!STRCMP(name.data, cmd->uc_name)) {
+ free_ucmd(cmd);
+
+ gap->ga_len -= 1;
+
+ if (i < gap->ga_len) {
+ memmove(cmd, cmd + 1, (size_t)(gap->ga_len - i) * sizeof(ucmd_T));
+ }
+
+ return;
+ }
+ }
+
+ api_set_error(err, kErrorTypeException, "No such user-defined command: %s", name.data);
+}
+
+void create_user_command(String name, Object command, Dict(user_command) *opts, int flags,
+ Error *err)
+{
+ uint32_t argt = 0;
+ long def = -1;
+ cmd_addr_T addr_type_arg = ADDR_NONE;
+ int compl = EXPAND_NOTHING;
+ char *compl_arg = NULL;
+ 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");
+ goto err;
+ }
+
+ if (mb_islower(name.data[0])) {
+ api_set_error(err, kErrorTypeValidation, "'name' must begin with an uppercase letter");
+ goto err;
+ }
+
+ if (HAS_KEY(opts->range) && HAS_KEY(opts->count)) {
+ api_set_error(err, kErrorTypeValidation, "'range' and 'count' are mutually exclusive");
+ goto err;
+ }
+
+ if (opts->nargs.type == kObjectTypeInteger) {
+ switch (opts->nargs.data.integer) {
+ case 0:
+ // Default value, nothing to do
+ break;
+ case 1:
+ argt |= EX_EXTRA | EX_NOSPC | EX_NEEDARG;
+ break;
+ default:
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
+ goto err;
+ }
+ } else if (opts->nargs.type == kObjectTypeString) {
+ if (opts->nargs.data.string.size > 1) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
+ goto err;
+ }
+
+ switch (opts->nargs.data.string.data[0]) {
+ case '*':
+ argt |= EX_EXTRA;
+ break;
+ case '?':
+ argt |= EX_EXTRA | EX_NOSPC;
+ break;
+ case '+':
+ argt |= EX_EXTRA | EX_NEEDARG;
+ break;
+ default:
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
+ goto err;
+ }
+ } else if (HAS_KEY(opts->nargs)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
+ goto err;
+ }
+
+ if (HAS_KEY(opts->complete) && !argt) {
+ api_set_error(err, kErrorTypeValidation, "'complete' used without 'nargs'");
+ goto err;
+ }
+
+ if (opts->range.type == kObjectTypeBoolean) {
+ if (opts->range.data.boolean) {
+ argt |= EX_RANGE;
+ 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'");
+ goto err;
+ }
+ } 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;
+ }
+
+ if (opts->count.type == kObjectTypeBoolean) {
+ if (opts->count.data.boolean) {
+ argt |= EX_COUNT | EX_ZEROR | EX_RANGE;
+ addr_type_arg = ADDR_OTHER;
+ def = 0;
+ }
+ } else if (opts->count.type == kObjectTypeInteger) {
+ 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;
+ }
+
+ 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'");
+ 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)) {
+ argt |= EX_BANG;
+ } else if (ERROR_SET(err)) {
+ goto err;
+ }
+
+ if (api_object_to_bool(opts->bar, "bar", false, err)) {
+ argt |= EX_TRLBAR;
+ } else if (ERROR_SET(err)) {
+ goto err;
+ }
+
+ if (api_object_to_bool(opts->register_, "register", false, err)) {
+ argt |= EX_REGSTR;
+ } else if (ERROR_SET(err)) {
+ goto err;
+ }
+
+ if (api_object_to_bool(opts->keepscript, "keepscript", false, err)) {
+ argt |= EX_KEEPSCRIPT;
+ } else if (ERROR_SET(err)) {
+ goto err;
+ }
+
+ bool force = api_object_to_bool(opts->force, "force", true, err);
+ if (ERROR_SET(err)) {
+ goto err;
+ }
+
+ if (opts->complete.type == kObjectTypeLuaRef) {
+ compl = EXPAND_USER_LUA;
+ compl_luaref = api_new_luaref(opts->complete.data.luaref);
+ } else if (opts->complete.type == kObjectTypeString) {
+ if (parse_compl_arg(opts->complete.data.string.data,
+ (int)opts->complete.data.string.size, &compl, &argt,
+ &compl_arg) != OK) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'");
+ goto err;
+ }
+ } else if (HAS_KEY(opts->complete)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'");
+ goto err;
+ }
+
+ if (opts->preview.type == kObjectTypeLuaRef) {
+ 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) {
+ case kObjectTypeLuaRef:
+ luaref = api_new_luaref(command.data.luaref);
+ if (opts->desc.type == kObjectTypeString) {
+ rep = opts->desc.data.string.data;
+ } else {
+ snprintf((char *)IObuff, IOSIZE, "<Lua function %d>", luaref);
+ rep = (char *)IObuff;
+ }
+ break;
+ case kObjectTypeString:
+ rep = command.data.string.data;
+ break;
+ default:
+ api_set_error(err, kErrorTypeValidation, "'command' must be a string or Lua function");
+ 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
+ }
+
+ return;
+
+err:
+ NLUA_CLEAR_REF(luaref);
+ NLUA_CLEAR_REF(compl_luaref);
+ xfree(compl_arg);
+}
+/// Gets a map of global (non-buffer-local) Ex commands.
+///
+/// Currently only |user-commands| are supported, not builtin Ex commands.
+///
+/// @param opts Optional parameters. Currently only supports
+/// {"builtin":false}
+/// @param[out] err Error details, if any.
+///
+/// @returns Map of maps describing commands.
+Dictionary nvim_get_commands(Dict(get_commands) *opts, Error *err)
+ FUNC_API_SINCE(4)
+{
+ return nvim_buf_get_commands(-1, opts, err);
+}
+
+/// Gets a map of buffer-local |user-commands|.
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param opts Optional parameters. Currently not used.
+/// @param[out] err Error details, if any.
+///
+/// @returns Map of maps describing commands.
+Dictionary nvim_buf_get_commands(Buffer buffer, Dict(get_commands) *opts, Error *err)
+ 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) {
+ api_set_error(err, kErrorTypeValidation, "builtin=true not implemented");
+ return (Dictionary)ARRAY_DICT_INIT;
+ }
+ return commands_array(NULL);
+ }
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (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
new file mode 100644
index 0000000000..b1c9230551
--- /dev/null
+++ b/src/nvim/api/command.h
@@ -0,0 +1,11 @@
+#ifndef NVIM_API_COMMAND_H
+#define NVIM_API_COMMAND_H
+
+#include "nvim/api/private/defs.h"
+#include "nvim/decoration.h"
+#include "nvim/ex_cmds.h"
+
+#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 76b699800e..abaac07755 100644
--- a/src/nvim/api/deprecated.c
+++ b/src/nvim/api/deprecated.c
@@ -22,11 +22,11 @@
/// @deprecated
/// @see nvim_exec
-String nvim_command_output(String command, Error *err)
+String nvim_command_output(uint64_t channel_id, String command, Error *err)
FUNC_API_SINCE(1)
FUNC_API_DEPRECATED_SINCE(7)
{
- return nvim_exec(command, true, err);
+ return nvim_exec(channel_id, command, true, err);
}
/// @deprecated Use nvim_exec_lua() instead.
@@ -51,11 +51,10 @@ Integer nvim_buf_get_number(Buffer buffer, Error *err)
FUNC_API_SINCE(1)
FUNC_API_DEPRECATED_SINCE(2)
{
- Integer rv = 0;
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
- return rv;
+ return 0;
}
return buf->b_fnum;
@@ -79,7 +78,6 @@ void nvim_buf_clear_highlight(Buffer buffer, Integer ns_id, Integer line_start,
nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end, err);
}
-
/// Set the virtual text (annotation) for a buffer line.
///
/// @deprecated use nvim_buf_set_extmark to use full virtual text
@@ -130,7 +128,7 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A
return 0;
}
- uint64_t ns_id = src2ns(&src_id);
+ uint32_t ns_id = src2ns(&src_id);
int width;
VirtText virt_text = parse_virt_text(chunks, err, &width);
@@ -138,7 +136,6 @@ 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);
if (existing) {
@@ -148,11 +145,12 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A
return src_id;
}
- Decoration *decor = xcalloc(1, sizeof(*decor));
- decor->virt_text = virt_text;
- decor->virt_text_width = width;
+ Decoration decor = DECORATION_INIT;
+ decor.virt_text = virt_text;
+ decor.virt_text_width = width;
+ decor.priority = 0;
- extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, decor, true,
+ extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, &decor, true,
false, kExtmarkNoUndo);
return src_id;
}
@@ -192,7 +190,7 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)
String rv = { .size = 0 };
index = convert_index(index);
- Array slice = nvim_buf_get_lines(0, buffer, index, index+1, true, err);
+ Array slice = nvim_buf_get_lines(0, buffer, index, index + 1, true, err);
if (!ERROR_SET(err) && slice.size) {
rv = slice.items[0].data.string;
@@ -221,7 +219,7 @@ void buffer_set_line(Buffer buffer, Integer index, String line, Error *err)
Object l = STRING_OBJ(line);
Array array = { .items = &l, .size = 1 };
index = convert_index(index);
- nvim_buf_set_lines(0, buffer, index, index+1, true, array, err);
+ nvim_buf_set_lines(0, buffer, index, index + 1, true, array, err);
}
/// Deletes a buffer line
@@ -239,7 +237,7 @@ void buffer_del_line(Buffer buffer, Integer index, Error *err)
{
Array array = ARRAY_DICT_INIT;
index = convert_index(index);
- nvim_buf_set_lines(0, buffer, index, index+1, true, array, err);
+ nvim_buf_set_lines(0, buffer, index, index + 1, true, array, err);
}
/// Retrieves a line range from the buffer
@@ -292,7 +290,6 @@ void buffer_set_line_slice(Buffer buffer, Integer start, Integer end, Boolean in
nvim_buf_set_lines(0, buffer, start, end, false, replacement, err);
}
-
/// Sets a buffer-scoped (b:) variable
///
/// @deprecated
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index 742b953c2a..da1b6beeda 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -8,11 +8,13 @@
#include "nvim/api/extmark.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/charset.h"
+#include "nvim/decoration_provider.h"
#include "nvim/extmark.h"
+#include "nvim/highlight_group.h"
#include "nvim/lua/executor.h"
#include "nvim/memline.h"
#include "nvim/screen.h"
-#include "nvim/syntax.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/extmark.c.generated.h"
@@ -85,43 +87,79 @@ const char *describe_ns(NS ns_id)
}
// Is the Namespace in use?
-static bool ns_initialized(uint64_t ns)
+static bool ns_initialized(uint32_t ns)
{
if (ns < 1) {
return false;
}
- return ns < (uint64_t)next_namespace_id;
+ return ns < (uint32_t)next_namespace_id;
}
-
-static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict)
+static Array extmark_to_array(const ExtmarkInfo *extmark, bool id, bool add_dict)
{
Array rv = ARRAY_DICT_INIT;
if (id) {
- ADD(rv, INTEGER_OBJ((Integer)extmark.mark_id));
+ ADD(rv, INTEGER_OBJ((Integer)extmark->mark_id));
}
- ADD(rv, INTEGER_OBJ(extmark.row));
- ADD(rv, INTEGER_OBJ(extmark.col));
+ ADD(rv, INTEGER_OBJ(extmark->row));
+ ADD(rv, INTEGER_OBJ(extmark->col));
if (add_dict) {
Dictionary dict = ARRAY_DICT_INIT;
- 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, "right_gravity", BOOLEAN_OBJ(extmark->right_gravity));
+
+ 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));
+ }
+
+ 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.decor) {
- 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));
+ 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));
}
- if (kv_size(decor->virt_text)) {
+ 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 (decor->ui_watched) {
+ PUT(dict, "ui_watched", 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;
- for (size_t i = 0; i < decor->virt_text.size; i++) {
+ 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 = &decor->virt_text.items[i];
+ VirtTextChunk *vtc = &vt->items[j];
ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text)));
if (vtc->hl_id > 0) {
ADD(chunk,
@@ -129,9 +167,14 @@ static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict)
}
ADD(chunks, ARRAY_OBJ(chunk));
}
- PUT(dict, "virt_text", ARRAY_OBJ(chunks));
+ 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 (decor->hl_id || kv_size(decor->virt_text) || decor->ui_watched) {
PUT(dict, "priority", INTEGER_OBJ(decor->priority));
}
@@ -166,7 +209,7 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
return rv;
}
- if (!ns_initialized((uint64_t)ns_id)) {
+ if (!ns_initialized((uint32_t)ns_id)) {
api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
return rv;
}
@@ -190,12 +233,11 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
}
}
-
- ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id);
+ ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id);
if (extmark.row < 0) {
return rv;
}
- return extmark_to_array(extmark, false, details);
+ return extmark_to_array(&extmark, false, details);
}
/// Gets extmarks in "traversal order" from a |charwise| region defined by
@@ -220,9 +262,9 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
/// 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, 0, {})
+/// 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, 0, 2, 0, {})
+/// local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, {})
/// -- 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.
@@ -252,7 +294,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
return rv;
}
- if (!ns_initialized((uint64_t)ns_id)) {
+ if (!ns_initialized((uint32_t)ns_id)) {
api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
return rv;
}
@@ -290,7 +332,6 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
limit = INT64_MAX;
}
-
bool reverse = false;
int l_row;
@@ -309,12 +350,11 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
reverse = true;
}
-
- ExtmarkInfoArray marks = extmark_get(buf, (uint64_t)ns_id, l_row, l_col,
+ ExtmarkInfoArray marks = extmark_get(buf, (uint32_t)ns_id, l_row, l_col,
u_row, u_col, (int64_t)limit, reverse);
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, (bool)details)));
}
kv_destroy(marks);
@@ -323,12 +363,11 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// Creates or updates an extmark.
///
-/// To create a new extmark, pass id=0. The extmark id will be returned.
-/// To move an existing mark, pass its id.
-///
-/// It is also allowed to create a new mark by passing in a previously unused
-/// id, but the caller must then keep track of existing and unused ids itself.
-/// (Useful over RPC, to avoid waiting for the return value.)
+/// By default a new extmark is created when no id is passed in, but it is also
+/// possible to create a new mark by passing in a previously unused id or move
+/// an existing mark by passing in its id. The caller must then keep track of
+/// existing and unused ids itself. (Useful over RPC, to avoid waiting for the
+/// return value.)
///
/// 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.
@@ -404,6 +443,40 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// for left). Defaults to false.
/// - priority: a priority value for the highlight group. For
/// example treesitter highlighting uses a value of 100.
+/// - strict: boolean that indicates extmark should not be placed
+/// if the line or column value is past the end of the
+/// buffer or end of the line respectively. Defaults to true.
+/// - sign_text: string of length 1-2 used to display in the
+/// sign column.
+/// Note: ranges are unsupported and decorations are only
+/// applied to start_row
+/// - sign_hl_group: name of the highlight group used to
+/// highlight the sign column text.
+/// Note: ranges are unsupported and decorations are only
+/// applied to start_row
+/// - number_hl_group: name of the highlight group used to
+/// highlight the number column.
+/// Note: ranges are unsupported and decorations are only
+/// applied to start_row
+/// - line_hl_group: name of the highlight group used to
+/// highlight the whole line.
+/// Note: ranges are unsupported and decorations are only
+/// applied to start_row
+/// - cursorline_hl_group: name of the highlight group used to
+/// highlight the line when the cursor is on the same line
+/// as the mark and 'cursorline' is enabled.
+/// Note: ranges are unsupported and decorations are only
+/// applied to start_row
+/// - conceal: string which should be either empty or a single
+/// character. Enable concealing similar to |:syn-conceal|.
+/// When a character is supplied it is used as |:syn-cchar|.
+/// "hl_group" is used as highlight for the cchar if provided,
+/// otherwise it defaults to |hl-Conceal|.
+/// - ui_watched: boolean that indicates the mark should be drawn
+/// by a UI. When set, the UI will receive win_extmark events.
+/// Note: the mark is positioned by virt_text attributes. Can be
+/// used together with virt_text.
+///
/// @param[out] err Error details, if any
/// @return Id of the created/updated extmark
Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer col,
@@ -411,20 +484,21 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
FUNC_API_SINCE(7)
{
Decoration decor = DECORATION_INIT;
+ bool has_decor = false;
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
goto error;
}
- if (!ns_initialized((uint64_t)ns_id)) {
+ if (!ns_initialized((uint32_t)ns_id)) {
api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
goto error;
}
- uint64_t id = 0;
+ uint32_t id = 0;
if (opts->id.type == kObjectTypeInteger && opts->id.data.integer > 0) {
- id = (uint64_t)opts->id.data.integer;
+ 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;
@@ -441,9 +515,18 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
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; \
+ }
+
+ bool strict = true;
+ OPTION_TO_BOOL(strict, strict, true);
+
if (opts->end_row.type == kObjectTypeInteger) {
Integer val = opts->end_row.data.integer;
- if (val < 0 || val > buf->b_ml.ml_line_count) {
+ if (val < 0 || (val > buf->b_ml.ml_line_count && strict)) {
api_set_error(err, kErrorTypeValidation, "end_row value outside range");
goto error;
} else {
@@ -468,16 +551,49 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
goto error;
}
- if (HAS_KEY(opts->hl_group)) {
- decor.hl_id = object_to_hl_id(opts->hl_group, "hl_group", err);
- if (ERROR_SET(err)) {
- goto error;
+ // uncrustify:off
+
+ 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 },
+ { NULL, NULL, NULL },
+ };
+
+ // uncrustify:on
+
+ for (int j = 0; hls[j].name && hls[j].dest; j++) {
+ if (HAS_KEY(*hls[j].opt)) {
+ *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);
+ }
+ 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 (ERROR_SET(err)) {
goto error;
}
@@ -512,12 +628,6 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
goto error;
}
-#define OPTION_TO_BOOL(target, name, val) \
- target = api_object_to_bool(opts->name, #name, val, err); \
- if (ERROR_SET(err)) { \
- goto error; \
- }
-
OPTION_TO_BOOL(decor.virt_text_hide, virt_text_hide, false);
OPTION_TO_BOOL(decor.hl_eol, hl_eol, false);
@@ -555,13 +665,13 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
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) {
@@ -577,6 +687,18 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
goto error;
}
+ if (opts->sign_text.type == kObjectTypeString) {
+ if (!init_sign_text((char **)&decor.sign_text,
+ opts->sign_text.data.string.data)) {
+ api_set_error(err, kErrorTypeValidation, "sign_text is not a valid value");
+ goto error;
+ }
+ has_decor = true;
+ } else if (HAS_KEY(opts->sign_text)) {
+ api_set_error(err, kErrorTypeValidation, "sign_text is not a String");
+ goto error;
+ }
+
bool right_gravity = true;
OPTION_TO_BOOL(right_gravity, right_gravity, true);
@@ -596,16 +718,35 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
bool ephemeral = false;
OPTION_TO_BOOL(ephemeral, ephemeral, false);
- if (line < 0 || line > buf->b_ml.ml_line_count) {
+ OPTION_TO_BOOL(decor.ui_watched, ui_watched, false);
+ if (decor.ui_watched) {
+ has_decor = true;
+ }
+
+ if (line < 0) {
api_set_error(err, kErrorTypeValidation, "line value outside range");
goto error;
+ } else if (line > buf->b_ml.ml_line_count) {
+ if (strict) {
+ api_set_error(err, kErrorTypeValidation, "line value outside range");
+ goto error;
+ } else {
+ 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 = ephemeral ? MAXCOL : STRLEN(ml_get_buf(buf, (linenr_T)line + 1, false));
}
if (col == -1) {
col = (Integer)len;
- } else if (col < -1 || col > (Integer)len) {
+ } else if (col > (Integer)len) {
+ if (strict) {
+ api_set_error(err, kErrorTypeValidation, "col value outside range");
+ goto error;
+ } else {
+ col = (Integer)len;
+ }
+ } else if (col < -1) {
api_set_error(err, kErrorTypeValidation, "col value outside range");
goto error;
}
@@ -621,49 +762,36 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
line2 = (int)line;
}
if (col2 > (Integer)len) {
- api_set_error(err, kErrorTypeValidation, "end_col value outside range");
- goto error;
+ if (strict) {
+ api_set_error(err, kErrorTypeValidation, "end_col value outside range");
+ goto error;
+ } else {
+ col2 = (int)len;
+ }
}
} else if (line2 >= 0) {
col2 = 0;
}
- Decoration *d = NULL;
-
- if (ephemeral) {
- d = &decor;
- } else if (kv_size(decor.virt_text) || kv_size(decor.virt_lines)
- || decor.priority != DECOR_PRIORITY_BASE
- || decor.hl_eol) {
- // TODO(bfredl): this is a bit sketchy. eventually we should
- // have predefined decorations for both marks/ephemerals
- d = xcalloc(1, sizeof(*d));
- *d = decor;
- } else if (decor.hl_id) {
- d = decor_hl(decor.hl_id);
- }
-
// TODO(bfredl): synergize these two branches even more
if (ephemeral && decor_state.buf == buf) {
- decor_add_ephemeral((int)line, (int)col, line2, col2, &decor);
+ decor_add_ephemeral((int)line, (int)col, line2, col2, &decor, (uint64_t)ns_id, id);
} else {
if (ephemeral) {
api_set_error(err, kErrorTypeException, "not yet implemented");
goto error;
}
- extmark_set(buf, (uint64_t)ns_id, &id, (int)line, (colnr_T)col, line2, col2,
- d, right_gravity, end_right_gravity, kExtmarkNoUndo);
-
- if (kv_size(decor.virt_lines)) {
- redraw_buf_line_later(buf, MIN(buf->b_ml.ml_line_count, line+1+(decor.virt_lines_above?0:1)));
- }
+ 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);
}
return (Integer)id;
error:
clear_virttext(&decor.virt_text);
+ xfree(decor.sign_text);
return 0;
}
@@ -682,23 +810,23 @@ Boolean nvim_buf_del_extmark(Buffer buffer, Integer ns_id, Integer id, Error *er
if (!buf) {
return false;
}
- if (!ns_initialized((uint64_t)ns_id)) {
+ if (!ns_initialized((uint32_t)ns_id)) {
api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
return false;
}
- return extmark_del(buf, (uint64_t)ns_id, (uint64_t)id);
+ return extmark_del(buf, (uint32_t)ns_id, (uint32_t)id);
}
-uint64_t src2ns(Integer *src_id)
+uint32_t src2ns(Integer *src_id)
{
if (*src_id == 0) {
*src_id = nvim_create_namespace((String)STRING_INIT);
}
if (*src_id < 0) {
- return UINT64_MAX;
+ return (((uint32_t)1) << 31) - 1;
} else {
- return (uint64_t)(*src_id);
+ return (uint32_t)(*src_id);
}
}
@@ -753,7 +881,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In
col_end = MAXCOL;
}
- uint64_t ns = src2ns(&ns_id);
+ uint32_t ns = src2ns(&ns_id);
if (!(line < buf->b_ml.ml_line_count)) {
// safety check, we can't add marks outside the range
@@ -762,7 +890,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In
int hl_id = 0;
if (hl_group.size > 0) {
- hl_id = syn_check_group(hl_group.data, (int)hl_group.size);
+ hl_id = syn_check_group(hl_group.data, hl_group.size);
} else {
return ns_id;
}
@@ -773,10 +901,13 @@ 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;
+
extmark_set(buf, ns, NULL,
(int)line, (colnr_T)col_start,
end_line, (colnr_T)col_end,
- decor_hl(hl_id), true, false, kExtmarkNoUndo);
+ &decor, true, false, kExtmarkNoUndo);
return ns_id;
}
@@ -808,9 +939,9 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
if (line_end < 0 || line_end > MAXLNUM) {
line_end = MAXLNUM;
}
- extmark_clear(buf, (ns_id < 0 ? 0 : (uint64_t)ns_id),
+ extmark_clear(buf, (ns_id < 0 ? 0 : (uint32_t)ns_id),
(int)line_start, 0,
- (int)line_end-1, MAXCOL);
+ (int)line_end - 1, MAXCOL);
}
/// Set or change decoration provider for a namespace
@@ -903,3 +1034,154 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Erro
error:
decor_provider_clear(p);
}
+
+/// Gets the line and column of an extmark.
+///
+/// Extmarks may be queried by position, name or even special names
+/// in the future such as "cursor".
+///
+/// @param[out] lnum extmark line
+/// @param[out] colnr extmark column
+///
+/// @return true if the extmark was found, else false
+static bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int *row,
+ colnr_T *col, Error *err)
+{
+ // Check if it is mark id
+ if (obj.type == kObjectTypeInteger) {
+ Integer id = obj.data.integer;
+ if (id == 0) {
+ *row = 0;
+ *col = 0;
+ return true;
+ } else if (id == -1) {
+ *row = MAXLNUM;
+ *col = MAXCOL;
+ return true;
+ } else if (id < 0) {
+ api_set_error(err, kErrorTypeValidation, "Mark id must be positive");
+ 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");
+ return false;
+ }
+
+ // 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");
+ return false;
+ }
+ Integer pos_row = pos.items[0].data.integer;
+ Integer pos_col = pos.items[1].data.integer;
+ *row = (int)(pos_row >= 0 ? pos_row : MAXLNUM);
+ *col = (colnr_T)(pos_col >= 0 ? pos_col : MAXCOL);
+ return true;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "Position must be a mark id Integer or position Array");
+ return false;
+ }
+}
+// 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, " ");
+ }
+
+ return OK;
+}
+
+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");
+ 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");
+ goto free_exit;
+ }
+
+ String str = chunk.items[0].data.string;
+
+ int hl_id = 0;
+ if (chunk.size == 2) {
+ Object hl = chunk.items[1];
+ if (hl.type == kObjectTypeArray) {
+ Array arr = hl.data.array;
+ for (size_t j = 0; j < arr.size; j++) {
+ hl_id = object_to_hl_id(arr.items[j], "virt_text highlight", err);
+ if (ERROR_SET(err)) {
+ goto free_exit;
+ }
+ if (j < arr.size - 1) {
+ kv_push(virt_text, ((VirtTextChunk){ .text = NULL,
+ .hl_id = hl_id }));
+ }
+ }
+ } else {
+ hl_id = object_to_hl_id(hl, "virt_text highlight", err);
+ if (ERROR_SET(err)) {
+ goto free_exit;
+ }
+ }
+ }
+
+ char *text = transstr(str.size > 0 ? str.data : "", false); // allocates
+ w += (int)mb_string2cells(text);
+
+ kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
+ }
+
+ *width = w;
+ return virt_text;
+
+free_exit:
+ clear_virttext(&virt_text);
+ return virt_text;
+}
diff --git a/src/nvim/api/extmark.h b/src/nvim/api/extmark.h
index c5e463cd86..74802c6efb 100644
--- a/src/nvim/api/extmark.h
+++ b/src/nvim/api/extmark.h
@@ -2,6 +2,7 @@
#define NVIM_API_EXTMARK_H
#include "nvim/api/private/defs.h"
+#include "nvim/decoration.h"
#include "nvim/map.h"
EXTERN Map(String, handle_T) namespace_ids INIT(= MAP_INIT);
diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
index df271c805f..1f1fa1e63a 100644
--- a/src/nvim/api/keysets.lua
+++ b/src/nvim/api/keysets.lua
@@ -21,6 +21,14 @@ return {
"virt_lines";
"virt_lines_above";
"virt_lines_leftcol";
+ "strict";
+ "sign_text";
+ "sign_hl_group";
+ "number_hl_group";
+ "line_hl_group";
+ "cursorline_hl_group";
+ "conceal";
+ "ui_watched";
};
keymap = {
"noremap";
@@ -43,7 +51,9 @@ return {
"count";
"desc";
"force";
+ "keepscript";
"nargs";
+ "preview";
"range";
"register";
};
@@ -67,16 +77,136 @@ return {
};
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";
+ "nocombine";
+ "default";
+ "global";
+ "cterm";
+ "foreground"; "fg";
+ "background"; "bg";
+ "ctermfg";
+ "ctermbg";
+ "special"; "sp";
+ "link";
+ "fallback";
+ "blend";
+ "temp";
+ };
+ highlight_cterm = {
+ "bold";
+ "standout";
+ "strikethrough";
+ "underline";
+ "undercurl";
+ "underdouble";
+ "underdotted";
+ "underdashed";
+ "italic";
+ "reverse";
+ "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";
+ "keepalt";
+ "keepjumps";
+ "keepmarks";
+ "keeppatterns";
+ "lockmarks";
+ "noswapfile";
+ "tab";
+ "verbose";
+ "vertical";
+ "split";
+ };
+ cmd_mods_filter = {
+ "pattern";
+ "force";
+ };
+ cmd_opts = {
+ "output";
};
}
diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c
new file mode 100644
index 0000000000..4ed676e613
--- /dev/null
+++ b/src/nvim/api/options.c
@@ -0,0 +1,554 @@
+// 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 <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "nvim/api/options.h"
+#include "nvim/api/private/converter.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/autocmd.h"
+#include "nvim/buffer.h"
+#include "nvim/option.h"
+#include "nvim/option_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,
+ Error *err)
+{
+ if (opts->scope.type == kObjectTypeString) {
+ if (!strcmp(opts->scope.data.string.data, "local")) {
+ *scope = OPT_LOCAL;
+ } else if (!strcmp(opts->scope.data.string.data, "global")) {
+ *scope = OPT_GLOBAL;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'");
+ return FAIL;
+ }
+ } else if (HAS_KEY(opts->scope)) {
+ api_set_error(err, kErrorTypeValidation, "invalid value for key: scope");
+ return FAIL;
+ }
+
+ *opt_type = SREQ_GLOBAL;
+
+ if (opts->win.type == kObjectTypeInteger) {
+ *opt_type = SREQ_WIN;
+ *from = find_window_by_handle((int)opts->win.data.integer, err);
+ if (ERROR_SET(err)) {
+ return FAIL;
+ }
+ } else if (HAS_KEY(opts->win)) {
+ api_set_error(err, kErrorTypeValidation, "invalid value for key: win");
+ return FAIL;
+ }
+
+ if (opts->buf.type == kObjectTypeInteger) {
+ *scope = OPT_LOCAL;
+ *opt_type = SREQ_BUF;
+ *from = find_buffer_by_handle((int)opts->buf.data.integer, err);
+ if (ERROR_SET(err)) {
+ return FAIL;
+ }
+ } else if (HAS_KEY(opts->buf)) {
+ api_set_error(err, kErrorTypeValidation, "invalid value for key: buf");
+ return FAIL;
+ }
+
+ if (HAS_KEY(opts->scope) && HAS_KEY(opts->buf)) {
+ api_set_error(err, kErrorTypeValidation, "scope and buf cannot be used together");
+ return FAIL;
+ }
+
+ if (HAS_KEY(opts->win) && HAS_KEY(opts->buf)) {
+ api_set_error(err, kErrorTypeValidation, "buf and win cannot be used together");
+ return FAIL;
+ }
+
+ return OK;
+}
+
+/// Gets the value of an option. The behavior of this function matches that of
+/// |:set|: the local value of an option is returned if it exists; otherwise,
+/// the global value is returned. Local values always correspond to the current
+/// buffer or window, unless "buf" or "win" is set in {opts}.
+///
+/// @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 value
+Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
+ FUNC_API_SINCE(9)
+{
+ Object rv = OBJECT_INIT;
+
+ int scope = 0;
+ int opt_type = SREQ_GLOBAL;
+ void *from = NULL;
+ if (!validate_option_value_args(opts, &scope, &opt_type, &from, err)) {
+ return rv;
+ }
+
+ long numval = 0;
+ char *stringval = NULL;
+ int result = access_option_value_for(name.data, &numval, &stringval, scope, opt_type, from,
+ true, err);
+ if (ERROR_SET(err)) {
+ return rv;
+ }
+
+ switch (result) {
+ case 0:
+ rv = STRING_OBJ(cstr_as_string(stringval));
+ break;
+ case 1:
+ rv = INTEGER_OBJ(numval);
+ break;
+ case 2:
+ 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;
+ }
+
+ return rv;
+}
+
+/// Sets the value of an option. The behavior of this function matches that of
+/// |:set|: for global-local options, both the global and local value are set
+/// unless otherwise specified with {scope}.
+///
+/// Note the options {win} and {buf} cannot be used together.
+///
+/// @param name Option name
+/// @param value New option value
+/// @param opts Optional parameters
+/// - scope: One of 'global' or 'local'. Analogous to
+/// |:setglobal| and |:setlocal|, respectively.
+/// - 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)
+ FUNC_API_SINCE(9)
+{
+ int scope = 0;
+ int opt_type = SREQ_GLOBAL;
+ void *to = NULL;
+ if (!validate_option_value_args(opts, &scope, &opt_type, &to, err)) {
+ return;
+ }
+
+ // If:
+ // - window id is provided
+ // - scope is not provided
+ // - 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 (flags & SOPT_GLOBAL) {
+ scope = OPT_LOCAL;
+ }
+ }
+
+ long numval = 0;
+ char *stringval = NULL;
+
+ 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");
+ return;
+ }
+
+ access_option_value_for(name.data, &numval, &stringval, scope, opt_type, to, false, 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|.
+///
+/// @return dictionary of all options
+Dictionary nvim_get_all_options_info(Error *err)
+ FUNC_API_SINCE(7)
+{
+ return get_all_vimoptions();
+}
+
+/// Gets the option information for one option
+///
+/// Resulting dictionary has keys:
+/// - name: Name of the option (like 'filetype')
+/// - shortname: Shortened name of the option (like 'ft')
+/// - type: type of option ("string", "number" or "boolean")
+/// - default: The default value for the option
+/// - was_set: Whether the option was set.
+///
+/// - last_set_sid: Last set script id (if any)
+/// - last_set_linenr: line number where option was set
+/// - last_set_chan: Channel where option was set (0 for local)
+///
+/// - scope: one of "global", "win", or "buf"
+/// - global_local: whether win or buf option has a global value
+///
+/// - commalist: List of comma separated values
+/// - flaglist: List of single char flags
+///
+///
+/// @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)
+{
+ 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);
+}
+
+/// 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, Error *err)
+ FUNC_API_SINCE(1)
+{
+ return get_option_from(NULL, SREQ_GLOBAL, name, err);
+}
+
+/// Gets a buffer option value
+///
+/// @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, Error *err)
+ FUNC_API_SINCE(1)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return (Object)OBJECT_INIT;
+ }
+
+ return get_option_from(buf, SREQ_BUF, name, err);
+}
+
+/// 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)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return;
+ }
+
+ set_option_to(channel_id, buf, SREQ_BUF, name, value, err);
+}
+
+/// Gets a window option value
+///
+/// @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, Error *err)
+ FUNC_API_SINCE(1)
+{
+ win_T *win = find_window_by_handle(window, err);
+
+ if (!win) {
+ return (Object)OBJECT_INIT;
+ }
+
+ return get_option_from(win, SREQ_WIN, name, err);
+}
+
+/// 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 (!win) {
+ return;
+ }
+
+ set_option_to(channel_id, win, SREQ_WIN, name, value, err);
+}
+
+/// 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
+Object get_option_from(void *from, int type, String name, Error *err)
+{
+ Object rv = OBJECT_INIT;
+
+ if (name.size == 0) {
+ api_set_error(err, kErrorTypeValidation, "Empty option name");
+ 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);
+ }
+
+ return rv;
+}
+
+/// Sets the value of a global or local(buffer, window) option.
+///
+/// @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)
+{
+ if (name.size == 0) {
+ api_set_error(err, kErrorTypeValidation, "Empty option name");
+ return;
+ }
+
+ int flags = get_option_value_strict(name.data, NULL, NULL, type, to);
+
+ if (flags == 0) {
+ api_set_error(err, kErrorTypeValidation, "Invalid option name '%s'",
+ name.data);
+ return;
+ }
+
+ 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;
+ }
+ }
+
+ long numval = 0;
+ char *stringval = NULL;
+
+ if (flags & SOPT_BOOL) {
+ if (value.type != kObjectTypeBoolean) {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "Option '%s' requires a Boolean value",
+ name.data);
+ return;
+ }
+
+ numval = value.data.boolean;
+ } else if (flags & SOPT_NUM) {
+ if (value.type != kObjectTypeInteger) {
+ api_set_error(err, kErrorTypeValidation,
+ "Option '%s' requires an integer value",
+ name.data);
+ return;
+ }
+
+ if (value.data.integer > INT_MAX || value.data.integer < INT_MIN) {
+ api_set_error(err, kErrorTypeValidation,
+ "Value for option '%s' is out of range",
+ name.data);
+ return;
+ }
+
+ numval = (int)value.data.integer;
+ } else {
+ if (value.type != kObjectTypeString) {
+ api_set_error(err, kErrorTypeValidation,
+ "Option '%s' requires a string value",
+ name.data);
+ return;
+ }
+
+ stringval = value.data.string.data;
+ }
+
+ // 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);
+ });
+}
+
+static int access_option_value(char *key, long *numval, char **stringval, int opt_flags, bool get,
+ Error *err)
+{
+ if (get) {
+ return get_option_value(key, numval, stringval, opt_flags);
+ } else {
+ char *errmsg;
+ if ((errmsg = set_option_value(key, *numval, *stringval, opt_flags))) {
+ if (try_end(err)) {
+ return 0;
+ }
+
+ api_set_error(err, kErrorTypeException, "%s", errmsg);
+ }
+ return 0;
+ }
+}
+
+static int access_option_value_for(char *key, long *numval, char **stringval, int opt_flags,
+ int opt_type, void *from, bool get, Error *err)
+{
+ bool need_switch = false;
+ switchwin_T switchwin;
+ aco_save_T aco;
+ int 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;
+ }
+
+ if (ERROR_SET(err)) {
+ return result;
+ }
+
+ try_end(err);
+
+ return result;
+}
diff --git a/src/nvim/api/options.h b/src/nvim/api/options.h
new file mode 100644
index 0000000000..efbfec3a6c
--- /dev/null
+++ b/src/nvim/api/options.h
@@ -0,0 +1,9 @@
+#ifndef NVIM_API_OPTIONS_H
+#define NVIM_API_OPTIONS_H
+
+#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 36da6c13a9..8724ef4432 100644
--- a/src/nvim/api/private/converter.c
+++ b/src/nvim/api/private/converter.c
@@ -10,6 +10,9 @@
#include "nvim/api/private/helpers.h"
#include "nvim/assert.h"
#include "nvim/eval/typval.h"
+#include "nvim/eval/userfunc.h"
+#include "nvim/lua/converter.h"
+#include "nvim/lua/executor.h"
/// Helper structure for vim_to_object
typedef struct {
@@ -54,14 +57,20 @@ typedef struct {
const size_t len_ = (size_t)(len); \
const blob_T *const blob_ = (blob); \
kvi_push(edata->stack, STRING_OBJ(((String) { \
- .data = len_ != 0 ? xmemdup(blob_->bv_ga.ga_data, len_) : NULL, \
+ .data = len_ != 0 ? xmemdupz(blob_->bv_ga.ga_data, len_) : xstrdup(""), \
.size = len_ \
}))); \
} while (0)
#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
do { \
- TYPVAL_ENCODE_CONV_NIL(tv); \
+ ufunc_T *fp = find_func(fun); \
+ if (fp != NULL && fp->uf_cb == nlua_CFunction_func_call) { \
+ LuaRef ref = api_new_luaref(((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref); \
+ kvi_push(edata->stack, LUAREF_OBJ(ref)); \
+ } else { \
+ TYPVAL_ENCODE_CONV_NIL(tv); \
+ } \
goto typval_encode_stop_converting_one_item; \
} while (0)
@@ -340,6 +349,17 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
tv->vval.v_dict = dict;
break;
}
+
+ case kObjectTypeLuaRef: {
+ LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
+ state->lua_callable.func_ref = api_new_luaref(obj.data.luaref);
+ char *name =
+ (char *)register_cfunc(&nlua_CFunction_func_call, &nlua_CFunction_func_free, state);
+ tv->v_type = VAR_FUNC;
+ tv->vval.v_string = xstrdup(name);
+ break;
+ }
+
default:
abort();
}
diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h
index 396fab721d..b1e0dd364c 100644
--- a/src/nvim/api/private/defs.h
+++ b/src/nvim/api/private/defs.h
@@ -6,9 +6,10 @@
#include <string.h>
#include "nvim/func_attr.h"
+#include "nvim/lib/kvec.h"
#include "nvim/types.h"
-#define ARRAY_DICT_INIT { .size = 0, .capacity = 0, .items = NULL }
+#define ARRAY_DICT_INIT KV_INITIAL_VALUE
#define STRING_INIT { .data = NULL, .size = 0 }
#define OBJECT_INIT { .type = kObjectTypeNil }
#define ERROR_INIT { .type = kErrorTypeNone, .msg = NULL }
@@ -84,18 +85,10 @@ REMOTE_TYPE(Window);
REMOTE_TYPE(Tabpage);
typedef struct object Object;
-
-typedef struct {
- Object *items;
- size_t size, capacity;
-} Array;
+typedef kvec_t(Object) Array;
typedef struct key_value_pair KeyValuePair;
-
-typedef struct {
- KeyValuePair *items;
- size_t size, capacity;
-} Dictionary;
+typedef kvec_t(KeyValuePair) Dictionary;
typedef enum {
kObjectTypeNil = 0,
diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c
index 8ab7743e01..d6a6fc1219 100644
--- a/src/nvim/api/private/dispatch.c
+++ b/src/nvim/api/private/dispatch.c
@@ -6,47 +6,50 @@
#include <msgpack.h>
#include <stdbool.h>
-#include "nvim/api/buffer.h"
#include "nvim/api/deprecated.h"
-#include "nvim/api/extmark.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/log.h"
+#include "nvim/map.h"
+#include "nvim/msgpack_rpc/helpers.h"
+#include "nvim/vim.h"
+
+// ===========================================================================
+// NEW API FILES MUST GO HERE.
+//
+// When creating a new API file, you must include it here,
+// so that the dispatcher can find the C functions that you are creating!
+// ===========================================================================
+#include "nvim/api/autocmd.h"
+#include "nvim/api/buffer.h"
+#include "nvim/api/command.h"
+#include "nvim/api/extmark.h"
+#include "nvim/api/options.h"
#include "nvim/api/tabpage.h"
#include "nvim/api/ui.h"
#include "nvim/api/vim.h"
#include "nvim/api/vimscript.h"
#include "nvim/api/win_config.h"
#include "nvim/api/window.h"
-#include "nvim/log.h"
-#include "nvim/map.h"
-#include "nvim/msgpack_rpc/helpers.h"
-#include "nvim/vim.h"
-
-static Map(String, MsgpackRpcRequestHandler) methods = MAP_INIT;
+#include "nvim/ui_client.h"
-static void msgpack_rpc_add_method_handler(String method, MsgpackRpcRequestHandler handler)
-{
- map_put(String, MsgpackRpcRequestHandler)(&methods, method, handler);
-}
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/private/dispatch_wrappers.generated.h"
+#endif
/// @param name API method name
/// @param name_len name size (includes terminating NUL)
MsgpackRpcRequestHandler msgpack_rpc_get_handler_for(const char *name, size_t name_len,
Error *error)
{
- String m = { .data = (char *)name, .size = name_len };
- MsgpackRpcRequestHandler rv =
- map_get(String, MsgpackRpcRequestHandler)(&methods, m);
+ int hash = msgpack_rpc_get_handler_for_hash(name, name_len);
- if (!rv.fn) {
+ if (hash < 0) {
api_set_error(error, kErrorTypeException, "Invalid method: %.*s",
- m.size > 0 ? (int)m.size : (int)sizeof("<empty>"),
- m.size > 0 ? m.data : "<empty>");
+ name_len > 0 ? (int)name_len : (int)sizeof("<empty>"),
+ name_len > 0 ? name : "<empty>");
+ return (MsgpackRpcRequestHandler){ 0 };
}
- return rv;
+ return method_handlers[hash];
}
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "api/private/dispatch_wrappers.generated.h"
-#endif
diff --git a/src/nvim/api/private/dispatch.h b/src/nvim/api/private/dispatch.h
index bad5a13934..4b7c394944 100644
--- a/src/nvim/api/private/dispatch.h
+++ b/src/nvim/api/private/dispatch.h
@@ -10,6 +10,7 @@ typedef Object (*ApiDispatchWrapper)(uint64_t channel_id,
/// The rpc_method_handlers table, used in msgpack_rpc_dispatch(), stores
/// functions of this type.
typedef struct {
+ const char *name;
ApiDispatchWrapper fn;
bool fast; // Function is safe to be executed immediately while running the
// uv loop (the loop is run very frequently due to breakcheck).
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index f9603acbda..fad75d55be 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -16,12 +16,12 @@
#include "nvim/assert.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
-#include "nvim/decoration.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
+#include "nvim/ex_cmds_defs.h"
#include "nvim/extmark.h"
#include "nvim/fileio.h"
-#include "nvim/getchar.h"
+#include "nvim/highlight_group.h"
#include "nvim/lib/kvec.h"
#include "nvim/lua/executor.h"
#include "nvim/map.h"
@@ -30,9 +30,6 @@
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/helpers.h"
-#include "nvim/option.h"
-#include "nvim/option_defs.h"
-#include "nvim/syntax.h"
#include "nvim/ui.h"
#include "nvim/version.h"
#include "nvim/vim.h"
@@ -111,7 +108,7 @@ bool try_leave(const TryState *const tstate, Error *const err)
/// try_enter()/try_leave() pair should be used instead.
void try_start(void)
{
- ++trylevel;
+ trylevel++;
}
/// End try block, set the error message if any and return true if an error
@@ -139,10 +136,10 @@ bool try_end(Error *err)
got_int = false;
} else if (msg_list != NULL && *msg_list != NULL) {
int should_free;
- char *msg = (char *)get_exception_string(*msg_list,
- ET_ERROR,
- NULL,
- &should_free);
+ char *msg = get_exception_string(*msg_list,
+ ET_ERROR,
+ NULL,
+ &should_free);
api_set_error(err, kErrorTypeException, "%s", msg);
free_global_msglist();
@@ -261,157 +258,6 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva
return rv;
}
-/// 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
-Object get_option_from(void *from, int type, String name, Error *err)
-{
- Object rv = OBJECT_INIT;
-
- if (name.size == 0) {
- api_set_error(err, kErrorTypeValidation, "Empty option name");
- 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);
- }
-
- return rv;
-}
-
-/// Sets the value of a global or local(buffer, window) option.
-///
-/// @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)
-{
- if (name.size == 0) {
- api_set_error(err, kErrorTypeValidation, "Empty option name");
- return;
- }
-
- int flags = get_option_value_strict(name.data, NULL, NULL, type, to);
-
- if (flags == 0) {
- api_set_error(err, kErrorTypeValidation, "Invalid option name '%s'",
- name.data);
- return;
- }
-
- 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;
- }
- }
-
- int numval = 0;
- char *stringval = NULL;
-
- if (flags & SOPT_BOOL) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(err,
- kErrorTypeValidation,
- "Option '%s' requires a Boolean value",
- name.data);
- return;
- }
-
- numval = value.data.boolean;
- } else if (flags & SOPT_NUM) {
- if (value.type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation,
- "Option '%s' requires an integer value",
- name.data);
- return;
- }
-
- if (value.data.integer > INT_MAX || value.data.integer < INT_MIN) {
- api_set_error(err, kErrorTypeValidation,
- "Value for option '%s' is out of range",
- name.data);
- return;
- }
-
- numval = (int)value.data.integer;
- } else {
- if (value.type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation,
- "Option '%s' requires a string value",
- name.data);
- return;
- }
-
- stringval = value.data.string.data;
- }
-
- const sctx_T save_current_sctx = current_sctx;
- current_sctx.sc_sid =
- channel_id == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT;
- current_sctx.sc_lnum = 0;
- current_channel_id = channel_id;
-
- const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL))
- ? 0 : (type == SREQ_GLOBAL)
- ? OPT_GLOBAL : OPT_LOCAL;
- set_option_value_for(name.data, numval, stringval,
- opt_flags, type, to, err);
-
- current_sctx = save_current_sctx;
-}
-
-
buf_T *find_buffer_by_handle(Buffer buffer, Error *err)
{
if (buffer == 0) {
@@ -493,6 +339,16 @@ String cstr_to_string(const char *str)
};
}
+/// Copies a String to an allocated, NUL-terminated C string.
+///
+/// @param str the String to copy
+/// @return the resulting C string
+char *string_to_cstr(String str)
+ FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return xstrndup(str.data, str.size);
+}
+
/// Copies buffer to an allocated String.
/// The resulting string is also NUL-terminated, to facilitate interoperating
/// with code using C strings.
@@ -584,145 +440,6 @@ Array string_to_array(const String input, bool crlf)
return ret;
}
-/// Set, tweak, or remove a mapping in a mode. Acts as the implementation for
-/// functions like @ref nvim_buf_set_keymap.
-///
-/// Arguments are handled like @ref nvim_set_keymap unless noted.
-/// @param buffer Buffer handle for a specific buffer, or 0 for the current
-/// buffer, or -1 to signify global behavior ("all buffers")
-/// @param is_unmap When true, removes the mapping that matches {lhs}.
-void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String rhs,
- Dict(keymap) *opts, Error *err)
-{
- LuaRef lua_funcref = LUA_NOREF;
- bool global = (buffer == -1);
- if (global) {
- buffer = 0;
- }
- buf_T *target_buf = find_buffer_by_handle(buffer, err);
-
- if (!target_buf) {
- return;
- }
-
- if (opts != NULL && opts->callback.type == kObjectTypeLuaRef) {
- lua_funcref = opts->callback.data.luaref;
- opts->callback.data.luaref = LUA_NOREF;
- }
- MapArguments parsed_args = MAP_ARGUMENTS_INIT;
- if (opts) {
-#define KEY_TO_BOOL(name) \
- parsed_args.name = api_object_to_bool(opts->name, #name, false, err); \
- if (ERROR_SET(err)) { \
- goto fail_and_free; \
- }
-
- KEY_TO_BOOL(nowait);
- KEY_TO_BOOL(noremap);
- KEY_TO_BOOL(silent);
- KEY_TO_BOOL(script);
- KEY_TO_BOOL(expr);
- KEY_TO_BOOL(unique);
-#undef KEY_TO_BOOL
- }
- parsed_args.buffer = !global;
-
- set_maparg_lhs_rhs((char_u *)lhs.data, lhs.size,
- (char_u *)rhs.data, rhs.size, lua_funcref,
- CPO_TO_CPO_FLAGS, &parsed_args);
- if (opts != NULL && opts->desc.type == kObjectTypeString) {
- parsed_args.desc = xstrdup(opts->desc.data.string.data);
- } else {
- parsed_args.desc = NULL;
- }
- if (parsed_args.lhs_len > MAXMAPLEN) {
- api_set_error(err, kErrorTypeValidation, "LHS exceeds maximum map length: %s", lhs.data);
- goto fail_and_free;
- }
-
- if (mode.size > 1) {
- api_set_error(err, kErrorTypeValidation, "Shortname is too long: %s", mode.data);
- goto fail_and_free;
- }
- int mode_val; // integer value of the mapping mode, to be passed to do_map()
- char_u *p = (char_u *)((mode.size) ? mode.data : "m");
- if (STRNCMP(p, "!", 2) == 0) {
- mode_val = get_map_mode(&p, true); // mapmode-ic
- } else {
- mode_val = get_map_mode(&p, false);
- if ((mode_val == VISUAL + SELECTMODE + NORMAL + OP_PENDING)
- && mode.size > 0) {
- // get_map_mode() treats unrecognized mode shortnames as ":map".
- // This is an error unless the given shortname was empty string "".
- api_set_error(err, kErrorTypeValidation, "Invalid mode shortname: \"%s\"", (char *)p);
- goto fail_and_free;
- }
- }
-
- if (parsed_args.lhs_len == 0) {
- api_set_error(err, kErrorTypeValidation, "Invalid (empty) LHS");
- goto fail_and_free;
- }
-
- bool is_noremap = parsed_args.noremap;
- assert(!(is_unmap && is_noremap));
-
- if (!is_unmap && lua_funcref == LUA_NOREF
- && (parsed_args.rhs_len == 0 && !parsed_args.rhs_is_noop)) {
- if (rhs.size == 0) { // assume that the user wants RHS to be a <Nop>
- parsed_args.rhs_is_noop = true;
- } else {
- // the given RHS was nonempty and not a <Nop>, but was parsed as if it
- // were empty?
- assert(false && "Failed to parse nonempty RHS!");
- api_set_error(err, kErrorTypeValidation, "Parsing of nonempty RHS failed: %s", rhs.data);
- goto fail_and_free;
- }
- } else if (is_unmap && (parsed_args.rhs_len || parsed_args.rhs_lua != LUA_NOREF)) {
- if (parsed_args.rhs_len) {
- api_set_error(err, kErrorTypeValidation,
- "Gave nonempty RHS in unmap command: %s", parsed_args.rhs);
- } else {
- api_set_error(err, kErrorTypeValidation, "Gave nonempty RHS for unmap");
- }
- goto fail_and_free;
- }
-
- // buf_do_map() reads noremap/unmap as its own argument.
- int maptype_val = 0;
- if (is_unmap) {
- maptype_val = 1;
- } else if (is_noremap) {
- maptype_val = 2;
- }
-
- switch (buf_do_map(maptype_val, &parsed_args, mode_val, 0, target_buf)) {
- case 0:
- break;
- case 1:
- api_set_error(err, kErrorTypeException, (char *)e_invarg, 0);
- goto fail_and_free;
- case 2:
- api_set_error(err, kErrorTypeException, (char *)e_nomap, 0);
- goto fail_and_free;
- case 5:
- api_set_error(err, kErrorTypeException,
- "E227: mapping already exists for %s", parsed_args.lhs);
- goto fail_and_free;
- default:
- assert(false && "Unrecognized return code!");
- goto fail_and_free;
- } // switch
-
- parsed_args.rhs_lua = LUA_NOREF; // don't clear ref on success
-fail_and_free:
- NLUA_CLEAR_REF(parsed_args.rhs_lua);
- xfree(parsed_args.rhs);
- xfree(parsed_args.orig_rhs);
- XFREE_CLEAR(parsed_args.desc);
- return;
-}
-
/// Collects `n` buffer lines into array `l`, optionally replacing newlines
/// with NUL.
///
@@ -759,6 +476,52 @@ bool buf_collect_lines(buf_T *buf, size_t n, int64_t start, bool replace_nl, Arr
return true;
}
+/// Returns a substring of a buffer line
+///
+/// @param buf Buffer handle
+/// @param lnum Line number (1-based)
+/// @param start_col Starting byte offset into line (0-based)
+/// @param end_col Ending byte offset into line (0-based, exclusive)
+/// @param replace_nl Replace newlines ('\n') with null ('\0')
+/// @param err Error object
+/// @return The text between start_col and end_col on line lnum of buffer buf
+String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col, bool replace_nl,
+ Error *err)
+{
+ String rv = STRING_INIT;
+
+ if (lnum >= MAXLNUM) {
+ api_set_error(err, kErrorTypeValidation, "Line index is too high");
+ return rv;
+ }
+
+ const char *bufstr = (char *)ml_get_buf(buf, (linenr_T)lnum, false);
+ size_t line_length = strlen(bufstr);
+
+ start_col = start_col < 0 ? (int64_t)line_length + start_col + 1 : start_col;
+ end_col = end_col < 0 ? (int64_t)line_length + end_col + 1 : end_col;
+
+ if (start_col >= MAXCOL || end_col >= MAXCOL) {
+ api_set_error(err, kErrorTypeValidation, "Column index is too high");
+ return rv;
+ }
+
+ if (start_col > end_col) {
+ api_set_error(err, kErrorTypeValidation, "start_col must be less than end_col");
+ return rv;
+ }
+
+ if ((size_t)start_col >= line_length) {
+ return rv;
+ }
+
+ rv = cstrn_to_string(&bufstr[start_col], (size_t)(end_col - start_col));
+ if (replace_nl) {
+ strchrsub(rv.data, '\n', '\0');
+ }
+
+ return rv;
+}
void api_free_string(String value)
{
@@ -769,6 +532,29 @@ void api_free_string(String value)
xfree(value.data);
}
+Array arena_array(Arena *arena, size_t max_size)
+{
+ Array arr = ARRAY_DICT_INIT;
+ kv_fixsize_arena(arena, arr, max_size);
+ return arr;
+}
+
+Dictionary arena_dict(Arena *arena, size_t max_size)
+{
+ Dictionary dict = ARRAY_DICT_INIT;
+ kv_fixsize_arena(arena, dict, max_size);
+ return dict;
+}
+
+String arena_string(Arena *arena, String str)
+{
+ if (str.size) {
+ return cbuf_as_string(arena_memdupz(arena, str.data, str.size), str.size);
+ } else {
+ return (String)STRING_INIT;
+ }
+}
+
void api_free_object(Object value)
{
switch (value.type) {
@@ -987,63 +773,6 @@ Object copy_object(Object obj)
}
}
-static void set_option_value_for(char *key, int numval, char *stringval, int opt_flags,
- int opt_type, void *from, Error *err)
-{
- win_T *save_curwin = NULL;
- tabpage_T *save_curtab = NULL;
- aco_save_T aco;
-
- try_start();
- switch (opt_type)
- {
- case SREQ_WIN:
- if (switch_win_noblock(&save_curwin, &save_curtab, (win_T *)from,
- win_find_tabpage((win_T *)from), true)
- == FAIL) {
- restore_win_noblock(save_curwin, save_curtab, true);
- if (try_end(err)) {
- return;
- }
- api_set_error(err,
- kErrorTypeException,
- "Problem while switching windows");
- return;
- }
- set_option_value_err(key, numval, stringval, opt_flags, err);
- restore_win_noblock(save_curwin, save_curtab, true);
- break;
- case SREQ_BUF:
- aucmd_prepbuf(&aco, (buf_T *)from);
- set_option_value_err(key, numval, stringval, opt_flags, err);
- aucmd_restbuf(&aco);
- break;
- case SREQ_GLOBAL:
- set_option_value_err(key, numval, stringval, opt_flags, err);
- break;
- }
-
- if (ERROR_SET(err)) {
- return;
- }
-
- try_end(err);
-}
-
-
-static void set_option_value_err(char *key, int numval, char *stringval, int opt_flags, Error *err)
-{
- char *errmsg;
-
- if ((errmsg = set_option_value(key, numval, stringval, opt_flags))) {
- if (try_end(err)) {
- return;
- }
-
- api_set_error(err, kErrorTypeException, "%s", errmsg);
- }
-}
-
void api_set_error(Error *err, ErrorType errType, const char *format, ...)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PRINTF(3, 4)
{
@@ -1064,171 +793,6 @@ void api_set_error(Error *err, ErrorType errType, const char *format, ...)
err->type = errType;
}
-/// Get an array containing dictionaries describing mappings
-/// based on mode and buffer id
-///
-/// @param mode The abbreviation for the mode
-/// @param buf The buffer to get the mapping array. NULL for global
-/// @param from_lua Whether it is called from internal lua api.
-/// @returns Array of maparg()-like dictionaries describing mappings
-ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf, bool from_lua)
-{
- Array mappings = ARRAY_DICT_INIT;
- dict_T *const dict = tv_dict_alloc();
-
- // Convert the string mode to the integer mode
- // that is stored within each mapblock
- char_u *p = (char_u *)mode.data;
- int int_mode = get_map_mode(&p, 0);
-
- // Determine the desired buffer value
- long buffer_value = (buf == NULL) ? 0 : buf->handle;
-
- for (int i = 0; i < MAX_MAPHASH; i++) {
- for (const mapblock_T *current_maphash = get_maphash(i, buf);
- current_maphash;
- current_maphash = current_maphash->m_next) {
- // Check for correct mode
- if (int_mode & current_maphash->m_mode) {
- mapblock_fill_dict(dict, current_maphash, buffer_value, false);
- Object api_dict = vim_to_object((typval_T[]) { { .v_type = VAR_DICT,
- .vval.v_dict = dict } });
- if (from_lua) {
- Dictionary d = api_dict.data.dictionary;
- for (size_t j = 0; j < d.size; j++) {
- if (strequal("callback", d.items[j].key.data)) {
- d.items[j].value.type = kObjectTypeLuaRef;
- d.items[j].value.data.luaref = api_new_luaref((LuaRef)d.items[j].value.data.integer);
- break;
- }
- }
- }
- ADD(mappings, api_dict);
- tv_dict_clear(dict);
- }
- }
- }
- tv_dict_free(dict);
-
- return mappings;
-}
-
-/// Gets the line and column of an extmark.
-///
-/// Extmarks may be queried by position, name or even special names
-/// in the future such as "cursor".
-///
-/// @param[out] lnum extmark line
-/// @param[out] colnr extmark column
-///
-/// @return true if the extmark was found, else false
-bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int
- *row, colnr_T *col, Error *err)
-{
- // Check if it is mark id
- if (obj.type == kObjectTypeInteger) {
- Integer id = obj.data.integer;
- if (id == 0) {
- *row = 0;
- *col = 0;
- return true;
- } else if (id == -1) {
- *row = MAXLNUM;
- *col = MAXCOL;
- return true;
- } else if (id < 0) {
- api_set_error(err, kErrorTypeValidation, "Mark id must be positive");
- return false;
- }
-
- ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_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");
- return false;
- }
-
- // 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");
- return false;
- }
- Integer pos_row = pos.items[0].data.integer;
- Integer pos_col = pos.items[1].data.integer;
- *row = (int)(pos_row >= 0 ? pos_row : MAXLNUM);
- *col = (colnr_T)(pos_col >= 0 ? pos_col : MAXCOL);
- return true;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "Position must be a mark id Integer or position Array");
- return false;
- }
-}
-
-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");
- 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");
- goto free_exit;
- }
-
- String str = chunk.items[0].data.string;
-
- int hl_id = 0;
- if (chunk.size == 2) {
- Object hl = chunk.items[1];
- if (hl.type == kObjectTypeArray) {
- Array arr = hl.data.array;
- for (size_t j = 0; j < arr.size; j++) {
- hl_id = object_to_hl_id(arr.items[j], "virt_text highlight", err);
- if (ERROR_SET(err)) {
- goto free_exit;
- }
- if (j < arr.size-1) {
- kv_push(virt_text, ((VirtTextChunk){ .text = NULL,
- .hl_id = hl_id }));
- }
- }
- } else {
- hl_id = object_to_hl_id(hl, "virt_text highlight", err);
- if (ERROR_SET(err)) {
- goto free_exit;
- }
- }
- }
-
- char *text = transstr(str.size > 0 ? str.data : "", false); // allocates
- w += (int)mb_string2cells((char_u *)text);
-
- kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
- }
-
- *width = w;
- return virt_text;
-
-free_exit:
- clear_virttext(&virt_text);
- return virt_text;
-}
-
/// Force obj to bool.
/// If it fails, returns false and sets err
/// @param obj The object to coerce to a boolean
@@ -1253,7 +817,7 @@ int object_to_hl_id(Object obj, const char *what, Error *err)
{
if (obj.type == kObjectTypeString) {
String str = obj.data.string;
- return str.size ? syn_check_group(str.data, (int)str.size) : 0;
+ return str.size ? syn_check_group(str.data, str.size) : 0;
} else if (obj.type == kObjectTypeInteger) {
return MAX((int)obj.data.integer, 0);
} else {
@@ -1287,7 +851,7 @@ HlMessage parse_hl_msg(Array chunks, Error *err)
String hl = chunk.items[1].data.string;
if (hl.size > 0) {
// TODO(bfredl): use object_to_hl_id and allow integer
- int hl_id = syn_check_group(hl.data, (int)hl.size);
+ int hl_id = syn_check_group(hl.data, hl.size);
attr = hl_id > 0 ? syn_id2attr(hl_id) : 0;
}
}
@@ -1297,8 +861,8 @@ HlMessage parse_hl_msg(Array chunks, Error *err)
return hl_msg;
free_exit:
- clear_hl_msg(&hl_msg);
- return hl_msg;
+ hl_msg_free(hl_msg);
+ return (HlMessage)KV_INITIAL_VALUE;
}
bool api_dict_to_keydict(void *rv, field_hash hashy, Dictionary dict, Error *err)
@@ -1350,8 +914,9 @@ bool set_mark(buf_T *buf, String name, Integer line, Integer col, Error *err)
return res;
}
}
- pos_T pos = { line, (int)col, (int)col };
- res = setmark_pos(*name.data, &pos, buf->handle);
+ assert(INT32_MIN <= line && line <= INT32_MAX);
+ pos_T pos = { (linenr_T)line, (int)col, (int)col };
+ res = setmark_pos(*name.data, &pos, buf->handle, NULL);
if (!res) {
if (deleting) {
api_set_error(err, kErrorTypeException,
@@ -1365,199 +930,43 @@ 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)
+const char *get_default_stl_hl(win_T *wp, bool use_winbar)
{
if (wp == NULL) {
return "TabLineFill";
- } else if (wp == curwin) {
- return "StatusLine";
+ } else if (use_winbar) {
+ return (wp == curwin) ? "WinBar" : "WinBarNC";
} else {
- return "StatusLineNC";
+ return (wp == curwin) ? "StatusLine" : "StatusLineNC";
}
}
-void add_user_command(String name, Object command, Dict(user_command) *opts, int flags, Error *err)
+int find_sid(uint64_t channel_id)
{
- uint32_t argt = 0;
- long def = -1;
- cmd_addr_T addr_type_arg = ADDR_NONE;
- int compl = EXPAND_NOTHING;
- char *compl_arg = NULL;
- char *rep = NULL;
- LuaRef luaref = LUA_NOREF;
- LuaRef compl_luaref = LUA_NOREF;
-
- if (mb_islower(name.data[0])) {
- api_set_error(err, kErrorTypeValidation, "'name' must begin with an uppercase letter");
- goto err;
- }
-
- if (HAS_KEY(opts->range) && HAS_KEY(opts->count)) {
- api_set_error(err, kErrorTypeValidation, "'range' and 'count' are mutually exclusive");
- goto err;
- }
-
- if (opts->nargs.type == kObjectTypeInteger) {
- switch (opts->nargs.data.integer) {
- case 0:
- // Default value, nothing to do
- break;
- case 1:
- argt |= EX_EXTRA | EX_NOSPC | EX_NEEDARG;
- break;
- default:
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
- goto err;
- }
- } else if (opts->nargs.type == kObjectTypeString) {
- if (opts->nargs.data.string.size > 1) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
- goto err;
- }
-
- switch (opts->nargs.data.string.data[0]) {
- case '*':
- argt |= EX_EXTRA;
- break;
- case '?':
- argt |= EX_EXTRA | EX_NOSPC;
- break;
- case '+':
- argt |= EX_EXTRA | EX_NEEDARG;
- break;
- default:
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
- goto err;
- }
- } else if (HAS_KEY(opts->nargs)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
- goto err;
- }
-
- if (HAS_KEY(opts->complete) && !argt) {
- api_set_error(err, kErrorTypeValidation, "'complete' used without 'nargs'");
- goto err;
- }
-
- if (opts->range.type == kObjectTypeBoolean) {
- if (opts->range.data.boolean) {
- argt |= EX_RANGE;
- 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'");
- goto err;
- }
- } 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;
- }
-
- if (opts->count.type == kObjectTypeBoolean) {
- if (opts->count.data.boolean) {
- argt |= EX_COUNT | EX_ZEROR | EX_RANGE;
- addr_type_arg = ADDR_OTHER;
- def = 0;
- }
- } else if (opts->count.type == kObjectTypeInteger) {
- 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;
- }
-
- if (opts->addr.type == kObjectTypeString) {
- if (parse_addr_type_arg((char_u *)opts->addr.data.string.data, (int)opts->addr.data.string.size,
- &addr_type_arg) != OK) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'");
- 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)) {
- argt |= EX_BANG;
- } else if (ERROR_SET(err)) {
- goto err;
- }
-
- if (api_object_to_bool(opts->bar, "bar", false, err)) {
- argt |= EX_TRLBAR;
- } else if (ERROR_SET(err)) {
- goto err;
- }
-
-
- if (api_object_to_bool(opts->register_, "register", false, err)) {
- argt |= EX_REGSTR;
- } else if (ERROR_SET(err)) {
- goto err;
- }
-
- bool force = api_object_to_bool(opts->force, "force", true, err);
- if (ERROR_SET(err)) {
- goto err;
- }
-
- if (opts->complete.type == kObjectTypeLuaRef) {
- compl = EXPAND_USER_LUA;
- compl_luaref = api_new_luaref(opts->complete.data.luaref);
- } else if (opts->complete.type == kObjectTypeString) {
- if (parse_compl_arg((char_u *)opts->complete.data.string.data,
- (int)opts->complete.data.string.size, &compl, &argt,
- (char_u **)&compl_arg) != OK) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'");
- goto err;
- }
- } else if (HAS_KEY(opts->complete)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'");
- goto err;
- }
-
- switch (command.type) {
- case kObjectTypeLuaRef:
- luaref = api_new_luaref(command.data.luaref);
- if (opts->desc.type == kObjectTypeString) {
- rep = opts->desc.data.string.data;
- } else {
- snprintf((char *)IObuff, IOSIZE, "<Lua function %d>", luaref);
- rep = (char *)IObuff;
- }
- break;
- case kObjectTypeString:
- rep = command.data.string.data;
- break;
+ switch (channel_id) {
+ case VIML_INTERNAL_CALL:
+ // TODO(autocmd): Figure out what this should be
+ // return SID_API_CLIENT;
+ case LUA_INTERNAL_CALL:
+ return SID_LUA;
default:
- api_set_error(err, kErrorTypeValidation, "'command' must be a string or Lua function");
- goto err;
- }
-
- if (uc_add_command((char_u *)name.data, name.size, (char_u *)rep, argt, def, flags,
- compl, (char_u *)compl_arg, compl_luaref, addr_type_arg, luaref,
- force) != OK) {
- api_set_error(err, kErrorTypeException, "Failed to create user command");
- goto err;
+ return SID_API_CLIENT;
}
+}
- return;
-
-err:
- NLUA_CLEAR_REF(luaref);
- NLUA_CLEAR_REF(compl_luaref);
+/// Sets sctx for API calls.
+///
+/// @param channel_id api clients id. Used to determine if it's a internal
+/// call or a rpc call.
+/// @return returns previous value of current_sctx. To be used
+/// to be used for restoring sctx to previous state.
+sctx_T api_set_sctx(uint64_t channel_id)
+{
+ sctx_T old_current_sctx = current_sctx;
+ if (channel_id != VIML_INTERNAL_CALL) {
+ current_sctx.sc_sid =
+ channel_id == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT;
+ current_sctx.sc_lnum = 0;
+ }
+ return old_current_sctx;
}
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index 6d0aec9c90..a4348d8b44 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -63,17 +63,31 @@
#define PUT(dict, k, v) \
kv_push(dict, ((KeyValuePair) { .key = cstr_to_string(k), .value = v }))
+#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)
+#define ADD_C(array, item) \
+ kv_push_c(array, item)
+
#define FIXED_TEMP_ARRAY(name, fixsize) \
Array name = ARRAY_DICT_INIT; \
Object name##__items[fixsize]; \
name.size = fixsize; \
name.items = name##__items; \
+#define MAXSIZE_TEMP_ARRAY(name, maxsize) \
+ Array name = ARRAY_DICT_INIT; \
+ Object name##__items[maxsize]; \
+ name.capacity = maxsize; \
+ name.items = name##__items; \
+
+#define cbuf_as_string(d, s) ((String) { .data = d, .size = s })
+
#define STATIC_CSTR_AS_STRING(s) ((String) { .data = s, .size = sizeof(s) - 1 })
/// Create a new String instance, putting data in allocated memory
@@ -138,10 +152,28 @@ typedef struct {
msg_list = saved_msg_list; /* Restore the exception context. */ \
} while (0)
+// Useful macro for executing some `code` for each item in an array.
+#define FOREACH_ITEM(a, __foreach_item, code) \
+ for (size_t (__foreach_item##_index) = 0; (__foreach_item##_index) < (a).size; \
+ (__foreach_item##_index)++) { \
+ Object __foreach_item = (a).items[__foreach_item##_index]; \
+ code; \
+ }
+
#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; \
+ 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_sctx = save_current_sctx; \
+ } while (0);
#endif // NVIM_API_PRIVATE_HELPERS_H
diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c
index 14b6be8eeb..b81fc3b7d7 100644
--- a/src/nvim/api/tabpage.c
+++ b/src/nvim/api/tabpage.c
@@ -102,11 +102,10 @@ void nvim_tabpage_del_var(Tabpage tabpage, String name, Error *err)
Window nvim_tabpage_get_win(Tabpage tabpage, Error *err)
FUNC_API_SINCE(1)
{
- Window rv = 0;
tabpage_T *tab = find_tab_by_handle(tabpage, err);
if (!tab || !valid_tabpage(tab)) {
- return rv;
+ return 0;
}
if (tab == curtab) {
@@ -130,11 +129,10 @@ Window nvim_tabpage_get_win(Tabpage tabpage, Error *err)
Integer nvim_tabpage_get_number(Tabpage tabpage, Error *err)
FUNC_API_SINCE(1)
{
- Integer rv = 0;
tabpage_T *tab = find_tab_by_handle(tabpage, err);
if (!tab) {
- return rv;
+ return 0;
}
return tabpage_index(tab);
@@ -152,4 +150,3 @@ Boolean nvim_tabpage_is_valid(Tabpage tabpage)
api_clear_error(&stub);
return ret;
}
-
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index d86aecc318..54ce838b9b 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -9,25 +9,46 @@
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/ui.h"
+#include "nvim/channel.h"
#include "nvim/cursor_shape.h"
#include "nvim/highlight.h"
#include "nvim/map.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/channel.h"
+#include "nvim/msgpack_rpc/helpers.h"
+#include "nvim/option.h"
#include "nvim/popupmnu.h"
#include "nvim/screen.h"
#include "nvim/ui.h"
#include "nvim/vim.h"
#include "nvim/window.h"
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "api/ui.c.generated.h"
-# include "ui_events_remote.generated.h"
-#endif
-
typedef struct {
uint64_t channel_id;
- Array buffer;
+
+#define UI_BUF_SIZE 4096 ///< total buffer size for pending msgpack data.
+ /// guranteed size available for each new event (so packing of simple events
+ /// and the header of grid_line will never fail)
+#define EVENT_BUF_SIZE 256
+ char buf[UI_BUF_SIZE]; ///< buffer of packed but not yet sent msgpack data
+ char *buf_wptr; ///< write head of buffer
+ const char *cur_event; ///< name of current event (might get multiple arglists)
+ Array call_buf; ///< buffer for constructing a single arg list (max 16 elements!)
+
+ // state for write_cb, while packing a single arglist to msgpack. This
+ // might fail due to buffer overflow.
+ size_t pack_totlen;
+ bool buf_overflow;
+ char *temp_buf;
+
+ // We start packing the two outermost msgpack arrays before knowing the total
+ // number of elements. Thus track the location where array size will need
+ // to be written in the msgpack buffer, once the specifc array is finished.
+ char *nevents_pos;
+ char *ncalls_pos;
+ uint32_t nevents; ///< number of distinct events (top-level args to "redraw"
+ uint32_t ncalls; ///< number of calls made to the current event (plus one for the name!)
+ bool flushed_events; ///< events where sent to client without "flush" event
int hl_id; // Current highlight for legacy put event.
Integer cursor_row, cursor_col; // Intended visible cursor position.
@@ -37,8 +58,76 @@ typedef struct {
bool wildmenu_active;
} UIData;
+#define BUF_POS(data) ((size_t)((data)->buf_wptr - (data)->buf))
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/ui.c.generated.h"
+# include "ui_events_remote.generated.h"
+#endif
+
static PMap(uint64_t) connected_uis = MAP_INIT;
+#define mpack_w(b, byte) *(*b)++ = (char)(byte);
+static void mpack_w2(char **b, uint32_t v)
+{
+ *(*b)++ = (char)((v >> 8) & 0xff);
+ *(*b)++ = (char)(v & 0xff);
+}
+
+static void mpack_w4(char **b, uint32_t v)
+{
+ *(*b)++ = (char)((v >> 24) & 0xff);
+ *(*b)++ = (char)((v >> 16) & 0xff);
+ *(*b)++ = (char)((v >> 8) & 0xff);
+ *(*b)++ = (char)(v & 0xff);
+}
+
+static void mpack_uint(char **buf, uint32_t val)
+{
+ if (val > 0xffff) {
+ mpack_w(buf, 0xce);
+ mpack_w4(buf, val);
+ } else if (val > 0xff) {
+ mpack_w(buf, 0xcd);
+ mpack_w2(buf, val);
+ } else if (val > 0x7f) {
+ mpack_w(buf, 0xcc);
+ mpack_w(buf, val);
+ } else {
+ mpack_w(buf, val);
+ }
+}
+
+static void mpack_array(char **buf, uint32_t len)
+{
+ if (len < 0x10) {
+ mpack_w(buf, 0x90 | len);
+ } else if (len < 0x10000) {
+ mpack_w(buf, 0xdc);
+ mpack_w2(buf, len);
+ } else {
+ mpack_w(buf, 0xdd);
+ mpack_w4(buf, len);
+ }
+}
+
+static char *mpack_array_dyn16(char **buf)
+{
+ mpack_w(buf, 0xdc);
+ char *pos = *buf;
+ mpack_w2(buf, 0xFFEF);
+ return pos;
+}
+
+static void mpack_str(char **buf, const char *str)
+{
+ assert(sizeof(schar_T) - 1 < 0x20);
+ size_t len = STRLEN(str);
+ mpack_w(buf, 0xa0 | len);
+ memcpy(*buf, str, len);
+ *buf += len;
+}
+
void remote_ui_disconnect(uint64_t channel_id)
{
UI *ui = pmap_get(uint64_t)(&connected_uis, channel_id);
@@ -46,9 +135,9 @@ void remote_ui_disconnect(uint64_t channel_id)
return;
}
UIData *data = ui->data;
- api_free_array(data->buffer); // Destroy pending screen updates.
+ kv_destroy(data->call_buf);
pmap_del(uint64_t)(&connected_uis, channel_id);
- xfree(ui->data);
+ xfree(data);
ui->data = NULL; // Flag UI as "stopped".
ui_detach_impl(ui, channel_id);
xfree(ui);
@@ -158,10 +247,19 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
UIData *data = xmalloc(sizeof(UIData));
data->channel_id = channel_id;
- data->buffer = (Array)ARRAY_DICT_INIT;
+ data->cur_event = NULL;
data->hl_id = 0;
data->client_col = -1;
+ data->nevents_pos = NULL;
+ data->nevents = 0;
+ data->flushed_events = false;
+ data->ncalls_pos = NULL;
+ data->ncalls = 0;
+ data->buf_wptr = data->buf;
+ data->temp_buf = NULL;
data->wildmenu_active = false;
+ data->call_buf = (Array)ARRAY_DICT_INIT;
+ kv_ensure_space(data->call_buf, 16);
ui->data = data;
pmap_put(uint64_t)(&connected_uis, channel_id, ui);
@@ -195,7 +293,6 @@ void nvim_ui_detach(uint64_t channel_id, Error *err)
remote_ui_disconnect(channel_id);
}
-
void nvim_ui_try_resize(uint64_t channel_id, Integer width, Integer height, Error *err)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
@@ -255,6 +352,49 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e
return;
}
+ if (strequal(name.data, "term_name")) {
+ if (value.type != kObjectTypeString) {
+ api_set_error(error, kErrorTypeValidation, "term_name must be a String");
+ return;
+ }
+ set_tty_option("term", string_to_cstr(value.data.string));
+ return;
+ }
+
+ if (strequal(name.data, "term_colors")) {
+ if (value.type != kObjectTypeInteger) {
+ api_set_error(error, kErrorTypeValidation, "term_colors must be a Integer");
+ 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);
+ 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");
+ return;
+ }
+
+ if (starting != NO_SCREEN) {
+ api_set_error(error, kErrorTypeValidation,
+ "stdin_fd can only be used with first attached ui");
+ return;
+ }
+
+ stdin_fd = (int)value.data.integer;
+ return;
+ }
+
// LEGACY: Deprecated option, use `ext_cmdline` instead.
bool is_popupmenu = strequal(name.data, "popupmenu_external");
@@ -305,7 +445,11 @@ void nvim_ui_try_resize_grid(uint64_t channel_id, Integer grid, Integer width, I
return;
}
- ui_grid_resize((handle_T)grid, (int)width, (int)height, err);
+ if (grid == DEFAULT_GRID_HANDLE) {
+ nvim_ui_try_resize(channel_id, width, height, err);
+ } else {
+ ui_grid_resize((handle_T)grid, (int)width, (int)height, err);
+ }
}
/// Tells Nvim the number of elements displaying in the popumenu, to decide
@@ -386,34 +530,128 @@ void nvim_ui_pum_set_bounds(uint64_t channel_id, Float width, Float height, Floa
ui->pum_pos = true;
}
-/// Pushes data into UI.UIData, to be consumed later by remote_ui_flush().
-static void push_call(UI *ui, const char *name, Array args)
+static void flush_event(UIData *data)
+{
+ if (data->cur_event) {
+ mpack_w2(&data->ncalls_pos, data->ncalls);
+ data->cur_event = NULL;
+ }
+ if (!data->nevents_pos) {
+ assert(BUF_POS(data) == 0);
+ char **buf = &data->buf_wptr;
+ // [2, "redraw", [...]]
+ mpack_array(buf, 3);
+ mpack_uint(buf, 2);
+ mpack_str(buf, "redraw");
+ data->nevents_pos = mpack_array_dyn16(buf);
+ }
+}
+
+static inline int write_cb(void *vdata, const char *buf, size_t len)
+{
+ UIData *data = (UIData *)vdata;
+ if (!buf) {
+ return 0;
+ }
+
+ data->pack_totlen += len;
+ if (!data->temp_buf && UI_BUF_SIZE - BUF_POS(data) < len) {
+ data->buf_overflow = true;
+ return 0;
+ }
+
+ memcpy(data->buf_wptr, buf, len);
+ data->buf_wptr += len;
+
+ return 0;
+}
+
+static bool prepare_call(UI *ui, const char *name)
{
- Array call = ARRAY_DICT_INIT;
UIData *data = ui->data;
- // To optimize data transfer(especially for "put"), we bundle adjacent
+ if (BUF_POS(data) > UI_BUF_SIZE - EVENT_BUF_SIZE) {
+ remote_ui_flush_buf(ui);
+ }
+
+ // To optimize data transfer(especially for "grid_line"), we bundle adjacent
// calls to same method together, so only add a new call entry if the last
// method call is different from "name"
- if (kv_size(data->buffer)) {
- call = kv_A(data->buffer, kv_size(data->buffer) - 1).data.array;
- }
- if (!kv_size(call) || strcmp(kv_A(call, 0).data.string.data, name)) {
- call = (Array)ARRAY_DICT_INIT;
- ADD(data->buffer, ARRAY_OBJ(call));
- ADD(call, STRING_OBJ(cstr_to_string(name)));
+ if (!data->cur_event || !strequal(data->cur_event, name)) {
+ flush_event(data);
+ data->cur_event = name;
+ char **buf = &data->buf_wptr;
+ data->ncalls_pos = mpack_array_dyn16(buf);
+ mpack_str(buf, name);
+ data->nevents++;
+ data->ncalls = 1;
+ return true;
}
- ADD(call, ARRAY_OBJ(args));
- kv_A(data->buffer, kv_size(data->buffer) - 1).data.array = call;
+ return false;
+}
+
+/// Pushes data into UI.UIData, to be consumed later by remote_ui_flush().
+static void push_call(UI *ui, const char *name, Array args)
+{
+ UIData *data = ui->data;
+ bool pending = data->nevents_pos;
+ char *buf_pos_save = data->buf_wptr;
+
+ bool new_event = prepare_call(ui, name);
+
+ msgpack_packer pac;
+ data->pack_totlen = 0;
+ data->buf_overflow = false;
+ msgpack_packer_init(&pac, data, write_cb);
+ msgpack_rpc_from_array(args, &pac);
+ if (data->buf_overflow) {
+ data->buf_wptr = buf_pos_save;
+ if (new_event) {
+ data->cur_event = NULL;
+ data->nevents--;
+ }
+ if (pending) {
+ remote_ui_flush_buf(ui);
+ }
+
+ if (data->pack_totlen > UI_BUF_SIZE - strlen(name) - 20) {
+ // TODO(bfredl): manually testable by setting UI_BUF_SIZE to 1024 (mode_info_set)
+ data->temp_buf = xmalloc(20 + strlen(name) + data->pack_totlen);
+ data->buf_wptr = data->temp_buf;
+ char **buf = &data->buf_wptr;
+ mpack_array(buf, 3);
+ mpack_uint(buf, 2);
+ mpack_str(buf, "redraw");
+ mpack_array(buf, 1);
+ mpack_array(buf, 2);
+ mpack_str(buf, name);
+ } else {
+ prepare_call(ui, name);
+ }
+ data->pack_totlen = 0;
+ data->buf_overflow = false;
+ msgpack_rpc_from_array(args, &pac);
+
+ if (data->temp_buf) {
+ size_t size = (size_t)(data->buf_wptr - data->temp_buf);
+ WBuffer *buf = wstream_new_buffer(data->temp_buf, size, 1, xfree);
+ rpc_write_raw(data->channel_id, buf);
+ data->temp_buf = NULL;
+ data->buf_wptr = data->buf;
+ data->nevents_pos = NULL;
+ }
+ }
+ data->ncalls++;
}
static void remote_ui_grid_clear(UI *ui, Integer grid)
{
- Array args = ARRAY_DICT_INIT;
+ UIData *data = ui->data;
+ Array args = data->call_buf;
if (ui->ui_ext[kUILinegrid]) {
- ADD(args, INTEGER_OBJ(grid));
+ ADD_C(args, INTEGER_OBJ(grid));
}
const char *name = ui->ui_ext[kUILinegrid] ? "grid_clear" : "clear";
push_call(ui, name, args);
@@ -421,12 +659,13 @@ static void remote_ui_grid_clear(UI *ui, Integer grid)
static void remote_ui_grid_resize(UI *ui, Integer grid, Integer width, Integer height)
{
- Array args = ARRAY_DICT_INIT;
+ UIData *data = ui->data;
+ Array args = data->call_buf;
if (ui->ui_ext[kUILinegrid]) {
- ADD(args, INTEGER_OBJ(grid));
+ ADD_C(args, INTEGER_OBJ(grid));
}
- ADD(args, INTEGER_OBJ(width));
- ADD(args, INTEGER_OBJ(height));
+ ADD_C(args, INTEGER_OBJ(width));
+ ADD_C(args, INTEGER_OBJ(height));
const char *name = ui->ui_ext[kUILinegrid] ? "grid_resize" : "resize";
push_call(ui, name, args);
}
@@ -434,35 +673,36 @@ static void remote_ui_grid_resize(UI *ui, Integer grid, Integer width, Integer h
static void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot, Integer left,
Integer right, Integer rows, Integer cols)
{
+ UIData *data = ui->data;
if (ui->ui_ext[kUILinegrid]) {
- Array args = ARRAY_DICT_INIT;
- ADD(args, INTEGER_OBJ(grid));
- ADD(args, INTEGER_OBJ(top));
- ADD(args, INTEGER_OBJ(bot));
- ADD(args, INTEGER_OBJ(left));
- ADD(args, INTEGER_OBJ(right));
- ADD(args, INTEGER_OBJ(rows));
- ADD(args, INTEGER_OBJ(cols));
+ Array args = data->call_buf;
+ ADD_C(args, INTEGER_OBJ(grid));
+ ADD_C(args, INTEGER_OBJ(top));
+ ADD_C(args, INTEGER_OBJ(bot));
+ ADD_C(args, INTEGER_OBJ(left));
+ ADD_C(args, INTEGER_OBJ(right));
+ ADD_C(args, INTEGER_OBJ(rows));
+ ADD_C(args, INTEGER_OBJ(cols));
push_call(ui, "grid_scroll", args);
} else {
- Array args = ARRAY_DICT_INIT;
- ADD(args, INTEGER_OBJ(top));
- ADD(args, INTEGER_OBJ(bot-1));
- ADD(args, INTEGER_OBJ(left));
- ADD(args, INTEGER_OBJ(right-1));
+ Array args = data->call_buf;
+ ADD_C(args, INTEGER_OBJ(top));
+ ADD_C(args, INTEGER_OBJ(bot - 1));
+ ADD_C(args, INTEGER_OBJ(left));
+ ADD_C(args, INTEGER_OBJ(right - 1));
push_call(ui, "set_scroll_region", args);
- args = (Array)ARRAY_DICT_INIT;
- ADD(args, INTEGER_OBJ(rows));
+ args = data->call_buf;
+ ADD_C(args, INTEGER_OBJ(rows));
push_call(ui, "scroll", args);
// some clients have "clear" being affected by scroll region,
// so reset it.
- args = (Array)ARRAY_DICT_INIT;
- ADD(args, INTEGER_OBJ(0));
- ADD(args, INTEGER_OBJ(ui->height-1));
- ADD(args, INTEGER_OBJ(0));
- ADD(args, INTEGER_OBJ(ui->width-1));
+ args = data->call_buf;
+ ADD_C(args, INTEGER_OBJ(0));
+ ADD_C(args, INTEGER_OBJ(ui->height - 1));
+ ADD_C(args, INTEGER_OBJ(0));
+ ADD_C(args, INTEGER_OBJ(ui->width - 1));
push_call(ui, "set_scroll_region", args);
}
}
@@ -473,26 +713,27 @@ static void remote_ui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg,
if (!ui->ui_ext[kUITermColors]) {
HL_SET_DEFAULT_COLORS(rgb_fg, rgb_bg, rgb_sp);
}
- Array args = ARRAY_DICT_INIT;
- ADD(args, INTEGER_OBJ(rgb_fg));
- ADD(args, INTEGER_OBJ(rgb_bg));
- ADD(args, INTEGER_OBJ(rgb_sp));
- ADD(args, INTEGER_OBJ(cterm_fg));
- ADD(args, INTEGER_OBJ(cterm_bg));
+ UIData *data = ui->data;
+ Array args = data->call_buf;
+ ADD_C(args, INTEGER_OBJ(rgb_fg));
+ ADD_C(args, INTEGER_OBJ(rgb_bg));
+ ADD_C(args, INTEGER_OBJ(rgb_sp));
+ ADD_C(args, INTEGER_OBJ(cterm_fg));
+ ADD_C(args, INTEGER_OBJ(cterm_bg));
push_call(ui, "default_colors_set", args);
// Deprecated
if (!ui->ui_ext[kUILinegrid]) {
- args = (Array)ARRAY_DICT_INIT;
- ADD(args, INTEGER_OBJ(ui->rgb ? rgb_fg : cterm_fg - 1));
+ args = data->call_buf;
+ ADD_C(args, INTEGER_OBJ(ui->rgb ? rgb_fg : cterm_fg - 1));
push_call(ui, "update_fg", args);
- args = (Array)ARRAY_DICT_INIT;
- ADD(args, INTEGER_OBJ(ui->rgb ? rgb_bg : cterm_bg - 1));
+ args = data->call_buf;
+ ADD_C(args, INTEGER_OBJ(ui->rgb ? rgb_bg : cterm_bg - 1));
push_call(ui, "update_bg", args);
- args = (Array)ARRAY_DICT_INIT;
- ADD(args, INTEGER_OBJ(ui->rgb ? rgb_sp : -1));
+ args = data->call_buf;
+ ADD_C(args, INTEGER_OBJ(ui->rgb ? rgb_sp : -1));
push_call(ui, "update_sp", args);
}
}
@@ -503,26 +744,29 @@ static void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAt
if (!ui->ui_ext[kUILinegrid]) {
return;
}
- Array args = ARRAY_DICT_INIT;
- ADD(args, INTEGER_OBJ(id));
- ADD(args, DICTIONARY_OBJ(hlattrs2dict(rgb_attrs, true)));
- ADD(args, DICTIONARY_OBJ(hlattrs2dict(cterm_attrs, false)));
+ UIData *data = ui->data;
+ Array args = data->call_buf;
+ ADD_C(args, INTEGER_OBJ(id));
+ ADD_C(args, DICTIONARY_OBJ(hlattrs2dict(rgb_attrs, true)));
+ ADD_C(args, DICTIONARY_OBJ(hlattrs2dict(cterm_attrs, false)));
if (ui->ui_ext[kUIHlState]) {
- ADD(args, ARRAY_OBJ(copy_array(info)));
+ ADD_C(args, ARRAY_OBJ(info));
} else {
- ADD(args, ARRAY_OBJ((Array)ARRAY_DICT_INIT));
+ ADD_C(args, ARRAY_OBJ((Array)ARRAY_DICT_INIT));
}
push_call(ui, "hl_attr_define", args);
+ // TODO(bfredl): could be elided
+ api_free_dictionary(kv_A(args, 1).data.dictionary);
+ api_free_dictionary(kv_A(args, 2).data.dictionary);
}
static void remote_ui_highlight_set(UI *ui, int id)
{
- Array args = ARRAY_DICT_INIT;
UIData *data = ui->data;
-
+ Array args = data->call_buf;
if (data->hl_id == id) {
return;
@@ -530,18 +774,20 @@ static void remote_ui_highlight_set(UI *ui, int id)
data->hl_id = id;
Dictionary hl = hlattrs2dict(syn_attr2entry(id), ui->rgb);
- ADD(args, DICTIONARY_OBJ(hl));
+ ADD_C(args, DICTIONARY_OBJ(hl));
push_call(ui, "highlight_set", args);
+ api_free_dictionary(kv_A(args, 0).data.dictionary);
}
/// "true" cursor used only for input focus
static void remote_ui_grid_cursor_goto(UI *ui, Integer grid, Integer row, Integer col)
{
if (ui->ui_ext[kUILinegrid]) {
- Array args = ARRAY_DICT_INIT;
- ADD(args, INTEGER_OBJ(grid));
- ADD(args, INTEGER_OBJ(row));
- ADD(args, INTEGER_OBJ(col));
+ UIData *data = ui->data;
+ Array args = data->call_buf;
+ ADD_C(args, INTEGER_OBJ(grid));
+ ADD_C(args, INTEGER_OBJ(row));
+ ADD_C(args, INTEGER_OBJ(col));
push_call(ui, "grid_cursor_goto", args);
} else {
UIData *data = ui->data;
@@ -560,9 +806,9 @@ static void remote_ui_cursor_goto(UI *ui, Integer row, Integer col)
}
data->client_row = row;
data->client_col = col;
- Array args = ARRAY_DICT_INIT;
- ADD(args, INTEGER_OBJ(row));
- ADD(args, INTEGER_OBJ(col));
+ Array args = data->call_buf;
+ ADD_C(args, INTEGER_OBJ(row));
+ ADD_C(args, INTEGER_OBJ(col));
push_call(ui, "cursor_goto", args);
}
@@ -570,8 +816,8 @@ static void remote_ui_put(UI *ui, const char *cell)
{
UIData *data = ui->data;
data->client_col++;
- Array args = ARRAY_DICT_INIT;
- ADD(args, STRING_OBJ(cstr_to_string(cell)));
+ Array args = data->call_buf;
+ ADD_C(args, STRING_OBJ(cstr_as_string((char *)cell)));
push_call(ui, "put", args);
}
@@ -581,47 +827,70 @@ static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startc
{
UIData *data = ui->data;
if (ui->ui_ext[kUILinegrid]) {
- Array args = ARRAY_DICT_INIT;
- ADD(args, INTEGER_OBJ(grid));
- ADD(args, INTEGER_OBJ(row));
- ADD(args, INTEGER_OBJ(startcol));
- Array cells = ARRAY_DICT_INIT;
- int repeat = 0;
- size_t ncells = (size_t)(endcol-startcol);
+ prepare_call(ui, "grid_line");
+ data->ncalls++;
+
+ char **buf = &data->buf_wptr;
+ mpack_array(buf, 4);
+ mpack_uint(buf, (uint32_t)grid);
+ mpack_uint(buf, (uint32_t)row);
+ mpack_uint(buf, (uint32_t)startcol);
+ char *lenpos = mpack_array_dyn16(buf);
+
+ uint32_t repeat = 0;
+ size_t ncells = (size_t)(endcol - startcol);
int last_hl = -1;
+ uint32_t nelem = 0;
for (size_t i = 0; i < ncells; i++) {
repeat++;
- if (i == ncells-1 || attrs[i] != attrs[i+1]
- || STRCMP(chunk[i], chunk[i+1])) {
- Array cell = ARRAY_DICT_INIT;
- ADD(cell, STRING_OBJ(cstr_to_string((const char *)chunk[i])));
- if (attrs[i] != last_hl || repeat > 1) {
- ADD(cell, INTEGER_OBJ(attrs[i]));
- last_hl = attrs[i];
+ if (i == ncells - 1 || attrs[i] != attrs[i + 1]
+ || STRCMP(chunk[i], chunk[i + 1])) {
+ if (UI_BUF_SIZE - BUF_POS(data) < 2 * (1 + 2 + sizeof(schar_T) + 5 + 5)) {
+ // 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);
+ remote_ui_flush_buf(ui);
+
+ prepare_call(ui, "grid_line");
+ data->ncalls++;
+ mpack_array(buf, 4);
+ mpack_uint(buf, (uint32_t)grid);
+ mpack_uint(buf, (uint32_t)row);
+ mpack_uint(buf, (uint32_t)startcol + (uint32_t)i - repeat + 1);
+ lenpos = mpack_array_dyn16(buf);
+ nelem = 0;
+ last_hl = -1;
}
- if (repeat > 1) {
- ADD(cell, INTEGER_OBJ(repeat));
+ uint32_t csize = (repeat > 1) ? 3 : ((attrs[i] != last_hl) ? 2 : 1);
+ nelem++;
+ mpack_array(buf, csize);
+ mpack_str(buf, (const char *)chunk[i]);
+ if (csize >= 2) {
+ mpack_uint(buf, (uint32_t)attrs[i]);
+ if (csize >= 3) {
+ mpack_uint(buf, repeat);
+ }
}
- ADD(cells, ARRAY_OBJ(cell));
+ last_hl = attrs[i];
repeat = 0;
}
}
if (endcol < clearcol) {
- Array cell = ARRAY_DICT_INIT;
- ADD(cell, STRING_OBJ(cstr_to_string(" ")));
- ADD(cell, INTEGER_OBJ(clearattr));
- ADD(cell, INTEGER_OBJ(clearcol-endcol));
- ADD(cells, ARRAY_OBJ(cell));
+ nelem++;
+ mpack_array(buf, 3);
+ mpack_str(buf, " ");
+ mpack_uint(buf, (uint32_t)clearattr);
+ mpack_uint(buf, (uint32_t)(clearcol - endcol));
}
- ADD(args, ARRAY_OBJ(cells));
-
- push_call(ui, "grid_line", args);
+ mpack_w2(&lenpos, nelem);
} else {
- for (int i = 0; i < endcol-startcol; i++) {
- remote_ui_cursor_goto(ui, row, startcol+i);
+ 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(chunk[i]))) {
+ if (utf_ambiguous_width(utf_ptr2char((char *)chunk[i]))) {
data->client_col = -1; // force cursor update
}
}
@@ -642,16 +911,47 @@ static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startc
}
}
+/// Flush the internal packing buffer to the client.
+///
+/// This might happen multiple times before the actual ui_flush, if the
+/// total redraw size is large!
+static void remote_ui_flush_buf(UI *ui)
+{
+ UIData *data = ui->data;
+ if (!data->nevents_pos) {
+ return;
+ }
+ if (data->cur_event) {
+ flush_event(data);
+ }
+ mpack_w2(&data->nevents_pos, data->nevents);
+ data->nevents = 0;
+ data->nevents_pos = NULL;
+
+ // TODO(bfredl): elide copy by a length one free-list like the arena
+ size_t size = BUF_POS(data);
+ WBuffer *buf = wstream_new_buffer(xmemdup(data->buf, size), size, 1, xfree);
+ rpc_write_raw(data->channel_id, buf);
+ data->buf_wptr = data->buf;
+ // we have sent events to the client, but possibly not yet the final "flush"
+ // event.
+ data->flushed_events = true;
+}
+
+/// An intentional flush (vsync) when Nvim is finished redrawing the screen
+///
+/// Clients can know this happened by a final "flush" event at the end of the
+/// "redraw" batch.
static void remote_ui_flush(UI *ui)
{
UIData *data = ui->data;
- if (data->buffer.size > 0) {
+ if (data->nevents > 0 || data->flushed_events) {
if (!ui->ui_ext[kUILinegrid]) {
remote_ui_cursor_goto(ui, data->cursor_row, data->cursor_col);
}
push_call(ui, "flush", (Array)ARRAY_DICT_INIT);
- rpc_send_event(data->channel_id, "redraw", data->buffer);
- data->buffer = (Array)ARRAY_DICT_INIT;
+ remote_ui_flush_buf(ui);
+ data->flushed_events = false;
}
}
@@ -686,7 +986,7 @@ static Array translate_firstarg(UI *ui, Array args)
return new_args;
}
-static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed)
+static void remote_ui_event(UI *ui, char *name, Array args)
{
UIData *data = ui->data;
if (!ui->ui_ext[kUILinegrid]) {
@@ -695,21 +995,24 @@ static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed)
if (strequal(name, "cmdline_show")) {
Array new_args = translate_firstarg(ui, args);
push_call(ui, name, new_args);
+ api_free_array(new_args);
return;
} else if (strequal(name, "cmdline_block_show")) {
- Array new_args = ARRAY_DICT_INIT;
+ Array new_args = data->call_buf;
Array block = args.items[0].data.array;
Array new_block = ARRAY_DICT_INIT;
for (size_t i = 0; i < block.size; i++) {
ADD(new_block,
ARRAY_OBJ(translate_contents(ui, block.items[i].data.array)));
}
- ADD(new_args, ARRAY_OBJ(new_block));
+ ADD_C(new_args, ARRAY_OBJ(new_block));
push_call(ui, name, new_args);
+ api_free_array(new_block);
return;
} else if (strequal(name, "cmdline_block_append")) {
Array new_args = translate_firstarg(ui, args);
push_call(ui, name, new_args);
+ api_free_array(new_args);
return;
}
}
@@ -720,18 +1023,19 @@ static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed)
data->wildmenu_active = (args.items[4].data.integer == -1)
|| !ui->ui_ext[kUIPopupmenu];
if (data->wildmenu_active) {
- Array new_args = ARRAY_DICT_INIT;
+ Array new_args = data->call_buf;
Array items = args.items[0].data.array;
Array new_items = ARRAY_DICT_INIT;
for (size_t i = 0; i < items.size; i++) {
ADD(new_items, copy_object(items.items[i].data.array.items[0]));
}
- ADD(new_args, ARRAY_OBJ(new_items));
+ ADD_C(new_args, ARRAY_OBJ(new_items));
push_call(ui, "wildmenu_show", new_args);
+ api_free_array(new_items);
if (args.items[1].data.integer != -1) {
- Array new_args2 = ARRAY_DICT_INIT;
- ADD(new_args2, args.items[1]);
- push_call(ui, "wildmenu_select", new_args);
+ Array new_args2 = data->call_buf;
+ ADD_C(new_args2, args.items[1]);
+ push_call(ui, "wildmenu_select", new_args2);
}
return;
}
@@ -746,19 +1050,7 @@ static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed)
}
}
-
- Array my_args = ARRAY_DICT_INIT;
- // Objects are currently single-reference
- // make a copy, but only if necessary
- if (*args_consumed) {
- for (size_t i = 0; i < args.size; i++) {
- ADD(my_args, copy_object(args.items[i]));
- }
- } else {
- my_args = args;
- *args_consumed = true;
- }
- push_call(ui, name, my_args);
+ push_call(ui, name, args);
}
static void remote_ui_inspect(UI *ui, Dictionary *info)
diff --git a/src/nvim/api/ui.h b/src/nvim/api/ui.h
index b3af14f8a8..bc70406acb 100644
--- a/src/nvim/api/ui.h
+++ b/src/nvim/api/ui.h
@@ -4,6 +4,7 @@
#include <stdint.h>
#include "nvim/api/private/defs.h"
+#include "nvim/map.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/ui.h.generated.h"
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index 03fe5c5058..8b7e01e1c3 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -78,13 +78,13 @@ void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs,
void hl_group_set(String name, Integer id)
FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL;
void grid_resize(Integer grid, Integer width, Integer height)
- FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL;
+ FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL FUNC_API_CLIENT_IMPL;
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)
- FUNC_API_SINCE(5) FUNC_API_REMOTE_ONLY;
+ 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)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL;
@@ -99,7 +99,7 @@ void raw_line(Integer grid, Integer row, Integer startcol,
LineFlags flags, const schar_T *chunk, const sattr_T *attrs)
FUNC_API_NOEXPORT FUNC_API_COMPOSITOR_IMPL;
-void event(char *name, Array args, bool *args_consumed)
+void event(char *name, Array args)
FUNC_API_NOEXPORT;
void win_pos(Integer grid, Window win, Integer startrow,
@@ -123,6 +123,10 @@ void win_viewport(Integer grid, Window win, Integer topline,
Integer line_count)
FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY;
+void win_extmark(Integer grid, Window win, Integer ns_id, Integer mark_id,
+ Integer row, Integer col)
+ FUNC_API_SINCE(10) FUNC_API_REMOTE_ONLY;
+
void popupmenu_show(Array items, Integer selected,
Integer row, Integer col, Integer grid)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
@@ -170,4 +174,6 @@ void msg_ruler(Array content)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
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
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 59db12f2c0..56516b2ac7 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -19,19 +19,26 @@
#include "nvim/ascii.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
+#include "nvim/charset.h"
#include "nvim/context.h"
#include "nvim/decoration.h"
+#include "nvim/decoration_provider.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds2.h"
+#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/file_search.h"
#include "nvim/fileio.h"
#include "nvim/getchar.h"
+#include "nvim/globals.h"
#include "nvim/highlight.h"
+#include "nvim/highlight_defs.h"
+#include "nvim/highlight_group.h"
#include "nvim/lua/executor.h"
+#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -39,6 +46,7 @@
#include "nvim/move.h"
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/msgpack_rpc/helpers.h"
+#include "nvim/msgpack_rpc/unpacker.h"
#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/os/input.h"
@@ -46,7 +54,6 @@
#include "nvim/popupmnu.h"
#include "nvim/screen.h"
#include "nvim/state.h"
-#include "nvim/syntax.h"
#include "nvim/types.h"
#include "nvim/ui.h"
#include "nvim/vim.h"
@@ -108,7 +115,7 @@ Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Error *err)
Integer nvim_get_hl_id_by_name(String name)
FUNC_API_SINCE(7)
{
- return syn_check_group(name.data, (int)name.size);
+ return syn_check_group(name.data, name.size);
}
Dictionary nvim__get_hl_defs(Integer ns_id, Error *err)
@@ -119,33 +126,56 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err)
abort();
}
-/// Set a highlight group.
-///
-/// @param ns_id number of namespace for this highlight
-/// @param name highlight group name, like ErrorMsg
-/// @param val highlight definition map, like |nvim_get_hl_by_name|.
-/// in addition the following keys are also recognized:
-/// `default`: don't override existing definition,
-/// like `hi default`
-/// `ctermfg`: sets foreground of cterm color
-/// `ctermbg`: sets background of cterm color
-/// `cterm` : cterm attribute map. sets attributed for
-/// cterm colors. similer to `hi cterm`
-/// Note: by default cterm attributes are
-/// same as attributes of gui color
+/// Sets a highlight group.
+///
+/// @note Unlike the `:highlight` command which can update a highlight group,
+/// this function completely replaces the definition. For example:
+/// ``nvim_set_hl(0, 'Visual', {})`` will clear the highlight group
+/// 'Visual'.
+///
+/// @note The fg and bg keys also accept the string values `"fg"` or `"bg"`
+/// which act as aliases to the corresponding foreground and background
+/// values of the Normal group. If the Normal group has not been defined,
+/// using these values results in an error.
+///
+/// @param ns_id Namespace id for this highlight |nvim_create_namespace()|.
+/// Use 0 to set a highlight group globally |:highlight|.
+/// @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.
+/// - bg (or background): color name or "#RRGGBB", see note.
+/// - sp (or special): color name or "#RRGGBB"
+/// - blend: integer between 0 and 100
+/// - bold: boolean
+/// - standout: boolean
+/// - underline: boolean
+/// - undercurl: boolean
+/// - underdouble: boolean
+/// - underdotted: boolean
+/// - underdashed: boolean
+/// - strikethrough: boolean
+/// - italic: boolean
+/// - reverse: boolean
+/// - nocombine: boolean
+/// - link: name of another highlight group to link to, see |:hi-link|.
+/// - default: Don't override existing definition |:hi-default|
+/// - ctermfg: Sets foreground of cterm color |highlight-ctermfg|
+/// - ctermbg: Sets background of cterm color |highlight-ctermbg|
+/// - cterm: cterm attribute map, like |highlight-args|. If not set,
+/// cterm attributes will match those from the attribute map
+/// documented above.
/// @param[out] err Error details, if any
///
-/// TODO: ns_id = 0, should modify :highlight namespace
-/// TODO val should take update vs reset flag
-void nvim_set_hl(Integer ns_id, String name, Dictionary val, Error *err)
+// TODO(bfredl): val should take update vs reset flag
+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, (int)name.size);
+ int hl_id = syn_check_group(name.data, name.size);
int link_id = -1;
HlAttrs attrs = dict2hlattrs(val, true, &link_id, err);
if (!ERROR_SET(err)) {
- ns_hl_def((NS)ns_id, hl_id, attrs, link_id);
+ ns_hl_def((NS)ns_id, hl_id, attrs, link_id, val);
}
}
@@ -180,28 +210,29 @@ static void on_redraw_event(void **argv)
redraw_all_later(NOT_VALID);
}
-
/// Sends input-keys to Nvim, subject to various quirks controlled by `mode`
/// flags. This is a blocking call, unlike |nvim_input()|.
///
/// On execution error: does not fail, but updates v:errmsg.
///
/// To input sequences like <C-o> use |nvim_replace_termcodes()| (typically
-/// with escape_csi=true) to replace |keycodes|, then pass the result to
+/// with escape_ks=false) to replace |keycodes|, then pass the result to
/// nvim_feedkeys().
///
/// Example:
/// <pre>
/// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true)
-/// :call nvim_feedkeys(key, 'n', v:true)
+/// :call nvim_feedkeys(key, 'n', v:false)
/// </pre>
///
/// @param keys to be typed
/// @param mode behavior flags, see |feedkeys()|
-/// @param escape_csi If true, escape K_SPECIAL/CSI bytes in `keys`
+/// @param escape_ks If true, escape K_SPECIAL bytes in `keys`
+/// This should be false if you already used
+/// |nvim_replace_termcodes()|, and true otherwise.
/// @see feedkeys()
-/// @see vim_strsave_escape_csi
-void nvim_feedkeys(String keys, String mode, Boolean escape_csi)
+/// @see vim_strsave_escape_ks
+void nvim_feedkeys(String keys, String mode, Boolean escape_ks)
FUNC_API_SINCE(1)
{
bool remap = true;
@@ -210,7 +241,7 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi)
bool execute = false;
bool dangerous = false;
- for (size_t i = 0; i < mode.size; ++i) {
+ for (size_t i = 0; i < mode.size; i++) {
switch (mode.data[i]) {
case 'n':
remap = false; break;
@@ -232,20 +263,20 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi)
}
char *keys_esc;
- if (escape_csi) {
- // Need to escape K_SPECIAL and CSI before putting the string in the
+ if (escape_ks) {
+ // Need to escape K_SPECIAL before putting the string in the
// typeahead buffer.
- keys_esc = (char *)vim_strsave_escape_csi((char_u *)keys.data);
+ keys_esc = vim_strsave_escape_ks(keys.data);
} else {
keys_esc = keys.data;
}
- ins_typebuf((char_u *)keys_esc, (remap ? REMAP_YES : REMAP_NONE),
+ ins_typebuf(keys_esc, (remap ? REMAP_YES : REMAP_NONE),
insert ? 0 : typebuf.tb_len, !typed, false);
if (vgetc_busy) {
typebuf_was_filled = true;
}
- if (escape_csi) {
+ if (escape_ks) {
xfree(keys_esc);
}
@@ -394,13 +425,22 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, Bool
return (String) { .data = NULL, .size = 0 };
}
+ int flags = 0;
+ if (from_part) {
+ flags |= REPTERM_FROM_PART;
+ }
+ if (do_lt) {
+ flags |= REPTERM_DO_LT;
+ }
+ if (!special) {
+ flags |= REPTERM_NO_SPECIAL;
+ }
+
char *ptr = NULL;
- replace_termcodes((char_u *)str.data, str.size, (char_u **)&ptr,
- from_part, do_lt, special, CPO_TO_CPO_FLAGS);
+ replace_termcodes(str.data, str.size, &ptr, flags, NULL, CPO_TO_CPO_FLAGS);
return cstr_as_string(ptr);
}
-
/// Execute Lua code. Parameters (if any) are available as `...` inside the
/// chunk. The chunk can return a value.
///
@@ -441,7 +481,7 @@ Object nvim_notify(String msg, Integer log_level, Dictionary opts, Error *err)
}
/// Calculates the number of display cells occupied by `text`.
-/// <Tab> counts as one cell.
+/// Control characters including <Tab> count as one cell.
///
/// @param text Some text
/// @param[out] err Error details, if any
@@ -454,7 +494,7 @@ Integer nvim_strwidth(String text, Error *err)
return 0;
}
- return (Integer)mb_string2cells((char_u *)text.data);
+ return (Integer)mb_string2cells(text.data);
}
/// Gets the paths contained in 'runtimepath'.
@@ -491,12 +531,15 @@ ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Error *err)
int flags = DIP_DIRFILE | (all ? DIP_ALL : 0);
- do_in_runtimepath((char_u *)(name.size ? name.data : ""),
- flags, find_runtime_cb, &rv);
+ TRY_WRAP({
+ try_start();
+ do_in_runtimepath((name.size ? name.data : ""), flags, find_runtime_cb, &rv);
+ try_end(err);
+ });
return rv;
}
-static void find_runtime_cb(char_u *fname, void *cookie)
+static void find_runtime_cb(char *fname, void *cookie)
{
Array *rv = (Array *)cookie;
if (fname != NULL) {
@@ -513,20 +556,33 @@ 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 options
-/// 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");
+ }
+
if (ERROR_SET(err)) {
return (Array)ARRAY_DICT_INIT;
}
- return runtime_get_named(is_lua, pat, all);
-}
+ ArrayOf(String) res = runtime_get_named(is_lua, pat, all);
+
+ if (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);
+ }
+ }
+
+ return res;
+}
/// Changes the global working directory.
///
@@ -546,14 +602,13 @@ void nvim_set_current_dir(String dir, Error *err)
try_start();
- if (vim_chdir((char_u *)string)) {
+ if (!changedir_func(string, kCdScopeGlobal)) {
if (!try_end(err)) {
api_set_error(err, kErrorTypeException, "Failed to change directory");
}
return;
}
- post_chdir(kCdScopeGlobal, true);
try_end(err);
}
@@ -596,7 +651,19 @@ void nvim_del_current_line(Error *err)
Object nvim_get_var(String name, Error *err)
FUNC_API_SINCE(1)
{
- return dict_get_value(&globvardict, name, 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);
+ 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);
+ return (Object)OBJECT_INIT;
+ }
+ return vim_to_object(&di->di_tv);
}
/// Sets a global (g:) variable.
@@ -642,189 +709,6 @@ void nvim_set_vvar(String name, Object value, Error *err)
dict_set_var(&vimvardict, name, value, false, false, err);
}
-/// 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, Error *err)
- FUNC_API_SINCE(1)
-{
- return get_option_from(NULL, SREQ_GLOBAL, name, err);
-}
-
-/// Gets the value of an option. The behavior of this function matches that of
-/// |:set|: the local value of an option is returned if it exists; otherwise,
-/// the global value is returned. Local values always correspond to the current
-/// buffer or window. To get a buffer-local or window-local option for a
-/// specific buffer or window, use |nvim_buf_get_option()| or
-/// |nvim_win_get_option()|.
-///
-/// @param name Option name
-/// @param opts Optional parameters
-/// - scope: One of 'global' or 'local'. Analogous to
-/// |:setglobal| and |:setlocal|, respectively.
-/// @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;
-
- int scope = 0;
- if (opts->scope.type == kObjectTypeString) {
- if (!strcmp(opts->scope.data.string.data, "local")) {
- scope = OPT_LOCAL;
- } else if (!strcmp(opts->scope.data.string.data, "global")) {
- scope = OPT_GLOBAL;
- } else {
- api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'");
- goto end;
- }
- } else if (HAS_KEY(opts->scope)) {
- api_set_error(err, kErrorTypeValidation, "invalid value for key: scope");
- goto end;
- }
-
- long numval = 0;
- char *stringval = NULL;
- switch (get_option_value(name.data, &numval, (char_u **)&stringval, scope)) {
- case 0:
- rv = STRING_OBJ(cstr_as_string(stringval));
- break;
- case 1:
- rv = INTEGER_OBJ(numval);
- break;
- case 2:
- 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);
- goto end;
- }
-
-end:
- return rv;
-}
-
-/// Sets the value of an option. The behavior of this function matches that of
-/// |:set|: for global-local options, both the global and local value are set
-/// unless otherwise specified with {scope}.
-///
-/// @param name Option name
-/// @param value New option value
-/// @param opts Optional parameters
-/// - scope: One of 'global' or 'local'. Analogous to
-/// |:setglobal| and |:setlocal|, respectively.
-/// @param[out] err Error details, if any
-void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error *err)
- FUNC_API_SINCE(9)
-{
- int scope = 0;
- if (opts->scope.type == kObjectTypeString) {
- if (!strcmp(opts->scope.data.string.data, "local")) {
- scope = OPT_LOCAL;
- } else if (!strcmp(opts->scope.data.string.data, "global")) {
- scope = OPT_GLOBAL;
- } else {
- api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'");
- return;
- }
- } else if (HAS_KEY(opts->scope)) {
- api_set_error(err, kErrorTypeValidation, "invalid value for key: scope");
- return;
- }
-
- long numval = 0;
- char *stringval = NULL;
-
- 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");
- return;
- }
-
- char *e = set_option_value(name.data, numval, stringval, scope);
- if (e) {
- api_set_error(err, kErrorTypeException, "%s", e);
- }
-}
-
-/// 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|.
-///
-/// @return dictionary of all options
-Dictionary nvim_get_all_options_info(Error *err)
- FUNC_API_SINCE(7)
-{
- return get_all_vimoptions();
-}
-
-/// Gets the option information for one option
-///
-/// Resulting dictionary has keys:
-/// - name: Name of the option (like 'filetype')
-/// - shortname: Shortened name of the option (like 'ft')
-/// - type: type of option ("string", "number" or "boolean")
-/// - default: The default value for the option
-/// - was_set: Whether the option was set.
-///
-/// - last_set_sid: Last set script id (if any)
-/// - last_set_linenr: line number where option was set
-/// - last_set_chan: Channel where option was set (0 for local)
-///
-/// - scope: one of "global", "win", or "buf"
-/// - global_local: whether win or buf option has a global value
-///
-/// - commalist: List of comma separated values
-/// - flaglist: List of single char flags
-///
-///
-/// @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)
-{
- 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);
-}
-
/// Echo a message.
///
/// @param chunks A list of [text, hl_group] arrays, each representing a
@@ -845,26 +729,15 @@ void nvim_echo(Array chunks, Boolean history, Dictionary opts, Error *err)
goto error;
}
- no_wait_return++;
- msg_start();
- msg_clr_eos();
- bool need_clear = false;
- for (uint32_t i = 0; i < kv_size(hl_msg); i++) {
- HlMessageChunk chunk = kv_A(hl_msg, i);
- msg_multiline_attr((const char *)chunk.text.data, chunk.attr,
- true, &need_clear);
- }
+ msg_multiattr(hl_msg, history ? "echomsg" : "echo", history);
+
if (history) {
- msg_ext_set_kind("echomsg");
- add_hl_msg_hist(hl_msg);
- } else {
- msg_ext_set_kind("echo");
+ // history takes ownership
+ return;
}
- no_wait_return--;
- msg_end();
error:
- clear_hl_msg(&hl_msg);
+ hl_msg_free(hl_msg);
}
/// Writes a message to the Vim output buffer. Does not append "\n", the
@@ -1114,6 +987,7 @@ Integer nvim_open_term(Buffer buffer, DictionaryOf(LuaRef) opts, Error *err)
TerminalOptions topts;
Channel *chan = channel_alloc(kChannelStreamInternal);
chan->stream.internal.cb = cb;
+ chan->stream.internal.closed = false;
topts.data = chan;
// NB: overridden in terminal_check_size if a window is already
// displaying the buffer
@@ -1152,14 +1026,12 @@ static void term_resize(uint16_t width, uint16_t height, void *data)
static void term_close(void *data)
{
Channel *chan = data;
- terminal_destroy(chan->term);
- chan->term = NULL;
+ terminal_destroy(&chan->term);
api_free_luaref(chan->stream.internal.cb);
chan->stream.internal.cb = LUA_NOREF;
channel_decref(chan);
}
-
/// Send data to channel `id`. For a job, it writes it to the
/// stdin of the process. For the stdio channel |channel-stdio|,
/// it writes to Nvim's stdout. For an internal terminal instance
@@ -1293,25 +1165,25 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err)
draining = true;
goto theend;
}
- if (!(State & (CMDLINE | INSERT)) && (phase == -1 || phase == 1)) {
+ if (!(State & (MODE_CMDLINE | MODE_INSERT)) && (phase == -1 || phase == 1)) {
ResetRedobuff();
AppendCharToRedobuff('a'); // Dot-repeat.
}
// vim.paste() decides if client should cancel. Errors do NOT cancel: we
// want to drain remaining chunks (rather than divert them to main input).
cancel = (rv.type == kObjectTypeBoolean && !rv.data.boolean);
- if (!cancel && !(State & CMDLINE)) { // Dot-repeat.
+ if (!cancel && !(State & MODE_CMDLINE)) { // Dot-repeat.
for (size_t i = 0; i < lines.size; i++) {
String s = lines.items[i].data.string;
- assert(data.size <= INT_MAX);
- AppendToRedobuffLit((char_u *)s.data, (int)s.size);
+ assert(s.size <= INT_MAX);
+ AppendToRedobuffLit(s.data, (int)s.size);
// readfile()-style: "\n" is indicated by presence of N+1 item.
if (i + 1 < lines.size) {
AppendCharToRedobuff(NL);
}
}
}
- if (!(State & (CMDLINE | INSERT)) && (phase == -1 || phase == 3)) {
+ if (!(State & (MODE_CMDLINE | MODE_INSERT)) && (phase == -1 || phase == 3)) {
AppendCharToRedobuff(ESC); // Dot-repeat.
}
theend:
@@ -1357,7 +1229,7 @@ void nvim_put(ArrayOf(String) lines, String type, Boolean after, Boolean follow,
goto cleanup;
}
String line = lines.items[i].data.string;
- reg->y_array[i] = (char_u *)xmemdupz(line.data, line.size);
+ reg->y_array[i] = xmemdupz(line.data, line.size);
memchrsub(reg->y_array[i], NUL, NL, line.size);
}
@@ -1422,7 +1294,8 @@ void nvim_unsubscribe(uint64_t channel_id, String event)
Integer nvim_get_color_by_name(String name)
FUNC_API_SINCE(1)
{
- return name_to_color(name.data);
+ int dummy;
+ return name_to_color(name.data, &dummy);
}
/// Returns a map of color names and RGB values.
@@ -1524,10 +1397,11 @@ Dictionary nvim_get_mode(void)
FUNC_API_SINCE(2) FUNC_API_FAST
{
Dictionary rv = ARRAY_DICT_INIT;
- char *modestr = get_mode();
+ char modestr[MODE_MAX_LENGTH];
+ get_mode(modestr);
bool blocked = input_blocking();
- PUT(rv, "mode", STRING_OBJ(cstr_as_string(modestr)));
+ PUT(rv, "mode", STRING_OBJ(cstr_to_string(modestr)));
PUT(rv, "blocking", BOOLEAN_OBJ(blocked));
return rv;
@@ -1561,21 +1435,24 @@ ArrayOf(Dictionary) nvim_get_keymap(uint64_t channel_id, String mode)
/// nmap <nowait> <Space><NL> <Nop>
/// </pre>
///
+/// @param channel_id
/// @param mode Mode short-name (map command prefix: "n", "i", "v", "x", …)
/// or "!" for |:map!|, or empty string for |:map|.
/// @param lhs Left-hand-side |{lhs}| of the mapping.
/// @param rhs Right-hand-side |{rhs}| of the mapping.
-/// @param opts Optional parameters map. Accepts all |:map-arguments|
-/// as keys excluding |<buffer>| but including |noremap| and "desc".
-/// |desc| can be used to give a description to keymap.
-/// When called from Lua, also accepts a "callback" key that takes
-/// a Lua function to call when the mapping is executed.
-/// Values are Booleans. Unknown key is an error.
+/// @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.
/// @param[out] err Error details, if any.
-void nvim_set_keymap(String mode, String lhs, String rhs, Dict(keymap) *opts, Error *err)
+void nvim_set_keymap(uint64_t channel_id, String mode, String lhs, String rhs, Dict(keymap) *opts,
+ Error *err)
FUNC_API_SINCE(6)
{
- modify_keymap(-1, false, mode, lhs, rhs, opts, err);
+ modify_keymap(channel_id, -1, false, mode, lhs, rhs, opts, err);
}
/// Unmaps a global |mapping| for the given mode.
@@ -1583,25 +1460,10 @@ void nvim_set_keymap(String mode, String lhs, String rhs, Dict(keymap) *opts, Er
/// To unmap a buffer-local mapping, use |nvim_buf_del_keymap()|.
///
/// @see |nvim_set_keymap()|
-void nvim_del_keymap(String mode, String lhs, Error *err)
+void nvim_del_keymap(uint64_t channel_id, String mode, String lhs, Error *err)
FUNC_API_SINCE(6)
{
- nvim_buf_del_keymap(-1, mode, lhs, err);
-}
-
-/// Gets a map of global (non-buffer-local) Ex commands.
-///
-/// Currently only |user-commands| are supported, not builtin Ex commands.
-///
-/// @param opts Optional parameters. Currently only supports
-/// {"builtin":false}
-/// @param[out] err Error details, if any.
-///
-/// @returns Map of maps describing commands.
-Dictionary nvim_get_commands(Dict(get_commands) *opts, Error *err)
- FUNC_API_SINCE(4)
-{
- return nvim_buf_get_commands(-1, opts, err);
+ nvim_buf_del_keymap(channel_id, -1, mode, lhs, err);
}
/// Returns a 2-tuple (Array), where item 0 is the current channel id and item
@@ -1713,7 +1575,7 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dictionary version,
/// - "pty" (optional) Name of pseudoterminal. On a POSIX system this
/// is a device path like "/dev/pts/1". If the name is unknown,
/// the key will still be present if a pty is used (e.g. for
-/// winpty on Windows).
+/// conpty on Windows).
/// - "buffer" (optional) Buffer with connected |terminal| instance.
/// - "client" (optional) Info about the peer (client on the other end of
/// the RPC channel), if provided by it via
@@ -1845,15 +1707,15 @@ static void write_msg(String message, bool to_err)
static char out_line_buf[LINE_BUFFER_SIZE], err_line_buf[LINE_BUFFER_SIZE];
#define PUSH_CHAR(i, pos, line_buf, msg) \
- if (message.data[i] == NL || pos == LINE_BUFFER_SIZE - 1) { \
- line_buf[pos] = NUL; \
+ if (message.data[i] == NL || (pos) == LINE_BUFFER_SIZE - 1) { \
+ (line_buf)[pos] = NUL; \
msg(line_buf); \
- pos = 0; \
+ (pos) = 0; \
continue; \
} \
- line_buf[pos++] = message.data[i];
+ (line_buf)[(pos)++] = message.data[i];
- ++no_wait_return;
+ no_wait_return++;
for (uint32_t i = 0; i < message.size; i++) {
if (got_int) {
break;
@@ -1864,7 +1726,7 @@ static void write_msg(String message, bool to_err)
PUSH_CHAR(i, out_pos, out_line_buf, msg);
}
}
- --no_wait_return;
+ no_wait_return--;
msg_end();
}
@@ -1929,8 +1791,9 @@ Dictionary nvim__stats(void)
{
Dictionary rv = ARRAY_DICT_INIT;
PUT(rv, "fsync", INTEGER_OBJ(g_stats.fsync));
+ PUT(rv, "log_skip", INTEGER_OBJ(g_stats.log_skip));
+ PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_get_global_ref_count()));
PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw));
- PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_refcount));
return rv;
}
@@ -1964,14 +1827,13 @@ Array nvim_get_proc_children(Integer pid, Error *err)
size_t proc_count;
int rv = os_proc_children((int)pid, &proc_list, &proc_count);
- if (rv != 0) {
+ if (rv == 2) {
// syscall failed (possibly because of kernel options), try shelling out.
DLOG("fallback to vim._os_proc_children()");
Array a = ARRAY_DICT_INIT;
ADD(a, INTEGER_OBJ(pid));
- String s = cstr_to_string("return vim._os_proc_children(select(1, ...))");
+ String s = STATIC_CSTR_AS_STRING("return vim._os_proc_children(...)");
Object o = nlua_exec(s, a, err);
- api_free_string(s);
api_free_array(a);
if (o.type == kObjectTypeArray) {
rvobj = o.data.array;
@@ -2082,8 +1944,8 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err)
}
}
- if (row < 0 || row >= g->Rows
- || col < 0 || col >= g->Columns) {
+ if (row < 0 || row >= g->rows
+ || col < 0 || col >= g->cols) {
return ret;
}
size_t off = g->line_offset[(size_t)row] + (size_t)col;
@@ -2103,6 +1965,11 @@ void nvim__screenshot(String path)
ui_call_screenshot(path);
}
+Object nvim__unpack(String str, Error *err)
+ FUNC_API_FAST
+{
+ return unpack(str.data, str.size, err);
+}
/// Deletes an uppercase/file named mark. See |mark-motions|.
///
@@ -2160,20 +2027,20 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err)
return rv;
}
- xfmark_T mark = get_global_mark(*name.data);
- pos_T pos = mark.fmark.mark;
+ xfmark_T *mark = mark_get_global(false, *name.data); // false avoids loading the mark buffer
+ pos_T pos = mark->fmark.mark;
bool allocated = false;
int bufnr;
char *filename;
// Marks are from an open buffer it fnum is non zero
- if (mark.fmark.fnum != 0) {
- bufnr = mark.fmark.fnum;
+ if (mark->fmark.fnum != 0) {
+ bufnr = mark->fmark.fnum;
filename = (char *)buflist_nr2name(bufnr, true, true);
allocated = true;
// Marks comes from shada
} else {
- filename = (char *)mark.fname;
+ filename = mark->fname;
bufnr = 0;
}
@@ -2214,10 +2081,11 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err)
/// - winid: (number) |window-ID| of the window to use as context for statusline.
/// - maxwidth: (number) Maximum width of statusline.
/// - fillchar: (string) Character to fill blank spaces in the statusline (see
-/// 'fillchars').
+/// 'fillchars'). Treated as single-width even if it isn't.
/// - highlights: (boolean) Return highlight information.
+/// - use_winbar: (boolean) Evaluate winbar instead of statusline.
/// - use_tabline: (boolean) Evaluate tabline instead of statusline. When |TRUE|, {winid}
-/// is ignored.
+/// is ignored. Mutually exclusive with {use_winbar}.
///
/// @param[out] err Error details, if any.
/// @return Dictionary containing statusline information, with these keys:
@@ -2234,11 +2102,20 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
Dictionary result = ARRAY_DICT_INIT;
int maxwidth;
- char fillchar = 0;
+ int fillchar = 0;
Window window = 0;
+ bool use_winbar = false;
bool use_tabline = false;
bool highlights = false;
+ if (str.size < 2 || memcmp(str.data, "%!", 2)) {
+ const char *const errmsg = check_stl_option(str.data);
+ if (errmsg) {
+ api_set_error(err, kErrorTypeValidation, "%s", errmsg);
+ return result;
+ }
+ }
+
if (HAS_KEY(opts->winid)) {
if (opts->winid.type != kObjectTypeInteger) {
api_set_error(err, kErrorTypeValidation, "winid must be an integer");
@@ -2247,16 +2124,15 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
window = (Window)opts->winid.data.integer;
}
-
if (HAS_KEY(opts->fillchar)) {
- if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size > 1) {
- api_set_error(err, kErrorTypeValidation, "fillchar must be an ASCII character");
+ 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");
return result;
}
-
- fillchar = opts->fillchar.data.string.data[0];
+ fillchar = utf_ptr2char(opts->fillchar.data.string.data);
}
-
if (HAS_KEY(opts->highlights)) {
highlights = api_object_to_bool(opts->highlights, "highlights", false, err);
@@ -2264,7 +2140,13 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
return result;
}
}
+ if (HAS_KEY(opts->use_winbar)) {
+ use_winbar = api_object_to_bool(opts->use_winbar, "use_winbar", false, err);
+ if (ERROR_SET(err)) {
+ return result;
+ }
+ }
if (HAS_KEY(opts->use_tabline)) {
use_tabline = api_object_to_bool(opts->use_tabline, "use_tabline", false, err);
@@ -2272,6 +2154,10 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
return result;
}
}
+ if (use_winbar && use_tabline) {
+ api_set_error(err, kErrorTypeValidation, "use_winbar and use_tabline are mutually exclusive");
+ return result;
+ }
win_T *wp, *ewp;
@@ -2281,11 +2167,19 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
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) {
- int attr;
- fillchar = (char)fillchar_status(&attr, wp);
+ if (use_winbar) {
+ fillchar = wp->w_p_fcs_chars.wbr;
+ } else {
+ int attr;
+ fillchar = fillchar_status(&attr, wp);
+ }
}
}
@@ -2297,7 +2191,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
maxwidth = (int)opts->maxwidth.data.integer;
} else {
- maxwidth = use_tabline ? Columns : wp->w_width;
+ maxwidth = (use_tabline || (!use_winbar && global_stl_height() > 0)) ? Columns : wp->w_width;
}
char buf[MAXPATHL];
@@ -2309,11 +2203,11 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
ewp->w_p_crb = false;
int width = build_stl_str_hl(ewp,
- (char_u *)buf,
+ buf,
sizeof(buf),
- (char_u *)str.data,
+ str.data,
false,
- (char_u)fillchar,
+ fillchar,
maxwidth,
hltab_ptr,
NULL);
@@ -2332,7 +2226,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
// add the default highlight at the beginning of the highlight list
if (hltab->start == NULL || ((char *)hltab->start - buf) != 0) {
Dictionary hl_info = ARRAY_DICT_INIT;
- grpname = get_default_stl_hl(wp);
+ grpname = get_default_stl_hl(wp, use_winbar);
PUT(hl_info, "start", INTEGER_OBJ(0));
PUT(hl_info, "group", CSTR_TO_OBJ(grpname));
@@ -2346,73 +2240,19 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
PUT(hl_info, "start", INTEGER_OBJ((char *)sp->start - buf));
if (sp->userhl == 0) {
- grpname = get_default_stl_hl(wp);
+ grpname = get_default_stl_hl(wp, use_winbar);
} else if (sp->userhl < 0) {
grpname = (char *)syn_id2name(-sp->userhl);
} else {
snprintf(user_group, sizeof(user_group), "User%d", sp->userhl);
grpname = user_group;
}
-
PUT(hl_info, "group", CSTR_TO_OBJ(grpname));
-
ADD(hl_values, DICTIONARY_OBJ(hl_info));
}
-
PUT(result, "highlights", ARRAY_OBJ(hl_values));
}
-
PUT(result, "str", CSTR_TO_OBJ((char *)buf));
return result;
}
-
-/// Create a new user command |user-commands|
-///
-/// {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.
-///
-/// Example:
-/// <pre>
-/// :call nvim_add_user_command('SayHello', 'echo "Hello world!"', {})
-/// :SayHello
-/// Hello world!
-/// </pre>
-///
-/// @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
-/// from Lua, the command can also be a Lua function. The function is called with a
-/// single table argument that contains the following keys:
-/// - args: (string) The args passed to the command, if any |<args>|
-/// - 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>|
-/// - range: (number) The number of items in the command range: 0, 1, or 2 |<range>|
-/// - count: (number) Any count supplied |<count>|
-/// - reg: (string) The optional register, if specified |<reg>|
-/// - mods: (string) Command modifiers, if any |<mods>|
-/// @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.
-/// @param[out] err Error details, if any.
-void nvim_add_user_command(String name, Object command, Dict(user_command) *opts, Error *err)
- FUNC_API_SINCE(9)
-{
- add_user_command(name, command, opts, 0, err);
-}
-
-/// Delete a user-defined command.
-///
-/// @param name Name of the command to delete.
-/// @param[out] err Error details, if any.
-void nvim_del_user_command(String name, Error *err)
- FUNC_API_SINCE(9)
-{
- nvim_buf_del_user_command(-1, name, err);
-}
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index 640144b234..478e146781 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -10,34 +10,40 @@
#include "nvim/api/private/helpers.h"
#include "nvim/api/vimscript.h"
#include "nvim/ascii.h"
+#include "nvim/autocmd.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds2.h"
+#include "nvim/ops.h"
+#include "nvim/strings.h"
+#include "nvim/vim.h"
#include "nvim/viml/parser/expressions.h"
#include "nvim/viml/parser/parser.h"
+#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/vimscript.c.generated.h"
#endif
-/// Executes Vimscript (multiline block of Ex-commands), like anonymous
+/// Executes Vimscript (multiline block of Ex commands), like anonymous
/// |:source|.
///
/// Unlike |nvim_command()| this function supports heredocs, script-scope (s:),
/// etc.
///
-/// On execution error: fails with VimL error, does not update v:errmsg.
+/// On execution error: fails with VimL 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[out] err Error details (Vim error), if any
/// @return Output (non-error, non-shell |:!|) if `output` is true,
/// else empty string.
-String nvim_exec(String src, Boolean output, Error *err)
+String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err)
FUNC_API_SINCE(7)
{
const int save_msg_silent = msg_silent;
@@ -52,11 +58,16 @@ String nvim_exec(String src, Boolean output, Error *err)
if (output) {
msg_silent++;
}
+
+ const sctx_T save_current_sctx = api_set_sctx(channel_id);
+
do_source_str(src.data, "nvim_exec()");
if (output) {
capture_ga = save_capture_ga;
msg_silent = save_msg_silent;
}
+
+ current_sctx = save_current_sctx;
try_end(err);
if (ERROR_SET(err)) {
@@ -83,13 +94,16 @@ theend:
return (String)STRING_INIT;
}
-/// Executes an ex-command.
+/// Executes an Ex command.
///
-/// On execution error: fails with VimL error, does not update v:errmsg.
+/// On execution error: fails with VimL error, updates v:errmsg.
///
-/// @see |nvim_exec()|
+/// 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
+/// 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()|.
///
-/// @param command Ex-command string
+/// @param command Ex command string
/// @param[out] err Error details (Vim error), if any
void nvim_command(String command, Error *err)
FUNC_API_SINCE(1)
@@ -102,7 +116,7 @@ void nvim_command(String command, Error *err)
/// Evaluates a VimL |expression|.
/// Dictionaries and Lists are recursively expanded.
///
-/// On execution error: fails with VimL error, does not update v:errmsg.
+/// On execution error: fails with VimL error, updates v:errmsg.
///
/// @param expr VimL expression string
/// @param[out] err Error details, if any
@@ -126,7 +140,7 @@ Object nvim_eval(String expr, Error *err)
try_start();
typval_T rettv;
- int ok = eval0((char_u *)expr.data, &rettv, NULL, true);
+ int ok = eval0(expr.data, &rettv, NULL, true);
if (!try_end(err)) {
if (ok == FAIL) {
@@ -191,7 +205,7 @@ static Object _call_function(String fn, Array args, dict_T *self, Error *err)
funcexe.selfdict = self;
// call_func() retval is deceptive, ignore it. Instead we set `msg_list`
// (see above) to capture abort-causing non-exception errors.
- (void)call_func((char_u *)fn.data, (int)fn.size, &rettv, (int)args.size,
+ (void)call_func(fn.data, (int)fn.size, &rettv, (int)args.size,
vim_args, &funcexe);
if (!try_end(err)) {
rv = vim_to_object(&rettv);
@@ -210,7 +224,7 @@ free_vim_args:
/// Calls a VimL function with the given arguments.
///
-/// On execution error: fails with VimL error, does not update v:errmsg.
+/// On execution error: fails with VimL error, updates v:errmsg.
///
/// @param fn Function to call
/// @param args Function arguments packed in an Array
@@ -224,7 +238,7 @@ Object nvim_call_function(String fn, Array args, Error *err)
/// Calls a VimL |Dictionary-function| with the given arguments.
///
-/// On execution error: fails with VimL error, does not update v:errmsg.
+/// On execution error: fails with VimL 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
@@ -241,7 +255,7 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err)
switch (dict.type) {
case kObjectTypeString:
try_start();
- if (eval0((char_u *)dict.data.string.data, &rettv, NULL, true) == FAIL) {
+ if (eval0(dict.data.string.data, &rettv, NULL, true) == FAIL) {
api_set_error(err, kErrorTypeException,
"Failed to evaluate dict expression");
}
@@ -284,7 +298,7 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err)
goto end;
}
fn = (String) {
- .data = (char *)di->di_tv.vval.v_string,
+ .data = di->di_tv.vval.v_string,
.size = STRLEN(di->di_tv.vval.v_string),
};
}
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
index 255e1e55cc..d36c5bfb95 100644
--- a/src/nvim/api/win_config.c
+++ b/src/nvim/api/win_config.c
@@ -10,6 +10,7 @@
#include "nvim/api/private/helpers.h"
#include "nvim/api/win_config.h"
#include "nvim/ascii.h"
+#include "nvim/highlight_group.h"
#include "nvim/option.h"
#include "nvim/screen.h"
#include "nvim/strings.h"
@@ -21,7 +22,6 @@
# include "api/win_config.c.generated.h"
#endif
-
/// Open a new window.
///
/// Currently this is used to open floating and external windows.
@@ -125,13 +125,13 @@
/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ].
/// If the number of chars are less than eight, they will be repeated. Thus
/// an ASCII border could be specified as
-/// [ "/", "-", "\\", "|" ],
+/// [ "/", "-", \"\\\\\", "|" ],
/// or all chars the same as
/// [ "x" ].
/// An empty string can be used to turn off a specific border, for instance,
/// [ "", "", "", ">", "", "", "", "<" ]
/// will only make vertical borders but not horizontal ones.
-/// By default, `FloatBorder` highlight is used, which links to `VertSplit`
+/// By default, `FloatBorder` highlight is used, which links to `WinSeparator`
/// when not defined. It could also be specified by character:
/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ].
/// - noautocmd: If true then no buffer-related autocommand events such as
@@ -149,7 +149,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, E
if (!parse_float_config(config, &fconfig, false, true, err)) {
return 0;
}
- win_T *wp = win_new_float(NULL, fconfig, err);
+ win_T *wp = win_new_float(NULL, false, fconfig, err);
if (!wp) {
return 0;
}
@@ -199,7 +199,7 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err)
return;
}
if (new_float) {
- if (!win_new_float(win, fconfig, err)) {
+ if (!win_new_float(win, false, fconfig, err)) {
return;
}
redraw_later(win, NOT_VALID);
@@ -263,7 +263,7 @@ Dictionary nvim_win_get_config(Window window, Error *err)
String s = cstrn_to_string((const char *)config->border_chars[i], sizeof(schar_T));
int hi_id = config->border_hl_ids[i];
- char_u *hi_name = syn_id2name(hi_id);
+ char *hi_name = (char *)syn_id2name(hi_id);
if (hi_name[0]) {
ADD(tuple, STRING_OBJ(s));
ADD(tuple, STRING_OBJ(cstr_to_string((const char *)hi_name)));
@@ -325,7 +325,7 @@ static bool parse_float_bufpos(Array bufpos, lpos_T *out)
|| bufpos.items[1].type != kObjectTypeInteger) {
return false;
}
- out->lnum = bufpos.items[0].data.integer;
+ out->lnum = (linenr_T)bufpos.items[0].data.integer;
out->col = (colnr_T)bufpos.items[1].data.integer;
return true;
}
@@ -353,7 +353,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
if (style.type == kObjectTypeArray) {
Array arr = style.data.array;
size_t size = arr.size;
- if (!size || size > 8 || (size & (size-1))) {
+ if (!size || size > 8 || (size & (size - 1))) {
api_set_error(err, kErrorTypeValidation,
"invalid number of border chars");
return;
@@ -386,12 +386,12 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
return;
}
if (string.size
- && mb_string2cells_len((char_u *)string.data, string.size) > 1) {
+ && mb_string2cells_len(string.data, string.size) > 1) {
api_set_error(err, kErrorTypeValidation,
"border chars must be one cell");
return;
}
- size_t len = MIN(string.size, sizeof(*chars)-1);
+ size_t len = MIN(string.size, sizeof(*chars) - 1);
if (len) {
memcpy(chars[i], string.data, len);
}
@@ -399,8 +399,8 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
hl_ids[i] = hl_id;
}
while (size < 8) {
- memcpy(chars+size, chars, sizeof(*chars) * size);
- memcpy(hl_ids+size, hl_ids, sizeof(*hl_ids) * size);
+ memcpy(chars + size, chars, sizeof(*chars) * size);
+ memcpy(hl_ids + size, hl_ids, sizeof(*hl_ids) * size);
size <<= 1;
}
if ((chars[7][0] && chars[1][0] && !chars[0][0])
@@ -440,18 +440,18 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
static bool parse_title(FloatConfig* out, String s)
{
// The raw title is going to be at most the length of the string.
- char_u* out_title_raw = xcalloc(sizeof(char_u), s.size + 1);
+ char* out_title_raw = xcalloc(sizeof(char), s.size + 1);
size_t out_cursor = 0;
- char_u* data = (char_u*) s.data;
+ char* data = s.data;
size_t out_hlrec_nalloc = 4;
stl_hlrec_t* out_hlrec = xcalloc(sizeof(stl_hlrec_t), out_hlrec_nalloc);
- out_hlrec[0].start = out_title_raw;
+ out_hlrec[0].start = (char*) out_title_raw;
out_hlrec[0].userhl = 0;
size_t out_hl_cur = 1;
- char_u hlbuf[128];
+ char hlbuf[128];
size_t hlbuf_cur = 0;
int hl;
@@ -471,7 +471,7 @@ static bool parse_title(FloatConfig* out, String s)
i ++;
}
hlbuf[hlbuf_cur++] = 0;
- hl = syn_check_group(hlbuf, (int) strlen((char*)hlbuf));
+ hl = syn_check_group(hlbuf, strlen(hlbuf));
hlbuf_cur = 0;
if (out_hl_cur >= out_hlrec_nalloc - 1) { // Leave room for last.
@@ -479,7 +479,7 @@ static bool parse_title(FloatConfig* out, String s)
xrealloc(out_hlrec, sizeof(stl_hlrec_t) * (out_hlrec_nalloc *= 2));
}
- out_hlrec[out_hl_cur].start = out_title_raw + out_cursor;
+ out_hlrec[out_hl_cur].start = (out_title_raw + out_cursor);
out_hlrec[out_hl_cur++].userhl = -hl;
} else {
out_title_raw[out_cursor++] = data[i];
@@ -653,7 +653,6 @@ 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)) {
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index 907306da7b..5a4ff70257 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -71,6 +71,7 @@ ArrayOf(Integer, 2) nvim_win_get_cursor(Window window, Error *err)
}
/// Sets the (1,0)-indexed cursor position in the window. |api-indexing|
+/// This scrolls the window even if it is not the current one.
///
/// @param window Window handle, or 0 for current window
/// @param pos (row, col) tuple representing the new position
@@ -118,6 +119,7 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err)
update_topline_win(win);
redraw_later(win, VALID);
+ win->w_redr_status = true;
}
/// Gets the window height
@@ -137,8 +139,7 @@ Integer nvim_win_get_height(Window window, Error *err)
return win->w_height;
}
-/// Sets the window height. This will only succeed if the screen is split
-/// horizontally.
+/// Sets the window height.
///
/// @param window Window handle, or 0 for current window
/// @param height Height as a count of rows
@@ -263,44 +264,6 @@ void nvim_win_del_var(Window window, String name, Error *err)
dict_set_var(win->w_vars, name, NIL, true, false, err);
}
-/// Gets a window option value
-///
-/// @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, Error *err)
- FUNC_API_SINCE(1)
-{
- win_T *win = find_window_by_handle(window, err);
-
- if (!win) {
- return (Object)OBJECT_INIT;
- }
-
- return get_option_from(win, SREQ_WIN, name, err);
-}
-
-/// 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 (!win) {
- return;
- }
-
- set_option_to(channel_id, win, SREQ_WIN, name, value, err);
-}
-
/// Gets the window position in display cells. First position is zero.
///
/// @param window Window handle, or 0 for current window
@@ -372,7 +335,6 @@ Boolean nvim_win_is_valid(Window window)
return ret;
}
-
/// Closes the window and hide the buffer it contains (like |:hide| with a
/// |window-ID|).
///
@@ -395,7 +357,7 @@ void nvim_win_hide(Window window, Error *err)
TryState tstate;
try_enter(&tstate);
if (tabpage == curtab) {
- win_close(win, false);
+ win_close(win, false, false);
} else {
win_close_othertab(win, false, tabpage);
}
@@ -455,17 +417,12 @@ Object nvim_win_call(Window window, LuaRef fun, Error *err)
}
tabpage_T *tabpage = win_find_tabpage(win);
- win_T *save_curwin;
- tabpage_T *save_curtab;
-
try_start();
Object res = OBJECT_INIT;
- if (switch_win_noblock(&save_curwin, &save_curtab, win, tabpage, true) ==
- OK) {
+ WIN_EXECUTE(win, tabpage, {
Array args = ARRAY_DICT_INIT;
res = nlua_call_ref(fun, NULL, args, true, err);
- }
- restore_win_noblock(save_curwin, save_curtab, true);
+ });
try_end(err);
return res;
}