diff options
Diffstat (limited to 'src/nvim/api')
| -rw-r--r-- | src/nvim/api/buffer.c | 721 | ||||
| -rw-r--r-- | src/nvim/api/buffer.h | 2 | ||||
| -rw-r--r-- | src/nvim/api/deprecated.c | 2 | ||||
| -rw-r--r-- | src/nvim/api/extmark.c | 895 | ||||
| -rw-r--r-- | src/nvim/api/extmark.h | 13 | ||||
| -rw-r--r-- | src/nvim/api/private/converter.c | 348 | ||||
| -rw-r--r-- | src/nvim/api/private/converter.h | 11 | ||||
| -rw-r--r-- | src/nvim/api/private/defs.h | 2 | ||||
| -rw-r--r-- | src/nvim/api/private/dispatch.c | 2 | ||||
| -rw-r--r-- | src/nvim/api/private/helpers.c | 355 | ||||
| -rw-r--r-- | src/nvim/api/private/helpers.h | 2 | ||||
| -rw-r--r-- | src/nvim/api/tabpage.h | 2 | ||||
| -rw-r--r-- | src/nvim/api/vim.c | 930 | ||||
| -rw-r--r-- | src/nvim/api/vim.h | 6 | ||||
| -rw-r--r-- | src/nvim/api/vimscript.c | 733 | ||||
| -rw-r--r-- | src/nvim/api/vimscript.h | 9 | ||||
| -rw-r--r-- | src/nvim/api/win_config.c | 28 | ||||
| -rw-r--r-- | src/nvim/api/win_config.h | 2 | ||||
| -rw-r--r-- | src/nvim/api/window.h | 2 |
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 |