diff options
Diffstat (limited to 'src/nvim/api')
-rw-r--r-- | src/nvim/api/buffer.c | 591 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.c | 77 | ||||
-rw-r--r-- | src/nvim/api/ui_events.in.h | 10 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 163 |
4 files changed, 554 insertions, 287 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 8e61976c4b..cad4c8314f 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -7,6 +7,7 @@ #include <stdint.h> #include <stdlib.h> #include <limits.h> + #include <lauxlib.h> #include "nvim/api/buffer.h" @@ -175,8 +176,7 @@ Boolean nvim_buf_attach(uint64_t channel_id, } cb.on_lines = v->data.luaref; v->data.luaref = LUA_NOREF; - } else if (is_lua && strequal("_on_bytes", k.data)) { - // NB: undocumented, untested and incomplete interface! + } else if (is_lua && strequal("on_bytes", k.data)) { if (v->type != kObjectTypeLuaRef) { api_set_error(err, kErrorTypeValidation, "callback is not a function"); goto error; @@ -213,10 +213,10 @@ Boolean nvim_buf_attach(uint64_t channel_id, error: // TODO(bfredl): ASAN build should check that the ref table is empty? - executor_free_luaref(cb.on_lines); - executor_free_luaref(cb.on_bytes); - executor_free_luaref(cb.on_changedtick); - executor_free_luaref(cb.on_detach); + api_free_luaref(cb.on_lines); + api_free_luaref(cb.on_bytes); + api_free_luaref(cb.on_changedtick); + api_free_luaref(cb.on_detach); return false; } @@ -245,78 +245,6 @@ Boolean nvim_buf_detach(uint64_t channel_id, return true; } -static void buf_clear_luahl(buf_T *buf, bool force) -{ - if (buf->b_luahl || force) { - executor_free_luaref(buf->b_luahl_start); - executor_free_luaref(buf->b_luahl_window); - executor_free_luaref(buf->b_luahl_line); - executor_free_luaref(buf->b_luahl_end); - } - buf->b_luahl_start = LUA_NOREF; - buf->b_luahl_window = LUA_NOREF; - buf->b_luahl_line = LUA_NOREF; - buf->b_luahl_end = LUA_NOREF; -} - -/// Unstabilized interface for defining syntax hl in lua. -/// -/// This is not yet safe for general use, lua callbacks will need to -/// be restricted, like textlock and probably other stuff. -/// -/// The API on_line/nvim__put_attr is quite raw and not intended to be the -/// final shape. Ideally this should operate on chunks larger than a single -/// line to reduce interpreter overhead, and generate annotation objects -/// (bufhl/virttext) on the fly but using the same representation. -void nvim__buf_set_luahl(uint64_t channel_id, Buffer buffer, - DictionaryOf(LuaRef) opts, Error *err) - FUNC_API_LUA_ONLY -{ - buf_T *buf = find_buffer_by_handle(buffer, err); - - if (!buf) { - return; - } - - redraw_buf_later(buf, NOT_VALID); - buf_clear_luahl(buf, false); - - for (size_t i = 0; i < opts.size; i++) { - String k = opts.items[i].key; - Object *v = &opts.items[i].value; - if (strequal("on_start", k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, "callback is not a function"); - goto error; - } - buf->b_luahl_start = v->data.luaref; - v->data.luaref = LUA_NOREF; - } else if (strequal("on_window", k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, "callback is not a function"); - goto error; - } - buf->b_luahl_window = v->data.luaref; - v->data.luaref = LUA_NOREF; - } else if (strequal("on_line", k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, "callback is not a function"); - goto error; - } - buf->b_luahl_line = v->data.luaref; - v->data.luaref = LUA_NOREF; - } else { - api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); - goto error; - } - } - buf->b_luahl = true; - return; -error: - buf_clear_luahl(buf, true); - buf->b_luahl = false; -} - void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last, Error *err) FUNC_API_LUA_ONLY @@ -1025,6 +953,53 @@ Boolean nvim_buf_is_loaded(Buffer buffer) return buf && buf->b_ml.ml_mfp != NULL; } +/// Deletes the buffer. See |:bwipeout| +/// +/// @param buffer Buffer handle, or 0 for current buffer +/// @param opts Optional parameters. Keys: +/// - force: Force deletion and ignore unsaved changes. +/// - unload: Unloaded only, do not delete. See |:bunload| +void nvim_buf_delete(Buffer buffer, Dictionary opts, Error *err) + FUNC_API_SINCE(7) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (ERROR_SET(err)) { + return; + } + + bool force = false; + bool unload = false; + for (size_t i = 0; i < opts.size; i++) { + String k = opts.items[i].key; + Object v = opts.items[i].value; + if (strequal("force", k.data)) { + force = api_coerce_to_bool(v, "force", false, err); + } else if (strequal("unload", k.data)) { + unload = api_coerce_to_bool(v, "unload", false, err); + } else { + api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); + return; + } + } + + if (ERROR_SET(err)) { + return; + } + + int result = do_buffer( + unload ? DOBUF_UNLOAD : DOBUF_WIPE, + DOBUF_FIRST, + FORWARD, + buf->handle, + force); + + if (result == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to unload buffer."); + return; + } +} + /// Checks if a buffer is valid. /// /// @note Even if a buffer is valid it may have been unloaded. See |api-buffer| @@ -1108,15 +1083,67 @@ 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)); + } + } + + if (dict.size) { + ADD(rv, DICTIONARY_OBJ(dict)); + } + } + + return rv; +} + /// Returns position for a given extmark id /// /// @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: +/// - limit: Maximum number of marks to return +/// - details Whether to include the details dict /// @param[out] err Error details, if any /// @return (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, Error *err) + Integer id, Dictionary opts, + Error *err) FUNC_API_SINCE(7) { Array rv = ARRAY_DICT_INIT; @@ -1132,13 +1159,31 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer 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; } - ADD(rv, INTEGER_OBJ((Integer)extmark.row)); - ADD(rv, INTEGER_OBJ((Integer)extmark.col)); - return rv; + return extmark_to_array(extmark, false, (bool)details); } /// Gets extmarks in "traversal order" from a |charwise| region defined by @@ -1181,10 +1226,12 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, /// (whose position defines the bound) /// @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) +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; @@ -1198,7 +1245,9 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, 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; @@ -1209,6 +1258,15 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, 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; @@ -1241,16 +1299,11 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, } - ExtmarkArray marks = extmark_get(buf, (uint64_t)ns_id, l_row, l_col, u_row, - u_col, (int64_t)limit, reverse); + 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++) { - Array mark = ARRAY_DICT_INIT; - ExtmarkInfo extmark = kv_A(marks, i); - ADD(mark, INTEGER_OBJ((Integer)extmark.mark_id)); - ADD(mark, INTEGER_OBJ(extmark.row)); - ADD(mark, INTEGER_OBJ(extmark.col)); - ADD(rv, ARRAY_OBJ(mark)); + ADD(rv, ARRAY_OBJ(extmark_to_array(kv_A(marks, i), true, (bool)details))); } kv_destroy(marks); @@ -1260,27 +1313,40 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, /// 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. +/// 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 id Extmark id, or 0 to create new /// @param line Line number where to place the mark /// @param col Column where to place the mark -/// @param opts Optional parameters. Currently not used. +/// @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 inclusive. +/// - hl_group : name of the highlight group used to highlight +/// this mark. +/// - virt_text : virtual text to link to this mark. +/// - 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. /// @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 id, +Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer col, Dictionary opts, Error *err) FUNC_API_SINCE(7) { buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { + api_set_error(err, kErrorTypeValidation, "Invalid buffer id"); return 0; } @@ -1289,11 +1355,6 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id, return 0; } - if (opts.size > 0) { - api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); - return 0; - } - size_t len = 0; if (line < 0 || line > buf->b_ml.ml_line_count) { api_set_error(err, kErrorTypeValidation, "line value outside range"); @@ -1309,18 +1370,138 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id, return 0; } - uint64_t id_num; - if (id >= 0) { - id_num = (uint64_t)id; + bool ephemeral = false; + + uint64_t id = 0; + int line2 = -1, hl_id = 0; + colnr_T col2 = 0; + VirtText virt_text = KV_INITIAL_VALUE; + for (size_t i = 0; i < opts.size; i++) { + String k = opts.items[i].key; + Object *v = &opts.items[i].value; + if (strequal("id", k.data)) { + if (v->type != kObjectTypeInteger || v->data.integer <= 0) { + api_set_error(err, kErrorTypeValidation, + "id is not a positive integer"); + goto error; + } + + id = (uint64_t)v->data.integer; + } else if (strequal("end_line", k.data)) { + if (v->type != kObjectTypeInteger) { + api_set_error(err, kErrorTypeValidation, + "end_line is not an integer"); + goto error; + } + if (v->data.integer < 0 || v->data.integer > buf->b_ml.ml_line_count) { + api_set_error(err, kErrorTypeValidation, + "end_line value outside range"); + goto error; + } + + line2 = (int)v->data.integer; + } else if (strequal("end_col", k.data)) { + if (v->type != kObjectTypeInteger) { + api_set_error(err, kErrorTypeValidation, + "end_col is not an integer"); + goto error; + } + if (v->data.integer < 0 || v->data.integer > MAXCOL) { + api_set_error(err, kErrorTypeValidation, + "end_col value outside range"); + goto error; + } + + col2 = (colnr_T)v->data.integer; + } else if (strequal("hl_group", k.data)) { + String hl_group; + switch (v->type) { + case kObjectTypeString: + hl_group = v->data.string; + hl_id = syn_check_group( + (char_u *)(hl_group.data), + (int)hl_group.size); + break; + case kObjectTypeInteger: + hl_id = (int)v->data.integer; + break; + default: + api_set_error(err, kErrorTypeValidation, + "hl_group is not valid."); + goto error; + } + } else if (strequal("virt_text", k.data)) { + if (v->type != kObjectTypeArray) { + api_set_error(err, kErrorTypeValidation, + "virt_text is not an Array"); + goto error; + } + virt_text = parse_virt_text(v->data.array, err); + if (ERROR_SET(err)) { + goto error; + } + } else if (strequal("ephemeral", k.data)) { + ephemeral = api_coerce_to_bool(*v, "ephemeral", false, err); + if (ERROR_SET(err)) { + goto error; + } + } else { + api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); + goto error; + } + } + + if (col2 >= 0) { + if (line2 >= 0 && line2 < buf->b_ml.ml_line_count) { + len = 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; + } + + // TODO(bfredl): synergize these two branches even more + if (ephemeral && redrawn_win && redrawn_win->w_buffer == buf) { + int attr_id = hl_id > 0 ? syn_id2attr(hl_id) : 0; + VirtText *vt_allocated = NULL; + if (kv_size(virt_text)) { + vt_allocated = xmalloc(sizeof *vt_allocated); + *vt_allocated = virt_text; + } + decorations_add_ephemeral(attr_id, (int)line, (colnr_T)col, + (int)line2, (colnr_T)col2, vt_allocated); } else { - api_set_error(err, kErrorTypeValidation, "Invalid mark id"); - return 0; + if (ephemeral) { + api_set_error(err, kErrorTypeException, "not yet implemented"); + goto error; + } + Decoration *decor = NULL; + if (kv_size(virt_text)) { + decor = xcalloc(1, sizeof(*decor)); + decor->hl_id = hl_id; + decor->virt_text = virt_text; + } else if (hl_id) { + decor = decoration_hl(hl_id); + } + + id = extmark_set(buf, (uint64_t)ns_id, id, (int)line, (colnr_T)col, + line2, col2, decor, kExtmarkNoUndo); } - id_num = extmark_set(buf, (uint64_t)ns_id, id_num, - (int)line, (colnr_T)col, kExtmarkUndo); + return (Integer)id; - return (Integer)id_num; +error: + clear_virttext(&virt_text); + return 0; } /// Removes an extmark. @@ -1358,17 +1539,17 @@ Boolean nvim_buf_del_extmark(Buffer buffer, /// 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 +/// 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 +/// 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. +/// |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 @@ -1412,9 +1593,9 @@ Integer nvim_buf_add_highlight(Buffer buffer, return src_id; } - int hlg_id = 0; + int hl_id = 0; if (hl_group.size > 0) { - hlg_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size); + hl_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size); } else { return src_id; } @@ -1425,10 +1606,10 @@ Integer nvim_buf_add_highlight(Buffer buffer, end_line++; } - extmark_add_decoration(buf, ns_id, hlg_id, - (int)line, (colnr_T)col_start, - end_line, (colnr_T)col_end, - VIRTTEXT_EMPTY); + extmark_set(buf, ns_id, 0, + (int)line, (colnr_T)col_start, + end_line, (colnr_T)col_end, + decoration_hl(hl_id), kExtmarkNoUndo); return src_id; } @@ -1470,7 +1651,7 @@ void nvim_buf_clear_namespace(Buffer buffer, /// Clears highlights and virtual text from namespace and range of lines /// -/// @deprecated use |nvim_buf_clear_namespace|. +/// @deprecated use |nvim_buf_clear_namespace()|. /// /// @param buffer Buffer handle, or 0 for current buffer /// @param ns_id Namespace to clear, or -1 to clear all. @@ -1488,43 +1669,6 @@ void nvim_buf_clear_highlight(Buffer buffer, nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end, err); } -static VirtText parse_virt_text(Array chunks, Error *err) -{ - VirtText virt_text = KV_INITIAL_VALUE; - 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"); - goto free_exit; - } - Array chunk = chunks.items[i].data.array; - if (chunk.size == 0 || chunk.size > 2 - || chunk.items[0].type != kObjectTypeString - || (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) { - api_set_error(err, kErrorTypeValidation, - "Chunk is not an array with one or two strings"); - goto free_exit; - } - - String str = chunk.items[0].data.string; - char *text = transstr(str.size > 0 ? str.data : ""); // allocates - - int hl_id = 0; - if (chunk.size == 2) { - String hl = chunk.items[1].data.string; - if (hl.size > 0) { - hl_id = syn_check_group((char_u *)hl.data, (int)hl.size); - } - } - kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id })); - } - - return virt_text; - -free_exit: - clear_virttext(&virt_text); - return virt_text; -} - /// Set the virtual text (annotation) for a buffer line. /// @@ -1534,11 +1678,11 @@ free_exit: /// begin one cell (|lcs-eol| or space) after the ordinary text. /// /// Namespaces are used to support batch deletion/updating of virtual text. -/// To create a namespace, use |nvim_create_namespace|. Virtual text is -/// cleared using |nvim_buf_clear_namespace|. The same `ns_id` can be used for -/// both virtual text and highlights added by |nvim_buf_add_highlight|, both -/// can then be cleared with a single call to |nvim_buf_clear_namespace|. If the -/// virtual text never will be cleared by an API call, pass `ns_id = -1`. +/// To create a namespace, use |nvim_create_namespace()|. Virtual text is +/// cleared using |nvim_buf_clear_namespace()|. The same `ns_id` can be used for +/// both virtual text and highlights added by |nvim_buf_add_highlight()|, both +/// can then be cleared with a single call to |nvim_buf_clear_namespace()|. If +/// the virtual text never will be cleared by an API call, pass `ns_id = -1`. /// /// As a shorthand, `ns_id = 0` can be used to create a new namespace for the /// virtual text, the allocated id is then returned. @@ -1592,113 +1736,49 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, return src_id; } - extmark_add_decoration(buf, ns_id, 0, - (int)line, 0, -1, -1, - virt_text); + Decoration *decor = xcalloc(1, sizeof(*decor)); + decor->virt_text = virt_text; + + extmark_set(buf, ns_id, 0, (int)line, 0, -1, -1, decor, kExtmarkNoUndo); return src_id; } -/// Get the virtual text (annotation) for a buffer line. -/// -/// The virtual text is returned as list of lists, whereas the inner lists have -/// either one or two elements. The first element is the actual text, the -/// optional second element is the highlight group. +/// call a function with buffer as temporary current buffer /// -/// The format is exactly the same as given to nvim_buf_set_virtual_text(). +/// This temporarily switches current buffer to "buffer". +/// If the current window already shows "buffer", the window is not switched +/// If a window inside the current tabpage (including a float) already shows the +/// buffer One of these windows will be set as current window temporarily. +/// Otherwise a temporary scratch window (calleed the "autocmd window" for +/// historical reasons) will be used. /// -/// If there is no virtual text associated with the given line, an empty list -/// is returned. +/// This is useful e.g. to call vimL functions that only work with the current +/// buffer/window currently, like |termopen()|. /// -/// @param buffer Buffer handle, or 0 for current buffer -/// @param line Line to get the virtual text from (zero-indexed) -/// @param[out] err Error details, if any -/// @return List of virtual text chunks -Array nvim_buf_get_virtual_text(Buffer buffer, Integer line, Error *err) +/// @param buffer Buffer handle, or 0 for current buffer +/// @param fun Function to call inside the buffer (currently lua callable +/// only) +/// @param[out] err Error details, if any +/// @return Return value of function. NB: will deepcopy lua values +/// currently, use upvalues to send lua references in and out. +Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err) FUNC_API_SINCE(7) -{ - Array chunks = ARRAY_DICT_INIT; - - buf_T *buf = find_buffer_by_handle(buffer, err); - if (!buf) { - return chunks; - } - - if (line < 0 || line >= MAXLNUM) { - api_set_error(err, kErrorTypeValidation, "Line number outside range"); - return chunks; - } - - VirtText *virt_text = extmark_find_virttext(buf, (int)line, 0); - - if (!virt_text) { - return chunks; - } - - for (size_t i = 0; i < virt_text->size; i++) { - Array chunk = ARRAY_DICT_INIT; - VirtTextChunk *vtc = &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)); - } - - return chunks; -} - -Integer nvim__buf_add_decoration(Buffer buffer, Integer ns_id, String hl_group, - Integer start_row, Integer start_col, - Integer end_row, Integer end_col, - Array virt_text, - Error *err) + FUNC_API_LUA_ONLY { buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { - return 0; - } - - if (!ns_initialized((uint64_t)ns_id)) { - api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); - return 0; - } - - - if (start_row < 0 || start_row >= MAXLNUM || end_row > MAXCOL) { - api_set_error(err, kErrorTypeValidation, "Line number outside range"); - return 0; - } - - if (start_col < 0 || start_col > MAXCOL || end_col > MAXCOL) { - api_set_error(err, kErrorTypeValidation, "Column value outside range"); - return 0; - } - if (end_row < 0 || end_col < 0) { - end_row = -1; - end_col = -1; - } - - if (start_row >= buf->b_ml.ml_line_count - || end_row >= buf->b_ml.ml_line_count) { - // safety check, we can't add marks outside the range - return 0; - } - - int hlg_id = 0; - if (hl_group.size > 0) { - hlg_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size); + return NIL; } + try_start(); + aco_save_T aco; + aucmd_prepbuf(&aco, (buf_T *)buf); - VirtText vt = parse_virt_text(virt_text, err); - if (ERROR_SET(err)) { - return 0; - } + Array args = ARRAY_DICT_INIT; + Object res = nlua_call_ref(fun, NULL, args, true, err); - uint64_t mark_id = extmark_add_decoration(buf, (uint64_t)ns_id, hlg_id, - (int)start_row, (colnr_T)start_col, - (int)end_row, (colnr_T)end_col, vt); - return (Integer)mark_id; + aucmd_restbuf(&aco); + try_end(err); + return res; } Dictionary nvim__buf_stats(Buffer buffer, Error *err) @@ -1721,6 +1801,7 @@ Dictionary nvim__buf_stats(Buffer buffer, Error *err) // NB: this should be zero at any time API functions are called, // this exists to debug issues PUT(rv, "dirty_bytes", INTEGER_OBJ((Integer)buf->deleted_bytes)); + PUT(rv, "dirty_bytes2", INTEGER_OBJ((Integer)buf->deleted_bytes2)); u_header_T *uhp = NULL; if (buf->b_u_curhead != NULL) { diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index f194b6b474..981d41ae6e 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -15,6 +15,8 @@ #include "nvim/lua/executor.h" #include "nvim/ascii.h" #include "nvim/assert.h" +#include "nvim/charset.h" +#include "nvim/syntax.h" #include "nvim/vim.h" #include "nvim/buffer.h" #include "nvim/window.h" @@ -1198,7 +1200,7 @@ void api_free_object(Object value) break; case kObjectTypeLuaRef: - executor_free_luaref(value.data.luaref); + api_free_luaref(value.data.luaref); break; default: @@ -1579,3 +1581,76 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int return false; } } + +VirtText parse_virt_text(Array chunks, Error *err) +{ + VirtText virt_text = KV_INITIAL_VALUE; + 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"); + goto free_exit; + } + Array chunk = chunks.items[i].data.array; + if (chunk.size == 0 || chunk.size > 2 + || chunk.items[0].type != kObjectTypeString + || (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) { + api_set_error(err, kErrorTypeValidation, + "Chunk is not an array with one or two strings"); + goto free_exit; + } + + String str = chunk.items[0].data.string; + char *text = transstr(str.size > 0 ? str.data : ""); // allocates + + int hl_id = 0; + if (chunk.size == 2) { + String hl = chunk.items[1].data.string; + if (hl.size > 0) { + hl_id = syn_check_group((char_u *)hl.data, (int)hl.size); + } + } + kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id })); + } + + return virt_text; + +free_exit: + clear_virttext(&virt_text); + return virt_text; +} + +/// Force obj to bool. +/// If it fails, returns false and sets err +/// @param obj The object to coerce to a boolean +/// @param what The name of the object, used for error message +/// @param nil_value What to return if the type is nil. +/// @param err Set if there was an error in converting to a bool +bool api_coerce_to_bool( + Object obj, + const char *what, + bool nil_value, + Error *err) +{ + if (obj.type == kObjectTypeBoolean) { + return obj.data.boolean; + } else if (obj.type == kObjectTypeInteger) { + return obj.data.integer; // C semantics: non-zero int is true + } else if (obj.type == kObjectTypeNil) { + return nil_value; // caller decides what NIL (missing retval in lua) means + } else { + api_set_error(err, kErrorTypeValidation, "%s is not an boolean", what); + return false; + } +} + +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)"; +} diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index ab31db39e9..e934d5dc92 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -1,7 +1,7 @@ #ifndef NVIM_API_UI_EVENTS_IN_H #define NVIM_API_UI_EVENTS_IN_H -// This file is not compiled, just parsed for definitons +// This file is not compiled, just parsed for definitions #ifdef INCLUDE_GENERATED_DECLARATIONS # error "don't include this file, include nvim/ui.h" #endif @@ -36,13 +36,15 @@ void set_title(String title) FUNC_API_SINCE(3); void set_icon(String icon) FUNC_API_SINCE(3); +void screenshot(String path) + FUNC_API_SINCE(7) FUNC_API_REMOTE_IMPL; void option_set(String name, Object value) FUNC_API_SINCE(4) FUNC_API_BRIDGE_IMPL; // Stop event is not exported as such, represented by EOF in the msgpack stream. void stop(void) FUNC_API_NOEXPORT; -// First revison of the grid protocol, used by default +// First revision of the grid protocol, used by default void update_fg(Integer fg) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void update_bg(Integer bg) @@ -66,7 +68,7 @@ void set_scroll_region(Integer top, Integer bot, Integer left, Integer right) void scroll(Integer count) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; -// Second revison of the grid protocol, used with ext_linegrid ui option +// Second revision of the grid protocol, used with ext_linegrid ui option void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, Integer cterm_fg, Integer cterm_bg) FUNC_API_SINCE(4) FUNC_API_REMOTE_IMPL; @@ -89,7 +91,7 @@ void grid_scroll(Integer grid, Integer top, Integer bot, void grid_destroy(Integer grid) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; -// For perfomance and simplicity, we use the dense screen representation +// For performance and simplicity, we use the dense screen representation // in internal code, such as compositor and TUI. The remote_ui module will // translate this in to the public grid_line format. void raw_line(Integer grid, Integer row, Integer startcol, diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index d6f95c7a5f..876b052a8e 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -203,10 +203,10 @@ Integer nvim_get_hl_id_by_name(String name) /// flags. This is a blocking call, unlike |nvim_input()|. /// /// On execution error: does not fail, but updates v:errmsg. -// -// If you need to input sequences like <C-o> use nvim_replace_termcodes -// to replace the termcodes and then pass the resulting string to -// nvim_feedkeys. You'll also want to enable escape_csi. +/// +/// If you need to input sequences like <C-o> use |nvim_replace_termcodes| to +/// replace the termcodes and then pass the resulting string to nvim_feedkeys. +/// You'll also want to enable escape_csi. /// /// Example: /// <pre> @@ -475,7 +475,7 @@ Object nvim_execute_lua(String code, Array args, Error *err) FUNC_API_DEPRECATED_SINCE(7) FUNC_API_REMOTE_ONLY { - return executor_exec_lua_api(code, args, err); + return nlua_exec(code, args, err); } /// Execute Lua code. Parameters (if any) are available as `...` inside the @@ -494,7 +494,7 @@ Object nvim_exec_lua(String code, Array args, Error *err) FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY { - return executor_exec_lua_api(code, args, err); + return nlua_exec(code, args, err); } /// Calls a VimL function. @@ -2477,7 +2477,7 @@ Array nvim_get_proc_children(Integer pid, Error *err) Array a = ARRAY_DICT_INIT; ADD(a, INTEGER_OBJ(pid)); String s = cstr_to_string("return vim._os_proc_children(select(1, ...))"); - Object o = nvim_exec_lua(s, a, err); + Object o = nlua_exec(s, a, err); api_free_string(s); api_free_array(a); if (o.type == kObjectTypeArray) { @@ -2523,7 +2523,7 @@ Object nvim_get_proc(Integer pid, Error *err) Array a = ARRAY_DICT_INIT; ADD(a, INTEGER_OBJ(pid)); String s = cstr_to_string("return vim._os_proc_info(select(1, ...))"); - Object o = nvim_exec_lua(s, a, err); + Object o = nlua_exec(s, a, err); api_free_string(s); api_free_array(a); if (o.type == kObjectTypeArray && o.data.array.size == 0) { @@ -2604,26 +2604,135 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err) return ret; } -/// Set attrs in nvim__buf_set_lua_hl callbacks -/// -/// TODO(bfredl): This is rather pedestrian. The final -/// interface should probably be derived from a reformed -/// bufhl/virttext interface with full support for multi-line -/// ranges etc -void nvim__put_attr(Integer id, Integer start_row, Integer start_col, - Integer end_row, Integer end_col) - FUNC_API_LUA_ONLY +void nvim__screenshot(String path) + FUNC_API_FAST { - if (!lua_attr_active) { - return; - } - if (id == 0 || syn_get_final_id((int)id) == 0) { - return; + ui_call_screenshot(path); +} + +static DecorationProvider *get_provider(NS ns_id, bool force) +{ + ssize_t i; + for (i = 0; i < (ssize_t)kv_size(decoration_providers); i++) { + DecorationProvider *item = &kv_A(decoration_providers, i); + if (item->ns_id == ns_id) { + return item; + } else if (item->ns_id > ns_id) { + break; + } } - int attr = syn_id2attr((int)id); - if (attr == 0) { - return; + + if (!force) { + return NULL; + } + + for (ssize_t j = (ssize_t)kv_size(decoration_providers)-1; j >= i; j++) { + // allocates if needed: + (void)kv_a(decoration_providers, (size_t)j+1); + kv_A(decoration_providers, (size_t)j+1) = kv_A(decoration_providers, j); + } + DecorationProvider *item = &kv_a(decoration_providers, (size_t)i); + *item = DECORATION_PROVIDER_INIT(ns_id); + + return item; +} + +static void clear_provider(DecorationProvider *p) +{ + NLUA_CLEAR_REF(p->redraw_start); + NLUA_CLEAR_REF(p->redraw_buf); + NLUA_CLEAR_REF(p->redraw_win); + NLUA_CLEAR_REF(p->redraw_line); + NLUA_CLEAR_REF(p->redraw_end); + p->active = false; +} + +/// 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. +/// Similarily, 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 decorations 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 +{ + DecorationProvider *p = get_provider((NS)ns_id, true); + clear_provider(p); + + // regardless of what happens, it seems good idea to redraw + redraw_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 }, + { 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; 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; + } } - decorations_add_luahl_attr(attr, (int)start_row, (colnr_T)start_col, - (int)end_row, (colnr_T)end_col); + + p->active = true; + return; +error: + clear_provider(p); } |