diff options
author | Björn Linse <bjorn.linse@gmail.com> | 2021-10-25 22:29:02 +0200 |
---|---|---|
committer | Björn Linse <bjorn.linse@gmail.com> | 2021-10-25 22:33:40 +0200 |
commit | c8882ca7e75e2f085ec2e0943d5afa09efc9c8ca (patch) | |
tree | 19317872a166dc5bf3a0f50490281d8b4a2fc1b4 | |
parent | 09e96fe6096f07365eb65b51bb7f2fd0f1b043b0 (diff) | |
download | rneovim-c8882ca7e75e2f085ec2e0943d5afa09efc9c8ca.tar.gz rneovim-c8882ca7e75e2f085ec2e0943d5afa09efc9c8ca.tar.bz2 rneovim-c8882ca7e75e2f085ec2e0943d5afa09efc9c8ca.zip |
refactor(api): move extmark API to its own file
-rw-r--r-- | src/nvim/api/buffer.c | 697 | ||||
-rw-r--r-- | src/nvim/api/buffer.h | 2 | ||||
-rw-r--r-- | src/nvim/api/deprecated.c | 1 | ||||
-rw-r--r-- | src/nvim/api/extmark.c | 896 | ||||
-rw-r--r-- | src/nvim/api/extmark.h | 13 | ||||
-rw-r--r-- | src/nvim/api/private/dispatch.c | 1 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.c | 21 | ||||
-rw-r--r-- | src/nvim/api/tabpage.h | 2 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 144 | ||||
-rw-r--r-- | src/nvim/api/vim.h | 6 | ||||
-rw-r--r-- | src/nvim/api/win_config.h | 2 | ||||
-rw-r--r-- | src/nvim/api/window.h | 2 | ||||
-rw-r--r-- | src/nvim/ex_cmds.c | 2 | ||||
-rw-r--r-- | src/nvim/extmark.c | 15 | ||||
-rw-r--r-- | src/nvim/main.c | 2 | ||||
-rw-r--r-- | src/nvim/memory.c | 4 | ||||
-rw-r--r-- | src/nvim/screen.c | 1 |
17 files changed, 917 insertions, 894 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 92fcb59b34..cbfb63581f 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" @@ -1238,702 +1237,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. -/// -/// - 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); -} - -/// 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 /// 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..815dd70a52 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -8,6 +8,7 @@ #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" diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c new file mode 100644 index 0000000000..e6756315c6 --- /dev/null +++ b/src/nvim/api/extmark.c @@ -0,0 +1,896 @@ +// 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/dispatch.c b/src/nvim/api/private/dispatch.c index 519bf8ff14..79af9bf176 100644 --- a/src/nvim/api/private/dispatch.c +++ b/src/nvim/api/private/dispatch.c @@ -8,6 +8,7 @@ #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" diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index f1259c8a18..745681c3f8 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1410,15 +1410,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 +1598,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/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 b5cc02e761..7f442c8e52 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -59,17 +59,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|. /// @@ -1414,49 +1403,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. @@ -2745,96 +2691,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|. /// 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/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 diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 558001cd62..17ab138d75 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -15,7 +15,7 @@ #include "nvim/api/buffer.h" #include "nvim/api/private/defs.h" -#include "nvim/api/vim.h" +#include "nvim/api/extmark.h" #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/buffer_updates.h" diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index 05d1d69f9d..a3a7791d21 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -30,7 +30,7 @@ #include <assert.h> -#include "nvim/api/vim.h" +#include "nvim/api/extmark.h" #include "nvim/buffer.h" #include "nvim/buffer_updates.h" #include "nvim/charset.h" @@ -715,16 +715,3 @@ void extmark_move_region(buf_T *buf, int start_row, colnr_T start_col, bcount_t .data.move = move })); } } - -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); - } -} - diff --git a/src/nvim/main.c b/src/nvim/main.c index 4b4ab687b2..c329c9a8f4 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -81,7 +81,7 @@ #ifndef WIN32 # include "nvim/os/pty_process_unix.h" #endif -#include "nvim/api/vim.h" +#include "nvim/api/extmark.h" // values for "window_layout" #define WIN_HOR 1 // "-o" horizontally split windows diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 5e6c6a8189..5345580b93 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -8,7 +8,7 @@ #include <stdbool.h> #include <string.h> -#include "nvim/api/vim.h" +#include "nvim/api/extmark.h" #include "nvim/context.h" #include "nvim/decoration.h" #include "nvim/eval.h" @@ -679,7 +679,7 @@ void free_all_mem(void) } eval_clear(); - api_vim_free_all_mem(); + api_extmark_free_all_mem(); ctx_free_all(); // Free all buffers. Reset 'autochdir' to avoid accessing things that diff --git a/src/nvim/screen.c b/src/nvim/screen.c index fcd428ba1b..16c81307ce 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -66,6 +66,7 @@ #include <string.h> #include "nvim/api/private/helpers.h" +#include "nvim/api/extmark.h" #include "nvim/api/vim.h" #include "nvim/arabic.h" #include "nvim/ascii.h" |