diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 22:39:54 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 22:39:54 +0000 |
commit | 21cb7d04c387e4198ca8098a884c78b56ffcf4c2 (patch) | |
tree | 84fe5690df1551f0bb2bdfe1a13aacd29ebc1de7 /src/nvim/api/extmark.c | |
parent | d9c904f85a23a496df4eb6be42aa43f007b22d50 (diff) | |
parent | 4a8bf24ac690004aedf5540fa440e788459e5e34 (diff) | |
download | rneovim-colorcolchar.tar.gz rneovim-colorcolchar.tar.bz2 rneovim-colorcolchar.zip |
Merge remote-tracking branch 'upstream/master' into colorcolcharcolorcolchar
Diffstat (limited to 'src/nvim/api/extmark.c')
-rw-r--r-- | src/nvim/api/extmark.c | 878 |
1 files changed, 438 insertions, 440 deletions
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index ab3b3485e4..d71498d6ed 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -1,29 +1,31 @@ -// 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 <lauxlib.h> #include <stdbool.h> #include <stdint.h> #include <string.h> #include "klib/kvec.h" -#include "lauxlib.h" #include "nvim/api/extmark.h" +#include "nvim/api/keysets_defs.h" #include "nvim/api/private/defs.h" +#include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/private/validate.h" #include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/decoration.h" #include "nvim/decoration_provider.h" #include "nvim/drawscreen.h" #include "nvim/extmark.h" +#include "nvim/func_attr.h" +#include "nvim/grid.h" #include "nvim/highlight_group.h" +#include "nvim/marktree.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/pos.h" -#include "nvim/strings.h" -#include "nvim/vim.h" +#include "nvim/pos_defs.h" +#include "nvim/sign.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/extmark.c.generated.h" @@ -32,12 +34,10 @@ void api_extmark_free_all_mem(void) { String name; - handle_T id; - map_foreach(&namespace_ids, name, id, { - (void)id; + map_foreach_key(&namespace_ids, name, { xfree(name.data); }) - map_destroy(String, handle_T)(&namespace_ids); + map_destroy(String, &namespace_ids); } /// Creates a new namespace or gets an existing one. \*namespace\* @@ -54,14 +54,14 @@ void api_extmark_free_all_mem(void) Integer nvim_create_namespace(String name) FUNC_API_SINCE(5) { - handle_T id = map_get(String, handle_T)(&namespace_ids, name); + handle_T id = map_get(String, int)(&namespace_ids, name); if (id > 0) { return id; } id = next_namespace_id++; if (name.size > 0) { String name_alloc = copy_string(name, NULL); - map_put(String, handle_T)(&namespace_ids, name_alloc, id); + map_put(String, int)(&namespace_ids, name_alloc, id); } return (Integer)id; } @@ -83,7 +83,7 @@ Dictionary nvim_get_namespaces(void) return retval; } -const char *describe_ns(NS ns_id) +const char *describe_ns(NS ns_id, const char *unknown) { String name; handle_T id; @@ -92,7 +92,7 @@ const char *describe_ns(NS ns_id) return name.data; } }) - return "(UNKNOWN PLUGIN)"; + return unknown; } // Is the Namespace in use? @@ -104,92 +104,73 @@ bool ns_initialized(uint32_t ns) return ns < (uint32_t)next_namespace_id; } -static Array extmark_to_array(const ExtmarkInfo *extmark, bool id, bool add_dict) +Array virt_text_to_array(VirtText vt, bool hl_name) { + Array chunks = ARRAY_DICT_INIT; + Array hl_array = ARRAY_DICT_INIT; + for (size_t i = 0; i < kv_size(vt); i++) { + char *text = kv_A(vt, i).text; + int hl_id = kv_A(vt, i).hl_id; + if (text == NULL) { + if (hl_id > 0) { + ADD(hl_array, hl_group_name(hl_id, hl_name)); + } + continue; + } + Array chunk = ARRAY_DICT_INIT; + ADD(chunk, CSTR_TO_OBJ(text)); + if (hl_array.size > 0) { + if (hl_id > 0) { + ADD(hl_array, hl_group_name(hl_id, hl_name)); + } + ADD(chunk, ARRAY_OBJ(hl_array)); + hl_array = (Array)ARRAY_DICT_INIT; + } else if (hl_id > 0) { + ADD(chunk, hl_group_name(hl_id, hl_name)); + } + ADD(chunks, ARRAY_OBJ(chunk)); + } + assert(hl_array.size == 0); + return chunks; +} + +static Array extmark_to_array(MTPair extmark, bool id, bool add_dict, bool hl_name) +{ + MTKey start = extmark.start; Array rv = ARRAY_DICT_INIT; if (id) { - ADD(rv, INTEGER_OBJ((Integer)extmark->mark_id)); + ADD(rv, INTEGER_OBJ((Integer)start.id)); } - ADD(rv, INTEGER_OBJ(extmark->row)); - ADD(rv, INTEGER_OBJ(extmark->col)); + ADD(rv, INTEGER_OBJ(start.pos.row)); + ADD(rv, INTEGER_OBJ(start.pos.col)); if (add_dict) { Dictionary dict = ARRAY_DICT_INIT; - PUT(dict, "right_gravity", BOOLEAN_OBJ(extmark->right_gravity)); + PUT(dict, "ns_id", INTEGER_OBJ((Integer)start.ns)); - if (extmark->end_row >= 0) { - PUT(dict, "end_row", INTEGER_OBJ(extmark->end_row)); - PUT(dict, "end_col", INTEGER_OBJ(extmark->end_col)); - PUT(dict, "end_right_gravity", BOOLEAN_OBJ(extmark->end_right_gravity)); - } + PUT(dict, "right_gravity", BOOLEAN_OBJ(mt_right(start))); - const Decoration *decor = &extmark->decor; - if (decor->hl_id) { - String name = cstr_to_string((const char *)syn_id2name(decor->hl_id)); - PUT(dict, "hl_group", STRING_OBJ(name)); - PUT(dict, "hl_eol", BOOLEAN_OBJ(decor->hl_eol)); - } - if (decor->hl_mode) { - PUT(dict, "hl_mode", STRING_OBJ(cstr_to_string(hl_mode_str[decor->hl_mode]))); + if (extmark.end_pos.row >= 0) { + PUT(dict, "end_row", INTEGER_OBJ(extmark.end_pos.row)); + PUT(dict, "end_col", INTEGER_OBJ(extmark.end_pos.col)); + PUT(dict, "end_right_gravity", BOOLEAN_OBJ(extmark.end_right_gravity)); } - 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, "virt_text_hide", BOOLEAN_OBJ(decor->virt_text_hide)); - if (decor->virt_text_pos == kVTWinCol) { - PUT(dict, "virt_text_win_col", INTEGER_OBJ(decor->col)); - } - PUT(dict, "virt_text_pos", - STRING_OBJ(cstr_to_string(virt_text_pos_str[decor->virt_text_pos]))); + if (mt_no_undo(start)) { + PUT(dict, "undo_restore", BOOLEAN_OBJ(false)); } - if (decor->ui_watched) { - PUT(dict, "ui_watched", BOOLEAN_OBJ(true)); + if (mt_invalidate(start)) { + PUT(dict, "invalidate", BOOLEAN_OBJ(true)); } - - if (kv_size(decor->virt_lines)) { - Array all_chunks = ARRAY_DICT_INIT; - bool virt_lines_leftcol = false; - for (size_t i = 0; i < decor->virt_lines.size; i++) { - Array chunks = ARRAY_DICT_INIT; - VirtText *vt = &decor->virt_lines.items[i].line; - virt_lines_leftcol = decor->virt_lines.items[i].left_col; - for (size_t j = 0; j < vt->size; j++) { - Array chunk = ARRAY_DICT_INIT; - VirtTextChunk *vtc = &vt->items[j]; - 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)); - } - ADD(all_chunks, ARRAY_OBJ(chunks)); - } - PUT(dict, "virt_lines", ARRAY_OBJ(all_chunks)); - PUT(dict, "virt_lines_above", BOOLEAN_OBJ(decor->virt_lines_above)); - PUT(dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_leftcol)); + if (mt_invalid(start)) { + PUT(dict, "invalid", BOOLEAN_OBJ(true)); } - if (decor->hl_id || kv_size(decor->virt_text) || decor->ui_watched) { - PUT(dict, "priority", INTEGER_OBJ(decor->priority)); - } + decor_to_dict_legacy(&dict, mt_decor(start), hl_name); - if (dict.size) { - ADD(rv, DICTIONARY_OBJ(dict)); - } + ADD(rv, DICTIONARY_OBJ(dict)); } return rv; @@ -202,6 +183,7 @@ static Array extmark_to_array(const ExtmarkInfo *extmark, bool id, bool add_dict /// @param id Extmark id /// @param opts Optional parameters. Keys: /// - details: Whether to include the details dict +/// - hl_name: Whether to include highlight group name instead of id, true if omitted /// @param[out] err Error details, if any /// @return 0-indexed (row, col) tuple or empty list () if extmark id was /// absent @@ -218,80 +200,92 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, return rv; } - if (!ns_initialized((uint32_t)ns_id)) { - api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); + VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, { return rv; - } + }); bool details = false; + bool hl_name = true; 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"); + details = api_object_to_bool(*v, "details", false, err); + if (ERROR_SET(err)) { + return rv; + } + } else if (strequal("hl_name", k.data)) { + hl_name = api_object_to_bool(*v, "hl_name", false, err); + if (ERROR_SET(err)) { return rv; } } else { - api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); - return rv; + VALIDATE_S(false, "'opts' key", k.data, { + return rv; + }); } } - ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id); - if (extmark.row < 0) { + MTPair extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id); + if (extmark.start.pos.row < 0) { return rv; } - return extmark_to_array(&extmark, false, details); + return extmark_to_array(extmark, false, details, hl_name); } -/// Gets |extmarks| in "traversal order" from a |charwise| region defined by -/// buffer positions (inclusive, 0-indexed |api-indexing|). +/// Gets |extmarks| (including |signs|) 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>lua -/// vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) -/// vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {}) -/// </pre> +/// +/// ```lua +/// vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) +/// vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {}) +/// ``` /// /// If `end` is less than `start`, traversal works backwards. (Useful /// with `limit`, to get the first marks prior to a given position.) /// +/// Note: when using extmark ranges (marks with a end_row/end_col position) +/// the `overlap` option might be useful. Otherwise only the start position +/// of an extmark will be considered. +/// /// Example: -/// <pre>lua -/// 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, {}) -/// -- Create new extmark at line 3, column 1. -/// local m2 = a.nvim_buf_set_extmark(0, ns, 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> +/// +/// ```lua +/// local api = vim.api +/// local pos = api.nvim_win_get_cursor(0) +/// local ns = api.nvim_create_namespace('my-plugin') +/// -- Create new extmark at line 1, column 1. +/// local m1 = api.nvim_buf_set_extmark(0, ns, 0, 0, {}) +/// -- Create new extmark at line 3, column 1. +/// local m2 = api.nvim_buf_set_extmark(0, ns, 2, 0, {}) +/// -- Get extmarks only from line 3. +/// local ms = api.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {}) +/// -- Get all marks in this buffer + namespace. +/// local all = api.nvim_buf_get_extmarks(0, ns, 0, -1, {}) +/// vim.print(ms) +/// ``` /// /// @param buffer Buffer handle, or 0 for current buffer -/// @param ns_id Namespace id from |nvim_create_namespace()| +/// @param ns_id Namespace id from |nvim_create_namespace()| or -1 for all namespaces /// @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 +/// - details: Whether to include the details dict +/// - hl_name: Whether to include highlight group name instead of id, true if omitted +/// - overlap: Also include marks which overlap the range, even if +/// their start position is less than `start` +/// - type: Filter marks by type: "highlight", "sign", "virt_text" and "virt_lines" /// @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) +Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object end, + Dict(get_extmarks) *opts, Error *err) FUNC_API_SINCE(7) { Array rv = ARRAY_DICT_INIT; @@ -301,38 +295,32 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e return rv; } - if (!ns_initialized((uint32_t)ns_id)) { - api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); + VALIDATE_INT(ns_id == -1 || ns_initialized((uint32_t)ns_id), "ns_id", 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; - } + }); + + bool details = opts->details; + bool hl_name = GET_BOOL_OR_TRUE(opts, get_extmarks, hl_name); + + ExtmarkType type = kExtmarkNone; + if (HAS_KEY(opts, get_extmarks, type)) { + if (strequal(opts->type.data, "sign")) { + type = kExtmarkSign; + } else if (strequal(opts->type.data, "virt_text")) { + type = kExtmarkVirtText; + } else if (strequal(opts->type.data, "virt_lines")) { + type = kExtmarkVirtLines; + } else if (strequal(opts->type.data, "highlight")) { + type = kExtmarkHighlight; } else { - api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); - return rv; + VALIDATE_EXP(false, "type", "sign, virt_text, virt_lines or highlight", opts->type.data, { + return rv; + }); } } + Integer limit = HAS_KEY(opts, get_extmarks, limit) ? opts->limit : -1; + if (limit == 0) { return rv; } else if (limit < 0) { @@ -357,11 +345,12 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e reverse = true; } - ExtmarkInfoArray marks = extmark_get(buf, (uint32_t)ns_id, l_row, l_col, - u_row, u_col, (int64_t)limit, reverse); + // note: ns_id=-1 allowed, represented as UINT32_MAX + ExtmarkInfoArray marks = extmark_get(buf, (uint32_t)ns_id, l_row, l_col, u_row, + u_col, (int64_t)limit, reverse, type, opts->overlap); for (size_t i = 0; i < kv_size(marks); i++) { - ADD(rv, ARRAY_OBJ(extmark_to_array(&kv_A(marks, i), true, (bool)details))); + ADD(rv, ARRAY_OBJ(extmark_to_array(kv_A(marks, i), true, details, hl_name))); } kv_destroy(marks); @@ -379,6 +368,11 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// 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. /// +/// If present, the position defined by `end_col` and `end_row` should be after +/// the start position in order for the extmark to cover a range. +/// An earlier end position is not an error, but then it behaves like an empty +/// range (no highlighting). +/// /// @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| @@ -402,24 +396,28 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// 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) +/// - "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. +/// - "inline": display at the specified column, and +/// shift the buffer text to the right as needed. /// - virt_text_win_col : position the virtual text at a fixed /// window column (starting from the first -/// text column) +/// text column of the screen line) instead +/// of "virt_text_pos". /// - virt_text_hide : hide the virtual text when the background -/// text is selected or hidden due to -/// horizontal scroll 'nowrap' +/// text is selected or hidden because of +/// scrolling with 'nowrap' or 'smoothscroll'. +/// Currently only affects "overlay" virt_text. /// - 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 +/// - "replace": only show the virt_text color. This is the default. +/// - "combine": combine with background text color. /// - "blend": blend with background text color. +/// Not supported for "inline" virt_text. /// /// - virt_lines : virtual lines to add next to this mark /// This should be an array over lines, where each line in @@ -443,11 +441,17 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// 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. +/// (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. +/// - undo_restore : Restore the exact position of the mark +/// if text around the mark was deleted and then restored by undo. +/// Defaults to true. +/// - invalidate : boolean that indicates whether to hide the +/// extmark if the entirety of its range is deleted. If +/// "undo_restore" is false, the extmark is deleted instead. /// - priority: a priority value for the highlight group or sign /// attribute. For example treesitter highlighting uses a /// value of 100. @@ -493,286 +497,255 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer Dict(set_extmark) *opts, Error *err) FUNC_API_SINCE(7) { - Decoration decor = DECORATION_INIT; - bool has_decor = false; + DecorHighlightInline hl = DECOR_HIGHLIGHT_INLINE_INIT; + // TODO(bfredl): in principle signs with max one (1) hl group and max 4 bytes of text. + // should be a candidate for inlining as well. + DecorSignHighlight sign = DECOR_SIGN_HIGHLIGHT_INIT; + DecorVirtText virt_text = DECOR_VIRT_TEXT_INIT; + DecorVirtText virt_lines = DECOR_VIRT_LINES_INIT; + bool has_hl = false; buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { goto error; } - if (!ns_initialized((uint32_t)ns_id)) { - api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); + VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, { goto error; - } + }); uint32_t id = 0; - if (opts->id.type == kObjectTypeInteger && opts->id.data.integer > 0) { - id = (uint32_t)opts->id.data.integer; - } else if (HAS_KEY(opts->id)) { - api_set_error(err, kErrorTypeValidation, "id is not a positive integer"); - goto error; + if (HAS_KEY(opts, set_extmark, id)) { + VALIDATE_EXP((opts->id > 0), "id", "positive Integer", NULL, { + goto error; + }); + + id = (uint32_t)opts->id; } int line2 = -1; + bool did_end_line = false; // For backward compatibility we support "end_line" as an alias for "end_row" - if (HAS_KEY(opts->end_line)) { - if (HAS_KEY(opts->end_row)) { - api_set_error(err, kErrorTypeValidation, "cannot use both end_row and end_line"); + if (HAS_KEY(opts, set_extmark, end_line)) { + VALIDATE(!HAS_KEY(opts, set_extmark, end_row), + "%s", "cannot use both 'end_row' and 'end_line'", { goto error; - } - opts->end_row = opts->end_line; - } + }); -#define OPTION_TO_BOOL(target, name, val) \ - target = api_object_to_bool(opts->name, #name, val, err); \ - if (ERROR_SET(err)) { \ - goto error; \ + opts->end_row = opts->end_line; + did_end_line = true; } - bool strict = true; - OPTION_TO_BOOL(strict, strict, true); + bool strict = GET_BOOL_OR_TRUE(opts, set_extmark, strict); - if (opts->end_row.type == kObjectTypeInteger) { - Integer val = opts->end_row.data.integer; - if (val < 0 || (val > buf->b_ml.ml_line_count && strict)) { - api_set_error(err, kErrorTypeValidation, "end_row value outside range"); + if (HAS_KEY(opts, set_extmark, end_row) || did_end_line) { + Integer val = opts->end_row; + VALIDATE_RANGE((val >= 0 && !(val > buf->b_ml.ml_line_count && strict)), "end_row", { goto error; - } else { - line2 = (int)val; - } - } else if (HAS_KEY(opts->end_row)) { - api_set_error(err, kErrorTypeValidation, "end_row is not an integer"); - goto error; + }); + line2 = (int)val; } 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"); + if (HAS_KEY(opts, set_extmark, end_col)) { + Integer val = opts->end_col; + VALIDATE_RANGE((val >= 0 && val <= MAXCOL), "end_col", { 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; + }); + col2 = (int)val; } // uncrustify:off + // TODO(bfredl): keyset type alias for hl_group? (nil|int|string) struct { const char *name; Object *opt; int *dest; } hls[] = { - { "hl_group" , &opts->hl_group , &decor.hl_id }, - { "sign_hl_group" , &opts->sign_hl_group , &decor.sign_hl_id }, - { "number_hl_group" , &opts->number_hl_group , &decor.number_hl_id }, - { "line_hl_group" , &opts->line_hl_group , &decor.line_hl_id }, - { "cursorline_hl_group", &opts->cursorline_hl_group, &decor.cursorline_hl_id }, + { "hl_group" , &opts->hl_group , &hl.hl_id }, + { "sign_hl_group" , &opts->sign_hl_group , &sign.hl_id }, + { "number_hl_group" , &opts->number_hl_group , &sign.number_hl_id }, + { "line_hl_group" , &opts->line_hl_group , &sign.line_hl_id }, + { "cursorline_hl_group", &opts->cursorline_hl_group, &sign.cursorline_hl_id }, { NULL, NULL, NULL }, }; // uncrustify:on for (int j = 0; hls[j].name && hls[j].dest; j++) { - if (HAS_KEY(*hls[j].opt)) { + if (hls[j].opt->type != kObjectTypeNil) { + if (j > 0) { + sign.flags |= kSHIsSign; + } else { + has_hl = true; + } *hls[j].dest = object_to_hl_id(*hls[j].opt, hls[j].name, err); if (ERROR_SET(err)) { goto error; } - has_decor = true; } } - if (opts->conceal.type == kObjectTypeString) { - String c = opts->conceal.data.string; - decor.conceal = true; - if (c.size) { - decor.conceal_char = utf_ptr2char(c.data); + if (HAS_KEY(opts, set_extmark, conceal)) { + hl.flags |= kSHConceal; + has_hl = true; + String c = opts->conceal; + if (c.size > 0) { + int ch; + hl.conceal_char = utfc_ptr2schar_len(c.data, (int)c.size, &ch); + if (!hl.conceal_char || !vim_isprintc(ch)) { + api_set_error(err, kErrorTypeValidation, "conceal char has to be printable"); + goto error; + } } - has_decor = true; - } else if (HAS_KEY(opts->conceal)) { - api_set_error(err, kErrorTypeValidation, "conceal is not a String"); - goto error; } - if (opts->virt_text.type == kObjectTypeArray) { - decor.virt_text = parse_virt_text(opts->virt_text.data.array, err, - &decor.virt_text_width); - has_decor = true; + if (HAS_KEY(opts, set_extmark, virt_text)) { + virt_text.data.virt_text = parse_virt_text(opts->virt_text, err, &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 (HAS_KEY(opts, set_extmark, virt_text_pos)) { + String str = opts->virt_text_pos; if (strequal("eol", str.data)) { - decor.virt_text_pos = kVTEndOfLine; + virt_text.pos = kVPosEndOfLine; } else if (strequal("overlay", str.data)) { - decor.virt_text_pos = kVTOverlay; + virt_text.pos = kVPosOverlay; } else if (strequal("right_align", str.data)) { - decor.virt_text_pos = kVTRightAlign; + virt_text.pos = kVPosRightAlign; + } else if (strequal("inline", str.data)) { + virt_text.pos = kVPosInline; } else { - api_set_error(err, kErrorTypeValidation, "virt_text_pos: invalid value"); - goto error; + VALIDATE_S(false, "virt_text_pos", str.data, { + 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; + if (HAS_KEY(opts, set_extmark, virt_text_win_col)) { + virt_text.col = (int)opts->virt_text_win_col; + virt_text.pos = kVPosWinCol; } - OPTION_TO_BOOL(decor.virt_text_hide, virt_text_hide, false); - OPTION_TO_BOOL(decor.hl_eol, hl_eol, false); + hl.flags |= opts->hl_eol ? kSHHlEol : 0; + virt_text.flags |= opts->virt_text_hide ? kVTHide : 0; - if (opts->hl_mode.type == kObjectTypeString) { - String str = opts->hl_mode.data.string; + if (HAS_KEY(opts, set_extmark, hl_mode)) { + String str = opts->hl_mode; if (strequal("replace", str.data)) { - decor.hl_mode = kHlModeReplace; + virt_text.hl_mode = kHlModeReplace; } else if (strequal("combine", str.data)) { - decor.hl_mode = kHlModeCombine; + virt_text.hl_mode = kHlModeCombine; } else if (strequal("blend", str.data)) { - decor.hl_mode = kHlModeBlend; + if (virt_text.pos == kVPosInline) { + VALIDATE(false, "%s", "cannot use 'blend' hl_mode with inline virtual text", { + goto error; + }); + } + virt_text.hl_mode = kHlModeBlend; } else { - api_set_error(err, kErrorTypeValidation, - "virt_text_pos: invalid value"); - goto error; + VALIDATE_S(false, "hl_mode", str.data, { + 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); + bool virt_lines_leftcol = opts->virt_lines_leftcol; - if (opts->virt_lines.type == kObjectTypeArray) { - Array a = opts->virt_lines.data.array; + if (HAS_KEY(opts, set_extmark, virt_lines)) { + Array a = opts->virt_lines; 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"); + VALIDATE_T("virt_text_line", kObjectTypeArray, a.items[j].type, { 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 })); + kv_push(virt_lines.data.virt_lines, ((struct virt_line){ jtem, virt_lines_leftcol })); if (ERROR_SET(err)) { goto error; } - has_decor = true; } - } else if (HAS_KEY(opts->virt_lines)) { - api_set_error(err, kErrorTypeValidation, "virt_lines is not an Array"); - goto error; } - OPTION_TO_BOOL(decor.virt_lines_above, virt_lines_above, false); - - if (opts->priority.type == kObjectTypeInteger) { - Integer val = opts->priority.data.integer; + virt_lines.flags |= opts->virt_lines_above ? kVTLinesAbove : 0; - if (val < 0 || val > UINT16_MAX) { - api_set_error(err, kErrorTypeValidation, "priority is not a valid value"); + if (HAS_KEY(opts, set_extmark, priority)) { + VALIDATE_RANGE((opts->priority >= 0 && opts->priority <= UINT16_MAX), "priority", { 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; + }); + hl.priority = (DecorPriority)opts->priority; + sign.priority = (DecorPriority)opts->priority; + virt_text.priority = (DecorPriority)opts->priority; + virt_lines.priority = (DecorPriority)opts->priority; } - if (opts->sign_text.type == kObjectTypeString) { - if (!init_sign_text(&decor.sign_text, - opts->sign_text.data.string.data)) { - api_set_error(err, kErrorTypeValidation, "sign_text is not a valid value"); + if (HAS_KEY(opts, set_extmark, sign_text)) { + sign.text.ptr = NULL; + VALIDATE_S(init_sign_text(NULL, &sign.text.ptr, opts->sign_text.data), + "sign_text", "", { goto error; - } - has_decor = true; - } else if (HAS_KEY(opts->sign_text)) { - api_set_error(err, kErrorTypeValidation, "sign_text is not a String"); - goto error; + }); + sign.flags |= kSHIsSign; } - bool right_gravity = true; - OPTION_TO_BOOL(right_gravity, right_gravity, true); + bool right_gravity = GET_BOOL_OR_TRUE(opts, set_extmark, right_gravity); // Only error out if they try to set end_right_gravity without // setting end_col or end_row - if (line2 == -1 && col2 == -1 && HAS_KEY(opts->end_right_gravity)) { - api_set_error(err, kErrorTypeValidation, - "cannot set end_right_gravity without setting end_row or end_col"); + VALIDATE(!(line2 == -1 && col2 == -1 && HAS_KEY(opts, set_extmark, end_right_gravity)), + "%s", "cannot set end_right_gravity without end_row 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 (opts->spell.type == kObjectTypeNil) { - decor.spell = kNone; - } else { - bool spell = false; - OPTION_TO_BOOL(spell, spell, false); - decor.spell = spell ? kTrue : kFalse; - has_decor = true; + if (HAS_KEY(opts, set_extmark, spell)) { + hl.flags |= (opts->spell) ? kSHSpellOn : kSHSpellOff; + has_hl = true; } - OPTION_TO_BOOL(decor.ui_watched, ui_watched, false); - if (decor.ui_watched) { - has_decor = true; + if (opts->ui_watched) { + hl.flags |= kSHUIWatched; + if (virt_text.pos == kVPosOverlay) { + // TODO(bfredl): in a revised interface this should be the default. + hl.flags |= kSHUIWatchedOverlay; + } + has_hl = true; } - if (line < 0) { - api_set_error(err, kErrorTypeValidation, "line value outside range"); + VALIDATE_RANGE((line >= 0), "line", { goto error; - } else if (line > buf->b_ml.ml_line_count) { - if (strict) { - api_set_error(err, kErrorTypeValidation, "line value outside range"); + }); + + if (line > buf->b_ml.ml_line_count) { + VALIDATE_RANGE(!strict, "line", { goto error; - } else { - line = buf->b_ml.ml_line_count; - } + }); + line = buf->b_ml.ml_line_count; } else if (line < buf->b_ml.ml_line_count) { - len = ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line + 1, false)); + len = opts->ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line + 1)); } if (col == -1) { col = (Integer)len; } else if (col > (Integer)len) { - if (strict) { - api_set_error(err, kErrorTypeValidation, "col value outside range"); + VALIDATE_RANGE(!strict, "col", { goto error; - } else { - col = (Integer)len; - } + }); + col = (Integer)len; } else if (col < -1) { - api_set_error(err, kErrorTypeValidation, "col value outside range"); - goto error; + VALIDATE_RANGE(false, "col", { + 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)); + len = opts->ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line2 + 1)); } else if (line2 == buf->b_ml.ml_line_count) { // We are trying to add an extmark past final newline len = 0; @@ -781,36 +754,96 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer line2 = (int)line; } if (col2 > (Integer)len) { - if (strict) { - api_set_error(err, kErrorTypeValidation, "end_col value outside range"); + VALIDATE_RANGE(!strict, "end_col", { goto error; - } else { - col2 = (int)len; - } + }); + col2 = (int)len; } } else if (line2 >= 0) { col2 = 0; } - // TODO(bfredl): synergize these two branches even more - if (ephemeral && decor_state.buf == buf) { - decor_add_ephemeral((int)line, (int)col, line2, col2, &decor, (uint64_t)ns_id, id); + if (opts->ephemeral && decor_state.win && decor_state.win->w_buffer == buf) { + int r = (int)line; + int c = (int)col; + if (line2 == -1) { + line2 = r; + col2 = c; + } + + if (kv_size(virt_text.data.virt_text)) { + decor_range_add_virt(&decor_state, r, c, line2, col2, decor_put_vt(virt_text, NULL), true); + } + if (kv_size(virt_lines.data.virt_lines)) { + decor_range_add_virt(&decor_state, r, c, line2, col2, decor_put_vt(virt_lines, NULL), true); + } + if (has_hl) { + DecorSignHighlight sh = decor_sh_from_inline(hl); + decor_range_add_sh(&decor_state, r, c, line2, col2, &sh, true, (uint32_t)ns_id, id); + } } else { - if (ephemeral) { + if (opts->ephemeral) { api_set_error(err, kErrorTypeException, "not yet implemented"); goto error; } + uint16_t decor_flags = 0; + + DecorVirtText *decor_alloc = NULL; + if (kv_size(virt_text.data.virt_text)) { + decor_alloc = decor_put_vt(virt_text, decor_alloc); + if (virt_text.pos == kVPosInline) { + decor_flags |= MT_FLAG_DECOR_VIRT_TEXT_INLINE; + } + } + if (kv_size(virt_lines.data.virt_lines)) { + decor_alloc = decor_put_vt(virt_lines, decor_alloc); + decor_flags |= MT_FLAG_DECOR_VIRT_LINES; + } + + uint32_t decor_indexed = DECOR_ID_INVALID; + if (sign.flags & kSHIsSign) { + decor_indexed = decor_put_sh(sign); + if (sign.text.ptr != NULL) { + decor_flags |= MT_FLAG_DECOR_SIGNTEXT; + } + if (sign.number_hl_id || sign.line_hl_id || sign.cursorline_hl_id) { + decor_flags |= MT_FLAG_DECOR_SIGNHL; + } + } + + DecorInline decor = DECOR_INLINE_INIT; + if (decor_alloc || decor_indexed != DECOR_ID_INVALID || schar_high(hl.conceal_char)) { + if (has_hl) { + DecorSignHighlight sh = decor_sh_from_inline(hl); + sh.next = decor_indexed; + decor_indexed = decor_put_sh(sh); + } + decor.ext = true; + decor.data.ext = (DecorExt){ .sh_idx = decor_indexed, .vt = decor_alloc }; + } else { + decor.data.hl = hl; + } + + if (has_hl) { + decor_flags |= MT_FLAG_DECOR_HL; + } + extmark_set(buf, (uint32_t)ns_id, &id, (int)line, (colnr_T)col, line2, col2, - has_decor ? &decor : NULL, right_gravity, end_right_gravity, - kExtmarkNoUndo); + decor, decor_flags, right_gravity, opts->end_right_gravity, + !GET_BOOL_OR_TRUE(opts, set_extmark, undo_restore), + opts->invalidate, err); + if (ERROR_SET(err)) { + decor_free(decor); + return 0; + } } return (Integer)id; error: - clear_virttext(&decor.virt_text); - xfree(decor.sign_text); + clear_virttext(&virt_text.data.virt_text); + clear_virtlines(&virt_lines.data.virt_lines); return 0; } @@ -829,12 +862,11 @@ Boolean nvim_buf_del_extmark(Buffer buffer, Integer ns_id, Integer id, Error *er if (!buf) { return false; } - if (!ns_initialized((uint32_t)ns_id)) { - api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); + VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, { return false; - } + }); - return extmark_del(buf, (uint32_t)ns_id, (uint32_t)id); + return extmark_del_id(buf, (uint32_t)ns_id, (uint32_t)id); } uint32_t src2ns(Integer *src_id) @@ -887,14 +919,13 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In return 0; } - if (line < 0 || line >= MAXLNUM) { - api_set_error(err, kErrorTypeValidation, "Line number outside range"); + VALIDATE_RANGE((line >= 0 && line < MAXLNUM), "line number", { return 0; - } - if (col_start < 0 || col_start > MAXCOL) { - api_set_error(err, kErrorTypeValidation, "Column value outside range"); + }); + VALIDATE_RANGE((col_start >= 0 && col_start <= MAXCOL), "column", { return 0; - } + }); + if (col_end < 0 || col_end > MAXCOL) { col_end = MAXCOL; } @@ -919,13 +950,11 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In end_line++; } - Decoration decor = DECORATION_INIT; - decor.hl_id = hl_id; + DecorInline decor = DECOR_INLINE_INIT; + decor.data.hl.hl_id = hl_id; - extmark_set(buf, ns, NULL, - (int)line, (colnr_T)col_start, - end_line, (colnr_T)col_end, - &decor, true, false, kExtmarkNoUndo); + extmark_set(buf, ns, NULL, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end, + decor, MT_FLAG_DECOR_HL, true, false, false, false, NULL); return ns_id; } @@ -950,10 +979,10 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start, return; } - if (line_start < 0 || line_start >= MAXLNUM) { - api_set_error(err, kErrorTypeValidation, "Line number outside range"); + VALIDATE_RANGE((line_start >= 0 && line_start < MAXLNUM), "line number", { return; - } + }); + if (line_end < 0 || line_end > MAXLNUM) { line_end = MAXLNUM; } @@ -964,14 +993,14 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start, /// Set or change decoration provider for a |namespace| /// -/// This is a very general purpose interface for having lua callbacks +/// 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 ). +/// 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` @@ -983,11 +1012,13 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start, /// 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 +/// Doing things like changing options are not explicitly 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. /// +/// Note: It is not allowed to remove or update extmarks in 'on_line' callbacks. +/// /// @param ns_id Namespace id from |nvim_create_namespace()| /// @param opts Table of callbacks: /// - on_start: called first on each screen redraw @@ -996,7 +1027,8 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start, /// window callbacks) /// ["buf", bufnr, tick] /// - on_win: called when starting to redraw a -/// specific window. +/// specific window. botline_guess is an approximation +/// that does not exceed the last line number. /// ["win", winid, bufnr, topline, botline_guess] /// - on_line: called for each buffer line being redrawn. /// (The interaction with fold lines is subject to change) @@ -1015,7 +1047,7 @@ void nvim_set_decoration_provider(Integer ns_id, Dict(set_decoration_provider) * struct { const char *name; - Object *source; + LuaRef *source; LuaRef *dest; } cbs[] = { { "on_start", &opts->on_start, &p->redraw_start }, @@ -1029,26 +1061,18 @@ void nvim_set_decoration_provider(Integer ns_id, Dict(set_decoration_provider) * }; for (size_t i = 0; cbs[i].source && cbs[i].dest && cbs[i].name; i++) { - Object *v = cbs[i].source; - if (v->type == kObjectTypeNil) { + LuaRef *v = cbs[i].source; + if (*v <= 0) { continue; } - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, - "%s is not a function", cbs[i].name); - goto error; - } - *(cbs[i].dest) = v->data.luaref; - v->data.luaref = LUA_NOREF; + *(cbs[i].dest) = *v; + *v = LUA_NOREF; } p->active = true; p->hl_valid++; p->hl_cached = false; - return; -error: - decor_provider_clear(p); } /// Gets the line and column of an |extmark|. @@ -1075,75 +1099,40 @@ static bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, in *col = MAXCOL; return true; } else if (id < 0) { - api_set_error(err, kErrorTypeValidation, "Mark id must be positive"); - return false; + VALIDATE_INT(false, "mark id", id, { + return false; + }); } - ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id); - if (extmark.row >= 0) { - *row = extmark.row; - *col = extmark.col; - return true; - } else { - api_set_error(err, kErrorTypeValidation, "No mark with requested id"); + MTPair extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id); + + VALIDATE_INT((extmark.start.pos.row >= 0), "mark id (not found)", id, { return false; - } + }); + *row = extmark.start.pos.row; + *col = extmark.start.pos.col; + return true; // Check if it is a position } else if (obj.type == kObjectTypeArray) { Array pos = obj.data.array; - if (pos.size != 2 - || pos.items[0].type != kObjectTypeInteger - || pos.items[1].type != kObjectTypeInteger) { - api_set_error(err, kErrorTypeValidation, - "Position must have 2 integer elements"); + VALIDATE_EXP((pos.size == 2 + && pos.items[0].type == kObjectTypeInteger + && pos.items[1].type == kObjectTypeInteger), + "mark position", "2 Integer items", NULL, { return false; - } + }); + Integer pos_row = pos.items[0].data.integer; Integer pos_col = pos.items[1].data.integer; - *row = (int)(pos_row >= 0 ? pos_row : MAXLNUM); + *row = (int)(pos_row >= 0 ? pos_row : MAXLNUM); *col = (colnr_T)(pos_col >= 0 ? pos_col : MAXCOL); return true; } else { - api_set_error(err, kErrorTypeValidation, - "Position must be a mark id Integer or position Array"); - return false; - } -} -// adapted from sign.c:sign_define_init_text. -// TODO(lewis6991): Consider merging -static int init_sign_text(char **sign_text, char *text) -{ - char *s; - - char *endp = text + (int)strlen(text); - - // Count cells and check for non-printable chars - int cells = 0; - for (s = text; s < endp; s += utfc_ptr2len(s)) { - if (!vim_isprintc(utf_ptr2char(s))) { - break; - } - cells += utf_ptr2cells(s); - } - // Currently must be empty, one or two display cells - if (s != endp || cells > 2) { - return FAIL; - } - if (cells < 1) { - return OK; - } - - // Allocate one byte more if we need to pad up - // with a space. - size_t len = (size_t)(endp - text + ((cells == 1) ? 1 : 0)); - *sign_text = xstrnsave(text, len); - - if (cells == 1) { - STRCPY(*sign_text + len - 1, " "); + VALIDATE_EXP(false, "mark position", "mark id Integer or 2-item Array", NULL, { + return false; + }); } - - return OK; } VirtText parse_virt_text(Array chunks, Error *err, int *width) @@ -1151,17 +1140,14 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width) VirtText virt_text = KV_INITIAL_VALUE; int w = 0; for (size_t i = 0; i < chunks.size; i++) { - if (chunks.items[i].type != kObjectTypeArray) { - api_set_error(err, kErrorTypeValidation, "Chunk is not an array"); + VALIDATE_T("chunk", kObjectTypeArray, chunks.items[i].type, { goto free_exit; - } + }); Array chunk = chunks.items[i].data.array; - if (chunk.size == 0 || chunk.size > 2 - || chunk.items[0].type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, - "Chunk is not an array with one or two strings"); + VALIDATE((chunk.size > 0 && chunk.size <= 2 && chunk.items[0].type == kObjectTypeString), + "%s", "Invalid chunk: expected Array with 1 or 2 Strings", { goto free_exit; - } + }); String str = chunk.items[0].data.string; @@ -1176,8 +1162,7 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width) goto free_exit; } if (j < arr.size - 1) { - kv_push(virt_text, ((VirtTextChunk){ .text = NULL, - .hl_id = hl_id })); + kv_push(virt_text, ((VirtTextChunk){ .text = NULL, .hl_id = hl_id })); } } } else { @@ -1194,10 +1179,23 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width) kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id })); } - *width = w; + if (width != NULL) { + *width = w; + } return virt_text; free_exit: clear_virttext(&virt_text); return virt_text; } + +String nvim__buf_debug_extmarks(Buffer buffer, Boolean keys, Boolean dot, Error *err) + FUNC_API_SINCE(7) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return NULL_STRING; + } + + return mt_inspect(buf->b_marktree, keys, dot); +} |