aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/api')
-rw-r--r--src/nvim/api/autocmd.c126
-rw-r--r--src/nvim/api/buffer.c171
-rw-r--r--src/nvim/api/buffer.h3
-rw-r--r--src/nvim/api/command.c186
-rw-r--r--src/nvim/api/deprecated.c12
-rw-r--r--src/nvim/api/extmark.c139
-rw-r--r--src/nvim/api/extmark.h3
-rw-r--r--src/nvim/api/keysets.lua105
-rw-r--r--src/nvim/api/options.c30
-rw-r--r--src/nvim/api/private/converter.c18
-rw-r--r--src/nvim/api/private/defs.h6
-rw-r--r--src/nvim/api/private/dispatch.c29
-rw-r--r--src/nvim/api/private/dispatch.h17
-rw-r--r--src/nvim/api/private/helpers.c150
-rw-r--r--src/nvim/api/private/helpers.h9
-rw-r--r--src/nvim/api/tabpage.c16
-rw-r--r--src/nvim/api/ui.c229
-rw-r--r--src/nvim/api/ui.h2
-rw-r--r--src/nvim/api/ui_events.in.h67
-rw-r--r--src/nvim/api/vim.c259
-rw-r--r--src/nvim/api/vimscript.c33
-rw-r--r--src/nvim/api/win_config.c158
-rw-r--r--src/nvim/api/window.c42
23 files changed, 1068 insertions, 742 deletions
diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
index 79ae7994f7..931363e199 100644
--- a/src/nvim/api/autocmd.c
+++ b/src/nvim/api/autocmd.c
@@ -1,8 +1,12 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+#include <assert.h>
#include <stdbool.h>
+#include <stdint.h>
#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
#include "lauxlib.h"
#include "nvim/api/autocmd.h"
@@ -12,7 +16,12 @@
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/eval/typval.h"
+#include "nvim/eval/typval_defs.h"
+#include "nvim/ex_cmds_defs.h"
+#include "nvim/globals.h"
#include "nvim/lua/executor.h"
+#include "nvim/memory.h"
+#include "nvim/vim.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/autocmd.c.generated.h"
@@ -38,7 +47,7 @@ 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>
+/// <pre>lua
/// -- Matches all criteria
/// autocommands = vim.api.nvim_get_autocmds({
/// group = "MyGroup",
@@ -59,6 +68,9 @@ static int64_t next_autocmd_id = 1;
/// - 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|.
+/// Cannot be used with {buffer}
+/// - buffer: Buffer number or list of buffer numbers for buffer local autocommands
+/// |autocmd-buflocal|. Cannot be used with {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).
@@ -240,7 +252,7 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
assert(pattern_filters[i]);
char *pat = pattern_filters[i];
- int patlen = (int)STRLEN(pat);
+ int patlen = (int)strlen(pat);
if (aupat_is_buflocal(pat, patlen)) {
aupat_normalize_buflocal_pat(pattern_buflocal,
@@ -349,82 +361,49 @@ cleanup:
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
+/// Creates an |autocommand| event handler, defined by `callback` (Lua function or Vimscript
+/// function _name_ string) or `command` (Ex command string).
///
+/// Example using Lua callback:
+/// <pre>lua
/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
/// pattern = {"*.c", "*.h"},
-/// callback = function() myluafun() end,
+/// callback = function(ev)
+/// print(string.format('event fired: %s', vim.inspect(ev)))
+/// end
/// })
/// </pre>
///
-/// Example using command:
-/// <pre>
+/// Example using an Ex command as the handler:
+/// <pre>lua
/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
/// pattern = {"*.c", "*.h"},
/// command = "echo 'Entering a C or C++ file'",
/// })
/// </pre>
///
-/// Example values for pattern:
-/// <pre>
-/// pattern = "*.py"
-/// pattern = { "*.py", "*.pyi" }
-/// </pre>
-///
-/// Example values for event:
-/// <pre>
-/// "BufWritePre"
-/// {"CursorHold", "BufWritePre", "BufWritePost"}
+/// Note: `pattern` is NOT automatically expanded (unlike with |:autocmd|), thus names like "$HOME"
+/// and "~" must be expanded explicitly:
+/// <pre>lua
+/// pattern = vim.fn.expand("~") .. "/some/path/*.py"
/// </pre>
///
-/// @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
+/// @param event (string|array) Event(s) that will trigger the handler (`callback` or `command`).
+/// @param opts Options dict:
+/// - group (string|integer) optional: autocommand group name or id to match against.
+/// - pattern (string|array) optional: pattern(s) to match literally |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>|
+/// - desc (string) optional: description (for documentation and troubleshooting).
+/// - callback (function|string) optional: Lua function (or Vimscript function name, if
+/// string) called when the event(s) is triggered. Lua callback can return true to
+/// delete the autocommand, and receives a table argument with these keys:
+/// - id: (number) autocommand id
+/// - event: (string) name of the triggered event |autocmd-events|
+/// - group: (number|nil) autocommand group id, if any
+/// - match: (string) expanded value of |<amatch>|
+/// - buf: (number) expanded value of |<abuf>|
+/// - file: (string) expanded value of |<afile>|
/// - data: (any) arbitrary data passed to |nvim_exec_autocmds()|
/// - command (string) optional: Vim command to execute on event. Cannot be used with
/// {callback}
@@ -433,7 +412,7 @@ cleanup:
/// - nested (boolean) optional: defaults to false. Run nested
/// autocommands |autocmd-nested|.
///
-/// @return Integer id of the created autocommand.
+/// @return Autocommand id (number)
/// @see |autocommand|
/// @see |nvim_del_autocmd()|
Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autocmd) *opts,
@@ -454,8 +433,7 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
}
if (opts->callback.type != kObjectTypeNil && opts->command.type != kObjectTypeNil) {
- api_set_error(err, kErrorTypeValidation,
- "cannot pass both: 'callback' and 'command' for the same autocmd");
+ api_set_error(err, kErrorTypeValidation, "specify either 'callback' or 'command', not both");
goto cleanup;
} else if (opts->callback.type != kObjectTypeNil) {
// TODO(tjdevries): It's possible we could accept callable tables,
@@ -596,7 +574,7 @@ void nvim_del_autocmd(Integer id, Error *err)
}
/// Clear all autocommands that match the corresponding {opts}. To delete
-/// a particular autocmd, see |nvim_del_autocmd|.
+/// a particular autocmd, see |nvim_del_autocmd()|.
/// @param opts Parameters
/// - event: (string|table)
/// Examples:
@@ -684,7 +662,7 @@ cleanup:
/// Create or get an autocommand group |autocmd-groups|.
///
/// To get an existing group id, do:
-/// <pre>
+/// <pre>lua
/// local id = vim.api.nvim_create_augroup("MyGroup", {
/// clear = false
/// })
@@ -725,7 +703,7 @@ Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augrou
///
/// To get a group id one can use |nvim_get_autocmds()|.
///
-/// NOTE: behavior differs from |augroup-delete|. When deleting a group, autocommands contained in
+/// 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()|
@@ -743,10 +721,10 @@ void nvim_del_augroup_by_id(Integer id, Error *err)
/// Delete an autocommand group by name.
///
-/// NOTE: behavior differs from |augroup-delete|. When deleting a group, autocommands contained in
+/// 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|
+/// @see |autocmd-groups|
void nvim_del_augroup_by_name(String name, Error *err)
FUNC_API_SINCE(9)
{
@@ -887,12 +865,12 @@ static bool check_autocmd_string_array(Array arr, char *k, Error *err)
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));
+ ADD(*array, copy_object(*v, NULL));
} else if (v->type == kObjectTypeArray) {
if (!check_autocmd_string_array(v->data.array, k, err)) {
return false;
}
- *array = copy_array(v->data.array);
+ *array = copy_array(v->data.array, NULL);
} else {
if (required) {
api_set_error(err,
@@ -962,7 +940,7 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob
patlen = aucmd_pattern_length(pat);
}
} else if (v->type == kObjectTypeArray) {
- if (!check_autocmd_string_array(*patterns, "pattern", err)) {
+ if (!check_autocmd_string_array(v->data.array, "pattern", err)) {
return false;
}
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 5e90e40dd3..fe9e6077d6 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -3,25 +3,30 @@
// Some of this code was adapted from 'if_py_both.h' from the original
// vim source
+
+#include <assert.h>
#include <lauxlib.h>
-#include <limits.h>
#include <stdbool.h>
+#include <stddef.h>
#include <stdint.h>
-#include <stdlib.h>
+#include <string.h>
+#include "klib/kvec.h"
+#include "lua.h"
#include "nvim/api/buffer.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/ascii.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
+#include "nvim/buffer_defs.h"
#include "nvim/buffer_updates.h"
#include "nvim/change.h"
#include "nvim/cursor.h"
-#include "nvim/decoration.h"
#include "nvim/drawscreen.h"
#include "nvim/ex_cmds.h"
-#include "nvim/ex_docmd.h"
#include "nvim/extmark.h"
+#include "nvim/globals.h"
#include "nvim/lua/executor.h"
#include "nvim/mapping.h"
#include "nvim/mark.h"
@@ -29,9 +34,10 @@
#include "nvim/memory.h"
#include "nvim/move.h"
#include "nvim/ops.h"
+#include "nvim/pos.h"
+#include "nvim/types.h"
#include "nvim/undo.h"
#include "nvim/vim.h"
-#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/buffer.c.generated.h"
@@ -78,7 +84,7 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err)
///
/// Example (Lua): capture buffer updates in a global `events` variable
/// (use "print(vim.inspect(events))" to see its contents):
-/// <pre>
+/// <pre>lua
/// events = {}
/// vim.api.nvim_buf_attach(0, false, {
/// on_lines=function(...) table.insert(events, {...}) end})
@@ -271,6 +277,7 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id,
Integer start,
Integer end,
Boolean strict_indexing,
+ lua_State *lstate,
Error *err)
FUNC_API_SINCE(1)
{
@@ -300,21 +307,18 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id,
return rv;
}
- rv.size = (size_t)(end - start);
- rv.items = xcalloc(rv.size, sizeof(Object));
+ size_t size = (size_t)(end - start);
+
+ init_line_array(lstate, &rv, size);
- if (!buf_collect_lines(buf, rv.size, start,
- (channel_id != VIML_INTERNAL_CALL), &rv, err)) {
+ if (!buf_collect_lines(buf, size, (linenr_T)start, 0, (channel_id != VIML_INTERNAL_CALL), &rv,
+ lstate, err)) {
goto end;
}
end:
if (ERROR_SET(err)) {
- for (size_t i = 0; i < rv.size; i++) {
- xfree(rv.items[i].data.string.data);
- }
-
- xfree(rv.items);
+ api_free_array(rv);
rv.items = NULL;
}
@@ -355,6 +359,8 @@ static bool check_string_array(Array arr, bool disallow_nl, Error *err)
/// Out-of-bounds indices are clamped to the nearest valid value, unless
/// `strict_indexing` is set.
///
+/// @see |nvim_buf_set_text()|
+///
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
/// @param start First line index
@@ -527,6 +533,8 @@ end:
///
/// Prefer |nvim_buf_set_lines()| if you are only adding or deleting entire lines.
///
+/// @see |nvim_buf_set_lines()|
+///
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
/// @param start_row First line index
@@ -570,7 +578,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
char *str_at_end = NULL;
// Another call to ml_get_buf() may free the line, so make a copy.
- str_at_start = xstrdup((char *)ml_get_buf(buf, (linenr_T)start_row, false));
+ str_at_start = xstrdup(ml_get_buf(buf, (linenr_T)start_row, false));
size_t len_at_start = strlen(str_at_start);
if (start_col < 0 || (size_t)start_col > len_at_start) {
api_set_error(err, kErrorTypeValidation, "start_col out of bounds");
@@ -578,7 +586,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
}
// Another call to ml_get_buf() may free the line, so make a copy.
- str_at_end = xstrdup((char *)ml_get_buf(buf, (linenr_T)end_row, false));
+ str_at_end = xstrdup(ml_get_buf(buf, (linenr_T)end_row, false));
size_t len_at_end = strlen(str_at_end);
if (end_col < 0 || (size_t)end_col > len_at_end) {
api_set_error(err, kErrorTypeValidation, "end_col out of bounds");
@@ -608,7 +616,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
for (int64_t i = 1; i < end_row - start_row; i++) {
int64_t lnum = start_row + i;
- const char *bufline = (char *)ml_get_buf(buf, (linenr_T)lnum, false);
+ const char *bufline = ml_get_buf(buf, (linenr_T)lnum, false);
old_byte += (bcount_t)(strlen(bufline)) + 1;
}
old_byte += (bcount_t)end_col + 1;
@@ -786,7 +794,8 @@ early_end:
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)
+ Dictionary opts, lua_State *lstate,
+ Error *err)
FUNC_API_SINCE(9)
{
Array rv = ARRAY_DICT_INIT;
@@ -826,33 +835,37 @@ ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer,
bool replace_nl = (channel_id != VIML_INTERNAL_CALL);
+ size_t size = (size_t)(end_row - start_row) + 1;
+
+ init_line_array(lstate, &rv, size);
+
if (start_row == end_row) {
- String line = buf_get_text(buf, start_row, start_col, end_col, replace_nl, err);
+ String line = buf_get_text(buf, start_row, start_col, end_col, err);
if (ERROR_SET(err)) {
- return rv;
+ goto end;
}
-
- ADD(rv, STRING_OBJ(line));
+ push_linestr(lstate, &rv, line.data, line.size, 0, replace_nl);
return rv;
}
- rv.size = (size_t)(end_row - start_row) + 1;
- rv.items = xcalloc(rv.size, sizeof(Object));
+ String str = buf_get_text(buf, start_row, start_col, MAXCOL - 1, err);
+
+ push_linestr(lstate, &rv, str.data, str.size, 0, replace_nl);
- 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)) {
+ if (size > 2) {
+ if (!buf_collect_lines(buf, size - 2, (linenr_T)start_row + 1, 1, replace_nl, &rv, lstate,
+ err)) {
goto end;
}
}
- rv.items[rv.size - 1] = STRING_OBJ(buf_get_text(buf, end_row, 0, end_col, replace_nl, err));
+ str = buf_get_text(buf, end_row, 0, end_col, err);
+ push_linestr(lstate, &rv, str.data, str.size, (int)(size - 1), replace_nl);
+
if (ERROR_SET(err)) {
goto end;
}
@@ -945,7 +958,7 @@ Integer nvim_buf_get_changedtick(Buffer buffer, Error *err)
/// @param[out] err Error details, if any
/// @returns Array of |maparg()|-like dictionaries describing mappings.
/// The "buffer" key holds the associated buffer handle.
-ArrayOf(Dictionary) nvim_buf_get_keymap(uint64_t channel_id, Buffer buffer, String mode, Error *err)
+ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err)
FUNC_API_SINCE(3)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -954,7 +967,7 @@ ArrayOf(Dictionary) nvim_buf_get_keymap(uint64_t channel_id, Buffer buffer, Stri
return (Array)ARRAY_DICT_INIT;
}
- return keymap_array(mode, buf, channel_id == LUA_INTERNAL_CALL);
+ return keymap_array(mode, buf);
}
/// Sets a buffer-local |mapping| for the given mode.
@@ -1021,7 +1034,7 @@ void nvim_buf_del_var(Buffer buffer, String name, Error *err)
/// @param buffer Buffer handle, or 0 for current buffer
/// @param[out] err Error details, if any
/// @return Buffer name
-String nvim_buf_get_name(Buffer buffer, Error *err)
+String nvim_buf_get_name(Buffer buffer, Arena *arena, Error *err)
FUNC_API_SINCE(1)
{
String rv = STRING_INIT;
@@ -1031,7 +1044,7 @@ String nvim_buf_get_name(Buffer buffer, Error *err)
return rv;
}
- return cstr_to_string((char *)buf->b_ffname);
+ return cstr_as_string(buf->b_ffname);
}
/// Sets the full file name for a buffer
@@ -1386,3 +1399,91 @@ static int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bo
index++;
return index;
}
+
+/// Initialise a string array either:
+/// - on the Lua stack (as a table) (if lstate is not NULL)
+/// - as an API array object (if lstate is NULL).
+///
+/// @param lstate Lua state. When NULL the Array is initialized instead.
+/// @param a Array to initialize
+/// @param size Size of array
+static inline void init_line_array(lua_State *lstate, Array *a, size_t size)
+{
+ if (lstate) {
+ lua_createtable(lstate, (int)size, 0);
+ } else {
+ a->size = size;
+ a->items = xcalloc(a->size, sizeof(Object));
+ }
+}
+
+/// Push a string onto either the Lua stack (as a table element) or an API array object.
+///
+/// For Lua, a table of the correct size must be created first.
+/// API array objects must be pre allocated.
+///
+/// @param lstate Lua state. When NULL the Array is pushed to instead.
+/// @param a Array to push onto when not using Lua
+/// @param s String to push
+/// @param len Size of string
+/// @param idx 0-based index to place s
+/// @param replace_nl Replace newlines ('\n') with null ('\0')
+static void push_linestr(lua_State *lstate, Array *a, const char *s, size_t len, int idx,
+ bool replace_nl)
+{
+ if (lstate) {
+ // Vim represents NULs as NLs
+ if (s && replace_nl && strchr(s, '\n')) {
+ char *tmp = xmemdupz(s, len);
+ strchrsub(tmp, '\n', '\0');
+ lua_pushlstring(lstate, tmp, len);
+ xfree(tmp);
+ } else {
+ lua_pushlstring(lstate, s, len);
+ }
+ lua_rawseti(lstate, -2, idx + 1);
+ } else {
+ String str = STRING_INIT;
+ if (s) {
+ str = cbuf_to_string(s, len);
+ if (replace_nl) {
+ // Vim represents NULs as NLs, but this may confuse clients.
+ strchrsub(str.data, '\n', '\0');
+ }
+ }
+
+ a->items[idx] = STRING_OBJ(str);
+ }
+}
+
+/// Collects `n` buffer lines into array `l` and/or lua_State `lstate`, optionally replacing
+/// newlines with NUL.
+///
+/// @param buf Buffer to get lines from
+/// @param n Number of lines to collect
+/// @param replace_nl Replace newlines ("\n") with NUL
+/// @param start Line number to start from
+/// @param start_idx First index to push to
+/// @param[out] l If not NULL, Lines are copied here
+/// @param[out] lstate If not NULL, Lines are pushed into a table onto the stack
+/// @param err[out] Error, if any
+/// @return true unless `err` was set
+bool buf_collect_lines(buf_T *buf, size_t n, linenr_T start, int start_idx, bool replace_nl,
+ Array *l, lua_State *lstate, Error *err)
+{
+ for (size_t i = 0; i < n; i++) {
+ linenr_T lnum = start + (linenr_T)i;
+
+ if (lnum >= MAXLNUM) {
+ if (err != NULL) {
+ api_set_error(err, kErrorTypeValidation, "Line index is too high");
+ }
+ return false;
+ }
+
+ char *bufstr = ml_get_buf(buf, lnum, false);
+ push_linestr(lstate, l, bufstr, strlen(bufstr), start_idx + (int)i, replace_nl);
+ }
+
+ return true;
+}
diff --git a/src/nvim/api/buffer.h b/src/nvim/api/buffer.h
index 1c4a93a587..0814da63cd 100644
--- a/src/nvim/api/buffer.h
+++ b/src/nvim/api/buffer.h
@@ -1,7 +1,10 @@
#ifndef NVIM_API_BUFFER_H
#define NVIM_API_BUFFER_H
+#include <lauxlib.h>
+
#include "nvim/api/private/defs.h"
+#include "nvim/buffer_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/buffer.h.generated.h"
diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c
index 1323fc347b..abd265f2cf 100644
--- a/src/nvim/api/command.c
+++ b/src/nvim/api/command.c
@@ -1,21 +1,36 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+#include <inttypes.h>
#include <stdbool.h>
-#include <stdint.h>
-#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "klib/kvec.h"
+#include "lauxlib.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/ascii.h"
#include "nvim/autocmd.h"
+#include "nvim/buffer_defs.h"
+#include "nvim/decoration.h"
+#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
+#include "nvim/garray.h"
+#include "nvim/globals.h"
#include "nvim/lua/executor.h"
+#include "nvim/macros.h"
+#include "nvim/mbyte.h"
+#include "nvim/memory.h"
#include "nvim/ops.h"
+#include "nvim/pos.h"
#include "nvim/regexp.h"
+#include "nvim/strings.h"
+#include "nvim/types.h"
#include "nvim/usercmd.h"
+#include "nvim/vim.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -31,17 +46,18 @@
/// @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.
+/// - range: (array) (optional) Command range (|<line1>| |<line2>|).
+/// Omitted if command doesn't accept a range.
+/// Otherwise, has no elements if no range was specified, one element if
+/// only a single range item was specified, or two elements if both range
+/// items were specified.
+/// - count: (number) (optional) Command |<count>|.
+/// Omitted if command cannot take a count.
+/// - reg: (string) (optional) Command |<register>|.
+/// Omitted 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.
+/// - addr: (string) Value of |:command-addr|. Uses short name or "line" for -addr=lines.
/// - 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.
@@ -62,13 +78,14 @@
/// - browse: (boolean) |:browse|.
/// - confirm: (boolean) |:confirm|.
/// - hide: (boolean) |:hide|.
+/// - horizontal: (boolean) |:horizontal|.
/// - keepalt: (boolean) |:keepalt|.
/// - keepjumps: (boolean) |:keepjumps|.
/// - keepmarks: (boolean) |:keepmarks|.
/// - keeppatterns: (boolean) |:keeppatterns|.
/// - lockmarks: (boolean) |:lockmarks|.
/// - noswapfile: (boolean) |:noswapfile|.
-/// - tab: (integer) |:tab|.
+/// - tab: (integer) |:tab|. -1 when omitted.
/// - verbose: (integer) |:verbose|. -1 when omitted.
/// - vertical: (boolean) |:vertical|.
/// - split: (string) Split modifier string, is an empty string when there's no split
@@ -104,7 +121,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
// Parse arguments
Array args = ARRAY_DICT_INIT;
- size_t length = STRLEN(ea.arg);
+ size_t length = strlen(ea.arg);
// For nargs = 1 or '?', pass the entire argument list as a single argument,
// otherwise split arguments by whitespace.
@@ -141,15 +158,15 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx)));
}
- if ((ea.argt & EX_RANGE) && ea.addr_count > 0) {
+ if (ea.argt & EX_RANGE) {
Array range = ARRAY_DICT_INIT;
- if (ea.addr_count > 1) {
- ADD(range, INTEGER_OBJ(ea.line1));
+ if (ea.addr_count > 0) {
+ if (ea.addr_count > 1) {
+ ADD(range, INTEGER_OBJ(ea.line1));
+ }
+ ADD(range, INTEGER_OBJ(ea.line2));
}
- 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) {
@@ -160,14 +177,12 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
} 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));
+ if (ea.argt & EX_REGSTR) {
+ char reg[2] = { (char)ea.regname, NUL };
+ PUT(result, "reg", CSTR_TO_OBJ(reg));
+ }
PUT(result, "bang", BOOLEAN_OBJ(ea.forceit));
PUT(result, "args", ARRAY_OBJ(args));
@@ -238,7 +253,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
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, "tab", INTEGER_OBJ(cmdinfo.cmdmod.cmod_tab - 1));
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));
@@ -250,6 +265,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
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));
+ PUT(mods, "horizontal", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_split & WSP_HOR));
const char *split;
if (cmdinfo.cmdmod.cmod_split & WSP_BOT) {
@@ -283,7 +299,11 @@ end:
/// 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.
+/// doesn't expand filenames, etc. Command arguments may also be Number, Boolean or String.
+///
+/// The first argument may also be used instead of count for commands that support it in order to
+/// make their usage simpler with |vim.cmd()|. For example, instead of
+/// `vim.cmd.bdelete{ count = 2 }`, you may do `vim.cmd.bdelete(2)`.
///
/// On execution error: fails with VimL error, updates v:errmsg.
///
@@ -308,8 +328,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
char *cmdline = NULL;
char *cmdname = NULL;
- ArrayOf(String) args;
- size_t argc = 0;
+ ArrayOf(String) args = ARRAY_DICT_INIT;
String retv = (String)STRING_INIT;
@@ -381,48 +400,70 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
if (cmd->args.type != kObjectTypeArray) {
VALIDATION_ERROR("'args' must be an Array");
}
- // Check if every argument is valid
+
+ // Process all arguments. Convert non-String arguments to String and check if String arguments
+ // have non-whitespace characters.
for (size_t i = 0; i < cmd->args.data.array.size; i++) {
Object elem = cmd->args.data.array.items[i];
- 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");
+ char *data_str;
+
+ switch (elem.type) {
+ case kObjectTypeBoolean:
+ data_str = xcalloc(2, sizeof(char));
+ data_str[0] = elem.data.boolean ? '1' : '0';
+ data_str[1] = '\0';
+ break;
+ case kObjectTypeBuffer:
+ case kObjectTypeWindow:
+ case kObjectTypeTabpage:
+ case kObjectTypeInteger:
+ data_str = xcalloc(NUMBUFLEN, sizeof(char));
+ snprintf(data_str, NUMBUFLEN, "%" PR