aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/api')
-rw-r--r--src/nvim/api/buffer.c721
-rw-r--r--src/nvim/api/buffer.h2
-rw-r--r--src/nvim/api/deprecated.c2
-rw-r--r--src/nvim/api/extmark.c895
-rw-r--r--src/nvim/api/extmark.h13
-rw-r--r--src/nvim/api/private/converter.c348
-rw-r--r--src/nvim/api/private/converter.h11
-rw-r--r--src/nvim/api/private/defs.h2
-rw-r--r--src/nvim/api/private/dispatch.c2
-rw-r--r--src/nvim/api/private/helpers.c355
-rw-r--r--src/nvim/api/private/helpers.h2
-rw-r--r--src/nvim/api/tabpage.h2
-rw-r--r--src/nvim/api/vim.c930
-rw-r--r--src/nvim/api/vim.h6
-rw-r--r--src/nvim/api/vimscript.c733
-rw-r--r--src/nvim/api/vimscript.h9
-rw-r--r--src/nvim/api/win_config.c28
-rw-r--r--src/nvim/api/win_config.h2
-rw-r--r--src/nvim/api/window.h2
19 files changed, 2086 insertions, 1979 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 31d44c68bf..4076a0d220 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -32,7 +32,6 @@
#include "nvim/misc1.h"
#include "nvim/move.h"
#include "nvim/ops.h"
-#include "nvim/syntax.h"
#include "nvim/undo.h"
#include "nvim/vim.h"
#include "nvim/window.h"
@@ -247,7 +246,6 @@ Boolean nvim_buf_detach(uint64_t channel_id, Buffer buffer, Error *err)
}
void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last, Error *err)
- FUNC_API_LUA_ONLY
{
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
@@ -1163,10 +1161,12 @@ Boolean nvim_buf_del_mark(Buffer buffer, String name, Error *err)
/// @param name Mark name
/// @param line Line number
/// @param col Column/row number
+/// @param opts Optional parameters. Reserved for future use.
/// @return true if the mark was set, else false.
/// @see |nvim_buf_del_mark()|
/// @see |nvim_buf_get_mark()|
-Boolean nvim_buf_set_mark(Buffer buffer, String name, Integer line, Integer col, Error *err)
+Boolean nvim_buf_set_mark(Buffer buffer, String name, Integer line, Integer col, Dictionary opts,
+ Error *err)
FUNC_API_SINCE(8)
{
bool res = false;
@@ -1239,720 +1239,6 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
return rv;
}
-static Array extmark_to_array(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(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));
- }
-
- 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));
- }
- PUT(dict, "virt_text", ARRAY_OBJ(chunks));
- }
-
- PUT(dict, "priority", INTEGER_OBJ(decor->priority));
- }
-
- if (dict.size) {
- ADD(rv, DICTIONARY_OBJ(dict));
- }
- }
-
- return rv;
-}
-
-/// Gets the position (0-indexed) of an extmark.
-///
-/// @param buffer Buffer handle, or 0 for current buffer
-/// @param ns_id Namespace id from |nvim_create_namespace()|
-/// @param id Extmark id
-/// @param opts Optional parameters. Keys:
-/// - details: Whether to include the details dict
-/// @param[out] err Error details, if any
-/// @return 0-indexed (row, col) tuple or empty list () if extmark id was
-/// absent
-ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
- Integer id, Dictionary opts,
- Error *err)
- FUNC_API_SINCE(7)
-{
- Array rv = ARRAY_DICT_INIT;
-
- buf_T *buf = find_buffer_by_handle(buffer, err);
-
- if (!buf) {
- return rv;
- }
-
- if (!ns_initialized((uint64_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
- return rv;
- }
-
- bool details = false;
- for (size_t i = 0; i < opts.size; i++) {
- String k = opts.items[i].key;
- Object *v = &opts.items[i].value;
- if (strequal("details", k.data)) {
- if (v->type == kObjectTypeBoolean) {
- details = v->data.boolean;
- } else if (v->type == kObjectTypeInteger) {
- details = v->data.integer;
- } else {
- api_set_error(err, kErrorTypeValidation, "details is not an boolean");
- return rv;
- }
- } else {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
- return rv;
- }
- }
-
-
- ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id);
- if (extmark.row < 0) {
- return rv;
- }
- return extmark_to_array(extmark, false, details);
-}
-
-/// Gets extmarks in "traversal order" from a |charwise| region defined by
-/// buffer positions (inclusive, 0-indexed |api-indexing|).
-///
-/// Region can be given as (row,col) tuples, or valid extmark ids (whose
-/// positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1)
-/// respectively, thus the following are equivalent:
-///
-/// <pre>
-/// nvim_buf_get_extmarks(0, my_ns, 0, -1, {})
-/// nvim_buf_get_extmarks(0, my_ns, [0,0], [-1,-1], {})
-/// </pre>
-///
-/// If `end` is less than `start`, traversal works backwards. (Useful
-/// with `limit`, to get the first marks prior to a given position.)
-///
-/// Example:
-///
-/// <pre>
-/// local a = vim.api
-/// local pos = a.nvim_win_get_cursor(0)
-/// local ns = a.nvim_create_namespace('my-plugin')
-/// -- Create new extmark at line 1, column 1.
-/// local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, 0, {})
-/// -- Create new extmark at line 3, column 1.
-/// local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, 0, {})
-/// -- Get extmarks only from line 3.
-/// local ms = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {})
-/// -- Get all marks in this buffer + namespace.
-/// local all = a.nvim_buf_get_extmarks(0, ns, 0, -1, {})
-/// print(vim.inspect(ms))
-/// </pre>
-///
-/// @param buffer Buffer handle, or 0 for current buffer
-/// @param ns_id Namespace id from |nvim_create_namespace()|
-/// @param start Start of range: a 0-indexed (row, col) or valid extmark id
-/// (whose position defines the bound). |api-indexing|
-/// @param end End of range (inclusive): a 0-indexed (row, col) or valid
-/// extmark id (whose position defines the bound). |api-indexing|
-/// @param opts Optional parameters. Keys:
-/// - limit: Maximum number of marks to return
-/// - details Whether to include the details dict
-/// @param[out] err Error details, if any
-/// @return List of [extmark_id, row, col] tuples in "traversal order".
-Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object end, Dictionary opts,
- Error *err)
- FUNC_API_SINCE(7)
-{
- Array rv = ARRAY_DICT_INIT;
-
- buf_T *buf = find_buffer_by_handle(buffer, err);
- if (!buf) {
- return rv;
- }
-
- if (!ns_initialized((uint64_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
- return rv;
- }
-
- Integer limit = -1;
- bool details = false;
-
- for (size_t i = 0; i < opts.size; i++) {
- String k = opts.items[i].key;
- Object *v = &opts.items[i].value;
- if (strequal("limit", k.data)) {
- if (v->type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation, "limit is not an integer");
- return rv;
- }
- limit = v->data.integer;
- } else if (strequal("details", k.data)) {
- if (v->type == kObjectTypeBoolean) {
- details = v->data.boolean;
- } else if (v->type == kObjectTypeInteger) {
- details = v->data.integer;
- } else {
- api_set_error(err, kErrorTypeValidation, "details is not an boolean");
- return rv;
- }
- } else {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
- return rv;
- }
- }
-
- if (limit == 0) {
- return rv;
- } else if (limit < 0) {
- limit = INT64_MAX;
- }
-
-
- bool reverse = false;
-
- int l_row;
- colnr_T l_col;
- if (!extmark_get_index_from_obj(buf, ns_id, start, &l_row, &l_col, err)) {
- return rv;
- }
-
- int u_row;
- colnr_T u_col;
- if (!extmark_get_index_from_obj(buf, ns_id, end, &u_row, &u_col, err)) {
- return rv;
- }
-
- if (l_row > u_row || (l_row == u_row && l_col > u_col)) {
- reverse = true;
- }
-
-
- ExtmarkInfoArray marks = extmark_get(buf, (uint64_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)));
- }
-
- kv_destroy(marks);
- return rv;
-}
-
-/// 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.)
-///
-/// 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.
-///
-/// @param buffer Buffer handle, or 0 for current buffer
-/// @param ns_id Namespace id from |nvim_create_namespace()|
-/// @param line Line where to place the mark, 0-based. |api-indexing|
-/// @param col Column where to place the mark, 0-based. |api-indexing|
-/// @param opts Optional parameters.
-/// - id : id of the extmark to edit.
-/// - end_line : ending line of the mark, 0-based inclusive.
-/// - end_col : ending col of the mark, 0-based exclusive.
-/// - hl_group : name of the highlight group used to highlight
-/// this mark.
-/// - hl_eol : when true, for a multiline highlight covering the
-/// EOL of a line, continue the highlight for the rest
-/// of the screen line (just like for diff and
-/// cursorline highlight).
-/// - virt_text : virtual text to link to this mark.
-/// A list of [text, highlight] tuples, each representing a
-/// text chunk with specified highlight. `highlight` element
-/// can either be a a single highlight group, or an array of
-/// multiple highlight groups that will be stacked
-/// (highest priority last). A highlight group can be supplied
-/// either as a string or as an integer, the latter which
-/// can be obtained using |nvim_get_hl_id_by_name|.
-/// - virt_text_pos : position of virtual text. Possible values:
-/// - "eol": right after eol character (default)
-/// - "overlay": display over the specified column, without
-/// shifting the underlying text.
-/// - "right_align": display right aligned in the window.
-/// - virt_text_win_col : position the virtual text at a fixed
-/// window column (starting from the first
-/// text column)
-/// - virt_text_hide : hide the virtual text when the background
-/// text is selected or hidden due to
-/// horizontal scroll 'nowrap'
-/// - hl_mode : control how highlights are combined with the
-/// highlights of the text. Currently only affects
-/// virt_text highlights, but might affect `hl_group`
-/// in later versions.
-/// - "replace": only show the virt_text color. This is the
-/// default
-/// - "combine": combine with background text color
-/// - "blend": blend with background text color.
-///
-/// - virt_lines : virtual lines to add next to this mark
-/// This should be an array over lines, where each line in
-/// turn is an array over [text, highlight] tuples. In
-/// general, buffer and window options do not affect the
-/// display of the text. In particular 'wrap'
-/// and 'linebreak' options do not take effect, so
-/// the number of extra screen lines will always match
-/// the size of the array. However the 'tabstop' buffer
-/// option is still used for hard tabs. By default lines are
-/// placed below the buffer line containing the mark.
-///
-/// Note: currently virtual lines are limited to one block
-/// per buffer. Thus setting a new mark disables any previous
-/// `virt_lines` decoration. However plugins should not rely
-/// on this behaviour, as this limitation is planned to be
-/// removed.
-///
-/// - virt_lines_above: place virtual lines above instead.
-/// - virt_lines_leftcol: Place extmarks in the leftmost
-/// column of the window, bypassing
-/// sign and number columns.
-///
-/// - ephemeral : for use with |nvim_set_decoration_provider|
-/// callbacks. The mark will only be used for the current
-/// redraw cycle, and not be permantently stored in the
-/// buffer.
-/// - right_gravity : boolean that indicates the direction
-/// the extmark will be shifted in when new text is inserted
-/// (true for right, false for left). defaults to true.
-/// - end_right_gravity : boolean that indicates the direction
-/// the extmark end position (if it exists) will be shifted
-/// in when new text is inserted (true for right, false
-/// for left). Defaults to false.
-/// - priority: a priority value for the highlight group. For
-/// example treesitter highlighting uses a value of 100.
-/// @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,
- Dict(set_extmark) *opts, Error *err)
- FUNC_API_SINCE(7)
-{
- Decoration decor = DECORATION_INIT;
-
- buf_T *buf = find_buffer_by_handle(buffer, err);
- if (!buf) {
- goto error;
- }
-
- if (!ns_initialized((uint64_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
- goto error;
- }
-
- uint64_t id = 0;
- if (opts->id.type == kObjectTypeInteger && opts->id.data.integer > 0) {
- id = (uint64_t)opts->id.data.integer;
- } else if (HAS_KEY(opts->id)) {
- api_set_error(err, kErrorTypeValidation, "id is not a positive integer");
- goto error;
- }
-
- int line2 = -1;
- if (opts->end_line.type == kObjectTypeInteger) {
- Integer val = opts->end_line.data.integer;
- if (val < 0 || val > buf->b_ml.ml_line_count) {
- api_set_error(err, kErrorTypeValidation, "end_line value outside range");
- goto error;
- } else {
- line2 = (int)val;
- }
- } else if (HAS_KEY(opts->end_line)) {
- api_set_error(err, kErrorTypeValidation, "end_line is not an integer");
- goto error;
- }
-
- colnr_T col2 = -1;
- if (opts->end_col.type == kObjectTypeInteger) {
- Integer val = opts->end_col.data.integer;
- if (val < 0 || val > MAXCOL) {
- api_set_error(err, kErrorTypeValidation, "end_col value outside range");
- goto error;
- } else {
- col2 = (int)val;
- }
- } else if (HAS_KEY(opts->end_col)) {
- api_set_error(err, kErrorTypeValidation, "end_col is not an integer");
- goto error;
- }
-
- 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;
- }
- }
-
- if (opts->virt_text.type == kObjectTypeArray) {
- decor.virt_text = parse_virt_text(opts->virt_text.data.array, err,
- &decor.virt_text_width);
- if (ERROR_SET(err)) {
- goto error;
- }
- } else if (HAS_KEY(opts->virt_text)) {
- api_set_error(err, kErrorTypeValidation, "virt_text is not an Array");
- goto error;
- }
-
- if (opts->virt_text_pos.type == kObjectTypeString) {
- String str = opts->virt_text_pos.data.string;
- if (strequal("eol", str.data)) {
- decor.virt_text_pos = kVTEndOfLine;
- } else if (strequal("overlay", str.data)) {
- decor.virt_text_pos = kVTOverlay;
- } else if (strequal("right_align", str.data)) {
- decor.virt_text_pos = kVTRightAlign;
- } else {
- api_set_error(err, kErrorTypeValidation, "virt_text_pos: invalid value");
- goto error;
- }
- } else if (HAS_KEY(opts->virt_text_pos)) {
- api_set_error(err, kErrorTypeValidation, "virt_text_pos is not a String");
- goto error;
- }
-
- if (opts->virt_text_win_col.type == kObjectTypeInteger) {
- decor.col = (int)opts->virt_text_win_col.data.integer;
- decor.virt_text_pos = kVTWinCol;
- } else if (HAS_KEY(opts->virt_text_win_col)) {
- api_set_error(err, kErrorTypeValidation,
- "virt_text_win_col is not a Number of the correct size");
- goto error;
- }
-
-#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);
-
- if (opts->hl_mode.type == kObjectTypeString) {
- String str = opts->hl_mode.data.string;
- if (strequal("replace", str.data)) {
- decor.hl_mode = kHlModeReplace;
- } else if (strequal("combine", str.data)) {
- decor.hl_mode = kHlModeCombine;
- } else if (strequal("blend", str.data)) {
- decor.hl_mode = kHlModeBlend;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "virt_text_pos: invalid value");
- goto error;
- }
- } else if (HAS_KEY(opts->hl_mode)) {
- api_set_error(err, kErrorTypeValidation, "hl_mode is not a String");
- goto error;
- }
-
- VirtLines virt_lines = KV_INITIAL_VALUE;
- bool virt_lines_above = false;
- bool virt_lines_leftcol = false;
-
- if (opts->virt_lines.type == kObjectTypeArray) {
- Array a = opts->virt_lines.data.array;
- for (size_t j = 0; j < a.size; j++) {
- if (a.items[j].type != kObjectTypeArray) {
- api_set_error(err, kErrorTypeValidation, "virt_text_line item is not an Array");
- goto error;
- }
- int dummig;
- VirtText jtem = parse_virt_text(a.items[j].data.array, err, &dummig);
- kv_push(virt_lines, jtem);
- if (ERROR_SET(err)) {
- goto error;
- }
- }
- } else if (HAS_KEY(opts->virt_lines)) {
- api_set_error(err, kErrorTypeValidation, "virt_lines is not an Array");
- goto error;
- }
-
- OPTION_TO_BOOL(virt_lines_above, virt_lines_above, false);
- OPTION_TO_BOOL(virt_lines_leftcol, virt_lines_leftcol, false);
-
- if (opts->priority.type == kObjectTypeInteger) {
- Integer val = opts->priority.data.integer;
-
- if (val < 0 || val > UINT16_MAX) {
- api_set_error(err, kErrorTypeValidation, "priority is not a valid value");
- goto error;
- }
- decor.priority = (DecorPriority)val;
- } else if (HAS_KEY(opts->priority)) {
- api_set_error(err, kErrorTypeValidation, "priority is not a Number of the correct size");
- goto error;
- }
-
- bool right_gravity = true;
- OPTION_TO_BOOL(right_gravity, right_gravity, true);
-
- // Only error out if they try to set end_right_gravity without
- // setting end_col or end_line
- if (line2 == -1 && col2 == -1 && HAS_KEY(opts->end_right_gravity)) {
- api_set_error(err, kErrorTypeValidation,
- "cannot set end_right_gravity without setting end_line or end_col");
- goto error;
- }
-
- bool end_right_gravity = false;
- OPTION_TO_BOOL(end_right_gravity, end_right_gravity, false);
-
- size_t len = 0;
-
- bool ephemeral = false;
- OPTION_TO_BOOL(ephemeral, ephemeral, false);
-
- if (line < 0 || line > buf->b_ml.ml_line_count) {
- api_set_error(err, kErrorTypeValidation, "line value outside range");
- goto error;
- } else if (line < buf->b_ml.ml_line_count) {
- 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) {
- api_set_error(err, kErrorTypeValidation, "col value outside range");
- goto error;
- }
-
- if (col2 >= 0) {
- if (line2 >= 0 && line2 < buf->b_ml.ml_line_count) {
- len = ephemeral ? MAXCOL : STRLEN(ml_get_buf(buf, (linenr_T)line2 + 1, false));
- } else if (line2 == buf->b_ml.ml_line_count) {
- // We are trying to add an extmark past final newline
- len = 0;
- } else {
- // reuse len from before
- line2 = (int)line;
- }
- if (col2 > (Integer)len) {
- api_set_error(err, kErrorTypeValidation, "end_col value outside range");
- goto error;
- }
- } else if (line2 >= 0) {
- col2 = 0;
- }
-
- Decoration *d = NULL;
-
- if (ephemeral) {
- d = &decor;
- } else if (kv_size(decor.virt_text)
- || 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);
- } else {
- if (ephemeral) {
- api_set_error(err, kErrorTypeException, "not yet implemented");
- goto error;
- }
-
- if (kv_size(virt_lines) && buf->b_virt_line_mark) {
- mtpos_t pos = marktree_lookup(buf->b_marktree, buf->b_virt_line_mark, NULL);
- clear_virt_lines(buf, pos.row); // handles pos.row == -1
- }
-
- uint64_t mark = 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(virt_lines)) {
- buf->b_virt_lines = virt_lines;
- buf->b_virt_line_mark = mark;
- buf->b_virt_line_pos = -1;
- buf->b_virt_line_above = virt_lines_above;
- buf->b_virt_line_leftcol = virt_lines_leftcol;
- redraw_buf_line_later(buf, MIN(buf->b_ml.ml_line_count, line+1+(virt_lines_above?0:1)));
- }
- }
-
- return (Integer)id;
-
-error:
- clear_virttext(&decor.virt_text);
- return 0;
-}
-
-/// Removes an extmark.
-///
-/// @param buffer Buffer handle, or 0 for current buffer
-/// @param ns_id Namespace id from |nvim_create_namespace()|
-/// @param id Extmark id
-/// @param[out] err Error details, if any
-/// @return true if the extmark was found, else false
-Boolean nvim_buf_del_extmark(Buffer buffer, Integer ns_id, Integer id, Error *err)
- FUNC_API_SINCE(7)
-{
- buf_T *buf = find_buffer_by_handle(buffer, err);
-
- if (!buf) {
- return false;
- }
- if (!ns_initialized((uint64_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
- return false;
- }
-
- return extmark_del(buf, (uint64_t)ns_id, (uint64_t)id);
-}
-
-/// Adds a highlight to buffer.
-///
-/// Useful for plugins that dynamically generate highlights to a buffer
-/// (like a semantic highlighter or linter). The function adds a single
-/// highlight to a buffer. Unlike |matchaddpos()| highlights follow changes to
-/// line numbering (as lines are inserted/removed above the highlighted line),
-/// like signs and marks do.
-///
-/// Namespaces are used for batch deletion/updating of a set of highlights. To
-/// create a namespace, use |nvim_create_namespace()| which returns a namespace
-/// id. Pass it in to this function as `ns_id` to add highlights to the
-/// namespace. All highlights in the same namespace can then be cleared with
-/// single call to |nvim_buf_clear_namespace()|. If the highlight never will be
-/// deleted by an API call, pass `ns_id = -1`.
-///
-/// As a shorthand, `ns_id = 0` can be used to create a new namespace for the
-/// highlight, the allocated id is then returned. If `hl_group` is the empty
-/// string no highlight is added, but a new `ns_id` is still returned. This is
-/// supported for backwards compatibility, new code should use
-/// |nvim_create_namespace()| to create a new empty namespace.
-///
-/// @param buffer Buffer handle, or 0 for current buffer
-/// @param ns_id namespace to use or -1 for ungrouped highlight
-/// @param hl_group Name of the highlight group to use
-/// @param line Line to highlight (zero-indexed)
-/// @param col_start Start of (byte-indexed) column range to highlight
-/// @param col_end End of (byte-indexed) column range to highlight,
-/// or -1 to highlight to end of line
-/// @param[out] err Error details, if any
-/// @return The ns_id that was used
-Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, Integer line,
- Integer col_start, Integer col_end, Error *err)
- FUNC_API_SINCE(1)
-{
- buf_T *buf = find_buffer_by_handle(buffer, err);
- if (!buf) {
- return 0;
- }
-
- if (line < 0 || line >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Line number outside range");
- return 0;
- }
- if (col_start < 0 || col_start > MAXCOL) {
- api_set_error(err, kErrorTypeValidation, "Column value outside range");
- return 0;
- }
- if (col_end < 0 || col_end > MAXCOL) {
- col_end = MAXCOL;
- }
-
- uint64_t ns = src2ns(&ns_id);
-
- if (!(line < buf->b_ml.ml_line_count)) {
- // safety check, we can't add marks outside the range
- return ns_id;
- }
-
- int hl_id = 0;
- if (hl_group.size > 0) {
- hl_id = syn_check_group(hl_group.data, (int)hl_group.size);
- } else {
- return ns_id;
- }
-
- int end_line = (int)line;
- if (col_end == MAXCOL) {
- col_end = 0;
- end_line++;
- }
-
- extmark_set(buf, ns, NULL,
- (int)line, (colnr_T)col_start,
- end_line, (colnr_T)col_end,
- decor_hl(hl_id), true, false, kExtmarkNoUndo);
- return ns_id;
-}
-
-/// Clears namespaced objects (highlights, extmarks, virtual text) from
-/// a region.
-///
-/// Lines are 0-indexed. |api-indexing| To clear the namespace in the entire
-/// buffer, specify line_start=0 and line_end=-1.
-///
-/// @param buffer Buffer handle, or 0 for current buffer
-/// @param ns_id Namespace to clear, or -1 to clear all namespaces.
-/// @param line_start Start of range of lines to clear
-/// @param line_end End of range of lines to clear (exclusive) or -1 to clear
-/// to end of buffer.
-/// @param[out] err Error details, if any
-void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start, Integer line_end,
- Error *err)
- FUNC_API_SINCE(5)
-{
- buf_T *buf = find_buffer_by_handle(buffer, err);
- if (!buf) {
- return;
- }
-
- if (line_start < 0 || line_start >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Line number outside range");
- return;
- }
- if (line_end < 0 || line_end > MAXLNUM) {
- line_end = MAXLNUM;
- }
- extmark_clear(buf, (ns_id < 0 ? 0 : (uint64_t)ns_id),
- (int)line_start, 0,
- (int)line_end-1, MAXCOL);
-}
/// call a function with buffer as temporary current buffer
///
@@ -2013,6 +1299,7 @@ Dictionary nvim__buf_stats(Buffer buffer, Error *err)
// this exists to debug issues
PUT(rv, "dirty_bytes", INTEGER_OBJ((Integer)buf->deleted_bytes));
PUT(rv, "dirty_bytes2", INTEGER_OBJ((Integer)buf->deleted_bytes2));
+ PUT(rv, "virt_blocks", INTEGER_OBJ((Integer)buf->b_virt_line_blocks));
u_header_T *uhp = NULL;
if (buf->b_u_curhead != NULL) {
diff --git a/src/nvim/api/buffer.h b/src/nvim/api/buffer.h
index 9aa898f7da..1c4a93a587 100644
--- a/src/nvim/api/buffer.h
+++ b/src/nvim/api/buffer.h
@@ -1,8 +1,6 @@
#ifndef NVIM_API_BUFFER_H
#define NVIM_API_BUFFER_H
-#include <stdint.h>
-
#include "nvim/api/private/defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c
index 907d09e5b7..76b699800e 100644
--- a/src/nvim/api/deprecated.c
+++ b/src/nvim/api/deprecated.c
@@ -8,9 +8,11 @@
#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/helpers.h"
#include "nvim/api/vim.h"
+#include "nvim/api/vimscript.h"
#include "nvim/extmark.h"
#include "nvim/lua/executor.h"
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
new file mode 100644
index 0000000000..6f1fb15dac
--- /dev/null
+++ b/src/nvim/api/extmark.c
@@ -0,0 +1,895 @@
+// 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/extmark.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/extmark.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"
+#endif
+
+void api_extmark_free_all_mem(void)
+{
+ String name;
+ handle_T id;
+ map_foreach(&namespace_ids, name, id, {
+ (void)id;
+ xfree(name.data);
+ })
+ map_destroy(String, handle_T)(&namespace_ids);
+}
+
+/// Creates a new \*namespace\* or gets an existing one.
+///
+/// Namespaces are used for buffer highlights and virtual text, see
+/// |nvim_buf_add_highlight()| and |nvim_buf_set_extmark()|.
+///
+/// Namespaces can be named or anonymous. If `name` matches an existing
+/// namespace, the associated id is returned. If `name` is an empty string
+/// a new, anonymous namespace is created.
+///
+/// @param name Namespace name or empty string
+/// @return Namespace id
+Integer nvim_create_namespace(String name)
+ FUNC_API_SINCE(5)
+{
+ handle_T id = map_get(String, handle_T)(&namespace_ids, name);
+ if (id > 0) {
+ return id;
+ }
+ id = next_namespace_id++;
+ if (name.size > 0) {
+ String name_alloc = copy_string(name);
+ map_put(String, handle_T)(&namespace_ids, name_alloc, id);
+ }
+ return (Integer)id;
+}
+
+/// Gets existing, non-anonymous namespaces.
+///
+/// @return dict that maps from names to namespace ids.
+Dictionary nvim_get_namespaces(void)
+ FUNC_API_SINCE(5)
+{
+ Dictionary retval = ARRAY_DICT_INIT;
+ String name;
+ handle_T id;
+
+ map_foreach(&namespace_ids, name, id, {
+ PUT(retval, name.data, INTEGER_OBJ(id));
+ })
+
+ return retval;
+}
+
+const char *describe_ns(NS ns_id)
+{
+ String name;
+ handle_T id;
+ map_foreach(&namespace_ids, name, id, {
+ if ((NS)id == ns_id && name.size) {
+ return name.data;
+ }
+ })
+ return "(UNKNOWN PLUGIN)";
+}
+
+// Is the Namespace in use?
+static bool ns_initialized(uint64_t ns)
+{
+ if (ns < 1) {
+ return false;
+ }
+ return ns < (uint64_t)next_namespace_id;
+}
+
+
+static Array extmark_to_array(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(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));
+ }
+
+ 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));
+ }
+ PUT(dict, "virt_text", ARRAY_OBJ(chunks));
+ }
+
+ PUT(dict, "priority", INTEGER_OBJ(decor->priority));
+ }
+
+ if (dict.size) {
+ ADD(rv, DICTIONARY_OBJ(dict));
+ }
+ }
+
+ return rv;
+}
+
+/// Gets the position (0-indexed) of an extmark.
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param id Extmark id
+/// @param opts Optional parameters. Keys:
+/// - details: Whether to include the details dict
+/// @param[out] err Error details, if any
+/// @return 0-indexed (row, col) tuple or empty list () if extmark id was
+/// absent
+ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
+ Integer id, Dictionary opts,
+ Error *err)
+ FUNC_API_SINCE(7)
+{
+ Array rv = ARRAY_DICT_INIT;
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return rv;
+ }
+
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ return rv;
+ }
+
+ bool details = false;
+ for (size_t i = 0; i < opts.size; i++) {
+ String k = opts.items[i].key;
+ Object *v = &opts.items[i].value;
+ if (strequal("details", k.data)) {
+ if (v->type == kObjectTypeBoolean) {
+ details = v->data.boolean;
+ } else if (v->type == kObjectTypeInteger) {
+ details = v->data.integer;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "details is not an boolean");
+ return rv;
+ }
+ } else {
+ api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ return rv;
+ }
+ }
+
+
+ ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id);
+ if (extmark.row < 0) {
+ return rv;
+ }
+ return extmark_to_array(extmark, false, details);
+}
+
+/// Gets extmarks in "traversal order" from a |charwise| region defined by
+/// buffer positions (inclusive, 0-indexed |api-indexing|).
+///
+/// Region can be given as (row,col) tuples, or valid extmark ids (whose
+/// positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1)
+/// respectively, thus the following are equivalent:
+///
+/// <pre>
+/// nvim_buf_get_extmarks(0, my_ns, 0, -1, {})
+/// nvim_buf_get_extmarks(0, my_ns, [0,0], [-1,-1], {})
+/// </pre>
+///
+/// If `end` is less than `start`, traversal works backwards. (Useful
+/// with `limit`, to get the first marks prior to a given position.)
+///
+/// Example:
+///
+/// <pre>
+/// local a = vim.api
+/// local pos = a.nvim_win_get_cursor(0)
+/// local ns = a.nvim_create_namespace('my-plugin')
+/// -- Create new extmark at line 1, column 1.
+/// local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, 0, {})
+/// -- Create new extmark at line 3, column 1.
+/// local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, 0, {})
+/// -- Get extmarks only from line 3.
+/// local ms = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {})
+/// -- Get all marks in this buffer + namespace.
+/// local all = a.nvim_buf_get_extmarks(0, ns, 0, -1, {})
+/// print(vim.inspect(ms))
+/// </pre>
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param start Start of range: a 0-indexed (row, col) or valid extmark id
+/// (whose position defines the bound). |api-indexing|
+/// @param end End of range (inclusive): a 0-indexed (row, col) or valid
+/// extmark id (whose position defines the bound). |api-indexing|
+/// @param opts Optional parameters. Keys:
+/// - limit: Maximum number of marks to return
+/// - details Whether to include the details dict
+/// @param[out] err Error details, if any
+/// @return List of [extmark_id, row, col] tuples in "traversal order".
+Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object end, Dictionary opts,
+ Error *err)
+ FUNC_API_SINCE(7)
+{
+ Array rv = ARRAY_DICT_INIT;
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return rv;
+ }
+
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ return rv;
+ }
+
+ Integer limit = -1;
+ bool details = false;
+
+ for (size_t i = 0; i < opts.size; i++) {
+ String k = opts.items[i].key;
+ Object *v = &opts.items[i].value;
+ if (strequal("limit", k.data)) {
+ if (v->type != kObjectTypeInteger) {
+ api_set_error(err, kErrorTypeValidation, "limit is not an integer");
+ return rv;
+ }
+ limit = v->data.integer;
+ } else if (strequal("details", k.data)) {
+ if (v->type == kObjectTypeBoolean) {
+ details = v->data.boolean;
+ } else if (v->type == kObjectTypeInteger) {
+ details = v->data.integer;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "details is not an boolean");
+ return rv;
+ }
+ } else {
+ api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ return rv;
+ }
+ }
+
+ if (limit == 0) {
+ return rv;
+ } else if (limit < 0) {
+ limit = INT64_MAX;
+ }
+
+
+ bool reverse = false;
+
+ int l_row;
+ colnr_T l_col;
+ if (!extmark_get_index_from_obj(buf, ns_id, start, &l_row, &l_col, err)) {
+ return rv;
+ }
+
+ int u_row;
+ colnr_T u_col;
+ if (!extmark_get_index_from_obj(buf, ns_id, end, &u_row, &u_col, err)) {
+ return rv;
+ }
+
+ if (l_row > u_row || (l_row == u_row && l_col > u_col)) {
+ reverse = true;
+ }
+
+
+ ExtmarkInfoArray marks = extmark_get(buf, (uint64_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)));
+ }
+
+ kv_destroy(marks);
+ return rv;
+}
+
+/// 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.)
+///
+/// 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.
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param line Line where to place the mark, 0-based. |api-indexing|
+/// @param col Column where to place the mark, 0-based. |api-indexing|
+/// @param opts Optional parameters.
+/// - id : id of the extmark to edit.
+/// - end_line : ending line of the mark, 0-based inclusive.
+/// - end_col : ending col of the mark, 0-based exclusive.
+/// - hl_group : name of the highlight group used to highlight
+/// this mark.
+/// - hl_eol : when true, for a multiline highlight covering the
+/// EOL of a line, continue the highlight for the rest
+/// of the screen line (just like for diff and
+/// cursorline highlight).
+/// - virt_text : virtual text to link to this mark.
+/// A list of [text, highlight] tuples, each representing a
+/// text chunk with specified highlight. `highlight` element
+/// can either be a a single highlight group, or an array of
+/// multiple highlight groups that will be stacked
+/// (highest priority last). A highlight group can be supplied
+/// either as a string or as an integer, the latter which
+/// can be obtained using |nvim_get_hl_id_by_name|.
+/// - virt_text_pos : position of virtual text. Possible values:
+/// - "eol": right after eol character (default)
+/// - "overlay": display over the specified column, without
+/// shifting the underlying text.
+/// - "right_align": display right aligned in the window.
+/// - virt_text_win_col : position the virtual text at a fixed
+/// window column (starting from the first
+/// text column)
+/// - virt_text_hide : hide the virtual text when the background
+/// text is selected or hidden due to
+/// horizontal scroll 'nowrap'
+/// - hl_mode : control how highlights are combined with the
+/// highlights of the text. Currently only affects
+/// virt_text highlights, but might affect `hl_group`
+/// in later versions.
+/// - "replace": only show the virt_text color. This is the
+/// default
+/// - "combine": combine with background text color
+/// - "blend": blend with background text color.
+///
+/// - virt_lines : virtual lines to add next to this mark
+/// This should be an array over lines, where each line in
+/// turn is an array over [text, highlight] tuples. In
+/// general, buffer and window options do not affect the
+/// display of the text. In particular 'wrap'
+/// and 'linebreak' options do not take effect, so
+/// the number of extra screen lines will always match
+/// the size of the array. However the 'tabstop' buffer
+/// option is still used for hard tabs. By default lines are
+/// placed below the buffer line containing the mark.
+///
+/// - virt_lines_above: place virtual lines above instead.
+/// - virt_lines_leftcol: Place extmarks in the leftmost
+/// column of the window, bypassing
+/// sign and number columns.
+///
+/// - ephemeral : for use with |nvim_set_decoration_provider|
+/// callbacks. The mark will only be used for the current
+/// redraw cycle, and not be permantently stored in the
+/// buffer.
+/// - right_gravity : boolean that indicates the direction
+/// the extmark will be shifted in when new text is inserted
+/// (true for right, false for left). defaults to true.
+/// - end_right_gravity : boolean that indicates the direction
+/// the extmark end position (if it exists) will be shifted
+/// in when new text is inserted (true for right, false
+/// for left). Defaults to false.
+/// - priority: a priority value for the highlight group. For
+/// example treesitter highlighting uses a value of 100.
+/// @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,
+ Dict(set_extmark) *opts, Error *err)
+ FUNC_API_SINCE(7)
+{
+ Decoration decor = DECORATION_INIT;
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ goto error;
+ }
+
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ goto error;
+ }
+
+ uint64_t id = 0;
+ if (opts->id.type == kObjectTypeInteger && opts->id.data.integer > 0) {
+ id = (uint64_t)opts->id.data.integer;
+ } else if (HAS_KEY(opts->id)) {
+ api_set_error(err, kErrorTypeValidation, "id is not a positive integer");
+ goto error;
+ }
+
+ int line2 = -1;
+ if (opts->end_line.type == kObjectTypeInteger) {
+ Integer val = opts->end_line.data.integer;
+ if (val < 0 || val > buf->b_ml.ml_line_count) {
+ api_set_error(err, kErrorTypeValidation, "end_line value outside range");
+ goto error;
+ } else {
+ line2 = (int)val;
+ }
+ } else if (HAS_KEY(opts->end_line)) {
+ api_set_error(err, kErrorTypeValidation, "end_line is not an integer");
+ goto error;
+ }
+
+ colnr_T col2 = -1;
+ if (opts->end_col.type == kObjectTypeInteger) {
+ Integer val = opts->end_col.data.integer;
+ if (val < 0 || val > MAXCOL) {
+ api_set_error(err, kErrorTypeValidation, "end_col value outside range");
+ goto error;
+ } else {
+ col2 = (int)val;
+ }
+ } else if (HAS_KEY(opts->end_col)) {
+ api_set_error(err, kErrorTypeValidation, "end_col is not an integer");
+ goto error;
+ }
+
+ 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;
+ }
+ }
+
+ if (opts->virt_text.type == kObjectTypeArray) {
+ decor.virt_text = parse_virt_text(opts->virt_text.data.array, err,
+ &decor.virt_text_width);
+ if (ERROR_SET(err)) {
+ goto error;
+ }
+ } else if (HAS_KEY(opts->virt_text)) {
+ api_set_error(err, kErrorTypeValidation, "virt_text is not an Array");
+ goto error;
+ }
+
+ if (opts->virt_text_pos.type == kObjectTypeString) {
+ String str = opts->virt_text_pos.data.string;
+ if (strequal("eol", str.data)) {
+ decor.virt_text_pos = kVTEndOfLine;
+ } else if (strequal("overlay", str.data)) {
+ decor.virt_text_pos = kVTOverlay;
+ } else if (strequal("right_align", str.data)) {
+ decor.virt_text_pos = kVTRightAlign;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "virt_text_pos: invalid value");
+ goto error;
+ }
+ } else if (HAS_KEY(opts->virt_text_pos)) {
+ api_set_error(err, kErrorTypeValidation, "virt_text_pos is not a String");
+ goto error;
+ }
+
+ if (opts->virt_text_win_col.type == kObjectTypeInteger) {
+ decor.col = (int)opts->virt_text_win_col.data.integer;
+ decor.virt_text_pos = kVTWinCol;
+ } else if (HAS_KEY(opts->virt_text_win_col)) {
+ api_set_error(err, kErrorTypeValidation,
+ "virt_text_win_col is not a Number of the correct size");
+ goto error;
+ }
+
+#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);
+
+ if (opts->hl_mode.type == kObjectTypeString) {
+ String str = opts->hl_mode.data.string;
+ if (strequal("replace", str.data)) {
+ decor.hl_mode = kHlModeReplace;
+ } else if (strequal("combine", str.data)) {
+ decor.hl_mode = kHlModeCombine;
+ } else if (strequal("blend", str.data)) {
+ decor.hl_mode = kHlModeBlend;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "virt_text_pos: invalid value");
+ goto error;
+ }
+ } else if (HAS_KEY(opts->hl_mode)) {
+ api_set_error(err, kErrorTypeValidation, "hl_mode is not a String");
+ goto error;
+ }
+
+ bool virt_lines_leftcol = false;
+ OPTION_TO_BOOL(virt_lines_leftcol, virt_lines_leftcol, false);
+
+ if (opts->virt_lines.type == kObjectTypeArray) {
+ Array a = opts->virt_lines.data.array;
+ for (size_t j = 0; j < a.size; j++) {
+ if (a.items[j].type != kObjectTypeArray) {
+ api_set_error(err, kErrorTypeValidation, "virt_text_line item is not an Array");
+ goto error;
+ }
+ int dummig;
+ VirtText jtem = parse_virt_text(a.items[j].data.array, err, &dummig);
+ kv_push(decor.virt_lines, ((struct virt_line){ jtem, virt_lines_leftcol }));
+ if (ERROR_SET(err)) {
+ goto error;
+ }
+ }
+ } else if (HAS_KEY(opts->virt_lines)) {
+ api_set_error(err, kErrorTypeValidation, "virt_lines is not an Array");
+ goto error;
+ }
+
+
+ OPTION_TO_BOOL(decor.virt_lines_above, virt_lines_above, false);
+
+ if (opts->priority.type == kObjectTypeInteger) {
+ Integer val = opts->priority.data.integer;
+
+ if (val < 0 || val > UINT16_MAX) {
+ api_set_error(err, kErrorTypeValidation, "priority is not a valid value");
+ goto error;
+ }
+ decor.priority = (DecorPriority)val;
+ } else if (HAS_KEY(opts->priority)) {
+ api_set_error(err, kErrorTypeValidation, "priority is not a Number of the correct size");
+ goto error;
+ }
+
+ bool right_gravity = true;
+ OPTION_TO_BOOL(right_gravity, right_gravity, true);
+
+ // Only error out if they try to set end_right_gravity without
+ // setting end_col or end_line
+ if (line2 == -1 && col2 == -1 && HAS_KEY(opts->end_right_gravity)) {
+ api_set_error(err, kErrorTypeValidation,
+ "cannot set end_right_gravity without setting end_line or end_col");
+ goto error;
+ }
+
+ bool end_right_gravity = false;
+ OPTION_TO_BOOL(end_right_gravity, end_right_gravity, false);
+
+ size_t len = 0;
+
+ bool ephemeral = false;
+ OPTION_TO_BOOL(ephemeral, ephemeral, false);
+
+ if (line < 0 || line > buf->b_ml.ml_line_count) {
+ api_set_error(err, kErrorTypeValidation, "line value outside range");
+ goto error;
+ } else if (line < buf->b_ml.ml_line_count) {
+ 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) {
+ api_set_error(err, kErrorTypeValidation, "col value outside range");
+ goto error;
+ }
+
+ if (col2 >= 0) {
+ if (line2 >= 0 && line2 < buf->b_ml.ml_line_count) {
+ len = ephemeral ? MAXCOL : STRLEN(ml_get_buf(buf, (linenr_T)line2 + 1, false));
+ } else if (line2 == buf->b_ml.ml_line_count) {
+ // We are trying to add an extmark past final newline
+ len = 0;
+ } else {
+ // reuse len from before
+ line2 = (int)line;
+ }
+ if (col2 > (Integer)len) {
+ api_set_error(err, kErrorTypeValidation, "end_col value outside range");
+ goto error;
+ }
+ } 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);
+ } 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)));
+ }
+ }
+
+ return (Integer)id;
+
+error:
+ clear_virttext(&decor.virt_text);
+ return 0;
+}
+
+/// Removes an extmark.
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param id Extmark id
+/// @param[out] err Error details, if any
+/// @return true if the extmark was found, else false
+Boolean nvim_buf_del_extmark(Buffer buffer, Integer ns_id, Integer id, Error *err)
+ FUNC_API_SINCE(7)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return false;
+ }
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ return false;
+ }
+
+ return extmark_del(buf, (uint64_t)ns_id, (uint64_t)id);
+}
+
+uint64_t src2ns(Integer *src_id)
+{
+ if (*src_id == 0) {
+ *src_id = nvim_create_namespace((String)STRING_INIT);
+ }
+ if (*src_id < 0) {
+ return UINT64_MAX;
+ } else {
+ return (uint64_t)(*src_id);
+ }
+}
+
+/// Adds a highlight to buffer.
+///
+/// Useful for plugins that dynamically generate highlights to a buffer
+/// (like a semantic highlighter or linter). The function adds a single
+/// highlight to a buffer. Unlike |matchaddpos()| highlights follow changes to
+/// line numbering (as lines are inserted/removed above the highlighted line),
+/// like signs and marks do.
+///
+/// Namespaces are used for batch deletion/updating of a set of highlights. To
+/// create a namespace, use |nvim_create_namespace()| which returns a namespace
+/// id. Pass it in to this function as `ns_id` to add highlights to the
+/// namespace. All highlights in the same namespace can then be cleared with
+/// single call to |nvim_buf_clear_namespace()|. If the highlight never will be
+/// deleted by an API call, pass `ns_id = -1`.
+///
+/// As a shorthand, `ns_id = 0` can be used to create a new namespace for the
+/// highlight, the allocated id is then returned. If `hl_group` is the empty
+/// string no highlight is added, but a new `ns_id` is still returned. This is
+/// supported for backwards compatibility, new code should use
+/// |nvim_create_namespace()| to create a new empty namespace.
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id namespace to use or -1 for ungrouped highlight
+/// @param hl_group Name of the highlight group to use
+/// @param line Line to highlight (zero-indexed)
+/// @param col_start Start of (byte-indexed) column range to highlight
+/// @param col_end End of (byte-indexed) column range to highlight,
+/// or -1 to highlight to end of line
+/// @param[out] err Error details, if any
+/// @return The ns_id that was used
+Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, Integer line,
+ Integer col_start, Integer col_end, Error *err)
+ FUNC_API_SINCE(1)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return 0;
+ }
+
+ if (line < 0 || line >= MAXLNUM) {
+ api_set_error(err, kErrorTypeValidation, "Line number outside range");
+ return 0;
+ }
+ if (col_start < 0 || col_start > MAXCOL) {
+ api_set_error(err, kErrorTypeValidation, "Column value outside range");
+ return 0;
+ }
+ if (col_end < 0 || col_end > MAXCOL) {
+ col_end = MAXCOL;
+ }
+
+ uint64_t ns = src2ns(&ns_id);
+
+ if (!(line < buf->b_ml.ml_line_count)) {
+ // safety check, we can't add marks outside the range
+ return ns_id;
+ }
+
+ int hl_id = 0;
+ if (hl_group.size > 0) {
+ hl_id = syn_check_group(hl_group.data, (int)hl_group.size);
+ } else {
+ return ns_id;
+ }
+
+ int end_line = (int)line;
+ if (col_end == MAXCOL) {
+ col_end = 0;
+ end_line++;
+ }
+
+ extmark_set(buf, ns, NULL,
+ (int)line, (colnr_T)col_start,
+ end_line, (colnr_T)col_end,
+ decor_hl(hl_id), true, false, kExtmarkNoUndo);
+ return ns_id;
+}
+
+/// Clears namespaced objects (highlights, extmarks, virtual text) from
+/// a region.
+///
+/// Lines are 0-indexed. |api-indexing| To clear the namespace in the entire
+/// buffer, specify line_start=0 and line_end=-1.
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace to clear, or -1 to clear all namespaces.
+/// @param line_start Start of range of lines to clear
+/// @param line_end End of range of lines to clear (exclusive) or -1 to clear
+/// to end of buffer.
+/// @param[out] err Error details, if any
+void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start, Integer line_end,
+ Error *err)
+ FUNC_API_SINCE(5)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return;
+ }
+
+ if (line_start < 0 || line_start >= MAXLNUM) {
+ api_set_error(err, kErrorTypeValidation, "Line number outside range");
+ return;
+ }
+ if (line_end < 0 || line_end > MAXLNUM) {
+ line_end = MAXLNUM;
+ }
+ extmark_clear(buf, (ns_id < 0 ? 0 : (uint64_t)ns_id),
+ (int)line_start, 0,
+ (int)line_end-1, MAXCOL);
+}
+
+/// Set or change decoration provider for a namespace
+///
+/// This is a very general purpose interface for having lua callbacks
+/// being triggered during the redraw code.
+///
+/// The expected usage is to set extmarks for the currently
+/// redrawn buffer. |nvim_buf_set_extmark| can be called to add marks
+/// on a per-window or per-lines basis. Use the `ephemeral` key to only
+/// use the mark for the current screen redraw (the callback will be called
+/// again for the next redraw ).
+///
+/// Note: this function should not be called often. Rather, the callbacks
+/// themselves can be used to throttle unneeded callbacks. the `on_start`
+/// callback can return `false` to disable the provider until the next redraw.
+/// Similarly, return `false` in `on_win` will skip the `on_lines` calls
+/// for that window (but any extmarks set in `on_win` will still be used).
+/// A plugin managing multiple sources of decoration should ideally only set
+/// one provider, and merge the sources internally. You can use multiple `ns_id`
+/// for the extmarks set/modified inside the callback anyway.
+///
+/// Note: doing anything other than setting extmarks is considered experimental.
+/// Doing things like changing options are not expliticly forbidden, but is
+/// likely to have unexpected consequences (such as 100% CPU consumption).
+/// doing `vim.rpcnotify` should be OK, but `vim.rpcrequest` is quite dubious
+/// for the moment.
+///
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param opts Callbacks invoked during redraw:
+/// - on_start: called first on each screen redraw
+/// ["start", tick]
+/// - on_buf: called for each buffer being redrawn (before window
+/// callbacks)
+/// ["buf", bufnr, tick]
+/// - on_win: called when starting to redraw a specific window.
+/// ["win", winid, bufnr, topline, botline_guess]
+/// - on_line: called for each buffer line being redrawn. (The
+/// interation with fold lines is subject to change)
+/// ["win", winid, bufnr, row]
+/// - on_end: called at the end of a redraw cycle
+/// ["end", tick]
+void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Error *err)
+ FUNC_API_SINCE(7) FUNC_API_LUA_ONLY
+{
+ DecorProvider *p = get_decor_provider((NS)ns_id, true);
+ assert(p != NULL);
+ decor_provider_clear(p);
+
+ // regardless of what happens, it seems good idea to redraw
+ redraw_all_later(NOT_VALID); // TODO(bfredl): too soon?
+
+ struct {
+ const char *name;
+ LuaRef *dest;
+ } cbs[] = {
+ { "on_start", &p->redraw_start },
+ { "on_buf", &p->redraw_buf },
+ { "on_win", &p->redraw_win },
+ { "on_line", &p->redraw_line },
+ { "on_end", &p->redraw_end },
+ { "_on_hl_def", &p->hl_def },
+ { NULL, NULL },
+ };
+
+ for (size_t i = 0; i < opts.size; i++) {
+ String k = opts.items[i].key;
+ Object *v = &opts.items[i].value;
+ size_t j;
+ for (j = 0; cbs[j].name && cbs[j].dest; j++) {
+ if (strequal(cbs[j].name, k.data)) {
+ if (v->type != kObjectTypeLuaRef) {
+ api_set_error(err, kErrorTypeValidation,
+ "%s is not a function", cbs[j].name);
+ goto error;
+ }
+ *(cbs[j].dest) = v->data.luaref;
+ v->data.luaref = LUA_NOREF;
+ break;
+ }
+ }
+ if (!cbs[j].name) {
+ api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ goto error;
+ }
+ }
+
+ p->active = true;
+ return;
+error:
+ decor_provider_clear(p);
+}
diff --git a/src/nvim/api/extmark.h b/src/nvim/api/extmark.h
new file mode 100644
index 0000000000..c5e463cd86
--- /dev/null
+++ b/src/nvim/api/extmark.h
@@ -0,0 +1,13 @@
+#ifndef NVIM_API_EXTMARK_H
+#define NVIM_API_EXTMARK_H
+
+#include "nvim/api/private/defs.h"
+#include "nvim/map.h"
+
+EXTERN Map(String, handle_T) namespace_ids INIT(= MAP_INIT);
+EXTERN handle_T next_namespace_id INIT(= 1);
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/extmark.h.generated.h"
+#endif
+#endif // NVIM_API_EXTMARK_H
diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c
new file mode 100644
index 0000000000..36da6c13a9
--- /dev/null
+++ b/src/nvim/api/private/converter.c
@@ -0,0 +1,348 @@
+// 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 <stddef.h>
+#include <stdlib.h>
+
+#include "nvim/api/private/converter.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/assert.h"
+#include "nvim/eval/typval.h"
+
+/// Helper structure for vim_to_object
+typedef struct {
+ kvec_withinit_t(Object, 2) stack; ///< Object stack.
+} EncodedData;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/private/converter.c.generated.h"
+#endif
+
+#define TYPVAL_ENCODE_ALLOW_SPECIALS false
+
+#define TYPVAL_ENCODE_CONV_NIL(tv) \
+ kvi_push(edata->stack, NIL)
+
+#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \
+ kvi_push(edata->stack, BOOLEAN_OBJ((Boolean)(num)))
+
+#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \
+ kvi_push(edata->stack, INTEGER_OBJ((Integer)(num)))
+
+#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER TYPVAL_ENCODE_CONV_NUMBER
+
+#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \
+ kvi_push(edata->stack, FLOAT_OBJ((Float)(flt)))
+
+#define TYPVAL_ENCODE_CONV_STRING(tv, str, len) \
+ do { \
+ const size_t len_ = (size_t)(len); \
+ const char *const str_ = (const char *)(str); \
+ assert(len_ == 0 || str_ != NULL); \
+ kvi_push(edata->stack, STRING_OBJ(cbuf_to_string((len_?str_:""), len_))); \
+ } while (0)
+
+#define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING
+
+#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \
+ TYPVAL_ENCODE_CONV_NIL(tv)
+
+#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
+ do { \
+ 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, \
+ .size = len_ \
+ }))); \
+ } while (0)
+
+#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
+ do { \
+ TYPVAL_ENCODE_CONV_NIL(tv); \
+ goto typval_encode_stop_converting_one_item; \
+ } while (0)
+
+#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len)
+#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, len)
+#define TYPVAL_ENCODE_CONV_FUNC_END(tv)
+
+#define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \
+ kvi_push(edata->stack, ARRAY_OBJ(((Array) { .capacity = 0, .size = 0 })))
+
+#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \
+ kvi_push(edata->stack, \
+ DICTIONARY_OBJ(((Dictionary) { .capacity = 0, .size = 0 })))
+
+static inline void typval_encode_list_start(EncodedData *const edata, const size_t len)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
+{
+ kvi_push(edata->stack, ARRAY_OBJ(((Array) {
+ .capacity = len,
+ .size = 0,
+ .items = xmalloc(len * sizeof(*((Object)OBJECT_INIT).data.array.items)),
+ })));
+}
+
+#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \
+ typval_encode_list_start(edata, (size_t)(len))
+
+#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv)
+
+static inline void typval_encode_between_list_items(EncodedData *const edata)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
+{
+ Object item = kv_pop(edata->stack);
+ Object *const list = &kv_last(edata->stack);
+ assert(list->type == kObjectTypeArray);
+ assert(list->data.array.size < list->data.array.capacity);
+ list->data.array.items[list->data.array.size++] = item;
+}
+
+#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv) \
+ typval_encode_between_list_items(edata)
+
+static inline void typval_encode_list_end(EncodedData *const edata)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
+{
+ typval_encode_between_list_items(edata);
+#ifndef NDEBUG
+ const Object *const list = &kv_last(edata->stack);
+ assert(list->data.array.size == list->data.array.capacity);
+#endif
+}
+
+#define TYPVAL_ENCODE_CONV_LIST_END(tv) \
+ typval_encode_list_end(edata)
+
+static inline void typval_encode_dict_start(EncodedData *const edata, const size_t len)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
+{
+ kvi_push(edata->stack, DICTIONARY_OBJ(((Dictionary) {
+ .capacity = len,
+ .size = 0,
+ .items = xmalloc(len * sizeof(*((Object)OBJECT_INIT).data.dictionary.items)),
+ })));
+}
+
+#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \
+ typval_encode_dict_start(edata, (size_t)(len))
+
+#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv)
+
+#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, kv_pair)
+
+static inline void typval_encode_after_key(EncodedData *const edata)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
+{
+ Object key = kv_pop(edata->stack);
+ Object *const dict = &kv_last(edata->stack);
+ assert(dict->type == kObjectTypeDictionary);
+ assert(dict->data.dictionary.size < dict->data.dictionary.capacity);
+ if (key.type == kObjectTypeString) {
+ dict->data.dictionary.items[dict->data.dictionary.size].key
+ = key.data.string;
+ } else {
+ api_free_object(key);
+ dict->data.dictionary.items[dict->data.dictionary.size].key
+ = STATIC_CSTR_TO_STRING("__INVALID_KEY__");
+ }
+}
+
+#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict) \
+ typval_encode_after_key(edata)
+
+static inline void typval_encode_between_dict_items(EncodedData *const edata)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
+{
+ Object val = kv_pop(edata->stack);
+ Object *const dict = &kv_last(edata->stack);
+ assert(dict->type == kObjectTypeDictionary);
+ assert(dict->data.dictionary.size < dict->data.dictionary.capacity);
+ dict->data.dictionary.items[dict->data.dictionary.size++].value = val;
+}
+
+#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) \
+ typval_encode_between_dict_items(edata)
+
+static inline void typval_encode_dict_end(EncodedData *const edata)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
+{
+ typval_encode_between_dict_items(edata);
+#ifndef NDEBUG
+ const Object *const dict = &kv_last(edata->stack);
+ assert(dict->data.dictionary.size == dict->data.dictionary.capacity);
+#endif
+}
+
+#define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) \
+ typval_encode_dict_end(edata)
+
+#define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \
+ TYPVAL_ENCODE_CONV_NIL(val)
+
+#define TYPVAL_ENCODE_SCOPE static
+#define TYPVAL_ENCODE_NAME object
+#define TYPVAL_ENCODE_FIRST_ARG_TYPE EncodedData *const
+#define TYPVAL_ENCODE_FIRST_ARG_NAME edata
+#include "nvim/eval/typval_encode.c.h"
+#undef TYPVAL_ENCODE_SCOPE
+#undef TYPVAL_ENCODE_NAME
+#undef TYPVAL_ENCODE_FIRST_ARG_TYPE
+#undef TYPVAL_ENCODE_FIRST_ARG_NAME
+
+#undef TYPVAL_ENCODE_CONV_STRING
+#undef TYPVAL_ENCODE_CONV_STR_STRING
+#undef TYPVAL_ENCODE_CONV_EXT_STRING
+#undef TYPVAL_ENCODE_CONV_BLOB
+#undef TYPVAL_ENCODE_CONV_NUMBER
+#undef TYPVAL_ENCODE_CONV_FLOAT
+#undef TYPVAL_ENCODE_CONV_FUNC_START
+#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS
+#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF
+#undef TYPVAL_ENCODE_CONV_FUNC_END
+#undef TYPVAL_ENCODE_CONV_EMPTY_LIST
+#undef TYPVAL_ENCODE_CONV_LIST_START
+#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START
+#undef TYPVAL_ENCODE_CONV_EMPTY_DICT
+#undef TYPVAL_ENCODE_CONV_NIL
+#undef TYPVAL_ENCODE_CONV_BOOL
+#undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER
+#undef TYPVAL_ENCODE_CONV_DICT_START
+#undef TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START
+#undef TYPVAL_ENCODE_CONV_DICT_END
+#undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY
+#undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS
+#undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK
+#undef TYPVAL_ENCODE_CONV_LIST_END
+#undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS
+#undef TYPVAL_ENCODE_CONV_RECURSE
+#undef TYPVAL_ENCODE_ALLOW_SPECIALS
+
+/// Convert a vim object to an `Object` instance, recursively expanding
+/// Arrays/Dictionaries.
+///
+/// @param obj The source object
+/// @return The converted value
+Object vim_to_object(typval_T *obj)
+{
+ EncodedData edata;
+ kvi_init(edata.stack);
+ const int evo_ret = encode_vim_to_object(&edata, obj,
+ "vim_to_object argument");
+ (void)evo_ret;
+ assert(evo_ret == OK);
+ Object ret = kv_A(edata.stack, 0);
+ assert(kv_size(edata.stack) == 1);
+ kvi_destroy(edata.stack);
+ return ret;
+}
+
+/// Converts from type Object to a VimL value.
+///
+/// @param obj Object to convert from.
+/// @param tv Conversion result is placed here. On failure member v_type is
+/// set to VAR_UNKNOWN (no allocation was made for this variable).
+/// returns true if conversion is successful, otherwise false.
+bool object_to_vim(Object obj, typval_T *tv, Error *err)
+{
+ tv->v_type = VAR_UNKNOWN;
+ tv->v_lock = VAR_UNLOCKED;
+
+ switch (obj.type) {
+ case kObjectTypeNil:
+ tv->v_type = VAR_SPECIAL;
+ tv->vval.v_special = kSpecialVarNull;
+ break;
+
+ case kObjectTypeBoolean:
+ tv->v_type = VAR_BOOL;
+ tv->vval.v_bool = obj.data.boolean? kBoolVarTrue: kBoolVarFalse;
+ break;
+
+ case kObjectTypeBuffer:
+ case kObjectTypeWindow:
+ case kObjectTypeTabpage:
+ case kObjectTypeInteger:
+ STATIC_ASSERT(sizeof(obj.data.integer) <= sizeof(varnumber_T),
+ "Integer size must be <= VimL number size");
+ tv->v_type = VAR_NUMBER;
+ tv->vval.v_number = (varnumber_T)obj.data.integer;
+ break;
+
+ case kObjectTypeFloat:
+ tv->v_type = VAR_FLOAT;
+ tv->vval.v_float = obj.data.floating;
+ break;
+
+ case kObjectTypeString:
+ tv->v_type = VAR_STRING;
+ if (obj.data.string.data == NULL) {
+ tv->vval.v_string = NULL;
+ } else {
+ tv->vval.v_string = xmemdupz(obj.data.string.data,
+ obj.data.string.size);
+ }
+ break;
+
+ case kObjectTypeArray: {
+ list_T *const list = tv_list_alloc((ptrdiff_t)obj.data.array.size);
+
+ for (uint32_t i = 0; i < obj.data.array.size; i++) {
+ Object item = obj.data.array.items[i];
+ typval_T li_tv;
+
+ if (!object_to_vim(item, &li_tv, err)) {
+ tv_list_free(list);
+ return false;
+ }
+
+ tv_list_append_owned_tv(list, li_tv);
+ }
+ tv_list_ref(list);
+
+ tv->v_type = VAR_LIST;
+ tv->vval.v_list = list;
+ break;
+ }
+
+ case kObjectTypeDictionary: {
+ dict_T *const dict = tv_dict_alloc();
+
+ for (uint32_t i = 0; i < obj.data.dictionary.size; i++) {
+ KeyValuePair item = obj.data.dictionary.items[i];
+ String key = item.key;
+
+ if (key.size == 0) {
+ api_set_error(err, kErrorTypeValidation,
+ "Empty dictionary keys aren't allowed");
+ // cleanup
+ tv_dict_free(dict);
+ return false;
+ }
+
+ dictitem_T *const di = tv_dict_item_alloc(key.data);
+
+ if (!object_to_vim(item.value, &di->di_tv, err)) {
+ // cleanup
+ tv_dict_item_free(di);
+ tv_dict_free(dict);
+ return false;
+ }
+
+ tv_dict_add(dict, di);
+ }
+ dict->dv_refcount++;
+
+ tv->v_type = VAR_DICT;
+ tv->vval.v_dict = dict;
+ break;
+ }
+ default:
+ abort();
+ }
+
+ return true;
+}
diff --git a/src/nvim/api/private/converter.h b/src/nvim/api/private/converter.h
new file mode 100644
index 0000000000..80ee640295
--- /dev/null
+++ b/src/nvim/api/private/converter.h
@@ -0,0 +1,11 @@
+#ifndef NVIM_API_PRIVATE_CONVERTER_H
+#define NVIM_API_PRIVATE_CONVERTER_H
+
+#include "nvim/api/private/defs.h"
+#include "nvim/eval/typval.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/private/converter.h.generated.h"
+#endif
+
+#endif // NVIM_API_PRIVATE_CONVERTER_H
diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h
index 663d1e16b5..396fab721d 100644
--- a/src/nvim/api/private/defs.h
+++ b/src/nvim/api/private/defs.h
@@ -26,7 +26,7 @@
typedef enum {
kErrorTypeNone = -1,
kErrorTypeException,
- kErrorTypeValidation
+ kErrorTypeValidation,
} ErrorType;
typedef enum {
diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c
index 519bf8ff14..8ab7743e01 100644
--- a/src/nvim/api/private/dispatch.c
+++ b/src/nvim/api/private/dispatch.c
@@ -8,12 +8,14 @@
#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/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"
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index f1259c8a18..d470def277 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -8,6 +8,7 @@
#include <stdlib.h>
#include <string.h>
+#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h"
@@ -37,11 +38,6 @@
#include "nvim/vim.h"
#include "nvim/window.h"
-/// Helper structure for vim_to_object
-typedef struct {
- kvec_withinit_t(Object, 2) stack; ///< Object stack.
-} EncodedData;
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/private/funcs_metadata.generated.h"
# include "api/private/helpers.c.generated.h"
@@ -415,226 +411,6 @@ void set_option_to(uint64_t channel_id, void *to, int type, String name, Object
current_sctx = save_current_sctx;
}
-#define TYPVAL_ENCODE_ALLOW_SPECIALS false
-
-#define TYPVAL_ENCODE_CONV_NIL(tv) \
- kvi_push(edata->stack, NIL)
-
-#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \
- kvi_push(edata->stack, BOOLEAN_OBJ((Boolean)(num)))
-
-#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \
- kvi_push(edata->stack, INTEGER_OBJ((Integer)(num)))
-
-#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER TYPVAL_ENCODE_CONV_NUMBER
-
-#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \
- kvi_push(edata->stack, FLOAT_OBJ((Float)(flt)))
-
-#define TYPVAL_ENCODE_CONV_STRING(tv, str, len) \
- do { \
- const size_t len_ = (size_t)(len); \
- const char *const str_ = (const char *)(str); \
- assert(len_ == 0 || str_ != NULL); \
- kvi_push(edata->stack, STRING_OBJ(cbuf_to_string((len_?str_:""), len_))); \
- } while (0)
-
-#define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING
-
-#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \
- TYPVAL_ENCODE_CONV_NIL(tv)
-
-#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
- do { \
- 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, \
- .size = len_ \
- }))); \
- } while (0)
-
-#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
- do { \
- TYPVAL_ENCODE_CONV_NIL(tv); \
- goto typval_encode_stop_converting_one_item; \
- } while (0)
-
-#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len)
-#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, len)
-#define TYPVAL_ENCODE_CONV_FUNC_END(tv)
-
-#define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \
- kvi_push(edata->stack, ARRAY_OBJ(((Array) { .capacity = 0, .size = 0 })))
-
-#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \
- kvi_push(edata->stack, \
- DICTIONARY_OBJ(((Dictionary) { .capacity = 0, .size = 0 })))
-
-static inline void typval_encode_list_start(EncodedData *const edata, const size_t len)
- FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
-{
- kvi_push(edata->stack, ARRAY_OBJ(((Array) {
- .capacity = len,
- .size = 0,
- .items = xmalloc(len * sizeof(*((Object)OBJECT_INIT).data.array.items)),
- })));
-}
-
-#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \
- typval_encode_list_start(edata, (size_t)(len))
-
-#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv)
-
-static inline void typval_encode_between_list_items(EncodedData *const edata)
- FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
-{
- Object item = kv_pop(edata->stack);
- Object *const list = &kv_last(edata->stack);
- assert(list->type == kObjectTypeArray);
- assert(list->data.array.size < list->data.array.capacity);
- list->data.array.items[list->data.array.size++] = item;
-}
-
-#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv) \
- typval_encode_between_list_items(edata)
-
-static inline void typval_encode_list_end(EncodedData *const edata)
- FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
-{
- typval_encode_between_list_items(edata);
-#ifndef NDEBUG
- const Object *const list = &kv_last(edata->stack);
- assert(list->data.array.size == list->data.array.capacity);
-#endif
-}
-
-#define TYPVAL_ENCODE_CONV_LIST_END(tv) \
- typval_encode_list_end(edata)
-
-static inline void typval_encode_dict_start(EncodedData *const edata, const size_t len)
- FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
-{
- kvi_push(edata->stack, DICTIONARY_OBJ(((Dictionary) {
- .capacity = len,
- .size = 0,
- .items = xmalloc(len * sizeof(
- *((Object)OBJECT_INIT).data.dictionary.items)),
- })));
-}
-
-#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \
- typval_encode_dict_start(edata, (size_t)(len))
-
-#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv)
-
-#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, kv_pair)
-
-static inline void typval_encode_after_key(EncodedData *const edata)
- FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
-{
- Object key = kv_pop(edata->stack);
- Object *const dict = &kv_last(edata->stack);
- assert(dict->type == kObjectTypeDictionary);
- assert(dict->data.dictionary.size < dict->data.dictionary.capacity);
- if (key.type == kObjectTypeString) {
- dict->data.dictionary.items[dict->data.dictionary.size].key
- = key.data.string;
- } else {
- api_free_object(key);
- dict->data.dictionary.items[dict->data.dictionary.size].key
- = STATIC_CSTR_TO_STRING("__INVALID_KEY__");
- }
-}
-
-#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict) \
- typval_encode_after_key(edata)
-
-static inline void typval_encode_between_dict_items(EncodedData *const edata)
- FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
-{
- Object val = kv_pop(edata->stack);
- Object *const dict = &kv_last(edata->stack);
- assert(dict->type == kObjectTypeDictionary);
- assert(dict->data.dictionary.size < dict->data.dictionary.capacity);
- dict->data.dictionary.items[dict->data.dictionary.size++].value = val;
-}
-
-#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) \
- typval_encode_between_dict_items(edata)
-
-static inline void typval_encode_dict_end(EncodedData *const edata)
- FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
-{
- typval_encode_between_dict_items(edata);
-#ifndef NDEBUG
- const Object *const dict = &kv_last(edata->stack);
- assert(dict->data.dictionary.size == dict->data.dictionary.capacity);
-#endif
-}
-
-#define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) \
- typval_encode_dict_end(edata)
-
-#define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \
- TYPVAL_ENCODE_CONV_NIL(val)
-
-#define TYPVAL_ENCODE_SCOPE static
-#define TYPVAL_ENCODE_NAME object
-#define TYPVAL_ENCODE_FIRST_ARG_TYPE EncodedData *const
-#define TYPVAL_ENCODE_FIRST_ARG_NAME edata
-#include "nvim/eval/typval_encode.c.h"
-#undef TYPVAL_ENCODE_SCOPE
-#undef TYPVAL_ENCODE_NAME
-#undef TYPVAL_ENCODE_FIRST_ARG_TYPE
-#undef TYPVAL_ENCODE_FIRST_ARG_NAME
-
-#undef TYPVAL_ENCODE_CONV_STRING
-#undef TYPVAL_ENCODE_CONV_STR_STRING
-#undef TYPVAL_ENCODE_CONV_EXT_STRING
-#undef TYPVAL_ENCODE_CONV_BLOB
-#undef TYPVAL_ENCODE_CONV_NUMBER
-#undef TYPVAL_ENCODE_CONV_FLOAT
-#undef TYPVAL_ENCODE_CONV_FUNC_START
-#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS
-#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF
-#undef TYPVAL_ENCODE_CONV_FUNC_END
-#undef TYPVAL_ENCODE_CONV_EMPTY_LIST
-#undef TYPVAL_ENCODE_CONV_LIST_START
-#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START
-#undef TYPVAL_ENCODE_CONV_EMPTY_DICT
-#undef TYPVAL_ENCODE_CONV_NIL
-#undef TYPVAL_ENCODE_CONV_BOOL
-#undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER
-#undef TYPVAL_ENCODE_CONV_DICT_START
-#undef TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START
-#undef TYPVAL_ENCODE_CONV_DICT_END
-#undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY
-#undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS
-#undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK
-#undef TYPVAL_ENCODE_CONV_LIST_END
-#undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS
-#undef TYPVAL_ENCODE_CONV_RECURSE
-#undef TYPVAL_ENCODE_ALLOW_SPECIALS
-
-/// Convert a vim object to an `Object` instance, recursively expanding
-/// Arrays/Dictionaries.
-///
-/// @param obj The source object
-/// @return The converted value
-Object vim_to_object(typval_T *obj)
-{
- EncodedData edata;
- kvi_init(edata.stack);
- const int evo_ret = encode_vim_to_object(&edata, obj,
- "vim_to_object argument");
- (void)evo_ret;
- assert(evo_ret == OK);
- Object ret = kv_A(edata.stack, 0);
- assert(kv_size(edata.stack) == 1);
- kvi_destroy(edata.stack);
- return ret;
-}
buf_T *find_buffer_by_handle(Buffer buffer, Error *err)
{
@@ -831,7 +607,7 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String
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); \
+ parsed_args.name = api_object_to_bool(opts->name, #name, false, err); \
if (ERROR_SET(err)) { \
goto fail_and_free; \
}
@@ -966,112 +742,6 @@ bool buf_collect_lines(buf_T *buf, size_t n, int64_t start, bool replace_nl, Arr
return true;
}
-/// Converts from type Object to a VimL value.
-///
-/// @param obj Object to convert from.
-/// @param tv Conversion result is placed here. On failure member v_type is
-/// set to VAR_UNKNOWN (no allocation was made for this variable).
-/// returns true if conversion is successful, otherwise false.
-bool object_to_vim(Object obj, typval_T *tv, Error *err)
-{
- tv->v_type = VAR_UNKNOWN;
- tv->v_lock = VAR_UNLOCKED;
-
- switch (obj.type) {
- case kObjectTypeNil:
- tv->v_type = VAR_SPECIAL;
- tv->vval.v_special = kSpecialVarNull;
- break;
-
- case kObjectTypeBoolean:
- tv->v_type = VAR_BOOL;
- tv->vval.v_bool = obj.data.boolean? kBoolVarTrue: kBoolVarFalse;
- break;
-
- case kObjectTypeBuffer:
- case kObjectTypeWindow:
- case kObjectTypeTabpage:
- case kObjectTypeInteger:
- STATIC_ASSERT(sizeof(obj.data.integer) <= sizeof(varnumber_T),
- "Integer size must be <= VimL number size");
- tv->v_type = VAR_NUMBER;
- tv->vval.v_number = (varnumber_T)obj.data.integer;
- break;
-
- case kObjectTypeFloat:
- tv->v_type = VAR_FLOAT;
- tv->vval.v_float = obj.data.floating;
- break;
-
- case kObjectTypeString:
- tv->v_type = VAR_STRING;
- if (obj.data.string.data == NULL) {
- tv->vval.v_string = NULL;
- } else {
- tv->vval.v_string = xmemdupz(obj.data.string.data,
- obj.data.string.size);
- }
- break;
-
- case kObjectTypeArray: {
- list_T *const list = tv_list_alloc((ptrdiff_t)obj.data.array.size);
-
- for (uint32_t i = 0; i < obj.data.array.size; i++) {
- Object item = obj.data.array.items[i];
- typval_T li_tv;
-
- if (!object_to_vim(item, &li_tv, err)) {
- tv_list_free(list);
- return false;
- }
-
- tv_list_append_owned_tv(list, li_tv);
- }
- tv_list_ref(list);
-
- tv->v_type = VAR_LIST;
- tv->vval.v_list = list;
- break;
- }
-
- case kObjectTypeDictionary: {
- dict_T *const dict = tv_dict_alloc();
-
- for (uint32_t i = 0; i < obj.data.dictionary.size; i++) {
- KeyValuePair item = obj.data.dictionary.items[i];
- String key = item.key;
-
- if (key.size == 0) {
- api_set_error(err, kErrorTypeValidation,
- "Empty dictionary keys aren't allowed");
- // cleanup
- tv_dict_free(dict);
- return false;
- }
-
- dictitem_T *const di = tv_dict_item_alloc(key.data);
-
- if (!object_to_vim(item.value, &di->di_tv, err)) {
- // cleanup
- tv_dict_item_free(di);
- tv_dict_free(dict);
- return false;
- }
-
- tv_dict_add(dict, di);
- }
- dict->dv_refcount++;
-
- tv->v_type = VAR_DICT;
- tv->vval.v_dict = dict;
- break;
- }
- default:
- abort();
- }
-
- return true;
-}
void api_free_string(String value)
{
@@ -1410,15 +1080,6 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf)
return mappings;
}
-// Is the Namespace in use?
-bool ns_initialized(uint64_t ns)
-{
- if (ns < 1) {
- return false;
- }
- return ns < (uint64_t)next_namespace_id;
-}
-
/// Gets the line and column of an extmark.
///
/// Extmarks may be queried by position, name or even special names
@@ -1607,18 +1268,6 @@ free_exit:
return hl_msg;
}
-const char *describe_ns(NS ns_id)
-{
- String name;
- handle_T id;
- map_foreach(&namespace_ids, name, id, {
- if ((NS)id == ns_id && name.size) {
- return name.data;
- }
- })
- return "(UNKNOWN PLUGIN)";
-}
-
bool api_dict_to_keydict(void *rv, field_hash hashy, Dictionary dict, Error *err)
{
for (size_t i = 0; i < dict.size; i++) {
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index 08d2c8d90c..6d0aec9c90 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -1,8 +1,6 @@
#ifndef NVIM_API_PRIVATE_HELPERS_H
#define NVIM_API_PRIVATE_HELPERS_H
-#include <stdbool.h>
-
#include "nvim/api/private/defs.h"
#include "nvim/decoration.h"
#include "nvim/ex_eval.h"
diff --git a/src/nvim/api/tabpage.h b/src/nvim/api/tabpage.h
index a822844af9..2689cf6ae6 100644
--- a/src/nvim/api/tabpage.h
+++ b/src/nvim/api/tabpage.h
@@ -1,8 +1,6 @@
#ifndef NVIM_API_TABPAGE_H
#define NVIM_API_TABPAGE_H
-#include <stdint.h>
-
#include "nvim/api/private/defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 233fd82d3c..c1374ff00e 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -10,6 +10,7 @@
#include "nvim/api/buffer.h"
#include "nvim/api/deprecated.h"
+#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
@@ -59,95 +60,6 @@
# include "api/vim.c.generated.h"
#endif
-void api_vim_free_all_mem(void)
-{
- String name;
- handle_T id;
- map_foreach(&namespace_ids, name, id, {
- (void)id;
- xfree(name.data);
- })
- map_destroy(String, handle_T)(&namespace_ids);
-}
-
-/// 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.
-///
-/// @see |execute()|
-/// @see |nvim_command()|
-///
-/// @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)
- FUNC_API_SINCE(7)
-{
- const int save_msg_silent = msg_silent;
- garray_T *const save_capture_ga = capture_ga;
- garray_T capture_local;
- if (output) {
- ga_init(&capture_local, 1, 80);
- capture_ga = &capture_local;
- }
-
- try_start();
- if (output) {
- msg_silent++;
- }
- do_source_str(src.data, "nvim_exec()");
- if (output) {
- capture_ga = save_capture_ga;
- msg_silent = save_msg_silent;
- }
- try_end(err);
-
- if (ERROR_SET(err)) {
- goto theend;
- }
-
- if (output && capture_local.ga_len > 1) {
- String s = (String){
- .data = capture_local.ga_data,
- .size = (size_t)capture_local.ga_len,
- };
- // redir usually (except :echon) prepends a newline.
- if (s.data[0] == '\n') {
- memmove(s.data, s.data + 1, s.size - 1);
- s.data[s.size - 1] = '\0';
- s.size = s.size - 1;
- }
- return s; // Caller will free the memory.
- }
-theend:
- if (output) {
- ga_clear(&capture_local);
- }
- return (String)STRING_INIT;
-}
-
-/// Executes an ex-command.
-///
-/// On execution error: fails with VimL error, does not update v:errmsg.
-///
-/// @see |nvim_exec()|
-///
-/// @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)
-{
- try_start();
- do_cmdline_cmd(command.data);
- try_end(err);
-}
-
/// Gets a highlight definition by name.
///
/// @param name Highlight group name
@@ -488,51 +400,6 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, Bool
return cstr_as_string(ptr);
}
-/// Evaluates a VimL |expression|.
-/// Dictionaries and Lists are recursively expanded.
-///
-/// On execution error: fails with VimL error, does not update v:errmsg.
-///
-/// @param expr VimL expression string
-/// @param[out] err Error details, if any
-/// @return Evaluation result or expanded object
-Object nvim_eval(String expr, Error *err)
- FUNC_API_SINCE(1)
-{
- static int recursive = 0; // recursion depth
- Object rv = OBJECT_INIT;
-
- TRY_WRAP({
- // Initialize `force_abort` and `suppress_errthrow` at the top level.
- if (!recursive) {
- force_abort = false;
- suppress_errthrow = false;
- current_exception = NULL;
- // `did_emsg` is set by emsg(), which cancels execution.
- did_emsg = false;
- }
- recursive++;
- try_start();
-
- typval_T rettv;
- int ok = eval0((char_u *)expr.data, &rettv, NULL, true);
-
- if (!try_end(err)) {
- if (ok == FAIL) {
- // Should never happen, try_end() should get the error. #8371
- api_set_error(err, kErrorTypeException,
- "Failed to evaluate expression: '%.*s'", 256, expr.data);
- } else {
- rv = vim_to_object(&rettv);
- }
- }
-
- tv_clear(&rettv);
- recursive--;
- });
-
- return rv;
-}
/// Execute Lua code. Parameters (if any) are available as `...` inside the
/// chunk. The chunk can return a value.
@@ -573,164 +440,6 @@ Object nvim_notify(String msg, Integer log_level, Dictionary opts, Error *err)
return nlua_exec(STATIC_CSTR_AS_STRING("return vim.notify(...)"), args, err);
}
-/// Calls a VimL function.
-///
-/// @param fn Function name
-/// @param args Function arguments
-/// @param self `self` dict, or NULL for non-dict functions
-/// @param[out] err Error details, if any
-/// @return Result of the function call
-static Object _call_function(String fn, Array args, dict_T *self, Error *err)
-{
- static int recursive = 0; // recursion depth
- Object rv = OBJECT_INIT;
-
- if (args.size > MAX_FUNC_ARGS) {
- api_set_error(err, kErrorTypeValidation,
- "Function called with too many arguments");
- return rv;
- }
-
- // Convert the arguments in args from Object to typval_T values
- typval_T vim_args[MAX_FUNC_ARGS + 1];
- size_t i = 0; // also used for freeing the variables
- for (; i < args.size; i++) {
- if (!object_to_vim(args.items[i], &vim_args[i], err)) {
- goto free_vim_args;
- }
- }
-
- TRY_WRAP({
- // Initialize `force_abort` and `suppress_errthrow` at the top level.
- if (!recursive) {
- force_abort = false;
- suppress_errthrow = false;
- current_exception = NULL;
- // `did_emsg` is set by emsg(), which cancels execution.
- did_emsg = false;
- }
- recursive++;
- try_start();
- typval_T rettv;
- funcexe_T funcexe = FUNCEXE_INIT;
- funcexe.firstline = curwin->w_cursor.lnum;
- funcexe.lastline = curwin->w_cursor.lnum;
- funcexe.evaluate = true;
- 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,
- vim_args, &funcexe);
- if (!try_end(err)) {
- rv = vim_to_object(&rettv);
- }
- tv_clear(&rettv);
- recursive--;
- });
-
-free_vim_args:
- while (i > 0) {
- tv_clear(&vim_args[--i]);
- }
-
- return rv;
-}
-
-/// Calls a VimL function with the given arguments.
-///
-/// On execution error: fails with VimL error, does not update v:errmsg.
-///
-/// @param fn Function to call
-/// @param args Function arguments packed in an Array
-/// @param[out] err Error details, if any
-/// @return Result of the function call
-Object nvim_call_function(String fn, Array args, Error *err)
- FUNC_API_SINCE(1)
-{
- return _call_function(fn, args, NULL, err);
-}
-
-/// Calls a VimL |Dictionary-function| with the given arguments.
-///
-/// On execution error: fails with VimL error, does not update v:errmsg.
-///
-/// @param dict Dictionary, or String evaluating to a VimL |self| dict
-/// @param fn Name of the function defined on the VimL dict
-/// @param args Function arguments packed in an Array
-/// @param[out] err Error details, if any
-/// @return Result of the function call
-Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err)
- FUNC_API_SINCE(4)
-{
- Object rv = OBJECT_INIT;
-
- typval_T rettv;
- bool mustfree = false;
- switch (dict.type) {
- case kObjectTypeString:
- try_start();
- if (eval0((char_u *)dict.data.string.data, &rettv, NULL, true) == FAIL) {
- api_set_error(err, kErrorTypeException,
- "Failed to evaluate dict expression");
- }
- if (try_end(err)) {
- return rv;
- }
- // Evaluation of the string arg created a new dict or increased the
- // refcount of a dict. Not necessary for a RPC dict.
- mustfree = true;
- break;
- case kObjectTypeDictionary:
- if (!object_to_vim(dict, &rettv, err)) {
- goto end;
- }
- break;
- default:
- api_set_error(err, kErrorTypeValidation,
- "dict argument type must be String or Dictionary");
- return rv;
- }
- dict_T *self_dict = rettv.vval.v_dict;
- if (rettv.v_type != VAR_DICT || !self_dict) {
- api_set_error(err, kErrorTypeValidation, "dict not found");
- goto end;
- }
-
- if (fn.data && fn.size > 0 && dict.type != kObjectTypeDictionary) {
- dictitem_T *const di = tv_dict_find(self_dict, fn.data, (ptrdiff_t)fn.size);
- if (di == NULL) {
- api_set_error(err, kErrorTypeValidation, "Not found: %s", fn.data);
- goto end;
- }
- if (di->di_tv.v_type == VAR_PARTIAL) {
- api_set_error(err, kErrorTypeValidation,
- "partial function not supported");
- goto end;
- }
- if (di->di_tv.v_type != VAR_FUNC) {
- api_set_error(err, kErrorTypeValidation, "Not a function: %s", fn.data);
- goto end;
- }
- fn = (String) {
- .data = (char *)di->di_tv.vval.v_string,
- .size = STRLEN(di->di_tv.vval.v_string),
- };
- }
-
- if (!fn.data || fn.size < 1) {
- api_set_error(err, kErrorTypeValidation, "Invalid (empty) function name");
- goto end;
- }
-
- rv = _call_function(fn, args, self_dict, err);
-end:
- if (mustfree) {
- tv_clear(&rettv);
- }
-
- return rv;
-}
-
/// Calculates the number of display cells occupied by `text`.
/// <Tab> counts as one cell.
///
@@ -1248,10 +957,16 @@ fail:
/// in a virtual terminal having the intended size.
///
/// @param buffer the buffer to use (expected to be empty)
-/// @param opts Optional parameters. Reserved for future use.
+/// @param opts Optional parameters.
+/// - on_input: lua callback for input sent, i e keypresses in terminal
+/// mode. Note: keypresses are sent raw as they would be to the pty
+/// master end. For instance, a carriage return is sent
+/// as a "\r", not as a "\n". |textlock| applies. It is possible
+/// to call |nvim_chan_send| directly in the callback however.
+/// ["input", term, bufnr, data]
/// @param[out] err Error details, if any
/// @return Channel id, or 0 on error
-Integer nvim_open_term(Buffer buffer, Dictionary opts, Error *err)
+Integer nvim_open_term(Buffer buffer, DictionaryOf(LuaRef) opts, Error *err)
FUNC_API_SINCE(7)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -1259,13 +974,27 @@ Integer nvim_open_term(Buffer buffer, Dictionary opts, Error *err)
return 0;
}
- if (opts.size > 0) {
- api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
- return 0;
+ LuaRef cb = LUA_NOREF;
+ for (size_t i = 0; i < opts.size; i++) {
+ String k = opts.items[i].key;
+ Object *v = &opts.items[i].value;
+ if (strequal("on_input", k.data)) {
+ if (v->type != kObjectTypeLuaRef) {
+ api_set_error(err, kErrorTypeValidation,
+ "%s is not a function", "on_input");
+ return 0;
+ }
+ cb = v->data.luaref;
+ v->data.luaref = LUA_NOREF;
+ break;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ }
}
TerminalOptions topts;
Channel *chan = channel_alloc(kChannelStreamInternal);
+ chan->stream.internal.cb = cb;
topts.data = chan;
// NB: overridden in terminal_check_size if a window is already
// displaying the buffer
@@ -1277,13 +1006,23 @@ Integer nvim_open_term(Buffer buffer, Dictionary opts, Error *err)
Terminal *term = terminal_open(buf, topts);
terminal_check_size(term);
chan->term = term;
- channel_incref(chan);
return (Integer)chan->id;
}
static void term_write(char *buf, size_t size, void *data)
{
- // TODO(bfredl): lua callback
+ Channel *chan = data;
+ LuaRef cb = chan->stream.internal.cb;
+ if (cb == LUA_NOREF) {
+ return;
+ }
+ FIXED_TEMP_ARRAY(args, 3);
+ args.items[0] = INTEGER_OBJ((Integer)chan->id);
+ args.items[1] = BUFFER_OBJ(terminal_buf(chan->term));
+ args.items[2] = STRING_OBJ(((String){ .data = buf, .size = size }));
+ textlock++;
+ nlua_call_ref(cb, "input", args, false, NULL);
+ textlock--;
}
static void term_resize(uint16_t width, uint16_t height, void *data)
@@ -1296,6 +1035,8 @@ static void term_close(void *data)
Channel *chan = data;
terminal_destroy(chan->term);
chan->term = NULL;
+ api_free_luaref(chan->stream.internal.cb);
+ chan->stream.internal.cb = LUA_NOREF;
channel_decref(chan);
}
@@ -1383,49 +1124,6 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err)
}
}
-/// Creates a new \*namespace\* or gets an existing one.
-///
-/// Namespaces are used for buffer highlights and virtual text, see
-/// |nvim_buf_add_highlight()| and |nvim_buf_set_extmark()|.
-///
-/// Namespaces can be named or anonymous. If `name` matches an existing
-/// namespace, the associated id is returned. If `name` is an empty string
-/// a new, anonymous namespace is created.
-///
-/// @param name Namespace name or empty string
-/// @return Namespace id
-Integer nvim_create_namespace(String name)
- FUNC_API_SINCE(5)
-{
- handle_T id = map_get(String, handle_T)(&namespace_ids, name);
- if (id > 0) {
- return id;
- }
- id = next_namespace_id++;
- if (name.size > 0) {
- String name_alloc = copy_string(name);
- map_put(String, handle_T)(&namespace_ids, name_alloc, id);
- }
- return (Integer)id;
-}
-
-/// Gets existing, non-anonymous namespaces.
-///
-/// @return dict that maps from names to namespace ids.
-Dictionary nvim_get_namespaces(void)
- FUNC_API_SINCE(5)
-{
- Dictionary retval = ARRAY_DICT_INIT;
- String name;
- handle_T id;
-
- map_foreach(&namespace_ids, name, id, {
- PUT(retval, name.data, INTEGER_OBJ(id));
- })
-
- return retval;
-}
-
/// Pastes at cursor, in any mode.
///
/// Invokes the `vim.paste` handler, which handles each mode appropriately.
@@ -2013,439 +1711,6 @@ theend:
return rv;
}
-typedef struct {
- ExprASTNode **node_p;
- Object *ret_node_p;
-} ExprASTConvStackItem;
-
-/// @cond DOXYGEN_NOT_A_FUNCTION
-typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack;
-/// @endcond
-
-/// Parse a VimL expression.
-///
-/// @param[in] expr Expression to parse. Always treated as a single line.
-/// @param[in] flags Flags:
-/// - "m" if multiple expressions in a row are allowed (only
-/// the first one will be parsed),
-/// - "E" if EOC tokens are not allowed (determines whether
-/// they will stop parsing process or be recognized as an
-/// operator/space, though also yielding an error).
-/// - "l" when needing to start parsing with lvalues for
-/// ":let" or ":for".
-/// Common flag sets:
-/// - "m" to parse like for ":echo".
-/// - "E" to parse like for "<C-r>=".
-/// - empty string for ":call".
-/// - "lm" to parse for ":let".
-/// @param[in] highlight If true, return value will also include "highlight"
-/// key containing array of 4-tuples (arrays) (Integer,
-/// Integer, Integer, String), where first three numbers
-/// define the highlighted region and represent line,
-/// starting column and ending column (latter exclusive:
-/// one should highlight region [start_col, end_col)).
-///
-/// @return
-/// - AST: top-level dictionary with these keys:
-/// - "error": Dictionary with error, present only if parser saw some
-/// error. Contains the following keys:
-/// - "message": String, error message in printf format, translated.
-/// Must contain exactly one "%.*s".
-/// - "arg": String, error message argument.
-/// - "len": Amount of bytes successfully parsed. With flags equal to ""
-/// that should be equal to the length of expr string.
-/// (“Successfully parsed” here means “participated in AST
-/// creation”, not “till the first error”.)
-/// - "ast": AST, either nil or a dictionary with these keys:
-/// - "type": node type, one of the value names from ExprASTNodeType
-/// stringified without "kExprNode" prefix.
-/// - "start": a pair [line, column] describing where node is "started"
-/// where "line" is always 0 (will not be 0 if you will be
-/// using nvim_parse_viml() on e.g. ":let", but that is not
-/// present yet). Both elements are Integers.
-/// - "len": “length” of the node. This and "start" are there for
-/// debugging purposes primary (debugging parser and providing
-/// debug information).
-/// - "children": a list of nodes described in top/"ast". There always
-/// is zero, one or two children, key will not be present
-/// if node has no children. Maximum number of children
-/// may be found in node_maxchildren array.
-/// - Local values (present only for certain nodes):
-/// - "scope": a single Integer, specifies scope for "Option" and
-/// "PlainIdentifier" nodes. For "Option" it is one of
-/// ExprOptScope values, for "PlainIdentifier" it is one of
-/// ExprVarScope values.
-/// - "ident": identifier (without scope, if any), present for "Option",
-/// "PlainIdentifier", "PlainKey" and "Environment" nodes.
-/// - "name": Integer, register name (one character) or -1. Only present
-/// for "Register" nodes.
-/// - "cmp_type": String, comparison type, one of the value names from
-/// ExprComparisonType, stringified without "kExprCmp"
-/// prefix. Only present for "Comparison" nodes.
-/// - "ccs_strategy": String, case comparison strategy, one of the
-/// value names from ExprCaseCompareStrategy,
-/// stringified without "kCCStrategy" prefix. Only
-/// present for "Comparison" nodes.
-/// - "augmentation": String, augmentation type for "Assignment" nodes.
-/// Is either an empty string, "Add", "Subtract" or
-/// "Concat" for "=", "+=", "-=" or ".=" respectively.
-/// - "invert": Boolean, true if result of comparison needs to be
-/// inverted. Only present for "Comparison" nodes.
-/// - "ivalue": Integer, integer value for "Integer" nodes.
-/// - "fvalue": Float, floating-point value for "Float" nodes.
-/// - "svalue": String, value for "SingleQuotedString" and
-/// "DoubleQuotedString" nodes.
-/// @param[out] err Error details, if any
-Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, Error *err)
- FUNC_API_SINCE(4) FUNC_API_FAST
-{
- int pflags = 0;
- for (size_t i = 0 ; i < flags.size ; i++) {
- switch (flags.data[i]) {
- case 'm':
- pflags |= kExprFlagsMulti; break;
- case 'E':
- pflags |= kExprFlagsDisallowEOC; break;
- case 'l':
- pflags |= kExprFlagsParseLet; break;
- case NUL:
- api_set_error(err, kErrorTypeValidation, "Invalid flag: '\\0' (%u)",
- (unsigned)flags.data[i]);
- return (Dictionary)ARRAY_DICT_INIT;
- default:
- api_set_error(err, kErrorTypeValidation, "Invalid flag: '%c' (%u)",
- flags.data[i], (unsigned)flags.data[i]);
- return (Dictionary)ARRAY_DICT_INIT;
- }
- }
- ParserLine parser_lines[] = {
- {
- .data = expr.data,
- .size = expr.size,
- .allocated = false,
- },
- { NULL, 0, false },
- };
- ParserLine *plines_p = parser_lines;
- ParserHighlight colors;
- kvi_init(colors);
- ParserHighlight *const colors_p = (highlight ? &colors : NULL);
- ParserState pstate;
- viml_parser_init(&pstate, parser_simple_get_line, &plines_p, colors_p);
- ExprAST east = viml_pexpr_parse(&pstate, pflags);
-
- const size_t ret_size = (
- 2 // "ast", "len"
- + (size_t)(east.err.msg != NULL) // "error"
- + (size_t)highlight // "highlight"
- + 0);
- Dictionary ret = {
- .items = xmalloc(ret_size * sizeof(ret.items[0])),
- .size = 0,
- .capacity = ret_size,
- };
- ret.items[ret.size++] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("ast"),
- .value = NIL,
- };
- ret.items[ret.size++] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("len"),
- .value = INTEGER_OBJ((Integer)(pstate.pos.line == 1
- ? parser_lines[0].size
- : pstate.pos.col)),
- };
- if (east.err.msg != NULL) {
- Dictionary err_dict = {
- .items = xmalloc(2 * sizeof(err_dict.items[0])),
- .size = 2,
- .capacity = 2,
- };
- err_dict.items[0] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("message"),
- .value = STRING_OBJ(cstr_to_string(east.err.msg)),
- };
- if (east.err.arg == NULL) {
- err_dict.items[1] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("arg"),
- .value = STRING_OBJ(STRING_INIT),
- };
- } else {
- err_dict.items[1] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("arg"),
- .value = STRING_OBJ(((String) {
- .data = xmemdupz(east.err.arg, (size_t)east.err.arg_len),
- .size = (size_t)east.err.arg_len,
- })),
- };
- }
- ret.items[ret.size++] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("error"),
- .value = DICTIONARY_OBJ(err_dict),
- };
- }
- if (highlight) {
- Array hl = (Array) {
- .items = xmalloc(kv_size(colors) * sizeof(hl.items[0])),
- .capacity = kv_size(colors),
- .size = kv_size(colors),
- };
- for (size_t i = 0 ; i < kv_size(colors) ; i++) {
- const ParserHighlightChunk chunk = kv_A(colors, i);
- Array chunk_arr = (Array) {
- .items = xmalloc(4 * sizeof(chunk_arr.items[0])),
- .capacity = 4,
- .size = 4,
- };
- chunk_arr.items[0] = INTEGER_OBJ((Integer)chunk.start.line);
- chunk_arr.items[1] = INTEGER_OBJ((Integer)chunk.start.col);
- chunk_arr.items[2] = INTEGER_OBJ((Integer)chunk.end_col);
- chunk_arr.items[3] = STRING_OBJ(cstr_to_string(chunk.group));
- hl.items[i] = ARRAY_OBJ(chunk_arr);
- }
- ret.items[ret.size++] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("highlight"),
- .value = ARRAY_OBJ(hl),
- };
- }
- kvi_destroy(colors);
-
- // Walk over the AST, freeing nodes in process.
- ExprASTConvStack ast_conv_stack;
- kvi_init(ast_conv_stack);
- kvi_push(ast_conv_stack, ((ExprASTConvStackItem) {
- .node_p = &east.root,
- .ret_node_p = &ret.items[0].value,
- }));
- while (kv_size(ast_conv_stack)) {
- ExprASTConvStackItem cur_item = kv_last(ast_conv_stack);
- ExprASTNode *const node = *cur_item.node_p;
- if (node == NULL) {
- assert(kv_size(ast_conv_stack) == 1);
- kv_drop(ast_conv_stack, 1);
- } else {
- if (cur_item.ret_node_p->type == kObjectTypeNil) {
- const size_t ret_node_items_size = (size_t)(
- 3 // "type", "start" and "len"
- + (node->children != NULL) // "children"
- + (node->type == kExprNodeOption
- || node->type == kExprNodePlainIdentifier) // "scope"
- + (node->type == kExprNodeOption
- || node->type == kExprNodePlainIdentifier
- || node->type == kExprNodePlainKey
- || node->type == kExprNodeEnvironment) // "ident"
- + (node->type == kExprNodeRegister) // "name"
- + (3 // "cmp_type", "ccs_strategy", "invert"
- * (node->type == kExprNodeComparison))
- + (node->type == kExprNodeInteger) // "ivalue"
- + (node->type == kExprNodeFloat) // "fvalue"
- + (node->type == kExprNodeDoubleQuotedString
- || node->type == kExprNodeSingleQuotedString) // "svalue"
- + (node->type == kExprNodeAssignment) // "augmentation"
- + 0);
- Dictionary ret_node = {
- .items = xmalloc(ret_node_items_size * sizeof(ret_node.items[0])),
- .capacity = ret_node_items_size,
- .size = 0,
- };
- *cur_item.ret_node_p = DICTIONARY_OBJ(ret_node);
- }
- Dictionary *ret_node = &cur_item.ret_node_p->data.dictionary;
- if (node->children != NULL) {
- const size_t num_children = 1 + (node->children->next != NULL);
- Array children_array = {
- .items = xmalloc(num_children * sizeof(children_array.items[0])),
- .capacity = num_children,
- .size = num_children,
- };
- for (size_t i = 0; i < num_children; i++) {
- children_array.items[i] = NIL;
- }
- ret_node->items[ret_node->size++] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("children"),
- .value = ARRAY_OBJ(children_array),
- };
- kvi_push(ast_conv_stack, ((ExprASTConvStackItem) {
- .node_p = &node->children,
- .ret_node_p = &children_array.items[0],
- }));
- } else if (node->next != NULL) {
- kvi_push(ast_conv_stack, ((ExprASTConvStackItem) {
- .node_p = &node->next,
- .ret_node_p = cur_item.ret_node_p + 1,
- }));
- } else {
- kv_drop(ast_conv_stack, 1);
- ret_node->items[ret_node->size++] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("type"),
- .value = STRING_OBJ(cstr_to_string(east_node_type_tab[node->type])),
- };
- Array start_array = {
- .items = xmalloc(2 * sizeof(start_array.items[0])),
- .capacity = 2,
- .size = 2,
- };
- start_array.items[0] = INTEGER_OBJ((Integer)node->start.line);
- start_array.items[1] = INTEGER_OBJ((Integer)node->start.col);
- ret_node->items[ret_node->size++] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("start"),
- .value = ARRAY_OBJ(start_array),
- };
- ret_node->items[ret_node->size++] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("len"),
- .value = INTEGER_OBJ((Integer)node->len),
- };
- switch (node->type) {
- case kExprNodeDoubleQuotedString:
- case kExprNodeSingleQuotedString:
- ret_node->items[ret_node->size++] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("svalue"),
- .value = STRING_OBJ(((String) {
- .data = node->data.str.value,
- .size = node->data.str.size,
- })),
- };
- break;
- case kExprNodeOption:
- ret_node->items[ret_node->size++] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("scope"),
- .value = INTEGER_OBJ(node->data.opt.scope),
- };
- ret_node->items[ret_node->size++] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("ident"),
- .value = STRING_OBJ(((String) {
- .data = xmemdupz(node->data.opt.ident,
- node->data.opt.ident_len),
- .size = node->data.opt.ident_len,
- })),
- };
- break;
- case kExprNodePlainIdentifier:
- ret_node->items[ret_node->size++] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("scope"),
- .value = INTEGER_OBJ(node->data.var.scope),
- };
- ret_node->items[ret_node->size++] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("ident"),
- .value = STRING_OBJ(((String) {
- .data = xmemdupz(node->data.var.ident,
- node->data.var.ident_len),
- .size = node->data.var.ident_len,
- })),
- };
- break;
- case kExprNodePlainKey:
- ret_node->items[ret_node->size++] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("ident"),
- .value = STRING_OBJ(((String) {
- .data = xmemdupz(node->data.var.ident,
- node->data.var.ident_len),
- .size = node->data.var.ident_len,
- })),
- };
- break;
- case kExprNodeEnvironment:
- ret_node->items[ret_node->size++] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("ident"),
- .value = STRING_OBJ(((String) {
- .data = xmemdupz(node->data.env.ident,
- node->data.env.ident_len),
- .size = node->data.env.ident_len,
- })),
- };
- break;
- case kExprNodeRegister:
- ret_node->items[ret_node->size++] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("name"),
- .value = INTEGER_OBJ(node->data.reg.name),
- };
- break;
- case kExprNodeComparison:
- ret_node->items[ret_node->size++] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("cmp_type"),
- .value = STRING_OBJ(cstr_to_string(eltkn_cmp_type_tab[node->data.cmp.type])),
- };
- ret_node->items[ret_node->size++] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("ccs_strategy"),
- .value = STRING_OBJ(cstr_to_string(ccs_tab[node->data.cmp.ccs])),
- };
- ret_node->items[ret_node->size++] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("invert"),
- .value = BOOLEAN_OBJ(node->data.cmp.inv),
- };
- break;
- case kExprNodeFloat:
- ret_node->items[ret_node->size++] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("fvalue"),
- .value = FLOAT_OBJ(node->data.flt.value),
- };
- break;
- case kExprNodeInteger:
- ret_node->items[ret_node->size++] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("ivalue"),
- .value = INTEGER_OBJ((Integer)(
- node->data.num.value > API_INTEGER_MAX
- ? API_INTEGER_MAX
- : (Integer)node->data.num.value)),
- };
- break;
- case kExprNodeAssignment: {
- const ExprAssignmentType asgn_type = node->data.ass.type;
- ret_node->items[ret_node->size++] = (KeyValuePair) {
- .key = STATIC_CSTR_TO_STRING("augmentation"),
- .value = STRING_OBJ(asgn_type == kExprAsgnPlain
- ? (String)STRING_INIT
- : cstr_to_string(expr_asgn_type_tab[asgn_type])),
- };
- break;
- }
- case kExprNodeMissing:
- case kExprNodeOpMissing:
- case kExprNodeTernary:
- case kExprNodeTernaryValue:
- case kExprNodeSubscript:
- case kExprNodeListLiteral:
- case kExprNodeUnaryPlus:
- case kExprNodeBinaryPlus:
- case kExprNodeNested:
- case kExprNodeCall:
- case kExprNodeComplexIdentifier:
- case kExprNodeUnknownFigure:
- case kExprNodeLambda:
- case kExprNodeDictLiteral:
- case kExprNodeCurlyBracesIdentifier:
- case kExprNodeComma:
- case kExprNodeColon:
- case kExprNodeArrow:
- case kExprNodeConcat:
- case kExprNodeConcatOrSubscript:
- case kExprNodeOr:
- case kExprNodeAnd:
- case kExprNodeUnaryMinus:
- case kExprNodeBinaryMinus:
- case kExprNodeNot:
- case kExprNodeMultiplication:
- case kExprNodeDivision:
- case kExprNodeMod:
- break;
- }
- assert(cur_item.ret_node_p->data.dictionary.size
- == cur_item.ret_node_p->data.dictionary.capacity);
- xfree(*cur_item.node_p);
- *cur_item.node_p = NULL;
- }
- }
- }
- kvi_destroy(ast_conv_stack);
-
- assert(ret.size == ret.capacity);
- // Should be a no-op actually, leaving it in case non-nodes will need to be
- // freed later.
- viml_pexpr_free_ast(east);
- viml_parser_destroy(&pstate);
- return ret;
-}
-
-
/// Writes a message to vim output or error buffer. The string is split
/// and flushed after each newline. Incomplete lines are kept for writing
/// later.
@@ -2460,11 +1725,10 @@ static void write_msg(String message, bool to_err)
#define PUSH_CHAR(i, pos, line_buf, msg) \
if (message.data[i] == NL || pos == LINE_BUFFER_SIZE - 1) { \
line_buf[pos] = NUL; \
- msg((char_u *)line_buf); \
+ msg(line_buf); \
pos = 0; \
continue; \
} \
- \
line_buf[pos++] = message.data[i];
++no_wait_return;
@@ -2714,96 +1978,6 @@ void nvim__screenshot(String path)
ui_call_screenshot(path);
}
-/// Set or change decoration provider for a namespace
-///
-/// This is a very general purpose interface for having lua callbacks
-/// being triggered during the redraw code.
-///
-/// The expected usage is to set extmarks for the currently
-/// redrawn buffer. |nvim_buf_set_extmark| can be called to add marks
-/// on a per-window or per-lines basis. Use the `ephemeral` key to only
-/// use the mark for the current screen redraw (the callback will be called
-/// again for the next redraw ).
-///
-/// Note: this function should not be called often. Rather, the callbacks
-/// themselves can be used to throttle unneeded callbacks. the `on_start`
-/// callback can return `false` to disable the provider until the next redraw.
-/// Similarly, return `false` in `on_win` will skip the `on_lines` calls
-/// for that window (but any extmarks set in `on_win` will still be used).
-/// A plugin managing multiple sources of decoration should ideally only set
-/// one provider, and merge the sources internally. You can use multiple `ns_id`
-/// for the extmarks set/modified inside the callback anyway.
-///
-/// Note: doing anything other than setting extmarks is considered experimental.
-/// Doing things like changing options are not expliticly forbidden, but is
-/// likely to have unexpected consequences (such as 100% CPU consumption).
-/// doing `vim.rpcnotify` should be OK, but `vim.rpcrequest` is quite dubious
-/// for the moment.
-///
-/// @param ns_id Namespace id from |nvim_create_namespace()|
-/// @param opts Callbacks invoked during redraw:
-/// - on_start: called first on each screen redraw
-/// ["start", tick]
-/// - on_buf: called for each buffer being redrawn (before window
-/// callbacks)
-/// ["buf", bufnr, tick]
-/// - on_win: called when starting to redraw a specific window.
-/// ["win", winid, bufnr, topline, botline_guess]
-/// - on_line: called for each buffer line being redrawn. (The
-/// interation with fold lines is subject to change)
-/// ["win", winid, bufnr, row]
-/// - on_end: called at the end of a redraw cycle
-/// ["end", tick]
-void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Error *err)
- FUNC_API_SINCE(7) FUNC_API_LUA_ONLY
-{
- DecorProvider *p = get_decor_provider((NS)ns_id, true);
- assert(p != NULL);
- decor_provider_clear(p);
-
- // regardless of what happens, it seems good idea to redraw
- redraw_all_later(NOT_VALID); // TODO(bfredl): too soon?
-
- struct {
- const char *name;
- LuaRef *dest;
- } cbs[] = {
- { "on_start", &p->redraw_start },
- { "on_buf", &p->redraw_buf },
- { "on_win", &p->redraw_win },
- { "on_line", &p->redraw_line },
- { "on_end", &p->redraw_end },
- { "_on_hl_def", &p->hl_def },
- { NULL, NULL },
- };
-
- for (size_t i = 0; i < opts.size; i++) {
- String k = opts.items[i].key;
- Object *v = &opts.items[i].value;
- size_t j;
- for (j = 0; cbs[j].name && cbs[j].dest; j++) {
- if (strequal(cbs[j].name, k.data)) {
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation,
- "%s is not a function", cbs[j].name);
- goto error;
- }
- *(cbs[j].dest) = v->data.luaref;
- v->data.luaref = LUA_NOREF;
- break;
- }
- }
- if (!cbs[j].name) {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
- goto error;
- }
- }
-
- p->active = true;
- return;
-error:
- decor_provider_clear(p);
-}
/// Deletes a uppercase/file named mark. See |mark-motions|.
///
@@ -2840,11 +2014,12 @@ Boolean nvim_del_mark(String name, Error *err)
///
/// @note fails with error if a lowercase or buffer local named mark is used.
/// @param name Mark name
+/// @param opts Optional parameters. Reserved for future use.
/// @return 4-tuple (row, col, buffer, buffername), (0, 0, 0, '') if the mark is
/// not set.
/// @see |nvim_buf_set_mark()|
/// @see |nvim_del_mark()|
-Array nvim_get_mark(String name, Error *err)
+Array nvim_get_mark(String name, Dictionary opts, Error *err)
FUNC_API_SINCE(8)
{
Array rv = ARRAY_DICT_INIT;
@@ -3008,16 +2183,15 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
int p_crb_save = ewp->w_p_crb;
ewp->w_p_crb = false;
- int width = build_stl_str_hl(
- ewp,
- (char_u *)buf,
- sizeof(buf),
- (char_u *)str.data,
- false,
- (char_u)fillchar,
- maxwidth,
- hltab_ptr,
- NULL);
+ int width = build_stl_str_hl(ewp,
+ (char_u *)buf,
+ sizeof(buf),
+ (char_u *)str.data,
+ false,
+ (char_u)fillchar,
+ maxwidth,
+ hltab_ptr,
+ NULL);
PUT(result, "width", INTEGER_OBJ(width));
diff --git a/src/nvim/api/vim.h b/src/nvim/api/vim.h
index 4fd353ce5c..de56c67665 100644
--- a/src/nvim/api/vim.h
+++ b/src/nvim/api/vim.h
@@ -1,13 +1,7 @@
#ifndef NVIM_API_VIM_H
#define NVIM_API_VIM_H
-#include <stdint.h>
-
#include "nvim/api/private/defs.h"
-#include "nvim/map.h"
-
-EXTERN Map(String, handle_T) namespace_ids INIT(= MAP_INIT);
-EXTERN handle_T next_namespace_id INIT(= 1);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/vim.h.generated.h"
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
new file mode 100644
index 0000000000..c516cedaf4
--- /dev/null
+++ b/src/nvim/api/vimscript.c
@@ -0,0 +1,733 @@
+// 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 <limits.h>
+#include <stdlib.h>
+
+#include "nvim/api/private/converter.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/api/vimscript.h"
+#include "nvim/ascii.h"
+#include "nvim/eval.h"
+#include "nvim/eval/typval.h"
+#include "nvim/eval/userfunc.h"
+#include "nvim/ex_cmds2.h"
+#include "nvim/viml/parser/expressions.h"
+#include "nvim/viml/parser/parser.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/vimscript.c.generated.h"
+#endif
+
+/// 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.
+///
+/// @see |execute()|
+/// @see |nvim_command()|
+///
+/// @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)
+ FUNC_API_SINCE(7)
+{
+ const int save_msg_silent = msg_silent;
+ garray_T *const save_capture_ga = capture_ga;
+ garray_T capture_local;
+ if (output) {
+ ga_init(&capture_local, 1, 80);
+ capture_ga = &capture_local;
+ }
+
+ try_start();
+ if (output) {
+ msg_silent++;
+ }
+ do_source_str(src.data, "nvim_exec()");
+ if (output) {
+ capture_ga = save_capture_ga;
+ msg_silent = save_msg_silent;
+ }
+ try_end(err);
+
+ if (ERROR_SET(err)) {
+ goto theend;
+ }
+
+ if (output && capture_local.ga_len > 1) {
+ String s = (String){
+ .data = capture_local.ga_data,
+ .size = (size_t)capture_local.ga_len,
+ };
+ // redir usually (except :echon) prepends a newline.
+ if (s.data[0] == '\n') {
+ memmove(s.data, s.data + 1, s.size - 1);
+ s.data[s.size - 1] = '\0';
+ s.size = s.size - 1;
+ }
+ return s; // Caller will free the memory.
+ }
+theend:
+ if (output) {
+ ga_clear(&capture_local);
+ }
+ return (String)STRING_INIT;
+}
+
+/// Executes an ex-command.
+///
+/// On execution error: fails with VimL error, does not update v:errmsg.
+///
+/// @see |nvim_exec()|
+///
+/// @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)
+{
+ try_start();
+ do_cmdline_cmd(command.data);
+ try_end(err);
+}
+
+/// Evaluates a VimL |expression|.
+/// Dictionaries and Lists are recursively expanded.
+///
+/// On execution error: fails with VimL error, does not update v:errmsg.
+///
+/// @param expr VimL expression string
+/// @param[out] err Error details, if any
+/// @return Evaluation result or expanded object
+Object nvim_eval(String expr, Error *err)
+ FUNC_API_SINCE(1)
+{
+ static int recursive = 0; // recursion depth
+ Object rv = OBJECT_INIT;
+
+ TRY_WRAP({
+ // Initialize `force_abort` and `suppress_errthrow` at the top level.
+ if (!recursive) {
+ force_abort = false;
+ suppress_errthrow = false;
+ current_exception = NULL;
+ // `did_emsg` is set by emsg(), which cancels execution.
+ did_emsg = false;
+ }
+ recursive++;
+ try_start();
+
+ typval_T rettv;
+ int ok = eval0((char_u *)expr.data, &rettv, NULL, true);
+
+ if (!try_end(err)) {
+ if (ok == FAIL) {
+ // Should never happen, try_end() should get the error. #8371
+ api_set_error(err, kErrorTypeException,
+ "Failed to evaluate expression: '%.*s'", 256, expr.data);
+ } else {
+ rv = vim_to_object(&rettv);
+ }
+ }
+
+ tv_clear(&rettv);
+ recursive--;
+ });
+
+ return rv;
+}
+
+/// Calls a VimL function.
+///
+/// @param fn Function name
+/// @param args Function arguments
+/// @param self `self` dict, or NULL for non-dict functions
+/// @param[out] err Error details, if any
+/// @return Result of the function call
+static Object _call_function(String fn, Array args, dict_T *self, Error *err)
+{
+ static int recursive = 0; // recursion depth
+ Object rv = OBJECT_INIT;
+
+ if (args.size > MAX_FUNC_ARGS) {
+ api_set_error(err, kErrorTypeValidation,
+ "Function called with too many arguments");
+ return rv;
+ }
+
+ // Convert the arguments in args from Object to typval_T values
+ typval_T vim_args[MAX_FUNC_ARGS + 1];
+ size_t i = 0; // also used for freeing the variables
+ for (; i < args.size; i++) {
+ if (!object_to_vim(args.items[i], &vim_args[i], err)) {
+ goto free_vim_args;
+ }
+ }
+
+ TRY_WRAP({
+ // Initialize `force_abort` and `suppress_errthrow` at the top level.
+ if (!recursive) {
+ force_abort = false;
+ suppress_errthrow = false;
+ current_exception = NULL;
+ // `did_emsg` is set by emsg(), which cancels execution.
+ did_emsg = false;
+ }
+ recursive++;
+ try_start();
+ typval_T rettv;
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.firstline = curwin->w_cursor.lnum;
+ funcexe.lastline = curwin->w_cursor.lnum;
+ funcexe.evaluate = true;
+ 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,
+ vim_args, &funcexe);
+ if (!try_end(err)) {
+ rv = vim_to_object(&rettv);
+ }
+ tv_clear(&rettv);
+ recursive--;
+ });
+
+free_vim_args:
+ while (i > 0) {
+ tv_clear(&vim_args[--i]);
+ }
+
+ return rv;
+}
+
+/// Calls a VimL function with the given arguments.
+///
+/// On execution error: fails with VimL error, does not update v:errmsg.
+///
+/// @param fn Function to call
+/// @param args Function arguments packed in an Array
+/// @param[out] err Error details, if any
+/// @return Result of the function call
+Object nvim_call_function(String fn, Array args, Error *err)
+ FUNC_API_SINCE(1)
+{
+ return _call_function(fn, args, NULL, err);
+}
+
+/// Calls a VimL |Dictionary-function| with the given arguments.
+///
+/// On execution error: fails with VimL error, does not update v:errmsg.
+///
+/// @param dict Dictionary, or String evaluating to a VimL |self| dict
+/// @param fn Name of the function defined on the VimL dict
+/// @param args Function arguments packed in an Array
+/// @param[out] err Error details, if any
+/// @return Result of the function call
+Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err)
+ FUNC_API_SINCE(4)
+{
+ Object rv = OBJECT_INIT;
+
+ typval_T rettv;
+ bool mustfree = false;
+ switch (dict.type) {
+ case kObjectTypeString:
+ try_start();
+ if (eval0((char_u *)dict.data.string.data, &rettv, NULL, true) == FAIL) {
+ api_set_error(err, kErrorTypeException,
+ "Failed to evaluate dict expression");
+ }
+ if (try_end(err)) {
+ return rv;
+ }
+ // Evaluation of the string arg created a new dict or increased the
+ // refcount of a dict. Not necessary for a RPC dict.
+ mustfree = true;
+ break;
+ case kObjectTypeDictionary:
+ if (!object_to_vim(dict, &rettv, err)) {
+ goto end;
+ }
+ break;
+ default:
+ api_set_error(err, kErrorTypeValidation,
+ "dict argument type must be String or Dictionary");
+ return rv;
+ }
+ dict_T *self_dict = rettv.vval.v_dict;
+ if (rettv.v_type != VAR_DICT || !self_dict) {
+ api_set_error(err, kErrorTypeValidation, "dict not found");
+ goto end;
+ }
+
+ if (fn.data && fn.size > 0 && dict.type != kObjectTypeDictionary) {
+ dictitem_T *const di = tv_dict_find(self_dict, fn.data, (ptrdiff_t)fn.size);
+ if (di == NULL) {
+ api_set_error(err, kErrorTypeValidation, "Not found: %s", fn.data);
+ goto end;
+ }
+ if (di->di_tv.v_type == VAR_PARTIAL) {
+ api_set_error(err, kErrorTypeValidation,
+ "partial function not supported");
+ goto end;
+ }
+ if (di->di_tv.v_type != VAR_FUNC) {
+ api_set_error(err, kErrorTypeValidation, "Not a function: %s", fn.data);
+ goto end;
+ }
+ fn = (String) {
+ .data = (char *)di->di_tv.vval.v_string,
+ .size = STRLEN(di->di_tv.vval.v_string),
+ };
+ }
+
+ if (!fn.data || fn.size < 1) {
+ api_set_error(err, kErrorTypeValidation, "Invalid (empty) function name");
+ goto end;
+ }
+
+ rv = _call_function(fn, args, self_dict, err);
+end:
+ if (mustfree) {
+ tv_clear(&rettv);
+ }
+
+ return rv;
+}
+
+typedef struct {
+ ExprASTNode **node_p;
+ Object *ret_node_p;
+} ExprASTConvStackItem;
+
+/// @cond DOXYGEN_NOT_A_FUNCTION
+typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack;
+/// @endcond
+
+/// Parse a VimL expression.
+///
+/// @param[in] expr Expression to parse. Always treated as a single line.
+/// @param[in] flags Flags:
+/// - "m" if multiple expressions in a row are allowed (only
+/// the first one will be parsed),
+/// - "E" if EOC tokens are not allowed (determines whether
+/// they will stop parsing process or be recognized as an
+/// operator/space, though also yielding an error).
+/// - "l" when needing to start parsing with lvalues for
+/// ":let" or ":for".
+/// Common flag sets:
+/// - "m" to parse like for ":echo".
+/// - "E" to parse like for "<C-r>=".
+/// - empty string for ":call".
+/// - "lm" to parse for ":let".
+/// @param[in] highlight If true, return value will also include "highlight"
+/// key containing array of 4-tuples (arrays) (Integer,
+/// Integer, Integer, String), where first three numbers
+/// define the highlighted region and represent line,
+/// starting column and ending column (latter exclusive:
+/// one should highlight region [start_col, end_col)).
+///
+/// @return
+/// - AST: top-level dictionary with these keys:
+/// - "error": Dictionary with error, present only if parser saw some
+/// error. Contains the following keys:
+/// - "message": String, error message in printf format, translated.
+/// Must contain exactly one "%.*s".
+/// - "arg": String, error message argument.
+/// - "len": Amount of bytes successfully parsed. With flags equal to ""
+/// that should be equal to the length of expr string.
+/// (“Successfully parsed” here means “participated in AST
+/// creation”, not “till the first error”.)
+/// - "ast": AST, either nil or a dictionary with these keys:
+/// - "type": node type, one of the value names from ExprASTNodeType
+/// stringified without "kExprNode" prefix.
+/// - "start": a pair [line, column] describing where node is "started"
+/// where "line" is always 0 (will not be 0 if you will be
+/// using nvim_parse_viml() on e.g. ":let", but that is not
+/// present yet). Both elements are Integers.
+/// - "len": “length” of the node. This and "start" are there for
+/// debugging purposes primary (debugging parser and providing
+/// debug information).
+/// - "children": a list of nodes described in top/"ast". There always
+/// is zero, one or two children, key will not be present
+/// if node has no children. Maximum number of children
+/// may be found in node_maxchildren array.
+/// - Local values (present only for certain nodes):
+/// - "scope": a single Integer, specifies scope for "Option" and
+/// "PlainIdentifier" nodes. For "Option" it is one of
+/// ExprOptScope values, for "PlainIdentifier" it is one of
+/// ExprVarScope values.
+/// - "ident": identifier (without scope, if any), present for "Option",
+/// "PlainIdentifier", "PlainKey" and "Environment" nodes.
+/// - "name": Integer, register name (one character) or -1. Only present
+/// for "Register" nodes.
+/// - "cmp_type": String, comparison type, one of the value names from
+/// ExprComparisonType, stringified without "kExprCmp"
+/// prefix. Only present for "Comparison" nodes.
+/// - "ccs_strategy": String, case comparison strategy, one of the
+/// value names from ExprCaseCompareStrategy,
+/// stringified without "kCCStrategy" prefix. Only
+/// present for "Comparison" nodes.
+/// - "augmentation": String, augmentation type for "Assignment" nodes.
+/// Is either an empty string, "Add", "Subtract" or
+/// "Concat" for "=", "+=", "-=" or ".=" respectively.
+/// - "invert": Boolean, true if result of comparison needs to be
+/// inverted. Only present for "Comparison" nodes.
+/// - "ivalue": Integer, integer value for "Integer" nodes.
+/// - "fvalue": Float, floating-point value for "Float" nodes.
+/// - "svalue": String, value for "SingleQuotedString" and
+/// "DoubleQuotedString" nodes.
+/// @param[out] err Error details, if any
+Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, Error *err)
+ FUNC_API_SINCE(4) FUNC_API_FAST
+{
+ int pflags = 0;
+ for (size_t i = 0 ; i < flags.size ; i++) {
+ switch (flags.data[i]) {
+ case 'm':
+ pflags |= kExprFlagsMulti; break;
+ case 'E':
+ pflags |= kExprFlagsDisallowEOC; break;
+ case 'l':
+ pflags |= kExprFlagsParseLet; break;
+ case NUL:
+ api_set_error(err, kErrorTypeValidation, "Invalid flag: '\\0' (%u)",
+ (unsigned)flags.data[i]);
+ return (Dictionary)ARRAY_DICT_INIT;
+ default:
+ api_set_error(err, kErrorTypeValidation, "Invalid flag: '%c' (%u)",
+ flags.data[i], (unsigned)flags.data[i]);
+ return (Dictionary)ARRAY_DICT_INIT;
+ }
+ }
+ ParserLine parser_lines[] = {
+ {
+ .data = expr.data,
+ .size = expr.size,
+ .allocated = false,
+ },
+ { NULL, 0, false },
+ };
+ ParserLine *plines_p = parser_lines;
+ ParserHighlight colors;
+ kvi_init(colors);
+ ParserHighlight *const colors_p = (highlight ? &colors : NULL);
+ ParserState pstate;
+ viml_parser_init(&pstate, parser_simple_get_line, &plines_p, colors_p);
+ ExprAST east = viml_pexpr_parse(&pstate, pflags);
+
+ const size_t ret_size = (2 // "ast", "len"
+ + (size_t)(east.err.msg != NULL) // "error"
+ + (size_t)highlight // "highlight"
+ + 0);
+ Dictionary ret = {
+ .items = xmalloc(ret_size * sizeof(ret.items[0])),
+ .size = 0,
+ .capacity = ret_size,
+ };
+ ret.items[ret.size++] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("ast"),
+ .value = NIL,
+ };
+ ret.items[ret.size++] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("len"),
+ .value = INTEGER_OBJ((Integer)(pstate.pos.line == 1
+ ? parser_lines[0].size
+ : pstate.pos.col)),
+ };
+ if (east.err.msg != NULL) {
+ Dictionary err_dict = {
+ .items = xmalloc(2 * sizeof(err_dict.items[0])),
+ .size = 2,
+ .capacity = 2,
+ };
+ err_dict.items[0] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("message"),
+ .value = STRING_OBJ(cstr_to_string(east.err.msg)),
+ };
+ if (east.err.arg == NULL) {
+ err_dict.items[1] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("arg"),
+ .value = STRING_OBJ(STRING_INIT),
+ };
+ } else {
+ err_dict.items[1] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("arg"),
+ .value = STRING_OBJ(((String) {
+ .data = xmemdupz(east.err.arg, (size_t)east.err.arg_len),
+ .size = (size_t)east.err.arg_len,
+ })),
+ };
+ }
+ ret.items[ret.size++] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("error"),
+ .value = DICTIONARY_OBJ(err_dict),
+ };
+ }
+ if (highlight) {
+ Array hl = (Array) {
+ .items = xmalloc(kv_size(colors) * sizeof(hl.items[0])),
+ .capacity = kv_size(colors),
+ .size = kv_size(colors),
+ };
+ for (size_t i = 0 ; i < kv_size(colors) ; i++) {
+ const ParserHighlightChunk chunk = kv_A(colors, i);
+ Array chunk_arr = (Array) {
+ .items = xmalloc(4 * sizeof(chunk_arr.items[0])),
+ .capacity = 4,
+ .size = 4,
+ };
+ chunk_arr.items[0] = INTEGER_OBJ((Integer)chunk.start.line);
+ chunk_arr.items[1] = INTEGER_OBJ((Integer)chunk.start.col);
+ chunk_arr.items[2] = INTEGER_OBJ((Integer)chunk.end_col);
+ chunk_arr.items[3] = STRING_OBJ(cstr_to_string(chunk.group));
+ hl.items[i] = ARRAY_OBJ(chunk_arr);
+ }
+ ret.items[ret.size++] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("highlight"),
+ .value = ARRAY_OBJ(hl),
+ };
+ }
+ kvi_destroy(colors);
+
+ // Walk over the AST, freeing nodes in process.
+ ExprASTConvStack ast_conv_stack;
+ kvi_init(ast_conv_stack);
+ kvi_push(ast_conv_stack, ((ExprASTConvStackItem) {
+ .node_p = &east.root,
+ .ret_node_p = &ret.items[0].value,
+ }));
+ while (kv_size(ast_conv_stack)) {
+ ExprASTConvStackItem cur_item = kv_last(ast_conv_stack);
+ ExprASTNode *const node = *cur_item.node_p;
+ if (node == NULL) {
+ assert(kv_size(ast_conv_stack) == 1);
+ kv_drop(ast_conv_stack, 1);
+ } else {
+ if (cur_item.ret_node_p->type == kObjectTypeNil) {
+ size_t items_size = (size_t)(3 // "type", "start" and "len"
+ + (node->children != NULL) // "children"
+ + (node->type == kExprNodeOption
+ || node->type == kExprNodePlainIdentifier) // "scope"
+ + (node->type == kExprNodeOption
+ || node->type == kExprNodePlainIdentifier
+ || node->type == kExprNodePlainKey
+ || node->type == kExprNodeEnvironment) // "ident"
+ + (node->type == kExprNodeRegister) // "name"
+ + (3 // "cmp_type", "ccs_strategy", "invert"
+ * (node->type == kExprNodeComparison))
+ + (node->type == kExprNodeInteger) // "ivalue"
+ + (node->type == kExprNodeFloat) // "fvalue"
+ + (node->type == kExprNodeDoubleQuotedString
+ || node->type == kExprNodeSingleQuotedString) // "svalue"
+ + (node->type == kExprNodeAssignment) // "augmentation"
+ + 0);
+ Dictionary ret_node = {
+ .items = xmalloc(items_size * sizeof(ret_node.items[0])),
+ .capacity = items_size,
+ .size = 0,
+ };
+ *cur_item.ret_node_p = DICTIONARY_OBJ(ret_node);
+ }
+ Dictionary *ret_node = &cur_item.ret_node_p->data.dictionary;
+ if (node->children != NULL) {
+ const size_t num_children = 1 + (node->children->next != NULL);
+ Array children_array = {
+ .items = xmalloc(num_children * sizeof(children_array.items[0])),
+ .capacity = num_children,
+ .size = num_children,
+ };
+ for (size_t i = 0; i < num_children; i++) {
+ children_array.items[i] = NIL;
+ }
+ ret_node->items[ret_node->size++] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("children"),
+ .value = ARRAY_OBJ(children_array),
+ };
+ kvi_push(ast_conv_stack, ((ExprASTConvStackItem) {
+ .node_p = &node->children,
+ .ret_node_p = &children_array.items[0],
+ }));
+ } else if (node->next != NULL) {
+ kvi_push(ast_conv_stack, ((ExprASTConvStackItem) {
+ .node_p = &node->next,
+ .ret_node_p = cur_item.ret_node_p + 1,
+ }));
+ } else {
+ kv_drop(ast_conv_stack, 1);
+ ret_node->items[ret_node->size++] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("type"),
+ .value = STRING_OBJ(cstr_to_string(east_node_type_tab[node->type])),
+ };
+ Array start_array = {
+ .items = xmalloc(2 * sizeof(start_array.items[0])),
+ .capacity = 2,
+ .size = 2,
+ };
+ start_array.items[0] = INTEGER_OBJ((Integer)node->start.line);
+ start_array.items[1] = INTEGER_OBJ((Integer)node->start.col);
+ ret_node->items[ret_node->size++] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("start"),
+ .value = ARRAY_OBJ(start_array),
+ };
+ ret_node->items[ret_node->size++] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("len"),
+ .value = INTEGER_OBJ((Integer)node->len),
+ };
+ switch (node->type) {
+ case kExprNodeDoubleQuotedString:
+ case kExprNodeSingleQuotedString:
+ ret_node->items[ret_node->size++] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("svalue"),
+ .value = STRING_OBJ(((String) {
+ .data = node->data.str.value,
+ .size = node->data.str.size,
+ })),
+ };
+ break;
+ case kExprNodeOption:
+ ret_node->items[ret_node->size++] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("scope"),
+ .value = INTEGER_OBJ(node->data.opt.scope),
+ };
+ ret_node->items[ret_node->size++] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("ident"),
+ .value = STRING_OBJ(((String) {
+ .data = xmemdupz(node->data.opt.ident,
+ node->data.opt.ident_len),
+ .size = node->data.opt.ident_len,
+ })),
+ };
+ break;
+ case kExprNodePlainIdentifier:
+ ret_node->items[ret_node->size++] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("scope"),
+ .value = INTEGER_OBJ(node->data.var.scope),
+ };
+ ret_node->items[ret_node->size++] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("ident"),
+ .value = STRING_OBJ(((String) {
+ .data = xmemdupz(node->data.var.ident,
+ node->data.var.ident_len),
+ .size = node->data.var.ident_len,
+ })),
+ };
+ break;
+ case kExprNodePlainKey:
+ ret_node->items[ret_node->size++] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("ident"),
+ .value = STRING_OBJ(((String) {
+ .data = xmemdupz(node->data.var.ident,
+ node->data.var.ident_len),
+ .size = node->data.var.ident_len,
+ })),
+ };
+ break;
+ case kExprNodeEnvironment:
+ ret_node->items[ret_node->size++] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("ident"),
+ .value = STRING_OBJ(((String) {
+ .data = xmemdupz(node->data.env.ident,
+ node->data.env.ident_len),
+ .size = node->data.env.ident_len,
+ })),
+ };
+ break;
+ case kExprNodeRegister:
+ ret_node->items[ret_node->size++] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("name"),
+ .value = INTEGER_OBJ(node->data.reg.name),
+ };
+ break;
+ case kExprNodeComparison:
+ ret_node->items[ret_node->size++] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("cmp_type"),
+ .value = STRING_OBJ(cstr_to_string(eltkn_cmp_type_tab[node->data.cmp.type])),
+ };
+ ret_node->items[ret_node->size++] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("ccs_strategy"),
+ .value = STRING_OBJ(cstr_to_string(ccs_tab[node->data.cmp.ccs])),
+ };
+ ret_node->items[ret_node->size++] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("invert"),
+ .value = BOOLEAN_OBJ(node->data.cmp.inv),
+ };
+ break;
+ case kExprNodeFloat:
+ ret_node->items[ret_node->size++] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("fvalue"),
+ .value = FLOAT_OBJ(node->data.flt.value),
+ };
+ break;
+ case kExprNodeInteger:
+ ret_node->items[ret_node->size++] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("ivalue"),
+ .value = INTEGER_OBJ((Integer)(node->data.num.value > API_INTEGER_MAX
+ ? API_INTEGER_MAX
+ : (Integer)node->data.num.value)),
+ };
+ break;
+ case kExprNodeAssignment: {
+ const ExprAssignmentType asgn_type = node->data.ass.type;
+ ret_node->items[ret_node->size++] = (KeyValuePair) {
+ .key = STATIC_CSTR_TO_STRING("augmentation"),
+ .value = STRING_OBJ(asgn_type == kExprAsgnPlain
+ ? (String)STRING_INIT
+ : cstr_to_string(expr_asgn_type_tab[asgn_type])),
+ };
+ break;
+ }
+ case kExprNodeMissing:
+ case kExprNodeOpMissing:
+ case kExprNodeTernary:
+ case kExprNodeTernaryValue:
+ case kExprNodeSubscript:
+ case kExprNodeListLiteral:
+ case kExprNodeUnaryPlus:
+ case kExprNodeBinaryPlus:
+ case kExprNodeNested:
+ case kExprNodeCall:
+ case kExprNodeComplexIdentifier:
+ case kExprNodeUnknownFigure:
+ case kExprNodeLambda:
+ case kExprNodeDictLiteral:
+ case kExprNodeCurlyBracesIdentifier:
+ case kExprNodeComma:
+ case kExprNodeColon:
+ case kExprNodeArrow:
+ case kExprNodeConcat:
+ case kExprNodeConcatOrSubscript:
+ case kExprNodeOr:
+ case kExprNodeAnd:
+ case kExprNodeUnaryMinus:
+ case kExprNodeBinaryMinus:
+ case kExprNodeNot:
+ case kExprNodeMultiplication:
+ case kExprNodeDivision:
+ case kExprNodeMod:
+ break;
+ }
+ assert(cur_item.ret_node_p->data.dictionary.size
+ == cur_item.ret_node_p->data.dictionary.capacity);
+ xfree(*cur_item.node_p);
+ *cur_item.node_p = NULL;
+ }
+ }
+ }
+ kvi_destroy(ast_conv_stack);
+
+ assert(ret.size == ret.capacity);
+ // Should be a no-op actually, leaving it in case non-nodes will need to be
+ // freed later.
+ viml_pexpr_free_ast(east);
+ viml_parser_destroy(&pstate);
+ return ret;
+}
diff --git a/src/nvim/api/vimscript.h b/src/nvim/api/vimscript.h
new file mode 100644
index 0000000000..be808b6b25
--- /dev/null
+++ b/src/nvim/api/vimscript.h
@@ -0,0 +1,9 @@
+#ifndef NVIM_API_VIMSCRIPT_H
+#define NVIM_API_VIMSCRIPT_H
+
+#include "nvim/api/private/defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/vimscript.h.generated.h"
+#endif
+#endif // NVIM_API_VIMSCRIPT_H
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
index d8105810aa..ceb7f71423 100644
--- a/src/nvim/api/win_config.c
+++ b/src/nvim/api/win_config.c
@@ -61,36 +61,36 @@
/// @param buffer Buffer to display, or 0 for current buffer
/// @param enter Enter the window (make it the current window)
/// @param config Map defining the window configuration. Keys:
-/// - `relative`: Sets the window layout to "floating", placed at (row,col)
+/// - relative: Sets the window layout to "floating", placed at (row,col)
/// coordinates relative to:
/// - "editor" The global editor grid
/// - "win" Window given by the `win` field, or current window.
/// - "cursor" Cursor position in current window.
-/// - `win`: |window-ID| for relative="win".
-/// - `anchor`: Decides which corner of the float to place at (row,col):
+/// - win: |window-ID| for relative="win".
+/// - anchor: Decides which corner of the float to place at (row,col):
/// - "NW" northwest (default)
/// - "NE" northeast
/// - "SW" southwest
/// - "SE" southeast
-/// - `width`: Window width (in character cells). Minimum of 1.
-/// - `height`: Window height (in character cells). Minimum of 1.
-/// - `bufpos`: Places float relative to buffer text (only when
+/// - width: Window width (in character cells). Minimum of 1.
+/// - height: Window height (in character cells). Minimum of 1.
+/// - bufpos: Places float relative to buffer text (only when
/// relative="win"). Takes a tuple of zero-indexed [line, column].
/// `row` and `col` if given are applied relative to this
/// position, else they default to:
/// - `row=1` and `col=0` if `anchor` is "NW" or "NE"
/// - `row=0` and `col=0` if `anchor` is "SW" or "SE"
/// (thus like a tooltip near the buffer text).
-/// - `row`: Row position in units of "screen cell height", may be fractional.
-/// - `col`: Column position in units of "screen cell width", may be
+/// - row: Row position in units of "screen cell height", may be fractional.
+/// - col: Column position in units of "screen cell width", may be
/// fractional.
-/// - `focusable`: Enable focus by user actions (wincmds, mouse events).
+/// - focusable: Enable focus by user actions (wincmds, mouse events).
/// Defaults to true. Non-focusable windows can be entered by
/// |nvim_set_current_win()|.
-/// - `external`: GUI should display the window as an external
+/// - external: GUI should display the window as an external
/// top-level window. Currently accepts no other positioning
/// configuration together with this.
-/// - `zindex`: Stacking order. floats with higher `zindex` go on top on
+/// - zindex: Stacking order. floats with higher `zindex` go on top on
/// floats with lower indices. Must be larger than zero. The
/// following screen elements have hard-coded z-indices:
/// - 100: insert completion popupmenu
@@ -99,7 +99,7 @@
/// The default value for floats are 50. In general, values below 100 are
/// recommended, unless there is a good reason to overshadow builtin
/// elements.
-/// - `style`: Configure the appearance of the window. Currently only takes
+/// - style: Configure the appearance of the window. Currently only takes
/// one non-empty value:
/// - "minimal" Nvim will display the window with many UI options
/// disabled. This is useful when displaying a temporary
@@ -110,7 +110,7 @@
/// end-of-buffer region is hidden by setting `eob` flag of
/// 'fillchars' to a space char, and clearing the
/// |EndOfBuffer| region in 'winhighlight'.
-/// - `border`: Style of (optional) window border. This can either be a string
+/// - border: Style of (optional) window border. This can either be a string
/// or an array. The string values are
/// - "none": No border (default).
/// - "single": A single line box.
@@ -134,7 +134,7 @@
/// By default, `FloatBorder` highlight is used, which links to `VertSplit`
/// when not defined. It could also be specified by character:
/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ].
-/// - `noautocmd`: If true then no buffer-related autocommand events such as
+/// - noautocmd: If true then no buffer-related autocommand events such as
/// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from
/// calling this function.
///
diff --git a/src/nvim/api/win_config.h b/src/nvim/api/win_config.h
index 9271c35f23..d3e5ede5e9 100644
--- a/src/nvim/api/win_config.h
+++ b/src/nvim/api/win_config.h
@@ -1,8 +1,6 @@
#ifndef NVIM_API_WIN_CONFIG_H
#define NVIM_API_WIN_CONFIG_H
-#include <stdint.h>
-
#include "nvim/api/private/defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/api/window.h b/src/nvim/api/window.h
index 632b160e62..0f36c12a9f 100644
--- a/src/nvim/api/window.h
+++ b/src/nvim/api/window.h
@@ -1,8 +1,6 @@
#ifndef NVIM_API_WINDOW_H
#define NVIM_API_WINDOW_H
-#include <stdint.h>
-
#include "nvim/api/private/defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS