From 11142f6ffe46da1f20c570333a2c05b6e3015f56 Mon Sep 17 00:00:00 2001 From: Michael Lingelbach Date: Sat, 8 Jan 2022 08:14:24 -0800 Subject: feat(extmarks): add strict option The strict option, when set to false, allows placing extmarks on invalid row and column values greater than the maximum buffer row or line column respectively. This allows for nvim_buf_set_extmark to be a drop-in replacement for nvim_buf_set_highlight. --- src/nvim/api/extmark.c | 13 ++++++++++--- src/nvim/api/keysets.lua | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 742b953c2a..04456eee01 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -404,6 +404,10 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// for left). Defaults to false. /// - priority: a priority value for the highlight group. For /// example treesitter highlighting uses a value of 100. +/// - strict: boolean that indicates extmark should be placed +/// even if the line or column value is past the end of the +/// buffer or end of the line respectively. Defaults to true. +/// /// @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, @@ -596,7 +600,10 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer bool ephemeral = false; OPTION_TO_BOOL(ephemeral, ephemeral, false); - if (line < 0 || line > buf->b_ml.ml_line_count) { + bool strict = true; + OPTION_TO_BOOL(strict, strict, true); + + if (line < 0 || (line > buf->b_ml.ml_line_count && strict)) { api_set_error(err, kErrorTypeValidation, "line value outside range"); goto error; } else if (line < buf->b_ml.ml_line_count) { @@ -605,7 +612,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer if (col == -1) { col = (Integer)len; - } else if (col < -1 || col > (Integer)len) { + } else if (col < -1 || (col > (Integer)len && strict)) { api_set_error(err, kErrorTypeValidation, "col value outside range"); goto error; } @@ -620,7 +627,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer // reuse len from before line2 = (int)line; } - if (col2 > (Integer)len) { + if (col2 > (Integer)len && strict) { api_set_error(err, kErrorTypeValidation, "end_col value outside range"); goto error; } diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index 97ee885ff6..a385cfc64f 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -21,6 +21,7 @@ return { "virt_lines"; "virt_lines_above"; "virt_lines_leftcol"; + "strict"; }; keymap = { "noremap"; -- cgit From d0d4fb792f2b3ac40a00b7929c8653968821e5ae Mon Sep 17 00:00:00 2001 From: Michael Lingelbach Date: Wed, 12 Jan 2022 12:01:29 -0800 Subject: Address 'review' --- src/nvim/api/extmark.c | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 04456eee01..ab97ff114c 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -404,8 +404,8 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// for left). Defaults to false. /// - priority: a priority value for the highlight group. For /// example treesitter highlighting uses a value of 100. -/// - strict: boolean that indicates extmark should be placed -/// even if the line or column value is past the end of the +/// - strict: boolean that indicates extmark should not be placed +/// if the line or column value is past the end of the /// buffer or end of the line respectively. Defaults to true. /// /// @param[out] err Error details, if any @@ -603,16 +603,31 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer bool strict = true; OPTION_TO_BOOL(strict, strict, true); - if (line < 0 || (line > buf->b_ml.ml_line_count && strict)) { + if (line < 0 ) { api_set_error(err, kErrorTypeValidation, "line value outside range"); goto error; + } + else if (line > buf->b_ml.ml_line_count) { + if (strict) { + api_set_error(err, kErrorTypeValidation, "line value outside range"); + goto error; + } else { + 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)); } if (col == -1) { col = (Integer)len; - } else if (col < -1 || (col > (Integer)len && strict)) { + } else if (col > (Integer)len) { + if (strict) { + api_set_error(err, kErrorTypeValidation, "col value outside range"); + goto error; + } else { + col = (Integer)len; + } + } else if (col < -1 ) { api_set_error(err, kErrorTypeValidation, "col value outside range"); goto error; } @@ -627,9 +642,13 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer // reuse len from before line2 = (int)line; } - if (col2 > (Integer)len && strict) { - api_set_error(err, kErrorTypeValidation, "end_col value outside range"); - goto error; + if (col2 > (Integer)len) { + if (strict) { + api_set_error(err, kErrorTypeValidation, "end_col value outside range"); + goto error; + } else { + col2 = (int)len; + } } } else if (line2 >= 0) { col2 = 0; -- cgit From 2fd8330628e007704c28043089a47ec75093bcb8 Mon Sep 17 00:00:00 2001 From: Michael Lingelbach Date: Wed, 12 Jan 2022 12:46:31 -0800 Subject: Address review r2 --- src/nvim/api/extmark.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index ab97ff114c..5624ae83dd 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -603,11 +603,10 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer bool strict = true; OPTION_TO_BOOL(strict, strict, true); - if (line < 0 ) { + if (line < 0) { api_set_error(err, kErrorTypeValidation, "line value outside range"); goto error; - } - else if (line > buf->b_ml.ml_line_count) { + } else if (line > buf->b_ml.ml_line_count) { if (strict) { api_set_error(err, kErrorTypeValidation, "line value outside range"); goto error; @@ -627,7 +626,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer } else { col = (Integer)len; } - } else if (col < -1 ) { + } else if (col < -1) { api_set_error(err, kErrorTypeValidation, "col value outside range"); goto error; } -- cgit From c8656e44d85502a1733df839b3cb3e8f239c5505 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 9 Jan 2022 09:02:02 +0800 Subject: feat(api, lua): more conversions between LuaRef and Vim Funcref --- src/nvim/api/private/converter.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c index 36da6c13a9..e370c0d4d4 100644 --- a/src/nvim/api/private/converter.c +++ b/src/nvim/api/private/converter.c @@ -10,6 +10,9 @@ #include "nvim/api/private/helpers.h" #include "nvim/assert.h" #include "nvim/eval/typval.h" +#include "nvim/eval/userfunc.h" +#include "nvim/lua/converter.h" +#include "nvim/lua/executor.h" /// Helper structure for vim_to_object typedef struct { @@ -228,6 +231,13 @@ static inline void typval_encode_dict_end(EncodedData *const edata) /// @return The converted value Object vim_to_object(typval_T *obj) { + if (obj->v_type == VAR_FUNC) { + ufunc_T *fp = find_func(obj->vval.v_string); + if (fp->uf_cb == nlua_CFunction_func_call) { + LuaRef ref = api_new_luaref(((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref); + return LUAREF_OBJ(ref); + } + } EncodedData edata; kvi_init(edata.stack); const int evo_ret = encode_vim_to_object(&edata, obj, @@ -340,6 +350,16 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) tv->vval.v_dict = dict; break; } + + case kObjectTypeLuaRef: { + LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); + state->lua_callable.func_ref = api_new_luaref(obj.data.luaref); + char_u *name = register_cfunc(&nlua_CFunction_func_call, &nlua_CFunction_func_free, state); + tv->v_type = VAR_FUNC; + tv->vval.v_string = vim_strsave(name); + break; + } + default: abort(); } -- cgit From facd07bcf76b3c0b41eb046fce6b9d5bad62b89e Mon Sep 17 00:00:00 2001 From: Michael Lingelbach Date: Sat, 15 Jan 2022 08:37:44 -0800 Subject: Address review r3 --- src/nvim/api/extmark.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 5624ae83dd..58be8f2807 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -445,9 +445,18 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer 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; \ + } + + bool strict = true; + OPTION_TO_BOOL(strict, strict, true); + if (opts->end_row.type == kObjectTypeInteger) { Integer val = opts->end_row.data.integer; - if (val < 0 || val > buf->b_ml.ml_line_count) { + if (val < 0 || (val > buf->b_ml.ml_line_count && strict)) { api_set_error(err, kErrorTypeValidation, "end_row value outside range"); goto error; } else { @@ -516,12 +525,6 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer 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); @@ -600,9 +603,6 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer bool ephemeral = false; OPTION_TO_BOOL(ephemeral, ephemeral, false); - bool strict = true; - OPTION_TO_BOOL(strict, strict, true); - if (line < 0) { api_set_error(err, kErrorTypeValidation, "line value outside range"); goto error; -- cgit From 95ab979fde66d8f9f97fceb943bfe9422739a0f8 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Mon, 25 Oct 2021 21:51:29 +0200 Subject: refactor(extmarks): use a more efficient representation marktree.c was originally constructed as a "generic" datatype, to make the prototyping of its internal logic as simple as possible and also as the usecases for various kinds of extmarks/decorations was not yet decided. As a consequence of this, various extra indirections and allocations was needed to use marktree to implement extmarks (ns/id pairs) and decorations of different kinds (some which is just a single highlight id, other an allocated list of virtual text/lines) This change removes a lot of indirection, by making Marktree specialized for the usecase. In particular, the namespace id and mark id is stored directly, instead of the 64-bit global id particular to the Marktree struct. This removes the two maps needed to convert between global and per-ns ids. Also, "small" decorations are stored inline, i.e. those who doesn't refer to external heap memory anyway. That is highlights (with priority+flags) are stored inline, while virtual text, which anyway occurs a lot of heap allocations, do not. (previously a hack was used to elide heap allocations for highlights with standard prio+flags) TODO(bfredl): the functionaltest-lua CI version of gcc is having severe issues with uint16_t bitfields, so splitting up compound assignments and redundant casts are needed. Clean this up once we switch to a working compiler version. --- src/nvim/api/deprecated.c | 11 ++--- src/nvim/api/extmark.c | 95 ++++++++++++++++++------------------------ src/nvim/api/private/helpers.c | 2 +- 3 files changed, 47 insertions(+), 61 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index 76b699800e..18243fec2b 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -130,7 +130,7 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A return 0; } - uint64_t ns_id = src2ns(&src_id); + uint32_t ns_id = src2ns(&src_id); int width; VirtText virt_text = parse_virt_text(chunks, err, &width); @@ -148,11 +148,12 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A return src_id; } - Decoration *decor = xcalloc(1, sizeof(*decor)); - decor->virt_text = virt_text; - decor->virt_text_width = width; + Decoration decor = DECORATION_INIT; + decor.virt_text = virt_text; + decor.virt_text_width = width; + decor.priority = 0; - extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, decor, true, + extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, &decor, true, false, kExtmarkNoUndo); return src_id; } diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 742b953c2a..a37bbae668 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -85,12 +85,12 @@ const char *describe_ns(NS ns_id) } // Is the Namespace in use? -static bool ns_initialized(uint64_t ns) +static bool ns_initialized(uint32_t ns) { if (ns < 1) { return false; } - return ns < (uint64_t)next_namespace_id; + return ns < (uint32_t)next_namespace_id; } @@ -111,27 +111,27 @@ static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict) 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)); + 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)))); } - PUT(dict, "virt_text", ARRAY_OBJ(chunks)); + ADD(chunks, ARRAY_OBJ(chunk)); } + PUT(dict, "virt_text", ARRAY_OBJ(chunks)); + } + if (decor->hl_id || kv_size(decor->virt_text)) { PUT(dict, "priority", INTEGER_OBJ(decor->priority)); } @@ -166,7 +166,7 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, return rv; } - if (!ns_initialized((uint64_t)ns_id)) { + if (!ns_initialized((uint32_t)ns_id)) { api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); return rv; } @@ -191,7 +191,7 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, } - ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id); + ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id); if (extmark.row < 0) { return rv; } @@ -252,7 +252,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e return rv; } - if (!ns_initialized((uint64_t)ns_id)) { + if (!ns_initialized((uint32_t)ns_id)) { api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); return rv; } @@ -310,7 +310,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e } - ExtmarkInfoArray marks = extmark_get(buf, (uint64_t)ns_id, l_row, l_col, + ExtmarkInfoArray marks = extmark_get(buf, (uint32_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++) { @@ -417,14 +417,14 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer goto error; } - if (!ns_initialized((uint64_t)ns_id)) { + if (!ns_initialized((uint32_t)ns_id)) { api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); goto error; } - uint64_t id = 0; + uint32_t id = 0; if (opts->id.type == kObjectTypeInteger && opts->id.data.integer > 0) { - id = (uint64_t)opts->id.data.integer; + 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; @@ -628,20 +628,6 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer 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) { @@ -652,12 +638,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer 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))); - } + extmark_set(buf, (uint32_t)ns_id, &id, (int)line, (colnr_T)col, line2, col2, + &decor, right_gravity, end_right_gravity, kExtmarkNoUndo); } return (Integer)id; @@ -682,23 +664,23 @@ Boolean nvim_buf_del_extmark(Buffer buffer, Integer ns_id, Integer id, Error *er if (!buf) { return false; } - if (!ns_initialized((uint64_t)ns_id)) { + if (!ns_initialized((uint32_t)ns_id)) { api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); return false; } - return extmark_del(buf, (uint64_t)ns_id, (uint64_t)id); + return extmark_del(buf, (uint32_t)ns_id, (uint32_t)id); } -uint64_t src2ns(Integer *src_id) +uint32_t src2ns(Integer *src_id) { if (*src_id == 0) { *src_id = nvim_create_namespace((String)STRING_INIT); } if (*src_id < 0) { - return UINT64_MAX; + return (((uint32_t)1) << 31) - 1; } else { - return (uint64_t)(*src_id); + return (uint32_t)(*src_id); } } @@ -753,7 +735,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In col_end = MAXCOL; } - uint64_t ns = src2ns(&ns_id); + uint32_t ns = src2ns(&ns_id); if (!(line < buf->b_ml.ml_line_count)) { // safety check, we can't add marks outside the range @@ -773,10 +755,13 @@ 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; + extmark_set(buf, ns, NULL, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end, - decor_hl(hl_id), true, false, kExtmarkNoUndo); + &decor, true, false, kExtmarkNoUndo); return ns_id; } @@ -808,7 +793,7 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start, if (line_end < 0 || line_end > MAXLNUM) { line_end = MAXLNUM; } - extmark_clear(buf, (ns_id < 0 ? 0 : (uint64_t)ns_id), + extmark_clear(buf, (ns_id < 0 ? 0 : (uint32_t)ns_id), (int)line_start, 0, (int)line_end-1, MAXCOL); } diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index f9603acbda..f777fa1d27 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1141,7 +1141,7 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int return false; } - ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id); + ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id); if (extmark.row >= 0) { *row = extmark.row; *col = extmark.col; -- cgit From be15ac06badbea6b11390ad7d9c2ddd4aea73480 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 16 Jan 2022 18:44:28 +0800 Subject: feat(statusline): support multibyte fillchar This includes a partial port of Vim patch 8.2.2569 and some changes to nvim_eval_statusline() to allow a multibyte fillchar. Literally every line of C code touched by that patch has been refactored in Nvim, and that patch contains some irrelevant foldcolumn tests I'm not sure how to port (as Nvim's foldcolumn behavior has diverged from Vim's). --- src/nvim/api/vim.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 59db12f2c0..7ef8faa67e 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -19,6 +19,7 @@ #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/buffer_defs.h" +#include "nvim/charset.h" #include "nvim/context.h" #include "nvim/decoration.h" #include "nvim/edit.h" @@ -2234,7 +2235,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * Dictionary result = ARRAY_DICT_INIT; int maxwidth; - char fillchar = 0; + int fillchar = 0; Window window = 0; bool use_tabline = false; bool highlights = false; @@ -2249,12 +2250,12 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * } if (HAS_KEY(opts->fillchar)) { - if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size > 1) { - api_set_error(err, kErrorTypeValidation, "fillchar must be an ASCII character"); + if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size == 0 + || char2cells(fillchar = utf_ptr2char((char_u *)opts->fillchar.data.string.data)) != 1 + || (size_t)utf_char2len(fillchar) != opts->fillchar.data.string.size) { + api_set_error(err, kErrorTypeValidation, "fillchar must be a single-width character"); return result; } - - fillchar = opts->fillchar.data.string.data[0]; } if (HAS_KEY(opts->highlights)) { @@ -2285,7 +2286,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * if (fillchar == 0) { int attr; - fillchar = (char)fillchar_status(&attr, wp); + fillchar = fillchar_status(&attr, wp); } } @@ -2313,7 +2314,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * sizeof(buf), (char_u *)str.data, false, - (char_u)fillchar, + fillchar, maxwidth, hltab_ptr, NULL); -- cgit From 0c541ab1f661f17aef317232483d1464ad13ef36 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Wed, 19 Jan 2022 21:42:10 -0500 Subject: refactor(coverity/345582): assert fp is non-NULL Since we already have a typval, we know the lookup will succeed. --- src/nvim/api/private/converter.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c index e370c0d4d4..3d4ff202fe 100644 --- a/src/nvim/api/private/converter.c +++ b/src/nvim/api/private/converter.c @@ -233,6 +233,7 @@ Object vim_to_object(typval_T *obj) { if (obj->v_type == VAR_FUNC) { ufunc_T *fp = find_func(obj->vval.v_string); + assert(fp != NULL); if (fp->uf_cb == nlua_CFunction_func_call) { LuaRef ref = api_new_luaref(((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref); return LUAREF_OBJ(ref); -- cgit From e850a929864508864ee52abcbac9579a6a2d2f28 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Wed, 19 Jan 2022 21:53:49 -0500 Subject: fix(coverity/340720): error if nvim_eval_statusline given invalid winid --- src/nvim/api/vim.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 59db12f2c0..88a3577de3 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -2281,6 +2281,11 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * fillchar = ' '; } else { wp = find_window_by_handle(window, err); + + if (wp == NULL) { + api_set_error(err, kErrorTypeException, "unknown winid %d", window); + return result; + } ewp = wp; if (fillchar == 0) { -- cgit From 5971b863383160d9bf744a9789c1fe5ca62b55a4 Mon Sep 17 00:00:00 2001 From: notomo Date: Thu, 13 Jan 2022 23:26:57 +0900 Subject: feat(api): expose extmark more details --- src/nvim/api/extmark.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 370f7fb47e..80bd88c4ee 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -115,7 +115,12 @@ static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict) 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 (kv_size(decor->virt_text)) { Array chunks = ARRAY_DICT_INIT; for (size_t i = 0; i < decor->virt_text.size; i++) { @@ -129,6 +134,36 @@ static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict) 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 (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 (decor->hl_id || kv_size(decor->virt_text)) { -- cgit From 6e69a3c3e79fd78b31753343213e68e73b0048c4 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 21 Jan 2022 18:08:56 +0800 Subject: refactor: remove CSI unescaping and clean up related names and comments --- src/nvim/api/vim.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 59db12f2c0..cc622a00dc 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -187,21 +187,23 @@ static void on_redraw_event(void **argv) /// On execution error: does not fail, but updates v:errmsg. /// /// To input sequences like use |nvim_replace_termcodes()| (typically -/// with escape_csi=true) to replace |keycodes|, then pass the result to +/// with escape_ks=false) to replace |keycodes|, then pass the result to /// nvim_feedkeys(). /// /// Example: ///
 ///     :let key = nvim_replace_termcodes("", v:true, v:false, v:true)
-///     :call nvim_feedkeys(key, 'n', v:true)
+///     :call nvim_feedkeys(key, 'n', v:false)
 /// 
/// /// @param keys to be typed /// @param mode behavior flags, see |feedkeys()| -/// @param escape_csi If true, escape K_SPECIAL/CSI bytes in `keys` +/// @param escape_ks If true, escape K_SPECIAL bytes in `keys` +/// This should be false if you already used +/// |nvim_replace_termcodes()|, and true otherwise. /// @see feedkeys() -/// @see vim_strsave_escape_csi -void nvim_feedkeys(String keys, String mode, Boolean escape_csi) +/// @see vim_strsave_escape_ks +void nvim_feedkeys(String keys, String mode, Boolean escape_ks) FUNC_API_SINCE(1) { bool remap = true; @@ -232,10 +234,10 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi) } char *keys_esc; - if (escape_csi) { - // Need to escape K_SPECIAL and CSI before putting the string in the + if (escape_ks) { + // Need to escape K_SPECIAL before putting the string in the // typeahead buffer. - keys_esc = (char *)vim_strsave_escape_csi((char_u *)keys.data); + keys_esc = (char *)vim_strsave_escape_ks((char_u *)keys.data); } else { keys_esc = keys.data; } @@ -245,7 +247,7 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi) typebuf_was_filled = true; } - if (escape_csi) { + if (escape_ks) { xfree(keys_esc); } -- cgit From 8e84d1b93043f33d997627f99a181159aa66434d Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 21 Jan 2022 18:18:18 +0800 Subject: vim-patch:8.2.3584: "verbose set efm" reports location of the :compiler command Problem: "verbose set efm" reports the location of the :compiler command. (Gary Johnson) Solution: Add the "-keepscript" argument to :command and use it when defining CompilerSet. https://github.com/vim/vim/commit/58ef8a31d7087d495ab1582be5b7a22796ac2451 --- src/nvim/api/keysets.lua | 1 + src/nvim/api/private/helpers.c | 6 ++++++ 2 files changed, 7 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index a385cfc64f..7d521bbf25 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -44,6 +44,7 @@ return { "count"; "desc"; "force"; + "keepscript"; "nargs"; "range"; "register"; diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index f777fa1d27..f540f8f7ee 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1510,6 +1510,12 @@ void add_user_command(String name, Object command, Dict(user_command) *opts, int goto err; } + if (api_object_to_bool(opts->keepscript, "keepscript", false, err)) { + argt |= EX_KEEPSCRIPT; + } else if (ERROR_SET(err)) { + goto err; + } + bool force = api_object_to_bool(opts->force, "force", true, err); if (ERROR_SET(err)) { goto err; -- cgit From 3d9ae9d2dad88a4e2c2263dc7e256657842244c0 Mon Sep 17 00:00:00 2001 From: notomo Date: Mon, 24 Jan 2022 09:52:13 +0900 Subject: feat(api): expose extmark right_gravity and end_right_gravity --- src/nvim/api/extmark.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 80bd88c4ee..3a968f07ab 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -106,9 +106,12 @@ static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict) if (add_dict) { Dictionary dict = ARRAY_DICT_INIT; + PUT(dict, "right_gravity", BOOLEAN_OBJ(extmark.right_gravity)); + 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)); } Decoration *decor = &extmark.decor; -- cgit From 2793fcae0ac2aef14c8f22636d579c68a97c0851 Mon Sep 17 00:00:00 2001 From: Dundar Göc Date: Fri, 28 Jan 2022 22:54:03 +0100 Subject: vim-patch:8.2.4241: some type casts are redundant Problem: Some type casts are redundant. Solution: Remove the type casts. (closes vim/vim#9643) https://github.com/vim/vim/commit/420fabcd4ffeaf79082a6e43db91e1d363f88f27 This is not a literal port but an equivalent one. --- src/nvim/api/private/helpers.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index f540f8f7ee..4ddd90f3c9 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -139,10 +139,10 @@ bool try_end(Error *err) got_int = false; } else if (msg_list != NULL && *msg_list != NULL) { int should_free; - char *msg = (char *)get_exception_string(*msg_list, - ET_ERROR, - NULL, - &should_free); + char *msg = get_exception_string(*msg_list, + ET_ERROR, + NULL, + &should_free); api_set_error(err, kErrorTypeException, "%s", msg); free_global_msglist(); @@ -720,7 +720,6 @@ fail_and_free: xfree(parsed_args.rhs); xfree(parsed_args.orig_rhs); XFREE_CLEAR(parsed_args.desc); - return; } /// Collects `n` buffer lines into array `l`, optionally replacing newlines -- cgit From 4aa0cdd3aa117e032325edeb755107acd4ecbf84 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 24 Jan 2022 09:36:15 +0000 Subject: feat(highlight): ns=0 to set :highlight namespace Passing ns=0 to nvim_set_hl will alter the `:highlight` namespace. --- src/nvim/api/vim.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 7c194935ce..af743eb63c 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -31,6 +31,7 @@ #include "nvim/fileio.h" #include "nvim/getchar.h" #include "nvim/highlight.h" +#include "nvim/highlight_defs.h" #include "nvim/lua/executor.h" #include "nvim/mark.h" #include "nvim/memline.h" @@ -121,7 +122,9 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) /// Set a highlight group. /// -/// @param ns_id number of namespace for this highlight +/// @param ns_id number of namespace for this highlight. Use value 0 +/// to set a highlight group in the global (`:highlight`) +/// namespace. /// @param name highlight group name, like ErrorMsg /// @param val highlight definition map, like |nvim_get_hl_by_name|. /// in addition the following keys are also recognized: @@ -135,18 +138,23 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) /// same as attributes of gui color /// @param[out] err Error details, if any /// -/// TODO: ns_id = 0, should modify :highlight namespace -/// TODO val should take update vs reset flag +// TODO(bfredl): val should take update vs reset flag void nvim_set_hl(Integer ns_id, String name, Dictionary val, Error *err) FUNC_API_SINCE(7) { int hl_id = syn_check_group(name.data, (int)name.size); int link_id = -1; - HlAttrs attrs = dict2hlattrs(val, true, &link_id, err); + HlAttrNames *names = NULL; // Only used when setting global namespace + if (ns_id == 0) { + names = xmalloc(sizeof(*names)); + *names = HLATTRNAMES_INIT; + } + HlAttrs attrs = dict2hlattrs(val, true, &link_id, names, err); if (!ERROR_SET(err)) { - ns_hl_def((NS)ns_id, hl_id, attrs, link_id); + ns_hl_def((NS)ns_id, hl_id, attrs, link_id, names); } + xfree(names); } /// Set active namespace for highlights. -- cgit From 0bafa44f8b9c4ad2a1cf5233bc207cba522a5dfe Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Wed, 2 Feb 2022 22:01:52 +0100 Subject: refactor(api): use a keyset for highlight dicts --- src/nvim/api/keysets.lua | 27 +++++++++++++++++++++++++++ src/nvim/api/vim.c | 12 +++--------- 2 files changed, 30 insertions(+), 9 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index 7d521bbf25..075e2c48d2 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -78,5 +78,32 @@ return { option = { "scope"; }; + highlight = { + "bold"; + "standout"; + "underline"; + "undercurl"; + "italic"; + "reverse"; + "default"; + "global"; + "cterm"; + "foreground"; "fg"; + "background"; "bg"; + "ctermfg"; + "ctermbg"; + "special"; "sp"; + "link"; + "fallback"; + "temp"; + }; + highlight_cterm = { + "bold"; + "standout"; + "underline"; + "undercurl"; + "italic"; + "reverse"; + }; } diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index e9182fde7f..ada041bab2 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -140,22 +140,16 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) /// @param[out] err Error details, if any /// // TODO(bfredl): val should take update vs reset flag -void nvim_set_hl(Integer ns_id, String name, Dictionary val, Error *err) +void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err) FUNC_API_SINCE(7) { int hl_id = syn_check_group(name.data, (int)name.size); int link_id = -1; - HlAttrNames *names = NULL; // Only used when setting global namespace - if (ns_id == 0) { - names = xmalloc(sizeof(*names)); - *names = HLATTRNAMES_INIT; - } - HlAttrs attrs = dict2hlattrs(val, true, &link_id, names, err); + HlAttrs attrs = dict2hlattrs(val, true, &link_id, err); if (!ERROR_SET(err)) { - ns_hl_def((NS)ns_id, hl_id, attrs, link_id, names); + ns_hl_def((NS)ns_id, hl_id, attrs, link_id, val); } - xfree(names); } /// Set active namespace for highlights. -- cgit From f326c9a77da3aef052ce6af0f851344f2479c844 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Thu, 6 Jan 2022 13:48:37 +0000 Subject: vim-patch:8.2.4018: ml_get error when win_execute redraws with Visual selection Problem: ml_get error when win_execute redraws with Visual selection. Solution: Disable Visual area temporarily. (closes vim/vim#9479) https://github.com/vim/vim/commit/18f4740f043b353abe47b7a00131317052457686 {switch_to/restore}_win_for_buf is N/A (marked as such in v8.0.0860; currently only used in Vim's if_py). Add a modeline to test_execute_func.vim. --- src/nvim/api/private/helpers.c | 10 ++++------ src/nvim/api/window.c | 9 +++------ 2 files changed, 7 insertions(+), 12 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 4ddd90f3c9..ddcfff0097 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -989,18 +989,16 @@ Object copy_object(Object obj) static void set_option_value_for(char *key, int numval, char *stringval, int opt_flags, int opt_type, void *from, Error *err) { - win_T *save_curwin = NULL; - tabpage_T *save_curtab = NULL; + switchwin_T switchwin; aco_save_T aco; try_start(); switch (opt_type) { case SREQ_WIN: - if (switch_win_noblock(&save_curwin, &save_curtab, (win_T *)from, - win_find_tabpage((win_T *)from), true) + if (switch_win_noblock(&switchwin, (win_T *)from, win_find_tabpage((win_T *)from), true) == FAIL) { - restore_win_noblock(save_curwin, save_curtab, true); + restore_win_noblock(&switchwin, true); if (try_end(err)) { return; } @@ -1010,7 +1008,7 @@ static void set_option_value_for(char *key, int numval, char *stringval, int opt return; } set_option_value_err(key, numval, stringval, opt_flags, err); - restore_win_noblock(save_curwin, save_curtab, true); + restore_win_noblock(&switchwin, true); break; case SREQ_BUF: aucmd_prepbuf(&aco, (buf_T *)from); diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 907306da7b..a762673fbf 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -455,17 +455,14 @@ Object nvim_win_call(Window window, LuaRef fun, Error *err) } tabpage_T *tabpage = win_find_tabpage(win); - win_T *save_curwin; - tabpage_T *save_curtab; - try_start(); Object res = OBJECT_INIT; - if (switch_win_noblock(&save_curwin, &save_curtab, win, tabpage, true) == - OK) { + switchwin_T switchwin; + if (switch_win_noblock(&switchwin, win, tabpage, true) == OK) { Array args = ARRAY_DICT_INIT; res = nlua_call_ref(fun, NULL, args, true, err); } - restore_win_noblock(save_curwin, save_curtab, true); + restore_win_noblock(&switchwin, true); try_end(err); return res; } -- cgit From 452b46fcf79de52317e2c41adb083d461a93ace5 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sat, 8 Jan 2022 12:27:35 +0000 Subject: fix(api/nvim_win_call): share common win_execute logic We have to be sure that the bugs fixed in the previous patches also apply to nvim_win_call. Checking v8.1.2124 and v8.2.4026 is especially important as these patches were only applied to win_execute, but nvim_win_call is also affected by the same bugs. A lot of win_execute's logic can be shared with nvim_win_call, so factor it out into a common macro to reduce the possibility of this happening again. --- src/nvim/api/window.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index a762673fbf..9fd4de4bb2 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -457,12 +457,10 @@ Object nvim_win_call(Window window, LuaRef fun, Error *err) try_start(); Object res = OBJECT_INIT; - switchwin_T switchwin; - if (switch_win_noblock(&switchwin, win, tabpage, true) == OK) { + WIN_EXECUTE(win, tabpage, { Array args = ARRAY_DICT_INIT; res = nlua_call_ref(fun, NULL, args, true, err); - } - restore_win_noblock(&switchwin, true); + }); try_end(err); return res; } -- cgit From 23c3f7f572d3a1f3816a9a9ae1b3632c53856923 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 10 Feb 2022 09:41:25 +0800 Subject: fix(api): use changedir_func() in nvim_set_current_dir() Co-Authored-By: smolck <46855713+smolck@users.noreply.github.com> --- src/nvim/api/vim.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index ada041bab2..f7c55344f5 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -31,6 +31,7 @@ #include "nvim/file_search.h" #include "nvim/fileio.h" #include "nvim/getchar.h" +#include "nvim/globals.h" #include "nvim/highlight.h" #include "nvim/highlight_defs.h" #include "nvim/lua/executor.h" @@ -545,20 +546,19 @@ void nvim_set_current_dir(String dir, Error *err) return; } - char string[MAXPATHL]; + char_u string[MAXPATHL]; memcpy(string, dir.data, dir.size); string[dir.size] = NUL; try_start(); - if (vim_chdir((char_u *)string)) { + if (!changedir_func(string, kCdScopeGlobal)) { if (!try_end(err)) { api_set_error(err, kErrorTypeException, "Failed to change directory"); } return; } - post_chdir(kCdScopeGlobal, true); try_end(err); } -- cgit From 85ae04dbfd405343b10c400d40e95334a44cc978 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Sat, 17 Apr 2021 17:33:59 -0400 Subject: fix: close floating windows when calling win_close() --- src/nvim/api/window.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 9fd4de4bb2..fc7823a070 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -395,7 +395,7 @@ void nvim_win_hide(Window window, Error *err) TryState tstate; try_enter(&tstate); if (tabpage == curtab) { - win_close(win, false); + win_close(win, false, false); } else { win_close_othertab(win, false, tabpage); } -- cgit From cb18545253259af339957316ab8361fb0cca48e5 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 12 Feb 2022 11:25:01 +0100 Subject: feat(api): add strikethrough, nocombine to set_hl --- src/nvim/api/keysets.lua | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index 075e2c48d2..f6dce1905e 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -81,10 +81,12 @@ return { highlight = { "bold"; "standout"; + "strikethrough"; "underline"; "undercurl"; "italic"; "reverse"; + "nocombine"; "default"; "global"; "cterm"; @@ -100,10 +102,12 @@ return { highlight_cterm = { "bold"; "standout"; + "strikethrough"; "underline"; "undercurl"; "italic"; "reverse"; + "nocombine"; }; } -- cgit From f292dd2126f8dacd6446799ac750ab368b852f81 Mon Sep 17 00:00:00 2001 From: shadmansaleh <13149513+shadmansaleh@users.noreply.github.com> Date: Sat, 12 Feb 2022 10:54:25 +0600 Subject: fix: autoload variables not loaded with vim.g & nvim_get_var --- src/nvim/api/vim.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index f7c55344f5..f4909b0801 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -601,7 +601,19 @@ void nvim_del_current_line(Error *err) Object nvim_get_var(String name, Error *err) FUNC_API_SINCE(1) { - return dict_get_value(&globvardict, name, err); + dictitem_T *di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size); + if (di == NULL) { // try to autoload script + if (!script_autoload(name.data, name.size, false) || aborting()) { + api_set_error(err, kErrorTypeValidation, "Key not found: %s", name.data); + return (Object)OBJECT_INIT; + } + di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size); + } + if (di == NULL) { + api_set_error(err, kErrorTypeValidation, "Key not found: %s", name.data); + return (Object)OBJECT_INIT; + } + return vim_to_object(&di->di_tv); } /// Sets a global (g:) variable. -- cgit From f92e74900fda4583001cb85ba7f974394948ce1a Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 15 Feb 2022 11:12:09 +0800 Subject: fix(api): nvim_win_set_cursor() redraw for cursorline and statusline --- src/nvim/api/window.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index fc7823a070..9c473ff724 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -71,6 +71,7 @@ ArrayOf(Integer, 2) nvim_win_get_cursor(Window window, Error *err) } /// Sets the (1,0)-indexed cursor position in the window. |api-indexing| +/// Unlike |win_execute()| this scrolls the window. /// /// @param window Window handle, or 0 for current window /// @param pos (row, col) tuple representing the new position @@ -118,6 +119,8 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) update_topline_win(win); redraw_later(win, VALID); + redraw_for_cursorline(win); + win->w_redr_status = true; } /// Gets the window height -- cgit From 238b944e58d12a28245be996e69bf36a2a452a90 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Tue, 15 Feb 2022 13:08:40 -0700 Subject: fix(api): validate command names in nvim_add_user_command (#17406) This uses the same validation used when defining commands with `:command`. --- src/nvim/api/private/helpers.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index ddcfff0097..2b107a3f27 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1384,6 +1384,11 @@ void add_user_command(String name, Object command, Dict(user_command) *opts, int LuaRef luaref = LUA_NOREF; LuaRef compl_luaref = LUA_NOREF; + if (!uc_validate_name(name.data)) { + api_set_error(err, kErrorTypeValidation, "Invalid command name"); + goto err; + } + if (mb_islower(name.data[0])) { api_set_error(err, kErrorTypeValidation, "'name' must begin with an uppercase letter"); goto err; -- cgit From d512be55a2ea54dd83914ff25f57c02d703f93b4 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 16 Dec 2021 11:40:23 +0000 Subject: fix(api): re-route nvim_get_runtime_file errors This allows nvim_get_runtime_file to be properly used via pcall --- src/nvim/api/vim.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index f4909b0801..0c11ea7e6e 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -497,8 +497,12 @@ ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Error *err) int flags = DIP_DIRFILE | (all ? DIP_ALL : 0); - do_in_runtimepath((char_u *)(name.size ? name.data : ""), - flags, find_runtime_cb, &rv); + TRY_WRAP({ + try_start(); + do_in_runtimepath((char_u *)(name.size ? name.data : ""), + flags, find_runtime_cb, &rv); + try_end(err); + }); return rv; } -- cgit From cc81a8253be032aa12f05730e8a2f1b5d94fd08c Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 16 Feb 2022 16:58:32 +0800 Subject: docs: minor changes related to mapping description --- src/nvim/api/vim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index f4909b0801..565015cada 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1584,7 +1584,7 @@ ArrayOf(Dictionary) nvim_get_keymap(uint64_t channel_id, String mode) /// @param rhs Right-hand-side |{rhs}| of the mapping. /// @param opts Optional parameters map. Accepts all |:map-arguments| /// as keys excluding || but including |noremap| and "desc". -/// |desc| can be used to give a description to keymap. +/// "desc" can be used to give a description to keymap. /// When called from Lua, also accepts a "callback" key that takes /// a Lua function to call when the mapping is executed. /// Values are Booleans. Unknown key is an error. -- cgit From 1e7cb2dcd975aadeb91b913f117b21c7775c3374 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 21 Feb 2022 20:17:36 +0000 Subject: fix(highlight): accept NONE as a color name (#17487) ... for when `ns=0`. Also update the documentation of nvim_set_hl to clarify the set behaviour. Fixes #17478 --- src/nvim/api/vim.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 11bb1750e4..4dc599564f 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -124,6 +124,10 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) /// Set a highlight group. /// +/// Note: unlike the `:highlight` command which can update a highlight group, +/// this function completely replaces the definition. For example: +/// `nvim_set_hl(0, 'Visual', {})` will clear the highlight group 'Visual'. +/// /// @param ns_id number of namespace for this highlight. Use value 0 /// to set a highlight group in the global (`:highlight`) /// namespace. -- cgit From 11f7aeed7aa83d342d19897d9a69ba9f32ece7f7 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Tue, 22 Feb 2022 13:19:21 -0700 Subject: feat(api): implement nvim_buf_get_text (#15181) nvim_buf_get_text is the mirror of nvim_buf_set_text. It differs from nvim_buf_get_lines in that it allows retrieving only portions of lines. While this can typically be done easily enough by API clients, implementing this function provides symmetry between the get/set text/lines APIs, and also provides a nice convenience that saves API clients the work of having to slice the result of nvim_buf_get_lines themselves. --- src/nvim/api/buffer.c | 119 +++++++++++++++++++++++++++++++++++++---- src/nvim/api/private/helpers.c | 47 +++++++++++++++- 2 files changed, 156 insertions(+), 10 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 2d5403d4b8..02bd294c74 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -287,8 +287,8 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, } bool oob = false; - start = normalize_index(buf, start, &oob); - end = normalize_index(buf, end, &oob); + start = normalize_index(buf, start, true, &oob); + end = normalize_index(buf, end, true, &oob); if (strict_indexing && oob) { api_set_error(err, kErrorTypeValidation, "Index out of bounds"); @@ -374,15 +374,14 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ } bool oob = false; - start = normalize_index(buf, start, &oob); - end = normalize_index(buf, end, &oob); + start = normalize_index(buf, start, true, &oob); + end = normalize_index(buf, end, true, &oob); if (strict_indexing && oob) { api_set_error(err, kErrorTypeValidation, "Index out of bounds"); return; } - if (start > end) { api_set_error(err, kErrorTypeValidation, @@ -554,13 +553,13 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In // check range is ordered and everything! // start_row, end_row within buffer len (except add text past the end?) - start_row = normalize_index(buf, start_row, &oob); + start_row = normalize_index(buf, start_row, true, &oob); if (oob || start_row == buf->b_ml.ml_line_count + 1) { api_set_error(err, kErrorTypeValidation, "start_row out of bounds"); return; } - end_row = normalize_index(buf, end_row, &oob); + end_row = normalize_index(buf, end_row, true, &oob); if (oob || end_row == buf->b_ml.ml_line_count + 1) { api_set_error(err, kErrorTypeValidation, "end_row out of bounds"); return; @@ -757,6 +756,108 @@ end: try_end(err); } +/// Gets a range from the buffer. +/// +/// This differs from |nvim_buf_get_lines()| in that it allows retrieving only +/// portions of a line. +/// +/// Indexing is zero-based. Column indices are end-exclusive. +/// +/// Prefer |nvim_buf_get_lines()| when retrieving entire lines. +/// +/// @param channel_id +/// @param buffer Buffer handle, or 0 for current buffer +/// @param start_row First line index +/// @param start_col Starting byte offset of first line +/// @param end_row Last line index +/// @param end_col Ending byte offset of last line (exclusive) +/// @param opts Optional parameters. Currently unused. +/// @param[out] err Error details, if any +/// @return Array of lines, or empty array for unloaded buffer. +ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer, + Integer start_row, Integer start_col, + Integer end_row, Integer end_col, + Dictionary opts, Error *err) + FUNC_API_SINCE(9) +{ + Array rv = ARRAY_DICT_INIT; + + if (opts.size > 0) { + api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); + return rv; + } + + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return rv; + } + + // return sentinel value if the buffer isn't loaded + if (buf->b_ml.ml_mfp == NULL) { + return rv; + } + + bool oob = false; + start_row = normalize_index(buf, start_row, false, &oob); + end_row = normalize_index(buf, end_row, false, &oob); + + if (oob) { + api_set_error(err, kErrorTypeValidation, "Index out of bounds"); + return rv; + } + + // nvim_buf_get_lines doesn't care if the start row is greater than the end + // row (it will just return an empty array), but nvim_buf_get_text does in + // order to maintain symmetry with nvim_buf_set_text. + if (start_row > end_row) { + api_set_error(err, kErrorTypeValidation, "start is higher than end"); + return rv; + } + + bool replace_nl = (channel_id != VIML_INTERNAL_CALL); + + if (start_row == end_row) { + String line = buf_get_text(buf, start_row, start_col, end_col, replace_nl, err); + if (ERROR_SET(err)) { + return rv; + } + + ADD(rv, STRING_OBJ(line)); + return rv; + } + + rv.size = (size_t)(end_row - start_row) + 1; + rv.items = xcalloc(rv.size, sizeof(Object)); + + rv.items[0] = STRING_OBJ(buf_get_text(buf, start_row, start_col, MAXCOL-1, replace_nl, err)); + if (ERROR_SET(err)) { + goto end; + } + + if (rv.size > 2) { + Array tmp = ARRAY_DICT_INIT; + tmp.items = &rv.items[1]; + if (!buf_collect_lines(buf, rv.size - 2, start_row + 1, replace_nl, &tmp, err)) { + goto end; + } + } + + rv.items[rv.size-1] = STRING_OBJ(buf_get_text(buf, end_row, 0, end_col, replace_nl, err)); + if (ERROR_SET(err)) { + goto end; + } + +end: + if (ERROR_SET(err)) { + api_free_array(rv); + rv.size = 0; + rv.items = NULL; + } + + return rv; +} + /// Returns the byte offset of a line (0-indexed). |api-indexing| /// /// Line 1 (index=0) has offset 0. UTF-8 bytes are counted. EOL is one byte. @@ -1386,11 +1487,11 @@ static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra) } // Normalizes 0-based indexes to buffer line numbers -static int64_t normalize_index(buf_T *buf, int64_t index, bool *oob) +static int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bool *oob) { int64_t line_count = buf->b_ml.ml_line_count; // Fix if < 0 - index = index < 0 ? line_count + index +1 : index; + index = index < 0 ? line_count + index + (int)end_exclusive : index; // Check for oob if (index > line_count) { diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 2b107a3f27..971fa1cb0f 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -411,7 +411,6 @@ void set_option_to(uint64_t channel_id, void *to, int type, String name, Object current_sctx = save_current_sctx; } - buf_T *find_buffer_by_handle(Buffer buffer, Error *err) { if (buffer == 0) { @@ -758,6 +757,52 @@ bool buf_collect_lines(buf_T *buf, size_t n, int64_t start, bool replace_nl, Arr return true; } +/// Returns a substring of a buffer line +/// +/// @param buf Buffer handle +/// @param lnum Line number (1-based) +/// @param start_col Starting byte offset into line (0-based) +/// @param end_col Ending byte offset into line (0-based, exclusive) +/// @param replace_nl Replace newlines ('\n') with null ('\0') +/// @param err Error object +/// @return The text between start_col and end_col on line lnum of buffer buf +String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col, bool replace_nl, + Error *err) +{ + String rv = STRING_INIT; + + if (lnum >= MAXLNUM) { + api_set_error(err, kErrorTypeValidation, "Line index is too high"); + return rv; + } + + const char *bufstr = (char *)ml_get_buf(buf, (linenr_T)lnum, false); + size_t line_length = strlen(bufstr); + + start_col = start_col < 0 ? (int64_t)line_length + start_col + 1 : start_col; + end_col = end_col < 0 ? (int64_t)line_length + end_col + 1 : end_col; + + if (start_col >= MAXCOL || end_col >= MAXCOL) { + api_set_error(err, kErrorTypeValidation, "Column index is too high"); + return rv; + } + + if (start_col > end_col) { + api_set_error(err, kErrorTypeValidation, "start_col must be less than end_col"); + return rv; + } + + if ((size_t)start_col >= line_length) { + return rv; + } + + rv = cstrn_to_string(&bufstr[start_col], (size_t)(end_col - start_col)); + if (replace_nl) { + strchrsub(rv.data, '\n', '\0'); + } + + return rv; +} void api_free_string(String value) { -- cgit From 15004473b531e37a6ff3aeca9e3bfeaaa1487d9e Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Wed, 23 Feb 2022 15:19:47 -0700 Subject: fix(api)!: correctly handle negative line numbers for nvim_buf_set_text (#17498) nvim_buf_set_text does not handle negative row numbers correctly: for example, nvim_buf_set_text(0, -2, 0, -1, 20, {"Hello", "world"}) should replace the 2nd to last line in the buffer with "Hello" and the first 20 characters of the last line with "world". Instead, it reports "start_row out of bounds". This happens because when negative line numbers are used, they are incremented by one additional number to make the non-negative line numbers end-exclusive. However, the line numbers for nvim_buf_set_text should be end-inclusive. In #15181 we handled this for nvim_buf_get_text by adding a new parameter to `normalize_index`. We can solve the problem with nvim_buf_set_text by simply availing ourselves of this new argument. This is a breaking change, but makes the semantics of negative line numbers much clearer and more obvious (as well as matching nvim_buf_get_text). BREAKING CHANGE: Existing usages of nvim_buf_set_text that use negative line numbers will be off-by-one. --- src/nvim/api/buffer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 02bd294c74..922d288da1 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -553,13 +553,13 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In // check range is ordered and everything! // start_row, end_row within buffer len (except add text past the end?) - start_row = normalize_index(buf, start_row, true, &oob); + start_row = normalize_index(buf, start_row, false, &oob); if (oob || start_row == buf->b_ml.ml_line_count + 1) { api_set_error(err, kErrorTypeValidation, "start_row out of bounds"); return; } - end_row = normalize_index(buf, end_row, true, &oob); + end_row = normalize_index(buf, end_row, false, &oob); if (oob || end_row == buf->b_ml.ml_line_count + 1) { api_set_error(err, kErrorTypeValidation, "end_row out of bounds"); return; -- cgit From b5bf4877c0239767c1095e4567e67c222bea38a0 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 24 Feb 2022 16:50:05 +0000 Subject: feat(highlight): support for blend in nvim_set_hl (#17516) --- src/nvim/api/keysets.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index f6dce1905e..45a57b9257 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -97,6 +97,7 @@ return { "special"; "sp"; "link"; "fallback"; + "blend"; "temp"; }; highlight_cterm = { -- cgit From b87867e69e94d9784468a126f21c721446f080de Mon Sep 17 00:00:00 2001 From: erw7 Date: Sat, 11 Sep 2021 11:48:58 +0900 Subject: feat(lua): add proper support of luv threads --- src/nvim/api/vim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 4dc599564f..cd9d61ed24 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1955,7 +1955,7 @@ Dictionary nvim__stats(void) Dictionary rv = ARRAY_DICT_INIT; PUT(rv, "fsync", INTEGER_OBJ(g_stats.fsync)); PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw)); - PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_refcount)); + PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_get_global_ref_count())); return rv; } -- cgit From f6cc604af2a4d95ec9bcaa5bee705cec2e06d541 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sat, 26 Feb 2022 13:27:43 +0000 Subject: fix(api): convert blob to NUL-terminated API string Looks like I did an oopsie; although API strings carry a size field, they should still be usable as C-strings! (even though they may contain embedded NULs) --- src/nvim/api/private/converter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c index 3d4ff202fe..49e3cf7df7 100644 --- a/src/nvim/api/private/converter.c +++ b/src/nvim/api/private/converter.c @@ -57,7 +57,7 @@ typedef struct { const size_t len_ = (size_t)(len); \ const blob_T *const blob_ = (blob); \ kvi_push(edata->stack, STRING_OBJ(((String) { \ - .data = len_ != 0 ? xmemdup(blob_->bv_ga.ga_data, len_) : NULL, \ + .data = len_ != 0 ? xmemdupz(blob_->bv_ga.ga_data, len_) : xstrdup(""), \ .size = len_ \ }))); \ } while (0) -- cgit From 1b5767aa3480c0cdc43f7a4b78f36a14e85a182f Mon Sep 17 00:00:00 2001 From: Javier Lopez Date: Sun, 27 Feb 2022 14:35:06 -0500 Subject: feat(lua): add to user commands callback (#17522) Works similar to ex . It only splits the arguments if the command has more than one posible argument. In cases were the command can only have 1 argument opts.fargs = { opts.args } --- src/nvim/api/vim.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index cd9d61ed24..302dccbde7 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -2415,6 +2415,8 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * /// from Lua, the command can also be a Lua function. The function is called with a /// single table argument that contains the following keys: /// - args: (string) The args passed to the command, if any || +/// - fargs: (table) The args split by unescaped whitespace (when more than one +/// argument is allowed), if any || /// - bang: (boolean) "true" if the command was executed with a ! modifier || /// - line1: (number) The starting line of the command range || /// - line2: (number) The final line of the command range || -- cgit From 991e472881bf29805982b402c1a010cde051ded3 Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Fri, 28 May 2021 15:45:34 -0400 Subject: feat(lua): add api and lua autocmds --- src/nvim/api/autocmd.c | 669 ++++++++++++++++++++++++++++++++++++++++ src/nvim/api/autocmd.h | 11 + src/nvim/api/keysets.lua | 29 ++ src/nvim/api/private/dispatch.c | 20 +- src/nvim/api/private/helpers.c | 34 +- src/nvim/api/private/helpers.h | 17 + 6 files changed, 761 insertions(+), 19 deletions(-) create mode 100644 src/nvim/api/autocmd.c create mode 100644 src/nvim/api/autocmd.h (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c new file mode 100644 index 0000000000..deb8ec8cf3 --- /dev/null +++ b/src/nvim/api/autocmd.c @@ -0,0 +1,669 @@ +#include +#include + +#include "lauxlib.h" +#include "nvim/api/autocmd.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" +#include "nvim/ascii.h" +#include "nvim/buffer.h" +#include "nvim/eval/typval.h" +#include "nvim/fileio.h" +#include "nvim/lua/executor.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "api/autocmd.c.generated.h" +#endif + +#define AUCMD_MAX_PATTERNS 256 + +// Check whether every item in the array is a kObjectTypeString +#define CHECK_STRING_ARRAY(__array, k, v, goto_name) \ + for (size_t j = 0; j < __array.size; j++) { \ + Object item = __array.items[j]; \ + if (item.type != kObjectTypeString) { \ + api_set_error(err, \ + kErrorTypeValidation, \ + "All entries in '%s' must be strings", \ + k); \ + goto goto_name; \ + } \ + } + +// Copy string or array of strings into an empty array. +#define UNPACK_STRING_OR_ARRAY(__array, k, v, goto_name) \ + if (v->type == kObjectTypeString) { \ + ADD(__array, copy_object(*v)); \ + } else if (v->type == kObjectTypeArray) { \ + CHECK_STRING_ARRAY(__array, k, v, goto_name); \ + __array = copy_array(v->data.array); \ + } else { \ + api_set_error(err, \ + kErrorTypeValidation, \ + "'%s' must be an array or a string.", \ + k); \ + goto goto_name; \ + } + +// Get the event number, unless it is an error. Then goto `goto_name`. +#define GET_ONE_EVENT(event_nr, event_str, goto_name) \ + char_u *__next_ev; \ + event_T event_nr = \ + event_name2nr((char_u *)event_str.data.string.data, &__next_ev); \ + if (event_nr >= NUM_EVENTS) { \ + api_set_error(err, kErrorTypeValidation, "unexpected event"); \ + goto goto_name; \ + } + + +// ID for associating autocmds created via nvim_create_autocmd +// Used to delete autocmds from nvim_del_autocmd +static int64_t next_autocmd_id = 1; + +/// Get autocmds that match the requirements passed to {opts}. +/// group +/// event +/// pattern +/// +/// -- @param {string} event - event or events to match against +/// vim.api.nvim_get_autocmds({ event = "FileType" }) +/// +Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) + FUNC_API_SINCE(9) +{ + Array autocmd_list = ARRAY_DICT_INIT; + char_u *pattern_filters[AUCMD_MAX_PATTERNS]; + char_u pattern_buflocal[BUFLOCAL_PAT_LEN]; + + bool event_set[NUM_EVENTS] = { false }; + bool check_event = false; + + int group = 0; + + if (opts->group.type != kObjectTypeNil) { + Object v = opts->group; + if (v.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, "group must be a string."); + goto cleanup; + } + + group = augroup_find(v.data.string.data); + + if (group < 0) { + api_set_error(err, kErrorTypeValidation, "invalid augroup passed."); + goto cleanup; + } + } + + if (opts->event.type != kObjectTypeNil) { + check_event = true; + + Object v = opts->event; + if (v.type == kObjectTypeString) { + GET_ONE_EVENT(event_nr, v, cleanup); + event_set[event_nr] = true; + } else if (v.type == kObjectTypeArray) { + FOREACH_ITEM(v.data.array, event_v, { + if (event_v.type != kObjectTypeString) { + api_set_error(err, + kErrorTypeValidation, + "Every event must be a string in 'event'"); + goto cleanup; + } + + GET_ONE_EVENT(event_nr, event_v, cleanup); + event_set[event_nr] = true; + }) + } else { + api_set_error(err, + kErrorTypeValidation, + "Not a valid 'event' value. Must be a string or an array"); + goto cleanup; + } + } + + int pattern_filter_count = 0; + if (opts->pattern.type != kObjectTypeNil) { + Object v = opts->pattern; + if (v.type == kObjectTypeString) { + pattern_filters[pattern_filter_count] = (char_u *)v.data.string.data; + pattern_filter_count += 1; + } else if (v.type == kObjectTypeArray) { + FOREACH_ITEM(v.data.array, item, { + pattern_filters[pattern_filter_count] = (char_u *)item.data.string.data; + pattern_filter_count += 1; + }); + } else { + api_set_error(err, + kErrorTypeValidation, + "Not a valid 'pattern' value. Must be a string or an array"); + goto cleanup; + } + + if (pattern_filter_count >= AUCMD_MAX_PATTERNS) { + api_set_error(err, + kErrorTypeValidation, + "Too many patterns. Please limit yourself to less"); + goto cleanup; + } + } + + FOR_ALL_AUEVENTS(event) { + if (check_event && !event_set[event]) { + continue; + } + + for (AutoPat *ap = au_get_autopat_for_event(event); + ap != NULL; + ap = ap->next) { + if (ap == NULL || ap->cmds == NULL) { + continue; + } + + // Skip autocmds from invalid groups if passed. + if (group != 0 && ap->group != group) { + continue; + } + + // Skip 'pattern' from invalid patterns if passed. + if (pattern_filter_count > 0) { + bool passed = false; + for (int i = 0; i < pattern_filter_count; i++) { + assert(i < AUCMD_MAX_PATTERNS); + assert(pattern_filters[i]); + + char_u *pat = pattern_filters[i]; + int patlen = (int)STRLEN(pat); + + if (aupat_is_buflocal(pat, patlen)) { + aupat_normalize_buflocal_pat(pattern_buflocal, + pat, + patlen, + aupat_get_buflocal_nr(pat, patlen)); + + pat = pattern_buflocal; + } + + if (strequal((char *)ap->pat, (char *)pat)) { + passed = true; + break; + } + } + + if (!passed) { + continue; + } + } + + for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) { + if (aucmd_exec_is_deleted(ac->exec)) { + continue; + } + Dictionary autocmd_info = ARRAY_DICT_INIT; + + if (ap->group != AUGROUP_DEFAULT) { + PUT(autocmd_info, "group", INTEGER_OBJ(ap->group)); + } + + if (ac->id > 0) { + PUT(autocmd_info, "id", INTEGER_OBJ(ac->id)); + } + + if (ac->desc != NULL) { + PUT(autocmd_info, "desc", CSTR_TO_OBJ(ac->desc)); + } + + PUT(autocmd_info, + "command", + STRING_OBJ(cstr_to_string(aucmd_exec_to_string(ac, ac->exec)))); + + PUT(autocmd_info, + "pattern", + STRING_OBJ(cstr_to_string((char *)ap->pat))); + + PUT(autocmd_info, "once", BOOLEAN_OBJ(ac->once)); + + if (ap->buflocal_nr) { + PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(true)); + PUT(autocmd_info, "buffer", INTEGER_OBJ(ap->buflocal_nr)); + } else { + PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(false)); + } + + // TODO(sctx): It would be good to unify script_ctx to actually work with lua + // right now it's just super weird, and never really gives you the info that + // you would expect from this. + // + // I think we should be able to get the line number, filename, etc. from lua + // when we're executing something, and it should be easy to then save that + // info here. + // + // I think it's a big loss not getting line numbers of where options, autocmds, + // etc. are set (just getting "Sourced (lua)" or something is not that helpful. + // + // Once we do that, we can put these into the autocmd_info, but I don't think it's + // useful to do that at this time. + // + // PUT(autocmd_info, "sid", INTEGER_OBJ(ac->script_ctx.sc_sid)); + // PUT(autocmd_info, "lnum", INTEGER_OBJ(ac->script_ctx.sc_lnum)); + + ADD(autocmd_list, DICTIONARY_OBJ(autocmd_info)); + } + } + } + +cleanup: + return autocmd_list; +} + +/// Define an autocmd. +/// @param opts Dictionary +/// Required keys: +/// event: string | ArrayOf(string) +/// event = "pat1,pat2,pat3", +/// event = "pat1" +/// event = {"pat1"} +/// event = {"pat1", "pat2", "pat3"} +/// +/// +/// -- @param {string} name - augroup name +/// -- @param {string | table} event - event or events to match against +/// -- @param {string | table} pattern - pattern or patterns to match against +/// -- @param {string | function} callback - function or string to execute on autocmd +/// -- @param {string} command - optional, vimscript command +/// Eg. command = "let g:value_set = v:true" +/// -- @param {boolean} once - optional, defaults to false +/// +/// -- pattern = comma delimited list of patterns | pattern | { pattern, ... } +/// +/// pattern = "*.py,*.pyi" +/// pattern = "*.py" +/// pattern = {"*.py"} +/// pattern = { "*.py", "*.pyi" } +/// +/// -- not supported +/// pattern = {"*.py,*.pyi"} +/// +/// -- event = string | string[] +/// event = "FileType,CursorHold" +/// event = "BufPreWrite" +/// event = {"BufPostWrite"} +/// event = {"CursorHold", "BufPreWrite", "BufPostWrite"} +Integer nvim_create_autocmd(uint64_t channel_id, Dict(create_autocmd) *opts, Error *err) + FUNC_API_SINCE(9) +{ + int64_t autocmd_id = -1; + + const char_u pattern_buflocal[BUFLOCAL_PAT_LEN]; + int au_group = AUGROUP_DEFAULT; + char *desc = NULL; + + Array patterns = ARRAY_DICT_INIT; + Array event_array = ARRAY_DICT_INIT; + + AucmdExecutable aucmd = AUCMD_EXECUTABLE_INIT; + Callback cb = CALLBACK_NONE; + + if (opts->callback.type != kObjectTypeNil && opts->command.type != kObjectTypeNil) { + api_set_error(err, kErrorTypeValidation, + "cannot pass both: 'callback' and 'command' for the same autocmd"); + goto cleanup; + } else if (opts->callback.type != kObjectTypeNil) { + // TODO(tjdevries): It's possible we could accept callable tables, + // but we don't do that many other places, so for the moment let's + // not do that. + + Object *callback = &opts->callback; + if (callback->type == kObjectTypeLuaRef) { + if (callback->data.luaref == LUA_NOREF) { + api_set_error(err, + kErrorTypeValidation, + "must pass an actual value"); + goto cleanup; + } + + if (!nlua_ref_is_function(callback->data.luaref)) { + api_set_error(err, + kErrorTypeValidation, + "must pass a function for callback"); + goto cleanup; + } + + cb.type = kCallbackLua; + cb.data.luaref = api_new_luaref(callback->data.luaref); + } else if (callback->type == kObjectTypeString) { + cb.type = kCallbackFuncref; + cb.data.funcref = vim_strsave((char_u *)callback->data.string.data); + } else { + api_set_error(err, + kErrorTypeException, + "'callback' must be a lua function or name of vim function"); + goto cleanup; + } + + aucmd.type = CALLABLE_CB; + aucmd.callable.cb = cb; + } else if (opts->command.type != kObjectTypeNil) { + Object *command = &opts->command; + if (command->type == kObjectTypeString) { + aucmd.type = CALLABLE_EX; + aucmd.callable.cmd = vim_strsave((char_u *)command->data.string.data); + } else { + api_set_error(err, + kErrorTypeValidation, + "'command' must be a string"); + goto cleanup; + } + } else { + api_set_error(err, kErrorTypeValidation, "must pass one of: 'command', 'callback'"); + goto cleanup; + } + + if (opts->event.type != kObjectTypeNil) { + UNPACK_STRING_OR_ARRAY(event_array, "event", (&opts->event), cleanup) + } + + bool is_once = api_object_to_bool(opts->once, "once", false, err); + bool is_nested = api_object_to_bool(opts->nested, "nested", false, err); + + // TOOD: accept number for namespace instead + if (opts->group.type != kObjectTypeNil) { + Object *v = &opts->group; + if (v->type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, "'group' must be a string"); + goto cleanup; + } + + au_group = augroup_find(v->data.string.data); + + if (au_group == AUGROUP_ERROR) { + api_set_error(err, + kErrorTypeException, + "invalid augroup: %s", v->data.string.data); + + goto cleanup; + } + } + + if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) { + api_set_error(err, kErrorTypeValidation, + "cannot pass both: 'pattern' and 'buffer' for the same autocmd"); + goto cleanup; + } else if (opts->pattern.type != kObjectTypeNil) { + Object *v = &opts->pattern; + + if (v->type == kObjectTypeString) { + char_u *pat = (char_u *)v->data.string.data; + size_t patlen = aucmd_pattern_length(pat); + while (patlen) { + ADD(patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen))); + + pat = aucmd_next_pattern(pat, patlen); + patlen = aucmd_pattern_length(pat); + } + } else if (v->type == kObjectTypeArray) { + CHECK_STRING_ARRAY(patterns, "pattern", v, cleanup); + + Array array = v->data.array; + for (size_t i = 0; i < array.size; i++) { + char_u *pat = (char_u *)array.items[i].data.string.data; + size_t patlen = aucmd_pattern_length(pat); + while (patlen) { + ADD(patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen))); + + pat = aucmd_next_pattern(pat, patlen); + patlen = aucmd_pattern_length(pat); + } + } + } else { + api_set_error(err, + kErrorTypeValidation, + "'pattern' must be a string"); + goto cleanup; + } + } else if (opts->buffer.type != kObjectTypeNil) { + if (opts->buffer.type != kObjectTypeInteger) { + api_set_error(err, + kErrorTypeValidation, + "'buffer' must be an integer"); + goto cleanup; + } + + buf_T *buf = find_buffer_by_handle((Buffer)opts->buffer.data.integer, err); + if (ERROR_SET(err)) { + goto cleanup; + } + + snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "", (int)buf->handle); + ADD(patterns, STRING_OBJ(cstr_to_string((char *)pattern_buflocal))); + } + + if (aucmd.type == CALLABLE_NONE) { + api_set_error(err, + kErrorTypeValidation, + "'command' or 'callback' is required"); + goto cleanup; + } + + if (opts->desc.type != kObjectTypeNil) { + if (opts->desc.type == kObjectTypeString) { + desc = opts->desc.data.string.data; + } else { + api_set_error(err, + kErrorTypeValidation, + "'desc' must be a string"); + goto cleanup; + } + } + + if (patterns.size == 0) { + ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("*"))); + } + + if (event_array.size == 0) { + api_set_error(err, kErrorTypeValidation, "'event' is a required key"); + goto cleanup; + } + + autocmd_id = next_autocmd_id++; + FOREACH_ITEM(event_array, event_str, { + GET_ONE_EVENT(event_nr, event_str, cleanup); + + int retval; + + for (size_t i = 0; i < patterns.size; i++) { + Object pat = patterns.items[i]; + + // See: TODO(sctx) + WITH_SCRIPT_CONTEXT(channel_id, { + retval = autocmd_register(autocmd_id, + event_nr, + (char_u *)pat.data.string.data, + (int)pat.data.string.size, + au_group, + is_once, + is_nested, + desc, + aucmd); + }); + + if (retval == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to set autocmd"); + goto cleanup; + } + } + }); + + +cleanup: + aucmd_exec_free(&aucmd); + api_free_array(event_array); + api_free_array(patterns); + + return autocmd_id; +} + +/// Delete an autocmd by ID. Autocmds only return IDs when created +/// via the API. +/// +/// @param id Integer The ID returned by nvim_create_autocmd +void nvim_del_autocmd(Integer id) + FUNC_API_SINCE(9) +{ + autocmd_delete_id(id); +} + +/// Create or get an augroup. +/// +/// To get an existing augroup ID, do: +///
+///     local id = vim.api.nvim_create_augroup({ name = name, clear = false });
+/// 
+/// +/// @param opts Parameters +/// - name (string): The name of the augroup +/// - clear (bool): Whether to clear existing commands or not. +// Defaults to true. +/// See |autocmd-groups| +Integer nvim_create_augroup(uint64_t channel_id, Dict(create_augroup) *opts, Error *err) + FUNC_API_SINCE(9) +{ + bool clear_autocmds = api_object_to_bool(opts->clear, "clear", true, err); + + if (opts->name.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, "'name' is required and must be a string"); + return -1; + } + char *name = opts->name.data.string.data; + + int augroup = -1; + WITH_SCRIPT_CONTEXT(channel_id, { + augroup = augroup_add(name); + if (augroup == AUGROUP_ERROR) { + api_set_error(err, kErrorTypeException, "Failed to set augroup"); + return -1; + } + + if (clear_autocmds) { + FOR_ALL_AUEVENTS(event) { + aupat_del_for_event_and_group(event, augroup); + } + } + }); + + return augroup; +} + +/// NOTE: behavior differs from augroup-delete. +/// When deleting an augroup, autocmds contained by this augroup will also be deleted and cleared. +/// This augroup will no longer exist +void nvim_del_augroup_by_id(Integer id) + FUNC_API_SINCE(9) +{ + char *name = augroup_name((int)id); + augroup_del(name, false); +} + +/// NOTE: behavior differs from augroup-delete. +/// When deleting an augroup, autocmds contained by this augroup will also be deleted and cleared. +/// This augroup will no longer exist +void nvim_del_augroup_by_name(String name) + FUNC_API_SINCE(9) +{ + augroup_del(name.data, false); +} + +/// -- @param {string} group - autocmd group name +/// -- @param {number} buffer - buffer number +/// -- @param {string | table} event - event or events to match against +/// -- @param {string | table} pattern - optional, defaults to "*". +/// vim.api.nvim_do_autcmd({ group, buffer, pattern, event, modeline }) +void nvim_do_autocmd(Dict(do_autocmd) *opts, Error *err) + FUNC_API_SINCE(9) +{ + int au_group = AUGROUP_ALL; + bool modeline = true; + + buf_T *buf = curbuf; + bool set_buf = false; + + char_u *pattern = NULL; + bool set_pattern = false; + + Array event_array = ARRAY_DICT_INIT; + + if (opts->group.type != kObjectTypeNil) { + if (opts->group.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, "'group' must be a string"); + goto cleanup; + } + + au_group = augroup_find(opts->group.data.string.data); + + if (au_group == AUGROUP_ERROR) { + api_set_error(err, + kErrorTypeException, + "invalid augroup: %s", opts->group.data.string.data); + + goto cleanup; + } + } + + if (opts->buffer.type != kObjectTypeNil) { + Object buf_obj = opts->buffer; + if (buf_obj.type != kObjectTypeInteger && buf_obj.type != kObjectTypeBuffer) { + api_set_error(err, kErrorTypeException, "invalid buffer: %d", buf_obj.type); + goto cleanup; + } + + buf = find_buffer_by_handle((Buffer)buf_obj.data.integer, err); + set_buf = true; + + if (ERROR_SET(err)) { + goto cleanup; + } + } + + if (opts->pattern.type != kObjectTypeNil) { + if (opts->pattern.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, "'pattern' must be a string"); + goto cleanup; + } + + pattern = vim_strsave((char_u *)opts->pattern.data.string.data); + set_pattern = true; + } + + if (opts->event.type != kObjectTypeNil) { + UNPACK_STRING_OR_ARRAY(event_array, "event", (&opts->event), cleanup) + } + + if (opts->modeline.type != kObjectTypeNil) { + modeline = api_object_to_bool(opts->modeline, "modeline", true, err); + } + + if (set_pattern && set_buf) { + api_set_error(err, kErrorTypeValidation, "must not set 'buffer' and 'pattern'"); + goto cleanup; + } + + bool did_aucmd = false; + FOREACH_ITEM(event_array, event_str, { + GET_ONE_EVENT(event_nr, event_str, cleanup) + + did_aucmd |= apply_autocmds_group(event_nr, pattern, NULL, true, au_group, buf, NULL); + }) + + if (did_aucmd && modeline) { + do_modelines(0); + } + +cleanup: + api_free_array(event_array); + XFREE_CLEAR(pattern); +} + + +#undef UNPACK_STRING_OR_ARRAY +#undef CHECK_STRING_ARRAY +#undef GET_ONE_EVENT diff --git a/src/nvim/api/autocmd.h b/src/nvim/api/autocmd.h new file mode 100644 index 0000000000..f9432830d9 --- /dev/null +++ b/src/nvim/api/autocmd.h @@ -0,0 +1,11 @@ +#ifndef NVIM_API_AUTOCMD_H +#define NVIM_API_AUTOCMD_H + +#include + +#include "nvim/api/private/defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "api/autocmd.h.generated.h" +#endif +#endif // NVIM_API_AUTOCMD_H diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index 45a57b9257..be71c446b1 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -110,5 +110,34 @@ return { "reverse"; "nocombine"; }; + -- Autocmds + create_autocmd = { + "buffer"; + "callback"; + "command"; + "desc"; + "event"; + "group"; + "once"; + "nested"; + "pattern"; + }; + do_autocmd = { + "buffer"; + "event"; + "group"; + "modeline"; + "pattern"; + }; + get_autocmds = { + "event"; + "group"; + "id"; + "pattern"; + }; + create_augroup = { + "clear"; + "name"; + }; } diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c index 8ab7743e01..f670f06357 100644 --- a/src/nvim/api/private/dispatch.c +++ b/src/nvim/api/private/dispatch.c @@ -6,22 +6,30 @@ #include #include -#include "nvim/api/buffer.h" #include "nvim/api/deprecated.h" -#include "nvim/api/extmark.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" +#include "nvim/log.h" +#include "nvim/map.h" +#include "nvim/msgpack_rpc/helpers.h" +#include "nvim/vim.h" + +// =========================================================================== +// NEW API FILES MUST GO HERE. +// +// When creating a new API file, you must include it here, +// so that the dispatcher can find the C functions that you are creating! +// =========================================================================== +#include "nvim/api/autocmd.h" +#include "nvim/api/buffer.h" +#include "nvim/api/extmark.h" #include "nvim/api/tabpage.h" #include "nvim/api/ui.h" #include "nvim/api/vim.h" #include "nvim/api/vimscript.h" #include "nvim/api/win_config.h" #include "nvim/api/window.h" -#include "nvim/log.h" -#include "nvim/map.h" -#include "nvim/msgpack_rpc/helpers.h" -#include "nvim/vim.h" static Map(String, MsgpackRpcRequestHandler) methods = MAP_INIT; diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 971fa1cb0f..3d4a04f096 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -396,19 +396,14 @@ void set_option_to(uint64_t channel_id, void *to, int type, String name, Object stringval = value.data.string.data; } - const sctx_T save_current_sctx = current_sctx; - current_sctx.sc_sid = - channel_id == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; - current_sctx.sc_lnum = 0; - current_channel_id = channel_id; - - const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL)) - ? 0 : (type == SREQ_GLOBAL) - ? OPT_GLOBAL : OPT_LOCAL; - set_option_value_for(name.data, numval, stringval, - opt_flags, type, to, err); - - current_sctx = save_current_sctx; + WITH_SCRIPT_CONTEXT(channel_id, { + const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL)) + ? 0 : (type == SREQ_GLOBAL) + ? OPT_GLOBAL : OPT_LOCAL; + + set_option_value_for(name.data, numval, stringval, + opt_flags, type, to, err); + }); } buf_T *find_buffer_by_handle(Buffer buffer, Error *err) @@ -1614,3 +1609,16 @@ err: NLUA_CLEAR_REF(luaref); NLUA_CLEAR_REF(compl_luaref); } + +int find_sid(uint64_t channel_id) +{ + switch (channel_id) { + case VIML_INTERNAL_CALL: + // TODO(autocmd): Figure out what this should be + // return SID_API_CLIENT; + case LUA_INTERNAL_CALL: + return SID_LUA; + default: + return SID_API_CLIENT; + } +} diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 6d0aec9c90..6969994c3b 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -138,10 +138,27 @@ typedef struct { msg_list = saved_msg_list; /* Restore the exception context. */ \ } while (0) +// Useful macro for executing some `code` for each item in an array. +#define FOREACH_ITEM(a, __foreach_item, code) \ + for (size_t __foreach_i = 0; __foreach_i < (a).size; __foreach_i++) { \ + Object __foreach_item = (a).items[__foreach_i]; \ + code; \ + } + + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/helpers.h.generated.h" # include "keysets.h.generated.h" #endif +#define WITH_SCRIPT_CONTEXT(channel_id, code) \ + const sctx_T save_current_sctx = current_sctx; \ + current_sctx.sc_sid = \ + (channel_id) == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; \ + current_sctx.sc_lnum = 0; \ + current_channel_id = channel_id; \ + code; \ + current_sctx = save_current_sctx; + #endif // NVIM_API_PRIVATE_HELPERS_H -- cgit From ebfe083337701534887ac3ea3d8e7ad47f7a206a Mon Sep 17 00:00:00 2001 From: shadmansaleh <13149513+shadmansaleh@users.noreply.github.com> Date: Sat, 8 Jan 2022 00:39:44 +0600 Subject: feat(lua): show proper verbose output for lua configuration `:verbose` didn't work properly with lua configs (For example: options or keymaps are set from lua, just say that they were set from lua, doesn't say where they were set at. This fixes that issue. Now `:verbose` will provide filename and line no when option/keymap is set from lua. Changes: - compiles lua/vim/keymap.lua as vim/keymap.lua - When souring a lua file current_sctx.sc_sid is set to SID_LUA - Moved finding scripts SID out of `do_source()` to `get_current_script_id()`. So it can be reused for lua files. - Added new function `nlua_get_sctx` that extracts current lua scripts name and line no with debug library. And creates a sctx for it. NOTE: This function ignores C functions and blacklist which currently contains only vim/_meta.lua so vim.o/opt wrappers aren't targeted. - Added function `nlua_set_sctx` that changes provided sctx to current lua scripts sctx if a lua file is being executed. - Added tests in tests/functional/lua/verbose_spec.lua - add primary support for additional types (:autocmd, :function, :syntax) to lua verbose Note: These can't yet be directly set from lua but once that's possible :verbose should work for them hopefully :D - add :verbose support for nvim_exec & nvim_command within lua Currently auto commands/commands/functions ... can only be defined by nvim_exec/nvim_command this adds support for them. Means if those Are defined within lua with vim.cmd/nvim_exec :verbose will show their location . Though note it'll show the line no on which nvim_exec call was made. --- src/nvim/api/buffer.c | 11 ++++++----- src/nvim/api/deprecated.c | 4 ++-- src/nvim/api/private/helpers.c | 24 ++++++++++++++++++++++-- src/nvim/api/vim.c | 10 ++++++---- src/nvim/api/vimscript.c | 7 ++++++- 5 files changed, 42 insertions(+), 14 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 922d288da1..3dd647e76f 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -953,11 +953,11 @@ ArrayOf(Dictionary) nvim_buf_get_keymap(uint64_t channel_id, Buffer buffer, Stri /// @see |nvim_set_keymap()| /// /// @param buffer Buffer handle, or 0 for current buffer -void nvim_buf_set_keymap(Buffer buffer, String mode, String lhs, String rhs, Dict(keymap) *opts, - Error *err) +void nvim_buf_set_keymap(uint64_t channel_id, Buffer buffer, String mode, String lhs, String rhs, + Dict(keymap) *opts, Error *err) FUNC_API_SINCE(6) { - modify_keymap(buffer, false, mode, lhs, rhs, opts, err); + modify_keymap(channel_id, buffer, false, mode, lhs, rhs, opts, err); } /// Unmaps a buffer-local |mapping| for the given mode. @@ -965,11 +965,12 @@ void nvim_buf_set_keymap(Buffer buffer, String mode, String lhs, String rhs, Dic /// @see |nvim_del_keymap()| /// /// @param buffer Buffer handle, or 0 for current buffer -void nvim_buf_del_keymap(Buffer buffer, String mode, String lhs, Error *err) +void nvim_buf_del_keymap(uint64_t channel_id, Buffer buffer, String mode, + String lhs, Error *err) FUNC_API_SINCE(6) { String rhs = { .data = "", .size = 0 }; - modify_keymap(buffer, true, mode, lhs, rhs, NULL, err); + modify_keymap(channel_id, buffer, true, mode, lhs, rhs, NULL, err); } /// Gets a map of buffer-local |user-commands|. diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index 18243fec2b..d2013c597c 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -22,11 +22,11 @@ /// @deprecated /// @see nvim_exec -String nvim_command_output(String command, Error *err) +String nvim_command_output(uint64_t channel_id, String command, Error *err) FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(7) { - return nvim_exec(command, true, err); + return nvim_exec(channel_id, command, true, err); } /// @deprecated Use nvim_exec_lua() instead. diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 3d4a04f096..5198b00f65 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -585,8 +585,8 @@ Array string_to_array(const String input, bool crlf) /// @param buffer Buffer handle for a specific buffer, or 0 for the current /// buffer, or -1 to signify global behavior ("all buffers") /// @param is_unmap When true, removes the mapping that matches {lhs}. -void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String rhs, - Dict(keymap) *opts, Error *err) +void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mode, String lhs, + String rhs, Dict(keymap) *opts, Error *err) { LuaRef lua_funcref = LUA_NOREF; bool global = (buffer == -1); @@ -599,6 +599,8 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String return; } + const sctx_T save_current_sctx = api_set_sctx(channel_id); + if (opts != NULL && opts->callback.type == kObjectTypeLuaRef) { lua_funcref = opts->callback.data.luaref; opts->callback.data.luaref = LUA_NOREF; @@ -710,6 +712,7 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String parsed_args.rhs_lua = LUA_NOREF; // don't clear ref on success fail_and_free: + current_sctx = save_current_sctx; NLUA_CLEAR_REF(parsed_args.rhs_lua); xfree(parsed_args.rhs); xfree(parsed_args.orig_rhs); @@ -1622,3 +1625,20 @@ int find_sid(uint64_t channel_id) return SID_API_CLIENT; } } + +/// Sets sctx for API calls. +/// +/// @param channel_id api clients id. Used to determine if it's a internal +/// call or a rpc call. +/// @return returns previous value of current_sctx. To be used +/// to be used for restoring sctx to previous state. +sctx_T api_set_sctx(uint64_t channel_id) +{ + sctx_T old_current_sctx = current_sctx; + if (channel_id != VIML_INTERNAL_CALL) { + current_sctx.sc_sid = + channel_id == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; + current_sctx.sc_lnum = 0; + } + return old_current_sctx; +} diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 302dccbde7..9d0b096a36 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1586,6 +1586,7 @@ ArrayOf(Dictionary) nvim_get_keymap(uint64_t channel_id, String mode) /// nmap /// /// +/// @param channel_id /// @param mode Mode short-name (map command prefix: "n", "i", "v", "x", …) /// or "!" for |:map!|, or empty string for |:map|. /// @param lhs Left-hand-side |{lhs}| of the mapping. @@ -1597,10 +1598,11 @@ ArrayOf(Dictionary) nvim_get_keymap(uint64_t channel_id, String mode) /// a Lua function to call when the mapping is executed. /// Values are Booleans. Unknown key is an error. /// @param[out] err Error details, if any. -void nvim_set_keymap(String mode, String lhs, String rhs, Dict(keymap) *opts, Error *err) +void nvim_set_keymap(uint64_t channel_id, String mode, String lhs, String rhs, Dict(keymap) *opts, + Error *err) FUNC_API_SINCE(6) { - modify_keymap(-1, false, mode, lhs, rhs, opts, err); + modify_keymap(channel_id, -1, false, mode, lhs, rhs, opts, err); } /// Unmaps a global |mapping| for the given mode. @@ -1608,10 +1610,10 @@ void nvim_set_keymap(String mode, String lhs, String rhs, Dict(keymap) *opts, Er /// To unmap a buffer-local mapping, use |nvim_buf_del_keymap()|. /// /// @see |nvim_set_keymap()| -void nvim_del_keymap(String mode, String lhs, Error *err) +void nvim_del_keymap(uint64_t channel_id, String mode, String lhs, Error *err) FUNC_API_SINCE(6) { - nvim_buf_del_keymap(-1, mode, lhs, err); + nvim_buf_del_keymap(channel_id, -1, mode, lhs, err); } /// Gets a map of global (non-buffer-local) Ex commands. diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 640144b234..4123674fe7 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -37,7 +37,7 @@ /// @param[out] err Error details (Vim error), if any /// @return Output (non-error, non-shell |:!|) if `output` is true, /// else empty string. -String nvim_exec(String src, Boolean output, Error *err) +String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err) FUNC_API_SINCE(7) { const int save_msg_silent = msg_silent; @@ -52,11 +52,16 @@ String nvim_exec(String src, Boolean output, Error *err) if (output) { msg_silent++; } + + const sctx_T save_current_sctx = api_set_sctx(channel_id); + do_source_str(src.data, "nvim_exec()"); if (output) { capture_ga = save_capture_ga; msg_silent = save_msg_silent; } + + current_sctx = save_current_sctx; try_end(err); if (ERROR_SET(err)) { -- cgit From 0f613482b389ad259dd53d893907b024a115352e Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Fri, 28 May 2021 15:45:34 -0400 Subject: feat(lua): add missing changes to autocmds lost in the rebase Note: some of these changes are breaking, like change of API signatures --- src/nvim/api/autocmd.c | 221 +++++++++++++++++++++++------------------ src/nvim/api/keysets.lua | 4 - src/nvim/api/private/helpers.c | 5 +- src/nvim/api/private/helpers.h | 16 +-- 4 files changed, 138 insertions(+), 108 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index deb8ec8cf3..6b89eb1770 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -17,34 +17,7 @@ #define AUCMD_MAX_PATTERNS 256 -// Check whether every item in the array is a kObjectTypeString -#define CHECK_STRING_ARRAY(__array, k, v, goto_name) \ - for (size_t j = 0; j < __array.size; j++) { \ - Object item = __array.items[j]; \ - if (item.type != kObjectTypeString) { \ - api_set_error(err, \ - kErrorTypeValidation, \ - "All entries in '%s' must be strings", \ - k); \ - goto goto_name; \ - } \ - } - // Copy string or array of strings into an empty array. -#define UNPACK_STRING_OR_ARRAY(__array, k, v, goto_name) \ - if (v->type == kObjectTypeString) { \ - ADD(__array, copy_object(*v)); \ - } else if (v->type == kObjectTypeArray) { \ - CHECK_STRING_ARRAY(__array, k, v, goto_name); \ - __array = copy_array(v->data.array); \ - } else { \ - api_set_error(err, \ - kErrorTypeValidation, \ - "'%s' must be an array or a string.", \ - k); \ - goto goto_name; \ - } - // Get the event number, unless it is an error. Then goto `goto_name`. #define GET_ONE_EVENT(event_nr, event_str, goto_name) \ char_u *__next_ev; \ @@ -61,16 +34,18 @@ static int64_t next_autocmd_id = 1; /// Get autocmds that match the requirements passed to {opts}. -/// group -/// event -/// pattern /// -/// -- @param {string} event - event or events to match against -/// vim.api.nvim_get_autocmds({ event = "FileType" }) +/// @param opts Optional Parameters: +/// - event : Name or list of name of events to match against +/// - group (string): Name of group to match against +/// - pattern: Pattern or list of patterns to match against /// +/// @return A list of autocmds that match Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) FUNC_API_SINCE(9) { + // TODO(tjdevries): Would be cool to add nvim_get_autocmds({ id = ... }) + Array autocmd_list = ARRAY_DICT_INIT; char_u *pattern_filters[AUCMD_MAX_PATTERNS]; char_u pattern_buflocal[BUFLOCAL_PAT_LEN]; @@ -199,6 +174,7 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) if (aucmd_exec_is_deleted(ac->exec)) { continue; } + Dictionary autocmd_info = ARRAY_DICT_INIT; if (ap->group != AUGROUP_DEFAULT) { @@ -256,40 +232,42 @@ cleanup: return autocmd_list; } -/// Define an autocmd. -/// @param opts Dictionary +/// Create an autocmd. +/// +/// @param event The event or events to register this autocmd /// Required keys: /// event: string | ArrayOf(string) -/// event = "pat1,pat2,pat3", -/// event = "pat1" -/// event = {"pat1"} -/// event = {"pat1", "pat2", "pat3"} -/// -/// -/// -- @param {string} name - augroup name -/// -- @param {string | table} event - event or events to match against -/// -- @param {string | table} pattern - pattern or patterns to match against -/// -- @param {string | function} callback - function or string to execute on autocmd -/// -- @param {string} command - optional, vimscript command -/// Eg. command = "let g:value_set = v:true" -/// -- @param {boolean} once - optional, defaults to false /// -/// -- pattern = comma delimited list of patterns | pattern | { pattern, ... } +/// Examples: +/// - event: "pat1,pat2,pat3", +/// - event: "pat1" +/// - event: { "pat1" } +/// - event: { "pat1", "pat2", "pat3" } /// -/// pattern = "*.py,*.pyi" -/// pattern = "*.py" -/// pattern = {"*.py"} -/// pattern = { "*.py", "*.pyi" } +/// @param opts Optional Parameters: +/// - callback: (string|function) +/// - (string): The name of the viml function to execute when triggering this autocmd +/// - (function): The lua function to execute when triggering this autocmd +/// - NOTE: Cannot be used with {command} +/// - command: (string) command +/// - vimscript command +/// - NOTE: Cannot be used with {callback} +/// Eg. command = "let g:value_set = v:true" +/// - pattern: (string|table) +/// - pattern or patterns to match against +/// - defaults to "*". +/// - NOTE: Cannot be used with {buffer} +/// - buffer: (bufnr) +/// - create a |autocmd-buflocal| autocmd. +/// - NOTE: Cannot be used with {pattern} +/// - group: (string) The augroup name +/// - once: (boolean) - See |autocmd-once| +/// - nested: (boolean) - See |autocmd-nested| +/// - desc: (string) - Description of the autocmd /// -/// -- not supported -/// pattern = {"*.py,*.pyi"} -/// -/// -- event = string | string[] -/// event = "FileType,CursorHold" -/// event = "BufPreWrite" -/// event = {"BufPostWrite"} -/// event = {"CursorHold", "BufPreWrite", "BufPostWrite"} -Integer nvim_create_autocmd(uint64_t channel_id, Dict(create_autocmd) *opts, Error *err) +/// @returns opaque value to use with nvim_del_autocmd +Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autocmd) *opts, + Error *err) FUNC_API_SINCE(9) { int64_t autocmd_id = -1; @@ -304,6 +282,11 @@ Integer nvim_create_autocmd(uint64_t channel_id, Dict(create_autocmd) *opts, Err AucmdExecutable aucmd = AUCMD_EXECUTABLE_INIT; Callback cb = CALLBACK_NONE; + + if (!unpack_string_or_array(&event_array, &event, "event", err)) { + goto cleanup; + } + if (opts->callback.type != kObjectTypeNil && opts->command.type != kObjectTypeNil) { api_set_error(err, kErrorTypeValidation, "cannot pass both: 'callback' and 'command' for the same autocmd"); @@ -359,14 +342,10 @@ Integer nvim_create_autocmd(uint64_t channel_id, Dict(create_autocmd) *opts, Err goto cleanup; } - if (opts->event.type != kObjectTypeNil) { - UNPACK_STRING_OR_ARRAY(event_array, "event", (&opts->event), cleanup) - } - bool is_once = api_object_to_bool(opts->once, "once", false, err); bool is_nested = api_object_to_bool(opts->nested, "nested", false, err); - // TOOD: accept number for namespace instead + // TODO(tjdevries): accept number for namespace instead if (opts->group.type != kObjectTypeNil) { Object *v = &opts->group; if (v->type != kObjectTypeString) { @@ -402,7 +381,9 @@ Integer nvim_create_autocmd(uint64_t channel_id, Dict(create_autocmd) *opts, Err patlen = aucmd_pattern_length(pat); } } else if (v->type == kObjectTypeArray) { - CHECK_STRING_ARRAY(patterns, "pattern", v, cleanup); + if (!check_autocmd_string_array(patterns, "pattern", err)) { + goto cleanup; + } Array array = v->data.array; for (size_t i = 0; i < array.size; i++) { @@ -503,8 +484,9 @@ cleanup: return autocmd_id; } -/// Delete an autocmd by ID. Autocmds only return IDs when created -/// via the API. +/// Delete an autocmd by {id}. Autocmds only return IDs when created +/// via the API. Will not error if called and no autocmds match +/// the {id}. /// /// @param id Integer The ID returned by nvim_create_autocmd void nvim_del_autocmd(Integer id) @@ -517,28 +499,28 @@ void nvim_del_autocmd(Integer id) /// /// To get an existing augroup ID, do: ///
-///     local id = vim.api.nvim_create_augroup({ name = name, clear = false });
+///     local id = vim.api.nvim_create_augroup(name, {
+///         clear = false
+///     })
 /// 
/// +/// @param name String: The name of the augroup to create /// @param opts Parameters -/// - name (string): The name of the augroup /// - clear (bool): Whether to clear existing commands or not. -// Defaults to true. +/// Defaults to true. /// See |autocmd-groups| -Integer nvim_create_augroup(uint64_t channel_id, Dict(create_augroup) *opts, Error *err) +/// +/// @returns opaque value to use with nvim_del_augroup_by_id +Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augroup) *opts, + Error *err) FUNC_API_SINCE(9) { + char *augroup_name = name.data; bool clear_autocmds = api_object_to_bool(opts->clear, "clear", true, err); - if (opts->name.type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, "'name' is required and must be a string"); - return -1; - } - char *name = opts->name.data.string.data; - int augroup = -1; WITH_SCRIPT_CONTEXT(channel_id, { - augroup = augroup_add(name); + augroup = augroup_add(augroup_name); if (augroup == AUGROUP_ERROR) { api_set_error(err, kErrorTypeException, "Failed to set augroup"); return -1; @@ -554,7 +536,11 @@ Integer nvim_create_augroup(uint64_t channel_id, Dict(create_augroup) *opts, Err return augroup; } +/// Delete an augroup by {id}. {id} can only be returned when augroup was +/// created with |nvim_create_augroup|. +/// /// NOTE: behavior differs from augroup-delete. +/// /// When deleting an augroup, autocmds contained by this augroup will also be deleted and cleared. /// This augroup will no longer exist void nvim_del_augroup_by_id(Integer id) @@ -564,7 +550,10 @@ void nvim_del_augroup_by_id(Integer id) augroup_del(name, false); } +/// Delete an augroup by {name}. +/// /// NOTE: behavior differs from augroup-delete. +/// /// When deleting an augroup, autocmds contained by this augroup will also be deleted and cleared. /// This augroup will no longer exist void nvim_del_augroup_by_name(String name) @@ -573,12 +562,17 @@ void nvim_del_augroup_by_name(String name) augroup_del(name.data, false); } -/// -- @param {string} group - autocmd group name -/// -- @param {number} buffer - buffer number -/// -- @param {string | table} event - event or events to match against -/// -- @param {string | table} pattern - optional, defaults to "*". -/// vim.api.nvim_do_autcmd({ group, buffer, pattern, event, modeline }) -void nvim_do_autocmd(Dict(do_autocmd) *opts, Error *err) +/// Do one autocmd. +/// +/// @param event The event or events to execute +/// @param opts Optional Parameters: +/// - buffer (number) - buffer number +/// - NOTE: Cannot be used with {pattern} +/// - pattern (string|table) - optional, defaults to "*". +/// - NOTE: Cannot be used with {buffer} +/// - group (string) - autocmd group name +/// - modeline (boolean) - Default true, see || +void nvim_do_autocmd(Object event, Dict(do_autocmd) *opts, Error *err) FUNC_API_SINCE(9) { int au_group = AUGROUP_ALL; @@ -592,6 +586,10 @@ void nvim_do_autocmd(Dict(do_autocmd) *opts, Error *err) Array event_array = ARRAY_DICT_INIT; + if (!unpack_string_or_array(&event_array, &event, "event", err)) { + goto cleanup; + } + if (opts->group.type != kObjectTypeNil) { if (opts->group.type != kObjectTypeString) { api_set_error(err, kErrorTypeValidation, "'group' must be a string"); @@ -634,13 +632,7 @@ void nvim_do_autocmd(Dict(do_autocmd) *opts, Error *err) set_pattern = true; } - if (opts->event.type != kObjectTypeNil) { - UNPACK_STRING_OR_ARRAY(event_array, "event", (&opts->event), cleanup) - } - - if (opts->modeline.type != kObjectTypeNil) { - modeline = api_object_to_bool(opts->modeline, "modeline", true, err); - } + modeline = api_object_to_bool(opts->modeline, "modeline", true, err); if (set_pattern && set_buf) { api_set_error(err, kErrorTypeValidation, "must not set 'buffer' and 'pattern'"); @@ -663,7 +655,46 @@ cleanup: XFREE_CLEAR(pattern); } +static bool check_autocmd_string_array(Array arr, char *k, Error *err) +{ + for (size_t i = 0; i < arr.size; i++) { + if (arr.items[i].type != kObjectTypeString) { + api_set_error(err, + kErrorTypeValidation, + "All entries in '%s' must be strings", + k); + return false; + } + + // Disallow newlines in the middle of the line. + const String l = arr.items[i].data.string; + if (memchr(l.data, NL, l.size)) { + api_set_error(err, kErrorTypeValidation, + "String cannot contain newlines"); + return false; + } + } + return true; +} + +static bool unpack_string_or_array(Array *array, Object *v, char *k, Error *err) +{ + if (v->type == kObjectTypeString) { + ADD(*array, copy_object(*v)); + } else if (v->type == kObjectTypeArray) { + if (!check_autocmd_string_array(v->data.array, k, err)) { + return false; + } + *array = copy_array(v->data.array); + } else { + api_set_error(err, + kErrorTypeValidation, + "'%s' must be an array or a string.", + k); + return false; + } + + return true; +} -#undef UNPACK_STRING_OR_ARRAY -#undef CHECK_STRING_ARRAY #undef GET_ONE_EVENT diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index be71c446b1..b05816f8ac 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -116,7 +116,6 @@ return { "callback"; "command"; "desc"; - "event"; "group"; "once"; "nested"; @@ -124,7 +123,6 @@ return { }; do_autocmd = { "buffer"; - "event"; "group"; "modeline"; "pattern"; @@ -132,12 +130,10 @@ return { get_autocmds = { "event"; "group"; - "id"; "pattern"; }; create_augroup = { "clear"; - "name"; }; } diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 3d4a04f096..c2106855d3 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -406,6 +406,7 @@ void set_option_to(uint64_t channel_id, void *to, int type, String name, Object }); } + buf_T *find_buffer_by_handle(Buffer buffer, Error *err) { if (buffer == 0) { @@ -1614,8 +1615,8 @@ int find_sid(uint64_t channel_id) { switch (channel_id) { case VIML_INTERNAL_CALL: - // TODO(autocmd): Figure out what this should be - // return SID_API_CLIENT; + // TODO(autocmd): Figure out what this should be + // return SID_API_CLIENT; case LUA_INTERNAL_CALL: return SID_LUA; default: diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 6969994c3b..bc7c2e6a60 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -152,13 +152,15 @@ typedef struct { #endif #define WITH_SCRIPT_CONTEXT(channel_id, code) \ - const sctx_T save_current_sctx = current_sctx; \ - current_sctx.sc_sid = \ - (channel_id) == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; \ - current_sctx.sc_lnum = 0; \ - current_channel_id = channel_id; \ - code; \ - current_sctx = save_current_sctx; + do { \ + const sctx_T save_current_sctx = current_sctx; \ + current_sctx.sc_sid = \ + (channel_id) == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; \ + current_sctx.sc_lnum = 0; \ + current_channel_id = channel_id; \ + code; \ + current_sctx = save_current_sctx; \ + } while (0); #endif // NVIM_API_PRIVATE_HELPERS_H -- cgit From 37a86a2f964b5d6f9dbfae8b78acaa3a71f981bb Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 1 Mar 2022 09:07:41 +0100 Subject: fix(api): include event in get_autocmds (#17553) --- src/nvim/api/autocmd.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 6b89eb1770..ad0e78cb50 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -197,6 +197,10 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) "pattern", STRING_OBJ(cstr_to_string((char *)ap->pat))); + PUT(autocmd_info, + "event", + STRING_OBJ(cstr_to_string((char *)event_nr2name(event)))); + PUT(autocmd_info, "once", BOOLEAN_OBJ(ac->once)); if (ap->buflocal_nr) { -- cgit From 045422e4a081e9dedff014346cc32eaef45e04e1 Mon Sep 17 00:00:00 2001 From: Charlie Groves Date: Tue, 1 Mar 2022 15:58:42 -0500 Subject: fix: respect os_proc_children rv of pid not found os_proc_children returns 2 if there's a failure in the underlying syscall. Only shell out to pgrep in that case. It returns 1 if the pid isn't found. In that case, we can roll forward with returning an empty list. --- src/nvim/api/vim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 9d0b096a36..c37df45c14 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1991,7 +1991,7 @@ Array nvim_get_proc_children(Integer pid, Error *err) size_t proc_count; int rv = os_proc_children((int)pid, &proc_list, &proc_count); - if (rv != 0) { + if (rv == 2) { // syscall failed (possibly because of kernel options), try shelling out. DLOG("fallback to vim._os_proc_children()"); Array a = ARRAY_DICT_INIT; -- cgit From 55a189583ea0a7b2e25fb1db7b43d80817bdf4af Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 2 Mar 2022 08:50:15 +0800 Subject: chore(PVS): add PVS header to api/autocmd.c --- src/nvim/api/autocmd.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index ad0e78cb50..9a8eccd22c 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -1,3 +1,6 @@ +// 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 #include -- cgit From 22d1b2423f6abe177ce8a99f460cfedd5bc7eecc Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 2 Mar 2022 15:14:11 +0800 Subject: refactor(PVS/V560): ap == NULL is always false --- src/nvim/api/autocmd.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 9a8eccd22c..2e19987609 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -131,10 +131,8 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) continue; } - for (AutoPat *ap = au_get_autopat_for_event(event); - ap != NULL; - ap = ap->next) { - if (ap == NULL || ap->cmds == NULL) { + for (AutoPat *ap = au_get_autopat_for_event(event); ap != NULL; ap = ap->next) { + if (ap->cmds == NULL) { continue; } -- cgit From 8ba47a64571b936c651a716db40e02685aedb160 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 2 Mar 2022 15:14:52 +0800 Subject: refactor(PVS/V547): aucmd.type == CALLABLE_NONE is always false --- src/nvim/api/autocmd.c | 7 ------- 1 file changed, 7 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 2e19987609..0ad7b320d0 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -424,13 +424,6 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc ADD(patterns, STRING_OBJ(cstr_to_string((char *)pattern_buflocal))); } - if (aucmd.type == CALLABLE_NONE) { - api_set_error(err, - kErrorTypeValidation, - "'command' or 'callback' is required"); - goto cleanup; - } - if (opts->desc.type != kObjectTypeNil) { if (opts->desc.type == kObjectTypeString) { desc = opts->desc.data.string.data; -- cgit From f89fb41a7a8b499159bfa44afa26dd17a845af45 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Wed, 2 Mar 2022 00:48:11 +0300 Subject: feat(tui): add support for `CSI 4 : [2,4,5] m` This commit finishes support for colored and styled underlines adding `CSI 4 : [2,4,5] m` support providing double, dashed, and dotted underlines Fixes #17362. --- src/nvim/api/keysets.lua | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index b05816f8ac..4676ec7596 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -83,7 +83,10 @@ return { "standout"; "strikethrough"; "underline"; + "underlineline"; "undercurl"; + "underdot"; + "underdash"; "italic"; "reverse"; "nocombine"; @@ -105,7 +108,10 @@ return { "standout"; "strikethrough"; "underline"; + "underlineline"; "undercurl"; + "underdot"; + "underdash"; "italic"; "reverse"; "nocombine"; -- cgit From 30e4cc3b3f2133e9a7170da9da8175832681f39a Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 3 Jan 2022 12:22:13 +0000 Subject: feat(decorations): support signs Add the following options to extmarks: - sign_text - sign_hl_group - number_hl_group - line_hl_group - cursorline_hl_group Note: ranges are unsupported and decorations are only applied to start_row --- src/nvim/api/extmark.c | 56 +++++++++++++++++++++++++++++++++++++++--- src/nvim/api/keysets.lua | 5 ++++ src/nvim/api/private/helpers.c | 36 +++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 4 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 3a968f07ab..0ee8134ec4 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -445,6 +445,27 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// - strict: boolean that indicates extmark should not be placed /// if the line or column value is past the end of the /// buffer or end of the line respectively. Defaults to true. +/// - sign_text: string of length 1-2 used to display in the +/// sign column. +/// Note: ranges are unsupported and decorations are only +/// applied to start_row +/// - sign_hl_group: name of the highlight group used to +/// highlight the sign column text. +/// Note: ranges are unsupported and decorations are only +/// applied to start_row +/// - number_hl_group: name of the highlight group used to +/// highlight the number column. +/// Note: ranges are unsupported and decorations are only +/// applied to start_row +/// - line_hl_group: name of the highlight group used to +/// highlight the whole line. +/// Note: ranges are unsupported and decorations are only +/// applied to start_row +/// - cursorline_hl_group: name of the highlight group used to +/// highlight the line when the cursor is on the same line +/// as the mark and 'cursorline' is enabled. +/// Note: ranges are unsupported and decorations are only +/// applied to start_row /// /// @param[out] err Error details, if any /// @return Id of the created/updated extmark @@ -519,10 +540,25 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, 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; + 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 }, + { NULL, NULL, NULL }, + }; + + for (int j = 0; hls[j].name && hls[j].dest; j++) { + if (HAS_KEY(*hls[j].opt)) { + *hls[j].dest = object_to_hl_id(*hls[j].opt, hls[j].name, err); + if (ERROR_SET(err)) { + goto error; + } } } @@ -622,6 +658,17 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer goto error; } + if (opts->sign_text.type == kObjectTypeString) { + if (!init_sign_text(&decor.sign_text, + (char_u *)opts->sign_text.data.string.data)) { + api_set_error(err, kErrorTypeValidation, "sign_text is not a valid value"); + goto error; + } + } else if (HAS_KEY(opts->sign_text)) { + api_set_error(err, kErrorTypeValidation, "sign_text is not a String"); + goto error; + } + bool right_gravity = true; OPTION_TO_BOOL(right_gravity, right_gravity, true); @@ -709,6 +756,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer error: clear_virttext(&decor.virt_text); + xfree(decor.sign_text); return 0; } diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index b05816f8ac..856b336a98 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -22,6 +22,11 @@ return { "virt_lines_above"; "virt_lines_leftcol"; "strict"; + "sign_text"; + "sign_hl_group"; + "number_hl_group"; + "line_hl_group"; + "cursorline_hl_group"; }; keymap = { "noremap"; diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 35e8589255..8056950e26 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1643,3 +1643,39 @@ sctx_T api_set_sctx(uint64_t channel_id) } return old_current_sctx; } + +// adapted from sign.c:sign_define_init_text. +// TODO(lewis6991): Consider merging +int init_sign_text(char_u **sign_text, char_u *text) +{ + char_u *s; + + char_u *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 = vim_strnsave(text, len); + + if (cells == 1) { + STRCPY(*sign_text + len - 1, " "); + } + + return OK; +} -- cgit From 92349b1db0039aac3a43089d0aade2437164505c Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Sun, 6 Mar 2022 12:35:14 -0700 Subject: feat(api): add 'buffer' argument to nvim_get_autocmds (#17594) This enables retrieving autocommands defined in the given buffers. Under the hood this simply translates the buffer numbers into '' patterns. --- src/nvim/api/autocmd.c | 66 ++++++++++++++++++++++++++++++++++++++++++++---- src/nvim/api/keysets.lua | 1 + 2 files changed, 62 insertions(+), 5 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 0ad7b320d0..685667ac64 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -41,7 +41,9 @@ static int64_t next_autocmd_id = 1; /// @param opts Optional Parameters: /// - event : Name or list of name of events to match against /// - group (string): Name of group to match against -/// - pattern: Pattern or list of patterns to match against +/// - pattern: Pattern or list of patterns to match against. Cannot be used with {buffer} +/// - buffer: Buffer number or list of buffer numbers for buffer local autocommands +/// |autocmd-buflocal|. Cannot be used with {pattern} /// /// @return A list of autocmds that match Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) @@ -53,6 +55,8 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) char_u *pattern_filters[AUCMD_MAX_PATTERNS]; char_u pattern_buflocal[BUFLOCAL_PAT_LEN]; + Array buffers = ARRAY_DICT_INIT; + bool event_set[NUM_EVENTS] = { false }; bool check_event = false; @@ -100,6 +104,12 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) } } + if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) { + api_set_error(err, kErrorTypeValidation, + "Cannot use both 'pattern' and 'buffer'"); + goto cleanup; + } + int pattern_filter_count = 0; if (opts->pattern.type != kObjectTypeNil) { Object v = opts->pattern; @@ -107,25 +117,70 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) pattern_filters[pattern_filter_count] = (char_u *)v.data.string.data; pattern_filter_count += 1; } else if (v.type == kObjectTypeArray) { + if (v.data.array.size > AUCMD_MAX_PATTERNS) { + api_set_error(err, kErrorTypeValidation, + "Too many patterns. Please limit yourself to %d or fewer", + AUCMD_MAX_PATTERNS); + goto cleanup; + } + FOREACH_ITEM(v.data.array, item, { + if (item.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'pattern': must be a string"); + goto cleanup; + } + pattern_filters[pattern_filter_count] = (char_u *)item.data.string.data; pattern_filter_count += 1; }); } else { - api_set_error(err, - kErrorTypeValidation, + api_set_error(err, kErrorTypeValidation, "Not a valid 'pattern' value. Must be a string or an array"); goto cleanup; } + } + + if (opts->buffer.type == kObjectTypeInteger || opts->buffer.type == kObjectTypeBuffer) { + buf_T *buf = find_buffer_by_handle((Buffer)opts->buffer.data.integer, err); + if (ERROR_SET(err)) { + goto cleanup; + } - if (pattern_filter_count >= AUCMD_MAX_PATTERNS) { + snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "", (int)buf->handle); + ADD(buffers, CSTR_TO_OBJ((char *)pattern_buflocal)); + } else if (opts->buffer.type == kObjectTypeArray) { + if (opts->buffer.data.array.size > AUCMD_MAX_PATTERNS) { api_set_error(err, kErrorTypeValidation, - "Too many patterns. Please limit yourself to less"); + "Too many buffers. Please limit yourself to %d or fewer", AUCMD_MAX_PATTERNS); goto cleanup; } + + FOREACH_ITEM(opts->buffer.data.array, bufnr, { + if (bufnr.type != kObjectTypeInteger && bufnr.type != kObjectTypeBuffer) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'buffer': must be an integer"); + goto cleanup; + } + + buf_T *buf = find_buffer_by_handle((Buffer)bufnr.data.integer, err); + if (ERROR_SET(err)) { + goto cleanup; + } + + snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "", (int)buf->handle); + ADD(buffers, CSTR_TO_OBJ((char *)pattern_buflocal)); + }); + } else if (opts->buffer.type != kObjectTypeNil) { + api_set_error(err, kErrorTypeValidation, + "Invalid value for 'buffer': must be an integer or array of integers"); + goto cleanup; } + FOREACH_ITEM(buffers, bufnr, { + pattern_filters[pattern_filter_count] = (char_u *)bufnr.data.string.data; + pattern_filter_count += 1; + }); + FOR_ALL_AUEVENTS(event) { if (check_event && !event_set[event]) { continue; @@ -234,6 +289,7 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) } cleanup: + api_free_array(buffers); return autocmd_list; } diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index 32d4e98822..435e8195dd 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -142,6 +142,7 @@ return { "event"; "group"; "pattern"; + "buffer"; }; create_augroup = { "clear"; -- cgit From 2783f4cc4a410cd3b73e8cdfbdf8c859c426c6c6 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Tue, 8 Mar 2022 09:45:43 +0530 Subject: feat(api): autocmd `group` can be either name or id (#17559) * feat(api): `group` can be either string or int This affects the following API functions: - `vim.api.nvim_create_autocmd` - `vim.api.nvim_get_autocmds` - `vim.api.nvim_do_autocmd` closes #17552 * refactor: add two maps for fast lookups * fix: delete augroup info from id->name map When in "stupid_legacy_mode", the value in name->id map would be updated to `AUGROUP_DELETED`, but the entry would still remain in id->name. This would create a problem in `augroup_name` function which would return the name of the augroup instead of `--DELETED--`. The id->name map is only used for fast loopup in `augroup_name` function so there's no point in keeping the entry of deleted augroup in it. Co-authored-by: TJ DeVries --- src/nvim/api/autocmd.c | 112 +++++++++++++++++++++++++++++-------------------- 1 file changed, 67 insertions(+), 45 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 685667ac64..5ede0e5265 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -40,7 +40,7 @@ static int64_t next_autocmd_id = 1; /// /// @param opts Optional Parameters: /// - event : Name or list of name of events to match against -/// - group (string): Name of group to match against +/// - group (string|int): Name or id of group to match against /// - pattern: Pattern or list of patterns to match against. Cannot be used with {buffer} /// - buffer: Buffer number or list of buffer numbers for buffer local autocommands /// |autocmd-buflocal|. Cannot be used with {pattern} @@ -62,19 +62,27 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) int group = 0; - if (opts->group.type != kObjectTypeNil) { - Object v = opts->group; - if (v.type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, "group must be a string."); - goto cleanup; - } - - group = augroup_find(v.data.string.data); - - if (group < 0) { - api_set_error(err, kErrorTypeValidation, "invalid augroup passed."); + switch (opts->group.type) { + case kObjectTypeNil: + break; + case kObjectTypeString: + group = augroup_find(opts->group.data.string.data); + if (group < 0) { + api_set_error(err, kErrorTypeValidation, "invalid augroup passed."); + goto cleanup; + } + break; + case kObjectTypeInteger: + group = (int)opts->group.data.integer; + char *name = augroup_name(group); + if (!augroup_exists(name)) { + api_set_error(err, kErrorTypeValidation, "invalid augroup passed."); + goto cleanup; + } + break; + default: + api_set_error(err, kErrorTypeValidation, "group must be a string or an integer."); goto cleanup; - } } if (opts->event.type != kObjectTypeNil) { @@ -321,7 +329,7 @@ cleanup: /// - buffer: (bufnr) /// - create a |autocmd-buflocal| autocmd. /// - NOTE: Cannot be used with {pattern} -/// - group: (string) The augroup name +/// - group: (string|int) The augroup name or id /// - once: (boolean) - See |autocmd-once| /// - nested: (boolean) - See |autocmd-nested| /// - desc: (string) - Description of the autocmd @@ -406,23 +414,29 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc bool is_once = api_object_to_bool(opts->once, "once", false, err); bool is_nested = api_object_to_bool(opts->nested, "nested", false, err); - // TODO(tjdevries): accept number for namespace instead - if (opts->group.type != kObjectTypeNil) { - Object *v = &opts->group; - if (v->type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, "'group' must be a string"); - goto cleanup; - } - - au_group = augroup_find(v->data.string.data); - - if (au_group == AUGROUP_ERROR) { - api_set_error(err, - kErrorTypeException, - "invalid augroup: %s", v->data.string.data); - + switch (opts->group.type) { + case kObjectTypeNil: + break; + case kObjectTypeString: + au_group = augroup_find(opts->group.data.string.data); + if (au_group == AUGROUP_ERROR) { + api_set_error(err, + kErrorTypeValidation, + "invalid augroup: %s", opts->group.data.string.data); + goto cleanup; + } + break; + case kObjectTypeInteger: + au_group = (int)opts->group.data.integer; + char *name = augroup_name(au_group); + if (!augroup_exists(name)) { + api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group); + goto cleanup; + } + break; + default: + api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer."); goto cleanup; - } } if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) { @@ -624,7 +638,7 @@ void nvim_del_augroup_by_name(String name) /// - NOTE: Cannot be used with {pattern} /// - pattern (string|table) - optional, defaults to "*". /// - NOTE: Cannot be used with {buffer} -/// - group (string) - autocmd group name +/// - group (string|int) - autocmd group name or id /// - modeline (boolean) - Default true, see || void nvim_do_autocmd(Object event, Dict(do_autocmd) *opts, Error *err) FUNC_API_SINCE(9) @@ -644,21 +658,29 @@ void nvim_do_autocmd(Object event, Dict(do_autocmd) *opts, Error *err) goto cleanup; } - if (opts->group.type != kObjectTypeNil) { - if (opts->group.type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, "'group' must be a string"); - goto cleanup; - } - - au_group = augroup_find(opts->group.data.string.data); - - if (au_group == AUGROUP_ERROR) { - api_set_error(err, - kErrorTypeException, - "invalid augroup: %s", opts->group.data.string.data); - + switch (opts->group.type) { + case kObjectTypeNil: + break; + case kObjectTypeString: + au_group = augroup_find(opts->group.data.string.data); + if (au_group == AUGROUP_ERROR) { + api_set_error(err, + kErrorTypeValidation, + "invalid augroup: %s", opts->group.data.string.data); + goto cleanup; + } + break; + case kObjectTypeInteger: + au_group = (int)opts->group.data.integer; + char *name = augroup_name(au_group); + if (!augroup_exists(name)) { + api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group); + goto cleanup; + } + break; + default: + api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer."); goto cleanup; - } } if (opts->buffer.type != kObjectTypeNil) { -- cgit From 3011794c8600f529bc049983a64ca99ae03908df Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 10 Mar 2022 07:18:49 +0800 Subject: feat(api): relax statusline fillchar width check Treat fillchar as single-width even if it isn't. --- src/nvim/api/vim.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index c37df45c14..1a324bfaa5 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -2241,7 +2241,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err) /// - winid: (number) |window-ID| of the window to use as context for statusline. /// - maxwidth: (number) Maximum width of statusline. /// - fillchar: (string) Character to fill blank spaces in the statusline (see -/// 'fillchars'). +/// 'fillchars'). Treated as single-width even if it isn't. /// - highlights: (boolean) Return highlight information. /// - use_tabline: (boolean) Evaluate tabline instead of statusline. When |TRUE|, {winid} /// is ignored. @@ -2277,11 +2277,12 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * if (HAS_KEY(opts->fillchar)) { if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size == 0 - || char2cells(fillchar = utf_ptr2char((char_u *)opts->fillchar.data.string.data)) != 1 - || (size_t)utf_char2len(fillchar) != opts->fillchar.data.string.size) { - api_set_error(err, kErrorTypeValidation, "fillchar must be a single-width character"); + || ((size_t)utf_ptr2len((char_u *)opts->fillchar.data.string.data) + != opts->fillchar.data.string.size)) { + api_set_error(err, kErrorTypeValidation, "fillchar must be a single character"); return result; } + fillchar = utf_ptr2char((char_u *)opts->fillchar.data.string.data); } if (HAS_KEY(opts->highlights)) { -- cgit From a7b1c8893c602196541e94b8b24c4c70c32c25b0 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Thu, 10 Mar 2022 07:34:55 +0100 Subject: chore: fix typos (#17331) Co-authored-by: Hongyi Lyu Co-authored-by: Gregory Anders Co-authored-by: notomo Co-authored-by: zeertzjq --- src/nvim/api/buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 3dd647e76f..3bddbe8fe6 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -133,7 +133,7 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err) /// - buffer handle /// - on_reload: Lua callback invoked on reload. The entire buffer /// content should be considered changed. Args: -/// - the string "detach" +/// - the string "reload" /// - buffer handle /// - utf_sizes: include UTF-32 and UTF-16 size of the replaced /// region, as args to `on_lines`. -- cgit From 7e3bdc75e44b9139d8afaea4381b53ae78b15746 Mon Sep 17 00:00:00 2001 From: Dundar Göc Date: Wed, 9 Mar 2022 21:19:37 +0100 Subject: refactor(uncrustify): format all c files --- src/nvim/api/autocmd.c | 122 ++++++++++++++++++++++++------------------------- src/nvim/api/buffer.c | 7 ++- src/nvim/api/vim.c | 18 ++++---- 3 files changed, 73 insertions(+), 74 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 5ede0e5265..8a7dd00b2a 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -63,26 +63,26 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) int group = 0; switch (opts->group.type) { - case kObjectTypeNil: - break; - case kObjectTypeString: - group = augroup_find(opts->group.data.string.data); - if (group < 0) { - api_set_error(err, kErrorTypeValidation, "invalid augroup passed."); - goto cleanup; - } - break; - case kObjectTypeInteger: - group = (int)opts->group.data.integer; - char *name = augroup_name(group); - if (!augroup_exists(name)) { - api_set_error(err, kErrorTypeValidation, "invalid augroup passed."); - goto cleanup; - } - break; - default: - api_set_error(err, kErrorTypeValidation, "group must be a string or an integer."); + case kObjectTypeNil: + break; + case kObjectTypeString: + group = augroup_find(opts->group.data.string.data); + if (group < 0) { + api_set_error(err, kErrorTypeValidation, "invalid augroup passed."); goto cleanup; + } + break; + case kObjectTypeInteger: + group = (int)opts->group.data.integer; + char *name = augroup_name(group); + if (!augroup_exists(name)) { + api_set_error(err, kErrorTypeValidation, "invalid augroup passed."); + goto cleanup; + } + break; + default: + api_set_error(err, kErrorTypeValidation, "group must be a string or an integer."); + goto cleanup; } if (opts->event.type != kObjectTypeNil) { @@ -415,28 +415,28 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc bool is_nested = api_object_to_bool(opts->nested, "nested", false, err); switch (opts->group.type) { - case kObjectTypeNil: - break; - case kObjectTypeString: - au_group = augroup_find(opts->group.data.string.data); - if (au_group == AUGROUP_ERROR) { - api_set_error(err, - kErrorTypeValidation, - "invalid augroup: %s", opts->group.data.string.data); - goto cleanup; - } - break; - case kObjectTypeInteger: - au_group = (int)opts->group.data.integer; - char *name = augroup_name(au_group); - if (!augroup_exists(name)) { - api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group); - goto cleanup; - } - break; - default: - api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer."); + case kObjectTypeNil: + break; + case kObjectTypeString: + au_group = augroup_find(opts->group.data.string.data); + if (au_group == AUGROUP_ERROR) { + api_set_error(err, + kErrorTypeValidation, + "invalid augroup: %s", opts->group.data.string.data); goto cleanup; + } + break; + case kObjectTypeInteger: + au_group = (int)opts->group.data.integer; + char *name = augroup_name(au_group); + if (!augroup_exists(name)) { + api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group); + goto cleanup; + } + break; + default: + api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer."); + goto cleanup; } if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) { @@ -659,28 +659,28 @@ void nvim_do_autocmd(Object event, Dict(do_autocmd) *opts, Error *err) } switch (opts->group.type) { - case kObjectTypeNil: - break; - case kObjectTypeString: - au_group = augroup_find(opts->group.data.string.data); - if (au_group == AUGROUP_ERROR) { - api_set_error(err, - kErrorTypeValidation, - "invalid augroup: %s", opts->group.data.string.data); - goto cleanup; - } - break; - case kObjectTypeInteger: - au_group = (int)opts->group.data.integer; - char *name = augroup_name(au_group); - if (!augroup_exists(name)) { - api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group); - goto cleanup; - } - break; - default: - api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer."); + case kObjectTypeNil: + break; + case kObjectTypeString: + au_group = augroup_find(opts->group.data.string.data); + if (au_group == AUGROUP_ERROR) { + api_set_error(err, + kErrorTypeValidation, + "invalid augroup: %s", opts->group.data.string.data); goto cleanup; + } + break; + case kObjectTypeInteger: + au_group = (int)opts->group.data.integer; + char *name = augroup_name(au_group); + if (!augroup_exists(name)) { + api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group); + goto cleanup; + } + break; + default: + api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer."); + goto cleanup; } if (opts->buffer.type != kObjectTypeNil) { diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 3bddbe8fe6..a3af51008f 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -965,8 +965,7 @@ void nvim_buf_set_keymap(uint64_t channel_id, Buffer buffer, String mode, String /// @see |nvim_del_keymap()| /// /// @param buffer Buffer handle, or 0 for current buffer -void nvim_buf_del_keymap(uint64_t channel_id, Buffer buffer, String mode, - String lhs, Error *err) +void nvim_buf_del_keymap(uint64_t channel_id, Buffer buffer, String mode, String lhs, Error *err) FUNC_API_SINCE(6) { String rhs = { .data = "", .size = 0 }; @@ -1380,8 +1379,8 @@ Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err) /// @param buffer Buffer handle, or 0 for current buffer. /// @param[out] err Error details, if any. /// @see nvim_add_user_command -void nvim_buf_add_user_command(Buffer buffer, String name, Object command, - Dict(user_command) *opts, Error *err) +void nvim_buf_add_user_command(Buffer buffer, String name, Object command, Dict(user_command) *opts, + Error *err) FUNC_API_SINCE(9) { buf_T *target_buf = find_buffer_by_handle(buffer, err); diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 1a324bfaa5..b2d866ae0e 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -722,15 +722,15 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) break; case 2: switch (numval) { - case 0: - case 1: - rv = BOOLEAN_OBJ(numval); - break; - default: - // Boolean options that return something other than 0 or 1 should return nil. Currently this - // only applies to 'autoread' which uses -1 as a local value to indicate "unset" - rv = NIL; - break; + case 0: + case 1: + rv = BOOLEAN_OBJ(numval); + break; + default: + // Boolean options that return something other than 0 or 1 should return nil. Currently this + // only applies to 'autoread' which uses -1 as a local value to indicate "unset" + rv = NIL; + break; } break; default: -- cgit From 1b054119ec1a7208b49feeaa496c2e1d55252989 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 10 Mar 2022 15:17:06 +0000 Subject: refactor(decorations): move provider code Move decoration provider code to a separate file. --- src/nvim/api/extmark.c | 1 + src/nvim/api/vim.c | 1 + 2 files changed, 2 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 0ee8134ec4..e355f82f4d 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -8,6 +8,7 @@ #include "nvim/api/extmark.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/decoration_provider.h" #include "nvim/extmark.h" #include "nvim/lua/executor.h" #include "nvim/memline.h" diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 1a324bfaa5..1573c6e0e3 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -22,6 +22,7 @@ #include "nvim/charset.h" #include "nvim/context.h" #include "nvim/decoration.h" +#include "nvim/decoration_provider.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" -- cgit From 5862176764c7a86d5fdd2685122810e14a3d5b02 Mon Sep 17 00:00:00 2001 From: Charlie Groves Date: Wed, 16 Feb 2022 17:11:50 -0500 Subject: feat(remote): add basic --remote support This is starting from @geekodour's work at https://github.com/neovim/neovim/pull/8326 --- src/nvim/api/vim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index ebf4f65c91..b82a7553cb 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1997,7 +1997,7 @@ Array nvim_get_proc_children(Integer pid, Error *err) DLOG("fallback to vim._os_proc_children()"); Array a = ARRAY_DICT_INIT; ADD(a, INTEGER_OBJ(pid)); - String s = cstr_to_string("return vim._os_proc_children(select(1, ...))"); + String s = cstr_to_string("return vim._os_proc_children(select(...))"); Object o = nlua_exec(s, a, err); api_free_string(s); api_free_array(a); -- cgit From 039e94f491d2f8576cbef1aeacd5ea1f7bc0982a Mon Sep 17 00:00:00 2001 From: Charlie Groves Date: Thu, 24 Feb 2022 10:47:41 -0500 Subject: test(remote): add tests for --remote This also fixes a fair number of issues found in running the tests --- src/nvim/api/vim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index b82a7553cb..b691dee2ef 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1997,7 +1997,7 @@ Array nvim_get_proc_children(Integer pid, Error *err) DLOG("fallback to vim._os_proc_children()"); Array a = ARRAY_DICT_INIT; ADD(a, INTEGER_OBJ(pid)); - String s = cstr_to_string("return vim._os_proc_children(select(...))"); + String s = cstr_to_string("return vim._os_proc_children(...)"); Object o = nlua_exec(s, a, err); api_free_string(s); api_free_array(a); -- cgit From 5051510ade5f171c1239906c8638e804356186fe Mon Sep 17 00:00:00 2001 From: erw7 Date: Fri, 12 Nov 2021 00:07:03 +0900 Subject: fix(channel): fix channel consistency - Fix the problem that chanclose() does not work for channel created by nvim_open_term(). - Fix the problem that the loopback channel is not released. - Fix the error message when sending raw data to the loopback channel. --- src/nvim/api/vim.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index b691dee2ef..c7ccc6bfeb 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1140,6 +1140,7 @@ Integer nvim_open_term(Buffer buffer, DictionaryOf(LuaRef) opts, Error *err) TerminalOptions topts; Channel *chan = channel_alloc(kChannelStreamInternal); chan->stream.internal.cb = cb; + chan->stream.internal.closed = false; topts.data = chan; // NB: overridden in terminal_check_size if a window is already // displaying the buffer -- cgit From 3a12737e6c13e9be774483f34655e7ac96e36c09 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 2 Nov 2019 15:06:32 +0100 Subject: refactor(main): separate connection code from --remote execution code --- src/nvim/api/vim.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index b691dee2ef..a942c94f46 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1997,9 +1997,8 @@ Array nvim_get_proc_children(Integer pid, Error *err) DLOG("fallback to vim._os_proc_children()"); Array a = ARRAY_DICT_INIT; ADD(a, INTEGER_OBJ(pid)); - String s = cstr_to_string("return vim._os_proc_children(...)"); + String s = STATIC_CSTR_AS_STRING("return vim._os_proc_children(...)"); Object o = nlua_exec(s, a, err); - api_free_string(s); api_free_array(a); if (o.type == kObjectTypeArray) { rvobj = o.data.array; -- cgit From a4400bf8cda8ace4c4aab67bc73a1820478f46f1 Mon Sep 17 00:00:00 2001 From: bfredl Date: Sat, 12 Mar 2022 13:47:50 +0100 Subject: feat(ui): connect to remote ui (only debug messages for now) co-authored-by: hlpr98 --- src/nvim/api/private/dispatch.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c index f670f06357..ba2e560d63 100644 --- a/src/nvim/api/private/dispatch.c +++ b/src/nvim/api/private/dispatch.c @@ -30,6 +30,7 @@ #include "nvim/api/vimscript.h" #include "nvim/api/win_config.h" #include "nvim/api/window.h" +#include "nvim/ui_client.h" static Map(String, MsgpackRpcRequestHandler) methods = MAP_INIT; @@ -38,6 +39,13 @@ static void msgpack_rpc_add_method_handler(String method, MsgpackRpcRequestHandl map_put(String, MsgpackRpcRequestHandler)(&methods, method, handler); } +void msgpack_rpc_add_redraw(void) +{ + msgpack_rpc_add_method_handler(STATIC_CSTR_AS_STRING("redraw"), + (MsgpackRpcRequestHandler) { .fn = ui_client_handle_redraw, + .fast = true }); +} + /// @param name API method name /// @param name_len name size (includes terminating NUL) MsgpackRpcRequestHandler msgpack_rpc_get_handler_for(const char *name, size_t name_len, -- cgit From 9e6bc228ec58b787c0985a65139d1959c9d889f0 Mon Sep 17 00:00:00 2001 From: adrian5 Date: Sun, 13 Mar 2022 13:42:12 +0100 Subject: docs(api): improve section on nvim_set_hl (#17692) --- src/nvim/api/vim.c | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index b8c66a034c..80fa677485 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -123,26 +123,24 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) abort(); } -/// Set a highlight group. +/// Sets a highlight group. /// -/// Note: unlike the `:highlight` command which can update a highlight group, +/// Note: Unlike the `:highlight` command which can update a highlight group, /// this function completely replaces the definition. For example: /// `nvim_set_hl(0, 'Visual', {})` will clear the highlight group 'Visual'. /// -/// @param ns_id number of namespace for this highlight. Use value 0 -/// to set a highlight group in the global (`:highlight`) -/// namespace. -/// @param name highlight group name, like ErrorMsg -/// @param val highlight definition map, like |nvim_get_hl_by_name|. -/// in addition the following keys are also recognized: -/// `default`: don't override existing definition, -/// like `hi default` -/// `ctermfg`: sets foreground of cterm color -/// `ctermbg`: sets background of cterm color -/// `cterm` : cterm attribute map. sets attributed for -/// cterm colors. similer to `hi cterm` -/// Note: by default cterm attributes are -/// same as attributes of gui color +/// @param ns_id Namespace id for this highlight |nvim_create_namespace()|. +/// Use 0 to set a highlight group globally |:highlight|. +/// @param name Highlight group name, e.g. "ErrorMsg" +/// @param val Highlight definition map, like |synIDattr()|. In +/// addition, the following keys are recognized: +/// - default: Don't override existing definition |:hi-default| +/// - ctermfg: Sets foreground of cterm color |highlight-ctermfg| +/// - ctermbg: Sets background of cterm color |highlight-ctermbg| +/// - cterm: cterm attribute map, like +/// |highlight-args|. +/// Note: Attributes default to those set for `gui` +/// if not set. /// @param[out] err Error details, if any /// // TODO(bfredl): val should take update vs reset flag -- cgit From 4ba12b3dda34472c193c9fa8ffd7d3bd5b6c04d6 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Sun, 13 Mar 2022 15:11:17 +0100 Subject: refactor: fix clint warnings (#17682) --- src/nvim/api/private/helpers.c | 5 ++--- src/nvim/api/vim.c | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 8056950e26..9f41393c6b 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -111,7 +111,7 @@ bool try_leave(const TryState *const tstate, Error *const err) /// try_enter()/try_leave() pair should be used instead. void try_start(void) { - ++trylevel; + trylevel++; } /// End try block, set the error message if any and return true if an error @@ -1037,8 +1037,7 @@ static void set_option_value_for(char *key, int numval, char *stringval, int opt aco_save_T aco; try_start(); - switch (opt_type) - { + switch (opt_type) { case SREQ_WIN: if (switch_win_noblock(&switchwin, (win_T *)from, win_find_tabpage((win_T *)from), true) == FAIL) { diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 80fa677485..3292ee2ef8 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -219,7 +219,7 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_ks) bool execute = false; bool dangerous = false; - for (size_t i = 0; i < mode.size; ++i) { + for (size_t i = 0; i < mode.size; i++) { switch (mode.data[i]) { case 'n': remap = false; break; @@ -1880,7 +1880,7 @@ static void write_msg(String message, bool to_err) } \ line_buf[pos++] = message.data[i]; - ++no_wait_return; + no_wait_return++; for (uint32_t i = 0; i < message.size; i++) { if (got_int) { break; @@ -1891,7 +1891,7 @@ static void write_msg(String message, bool to_err) PUSH_CHAR(i, out_pos, out_line_buf, msg); } } - --no_wait_return; + no_wait_return--; msg_end(); } -- cgit From 198bf3a8f2897d679a297fd04e3183dbac8bd61e Mon Sep 17 00:00:00 2001 From: Dundar Göc Date: Sat, 12 Mar 2022 17:54:31 +0100 Subject: refactor: minimize variable scope and eliminate empty declarations --- src/nvim/api/buffer.c | 3 +-- src/nvim/api/deprecated.c | 3 +-- src/nvim/api/tabpage.c | 6 ++---- 3 files changed, 4 insertions(+), 8 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index a3af51008f..9f0cadd5ce 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -597,12 +597,11 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In if (start_row == end_row) { old_byte = (bcount_t)end_col - start_col; } else { - const char *bufline; old_byte += (bcount_t)strlen(str_at_start) - start_col; for (int64_t i = 1; i < end_row - start_row; i++) { int64_t lnum = start_row + i; - bufline = (char *)ml_get_buf(buf, lnum, false); + const char *bufline = (char *)ml_get_buf(buf, lnum, false); old_byte += (bcount_t)(strlen(bufline))+1; } old_byte += (bcount_t)end_col+1; diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index d2013c597c..6a41df0aa9 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -51,11 +51,10 @@ Integer nvim_buf_get_number(Buffer buffer, Error *err) FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(2) { - Integer rv = 0; buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { - return rv; + return 0; } return buf->b_fnum; diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c index 14b6be8eeb..b994d18c43 100644 --- a/src/nvim/api/tabpage.c +++ b/src/nvim/api/tabpage.c @@ -102,11 +102,10 @@ void nvim_tabpage_del_var(Tabpage tabpage, String name, Error *err) Window nvim_tabpage_get_win(Tabpage tabpage, Error *err) FUNC_API_SINCE(1) { - Window rv = 0; tabpage_T *tab = find_tab_by_handle(tabpage, err); if (!tab || !valid_tabpage(tab)) { - return rv; + return 0; } if (tab == curtab) { @@ -130,11 +129,10 @@ Window nvim_tabpage_get_win(Tabpage tabpage, Error *err) Integer nvim_tabpage_get_number(Tabpage tabpage, Error *err) FUNC_API_SINCE(1) { - Integer rv = 0; tabpage_T *tab = find_tab_by_handle(tabpage, err); if (!tab) { - return rv; + return 0; } return tabpage_index(tab); -- cgit From 794d2744f33562326172801ddd729853e7135347 Mon Sep 17 00:00:00 2001 From: hlpr98 Date: Sat, 12 Mar 2022 21:08:29 +0100 Subject: feat(ui): implement ui_client event handlers --- src/nvim/api/ui_events.in.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 03fe5c5058..78826072a7 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -84,7 +84,7 @@ void grid_clear(Integer grid) void grid_cursor_goto(Integer grid, Integer row, Integer col) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; void grid_line(Integer grid, Integer row, Integer col_start, Array data) - FUNC_API_SINCE(5) FUNC_API_REMOTE_ONLY; + FUNC_API_SINCE(5) FUNC_API_REMOTE_ONLY FUNC_API_CLIENT_IMPL; void grid_scroll(Integer grid, Integer top, Integer bot, Integer left, Integer right, Integer rows, Integer cols) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; -- cgit From c0b4d931e12910f67cc3eade664247ea2d2bb913 Mon Sep 17 00:00:00 2001 From: bfredl Date: Sun, 13 Mar 2022 16:02:53 +0100 Subject: refactor(ui): make ui_client_event_grid_line typesafe --- src/nvim/api/ui_events.in.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 78826072a7..db348359eb 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -78,7 +78,7 @@ void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, void hl_group_set(String name, Integer id) FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL; void grid_resize(Integer grid, Integer width, Integer height) - FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL FUNC_API_CLIENT_IMPL; void grid_clear(Integer grid) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; void grid_cursor_goto(Integer grid, Integer row, Integer col) -- cgit From d238b8f6003d34cae7f65ff7585b48a2cd9449fb Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Thu, 17 Mar 2022 06:21:24 +0100 Subject: chore: fix typos (#17670) Co-authored-by: zeertzjq --- src/nvim/api/vim.c | 2 +- src/nvim/api/window.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 3292ee2ef8..0bdccf7a0b 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -127,7 +127,7 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) /// /// Note: Unlike the `:highlight` command which can update a highlight group, /// this function completely replaces the definition. For example: -/// `nvim_set_hl(0, 'Visual', {})` will clear the highlight group 'Visual'. +/// ``nvim_set_hl(0, 'Visual', {})`` will clear the highlight group 'Visual'. /// /// @param ns_id Namespace id for this highlight |nvim_create_namespace()|. /// Use 0 to set a highlight group globally |:highlight|. diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 9c473ff724..be43708604 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -71,7 +71,7 @@ ArrayOf(Integer, 2) nvim_win_get_cursor(Window window, Error *err) } /// Sets the (1,0)-indexed cursor position in the window. |api-indexing| -/// Unlike |win_execute()| this scrolls the window. +/// This scrolls the window even if it is not the current one. /// /// @param window Window handle, or 0 for current window /// @param pos (row, col) tuple representing the new position -- cgit From 5ab122917474b3f9e88be4ee88bc6d627980cfe0 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Sun, 30 Jan 2022 11:57:41 +0600 Subject: feat: add support for global statusline Ref: #9342 Adds the option to have a single global statusline for the current window at the bottom of the screen instead of a statusline at the bottom of every window. Enabled by setting `laststatus = 3`. Due to the fact that statuslines at the bottom of windows are removed when global statusline is enabled, horizontal separators are used instead to separate horizontal splits. The horizontal separator character is configurable through the`horiz` item in `'fillchars'`. Separator connector characters are also used to connect the horizontal and vertical separators together, which are also configurable through the `horizup`, `horizdown`, `vertleft`, `vertright` and `verthoriz` items in `fillchars`. The window separators are highlighted using the `WinSeparator` highlight group, which supersedes `VertSplit` and is linked to `VertSplit` by default in order to maintain backwards compatibility. --- src/nvim/api/vim.c | 2 +- src/nvim/api/win_config.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index f4909b0801..2e3d99cdad 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -2319,7 +2319,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * maxwidth = (int)opts->maxwidth.data.integer; } else { - maxwidth = use_tabline ? Columns : wp->w_width; + maxwidth = (use_tabline || global_stl_height() > 0) ? Columns : wp->w_width; } char buf[MAXPATHL]; diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index ceb7f71423..d8ccd67bcd 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -131,7 +131,7 @@ /// An empty string can be used to turn off a specific border, for instance, /// [ "", "", "", ">", "", "", "", "<" ] /// will only make vertical borders but not horizontal ones. -/// By default, `FloatBorder` highlight is used, which links to `VertSplit` +/// By default, `FloatBorder` highlight is used, which links to `WinSeparator` /// when not defined. It could also be specified by character: /// [ {"+", "MyCorner"}, {"x", "MyBorder"} ]. /// - noautocmd: If true then no buffer-related autocommand events such as -- cgit From cac90d2de728181edce7ba38fb9ad588d231651b Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 18 Mar 2022 03:21:47 +0800 Subject: feat(api, lua): support converting nested Funcref back to LuaRef (#17749) --- src/nvim/api/private/converter.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c index 49e3cf7df7..82ec1ad0d8 100644 --- a/src/nvim/api/private/converter.c +++ b/src/nvim/api/private/converter.c @@ -64,7 +64,14 @@ typedef struct { #define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ do { \ - TYPVAL_ENCODE_CONV_NIL(tv); \ + ufunc_T *fp = find_func(fun); \ + assert(fp != NULL); \ + if (fp->uf_cb == nlua_CFunction_func_call) { \ + LuaRef ref = api_new_luaref(((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref); \ + kvi_push(edata->stack, LUAREF_OBJ(ref)); \ + } else { \ + TYPVAL_ENCODE_CONV_NIL(tv); \ + } \ goto typval_encode_stop_converting_one_item; \ } while (0) @@ -231,14 +238,6 @@ static inline void typval_encode_dict_end(EncodedData *const edata) /// @return The converted value Object vim_to_object(typval_T *obj) { - if (obj->v_type == VAR_FUNC) { - ufunc_T *fp = find_func(obj->vval.v_string); - assert(fp != NULL); - if (fp->uf_cb == nlua_CFunction_func_call) { - LuaRef ref = api_new_luaref(((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref); - return LUAREF_OBJ(ref); - } - } EncodedData edata; kvi_init(edata.stack); const int evo_ret = encode_vim_to_object(&edata, obj, -- cgit From 00effff56944d5b59440dcdb5e3496d49a76d3e2 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 18 Mar 2022 04:47:08 +0000 Subject: vim-patch:8.1.1693: syntax coloring and highlighting is in one big file (#17721) Problem: Syntax coloring and highlighting is in one big file. Solution: Move the highlighting to a separate file. (Yegappan Lakshmanan, closes vim/vim#4674) https://github.com/vim/vim/commit/f9cc9f209ede9f15959e4c2351e970477c139614 Name the new file highlight_group.c instead. Co-authored-by: zeertzjq --- src/nvim/api/extmark.c | 4 ++-- src/nvim/api/private/helpers.c | 6 +++--- src/nvim/api/vim.c | 6 +++--- src/nvim/api/win_config.c | 1 + 4 files changed, 9 insertions(+), 8 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index e355f82f4d..c02688a815 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -10,10 +10,10 @@ #include "nvim/api/private/helpers.h" #include "nvim/decoration_provider.h" #include "nvim/extmark.h" +#include "nvim/highlight_group.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" @@ -856,7 +856,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In int hl_id = 0; if (hl_group.size > 0) { - hl_id = syn_check_group(hl_group.data, (int)hl_group.size); + hl_id = syn_check_group(hl_group.data, hl_group.size); } else { return ns_id; } diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 9f41393c6b..88954a1aa2 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -22,6 +22,7 @@ #include "nvim/extmark.h" #include "nvim/fileio.h" #include "nvim/getchar.h" +#include "nvim/highlight_group.h" #include "nvim/lib/kvec.h" #include "nvim/lua/executor.h" #include "nvim/map.h" @@ -32,7 +33,6 @@ #include "nvim/msgpack_rpc/helpers.h" #include "nvim/option.h" #include "nvim/option_defs.h" -#include "nvim/syntax.h" #include "nvim/ui.h" #include "nvim/version.h" #include "nvim/vim.h" @@ -1293,7 +1293,7 @@ int object_to_hl_id(Object obj, const char *what, Error *err) { if (obj.type == kObjectTypeString) { String str = obj.data.string; - return str.size ? syn_check_group(str.data, (int)str.size) : 0; + return str.size ? syn_check_group(str.data, str.size) : 0; } else if (obj.type == kObjectTypeInteger) { return MAX((int)obj.data.integer, 0); } else { @@ -1327,7 +1327,7 @@ HlMessage parse_hl_msg(Array chunks, Error *err) String hl = chunk.items[1].data.string; if (hl.size > 0) { // TODO(bfredl): use object_to_hl_id and allow integer - int hl_id = syn_check_group(hl.data, (int)hl.size); + int hl_id = syn_check_group(hl.data, hl.size); attr = hl_id > 0 ? syn_id2attr(hl_id) : 0; } } diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index d1ca7662f6..bdeac1a9f4 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -35,6 +35,7 @@ #include "nvim/globals.h" #include "nvim/highlight.h" #include "nvim/highlight_defs.h" +#include "nvim/highlight_group.h" #include "nvim/lua/executor.h" #include "nvim/mark.h" #include "nvim/memline.h" @@ -50,7 +51,6 @@ #include "nvim/popupmnu.h" #include "nvim/screen.h" #include "nvim/state.h" -#include "nvim/syntax.h" #include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -112,7 +112,7 @@ Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Error *err) Integer nvim_get_hl_id_by_name(String name) FUNC_API_SINCE(7) { - return syn_check_group(name.data, (int)name.size); + return syn_check_group(name.data, name.size); } Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) @@ -147,7 +147,7 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err) FUNC_API_SINCE(7) { - int hl_id = syn_check_group(name.data, (int)name.size); + int hl_id = syn_check_group(name.data, name.size); int link_id = -1; HlAttrs attrs = dict2hlattrs(val, true, &link_id, err); diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index d8ccd67bcd..b0267f5ed0 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -10,6 +10,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/win_config.h" #include "nvim/ascii.h" +#include "nvim/highlight_group.h" #include "nvim/option.h" #include "nvim/screen.h" #include "nvim/strings.h" -- cgit From f2e5f509d995b291e4b9e05a0c059e83490a18e1 Mon Sep 17 00:00:00 2001 From: Xiretza Date: Fri, 18 Mar 2022 19:58:00 +0100 Subject: docs: reword description for nvim_buf_line_count() (#17766) This adds a few more keywords to make the function easier to find. --- src/nvim/api/buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 9f0cadd5ce..18f7177489 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -52,7 +52,7 @@ /// whether a buffer is loaded. -/// Gets the buffer line count +/// Returns the number of lines in the given buffer. /// /// @param buffer Buffer handle, or 0 for current buffer /// @param[out] err Error details, if any -- cgit From b1207e5080087fa0ad05d13787a02ebee3f20ac0 Mon Sep 17 00:00:00 2001 From: Javier López Date: Sat, 19 Mar 2022 15:32:18 -0500 Subject: docs: properly escape to avoid doxygen weirdness If this is not properly escaped doxygen 1.9.3 will not work correctly, and the documentation generated in local machines will differ with what is generated in CI. --- src/nvim/api/win_config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index b0267f5ed0..42e880dc19 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -126,7 +126,7 @@ /// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ]. /// If the number of chars are less than eight, they will be repeated. Thus /// an ASCII border could be specified as -/// [ "/", "-", "\\", "|" ], +/// [ "/", "-", \"\\\\\", "|" ], /// or all chars the same as /// [ "x" ]. /// An empty string can be used to turn off a specific border, for instance, -- cgit From 77eb6f9dc75ebe00aa835441ad623ba46d7108bb Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 20 Mar 2022 08:08:50 +0800 Subject: fix(api, lua): return NIL on failure to find converted function (#17779) --- src/nvim/api/private/converter.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c index 82ec1ad0d8..a26383ec7d 100644 --- a/src/nvim/api/private/converter.c +++ b/src/nvim/api/private/converter.c @@ -65,8 +65,7 @@ typedef struct { #define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ do { \ ufunc_T *fp = find_func(fun); \ - assert(fp != NULL); \ - if (fp->uf_cb == nlua_CFunction_func_call) { \ + if (fp != NULL && fp->uf_cb == nlua_CFunction_func_call) { \ LuaRef ref = api_new_luaref(((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref); \ kvi_push(edata->stack, LUAREF_OBJ(ref)); \ } else { \ -- cgit From 6eca9b69c4a1f40f27a6b41961af787327259de8 Mon Sep 17 00:00:00 2001 From: bfredl Date: Sat, 19 Mar 2022 13:48:03 +0100 Subject: feat(ui): allow conceal to be defined in decorations Unlike syntax conceal, change highlight of concealed char Can be used in tree-sitter using "conceal" metadata. --- src/nvim/api/extmark.c | 16 ++++++++++++++++ src/nvim/api/keysets.lua | 1 + 2 files changed, 17 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index c02688a815..797b64e2af 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -467,6 +467,11 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// as the mark and 'cursorline' is enabled. /// Note: ranges are unsupported and decorations are only /// applied to start_row +/// - conceal: string which should be either empty or a single +/// character. Enable concealing similar to |:syn-conceal|. +/// When a character is supplied it is used as |:syn-cchar|. +/// "hl_group" is used as highlight for the cchar if provided, +/// otherwise it defaults to |hl-Conceal|. /// /// @param[out] err Error details, if any /// @return Id of the created/updated extmark @@ -563,6 +568,17 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer } } + if (opts->conceal.type == kObjectTypeString) { + String c = opts->conceal.data.string; + decor.conceal = true; + if (c.size) { + decor.conceal_char = utf_ptr2char((const char_u *)c.data); + } + } 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); diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index 435e8195dd..b6264cdfab 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -27,6 +27,7 @@ return { "number_hl_group"; "line_hl_group"; "cursorline_hl_group"; + "conceal"; }; keymap = { "noremap"; -- cgit From 89712dcbf8ecc41d6fab9608f684ce199667ed2e Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 21 Mar 2022 21:19:09 +0800 Subject: fix(aucmd_win): always make aucmd_win the last window --- src/nvim/api/win_config.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 42e880dc19..5e37596884 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -150,7 +150,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, E if (!parse_float_config(config, &fconfig, false, true, err)) { return 0; } - win_T *wp = win_new_float(NULL, fconfig, err); + win_T *wp = win_new_float(NULL, false, fconfig, err); if (!wp) { return 0; } @@ -200,7 +200,7 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err) return; } if (new_float) { - if (!win_new_float(win, fconfig, err)) { + if (!win_new_float(win, false, fconfig, err)) { return; } redraw_later(win, NOT_VALID); -- cgit From c29a14d1fa58d5472bd14fec99c5b4228ed38b24 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 23 Mar 2022 11:28:32 +0800 Subject: perf(screen): reduce cursorline redrawing when jumping around vim-patch:8.2.4614: redrawing too much when 'cursorline' is set Problem: Redrawing too much when 'cursorline' is set and jumping around. Solution: Rely on win_update() to redraw the current and previous cursor line, do not mark lines as modified. (closes vim/vim#9996) https://github.com/vim/vim/commit/c20e46a4e3efcd408ef132872238144ea34f7ae5 This doesn't match the patch exactly, because I missed some lines when porting patch 8.1.2029, and these lines were removed in this patch. This also makes win_update() always update for 'concealcursor' like how it always updates for 'cursorline', as 'cursorline' and 'concealcursor' redrawing logic has been unified in Nvim. As redrawing for 'cursorline' now always only requires VALID redraw type, it is no longer necessary to call redraw_for_cursorline() in nvim_win_set_cursor(). --- src/nvim/api/window.c | 1 - 1 file changed, 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index be43708604..fd33a82be3 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -119,7 +119,6 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) update_topline_win(win); redraw_later(win, VALID); - redraw_for_cursorline(win); win->w_redr_status = true; } -- cgit From 4a11c7e56fa23c92f5ddd09007a7838dcfca9b02 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 24 Mar 2022 19:40:00 +0800 Subject: chore(nvim_paste): assert the correct String (#17752) --- src/nvim/api/vim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index bdeac1a9f4..7c7ada55a2 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1328,7 +1328,7 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err) if (!cancel && !(State & CMDLINE)) { // Dot-repeat. for (size_t i = 0; i < lines.size; i++) { String s = lines.items[i].data.string; - assert(data.size <= INT_MAX); + assert(s.size <= INT_MAX); AppendToRedobuffLit((char_u *)s.data, (int)s.size); // readfile()-style: "\n" is indicated by presence of N+1 item. if (i + 1 < lines.size) { -- cgit From 174deafcef27bc98df36c952ee93e316488bc765 Mon Sep 17 00:00:00 2001 From: Javier Lopez Date: Fri, 25 Mar 2022 13:24:53 -0500 Subject: docs(api): improve autocommand docs (#17545) [skip ci] --- src/nvim/api/autocmd.c | 201 +++++++++++++++++++++++++++++++------------------ 1 file changed, 129 insertions(+), 72 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 8a7dd00b2a..94a24705d0 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -36,16 +36,42 @@ // Used to delete autocmds from nvim_del_autocmd static int64_t next_autocmd_id = 1; -/// Get autocmds that match the requirements passed to {opts}. +/// Get autocommands that match the requirements passed to {opts}. /// -/// @param opts Optional Parameters: -/// - event : Name or list of name of events to match against -/// - group (string|int): Name or id of group to match against -/// - pattern: Pattern or list of patterns to match against. Cannot be used with {buffer} -/// - buffer: Buffer number or list of buffer numbers for buffer local autocommands -/// |autocmd-buflocal|. Cannot be used with {pattern} +/// These examples will get autocommands matching ALL the given criteria: +///
+///   -- Matches all criteria
+///   autocommands = vim.api.nvim_get_autocmds({
+///     group = "MyGroup",
+///     event = {"BufEnter", "BufWinEnter"},
+///     pattern = {"*.c", "*.h"}
+///   })
+///
+///   -- All commands from one group
+///   autocommands = vim.api.nvim_get_autocmds({
+///     group = "MyGroup",
+///   })
+/// 
/// -/// @return A list of autocmds that match +/// NOTE: When multiple patterns or events are provided, it will find all the autocommands that +/// match any combination of them. +/// +/// @param opts Dictionary with at least one of the following: +/// - group (string|integer): the autocommand group name or id to match against. +/// - event (string|array): event or events to match against |autocmd-events|. +/// - pattern (string|array): pattern or patterns to match against |autocmd-pattern|. +/// @return Array of autocommands matching the criteria, with each item +/// containing the following fields: +/// - id (number): the autocommand id (only when defined with the API). +/// - group (integer): the autocommand group id. +/// - desc (string): the autocommand description. +/// - event (string): the autocommand event. +/// - command (string): the autocommand command. +/// - once (boolean): whether the autocommand is only run once. +/// - pattern (string): the autocommand pattern. +/// If the autocommand is buffer local |autocmd-buffer-local|: +/// - buflocal (boolean): true if the autocommand is buffer local. +/// - buffer (number): the buffer number. Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) FUNC_API_SINCE(9) { @@ -301,40 +327,68 @@ cleanup: return autocmd_list; } -/// Create an autocmd. +/// Create an |autocommand| +/// +/// The API allows for two (mutually exclusive) types of actions to be executed when the autocommand +/// triggers: a callback function (Lua or Vimscript), or a command (like regular autocommands). +/// +/// Example using callback: +///
+///     -- Lua function
+///     local myluafun = function() print("This buffer enters") end
+///
+///     -- Vimscript function name (as a string)
+///     local myvimfun = "g:MyVimFunction"
+///
+///     vim.api.nvim_create_autocmd({
+///       event = {"BufEnter", "BufWinEnter"},
+///       pattern = {"*.c", "*.h"},
+///       callback = myluafun,  -- Or myvimfun
+///     })
+/// 
/// -/// @param event The event or events to register this autocmd -/// Required keys: -/// event: string | ArrayOf(string) +/// Example using command: +///
+///     vim.api.nvim_create_autocmd({
+///       event = {"BufEnter", "BufWinEnter"},
+///       pattern = {"*.c", "*.h"},
+///       command = "echo 'Entering a C or C++ file'",
+///     })
+/// 
/// -/// Examples: -/// - event: "pat1,pat2,pat3", -/// - event: "pat1" -/// - event: { "pat1" } -/// - event: { "pat1", "pat2", "pat3" } +/// Example values for pattern: +///
+///   pattern = "*.py"
+///   pattern = { "*.py", "*.pyi" }
+/// 
/// -/// @param opts Optional Parameters: -/// - callback: (string|function) -/// - (string): The name of the viml function to execute when triggering this autocmd -/// - (function): The lua function to execute when triggering this autocmd -/// - NOTE: Cannot be used with {command} -/// - command: (string) command -/// - vimscript command -/// - NOTE: Cannot be used with {callback} -/// Eg. command = "let g:value_set = v:true" -/// - pattern: (string|table) -/// - pattern or patterns to match against -/// - defaults to "*". -/// - NOTE: Cannot be used with {buffer} -/// - buffer: (bufnr) -/// - create a |autocmd-buflocal| autocmd. -/// - NOTE: Cannot be used with {pattern} -/// - group: (string|int) The augroup name or id -/// - once: (boolean) - See |autocmd-once| -/// - nested: (boolean) - See |autocmd-nested| -/// - desc: (string) - Description of the autocmd +/// Examples values for event: +///
+///   event = "BufPreWrite"
+///   event = {"CursorHold", "BufPreWrite", "BufPostWrite"}
+/// 
+/// +/// @param event (String|Array) The event or events to register this autocommand +/// @param opts Dictionary of autocommand options: +/// - group (string|integer) optional: the autocommand group name or +/// id to match against. +/// - pattern (string|array) optional: pattern or patterns to match +/// against |autocmd-pattern|. +/// - buffer (integer) optional: buffer number for buffer local autocommands +/// |autocmd-buflocal|. Cannot be used with {pattern}. +/// - desc (string) optional: description of the autocommand. +/// - callback (function|string) optional: Lua function or Vim function (as string) to +/// execute on event. Cannot be used with {command} +/// - command (string) optional: Vim command to execute on event. Cannot be used with +/// {callback} +/// - once (boolean) optional: defaults to false. Run the autocommand +/// only once |autocmd-once|. +/// - nested (boolean) optional: defaults to false. Run nested +/// autocommands |autocmd-nested|. /// -/// @returns opaque value to use with nvim_del_autocmd +/// @return Integer id of the created autocommand. +/// @see |autocommand| +/// @see |nvim_del_autocmd()| Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autocmd) *opts, Error *err) FUNC_API_SINCE(9) @@ -552,33 +606,32 @@ cleanup: return autocmd_id; } -/// Delete an autocmd by {id}. Autocmds only return IDs when created -/// via the API. Will not error if called and no autocmds match -/// the {id}. +/// Delete an autocommand by id. /// -/// @param id Integer The ID returned by nvim_create_autocmd +/// NOTE: Only autocommands created via the API have an id. +/// @param id Integer The id returned by nvim_create_autocmd +/// @see |nvim_create_autocmd()| void nvim_del_autocmd(Integer id) FUNC_API_SINCE(9) { autocmd_delete_id(id); } -/// Create or get an augroup. +/// Create or get an autocommand group |autocmd-groups|. /// -/// To get an existing augroup ID, do: +/// To get an existing group id, do: ///
-///     local id = vim.api.nvim_create_augroup(name, {
+///     local id = vim.api.nvim_create_augroup("MyGroup", {
 ///         clear = false
 ///     })
 /// 
/// -/// @param name String: The name of the augroup to create -/// @param opts Parameters -/// - clear (bool): Whether to clear existing commands or not. -/// Defaults to true. -/// See |autocmd-groups| -/// -/// @returns opaque value to use with nvim_del_augroup_by_id +/// @param name String: The name of the group +/// @param opts Dictionary Parameters +/// - clear (bool) optional: defaults to true. Clear existing +/// commands if the group already exists |autocmd-groups|. +/// @return Integer id of the created group. +/// @see |autocmd-groups| Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augroup) *opts, Error *err) FUNC_API_SINCE(9) @@ -604,13 +657,15 @@ Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augrou return augroup; } -/// Delete an augroup by {id}. {id} can only be returned when augroup was -/// created with |nvim_create_augroup|. +/// Delete an autocommand group by id. /// -/// NOTE: behavior differs from augroup-delete. +/// To get a group id one can use |nvim_get_autocmds()|. /// -/// When deleting an augroup, autocmds contained by this augroup will also be deleted and cleared. -/// This augroup will no longer exist +/// NOTE: behavior differs from |augroup-delete|. When deleting a group, autocommands contained in +/// this group will also be deleted and cleared. This group will no longer exist. +/// @param id Integer The id of the group. +/// @see |nvim_del_augroup_by_name()| +/// @see |nvim_create_augroup()| void nvim_del_augroup_by_id(Integer id) FUNC_API_SINCE(9) { @@ -618,28 +673,30 @@ void nvim_del_augroup_by_id(Integer id) augroup_del(name, false); } -/// Delete an augroup by {name}. +/// Delete an autocommand group by name. /// -/// NOTE: behavior differs from augroup-delete. -/// -/// When deleting an augroup, autocmds contained by this augroup will also be deleted and cleared. -/// This augroup will no longer exist +/// NOTE: behavior differs from |augroup-delete|. When deleting a group, autocommands contained in +/// this group will also be deleted and cleared. This group will no longer exist. +/// @param name String The name of the group. +/// @see |autocommand-groups| void nvim_del_augroup_by_name(String name) FUNC_API_SINCE(9) { augroup_del(name.data, false); } -/// Do one autocmd. -/// -/// @param event The event or events to execute -/// @param opts Optional Parameters: -/// - buffer (number) - buffer number -/// - NOTE: Cannot be used with {pattern} -/// - pattern (string|table) - optional, defaults to "*". -/// - NOTE: Cannot be used with {buffer} -/// - group (string|int) - autocmd group name or id -/// - modeline (boolean) - Default true, see || +/// Execute an autocommand |autocmd-execute|. +/// @param event (String|Array) The event or events to execute +/// @param opts Dictionary of autocommand options: +/// - group (string|integer) optional: the autocommand group name or +/// id to match against. |autocmd-groups|. +/// - pattern (string|array) optional: defaults to "*" |autocmd-pattern|. Cannot be used +/// with {buffer}. +/// - buffer (integer) optional: buffer number |autocmd-buflocal|. Cannot be used with +/// {pattern}. +/// - modeline (bool) optional: defaults to true. Process the +/// modeline after the autocommands ||. +/// @see |:doautocmd| void nvim_do_autocmd(Object event, Dict(do_autocmd) *opts, Error *err) FUNC_API_SINCE(9) { -- cgit From 85821d8b6fa366f7361f84e07e62dc8fc951f26d Mon Sep 17 00:00:00 2001 From: かわえもん Date: Sat, 26 Mar 2022 21:21:32 +0900 Subject: docs(api): fix wrong documentation of `nvim_create_autocmd` (#17870) also add doc changes from typofix PR --- src/nvim/api/autocmd.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 94a24705d0..9aaa0418dc 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -340,8 +340,7 @@ cleanup: /// -- Vimscript function name (as a string) /// local myvimfun = "g:MyVimFunction" /// -/// vim.api.nvim_create_autocmd({ -/// event = {"BufEnter", "BufWinEnter"}, +/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { /// pattern = {"*.c", "*.h"}, /// callback = myluafun, -- Or myvimfun /// }) @@ -349,8 +348,7 @@ cleanup: /// /// Example using command: ///
-///     vim.api.nvim_create_autocmd({
-///       event = {"BufEnter", "BufWinEnter"},
+///     vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
 ///       pattern = {"*.c", "*.h"},
 ///       command = "echo 'Entering a C or C++ file'",
 ///     })
@@ -364,8 +362,8 @@ cleanup:
 ///
 /// Examples values for event:
 /// 
-///   event = "BufPreWrite"
-///   event = {"CursorHold", "BufPreWrite", "BufPostWrite"}
+///   "BufPreWrite"
+///   {"CursorHold", "BufPreWrite", "BufPostWrite"}
 /// 
/// /// @param event (String|Array) The event or events to register this autocommand -- cgit From a490db5ba819218e9188cbb51d885dbf3a194000 Mon Sep 17 00:00:00 2001 From: Javier Lopez Date: Sat, 26 Mar 2022 09:34:56 -0500 Subject: refactor!: rename nvim_do_autocmd to nvim_exec_autocmd (#17854) according to established code standards (`:h dev-api`) --- src/nvim/api/autocmd.c | 2 +- src/nvim/api/keysets.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 9aaa0418dc..49f6f98ba4 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -695,7 +695,7 @@ void nvim_del_augroup_by_name(String name) /// - modeline (bool) optional: defaults to true. Process the /// modeline after the autocommands ||. /// @see |:doautocmd| -void nvim_do_autocmd(Object event, Dict(do_autocmd) *opts, Error *err) +void nvim_exec_autocmd(Object event, Dict(exec_autocmd) *opts, Error *err) FUNC_API_SINCE(9) { int au_group = AUGROUP_ALL; diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index b6264cdfab..6a29b0fb59 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -133,7 +133,7 @@ return { "nested"; "pattern"; }; - do_autocmd = { + exec_autocmd = { "buffer"; "group"; "modeline"; -- cgit From b80651eda9c50d4e438f02af9311b18c5c202656 Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Sat, 12 Mar 2022 15:12:02 -0500 Subject: feat(api): nvim_clear_autocmd Co-authored-by: Christian Clason --- src/nvim/api/autocmd.c | 291 +++++++++++++++++++++++++++++------------ src/nvim/api/keysets.lua | 8 +- src/nvim/api/private/helpers.h | 5 +- 3 files changed, 217 insertions(+), 87 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 49f6f98ba4..482aa80f1e 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -392,9 +392,6 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc FUNC_API_SINCE(9) { int64_t autocmd_id = -1; - - const char_u pattern_buflocal[BUFLOCAL_PAT_LEN]; - int au_group = AUGROUP_DEFAULT; char *desc = NULL; Array patterns = ARRAY_DICT_INIT; @@ -404,7 +401,7 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc Callback cb = CALLBACK_NONE; - if (!unpack_string_or_array(&event_array, &event, "event", err)) { + if (!unpack_string_or_array(&event_array, &event, "event", true, err)) { goto cleanup; } @@ -466,84 +463,13 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc bool is_once = api_object_to_bool(opts->once, "once", false, err); bool is_nested = api_object_to_bool(opts->nested, "nested", false, err); - switch (opts->group.type) { - case kObjectTypeNil: - break; - case kObjectTypeString: - au_group = augroup_find(opts->group.data.string.data); - if (au_group == AUGROUP_ERROR) { - api_set_error(err, - kErrorTypeValidation, - "invalid augroup: %s", opts->group.data.string.data); - goto cleanup; - } - break; - case kObjectTypeInteger: - au_group = (int)opts->group.data.integer; - char *name = augroup_name(au_group); - if (!augroup_exists(name)) { - api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group); - goto cleanup; - } - break; - default: - api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer."); + int au_group = get_augroup_from_object(opts->group, err); + if (au_group == AUGROUP_ERROR) { goto cleanup; } - if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) { - api_set_error(err, kErrorTypeValidation, - "cannot pass both: 'pattern' and 'buffer' for the same autocmd"); + if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) { goto cleanup; - } else if (opts->pattern.type != kObjectTypeNil) { - Object *v = &opts->pattern; - - if (v->type == kObjectTypeString) { - char_u *pat = (char_u *)v->data.string.data; - size_t patlen = aucmd_pattern_length(pat); - while (patlen) { - ADD(patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen))); - - pat = aucmd_next_pattern(pat, patlen); - patlen = aucmd_pattern_length(pat); - } - } else if (v->type == kObjectTypeArray) { - if (!check_autocmd_string_array(patterns, "pattern", err)) { - goto cleanup; - } - - Array array = v->data.array; - for (size_t i = 0; i < array.size; i++) { - char_u *pat = (char_u *)array.items[i].data.string.data; - size_t patlen = aucmd_pattern_length(pat); - while (patlen) { - ADD(patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen))); - - pat = aucmd_next_pattern(pat, patlen); - patlen = aucmd_pattern_length(pat); - } - } - } else { - api_set_error(err, - kErrorTypeValidation, - "'pattern' must be a string"); - goto cleanup; - } - } else if (opts->buffer.type != kObjectTypeNil) { - if (opts->buffer.type != kObjectTypeInteger) { - api_set_error(err, - kErrorTypeValidation, - "'buffer' must be an integer"); - goto cleanup; - } - - buf_T *buf = find_buffer_by_handle((Buffer)opts->buffer.data.integer, err); - if (ERROR_SET(err)) { - goto cleanup; - } - - snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "", (int)buf->handle); - ADD(patterns, STRING_OBJ(cstr_to_string((char *)pattern_buflocal))); } if (opts->desc.type != kObjectTypeNil) { @@ -615,6 +541,94 @@ void nvim_del_autocmd(Integer id) autocmd_delete_id(id); } +/// Clear all autocommands that match the corresponding {opts}. To delete +/// a particular autocmd, see |nvim_del_autocmd|. +/// @param opts Parameters +/// - event: (string|table) +/// Examples: +/// - event: "pat1" +/// - event: { "pat1" } +/// - event: { "pat1", "pat2", "pat3" } +/// - pattern: (string|table) +/// - pattern or patterns to match exactly. +/// - For example, if you have `*.py` as that pattern for the autocmd, +/// you must pass `*.py` exactly to clear it. `test.py` will not +/// match the pattern. +/// - defaults to clearing all patterns. +/// - NOTE: Cannot be used with {buffer} +/// - buffer: (bufnr) +/// - clear only |autocmd-buflocal| autocommands. +/// - NOTE: Cannot be used with {pattern} +/// - group: (string|int) The augroup name or id. +/// - NOTE: If not passed, will only delete autocmds *not* in any group. +/// +void nvim_clear_autocmd(Dict(clear_autocmd) *opts, Error *err) + FUNC_API_SINCE(9) +{ + // TODO(tjdevries): Future improvements: + // - once: (boolean) - Only clear autocmds with once. See |autocmd-once| + // - nested: (boolean) - Only clear autocmds with nested. See |autocmd-nested| + // - group: Allow passing "*" or true or something like that to force doing all + // autocmds, regardless of their group. + + Array patterns = ARRAY_DICT_INIT; + Array event_array = ARRAY_DICT_INIT; + + if (!unpack_string_or_array(&event_array, &opts->event, "event", false, err)) { + goto cleanup; + } + + if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) { + api_set_error(err, kErrorTypeValidation, + "Cannot use both 'pattern' and 'buffer'"); + goto cleanup; + } + + int au_group = get_augroup_from_object(opts->group, err); + if (au_group == AUGROUP_ERROR) { + goto cleanup; + } + + if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) { + goto cleanup; + } + + // When we create the autocmds, we want to say that they are all matched, so that's * + // but when we clear them, we want to say that we didn't pass a pattern, so that's NUL + if (patterns.size == 0) { + ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING(""))); + } + + // If we didn't pass any events, that means clear all events. + if (event_array.size == 0) { + FOR_ALL_AUEVENTS(event) { + FOREACH_ITEM(patterns, pat_object, { + char_u *pat = (char_u *)pat_object.data.string.data; + if (!clear_autocmd(event, pat, au_group, err)) { + goto cleanup; + } + }); + } + } else { + FOREACH_ITEM(event_array, event_str, { + GET_ONE_EVENT(event_nr, event_str, cleanup); + + FOREACH_ITEM(patterns, pat_object, { + char_u *pat = (char_u *)pat_object.data.string.data; + if (!clear_autocmd(event_nr, pat, au_group, err)) { + goto cleanup; + } + }); + }); + } + +cleanup: + api_free_array(event_array); + api_free_array(patterns); + + return; +} + /// Create or get an autocommand group |autocmd-groups|. /// /// To get an existing group id, do: @@ -709,7 +723,7 @@ void nvim_exec_autocmd(Object event, Dict(exec_autocmd) *opts, Error *err) Array event_array = ARRAY_DICT_INIT; - if (!unpack_string_or_array(&event_array, &event, "event", err)) { + if (!unpack_string_or_array(&event_array, &event, "event", true, err)) { goto cleanup; } @@ -808,7 +822,7 @@ static bool check_autocmd_string_array(Array arr, char *k, Error *err) return true; } -static bool unpack_string_or_array(Array *array, Object *v, char *k, Error *err) +static bool unpack_string_or_array(Array *array, Object *v, char *k, bool required, Error *err) { if (v->type == kObjectTypeString) { ADD(*array, copy_object(*v)); @@ -818,10 +832,119 @@ static bool unpack_string_or_array(Array *array, Object *v, char *k, Error *err) } *array = copy_array(v->data.array); } else { - api_set_error(err, - kErrorTypeValidation, - "'%s' must be an array or a string.", - k); + if (required) { + api_set_error(err, + kErrorTypeValidation, + "'%s' must be an array or a string.", + k); + return false; + } + } + + return true; +} + +// Returns AUGROUP_ERROR if there was a problem with {group} +static int get_augroup_from_object(Object group, Error *err) +{ + int au_group = AUGROUP_ERROR; + + switch (group.type) { + case kObjectTypeNil: + return AUGROUP_DEFAULT; + case kObjectTypeString: + au_group = augroup_find(group.data.string.data); + if (au_group == AUGROUP_ERROR) { + api_set_error(err, + kErrorTypeValidation, + "invalid augroup: %s", group.data.string.data); + + return AUGROUP_ERROR; + } + + return au_group; + case kObjectTypeInteger: + au_group = (int)group.data.integer; + char *name = augroup_name(au_group); + if (!augroup_exists(name)) { + api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group); + return AUGROUP_ERROR; + } + + return au_group; + default: + api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer."); + return AUGROUP_ERROR; + } +} + +static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Object buffer, + Error *err) +{ + const char_u pattern_buflocal[BUFLOCAL_PAT_LEN]; + + if (pattern.type != kObjectTypeNil && buffer.type != kObjectTypeNil) { + api_set_error(err, kErrorTypeValidation, + "cannot pass both: 'pattern' and 'buffer' for the same autocmd"); + return false; + } else if (pattern.type != kObjectTypeNil) { + Object *v = &pattern; + + if (v->type == kObjectTypeString) { + char_u *pat = (char_u *)v->data.string.data; + size_t patlen = aucmd_pattern_length(pat); + while (patlen) { + ADD(*patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen))); + + pat = aucmd_next_pattern(pat, patlen); + patlen = aucmd_pattern_length(pat); + } + } else if (v->type == kObjectTypeArray) { + if (!check_autocmd_string_array(*patterns, "pattern", err)) { + return false; + } + + Array array = v->data.array; + for (size_t i = 0; i < array.size; i++) { + char_u *pat = (char_u *)array.items[i].data.string.data; + size_t patlen = aucmd_pattern_length(pat); + while (patlen) { + ADD(*patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen))); + + pat = aucmd_next_pattern(pat, patlen); + patlen = aucmd_pattern_length(pat); + } + } + } else { + api_set_error(err, + kErrorTypeValidation, + "'pattern' must be a string"); + return false; + } + } else if (buffer.type != kObjectTypeNil) { + if (buffer.type != kObjectTypeInteger) { + api_set_error(err, + kErrorTypeValidation, + "'buffer' must be an integer"); + return false; + } + + buf_T *buf = find_buffer_by_handle((Buffer)buffer.data.integer, err); + if (ERROR_SET(err)) { + return false; + } + + snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "", (int)buf->handle); + ADD(*patterns, STRING_OBJ(cstr_to_string((char *)pattern_buflocal))); + } + + return true; +} + +static bool clear_autocmd(event_T event, char_u *pat, int au_group, Error *err) +{ + if (do_autocmd_event(event, pat, false, false, (char_u *)"", true, au_group) == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to clear autocmd"); return false; } diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index 6a29b0fb59..520bb7fbc6 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -123,14 +123,20 @@ return { "nocombine"; }; -- Autocmds + clear_autocmd = { + "buffer"; + "event"; + "group"; + "pattern"; + }; create_autocmd = { "buffer"; "callback"; "command"; "desc"; "group"; - "once"; "nested"; + "once"; "pattern"; }; exec_autocmd = { diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index bc7c2e6a60..650349cde7 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -140,8 +140,9 @@ typedef struct { // Useful macro for executing some `code` for each item in an array. #define FOREACH_ITEM(a, __foreach_item, code) \ - for (size_t __foreach_i = 0; __foreach_i < (a).size; __foreach_i++) { \ - Object __foreach_item = (a).items[__foreach_i]; \ + for (size_t (__foreach_item ## _index) = 0; (__foreach_item ## _index) < (a).size; \ + (__foreach_item ## _index)++) { \ + Object __foreach_item = (a).items[__foreach_item ## _index]; \ code; \ } -- cgit From 929293815bc6b9b1b5fdd129970c4e2f7279a6d6 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 31 Mar 2022 09:59:14 +0100 Subject: fix(api): improve autocmd error handling - nvim_del_augroup_* now works with pcall - nvim_del_autocmd now errors for invalid ids --- src/nvim/api/autocmd.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 49f6f98ba4..66ac394498 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -609,10 +609,16 @@ cleanup: /// NOTE: Only autocommands created via the API have an id. /// @param id Integer The id returned by nvim_create_autocmd /// @see |nvim_create_autocmd()| -void nvim_del_autocmd(Integer id) +void nvim_del_autocmd(Integer id, Error *err) FUNC_API_SINCE(9) { - autocmd_delete_id(id); + if (id <= 0) { + api_set_error(err, kErrorTypeException, "Invalid autocmd id"); + return; + } + if (!autocmd_delete_id(id)) { + api_set_error(err, kErrorTypeException, "Failed to delete autocmd"); + } } /// Create or get an autocommand group |autocmd-groups|. @@ -664,11 +670,15 @@ Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augrou /// @param id Integer The id of the group. /// @see |nvim_del_augroup_by_name()| /// @see |nvim_create_augroup()| -void nvim_del_augroup_by_id(Integer id) +void nvim_del_augroup_by_id(Integer id, Error *err) FUNC_API_SINCE(9) { - char *name = augroup_name((int)id); - augroup_del(name, false); + TRY_WRAP({ + try_start(); + char *name = augroup_name((int)id); + augroup_del(name, false); + try_end(err); + }); } /// Delete an autocommand group by name. @@ -677,10 +687,14 @@ void nvim_del_augroup_by_id(Integer id) /// this group will also be deleted and cleared. This group will no longer exist. /// @param name String The name of the group. /// @see |autocommand-groups| -void nvim_del_augroup_by_name(String name) +void nvim_del_augroup_by_name(String name, Error *err) FUNC_API_SINCE(9) { - augroup_del(name.data, false); + TRY_WRAP({ + try_start(); + augroup_del(name.data, false); + try_end(err); + }); } /// Execute an autocommand |autocmd-execute|. -- cgit From ebab51b192f3737b4d902a500b6366e174e7a891 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Thu, 31 Mar 2022 18:05:17 +0200 Subject: docs(extmark): fix nvim_buf_get_extmarks example (#17934) --- src/nvim/api/extmark.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 797b64e2af..8dca37a321 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -259,9 +259,9 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, /// 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, {}) +/// 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, 0, 2, 0, {}) +/// local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, {}) /// -- 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. -- cgit From 9d40b2fda96709a8219369316a5768ca4b689e4f Mon Sep 17 00:00:00 2001 From: György Andorka Date: Thu, 31 Mar 2022 19:13:22 +0200 Subject: refactor(api)!: use singular/plural consistently in the autocmd API --- src/nvim/api/autocmd.c | 9 +++++---- src/nvim/api/keysets.lua | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 482aa80f1e..00a418367f 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -36,7 +36,7 @@ // Used to delete autocmds from nvim_del_autocmd static int64_t next_autocmd_id = 1; -/// Get autocommands that match the requirements passed to {opts}. +/// Get all autocommands that match the corresponding {opts}. /// /// These examples will get autocommands matching ALL the given criteria: ///
@@ -562,7 +562,7 @@ void nvim_del_autocmd(Integer id)
 ///         - group: (string|int) The augroup name or id.
 ///             - NOTE: If not passed, will only delete autocmds *not* in any group.
 ///
-void nvim_clear_autocmd(Dict(clear_autocmd) *opts, Error *err)
+void nvim_clear_autocmds(Dict(clear_autocmds) *opts, Error *err)
   FUNC_API_SINCE(9)
 {
   // TODO(tjdevries): Future improvements:
@@ -697,7 +697,8 @@ void nvim_del_augroup_by_name(String name)
   augroup_del(name.data, false);
 }
 
-/// Execute an autocommand |autocmd-execute|.
+/// Execute all autocommands for {event} that match the corresponding
+///  {opts} |autocmd-execute|.
 /// @param event (String|Array) The event or events to execute
 /// @param opts Dictionary of autocommand options:
 ///             - group (string|integer) optional: the autocommand group name or
@@ -709,7 +710,7 @@ void nvim_del_augroup_by_name(String name)
 ///             - modeline (bool) optional: defaults to true. Process the
 ///             modeline after the autocommands ||.
 /// @see |:doautocmd|
-void nvim_exec_autocmd(Object event, Dict(exec_autocmd) *opts, Error *err)
+void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err)
   FUNC_API_SINCE(9)
 {
   int au_group = AUGROUP_ALL;
diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
index 520bb7fbc6..8ad4dae928 100644
--- a/src/nvim/api/keysets.lua
+++ b/src/nvim/api/keysets.lua
@@ -123,7 +123,7 @@ return {
     "nocombine";
   };
   -- Autocmds
-  clear_autocmd = {
+  clear_autocmds = {
     "buffer";
     "event";
     "group";
@@ -139,7 +139,7 @@ return {
     "once";
     "pattern";
   };
-  exec_autocmd = {
+  exec_autocmds = {
     "buffer";
     "group";
     "modeline";
-- 
cgit 


From 30bc02c6364f384e437a6f53b057522d585492fc Mon Sep 17 00:00:00 2001
From: Gregory Anders 
Date: Sat, 19 Mar 2022 19:16:19 -0600
Subject: feat(api)!: pass args table to autocommand callbacks

---
 src/nvim/api/autocmd.c | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
index 57f392f98e..ccf4ae3d02 100644
--- a/src/nvim/api/autocmd.c
+++ b/src/nvim/api/autocmd.c
@@ -366,7 +366,7 @@ cleanup:
 ///   {"CursorHold", "BufPreWrite", "BufPostWrite"}
 /// 
/// -/// @param event (String|Array) The event or events to register this autocommand +/// @param event (string|array) The event or events to register this autocommand /// @param opts Dictionary of autocommand options: /// - group (string|integer) optional: the autocommand group name or /// id to match against. @@ -375,8 +375,18 @@ cleanup: /// - buffer (integer) optional: buffer number for buffer local autocommands /// |autocmd-buflocal|. Cannot be used with {pattern}. /// - desc (string) optional: description of the autocommand. -/// - callback (function|string) optional: Lua function or Vim function (as string) to -/// execute on event. Cannot be used with {command} +/// - callback (function|string) optional: if a string, the name of a Vimscript function +/// to call when this autocommand is triggered. Otherwise, a Lua function which is +/// called when this autocommand is triggered. Cannot be used with {command}. Lua +/// callbacks can return true to delete the autocommand; in addition, they accept a +/// single table argument with the following keys: +/// - id: (number) the autocommand id +/// - event: (string) the name of the event that triggered the autocommand +/// |autocmd-events| +/// - group: (number|nil) the autocommand group id, if it exists +/// - match: (string) the expanded value of || +/// - buf: (number) the expanded value of || +/// - file: (string) the expanded value of || /// - command (string) optional: Vim command to execute on event. Cannot be used with /// {callback} /// - once (boolean) optional: defaults to false. Run the autocommand -- cgit From 263a7fde35f2341f526a536690122b927300021a Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 10 Apr 2022 07:20:35 +0800 Subject: vim-patch:8.2.4723: the ModeChanged autocmd event is inefficient Problem: The ModeChanged autocmd event is inefficient. Solution: Avoid allocating memory. (closes vim/vim#10134) Rename trigger_modechanged() to may_trigger_modechanged(). https://github.com/vim/vim/commit/2bf52dd065495cbf28e28792f2c2d50d44546d9f Make v:event readonly for ModeChanged. --- src/nvim/api/vim.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 7c7ada55a2..503a1c9f23 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1549,10 +1549,11 @@ Dictionary nvim_get_mode(void) FUNC_API_SINCE(2) FUNC_API_FAST { Dictionary rv = ARRAY_DICT_INIT; - char *modestr = get_mode(); + char modestr[MODE_MAX_LENGTH]; + get_mode(modestr); bool blocked = input_blocking(); - PUT(rv, "mode", STRING_OBJ(cstr_as_string(modestr))); + PUT(rv, "mode", STRING_OBJ(cstr_to_string(modestr))); PUT(rv, "blocking", BOOLEAN_OBJ(blocked)); return rv; -- cgit From f94f75dc0512def7fbfdfe100eea2dab3352d61f Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Sun, 10 Apr 2022 19:12:41 -0600 Subject: refactor!: rename nvim_add_user_command to nvim_create_user_command --- src/nvim/api/buffer.c | 10 +++++----- src/nvim/api/private/helpers.c | 3 ++- src/nvim/api/vim.c | 6 +++--- 3 files changed, 10 insertions(+), 9 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 18f7177489..6d5803a5fe 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1377,9 +1377,9 @@ Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err) /// /// @param buffer Buffer handle, or 0 for current buffer. /// @param[out] err Error details, if any. -/// @see nvim_add_user_command -void nvim_buf_add_user_command(Buffer buffer, String name, Object command, Dict(user_command) *opts, - Error *err) +/// @see nvim_create_user_command +void nvim_buf_create_user_command(Buffer buffer, String name, Object command, + Dict(user_command) *opts, Error *err) FUNC_API_SINCE(9) { buf_T *target_buf = find_buffer_by_handle(buffer, err); @@ -1389,14 +1389,14 @@ void nvim_buf_add_user_command(Buffer buffer, String name, Object command, Dict( buf_T *save_curbuf = curbuf; curbuf = target_buf; - add_user_command(name, command, opts, UC_BUFFER, err); + create_user_command(name, command, opts, UC_BUFFER, err); curbuf = save_curbuf; } /// Delete a buffer-local user-defined command. /// /// Only commands created with |:command-buffer| or -/// |nvim_buf_add_user_command()| can be deleted with this function. +/// |nvim_buf_create_user_command()| can be deleted with this function. /// /// @param buffer Buffer handle, or 0 for current buffer. /// @param name Name of the command to delete. diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 88954a1aa2..5ba4700f62 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1416,7 +1416,8 @@ const char *get_default_stl_hl(win_T *wp) } } -void add_user_command(String name, Object command, Dict(user_command) *opts, int flags, Error *err) +void create_user_command(String name, Object command, Dict(user_command) *opts, int flags, + Error *err) { uint32_t argt = 0; long def = -1; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 503a1c9f23..626f7dc3eb 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -2408,7 +2408,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * /// /// Example: ///
-///    :call nvim_add_user_command('SayHello', 'echo "Hello world!"', {})
+///    :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {})
 ///    :SayHello
 ///    Hello world!
 /// 
@@ -2436,10 +2436,10 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * /// {command}. /// - force: (boolean, default true) Override any previous definition. /// @param[out] err Error details, if any. -void nvim_add_user_command(String name, Object command, Dict(user_command) *opts, Error *err) +void nvim_create_user_command(String name, Object command, Dict(user_command) *opts, Error *err) FUNC_API_SINCE(9) { - add_user_command(name, command, opts, 0, err); + create_user_command(name, command, opts, 0, err); } /// Delete a user-defined command. -- cgit From e63e5d1dbd3dd4711efa0ecf9e844ff308b370a6 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Fri, 15 Apr 2022 12:35:06 +0200 Subject: docs: typo fixes (#17859) Co-authored-by: Elias Alves Moura Co-authored-by: venkatesh Co-authored-by: zeertzjq Co-authored-by: Vikas Raj <24727447+numToStr@users.noreply.github.com> Co-authored-by: Steve Vermeulen Co-authored-by: Evgeni Chasnovski Co-authored-by: rwxd Co-authored-by: casswedson <58050969+casswedson@users.noreply.github.com> --- src/nvim/api/autocmd.c | 6 +++--- src/nvim/api/window.c | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index ccf4ae3d02..a012f3d7fc 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -360,10 +360,10 @@ cleanup: /// pattern = { "*.py", "*.pyi" } ///
/// -/// Examples values for event: +/// Example values for event: ///
-///   "BufPreWrite"
-///   {"CursorHold", "BufPreWrite", "BufPostWrite"}
+///   "BufWritePre"
+///   {"CursorHold", "BufWritePre", "BufWritePost"}
 /// 
/// /// @param event (string|array) The event or events to register this autocommand diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index fd33a82be3..3a3a65f812 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -139,8 +139,7 @@ Integer nvim_win_get_height(Window window, Error *err) return win->w_height; } -/// Sets the window height. This will only succeed if the screen is split -/// horizontally. +/// Sets the window height. /// /// @param window Window handle, or 0 for current window /// @param height Height as a count of rows -- cgit From d63ad42e4978c2de9486e383fc9865f22a743939 Mon Sep 17 00:00:00 2001 From: hlpr98 Date: Sat, 16 Apr 2022 14:46:20 +0200 Subject: feat(api): ui options relevant for remote TUI --- src/nvim/api/ui.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index d86aecc318..383c9c16ab 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -14,6 +14,7 @@ #include "nvim/map.h" #include "nvim/memory.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/option.h" #include "nvim/popupmnu.h" #include "nvim/screen.h" #include "nvim/ui.h" @@ -255,6 +256,33 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e return; } + if (strequal(name.data, "term_name")) { + if (value.type != kObjectTypeString) { + api_set_error(error, kErrorTypeValidation, "term_name must be a String"); + return; + } + set_tty_option("term", xstrdup(value.data.string.data)); + return; + } + + if (strequal(name.data, "term_colors")) { + if (value.type != kObjectTypeInteger) { + api_set_error(error, kErrorTypeValidation, "term_colors must be a Integer"); + return; + } + t_colors = (int)value.data.integer; + return; + } + + if (strequal(name.data, "term_background")) { + if (value.type != kObjectTypeString) { + api_set_error(error, kErrorTypeValidation, "term_background must be a String"); + return; + } + set_tty_background(value.data.string.data); + return; + } + // LEGACY: Deprecated option, use `ext_cmdline` instead. bool is_popupmenu = strequal(name.data, "popupmenu_external"); -- cgit From a48a0a4f7b2b1255025ceb128f6cc97fa0f992aa Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Wed, 20 Apr 2022 18:42:07 +0200 Subject: docs(api): add example showing necessity to wrap callback function (#18179) Some people ran into issues trying to use `callback = myluafun` because of the event data payload. Co-authored-by: Gregory Anders <8965202+gpanders@users.noreply.github.com> --- src/nvim/api/autocmd.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index a012f3d7fc..45972ec8ea 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -346,6 +346,22 @@ cleanup: /// }) /// /// +/// Lua functions receive a table with information about the autocmd event as an argument. To use +/// a function which itself accepts another (optional) parameter, wrap the function +/// in a lambda: +/// +///
+///     -- Lua function with an optional parameter.
+///     -- The autocmd callback would pass a table as argument but this
+///     -- function expects number|nil
+///     local myluafun = function(bufnr) bufnr = bufnr or vim.api.nvim_get_current_buf() end
+///
+///     vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
+///       pattern = {"*.c", "*.h"},
+///       callback = function() myluafun() end,
+///     })
+/// 
+/// /// Example using command: ///
 ///     vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
-- 
cgit 


From 92f7286377cb077cb42484a83740a8c95f8ae7f1 Mon Sep 17 00:00:00 2001
From: dundargoc <33953936+dundargoc@users.noreply.github.com>
Date: Sun, 24 Apr 2022 02:01:01 +0200
Subject: docs: make docstring consistent with parameters #18178

Closes: https://github.com/neovim/neovim/issues/12691
---
 src/nvim/api/buffer.c | 4 ++--
 src/nvim/api/vim.c    | 3 +--
 2 files changed, 3 insertions(+), 4 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 6d5803a5fe..0c68325f40 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -529,9 +529,9 @@ end:
 /// @param channel_id
 /// @param buffer           Buffer handle, or 0 for current buffer
 /// @param start_row        First line index
-/// @param start_column     First column
+/// @param start_col        First column
 /// @param end_row          Last line index
-/// @param end_column       Last column
+/// @param end_col          Last column
 /// @param replacement      Array of lines to use as replacement
 /// @param[out] err         Error details, if any
 void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, Integer start_col,
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 626f7dc3eb..7a966777af 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -526,8 +526,7 @@ String nvim__get_lib_dir(void)
 ///
 /// @param pat pattern of files to search for
 /// @param all whether to return all matches or only the first
-/// @param options
-///          is_lua: only search lua subdirs
+/// @param opts is_lua: only search lua subdirs
 /// @return list of absolute paths to the found files
 ArrayOf(String) nvim__get_runtime(Array pat, Boolean all, Dict(runtime) *opts, Error *err)
   FUNC_API_SINCE(8)
-- 
cgit 


From 5f3018fa1a7a97d1f961f4c33e5ae418c19202ef Mon Sep 17 00:00:00 2001
From: erw7 
Date: Wed, 27 Apr 2022 13:17:06 +0900
Subject: refactor(terminal)!: drop winpty, require Windows 10 #18253

Problem:
winpty is only needed for Windows 8.1. Removing it reduces our build and code
complexity.

Solution:
- Remove winpty.
- Require Windows 10.

closes #18252
---
 src/nvim/api/vim.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 7a966777af..0f9a4a0e0d 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -1740,7 +1740,7 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dictionary version,
 ///    -  "pty"     (optional) Name of pseudoterminal. On a POSIX system this
 ///                 is a device path like "/dev/pts/1". If the name is unknown,
 ///                 the key will still be present if a pty is used (e.g. for
-///                 winpty on Windows).
+///                 conpty on Windows).
 ///    -  "buffer"  (optional) Buffer with connected |terminal| instance.
 ///    -  "client"  (optional) Info about the peer (client on the other end of
 ///                 the RPC channel), if provided by it via
-- 
cgit 


From dde4f09f51ffaf8df5cc2a81eed935e31e1f94ba Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Thu, 31 Mar 2022 15:47:53 +0800
Subject: vim-patch:8.1.2145: cannot map  when modifyOtherKeys is enabled

Problem:    Cannot map  when modifyOtherKeys is enabled.
Solution:   Add the  mapping twice, both with modifier and as 0x08.  Use
            only the first one when modifyOtherKeys has been detected.
https://github.com/vim/vim/commit/459fd785e4a8d044147a3f83a5fca8748528aa84

Add REPTERM_NO_SPECIAL instead of REPTERM_SPECIAL because the meaning of
"special" is different between Vim and Nvim.
Omit seenModifyOtherKeys as Nvim supports attaching multiple UIs.
Omit tests as they send terminal codes.
Keep the behavior of API functions.
---
 src/nvim/api/private/helpers.c |  5 ++++-
 src/nvim/api/vim.c             | 14 ++++++++++++--
 2 files changed, 16 insertions(+), 3 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 5ba4700f62..8383f29400 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -632,7 +632,7 @@ void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mod
   } else {
     parsed_args.desc = NULL;
   }
-  if (parsed_args.lhs_len > MAXMAPLEN) {
+  if (parsed_args.lhs_len > MAXMAPLEN || parsed_args.alt_lhs_len > MAXMAPLEN) {
     api_set_error(err, kErrorTypeValidation,  "LHS exceeds maximum map length: %s", lhs.data);
     goto fail_and_free;
   }
@@ -1128,6 +1128,9 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf, bool from_lua)
     for (const mapblock_T *current_maphash = get_maphash(i, buf);
          current_maphash;
          current_maphash = current_maphash->m_next) {
+      if (current_maphash->m_simplified) {
+        continue;
+      }
       // Check for correct mode
       if (int_mode & current_maphash->m_mode) {
         mapblock_fill_dict(dict, current_maphash, buffer_value, false);
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 0f9a4a0e0d..2174cd1620 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -403,9 +403,19 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, Bool
     return (String) { .data = NULL, .size = 0 };
   }
 
+  int flags = 0;
+  if (from_part) {
+    flags |= REPTERM_FROM_PART;
+  }
+  if (do_lt) {
+    flags |= REPTERM_DO_LT;
+  }
+  if (!special) {
+    flags |= REPTERM_NO_SPECIAL;
+  }
+
   char *ptr = NULL;
-  replace_termcodes((char_u *)str.data, str.size, (char_u **)&ptr,
-                    from_part, do_lt, special, CPO_TO_CPO_FLAGS);
+  replace_termcodes((char_u *)str.data, str.size, (char_u **)&ptr, flags, NULL, CPO_TO_CPO_FLAGS);
   return cstr_as_string(ptr);
 }
 
-- 
cgit 


From 995c1863685d7fa0cc2638b55efee55c4cb7ffc9 Mon Sep 17 00:00:00 2001
From: Dundar Goc 
Date: Mon, 18 Apr 2022 12:54:25 +0200
Subject: refactor(uncrustify): disable formatting in problematic code sections

---
 src/nvim/api/extmark.c | 4 ++++
 1 file changed, 4 insertions(+)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index 8dca37a321..ff9c8f2d47 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -546,6 +546,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
     goto error;
   }
 
+  // uncrustify:off
+
   struct {
     const char *name;
     Object *opt;
@@ -559,6 +561,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
     { NULL, NULL, NULL },
   };
 
+  // uncrustify:on
+
   for (int j = 0; hls[j].name && hls[j].dest; j++) {
     if (HAS_KEY(*hls[j].opt)) {
       *hls[j].dest = object_to_hl_id(*hls[j].opt, hls[j].name, err);
-- 
cgit 


From 0b3ae64480ea28bb57783c2269a61f0a60ffc55e Mon Sep 17 00:00:00 2001
From: Dundar Goc 
Date: Fri, 29 Apr 2022 13:52:43 +0200
Subject: refactor(uncrustify): format all c code under /src/nvim/

---
 src/nvim/api/private/helpers.h | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index 650349cde7..bbbc3de7d5 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -140,9 +140,9 @@ typedef struct {
 
 // Useful macro for executing some `code` for each item in an array.
 #define FOREACH_ITEM(a, __foreach_item, code) \
-  for (size_t (__foreach_item ## _index) = 0; (__foreach_item ## _index) < (a).size; \
-       (__foreach_item ## _index)++) { \
-    Object __foreach_item = (a).items[__foreach_item ## _index]; \
+  for (size_t (__foreach_item##_index) = 0; (__foreach_item##_index) < (a).size; \
+       (__foreach_item##_index)++) { \
+    Object __foreach_item = (a).items[__foreach_item##_index]; \
     code; \
   }
 
-- 
cgit 


From eef8de4df0247157e57f306062b1b86e01a41454 Mon Sep 17 00:00:00 2001
From: Dundar Goc 
Date: Fri, 29 Apr 2022 13:53:42 +0200
Subject: refactor(uncrustify): change rules to better align with the style
 guide

Add space around arithmetic operators '+' and '-'.
Remove space between back-to-back parentheses, i.e. ')(' vs. ') ('.
Remove space between '((' or '))' of control statements.
Add space between ')' and '{' of control statements.
Remove space between function name and '(' on function declaration.
Collapse empty blocks between '{' and '}'.
Remove newline at the end of the file.
Remove newline between 'enum' and '{'.
Remove newline between '}' and ')' in a function invocation.
Remove newline between '}' and 'while' of 'do' statement.
---
 src/nvim/api/buffer.c          | 44 +++++++++++++++++++++---------------------
 src/nvim/api/deprecated.c      |  6 +++---
 src/nvim/api/extmark.c         |  4 ++--
 src/nvim/api/private/helpers.c |  2 +-
 src/nvim/api/tabpage.c         |  1 -
 src/nvim/api/ui.c              | 20 +++++++++----------
 src/nvim/api/win_config.c      |  8 ++++----
 7 files changed, 42 insertions(+), 43 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 0c68325f40..9b21608ce9 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -247,7 +247,7 @@ void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last, Error *e
     return;
   }
 
-  redraw_buf_range_later(buf, (linenr_T)first+1, (linenr_T)last);
+  redraw_buf_range_later(buf, (linenr_T)first + 1, (linenr_T)last);
 }
 
 /// Gets a line-range from the buffer.
@@ -495,7 +495,7 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
               (long)extra,
               kExtmarkNOOP);
 
-  extmark_splice(curbuf, (int)start-1, 0, (int)(end-start), 0,
+  extmark_splice(curbuf, (int)start - 1, 0, (int)(end - start), 0,
                  deleted_bytes, (int)new_len, 0, inserted_bytes,
                  kExtmarkUndo);
 
@@ -602,15 +602,15 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
       int64_t lnum = start_row + i;
 
       const char *bufline = (char *)ml_get_buf(buf, lnum, false);
-      old_byte += (bcount_t)(strlen(bufline))+1;
+      old_byte += (bcount_t)(strlen(bufline)) + 1;
     }
-    old_byte += (bcount_t)end_col+1;
+    old_byte += (bcount_t)end_col + 1;
   }
 
   String first_item = replacement.items[0].data.string;
-  String last_item = replacement.items[replacement.size-1].data.string;
+  String last_item = replacement.items[replacement.size - 1].data.string;
 
-  size_t firstlen = (size_t)start_col+first_item.size;
+  size_t firstlen = (size_t)start_col + first_item.size;
   size_t last_part_len = strlen(str_at_end) - (size_t)end_col;
   if (replacement.size == 1) {
     firstlen += last_part_len;
@@ -618,32 +618,32 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
   char *first = xmallocz(firstlen);
   char *last = NULL;
   memcpy(first, str_at_start, (size_t)start_col);
-  memcpy(first+start_col, first_item.data, first_item.size);
-  memchrsub(first+start_col, NUL, NL, first_item.size);
+  memcpy(first + start_col, first_item.data, first_item.size);
+  memchrsub(first + start_col, NUL, NL, first_item.size);
   if (replacement.size == 1) {
-    memcpy(first+start_col+first_item.size, str_at_end+end_col, last_part_len);
+    memcpy(first + start_col + first_item.size, str_at_end + end_col, last_part_len);
   } else {
-    last = xmallocz(last_item.size+last_part_len);
+    last = xmallocz(last_item.size + last_part_len);
     memcpy(last, last_item.data, last_item.size);
     memchrsub(last, NUL, NL, last_item.size);
-    memcpy(last+last_item.size, str_at_end+end_col, last_part_len);
+    memcpy(last + last_item.size, str_at_end + end_col, last_part_len);
   }
 
   char **lines = xcalloc(new_len, sizeof(char *));
   lines[0] = first;
   new_byte += (bcount_t)(first_item.size);
-  for (size_t i = 1; i < new_len-1; i++) {
+  for (size_t i = 1; i < new_len - 1; i++) {
     const String l = replacement.items[i].data.string;
 
     // Fill lines[i] with l's contents. Convert NULs to newlines as required by
     // NL-used-for-NUL.
     lines[i] = xmemdupz(l.data, l.size);
     memchrsub(lines[i], NUL, NL, l.size);
-    new_byte += (bcount_t)(l.size)+1;
+    new_byte += (bcount_t)(l.size) + 1;
   }
   if (replacement.size > 1) {
-    lines[replacement.size-1] = last;
-    new_byte += (bcount_t)(last_item.size)+1;
+    lines[replacement.size - 1] = last;
+    new_byte += (bcount_t)(last_item.size) + 1;
   }
 
   try_start();
@@ -663,7 +663,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
   }
 
   ptrdiff_t extra = 0;  // lines added to text, can be negative
-  size_t old_len = (size_t)(end_row-start_row+1);
+  size_t old_len = (size_t)(end_row - start_row + 1);
 
   // If the size of the range is reducing (ie, new_len < old_len) we
   // need to delete some old_len. We do this at the start, by
@@ -731,9 +731,9 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
 
   colnr_T col_extent = (colnr_T)(end_col
                                  - ((end_row == start_row) ? start_col : 0));
-  extmark_splice(buf, (int)start_row-1, (colnr_T)start_col,
-                 (int)(end_row-start_row), col_extent, old_byte,
-                 (int)new_len-1, (colnr_T)last_item.size, new_byte,
+  extmark_splice(buf, (int)start_row - 1, (colnr_T)start_col,
+                 (int)(end_row - start_row), col_extent, old_byte,
+                 (int)new_len - 1, (colnr_T)last_item.size, new_byte,
                  kExtmarkUndo);
 
 
@@ -829,7 +829,7 @@ ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer,
   rv.size = (size_t)(end_row - start_row) + 1;
   rv.items = xcalloc(rv.size, sizeof(Object));
 
-  rv.items[0] = STRING_OBJ(buf_get_text(buf, start_row, start_col, MAXCOL-1, replace_nl, err));
+  rv.items[0] = STRING_OBJ(buf_get_text(buf, start_row, start_col, MAXCOL - 1, replace_nl, err));
   if (ERROR_SET(err)) {
     goto end;
   }
@@ -842,7 +842,7 @@ ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer,
     }
   }
 
-  rv.items[rv.size-1] = STRING_OBJ(buf_get_text(buf, end_row, 0, end_col, replace_nl, err));
+  rv.items[rv.size - 1] = STRING_OBJ(buf_get_text(buf, end_row, 0, end_col, replace_nl, err));
   if (ERROR_SET(err)) {
     goto end;
   }
@@ -889,7 +889,7 @@ Integer nvim_buf_get_offset(Buffer buffer, Integer index, Error *err)
     return 0;
   }
 
-  return ml_find_line_or_offset(buf, (int)index+1, NULL, true);
+  return ml_find_line_or_offset(buf, (int)index + 1, NULL, true);
 }
 
 /// Gets a buffer-scoped (b:) variable.
diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c
index 6a41df0aa9..f968593e47 100644
--- a/src/nvim/api/deprecated.c
+++ b/src/nvim/api/deprecated.c
@@ -192,7 +192,7 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)
   String rv = { .size = 0 };
 
   index = convert_index(index);
-  Array slice = nvim_buf_get_lines(0, buffer, index, index+1, true, err);
+  Array slice = nvim_buf_get_lines(0, buffer, index, index + 1, true, err);
 
   if (!ERROR_SET(err) && slice.size) {
     rv = slice.items[0].data.string;
@@ -221,7 +221,7 @@ void buffer_set_line(Buffer buffer, Integer index, String line, Error *err)
   Object l = STRING_OBJ(line);
   Array array = { .items = &l, .size = 1 };
   index = convert_index(index);
-  nvim_buf_set_lines(0, buffer, index, index+1, true,  array, err);
+  nvim_buf_set_lines(0, buffer, index, index + 1, true,  array, err);
 }
 
 /// Deletes a buffer line
@@ -239,7 +239,7 @@ void buffer_del_line(Buffer buffer, Integer index, Error *err)
 {
   Array array = ARRAY_DICT_INIT;
   index = convert_index(index);
-  nvim_buf_set_lines(0, buffer, index, index+1, true, array, err);
+  nvim_buf_set_lines(0, buffer, index, index + 1, true, array, err);
 }
 
 /// Retrieves a line range from the buffer
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index ff9c8f2d47..e408d88854 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -720,7 +720,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
       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 = ephemeral ? MAXCOL : STRLEN(ml_get_buf(buf, (linenr_T)line + 1, false));
   }
 
   if (col == -1) {
@@ -927,7 +927,7 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
   }
   extmark_clear(buf, (ns_id < 0 ? 0 : (uint32_t)ns_id),
                 (int)line_start, 0,
-                (int)line_end-1, MAXCOL);
+                (int)line_end - 1, MAXCOL);
 }
 
 /// Set or change decoration provider for a namespace
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 8383f29400..1c43564dca 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1245,7 +1245,7 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width)
           if (ERROR_SET(err)) {
             goto free_exit;
           }
-          if (j < arr.size-1) {
+          if (j < arr.size - 1) {
             kv_push(virt_text, ((VirtTextChunk){ .text = NULL,
                                                  .hl_id = hl_id }));
           }
diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c
index b994d18c43..b81fc3b7d7 100644
--- a/src/nvim/api/tabpage.c
+++ b/src/nvim/api/tabpage.c
@@ -150,4 +150,3 @@ Boolean nvim_tabpage_is_valid(Tabpage tabpage)
   api_clear_error(&stub);
   return ret;
 }
-
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index 383c9c16ab..997f0c218a 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -475,9 +475,9 @@ static void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot
   } else {
     Array args = ARRAY_DICT_INIT;
     ADD(args, INTEGER_OBJ(top));
-    ADD(args, INTEGER_OBJ(bot-1));
+    ADD(args, INTEGER_OBJ(bot - 1));
     ADD(args, INTEGER_OBJ(left));
-    ADD(args, INTEGER_OBJ(right-1));
+    ADD(args, INTEGER_OBJ(right - 1));
     push_call(ui, "set_scroll_region", args);
 
     args = (Array)ARRAY_DICT_INIT;
@@ -488,9 +488,9 @@ static void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot
     // so reset it.
     args = (Array)ARRAY_DICT_INIT;
     ADD(args, INTEGER_OBJ(0));
-    ADD(args, INTEGER_OBJ(ui->height-1));
+    ADD(args, INTEGER_OBJ(ui->height - 1));
     ADD(args, INTEGER_OBJ(0));
-    ADD(args, INTEGER_OBJ(ui->width-1));
+    ADD(args, INTEGER_OBJ(ui->width - 1));
     push_call(ui, "set_scroll_region", args);
   }
 }
@@ -615,12 +615,12 @@ static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startc
     ADD(args, INTEGER_OBJ(startcol));
     Array cells = ARRAY_DICT_INIT;
     int repeat = 0;
-    size_t ncells = (size_t)(endcol-startcol);
+    size_t ncells = (size_t)(endcol - startcol);
     int last_hl = -1;
     for (size_t i = 0; i < ncells; i++) {
       repeat++;
-      if (i == ncells-1 || attrs[i] != attrs[i+1]
-          || STRCMP(chunk[i], chunk[i+1])) {
+      if (i == ncells - 1 || attrs[i] != attrs[i + 1]
+          || STRCMP(chunk[i], chunk[i + 1])) {
         Array cell = ARRAY_DICT_INIT;
         ADD(cell, STRING_OBJ(cstr_to_string((const char *)chunk[i])));
         if (attrs[i] != last_hl || repeat > 1) {
@@ -638,15 +638,15 @@ static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startc
       Array cell = ARRAY_DICT_INIT;
       ADD(cell, STRING_OBJ(cstr_to_string(" ")));
       ADD(cell, INTEGER_OBJ(clearattr));
-      ADD(cell, INTEGER_OBJ(clearcol-endcol));
+      ADD(cell, INTEGER_OBJ(clearcol - endcol));
       ADD(cells, ARRAY_OBJ(cell));
     }
     ADD(args, ARRAY_OBJ(cells));
 
     push_call(ui, "grid_line", args);
   } else {
-    for (int i = 0; i < endcol-startcol; i++) {
-      remote_ui_cursor_goto(ui, row, startcol+i);
+    for (int i = 0; i < endcol - startcol; i++) {
+      remote_ui_cursor_goto(ui, row, startcol + i);
       remote_ui_highlight_set(ui, attrs[i]);
       remote_ui_put(ui, (const char *)chunk[i]);
       if (utf_ambiguous_width(utf_ptr2char(chunk[i]))) {
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
index 5e37596884..e7704f72d7 100644
--- a/src/nvim/api/win_config.c
+++ b/src/nvim/api/win_config.c
@@ -354,7 +354,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
   if (style.type == kObjectTypeArray) {
     Array arr = style.data.array;
     size_t size = arr.size;
-    if (!size || size > 8 || (size & (size-1))) {
+    if (!size || size > 8 || (size & (size - 1))) {
       api_set_error(err, kErrorTypeValidation,
                     "invalid number of border chars");
       return;
@@ -392,7 +392,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
                       "border chars must be one cell");
         return;
       }
-      size_t len = MIN(string.size, sizeof(*chars)-1);
+      size_t len = MIN(string.size, sizeof(*chars) - 1);
       if (len) {
         memcpy(chars[i], string.data, len);
       }
@@ -400,8 +400,8 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
       hl_ids[i] = hl_id;
     }
     while (size < 8) {
-      memcpy(chars+size, chars, sizeof(*chars) * size);
-      memcpy(hl_ids+size, hl_ids, sizeof(*hl_ids) * size);
+      memcpy(chars + size, chars, sizeof(*chars) * size);
+      memcpy(hl_ids + size, hl_ids, sizeof(*hl_ids) * size);
       size <<= 1;
     }
     if ((chars[7][0] && chars[1][0] && !chars[0][0])
-- 
cgit 


From 3c23100130725bb79c04e933c505bbeda96fb3bb Mon Sep 17 00:00:00 2001
From: dundargoc <33953936+dundargoc@users.noreply.github.com>
Date: Sat, 30 Apr 2022 16:48:00 +0200
Subject: refactor: replace char_u variables and functions with char (#18288)

Work on https://github.com/neovim/neovim/issues/459
---
 src/nvim/api/private/helpers.c | 11 +++++------
 src/nvim/api/vim.c             |  2 +-
 2 files changed, 6 insertions(+), 7 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 1c43564dca..a07b5b6e3a 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1526,7 +1526,7 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
   }
 
   if (opts->addr.type == kObjectTypeString) {
-    if (parse_addr_type_arg((char_u *)opts->addr.data.string.data, (int)opts->addr.data.string.size,
+    if (parse_addr_type_arg(opts->addr.data.string.data, (int)opts->addr.data.string.size,
                             &addr_type_arg) != OK) {
       api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'");
       goto err;
@@ -1574,9 +1574,9 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
     compl = EXPAND_USER_LUA;
     compl_luaref = api_new_luaref(opts->complete.data.luaref);
   } else if (opts->complete.type == kObjectTypeString) {
-    if (parse_compl_arg((char_u *)opts->complete.data.string.data,
+    if (parse_compl_arg(opts->complete.data.string.data,
                         (int)opts->complete.data.string.size, &compl, &argt,
-                        (char_u **)&compl_arg) != OK) {
+                        &compl_arg) != OK) {
       api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'");
       goto err;
     }
@@ -1603,9 +1603,8 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
     goto err;
   }
 
-  if (uc_add_command((char_u *)name.data, name.size, (char_u *)rep, argt, def, flags,
-                     compl, (char_u *)compl_arg, compl_luaref, addr_type_arg, luaref,
-                     force) != OK) {
+  if (uc_add_command(name.data, name.size, rep, argt, def, flags, compl, compl_arg, compl_luaref,
+                     addr_type_arg, luaref, force) != OK) {
     api_set_error(err, kErrorTypeException, "Failed to create user command");
     goto err;
   }
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 2174cd1620..ac2fc09056 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -568,7 +568,7 @@ void nvim_set_current_dir(String dir, Error *err)
 
   try_start();
 
-  if (!changedir_func(string, kCdScopeGlobal)) {
+  if (!changedir_func((char *)string, kCdScopeGlobal)) {
     if (!try_end(err)) {
       api_set_error(err, kErrorTypeException, "Failed to change directory");
     }
-- 
cgit 


From 8dbb11ebf633e40cb57568e77c7168deffc8bd7f Mon Sep 17 00:00:00 2001
From: Famiu Haque 
Date: Sat, 23 Apr 2022 10:21:59 +0600
Subject: feat(api): add `nvim_parse_cmdline`

Adds an API function to parse a command line string and get command information from it.
---
 src/nvim/api/vim.c | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 201 insertions(+)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index ac2fc09056..3d1b5eade4 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -28,6 +28,7 @@
 #include "nvim/eval/typval.h"
 #include "nvim/eval/userfunc.h"
 #include "nvim/ex_cmds2.h"
+#include "nvim/ex_cmds_defs.h"
 #include "nvim/ex_docmd.h"
 #include "nvim/file_search.h"
 #include "nvim/fileio.h"
@@ -2460,3 +2461,203 @@ void nvim_del_user_command(String name, Error *err)
 {
   nvim_buf_del_user_command(-1, name, err);
 }
+
+/// Parse command line.
+///
+/// Doesn't check the validity of command arguments.
+///
+/// @param str       Command line string to parse. Cannot contain "\n".
+/// @param opts      Optional parameters. Reserved for future use.
+/// @param[out] err  Error details, if any.
+/// @return Dictionary containing command information, with these keys:
+///         - cmd: (string) Command name.
+///         - line1: (number) Starting line of command range. Only applicable if command can take a
+///                  range.
+///         - line2: (number) Final line of command range. Only applicable if command can take a
+///                  range.
+///         - bang: (boolean) Whether command contains a bang (!) modifier.
+///         - args: (array) Command arguments.
+///         - addr: (string) Value of |:command-addr|. Uses short name.
+///         - nargs: (string) Value of |:command-nargs|.
+///         - nextcmd: (string) Next command if there are multiple commands separated by a |:bar|.
+///                             Empty if there isn't a next command.
+///         - magic: (dictionary) Which characters have special meaning in the command arguments.
+///             - file: (boolean) The command expands filenames. Which means characters such as "%",
+///                               "#" and wildcards are expanded.
+///             - bar: (boolean) The "|" character is treated as a command separator and the double
+///                              quote character (\") is treated as the start of a comment.
+///         - mods: (dictionary) |:command-modifiers|.
+///             - silent: (boolean) |:silent|.
+///             - emsg_silent: (boolean) |:silent!|.
+///             - sandbox: (boolean) |:sandbox|.
+///             - noautocmd: (boolean) |:noautocmd|.
+///             - browse: (boolean) |:browse|.
+///             - confirm: (boolean) |:confirm|.
+///             - hide: (boolean) |:hide|.
+///             - keepalt: (boolean) |:keepalt|.
+///             - keepjumps: (boolean) |:keepjumps|.
+///             - keepmarks: (boolean) |:keepmarks|.
+///             - keeppatterns: (boolean) |:keeppatterns|.
+///             - lockmarks: (boolean) |:lockmarks|.
+///             - noswapfile: (boolean) |:noswapfile|.
+///             - tab: (integer) |:tab|.
+///             - verbose: (integer) |:verbose|.
+///             - vertical: (boolean) |:vertical|.
+///             - split: (string) Split modifier string, is an empty string when there's no split
+///                               modifier. If there is a split modifier it can be one of:
+///               - "aboveleft": |:aboveleft|.
+///               - "belowright": |:belowright|.
+///               - "topleft": |:topleft|.
+///               - "botright": |:botright|.
+Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
+  FUNC_API_SINCE(10) FUNC_API_FAST
+{
+  Dictionary result = ARRAY_DICT_INIT;
+
+  if (opts.size > 0) {
+    api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+    return result;
+  }
+
+  // Parse command line
+  exarg_T ea;
+  CmdParseInfo cmdinfo;
+  char_u *cmdline = vim_strsave((char_u *)str.data);
+
+  if (!parse_cmdline(cmdline, &ea, &cmdinfo)) {
+    api_set_error(err, kErrorTypeException, "Error while parsing command line");
+    goto end;
+  }
+
+  // Parse arguments
+  Array args = ARRAY_DICT_INIT;
+  size_t length = STRLEN(ea.arg);
+
+  // For nargs = 1 or '?', pass the entire argument list as a single argument,
+  // otherwise split arguments by whitespace.
+  if (ea.argt & EX_NOSPC) {
+    if (*ea.arg != NUL) {
+      ADD(args, STRING_OBJ(cstrn_to_string((char *)ea.arg, length)));
+    }
+  } else {
+    size_t end = 0;
+    size_t len = 0;
+    char *buf = xcalloc(length, sizeof(char));
+    bool done = false;
+
+    while (!done) {
+      done = uc_split_args_iter(ea.arg, length, &end, buf, &len);
+      if (len > 0) {
+        ADD(args, STRING_OBJ(cstrn_to_string(buf, len)));
+      }
+    }
+
+    xfree(buf);
+  }
+
+  if (ea.cmdidx == CMD_USER) {
+    PUT(result, "cmd", CSTR_TO_OBJ((char *)USER_CMD(ea.useridx)->uc_name));
+  } else if (ea.cmdidx == CMD_USER_BUF) {
+    PUT(result, "cmd", CSTR_TO_OBJ((char *)USER_CMD_GA(&curbuf->b_ucmds, ea.useridx)->uc_name));
+  } else {
+    PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx)));
+  }
+  PUT(result, "line1", INTEGER_OBJ(ea.line1));
+  PUT(result, "line2", INTEGER_OBJ(ea.line2));
+  PUT(result, "bang", BOOLEAN_OBJ(ea.forceit));
+  PUT(result, "args", ARRAY_OBJ(args));
+
+  char nargs[2];
+  if (ea.argt & EX_EXTRA) {
+    if (ea.argt & EX_NOSPC) {
+      if (ea.argt & EX_NEEDARG) {
+        nargs[0] = '1';
+      } else {
+        nargs[0] = '?';
+      }
+    } else if (ea.argt & EX_NEEDARG) {
+      nargs[0] = '+';
+    } else {
+      nargs[0] = '*';
+    }
+  } else {
+    nargs[0] = '0';
+  }
+  nargs[1] = '\0';
+  PUT(result, "nargs", CSTR_TO_OBJ(nargs));
+
+  const char *addr;
+  switch (ea.addr_type) {
+  case ADDR_LINES:
+    addr = "line";
+    break;
+  case ADDR_ARGUMENTS:
+    addr = "arg";
+    break;
+  case ADDR_BUFFERS:
+    addr = "buf";
+    break;
+  case ADDR_LOADED_BUFFERS:
+    addr = "load";
+    break;
+  case ADDR_WINDOWS:
+    addr = "win";
+    break;
+  case ADDR_TABS:
+    addr = "tab";
+    break;
+  case ADDR_QUICKFIX:
+    addr = "qf";
+    break;
+  case ADDR_NONE:
+    addr = "none";
+    break;
+  default:
+    addr = "?";
+    break;
+  }
+  PUT(result, "addr", CSTR_TO_OBJ(addr));
+  PUT(result, "nextcmd", CSTR_TO_OBJ((char *)ea.nextcmd));
+
+  Dictionary mods = ARRAY_DICT_INIT;
+  PUT(mods, "silent", BOOLEAN_OBJ(cmdinfo.silent));
+  PUT(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.emsg_silent));
+  PUT(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.sandbox));
+  PUT(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.noautocmd));
+  PUT(mods, "tab", INTEGER_OBJ(cmdmod.tab));
+  PUT(mods, "verbose", INTEGER_OBJ(cmdinfo.verbose));
+  PUT(mods, "browse", BOOLEAN_OBJ(cmdmod.browse));
+  PUT(mods, "confirm", BOOLEAN_OBJ(cmdmod.confirm));
+  PUT(mods, "hide", BOOLEAN_OBJ(cmdmod.hide));
+  PUT(mods, "keepalt", BOOLEAN_OBJ(cmdmod.keepalt));
+  PUT(mods, "keepjumps", BOOLEAN_OBJ(cmdmod.keepjumps));
+  PUT(mods, "keepmarks", BOOLEAN_OBJ(cmdmod.keepmarks));
+  PUT(mods, "keeppatterns", BOOLEAN_OBJ(cmdmod.keeppatterns));
+  PUT(mods, "lockmarks", BOOLEAN_OBJ(cmdmod.lockmarks));
+  PUT(mods, "noswapfile", BOOLEAN_OBJ(cmdmod.noswapfile));
+  PUT(mods, "vertical", BOOLEAN_OBJ(cmdmod.split & WSP_VERT));
+
+  const char *split;
+  if (cmdmod.split & WSP_BOT) {
+    split = "botright";
+  } else if (cmdmod.split & WSP_TOP) {
+    split = "topleft";
+  } else if (cmdmod.split & WSP_BELOW) {
+    split = "belowright";
+  } else if (cmdmod.split & WSP_ABOVE) {
+    split = "aboveleft";
+  } else {
+    split = "";
+  }
+  PUT(mods, "split", CSTR_TO_OBJ(split));
+
+  PUT(result, "mods", DICTIONARY_OBJ(mods));
+
+  Dictionary magic = ARRAY_DICT_INIT;
+  PUT(magic, "file", BOOLEAN_OBJ(cmdinfo.magic.file));
+  PUT(magic, "bar", BOOLEAN_OBJ(cmdinfo.magic.bar));
+  PUT(result, "magic", DICTIONARY_OBJ(magic));
+end:
+  xfree(cmdline);
+  return result;
+}
-- 
cgit 


From af782a630633ffe0cb082bda974b24d4f577313e Mon Sep 17 00:00:00 2001
From: Dundar Goc 
Date: Sat, 30 Apr 2022 20:28:04 +0200
Subject: refactor: replace char_u variables and functions with char

Work on https://github.com/neovim/neovim/issues/459
---
 src/nvim/api/buffer.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 9b21608ce9..b4d4265a37 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -1113,7 +1113,7 @@ void nvim_buf_set_name(Buffer buffer, String name, Error *err)
   // Using aucmd_*: autocommands will be executed by rename_buffer
   aco_save_T aco;
   aucmd_prepbuf(&aco, buf);
-  int ren_ret = rename_buffer((char_u *)name.data);
+  int ren_ret = rename_buffer(name.data);
   aucmd_restbuf(&aco);
 
   if (try_end(err)) {
-- 
cgit 


From bfb72f637b9d8dc9e4a05479c2b1a0fe1fbc5e36 Mon Sep 17 00:00:00 2001
From: Famiu Haque 
Date: Mon, 2 May 2022 11:23:16 +0600
Subject: fix(api): preserve `cmdmod` on `nvim_parse_cmd`

---
 src/nvim/api/vim.c | 30 +++++++++++++++---------------
 1 file changed, 15 insertions(+), 15 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 3d1b5eade4..061653c5af 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -2624,27 +2624,27 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
   PUT(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.emsg_silent));
   PUT(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.sandbox));
   PUT(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.noautocmd));
-  PUT(mods, "tab", INTEGER_OBJ(cmdmod.tab));
+  PUT(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.tab));
   PUT(mods, "verbose", INTEGER_OBJ(cmdinfo.verbose));
-  PUT(mods, "browse", BOOLEAN_OBJ(cmdmod.browse));
-  PUT(mods, "confirm", BOOLEAN_OBJ(cmdmod.confirm));
-  PUT(mods, "hide", BOOLEAN_OBJ(cmdmod.hide));
-  PUT(mods, "keepalt", BOOLEAN_OBJ(cmdmod.keepalt));
-  PUT(mods, "keepjumps", BOOLEAN_OBJ(cmdmod.keepjumps));
-  PUT(mods, "keepmarks", BOOLEAN_OBJ(cmdmod.keepmarks));
-  PUT(mods, "keeppatterns", BOOLEAN_OBJ(cmdmod.keeppatterns));
-  PUT(mods, "lockmarks", BOOLEAN_OBJ(cmdmod.lockmarks));
-  PUT(mods, "noswapfile", BOOLEAN_OBJ(cmdmod.noswapfile));
-  PUT(mods, "vertical", BOOLEAN_OBJ(cmdmod.split & WSP_VERT));
+  PUT(mods, "browse", BOOLEAN_OBJ(cmdinfo.cmdmod.browse));
+  PUT(mods, "confirm", BOOLEAN_OBJ(cmdinfo.cmdmod.confirm));
+  PUT(mods, "hide", BOOLEAN_OBJ(cmdinfo.cmdmod.hide));
+  PUT(mods, "keepalt", BOOLEAN_OBJ(cmdinfo.cmdmod.keepalt));
+  PUT(mods, "keepjumps", BOOLEAN_OBJ(cmdinfo.cmdmod.keepjumps));
+  PUT(mods, "keepmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.keepmarks));
+  PUT(mods, "keeppatterns", BOOLEAN_OBJ(cmdinfo.cmdmod.keeppatterns));
+  PUT(mods, "lockmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.lockmarks));
+  PUT(mods, "noswapfile", BOOLEAN_OBJ(cmdinfo.cmdmod.noswapfile));
+  PUT(mods, "vertical", BOOLEAN_OBJ(cmdinfo.cmdmod.split & WSP_VERT));
 
   const char *split;
-  if (cmdmod.split & WSP_BOT) {
+  if (cmdinfo.cmdmod.split & WSP_BOT) {
     split = "botright";
-  } else if (cmdmod.split & WSP_TOP) {
+  } else if (cmdinfo.cmdmod.split & WSP_TOP) {
     split = "topleft";
-  } else if (cmdmod.split & WSP_BELOW) {
+  } else if (cmdinfo.cmdmod.split & WSP_BELOW) {
     split = "belowright";
-  } else if (cmdmod.split & WSP_ABOVE) {
+  } else if (cmdinfo.cmdmod.split & WSP_ABOVE) {
     split = "aboveleft";
   } else {
     split = "";
-- 
cgit 


From 619c8f4b9143e4dea0fb967ccdce594e14956ed3 Mon Sep 17 00:00:00 2001
From: hlpr98 
Date: Sat, 16 Apr 2022 14:46:20 +0200
Subject: feat(api): support handling stdin stream in remote ui

---
 src/nvim/api/vim.c | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 061653c5af..9c34c912c0 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -2661,3 +2661,35 @@ end:
   xfree(cmdline);
   return result;
 }
+
+/// Invokes the nvim server to read from stdin when it is not a tty
+///
+/// It enables functionalities like:
+/// - echo "1f u c4n r34d th1s u r34lly n33d t0 g37 r357"| nvim -
+/// - cat path/to/a/file | nvim -
+/// It has to be called before |nvim_ui_attach()| is called in order
+/// to ensure proper functioning.
+///
+/// @param channel_id: The channel id of the GUI-client
+/// @param filedesc: The file descriptor of the GUI-client process' stdin
+/// @param implicit: Tells if read_stdin call is implicit.
+///                  i.e for cases like `echo xxx | nvim`
+/// @param[out] err Error details, if any
+void nvim_read_stdin(uint64_t channel_id, Integer filedesc, Error *err)
+FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY
+{
+  if (starting != NO_SCREEN) {
+    api_set_error(err, kErrorTypeValidation,
+                  "nvim_read_stdin must be called before nvim_ui_attach");
+    return;
+  }
+  if (filedesc < 0) {
+    api_set_error(err, kErrorTypeValidation,
+                  "file descriptor must be non-negative");
+    return;
+  }
+
+  stdin_filedesc = (int)filedesc;
+  implicit_readstdin = implicit;
+  return;
+}
-- 
cgit 


From ad63b94b03c166f37bda477db6cbac2a9583d586 Mon Sep 17 00:00:00 2001
From: bfredl 
Date: Fri, 22 Apr 2022 20:56:31 +0200
Subject: refactor(ui): simplify stdin handling

---
 src/nvim/api/ui.c  | 16 ++++++++++++++++
 src/nvim/api/vim.c | 32 --------------------------------
 2 files changed, 16 insertions(+), 32 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index 997f0c218a..f449ccc3c7 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -283,6 +283,22 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e
     return;
   }
 
+  if (strequal(name.data, "stdin_fd")) {
+    if (value.type != kObjectTypeInteger || value.data.integer < 0) {
+      api_set_error(error, kErrorTypeValidation, "stdin_fd must be a non-negative Integer");
+      return;
+    }
+
+    if (starting != NO_SCREEN) {
+      api_set_error(error, kErrorTypeValidation,
+                    "stdin_fd can only be used with first attached ui");
+      return;
+    }
+
+    stdin_fd = (int)value.data.integer;
+    return;
+  }
+
   // LEGACY: Deprecated option, use `ext_cmdline` instead.
   bool is_popupmenu = strequal(name.data, "popupmenu_external");
 
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 9c34c912c0..061653c5af 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -2661,35 +2661,3 @@ end:
   xfree(cmdline);
   return result;
 }
-
-/// Invokes the nvim server to read from stdin when it is not a tty
-///
-/// It enables functionalities like:
-/// - echo "1f u c4n r34d th1s u r34lly n33d t0 g37 r357"| nvim -
-/// - cat path/to/a/file | nvim -
-/// It has to be called before |nvim_ui_attach()| is called in order
-/// to ensure proper functioning.
-///
-/// @param channel_id: The channel id of the GUI-client
-/// @param filedesc: The file descriptor of the GUI-client process' stdin
-/// @param implicit: Tells if read_stdin call is implicit.
-///                  i.e for cases like `echo xxx | nvim`
-/// @param[out] err Error details, if any
-void nvim_read_stdin(uint64_t channel_id, Integer filedesc, Error *err)
-FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY
-{
-  if (starting != NO_SCREEN) {
-    api_set_error(err, kErrorTypeValidation,
-                  "nvim_read_stdin must be called before nvim_ui_attach");
-    return;
-  }
-  if (filedesc < 0) {
-    api_set_error(err, kErrorTypeValidation,
-                  "file descriptor must be non-negative");
-    return;
-  }
-
-  stdin_filedesc = (int)filedesc;
-  implicit_readstdin = implicit;
-  return;
-}
-- 
cgit 


From 13520aae163bfc243fc050cf16b89082c0896eaf Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Tue, 3 May 2022 09:29:55 +0800
Subject: fix(coverity): use xstrndup() instead of vim_strsave() (#18363)

---
 src/nvim/api/autocmd.c         |  6 +++---
 src/nvim/api/private/helpers.c | 12 +++++++++++-
 src/nvim/api/ui.c              |  2 +-
 src/nvim/api/vim.c             |  2 +-
 4 files changed, 16 insertions(+), 6 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
index 45972ec8ea..0db9a63e7a 100644
--- a/src/nvim/api/autocmd.c
+++ b/src/nvim/api/autocmd.c
@@ -460,7 +460,7 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
       cb.data.luaref = api_new_luaref(callback->data.luaref);
     } else if (callback->type == kObjectTypeString) {
       cb.type = kCallbackFuncref;
-      cb.data.funcref = vim_strsave((char_u *)callback->data.string.data);
+      cb.data.funcref = (char_u *)string_to_cstr(callback->data.string);
     } else {
       api_set_error(err,
                     kErrorTypeException,
@@ -474,7 +474,7 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
     Object *command = &opts->command;
     if (command->type == kObjectTypeString) {
       aucmd.type = CALLABLE_EX;
-      aucmd.callable.cmd = vim_strsave((char_u *)command->data.string.data);
+      aucmd.callable.cmd = (char_u *)string_to_cstr(command->data.string);
     } else {
       api_set_error(err,
                     kErrorTypeValidation,
@@ -814,7 +814,7 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err)
       goto cleanup;
     }
 
-    pattern = vim_strsave((char_u *)opts->pattern.data.string.data);
+    pattern = (char_u *)string_to_cstr(opts->pattern.data.string);
     set_pattern = true;
   }
 
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index a07b5b6e3a..a89a254f20 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -488,6 +488,16 @@ String cstr_to_string(const char *str)
   };
 }
 
+/// Copies a String to an allocated, NUL-terminated C string.
+///
+/// @param str the String to copy
+/// @return the resulting C string
+char *string_to_cstr(String str)
+  FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
+{
+  return xstrndup(str.data, str.size);
+}
+
 /// Copies buffer to an allocated String.
 /// The resulting string is also NUL-terminated, to facilitate interoperating
 /// with code using C strings.
@@ -628,7 +638,7 @@ void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mod
                      (char_u *)rhs.data, rhs.size, lua_funcref,
                      CPO_TO_CPO_FLAGS, &parsed_args);
   if (opts != NULL && opts->desc.type == kObjectTypeString) {
-    parsed_args.desc = xstrdup(opts->desc.data.string.data);
+    parsed_args.desc = string_to_cstr(opts->desc.data.string);
   } else {
     parsed_args.desc = NULL;
   }
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index f449ccc3c7..d1b86ed328 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -261,7 +261,7 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e
       api_set_error(error, kErrorTypeValidation, "term_name must be a String");
       return;
     }
-    set_tty_option("term", xstrdup(value.data.string.data));
+    set_tty_option("term", string_to_cstr(value.data.string));
     return;
   }
 
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 061653c5af..d9ab097316 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -2522,7 +2522,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
   // Parse command line
   exarg_T ea;
   CmdParseInfo cmdinfo;
-  char_u *cmdline = vim_strsave((char_u *)str.data);
+  char_u *cmdline = (char_u *)string_to_cstr(str);
 
   if (!parse_cmdline(cmdline, &ea, &cmdinfo)) {
     api_set_error(err, kErrorTypeException, "Error while parsing command line");
-- 
cgit 


From b9bdd0f61e5e7365c07aadbc3f796556b6d85fdf Mon Sep 17 00:00:00 2001
From: Dundar Goc 
Date: Sun, 1 May 2022 11:18:17 +0200
Subject: refactor: replace char_u variables and functions with char

Work on https://github.com/neovim/neovim/issues/459
---
 src/nvim/api/vimscript.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index 4123674fe7..40ac0b8b64 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -131,7 +131,7 @@ Object nvim_eval(String expr, Error *err)
     try_start();
 
     typval_T rettv;
-    int ok = eval0((char_u *)expr.data, &rettv, NULL, true);
+    int ok = eval0(expr.data, &rettv, NULL, true);
 
     if (!try_end(err)) {
       if (ok == FAIL) {
@@ -246,7 +246,7 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err)
   switch (dict.type) {
   case kObjectTypeString:
     try_start();
-    if (eval0((char_u *)dict.data.string.data, &rettv, NULL, true) == FAIL) {
+    if (eval0(dict.data.string.data, &rettv, NULL, true) == FAIL) {
       api_set_error(err, kErrorTypeException,
                     "Failed to evaluate dict expression");
     }
-- 
cgit 


From 29a6cda3ffe981b09d4c59d49d6c97a4ea13ca8b Mon Sep 17 00:00:00 2001
From: Yatao Li 
Date: Thu, 16 Sep 2021 07:53:56 +0800
Subject: feat(api/ui): win_extmarks

---
 src/nvim/api/extmark.c      | 14 ++++++++++++--
 src/nvim/api/keysets.lua    |  1 +
 src/nvim/api/ui.c           |  1 +
 src/nvim/api/ui.h           |  1 +
 src/nvim/api/ui_events.in.h |  4 ++++
 5 files changed, 19 insertions(+), 2 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index e408d88854..fa6923e6d5 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -146,6 +146,10 @@ static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict)
           STRING_OBJ(cstr_to_string(virt_text_pos_str[decor->virt_text_pos])));
     }
 
+    if (decor->ui_watched) {
+      PUT(dict, "ui_watched", BOOLEAN_OBJ(true));
+    }
+
     if (kv_size(decor->virt_lines)) {
       Array all_chunks = ARRAY_DICT_INIT;
       bool virt_lines_leftcol = false;
@@ -170,7 +174,7 @@ static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict)
       PUT(dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_leftcol));
     }
 
-    if (decor->hl_id || kv_size(decor->virt_text)) {
+    if (decor->hl_id || kv_size(decor->virt_text) || decor->ui_watched) {
       PUT(dict, "priority", INTEGER_OBJ(decor->priority));
     }
 
@@ -472,6 +476,10 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
 ///                   When a character is supplied it is used as |:syn-cchar|.
 ///                   "hl_group" is used as highlight for the cchar if provided,
 ///                   otherwise it defaults to |hl-Conceal|.
+///               - ui_watched: boolean that indicates the mark should be drawn
+///                   by a UI. When set, the UI will receive win_extmark events.
+///                   Note: the mark is positioned by virt_text attributes. Can be
+///                   used together with virt_text.
 ///
 /// @param[out]  err   Error details, if any
 /// @return Id of the created/updated extmark
@@ -709,6 +717,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
   bool ephemeral = false;
   OPTION_TO_BOOL(ephemeral, ephemeral, false);
 
+  OPTION_TO_BOOL(decor.ui_watched, ui_watched, false);
+
   if (line < 0) {
     api_set_error(err, kErrorTypeValidation, "line value outside range");
     goto error;
@@ -762,7 +772,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
 
   // TODO(bfredl): synergize these two branches even more
   if (ephemeral && decor_state.buf == buf) {
-    decor_add_ephemeral((int)line, (int)col, line2, col2, &decor);
+    decor_add_ephemeral((int)line, (int)col, line2, col2, &decor, (uint64_t)ns_id, id);
   } else {
     if (ephemeral) {
       api_set_error(err, kErrorTypeException, "not yet implemented");
diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
index 8ad4dae928..5baffaf505 100644
--- a/src/nvim/api/keysets.lua
+++ b/src/nvim/api/keysets.lua
@@ -28,6 +28,7 @@ return {
     "line_hl_group";
     "cursorline_hl_group";
     "conceal";
+    "ui_watched";
   };
   keymap = {
     "noremap";
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index d1b86ed328..4f9592bd52 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -810,3 +810,4 @@ static void remote_ui_inspect(UI *ui, Dictionary *info)
   UIData *data = ui->data;
   PUT(*info, "chan", INTEGER_OBJ((Integer)data->channel_id));
 }
+
diff --git a/src/nvim/api/ui.h b/src/nvim/api/ui.h
index b3af14f8a8..bc70406acb 100644
--- a/src/nvim/api/ui.h
+++ b/src/nvim/api/ui.h
@@ -4,6 +4,7 @@
 #include 
 
 #include "nvim/api/private/defs.h"
+#include "nvim/map.h"
 
 #ifdef INCLUDE_GENERATED_DECLARATIONS
 # include "api/ui.h.generated.h"
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index db348359eb..63aaaae38a 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -123,6 +123,10 @@ void win_viewport(Integer grid, Window win, Integer topline,
                   Integer line_count)
   FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY;
 
+void win_extmark(Integer grid, Window win, Integer ns_id, Integer mark_id,
+                 Integer row, Integer col)
+  FUNC_API_SINCE(10) FUNC_API_REMOTE_ONLY;
+
 void popupmenu_show(Array items, Integer selected,
                     Integer row, Integer col, Integer grid)
   FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
-- 
cgit 


From 3ec93ca92cb08faed342586e86a6f21b35264376 Mon Sep 17 00:00:00 2001
From: Famiu Haque 
Date: Wed, 4 May 2022 18:04:01 +0600
Subject: feat(nvim_parse_cmd): add range, count, reg #18383

Adds range, count and reg to the return values of nvim_parse_cmd. Also makes
line1 and line2 be -1 if the command does not take a range. Also moves
nvim_parse_cmd to vimscript.c because it fits better there.
---
 src/nvim/api/vim.c       | 200 -----------------------------------------
 src/nvim/api/vimscript.c | 229 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 229 insertions(+), 200 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index d9ab097316..3f0cfb82a6 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -2461,203 +2461,3 @@ void nvim_del_user_command(String name, Error *err)
 {
   nvim_buf_del_user_command(-1, name, err);
 }
-
-/// Parse command line.
-///
-/// Doesn't check the validity of command arguments.
-///
-/// @param str       Command line string to parse. Cannot contain "\n".
-/// @param opts      Optional parameters. Reserved for future use.
-/// @param[out] err  Error details, if any.
-/// @return Dictionary containing command information, with these keys:
-///         - cmd: (string) Command name.
-///         - line1: (number) Starting line of command range. Only applicable if command can take a
-///                  range.
-///         - line2: (number) Final line of command range. Only applicable if command can take a
-///                  range.
-///         - bang: (boolean) Whether command contains a bang (!) modifier.
-///         - args: (array) Command arguments.
-///         - addr: (string) Value of |:command-addr|. Uses short name.
-///         - nargs: (string) Value of |:command-nargs|.
-///         - nextcmd: (string) Next command if there are multiple commands separated by a |:bar|.
-///                             Empty if there isn't a next command.
-///         - magic: (dictionary) Which characters have special meaning in the command arguments.
-///             - file: (boolean) The command expands filenames. Which means characters such as "%",
-///                               "#" and wildcards are expanded.
-///             - bar: (boolean) The "|" character is treated as a command separator and the double
-///                              quote character (\") is treated as the start of a comment.
-///         - mods: (dictionary) |:command-modifiers|.
-///             - silent: (boolean) |:silent|.
-///             - emsg_silent: (boolean) |:silent!|.
-///             - sandbox: (boolean) |:sandbox|.
-///             - noautocmd: (boolean) |:noautocmd|.
-///             - browse: (boolean) |:browse|.
-///             - confirm: (boolean) |:confirm|.
-///             - hide: (boolean) |:hide|.
-///             - keepalt: (boolean) |:keepalt|.
-///             - keepjumps: (boolean) |:keepjumps|.
-///             - keepmarks: (boolean) |:keepmarks|.
-///             - keeppatterns: (boolean) |:keeppatterns|.
-///             - lockmarks: (boolean) |:lockmarks|.
-///             - noswapfile: (boolean) |:noswapfile|.
-///             - tab: (integer) |:tab|.
-///             - verbose: (integer) |:verbose|.
-///             - vertical: (boolean) |:vertical|.
-///             - split: (string) Split modifier string, is an empty string when there's no split
-///                               modifier. If there is a split modifier it can be one of:
-///               - "aboveleft": |:aboveleft|.
-///               - "belowright": |:belowright|.
-///               - "topleft": |:topleft|.
-///               - "botright": |:botright|.
-Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
-  FUNC_API_SINCE(10) FUNC_API_FAST
-{
-  Dictionary result = ARRAY_DICT_INIT;
-
-  if (opts.size > 0) {
-    api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
-    return result;
-  }
-
-  // Parse command line
-  exarg_T ea;
-  CmdParseInfo cmdinfo;
-  char_u *cmdline = (char_u *)string_to_cstr(str);
-
-  if (!parse_cmdline(cmdline, &ea, &cmdinfo)) {
-    api_set_error(err, kErrorTypeException, "Error while parsing command line");
-    goto end;
-  }
-
-  // Parse arguments
-  Array args = ARRAY_DICT_INIT;
-  size_t length = STRLEN(ea.arg);
-
-  // For nargs = 1 or '?', pass the entire argument list as a single argument,
-  // otherwise split arguments by whitespace.
-  if (ea.argt & EX_NOSPC) {
-    if (*ea.arg != NUL) {
-      ADD(args, STRING_OBJ(cstrn_to_string((char *)ea.arg, length)));
-    }
-  } else {
-    size_t end = 0;
-    size_t len = 0;
-    char *buf = xcalloc(length, sizeof(char));
-    bool done = false;
-
-    while (!done) {
-      done = uc_split_args_iter(ea.arg, length, &end, buf, &len);
-      if (len > 0) {
-        ADD(args, STRING_OBJ(cstrn_to_string(buf, len)));
-      }
-    }
-
-    xfree(buf);
-  }
-
-  if (ea.cmdidx == CMD_USER) {
-    PUT(result, "cmd", CSTR_TO_OBJ((char *)USER_CMD(ea.useridx)->uc_name));
-  } else if (ea.cmdidx == CMD_USER_BUF) {
-    PUT(result, "cmd", CSTR_TO_OBJ((char *)USER_CMD_GA(&curbuf->b_ucmds, ea.useridx)->uc_name));
-  } else {
-    PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx)));
-  }
-  PUT(result, "line1", INTEGER_OBJ(ea.line1));
-  PUT(result, "line2", INTEGER_OBJ(ea.line2));
-  PUT(result, "bang", BOOLEAN_OBJ(ea.forceit));
-  PUT(result, "args", ARRAY_OBJ(args));
-
-  char nargs[2];
-  if (ea.argt & EX_EXTRA) {
-    if (ea.argt & EX_NOSPC) {
-      if (ea.argt & EX_NEEDARG) {
-        nargs[0] = '1';
-      } else {
-        nargs[0] = '?';
-      }
-    } else if (ea.argt & EX_NEEDARG) {
-      nargs[0] = '+';
-    } else {
-      nargs[0] = '*';
-    }
-  } else {
-    nargs[0] = '0';
-  }
-  nargs[1] = '\0';
-  PUT(result, "nargs", CSTR_TO_OBJ(nargs));
-
-  const char *addr;
-  switch (ea.addr_type) {
-  case ADDR_LINES:
-    addr = "line";
-    break;
-  case ADDR_ARGUMENTS:
-    addr = "arg";
-    break;
-  case ADDR_BUFFERS:
-    addr = "buf";
-    break;
-  case ADDR_LOADED_BUFFERS:
-    addr = "load";
-    break;
-  case ADDR_WINDOWS:
-    addr = "win";
-    break;
-  case ADDR_TABS:
-    addr = "tab";
-    break;
-  case ADDR_QUICKFIX:
-    addr = "qf";
-    break;
-  case ADDR_NONE:
-    addr = "none";
-    break;
-  default:
-    addr = "?";
-    break;
-  }
-  PUT(result, "addr", CSTR_TO_OBJ(addr));
-  PUT(result, "nextcmd", CSTR_TO_OBJ((char *)ea.nextcmd));
-
-  Dictionary mods = ARRAY_DICT_INIT;
-  PUT(mods, "silent", BOOLEAN_OBJ(cmdinfo.silent));
-  PUT(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.emsg_silent));
-  PUT(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.sandbox));
-  PUT(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.noautocmd));
-  PUT(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.tab));
-  PUT(mods, "verbose", INTEGER_OBJ(cmdinfo.verbose));
-  PUT(mods, "browse", BOOLEAN_OBJ(cmdinfo.cmdmod.browse));
-  PUT(mods, "confirm", BOOLEAN_OBJ(cmdinfo.cmdmod.confirm));
-  PUT(mods, "hide", BOOLEAN_OBJ(cmdinfo.cmdmod.hide));
-  PUT(mods, "keepalt", BOOLEAN_OBJ(cmdinfo.cmdmod.keepalt));
-  PUT(mods, "keepjumps", BOOLEAN_OBJ(cmdinfo.cmdmod.keepjumps));
-  PUT(mods, "keepmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.keepmarks));
-  PUT(mods, "keeppatterns", BOOLEAN_OBJ(cmdinfo.cmdmod.keeppatterns));
-  PUT(mods, "lockmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.lockmarks));
-  PUT(mods, "noswapfile", BOOLEAN_OBJ(cmdinfo.cmdmod.noswapfile));
-  PUT(mods, "vertical", BOOLEAN_OBJ(cmdinfo.cmdmod.split & WSP_VERT));
-
-  const char *split;
-  if (cmdinfo.cmdmod.split & WSP_BOT) {
-    split = "botright";
-  } else if (cmdinfo.cmdmod.split & WSP_TOP) {
-    split = "topleft";
-  } else if (cmdinfo.cmdmod.split & WSP_BELOW) {
-    split = "belowright";
-  } else if (cmdinfo.cmdmod.split & WSP_ABOVE) {
-    split = "aboveleft";
-  } else {
-    split = "";
-  }
-  PUT(mods, "split", CSTR_TO_OBJ(split));
-
-  PUT(result, "mods", DICTIONARY_OBJ(mods));
-
-  Dictionary magic = ARRAY_DICT_INIT;
-  PUT(magic, "file", BOOLEAN_OBJ(cmdinfo.magic.file));
-  PUT(magic, "bar", BOOLEAN_OBJ(cmdinfo.magic.bar));
-  PUT(result, "magic", DICTIONARY_OBJ(magic));
-end:
-  xfree(cmdline);
-  return result;
-}
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index 40ac0b8b64..99d1406cfb 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -16,6 +16,7 @@
 #include "nvim/ex_cmds2.h"
 #include "nvim/viml/parser/expressions.h"
 #include "nvim/viml/parser/parser.h"
+#include "nvim/window.h"
 
 #ifdef INCLUDE_GENERATED_DECLARATIONS
 # include "api/vimscript.c.generated.h"
@@ -736,3 +737,231 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E
   viml_parser_destroy(&pstate);
   return ret;
 }
+
+/// Parse command line.
+///
+/// Doesn't check the validity of command arguments.
+///
+/// @param str       Command line string to parse. Cannot contain "\n".
+/// @param opts      Optional parameters. Reserved for future use.
+/// @param[out] err  Error details, if any.
+/// @return Dictionary containing command information, with these keys:
+///         - cmd: (string) Command name.
+///         - range: (number) Number of items in the command ||. Can be 0, 1 or 2.
+///         - line1: (number) Starting line of command ||. -1 if command cannot take a range.
+///                           ||
+///         - line2: (number) Final line of command ||. -1 if command cannot take a range.
+///                           ||
+///         - count: (number) Any || that was supplied to the command. -1 if command cannot
+///                           take a count.
+///         - reg: (number) The optional command ||, if specified. Empty string if not
+///                         specified or if command cannot take a register.
+///         - bang: (boolean) Whether command contains a || (!) modifier.
+///         - args: (array) Command arguments.
+///         - addr: (string) Value of |:command-addr|. Uses short name.
+///         - nargs: (string) Value of |:command-nargs|.
+///         - nextcmd: (string) Next command if there are multiple commands separated by a |:bar|.
+///                             Empty if there isn't a next command.
+///         - magic: (dictionary) Which characters have special meaning in the command arguments.
+///             - file: (boolean) The command expands filenames. Which means characters such as "%",
+///                               "#" and wildcards are expanded.
+///             - bar: (boolean) The "|" character is treated as a command separator and the double
+///                              quote character (\") is treated as the start of a comment.
+///         - mods: (dictionary) |:command-modifiers|.
+///             - silent: (boolean) |:silent|.
+///             - emsg_silent: (boolean) |:silent!|.
+///             - sandbox: (boolean) |:sandbox|.
+///             - noautocmd: (boolean) |:noautocmd|.
+///             - browse: (boolean) |:browse|.
+///             - confirm: (boolean) |:confirm|.
+///             - hide: (boolean) |:hide|.
+///             - keepalt: (boolean) |:keepalt|.
+///             - keepjumps: (boolean) |:keepjumps|.
+///             - keepmarks: (boolean) |:keepmarks|.
+///             - keeppatterns: (boolean) |:keeppatterns|.
+///             - lockmarks: (boolean) |:lockmarks|.
+///             - noswapfile: (boolean) |:noswapfile|.
+///             - tab: (integer) |:tab|.
+///             - verbose: (integer) |:verbose|. -1 when omitted.
+///             - vertical: (boolean) |:vertical|.
+///             - split: (string) Split modifier string, is an empty string when there's no split
+///                               modifier. If there is a split modifier it can be one of:
+///               - "aboveleft": |:aboveleft|.
+///               - "belowright": |:belowright|.
+///               - "topleft": |:topleft|.
+///               - "botright": |:botright|.
+Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
+  FUNC_API_SINCE(10) FUNC_API_FAST
+{
+  Dictionary result = ARRAY_DICT_INIT;
+
+  if (opts.size > 0) {
+    api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+    return result;
+  }
+
+  // Parse command line
+  exarg_T ea;
+  CmdParseInfo cmdinfo;
+  char_u *cmdline = (char_u *)string_to_cstr(str);
+
+  if (!parse_cmdline(cmdline, &ea, &cmdinfo)) {
+    api_set_error(err, kErrorTypeException, "Error while parsing command line");
+    goto end;
+  }
+
+  // Parse arguments
+  Array args = ARRAY_DICT_INIT;
+  size_t length = STRLEN(ea.arg);
+
+  // For nargs = 1 or '?', pass the entire argument list as a single argument,
+  // otherwise split arguments by whitespace.
+  if (ea.argt & EX_NOSPC) {
+    if (*ea.arg != NUL) {
+      ADD(args, STRING_OBJ(cstrn_to_string((char *)ea.arg, length)));
+    }
+  } else {
+    size_t end = 0;
+    size_t len = 0;
+    char *buf = xcalloc(length, sizeof(char));
+    bool done = false;
+
+    while (!done) {
+      done = uc_split_args_iter(ea.arg, length, &end, buf, &len);
+      if (len > 0) {
+        ADD(args, STRING_OBJ(cstrn_to_string(buf, len)));
+      }
+    }
+
+    xfree(buf);
+  }
+
+  ucmd_T *cmd = NULL;
+  if (ea.cmdidx == CMD_USER) {
+    cmd = USER_CMD(ea.useridx);
+  } else if (ea.cmdidx == CMD_USER_BUF) {
+    cmd = USER_CMD_GA(&curbuf->b_ucmds, ea.useridx);
+  }
+
+  if (cmd != NULL) {
+    PUT(result, "cmd", CSTR_TO_OBJ((char *)cmd->uc_name));
+  } else {
+    PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx)));
+  }
+
+  PUT(result, "range", INTEGER_OBJ(ea.addr_count));
+  PUT(result, "line1", INTEGER_OBJ((ea.argt & EX_RANGE) ? ea.line1 : -1));
+  PUT(result, "line2", INTEGER_OBJ((ea.argt & EX_RANGE) ? ea.line2 : -1));
+
+  if (ea.argt & EX_COUNT) {
+    if (ea.addr_count > 0 || cmd == NULL) {
+      PUT(result, "count", INTEGER_OBJ(ea.line2));
+    } else {
+      PUT(result, "count", INTEGER_OBJ(cmd->uc_def));
+    }
+  } else {
+    PUT(result, "count", INTEGER_OBJ(-1));
+  }
+
+  char reg[2];
+  reg[0] = (char)ea.regname;
+  reg[1] = '\0';
+  PUT(result, "reg", CSTR_TO_OBJ(reg));
+
+  PUT(result, "bang", BOOLEAN_OBJ(ea.forceit));
+  PUT(result, "args", ARRAY_OBJ(args));
+
+  char nargs[2];
+  if (ea.argt & EX_EXTRA) {
+    if (ea.argt & EX_NOSPC) {
+      if (ea.argt & EX_NEEDARG) {
+        nargs[0] = '1';
+      } else {
+        nargs[0] = '?';
+      }
+    } else if (ea.argt & EX_NEEDARG) {
+      nargs[0] = '+';
+    } else {
+      nargs[0] = '*';
+    }
+  } else {
+    nargs[0] = '0';
+  }
+  nargs[1] = '\0';
+  PUT(result, "nargs", CSTR_TO_OBJ(nargs));
+
+  const char *addr;
+  switch (ea.addr_type) {
+  case ADDR_LINES:
+    addr = "line";
+    break;
+  case ADDR_ARGUMENTS:
+    addr = "arg";
+    break;
+  case ADDR_BUFFERS:
+    addr = "buf";
+    break;
+  case ADDR_LOADED_BUFFERS:
+    addr = "load";
+    break;
+  case ADDR_WINDOWS:
+    addr = "win";
+    break;
+  case ADDR_TABS:
+    addr = "tab";
+    break;
+  case ADDR_QUICKFIX:
+    addr = "qf";
+    break;
+  case ADDR_NONE:
+    addr = "none";
+    break;
+  default:
+    addr = "?";
+    break;
+  }
+  PUT(result, "addr", CSTR_TO_OBJ(addr));
+  PUT(result, "nextcmd", CSTR_TO_OBJ((char *)ea.nextcmd));
+
+  Dictionary mods = ARRAY_DICT_INIT;
+  PUT(mods, "silent", BOOLEAN_OBJ(cmdinfo.silent));
+  PUT(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.emsg_silent));
+  PUT(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.sandbox));
+  PUT(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.noautocmd));
+  PUT(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.tab));
+  PUT(mods, "verbose", INTEGER_OBJ(cmdinfo.verbose));
+  PUT(mods, "browse", BOOLEAN_OBJ(cmdinfo.cmdmod.browse));
+  PUT(mods, "confirm", BOOLEAN_OBJ(cmdinfo.cmdmod.confirm));
+  PUT(mods, "hide", BOOLEAN_OBJ(cmdinfo.cmdmod.hide));
+  PUT(mods, "keepalt", BOOLEAN_OBJ(cmdinfo.cmdmod.keepalt));
+  PUT(mods, "keepjumps", BOOLEAN_OBJ(cmdinfo.cmdmod.keepjumps));
+  PUT(mods, "keepmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.keepmarks));
+  PUT(mods, "keeppatterns", BOOLEAN_OBJ(cmdinfo.cmdmod.keeppatterns));
+  PUT(mods, "lockmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.lockmarks));
+  PUT(mods, "noswapfile", BOOLEAN_OBJ(cmdinfo.cmdmod.noswapfile));
+  PUT(mods, "vertical", BOOLEAN_OBJ(cmdinfo.cmdmod.split & WSP_VERT));
+
+  const char *split;
+  if (cmdinfo.cmdmod.split & WSP_BOT) {
+    split = "botright";
+  } else if (cmdinfo.cmdmod.split & WSP_TOP) {
+    split = "topleft";
+  } else if (cmdinfo.cmdmod.split & WSP_BELOW) {
+    split = "belowright";
+  } else if (cmdinfo.cmdmod.split & WSP_ABOVE) {
+    split = "aboveleft";
+  } else {
+    split = "";
+  }
+  PUT(mods, "split", CSTR_TO_OBJ(split));
+
+  PUT(result, "mods", DICTIONARY_OBJ(mods));
+
+  Dictionary magic = ARRAY_DICT_INIT;
+  PUT(magic, "file", BOOLEAN_OBJ(cmdinfo.magic.file));
+  PUT(magic, "bar", BOOLEAN_OBJ(cmdinfo.magic.bar));
+  PUT(result, "magic", DICTIONARY_OBJ(magic));
+end:
+  xfree(cmdline);
+  return result;
+}
-- 
cgit 


From 5576d30e89153c817fb1a8d23c30cfc0432bc7c6 Mon Sep 17 00:00:00 2001
From: Dundar Goc 
Date: Tue, 3 May 2022 11:06:27 +0200
Subject: refactor: replace char_u variables and functions with char

Work on https://github.com/neovim/neovim/issues/459
---
 src/nvim/api/autocmd.c   | 4 +---
 src/nvim/api/vim.c       | 2 +-
 src/nvim/api/vimscript.c | 2 +-
 3 files changed, 3 insertions(+), 5 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
index 0db9a63e7a..2f4f57049e 100644
--- a/src/nvim/api/autocmd.c
+++ b/src/nvim/api/autocmd.c
@@ -474,7 +474,7 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
     Object *command = &opts->command;
     if (command->type == kObjectTypeString) {
       aucmd.type = CALLABLE_EX;
-      aucmd.callable.cmd = (char_u *)string_to_cstr(command->data.string);
+      aucmd.callable.cmd = string_to_cstr(command->data.string);
     } else {
       api_set_error(err,
                     kErrorTypeValidation,
@@ -657,8 +657,6 @@ void nvim_clear_autocmds(Dict(clear_autocmds) *opts, Error *err)
 cleanup:
   api_free_array(event_array);
   api_free_array(patterns);
-
-  return;
 }
 
 /// Create or get an autocommand group |autocmd-groups|.
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 3f0cfb82a6..2323b8db47 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -2210,7 +2210,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err)
     allocated = true;
     // Marks comes from shada
   } else {
-    filename = (char *)mark.fname;
+    filename = mark.fname;
     bufnr = 0;
   }
 
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index 99d1406cfb..acd89119f9 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -827,7 +827,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
     bool done = false;
 
     while (!done) {
-      done = uc_split_args_iter(ea.arg, length, &end, buf, &len);
+      done = uc_split_args_iter((char_u *)ea.arg, length, &end, buf, &len);
       if (len > 0) {
         ADD(args, STRING_OBJ(cstrn_to_string(buf, len)));
       }
-- 
cgit 


From f08477789fe241c3868d3856643c78da0760cd19 Mon Sep 17 00:00:00 2001
From: Dundar Goc 
Date: Wed, 4 May 2022 13:00:18 +0200
Subject: refactor: replace char_u variables and functions with char

Work on https://github.com/neovim/neovim/issues/459
---
 src/nvim/api/autocmd.c | 42 +++++++++++++++++++++---------------------
 src/nvim/api/ui.c      |  1 -
 2 files changed, 21 insertions(+), 22 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
index 2f4f57049e..010c03e505 100644
--- a/src/nvim/api/autocmd.c
+++ b/src/nvim/api/autocmd.c
@@ -23,9 +23,9 @@
 // Copy string or array of strings into an empty array.
 // Get the event number, unless it is an error. Then goto `goto_name`.
 #define GET_ONE_EVENT(event_nr, event_str, goto_name) \
-  char_u *__next_ev; \
+  char *__next_ev; \
   event_T event_nr = \
-    event_name2nr((char_u *)event_str.data.string.data, &__next_ev); \
+    event_name2nr(event_str.data.string.data, &__next_ev); \
   if (event_nr >= NUM_EVENTS) { \
     api_set_error(err, kErrorTypeValidation, "unexpected event"); \
     goto goto_name; \
@@ -78,8 +78,8 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
   // TODO(tjdevries): Would be cool to add nvim_get_autocmds({ id = ... })
 
   Array autocmd_list = ARRAY_DICT_INIT;
-  char_u *pattern_filters[AUCMD_MAX_PATTERNS];
-  char_u pattern_buflocal[BUFLOCAL_PAT_LEN];
+  char *pattern_filters[AUCMD_MAX_PATTERNS];
+  char pattern_buflocal[BUFLOCAL_PAT_LEN];
 
   Array buffers = ARRAY_DICT_INIT;
 
@@ -148,7 +148,7 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
   if (opts->pattern.type != kObjectTypeNil) {
     Object v = opts->pattern;
     if (v.type == kObjectTypeString) {
-      pattern_filters[pattern_filter_count] = (char_u *)v.data.string.data;
+      pattern_filters[pattern_filter_count] = v.data.string.data;
       pattern_filter_count += 1;
     } else if (v.type == kObjectTypeArray) {
       if (v.data.array.size > AUCMD_MAX_PATTERNS) {
@@ -164,7 +164,7 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
           goto cleanup;
         }
 
-        pattern_filters[pattern_filter_count] = (char_u *)item.data.string.data;
+        pattern_filters[pattern_filter_count] = item.data.string.data;
         pattern_filter_count += 1;
       });
     } else {
@@ -211,7 +211,7 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
   }
 
   FOREACH_ITEM(buffers, bufnr, {
-    pattern_filters[pattern_filter_count] = (char_u *)bufnr.data.string.data;
+    pattern_filters[pattern_filter_count] = bufnr.data.string.data;
     pattern_filter_count += 1;
   });
 
@@ -237,7 +237,7 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
           assert(i < AUCMD_MAX_PATTERNS);
           assert(pattern_filters[i]);
 
-          char_u *pat = pattern_filters[i];
+          char *pat = pattern_filters[i];
           int patlen = (int)STRLEN(pat);
 
           if (aupat_is_buflocal(pat, patlen)) {
@@ -249,7 +249,7 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
             pat = pattern_buflocal;
           }
 
-          if (strequal((char *)ap->pat, (char *)pat)) {
+          if (strequal(ap->pat, pat)) {
             passed = true;
             break;
           }
@@ -531,7 +531,7 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
       WITH_SCRIPT_CONTEXT(channel_id, {
         retval = autocmd_register(autocmd_id,
                                   event_nr,
-                                  (char_u *)pat.data.string.data,
+                                  pat.data.string.data,
                                   (int)pat.data.string.size,
                                   au_group,
                                   is_once,
@@ -635,8 +635,8 @@ void nvim_clear_autocmds(Dict(clear_autocmds) *opts, Error *err)
   if (event_array.size == 0) {
     FOR_ALL_AUEVENTS(event) {
       FOREACH_ITEM(patterns, pat_object, {
-        char_u *pat = (char_u *)pat_object.data.string.data;
-        if (!clear_autocmd(event, pat, au_group, err)) {
+        char *pat = pat_object.data.string.data;
+        if (!clear_autocmd(event, (char *)pat, au_group, err)) {
           goto cleanup;
         }
       });
@@ -646,8 +646,8 @@ void nvim_clear_autocmds(Dict(clear_autocmds) *opts, Error *err)
       GET_ONE_EVENT(event_nr, event_str, cleanup);
 
       FOREACH_ITEM(patterns, pat_object, {
-        char_u *pat = (char_u *)pat_object.data.string.data;
-        if (!clear_autocmd(event_nr, pat, au_group, err)) {
+        char *pat = pat_object.data.string.data;
+        if (!clear_autocmd(event_nr, (char *)pat, au_group, err)) {
           goto cleanup;
         }
       });
@@ -757,7 +757,7 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err)
   buf_T *buf = curbuf;
   bool set_buf = false;
 
-  char_u *pattern = NULL;
+  char *pattern = NULL;
   bool set_pattern = false;
 
   Array event_array = ARRAY_DICT_INIT;
@@ -812,7 +812,7 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err)
       goto cleanup;
     }
 
-    pattern = (char_u *)string_to_cstr(opts->pattern.data.string);
+    pattern = string_to_cstr(opts->pattern.data.string);
     set_pattern = true;
   }
 
@@ -920,7 +920,7 @@ static int get_augroup_from_object(Object group, Error *err)
 static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Object buffer,
                                              Error *err)
 {
-  const char_u pattern_buflocal[BUFLOCAL_PAT_LEN];
+  const char pattern_buflocal[BUFLOCAL_PAT_LEN];
 
   if (pattern.type != kObjectTypeNil && buffer.type != kObjectTypeNil) {
     api_set_error(err, kErrorTypeValidation,
@@ -930,7 +930,7 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob
     Object *v = &pattern;
 
     if (v->type == kObjectTypeString) {
-      char_u *pat = (char_u *)v->data.string.data;
+      char *pat = v->data.string.data;
       size_t patlen = aucmd_pattern_length(pat);
       while (patlen) {
         ADD(*patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen)));
@@ -945,7 +945,7 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob
 
       Array array = v->data.array;
       for (size_t i = 0; i < array.size; i++) {
-        char_u *pat = (char_u *)array.items[i].data.string.data;
+        char *pat = array.items[i].data.string.data;
         size_t patlen = aucmd_pattern_length(pat);
         while (patlen) {
           ADD(*patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen)));
@@ -980,9 +980,9 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob
   return true;
 }
 
-static bool clear_autocmd(event_T event, char_u *pat, int au_group, Error *err)
+static bool clear_autocmd(event_T event, char *pat, int au_group, Error *err)
 {
-  if (do_autocmd_event(event, pat, false, false, (char_u *)"", true, au_group) == FAIL) {
+  if (do_autocmd_event(event, pat, false, false, "", true, au_group) == FAIL) {
     api_set_error(err, kErrorTypeException, "Failed to clear autocmd");
     return false;
   }
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index 4f9592bd52..d1b86ed328 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -810,4 +810,3 @@ static void remote_ui_inspect(UI *ui, Dictionary *info)
   UIData *data = ui->data;
   PUT(*info, "chan", INTEGER_OBJ((Integer)data->channel_id));
 }
-
-- 
cgit 


From 7aedcd8febaf74403851f8482529302e3ab30922 Mon Sep 17 00:00:00 2001
From: Famiu Haque 
Date: Thu, 5 May 2022 01:49:29 +0600
Subject: refactor(api): make `range` in `nvim_parse_cmd` an array

Changes the `range` value in `nvim_parse_cmd` into an array to describe
range information more concisely. Also makes `range` and `count` be
mutually exclusive by making count `-1` when command takes a range
instead of a count. Additionally corrects the behavior of `count` for
built-in commands by making the default count `0`.
---
 src/nvim/api/vimscript.c | 30 +++++++++++++++++++-----------
 1 file changed, 19 insertions(+), 11 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index acd89119f9..698b2d06fb 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -747,13 +747,12 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E
 /// @param[out] err  Error details, if any.
 /// @return Dictionary containing command information, with these keys:
 ///         - cmd: (string) Command name.
-///         - range: (number) Number of items in the command ||. Can be 0, 1 or 2.
-///         - line1: (number) Starting line of command ||. -1 if command cannot take a range.
-///                           ||
-///         - line2: (number) Final line of command ||. -1 if command cannot take a range.
-///                           ||
+///         - range: (array) Command . Can have 0-2 elements depending on how many items the
+///                          range contains. Has no elements if command doesn't accept a range or if
+///                          no range was specified, one element if only a single range item was
+///                          specified and two elements if both range items were specified.
 ///         - count: (number) Any || that was supplied to the command. -1 if command cannot
-///                           take a count.
+///                           take a count. Mutually exclusive with "range".
 ///         - reg: (number) The optional command ||, if specified. Empty string if not
 ///                         specified or if command cannot take a register.
 ///         - bang: (boolean) Whether command contains a || (!) modifier.
@@ -849,15 +848,24 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
     PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx)));
   }
 
-  PUT(result, "range", INTEGER_OBJ(ea.addr_count));
-  PUT(result, "line1", INTEGER_OBJ((ea.argt & EX_RANGE) ? ea.line1 : -1));
-  PUT(result, "line2", INTEGER_OBJ((ea.argt & EX_RANGE) ? ea.line2 : -1));
+  if ((ea.argt & EX_RANGE) && !(ea.argt & EX_COUNT) && ea.addr_count > 0) {
+    Array range = ARRAY_DICT_INIT;
+    if (ea.addr_count > 1) {
+      ADD(range, INTEGER_OBJ(ea.line1));
+    }
+    ADD(range, INTEGER_OBJ(ea.line2));
+    PUT(result, "range", ARRAY_OBJ(range));;
+  } else {
+    PUT(result, "range", ARRAY_OBJ(ARRAY_DICT_INIT));
+  }
 
   if (ea.argt & EX_COUNT) {
-    if (ea.addr_count > 0 || cmd == NULL) {
+    if (ea.addr_count > 0) {
       PUT(result, "count", INTEGER_OBJ(ea.line2));
-    } else {
+    } else if (cmd != NULL) {
       PUT(result, "count", INTEGER_OBJ(cmd->uc_def));
+    } else {
+      PUT(result, "count", INTEGER_OBJ(0));
     }
   } else {
     PUT(result, "count", INTEGER_OBJ(-1));
-- 
cgit 


From 9a671e6a24243a5ff2879599d91ab5aec8b4e77d Mon Sep 17 00:00:00 2001
From: Dundar Goc 
Date: Wed, 4 May 2022 18:27:22 +0200
Subject: refactor: replace char_u variables and functions with char

Work on https://github.com/neovim/neovim/issues/459
---
 src/nvim/api/autocmd.c           |  2 +-
 src/nvim/api/buffer.c            |  8 ++++----
 src/nvim/api/extmark.c           |  4 ++--
 src/nvim/api/private/converter.c |  5 +++--
 src/nvim/api/private/helpers.c   | 28 ++++++++++++++--------------
 src/nvim/api/vim.c               |  2 +-
 6 files changed, 25 insertions(+), 24 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
index 010c03e505..6f262ebfa0 100644
--- a/src/nvim/api/autocmd.c
+++ b/src/nvim/api/autocmd.c
@@ -460,7 +460,7 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
       cb.data.luaref = api_new_luaref(callback->data.luaref);
     } else if (callback->type == kObjectTypeString) {
       cb.type = kCallbackFuncref;
-      cb.data.funcref = (char_u *)string_to_cstr(callback->data.string);
+      cb.data.funcref = string_to_cstr(callback->data.string);
     } else {
       api_set_error(err,
                     kErrorTypeException,
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index b4d4265a37..45dadae1dd 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -452,7 +452,7 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
       goto end;
     }
 
-    if (ml_replace((linenr_T)lnum, (char_u *)lines[i], false) == FAIL) {
+    if (ml_replace((linenr_T)lnum, lines[i], false) == FAIL) {
       api_set_error(err, kErrorTypeException, "Failed to replace line");
       goto end;
     }
@@ -472,7 +472,7 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
       goto end;
     }
 
-    if (ml_append((linenr_T)lnum, (char_u *)lines[i], 0, false) == FAIL) {
+    if (ml_append((linenr_T)lnum, lines[i], 0, false) == FAIL) {
       api_set_error(err, kErrorTypeException, "Failed to insert line");
       goto end;
     }
@@ -692,7 +692,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
       goto end;
     }
 
-    if (ml_replace((linenr_T)lnum, (char_u *)lines[i], false) == FAIL) {
+    if (ml_replace((linenr_T)lnum, lines[i], false) == FAIL) {
       api_set_error(err, kErrorTypeException, "Failed to replace line");
       goto end;
     }
@@ -710,7 +710,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
       goto end;
     }
 
-    if (ml_append((linenr_T)lnum, (char_u *)lines[i], 0, false) == FAIL) {
+    if (ml_append((linenr_T)lnum, lines[i], 0, false) == FAIL) {
       api_set_error(err, kErrorTypeException, "Failed to insert line");
       goto end;
     }
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index fa6923e6d5..bbc1ee9d71 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -688,8 +688,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
   }
 
   if (opts->sign_text.type == kObjectTypeString) {
-    if (!init_sign_text(&decor.sign_text,
-                        (char_u *)opts->sign_text.data.string.data)) {
+    if (!init_sign_text((char **)&decor.sign_text,
+                        opts->sign_text.data.string.data)) {
       api_set_error(err, kErrorTypeValidation, "sign_text is not a valid value");
       goto error;
     }
diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c
index a26383ec7d..8724ef4432 100644
--- a/src/nvim/api/private/converter.c
+++ b/src/nvim/api/private/converter.c
@@ -353,9 +353,10 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
   case kObjectTypeLuaRef: {
     LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
     state->lua_callable.func_ref = api_new_luaref(obj.data.luaref);
-    char_u *name = register_cfunc(&nlua_CFunction_func_call, &nlua_CFunction_func_free, state);
+    char *name =
+      (char *)register_cfunc(&nlua_CFunction_func_call, &nlua_CFunction_func_free, state);
     tv->v_type = VAR_FUNC;
-    tv->vval.v_string = vim_strsave(name);
+    tv->vval.v_string = xstrdup(name);
     break;
   }
 
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index a89a254f20..9f894d5533 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -634,8 +634,8 @@ void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mod
   }
   parsed_args.buffer = !global;
 
-  set_maparg_lhs_rhs((char_u *)lhs.data, lhs.size,
-                     (char_u *)rhs.data, rhs.size, lua_funcref,
+  set_maparg_lhs_rhs(lhs.data, lhs.size,
+                     rhs.data, rhs.size, lua_funcref,
                      CPO_TO_CPO_FLAGS, &parsed_args);
   if (opts != NULL && opts->desc.type == kObjectTypeString) {
     parsed_args.desc = string_to_cstr(opts->desc.data.string);
@@ -652,16 +652,16 @@ void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mod
     goto fail_and_free;
   }
   int mode_val;  // integer value of the mapping mode, to be passed to do_map()
-  char_u *p = (char_u *)((mode.size) ? mode.data : "m");
+  char *p = (mode.size) ? mode.data : "m";
   if (STRNCMP(p, "!", 2) == 0) {
-    mode_val = get_map_mode(&p, true);  // mapmode-ic
+    mode_val = get_map_mode((char_u **)&p, true);  // mapmode-ic
   } else {
-    mode_val = get_map_mode(&p, false);
+    mode_val = get_map_mode((char_u **)&p, false);
     if ((mode_val == VISUAL + SELECTMODE + NORMAL + OP_PENDING)
         && mode.size > 0) {
       // get_map_mode() treats unrecognized mode shortnames as ":map".
       // This is an error unless the given shortname was empty string "".
-      api_set_error(err, kErrorTypeValidation, "Invalid mode shortname: \"%s\"", (char *)p);
+      api_set_error(err, kErrorTypeValidation, "Invalid mode shortname: \"%s\"", p);
       goto fail_and_free;
     }
   }
@@ -1269,7 +1269,7 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width)
     }
 
     char *text = transstr(str.size > 0 ? str.data : "", false);  // allocates
-    w += (int)mb_string2cells((char_u *)text);
+    w += (int)mb_string2cells(text);
 
     kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
   }
@@ -1658,19 +1658,19 @@ sctx_T api_set_sctx(uint64_t channel_id)
 
 // adapted from sign.c:sign_define_init_text.
 // TODO(lewis6991): Consider merging
-int init_sign_text(char_u **sign_text, char_u *text)
+int init_sign_text(char **sign_text, char *text)
 {
-  char_u *s;
+  char *s;
 
-  char_u *endp = text + (int)STRLEN(text);
+  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))) {
+  for (s = text; s < endp; s += utfc_ptr2len((char_u *)s)) {
+    if (!vim_isprintc(utf_ptr2char((char_u *)s))) {
       break;
     }
-    cells += utf_ptr2cells(s);
+    cells += utf_ptr2cells((char_u *)s);
   }
   // Currently must be empty, one or two display cells
   if (s != endp || cells > 2) {
@@ -1683,7 +1683,7 @@ int init_sign_text(char_u **sign_text, char_u *text)
   // 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 = vim_strnsave(text, len);
+  *sign_text = xstrnsave(text, len);
 
   if (cells == 1) {
     STRCPY(*sign_text + len - 1, " ");
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 2323b8db47..7f4fafa71b 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -474,7 +474,7 @@ Integer nvim_strwidth(String text, Error *err)
     return 0;
   }
 
-  return (Integer)mb_string2cells((char_u *)text.data);
+  return (Integer)mb_string2cells(text.data);
 }
 
 /// Gets the paths contained in 'runtimepath'.
-- 
cgit 


From 511f06a56e77f612f7bc8ca563ebecb7239a9914 Mon Sep 17 00:00:00 2001
From: Famiu Haque 
Date: Thu, 5 May 2022 23:11:57 +0600
Subject: fix(api): make `nvim_parse_cmd` propagate errors

Makes `nvim_parse_cmd` propagate any errors that occur while parsing to
give the user a better idea of what's wrong with the command.
---
 src/nvim/api/vimscript.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index 698b2d06fb..3346b5a237 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -802,10 +802,15 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
   // Parse command line
   exarg_T ea;
   CmdParseInfo cmdinfo;
-  char_u *cmdline = (char_u *)string_to_cstr(str);
+  char *cmdline = string_to_cstr(str);
+  char *errormsg = NULL;
 
-  if (!parse_cmdline(cmdline, &ea, &cmdinfo)) {
-    api_set_error(err, kErrorTypeException, "Error while parsing command line");
+  if (!parse_cmdline(cmdline, &ea, &cmdinfo, &errormsg)) {
+    if (errormsg != NULL) {
+      api_set_error(err, kErrorTypeException, "Error while parsing command line: %s", errormsg);
+    } else {
+      api_set_error(err, kErrorTypeException, "Error while parsing command line");
+    }
     goto end;
   }
 
-- 
cgit 


From 96289f24169281da419e8ce1078705258db3229b Mon Sep 17 00:00:00 2001
From: Famiu Haque 
Date: Thu, 5 May 2022 21:00:47 +0600
Subject: feat(api): add `group_name` to `nvim_get_autocmds`

---
 src/nvim/api/autocmd.c | 2 ++
 1 file changed, 2 insertions(+)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
index 010c03e505..4645b77a1c 100644
--- a/src/nvim/api/autocmd.c
+++ b/src/nvim/api/autocmd.c
@@ -64,6 +64,7 @@ static int64_t next_autocmd_id = 1;
 ///         containing the following fields:
 ///             - id (number): the autocommand id (only when defined with the API).
 ///             - group (integer): the autocommand group id.
+///             - group_name (string): the autocommand group name.
 ///             - desc (string): the autocommand description.
 ///             - event (string): the autocommand event.
 ///             - command (string): the autocommand command.
@@ -269,6 +270,7 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
 
         if (ap->group != AUGROUP_DEFAULT) {
           PUT(autocmd_info, "group", INTEGER_OBJ(ap->group));
+          PUT(autocmd_info, "group_name", CSTR_TO_OBJ(augroup_name(ap->group)));
         }
 
         if (ac->id > 0) {
-- 
cgit 


From 544ef994df72c3cbe0dca6b856ce2dcbc5169767 Mon Sep 17 00:00:00 2001
From: Dundar Goc 
Date: Fri, 6 May 2022 00:46:30 +0200
Subject: refactor: upgrade uncrustify configuration to version 0.75

---
 src/nvim/api/vimscript.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index 698b2d06fb..f5fdabdc0c 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -854,7 +854,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
       ADD(range, INTEGER_OBJ(ea.line1));
     }
     ADD(range, INTEGER_OBJ(ea.line2));
-    PUT(result, "range", ARRAY_OBJ(range));;
+    PUT(result, "range", ARRAY_OBJ(range));
   } else {
     PUT(result, "range", ARRAY_OBJ(ARRAY_DICT_INIT));
   }
-- 
cgit 


From 14f3383c0da1413a5ae82feb19ac89f01d4b9aad Mon Sep 17 00:00:00 2001
From: Famiu Haque 
Date: Sat, 7 May 2022 08:57:21 +0600
Subject: fix(api): make `nvim_parse_cmd` work correctly with both range and
 count

It seems range and count can be used together in commands. This PR fixes
the behavior of `nvim_parse_cmd` for those cases by removing the mutual
exclusivity of "range" and "count". It also removes range line number
validation for `nvim_parse_cmd` as it's not its job to validate the
command.
---
 src/nvim/api/vimscript.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index d3675a3c40..9396435466 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -752,7 +752,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E
 ///                          no range was specified, one element if only a single range item was
 ///                          specified and two elements if both range items were specified.
 ///         - count: (number) Any || that was supplied to the command. -1 if command cannot
-///                           take a count. Mutually exclusive with "range".
+///                           take a count.
 ///         - reg: (number) The optional command ||, if specified. Empty string if not
 ///                         specified or if command cannot take a register.
 ///         - bang: (boolean) Whether command contains a || (!) modifier.
@@ -853,7 +853,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
     PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx)));
   }
 
-  if ((ea.argt & EX_RANGE) && !(ea.argt & EX_COUNT) && ea.addr_count > 0) {
+  if ((ea.argt & EX_RANGE) && ea.addr_count > 0) {
     Array range = ARRAY_DICT_INIT;
     if (ea.addr_count > 1) {
       ADD(range, INTEGER_OBJ(ea.line1));
-- 
cgit 


From 5e2346178c8c5a5dd895b4b0b85c8783f048625c Mon Sep 17 00:00:00 2001
From: bfredl 
Date: Sun, 24 Apr 2022 09:28:04 +0200
Subject: refactor(decor): use decor levels properly

---
 src/nvim/api/extmark.c | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index bbc1ee9d71..1f47ac4ebb 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -488,6 +488,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
   FUNC_API_SINCE(7)
 {
   Decoration decor = DECORATION_INIT;
+  bool has_decor = false;
 
   buf_T *buf = find_buffer_by_handle(buffer, err);
   if (!buf) {
@@ -577,6 +578,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
       if (ERROR_SET(err)) {
         goto error;
       }
+      has_decor = true;
     }
   }
 
@@ -586,6 +588,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
     if (c.size) {
       decor.conceal_char = utf_ptr2char((const char_u *)c.data);
     }
+    has_decor = true;
   } else if (HAS_KEY(opts->conceal)) {
     api_set_error(err, kErrorTypeValidation, "conceal is not a String");
     goto error;
@@ -594,6 +597,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
   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 (ERROR_SET(err)) {
       goto error;
     }
@@ -665,6 +669,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
       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");
@@ -693,6 +698,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
       api_set_error(err, kErrorTypeValidation, "sign_text is not a valid value");
       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;
@@ -718,6 +724,9 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
   OPTION_TO_BOOL(ephemeral, ephemeral, false);
 
   OPTION_TO_BOOL(decor.ui_watched, ui_watched, false);
+  if (decor.ui_watched) {
+    has_decor = true;
+  }
 
   if (line < 0) {
     api_set_error(err, kErrorTypeValidation, "line value outside range");
@@ -780,7 +789,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
     }
 
     extmark_set(buf, (uint32_t)ns_id, &id, (int)line, (colnr_T)col, line2, col2,
-                &decor, right_gravity, end_right_gravity, kExtmarkNoUndo);
+                has_decor ? &decor : NULL, right_gravity, end_right_gravity,
+                kExtmarkNoUndo);
   }
 
   return (Integer)id;
-- 
cgit 


From 2a378e6e82cececb12339f2df51ffe107039d867 Mon Sep 17 00:00:00 2001
From: Dundar Goc 
Date: Wed, 4 May 2022 22:35:50 +0200
Subject: refactor: replace char_u variables and functions with char

Work on https://github.com/neovim/neovim/issues/459
---
 src/nvim/api/extmark.c         |  2 +-
 src/nvim/api/private/helpers.c | 12 ++++++------
 src/nvim/api/ui.c              |  2 +-
 src/nvim/api/vim.c             |  4 ++--
 4 files changed, 10 insertions(+), 10 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index bbc1ee9d71..12b5ea2e97 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -584,7 +584,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
     String c = opts->conceal.data.string;
     decor.conceal = true;
     if (c.size) {
-      decor.conceal_char = utf_ptr2char((const char_u *)c.data);
+      decor.conceal_char = utf_ptr2char(c.data);
     }
   } else if (HAS_KEY(opts->conceal)) {
     api_set_error(err, kErrorTypeValidation, "conceal is not a String");
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 9f894d5533..a2ee4f91bc 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -654,9 +654,9 @@ void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mod
   int mode_val;  // integer value of the mapping mode, to be passed to do_map()
   char *p = (mode.size) ? mode.data : "m";
   if (STRNCMP(p, "!", 2) == 0) {
-    mode_val = get_map_mode((char_u **)&p, true);  // mapmode-ic
+    mode_val = get_map_mode(&p, true);  // mapmode-ic
   } else {
-    mode_val = get_map_mode((char_u **)&p, false);
+    mode_val = get_map_mode(&p, false);
     if ((mode_val == VISUAL + SELECTMODE + NORMAL + OP_PENDING)
         && mode.size > 0) {
       // get_map_mode() treats unrecognized mode shortnames as ":map".
@@ -1128,7 +1128,7 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf, bool from_lua)
 
   // Convert the string mode to the integer mode
   // that is stored within each mapblock
-  char_u *p = (char_u *)mode.data;
+  char *p = mode.data;
   int int_mode = get_map_mode(&p, 0);
 
   // Determine the desired buffer value
@@ -1666,11 +1666,11 @@ int init_sign_text(char **sign_text, char *text)
 
   // Count cells and check for non-printable chars
   int cells = 0;
-  for (s = text; s < endp; s += utfc_ptr2len((char_u *)s)) {
-    if (!vim_isprintc(utf_ptr2char((char_u *)s))) {
+  for (s = text; s < endp; s += utfc_ptr2len(s)) {
+    if (!vim_isprintc(utf_ptr2char(s))) {
       break;
     }
-    cells += utf_ptr2cells((char_u *)s);
+    cells += utf_ptr2cells(s);
   }
   // Currently must be empty, one or two display cells
   if (s != endp || cells > 2) {
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index d1b86ed328..b4d20ed975 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -665,7 +665,7 @@ static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startc
       remote_ui_cursor_goto(ui, row, startcol + i);
       remote_ui_highlight_set(ui, attrs[i]);
       remote_ui_put(ui, (const char *)chunk[i]);
-      if (utf_ambiguous_width(utf_ptr2char(chunk[i]))) {
+      if (utf_ambiguous_width(utf_ptr2char((char *)chunk[i]))) {
         data->client_col = -1;  // force cursor update
       }
     }
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 7f4fafa71b..185c043b06 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -2287,12 +2287,12 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
 
   if (HAS_KEY(opts->fillchar)) {
     if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size == 0
-        || ((size_t)utf_ptr2len((char_u *)opts->fillchar.data.string.data)
+        || ((size_t)utf_ptr2len(opts->fillchar.data.string.data)
             != opts->fillchar.data.string.size)) {
       api_set_error(err, kErrorTypeValidation, "fillchar must be a single character");
       return result;
     }
-    fillchar = utf_ptr2char((char_u *)opts->fillchar.data.string.data);
+    fillchar = utf_ptr2char(opts->fillchar.data.string.data);
   }
 
   if (HAS_KEY(opts->highlights)) {
-- 
cgit 


From e31b32a293f6ba8708499a176234f8be1df6a145 Mon Sep 17 00:00:00 2001
From: Dundar Goc 
Date: Thu, 5 May 2022 13:36:14 +0200
Subject: refactor: replace char_u variables and functions with char

Work on https://github.com/neovim/neovim/issues/459
---
 src/nvim/api/vim.c        | 25 ++++++++++++-------------
 src/nvim/api/vimscript.c  |  6 +++---
 src/nvim/api/win_config.c |  4 ++--
 3 files changed, 17 insertions(+), 18 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 185c043b06..5e7e51762a 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -245,11 +245,11 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_ks)
   if (escape_ks) {
     // Need to escape K_SPECIAL before putting the string in the
     // typeahead buffer.
-    keys_esc = (char *)vim_strsave_escape_ks((char_u *)keys.data);
+    keys_esc = vim_strsave_escape_ks(keys.data);
   } else {
     keys_esc = keys.data;
   }
-  ins_typebuf((char_u *)keys_esc, (remap ? REMAP_YES : REMAP_NONE),
+  ins_typebuf(keys_esc, (remap ? REMAP_YES : REMAP_NONE),
               insert ? 0 : typebuf.tb_len, !typed, false);
   if (vgetc_busy) {
     typebuf_was_filled = true;
@@ -416,7 +416,7 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, Bool
   }
 
   char *ptr = NULL;
-  replace_termcodes((char_u *)str.data, str.size, (char_u **)&ptr, flags, NULL, CPO_TO_CPO_FLAGS);
+  replace_termcodes(str.data, str.size, &ptr, flags, NULL, CPO_TO_CPO_FLAGS);
   return cstr_as_string(ptr);
 }
 
@@ -513,14 +513,13 @@ ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Error *err)
 
   TRY_WRAP({
     try_start();
-    do_in_runtimepath((char_u *)(name.size ? name.data : ""),
-                      flags, find_runtime_cb, &rv);
+    do_in_runtimepath((name.size ? name.data : ""), flags, find_runtime_cb, &rv);
     try_end(err);
   });
   return rv;
 }
 
-static void find_runtime_cb(char_u *fname, void *cookie)
+static void find_runtime_cb(char *fname, void *cookie)
 {
   Array *rv = (Array *)cookie;
   if (fname != NULL) {
@@ -563,13 +562,13 @@ void nvim_set_current_dir(String dir, Error *err)
     return;
   }
 
-  char_u string[MAXPATHL];
+  char string[MAXPATHL];
   memcpy(string, dir.data, dir.size);
   string[dir.size] = NUL;
 
   try_start();
 
-  if (!changedir_func((char *)string, kCdScopeGlobal)) {
+  if (!changedir_func(string, kCdScopeGlobal)) {
     if (!try_end(err)) {
       api_set_error(err, kErrorTypeException, "Failed to change directory");
     }
@@ -722,7 +721,7 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
 
   long numval = 0;
   char *stringval = NULL;
-  switch (get_option_value(name.data, &numval, (char_u **)&stringval, scope)) {
+  switch (get_option_value(name.data, &numval, &stringval, scope)) {
   case 0:
     rv = STRING_OBJ(cstr_as_string(stringval));
     break;
@@ -1339,7 +1338,7 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err)
     for (size_t i = 0; i < lines.size; i++) {
       String s = lines.items[i].data.string;
       assert(s.size <= INT_MAX);
-      AppendToRedobuffLit((char_u *)s.data, (int)s.size);
+      AppendToRedobuffLit(s.data, (int)s.size);
       // readfile()-style: "\n" is indicated by presence of N+1 item.
       if (i + 1 < lines.size) {
         AppendCharToRedobuff(NL);
@@ -1392,7 +1391,7 @@ void nvim_put(ArrayOf(String) lines, String type, Boolean after, Boolean follow,
       goto cleanup;
     }
     String line = lines.items[i].data.string;
-    reg->y_array[i] = (char_u *)xmemdupz(line.data, line.size);
+    reg->y_array[i] = xmemdupz(line.data, line.size);
     memchrsub(reg->y_array[i], NUL, NL, line.size);
   }
 
@@ -2352,9 +2351,9 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
   ewp->w_p_crb = false;
 
   int width = build_stl_str_hl(ewp,
-                               (char_u *)buf,
+                               buf,
                                sizeof(buf),
-                               (char_u *)str.data,
+                               str.data,
                                false,
                                fillchar,
                                maxwidth,
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index 9396435466..c4a301839f 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -197,7 +197,7 @@ static Object _call_function(String fn, Array args, dict_T *self, Error *err)
     funcexe.selfdict = self;
     // call_func() retval is deceptive, ignore it.  Instead we set `msg_list`
     // (see above) to capture abort-causing non-exception errors.
-    (void)call_func((char_u *)fn.data, (int)fn.size, &rettv, (int)args.size,
+    (void)call_func(fn.data, (int)fn.size, &rettv, (int)args.size,
                     vim_args, &funcexe);
     if (!try_end(err)) {
       rv = vim_to_object(&rettv);
@@ -290,7 +290,7 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err)
       goto end;
     }
     fn = (String) {
-      .data = (char *)di->di_tv.vval.v_string,
+      .data = di->di_tv.vval.v_string,
       .size = STRLEN(di->di_tv.vval.v_string),
     };
   }
@@ -831,7 +831,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
     bool done = false;
 
     while (!done) {
-      done = uc_split_args_iter((char_u *)ea.arg, length, &end, buf, &len);
+      done = uc_split_args_iter(ea.arg, length, &end, buf, &len);
       if (len > 0) {
         ADD(args, STRING_OBJ(cstrn_to_string(buf, len)));
       }
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
index e7704f72d7..0d8bdbec61 100644
--- a/src/nvim/api/win_config.c
+++ b/src/nvim/api/win_config.c
@@ -264,7 +264,7 @@ Dictionary nvim_win_get_config(Window window, Error *err)
         String s = cstrn_to_string((const char *)config->border_chars[i], sizeof(schar_T));
 
         int hi_id = config->border_hl_ids[i];
-        char_u *hi_name = syn_id2name(hi_id);
+        char *hi_name = (char *)syn_id2name(hi_id);
         if (hi_name[0]) {
           ADD(tuple, STRING_OBJ(s));
           ADD(tuple, STRING_OBJ(cstr_to_string((const char *)hi_name)));
@@ -387,7 +387,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
         return;
       }
       if (string.size
-          && mb_string2cells_len((char_u *)string.data, string.size) > 1) {
+          && mb_string2cells_len(string.data, string.size) > 1) {
         api_set_error(err, kErrorTypeValidation,
                       "border chars must be one cell");
         return;
-- 
cgit 


From 9aa5647e686e5420e5b9b51828ec7d55631f98ed Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Tue, 10 May 2022 07:58:58 +0800
Subject: vim-patch:8.2.4911: the mode #defines are not clearly named (#18499)

Problem:    The mode #defines are not clearly named.
Solution:   Prepend MODE_.  Renumber them to put the mapped modes first.
https://github.com/vim/vim/commit/249591057b4840785c50e41dd850efb8a8faf435

A hunk from the patch depends on patch 8.2.4861, which hasn't been
ported yet, but that should be easy to notice.
---
 src/nvim/api/private/helpers.c | 3 +--
 src/nvim/api/vim.c             | 6 +++---
 2 files changed, 4 insertions(+), 5 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index a2ee4f91bc..eb86964a72 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -657,8 +657,7 @@ void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mod
     mode_val = get_map_mode(&p, true);  // mapmode-ic
   } else {
     mode_val = get_map_mode(&p, false);
-    if ((mode_val == VISUAL + SELECTMODE + NORMAL + OP_PENDING)
-        && mode.size > 0) {
+    if (mode_val == (MODE_VISUAL | MODE_SELECT | MODE_NORMAL | MODE_OP_PENDING) && mode.size > 0) {
       // get_map_mode() treats unrecognized mode shortnames as ":map".
       // This is an error unless the given shortname was empty string "".
       api_set_error(err, kErrorTypeValidation, "Invalid mode shortname: \"%s\"", p);
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 5e7e51762a..09bf032daa 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -1327,14 +1327,14 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err)
     draining = true;
     goto theend;
   }
-  if (!(State & (CMDLINE | INSERT)) && (phase == -1 || phase == 1)) {
+  if (!(State & (MODE_CMDLINE | MODE_INSERT)) && (phase == -1 || phase == 1)) {
     ResetRedobuff();
     AppendCharToRedobuff('a');  // Dot-repeat.
   }
   // vim.paste() decides if client should cancel.  Errors do NOT cancel: we
   // want to drain remaining chunks (rather than divert them to main input).
   cancel = (rv.type == kObjectTypeBoolean && !rv.data.boolean);
-  if (!cancel && !(State & CMDLINE)) {  // Dot-repeat.
+  if (!cancel && !(State & MODE_CMDLINE)) {  // Dot-repeat.
     for (size_t i = 0; i < lines.size; i++) {
       String s = lines.items[i].data.string;
       assert(s.size <= INT_MAX);
@@ -1345,7 +1345,7 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err)
       }
     }
   }
-  if (!(State & (CMDLINE | INSERT)) && (phase == -1 || phase == 3)) {
+  if (!(State & (MODE_CMDLINE | MODE_INSERT)) && (phase == -1 || phase == 3)) {
     AppendCharToRedobuff(ESC);  // Dot-repeat.
   }
 theend:
-- 
cgit 


From dfcc58466505f7fb5f62d636a67facaeea143285 Mon Sep 17 00:00:00 2001
From: Famiu Haque 
Date: Sun, 8 May 2022 17:39:45 +0600
Subject: feat(api): add `nvim_cmd`

Adds the API function `nvim_cmd` which allows executing an Ex-command through a Dictionary which can have the same values as the return value of `nvim_parse_cmd()`. This makes it much easier to do things like passing arguments with a space to commands that otherwise may not allow it, or to make commands interpret certain characters literally when they otherwise would not.
---
 src/nvim/api/keysets.lua       |  39 +++++
 src/nvim/api/private/helpers.c | 172 ++++++++++++++++++
 src/nvim/api/vimscript.c       | 385 ++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 592 insertions(+), 4 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
index 5baffaf505..78f8ed3cca 100644
--- a/src/nvim/api/keysets.lua
+++ b/src/nvim/api/keysets.lua
@@ -155,5 +155,44 @@ return {
   create_augroup = {
     "clear";
   };
+  cmd = {
+    "cmd";
+    "range";
+    "count";
+    "reg";
+    "bang";
+    "args";
+    "magic";
+    "mods";
+    "nargs";
+    "addr";
+    "nextcmd";
+  };
+  cmd_magic = {
+    "file";
+    "bar";
+  };
+  cmd_mods = {
+    "silent";
+    "emsg_silent";
+    "sandbox";
+    "noautocmd";
+    "browse";
+    "confirm";
+    "hide";
+    "keepalt";
+    "keepjumps";
+    "keepmarks";
+    "keeppatterns";
+    "lockmarks";
+    "noswapfile";
+    "tab";
+    "verbose";
+    "vertical";
+    "split";
+  };
+  cmd_opts = {
+    "output";
+  };
 }
 
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index a2ee4f91bc..725033a4c5 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -19,6 +19,7 @@
 #include "nvim/decoration.h"
 #include "nvim/eval.h"
 #include "nvim/eval/typval.h"
+#include "nvim/ex_cmds_defs.h"
 #include "nvim/extmark.h"
 #include "nvim/fileio.h"
 #include "nvim/getchar.h"
@@ -1691,3 +1692,174 @@ int init_sign_text(char **sign_text, char *text)
 
   return OK;
 }
+
+/// Check if a string contains only whitespace characters.
+bool string_iswhite(String str)
+{
+  for (size_t i = 0; i < str.size; i++) {
+    if (!ascii_iswhite(str.data[i])) {
+      // Found a non-whitespace character
+      return false;
+    } else if (str.data[i] == NUL) {
+      // Terminate at first occurence of a NUL character
+      break;
+    }
+  }
+  return true;
+}
+
+// Add modifier string for command into the command line. Includes trailing whitespace if non-empty.
+// @return OK or FAIL.
+static int add_cmd_modifier_str(char *cmdline, size_t *pos, const size_t bufsize,
+                                CmdParseInfo *cmdinfo)
+{
+#define APPEND_MODIFIER(...) \
+  do { \
+    if (*pos < bufsize) { \
+      *pos += (size_t)snprintf(cmdline + *pos, bufsize - *pos, __VA_ARGS__); \
+    } \
+    if (*pos < bufsize) { \
+      cmdline[*pos] = ' '; \
+      *pos += 1; \
+    } else { \
+      goto err; \
+    } \
+  } while (0)
+
+#define APPEND_MODIFIER_IF(cond, mod) \
+  do { \
+    if (cond) { \
+      APPEND_MODIFIER(mod); \
+    } \
+  } while (0)
+
+  if (cmdinfo->cmdmod.tab != 0) {
+    APPEND_MODIFIER("%dtab", cmdinfo->cmdmod.tab - 1);
+  }
+  if (cmdinfo->verbose != -1) {
+    APPEND_MODIFIER("%ldverbose", cmdinfo->verbose);
+  }
+
+  switch (cmdinfo->cmdmod.split & (WSP_ABOVE | WSP_BELOW | WSP_TOP | WSP_BOT)) {
+  case WSP_ABOVE:
+    APPEND_MODIFIER("aboveleft");
+    break;
+  case WSP_BELOW:
+    APPEND_MODIFIER("belowright");
+    break;
+  case WSP_TOP:
+    APPEND_MODIFIER("topleft");
+    break;
+  case WSP_BOT:
+    APPEND_MODIFIER("botright");
+    break;
+  default:
+    break;
+  }
+
+  APPEND_MODIFIER_IF(cmdinfo->cmdmod.split & WSP_VERT, "vertical");
+
+  if (cmdinfo->emsg_silent) {
+    APPEND_MODIFIER("silent!");
+  } else if (cmdinfo->silent) {
+    APPEND_MODIFIER("silent");
+  }
+
+  APPEND_MODIFIER_IF(cmdinfo->sandbox, "sandbox");
+  APPEND_MODIFIER_IF(cmdinfo->noautocmd, "noautocmd");
+  APPEND_MODIFIER_IF(cmdinfo->cmdmod.browse, "browse");
+  APPEND_MODIFIER_IF(cmdinfo->cmdmod.confirm, "confirm");
+  APPEND_MODIFIER_IF(cmdinfo->cmdmod.hide, "hide");
+  APPEND_MODIFIER_IF(cmdinfo->cmdmod.keepalt, "keepalt");
+  APPEND_MODIFIER_IF(cmdinfo->cmdmod.keepjumps, "keepjumps");
+  APPEND_MODIFIER_IF(cmdinfo->cmdmod.keepmarks, "keepmarks");
+  APPEND_MODIFIER_IF(cmdinfo->cmdmod.keeppatterns, "keeppatterns");
+  APPEND_MODIFIER_IF(cmdinfo->cmdmod.lockmarks, "lockmarks");
+  APPEND_MODIFIER_IF(cmdinfo->cmdmod.noswapfile, "noswapfile");
+
+  return OK;
+err:
+  return FAIL;
+
+#undef APPEND_MODIFIER
+#undef APPEND_MODIFIER_IF
+}
+
+/// Build cmdline string for command, used by `nvim_cmd()`.
+///
+/// @return OK or FAIL.
+int build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdinfo, char **args,
+                      size_t argc)
+{
+  const size_t bufsize = IOSIZE;
+  size_t pos = 0;
+  char *cmdline = xcalloc(bufsize, sizeof(char));
+
+#define CMDLINE_APPEND(...) \
+  do { \
+    if (pos < bufsize) { \
+      pos += (size_t)snprintf(cmdline + pos, bufsize - pos, __VA_ARGS__); \
+    } else { \
+      goto err; \
+    } \
+  } while (0)
+
+  // Command modifiers.
+  if (add_cmd_modifier_str(cmdline, &pos, bufsize, cmdinfo) == FAIL) {
+    goto err;
+  }
+
+  // Command range / count.
+  if (eap->argt & EX_RANGE) {
+    if (eap->addr_count == 1) {
+      CMDLINE_APPEND("%ld", eap->line2);
+    } else if (eap->addr_count > 1) {
+      CMDLINE_APPEND("%ld,%ld", eap->line1, eap->line2);
+      eap->addr_count = 2;  // Make sure address count is not greater than 2
+    }
+  }
+
+  // Keep the index of the position where command name starts, so eap->cmd can point to it.
+  size_t cmdname_idx = pos;
+  CMDLINE_APPEND("%s", eap->cmd);
+  eap->cmd = cmdline + cmdname_idx;
+
+  // Command bang.
+  if (eap->argt & EX_BANG && eap->forceit) {
+    CMDLINE_APPEND("!");
+  }
+
+  // Command register.
+  if (eap->argt & EX_REGSTR && eap->regname) {
+    CMDLINE_APPEND(" %c", eap->regname);
+  }
+
+  // Iterate through each argument and store the starting position and length of each argument in
+  // the cmdline string in `eap->args` and `eap->arglens`, respectively.
+  eap->args = xcalloc(argc, sizeof(char *));
+  eap->arglens = xcalloc(argc, sizeof(size_t));
+  for (size_t i = 0; i < argc; i++) {
+    eap->args[i] = cmdline + pos + 1;  // add 1 to skip the leading space.
+    eap->arglens[i] = STRLEN(args[i]);
+    CMDLINE_APPEND(" %s", args[i]);
+  }
+  eap->argc = argc;
+  eap->arg = argc > 0 ? eap->args[0] : NULL;
+
+  // Replace, :make and :grep with 'makeprg' and 'grepprg'.
+  char *p = replace_makeprg(eap, eap->arg, cmdlinep);
+  if (p != eap->arg) {
+    // If replace_makeprg modified the cmdline string, correct the argument pointers.
+    assert(argc == 1);
+    eap->arg = p;
+    eap->args[0] = p;
+  }
+
+  *cmdlinep = cmdline;
+  return OK;
+err:
+  xfree(cmdline);
+  return FAIL;
+
+#undef CMDLINE_APPEND
+}
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index c4a301839f..02f406d686 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -10,10 +10,14 @@
 #include "nvim/api/private/helpers.h"
 #include "nvim/api/vimscript.h"
 #include "nvim/ascii.h"
+#include "nvim/autocmd.h"
 #include "nvim/eval.h"
 #include "nvim/eval/typval.h"
 #include "nvim/eval/userfunc.h"
 #include "nvim/ex_cmds2.h"
+#include "nvim/ops.h"
+#include "nvim/strings.h"
+#include "nvim/vim.h"
 #include "nvim/viml/parser/expressions.h"
 #include "nvim/viml/parser/parser.h"
 #include "nvim/window.h"
@@ -22,7 +26,9 @@
 # include "api/vimscript.c.generated.h"
 #endif
 
-/// Executes Vimscript (multiline block of Ex-commands), like anonymous
+#define IS_USER_CMDIDX(idx) ((int)(idx) < 0)
+
+/// Executes Vimscript (multiline block of Ex commands), like anonymous
 /// |:source|.
 ///
 /// Unlike |nvim_command()| this function supports heredocs, script-scope (s:),
@@ -32,6 +38,7 @@
 ///
 /// @see |execute()|
 /// @see |nvim_command()|
+/// @see |nvim_cmd()|
 ///
 /// @param src      Vimscript code
 /// @param output   Capture and return all (non-error, non-shell |:!|) output
@@ -89,13 +96,16 @@ theend:
   return (String)STRING_INIT;
 }
 
-/// Executes an ex-command.
+/// Executes an Ex command.
 ///
 /// On execution error: fails with VimL error, does not update v:errmsg.
 ///
-/// @see |nvim_exec()|
+/// Prefer using |nvim_cmd()| or |nvim_exec()| over this. To evaluate multiple lines of Vim script
+/// or an Ex command directly, use |nvim_exec()|. To construct an Ex command using a structured
+/// format and then execute it, use |nvim_cmd()|. To modify an Ex command before evaluating it, use
+/// |nvim_parse_cmd()| in conjunction with |nvim_cmd()|.
 ///
-/// @param command  Ex-command string
+/// @param command  Ex command string
 /// @param[out] err Error details (Vim error), if any
 void nvim_command(String command, Error *err)
   FUNC_API_SINCE(1)
@@ -978,3 +988,370 @@ end:
   xfree(cmdline);
   return result;
 }
+
+/// Executes an Ex command.
+///
+/// Unlike |nvim_command()| this command takes a structured Dictionary instead of a String. This
+/// allows for easier construction and manipulation of an Ex command. This also allows for things
+/// such as having spaces inside a command argument, expanding filenames in a command that otherwise
+/// doesn't expand filenames, etc.
+///
+/// @see |nvim_exec()|
+/// @see |nvim_command()|
+///
+/// @param cmd       Command to execute. Must be a Dictionary that can contain the same values as
+///                  the return value of |nvim_parse_cmd()| except "addr", "nargs" and "nextcmd"
+///                  which are ignored if provided. All values except for "cmd" are optional.
+/// @param opts      Optional parameters.
+///                  - output: (boolean, default false) Whether to return command output.
+/// @param[out] err  Error details, if any.
+/// @return Command output (non-error, non-shell |:!|) if `output` is true, else empty string.
+String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error *err)
+  FUNC_API_SINCE(10)
+{
+  exarg_T ea;
+  memset(&ea, 0, sizeof(ea));
+  ea.verbose_save = -1;
+  ea.save_msg_silent = -1;
+
+  CmdParseInfo cmdinfo;
+  memset(&cmdinfo, 0, sizeof(cmdinfo));
+  cmdinfo.verbose = -1;
+
+  char *cmdline = NULL;
+  char *cmdname = NULL;
+  char **args = NULL;
+  size_t argc = 0;
+
+  String retv = (String)STRING_INIT;
+
+#define OBJ_TO_BOOL(var, value, default, varname) \
+  do { \
+    var = api_object_to_bool(value, varname, default, err); \
+    if (ERROR_SET(err)) { \
+      goto end; \
+    } \
+  } while (0)
+
+#define VALIDATION_ERROR(...) \
+  do { \
+    api_set_error(err, kErrorTypeValidation, __VA_ARGS__); \
+    goto end; \
+  } while (0)
+
+  bool output;
+  OBJ_TO_BOOL(output, opts->output, false, "'output'");
+
+  // First, parse the command name and check if it exists and is valid.
+  if (!HAS_KEY(cmd->cmd) || cmd->cmd.type != kObjectTypeString
+      || cmd->cmd.data.string.data[0] == NUL) {
+    VALIDATION_ERROR("'cmd' must be a non-empty String");
+  }
+
+  cmdname = string_to_cstr(cmd->cmd.data.string);
+  ea.cmd = cmdname;
+
+  char *p = find_ex_command(&ea, NULL);
+
+  // If this looks like an undefined user command and there are CmdUndefined
+  // autocommands defined, trigger the matching autocommands.
+  if (p != NULL && ea.cmdidx == CMD_SIZE && ASCII_ISUPPER(*ea.cmd)
+      && has_event(EVENT_CMDUNDEFINED)) {
+    p = xstrdup(cmdname);
+    int ret = apply_autocmds(EVENT_CMDUNDEFINED, (char_u *)p, (char_u *)p, true, NULL);
+    xfree(p);
+    // If the autocommands did something and didn't cause an error, try
+    // finding the command again.
+    p = (ret && !aborting()) ? find_ex_command(&ea, NULL) : ea.cmd;
+  }
+
+  if (p == NULL || ea.cmdidx == CMD_SIZE) {
+    VALIDATION_ERROR("Command not found: %s", cmdname);
+  }
+  if (is_cmd_ni(ea.cmdidx)) {
+    VALIDATION_ERROR("Command not implemented: %s", cmdname);
+  }
+
+  // Get the command flags so that we can know what type of arguments the command uses.
+  // Not required for a user command since `find_ex_command` already deals with it in that case.
+  if (!IS_USER_CMDIDX(ea.cmdidx)) {
+    ea.argt = get_cmd_argt(ea.cmdidx);
+  }
+
+  // Parse command arguments since it's needed to get the command address type.
+  if (HAS_KEY(cmd->args)) {
+    if (cmd->args.type != kObjectTypeArray) {
+      VALIDATION_ERROR("'args' must be an Array");
+    }
+    // Check if every argument is valid
+    for (size_t i = 0; i < cmd->args.data.array.size; i++) {
+      Object elem = cmd->args.data.array.items[i];
+      if (elem.type != kObjectTypeString) {
+        VALIDATION_ERROR("Command argument must be a String");
+      } else if (string_iswhite(elem.data.string)) {
+        VALIDATION_ERROR("Command argument must have non-whitespace characters");
+      }
+    }
+
+    argc = cmd->args.data.array.size;
+    bool argc_valid;
+
+    // Check if correct number of arguments is used.
+    switch (ea.argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) {
+    case EX_EXTRA | EX_NOSPC | EX_NEEDARG:
+      argc_valid = argc == 1;
+      break;
+    case EX_EXTRA | EX_NOSPC:
+      argc_valid = argc <= 1;
+      break;
+    case EX_EXTRA | EX_NEEDARG:
+      argc_valid = argc >= 1;
+      break;
+    case EX_EXTRA:
+      argc_valid = true;
+      break;
+    default:
+      argc_valid = argc == 0;
+      break;
+    }
+
+    if (!argc_valid) {
+      argc = 0;  // Ensure that args array isn't erroneously freed at the end.
+      VALIDATION_ERROR("Incorrect number of arguments supplied");
+    }
+
+    if (argc != 0) {
+      args = xcalloc(argc, sizeof(char *));
+
+      for (size_t i = 0; i < argc; i++) {
+        args[i] = string_to_cstr(cmd->args.data.array.items[i].data.string);
+      }
+    }
+  }
+
+  // Simply pass the first argument (if it exists) as the arg pointer to `set_cmd_addr_type()`
+  // since it only ever checks the first argument.
+  set_cmd_addr_type(&ea, argc > 0 ? (char_u *)args[0] : NULL);
+
+  if (HAS_KEY(cmd->range)) {
+    if (!(ea.argt & EX_RANGE)) {
+      VALIDATION_ERROR("Command cannot accept a range");
+    } else if (cmd->range.type != kObjectTypeArray) {
+      VALIDATION_ERROR("'range' must be an Array");
+    } else if (cmd->range.data.array.size > 2) {
+      VALIDATION_ERROR("'range' cannot contain more than two elements");
+    }
+
+    Array range = cmd->range.data.array;
+    ea.addr_count = (int)range.size;
+
+    for (size_t i = 0; i < range.size; i++) {
+      Object elem = range.items[i];
+      if (elem.type != kObjectTypeInteger || elem.data.integer < 0) {
+        VALIDATION_ERROR("'range' element must be a non-negative Integer");
+      }
+    }
+
+    if (range.size > 0) {
+      ea.line1 = range.items[0].data.integer;
+      ea.line2 = range.items[range.size - 1].data.integer;
+    }
+
+    if (invalid_range(&ea) != NULL) {
+      VALIDATION_ERROR("Invalid range provided");
+    }
+  }
+  if (ea.addr_count == 0) {
+    if (ea.argt & EX_DFLALL) {
+      set_cmd_dflall_range(&ea);  // Default range for range=%
+    } else {
+      ea.line1 = ea.line2 = get_cmd_default_range(&ea);  // Default range.
+
+      if (ea.addr_type == ADDR_OTHER) {
+        // Default is 1, not cursor.
+        ea.line2 = 1;
+      }
+    }
+  }
+
+  if (HAS_KEY(cmd->count)) {
+    if (!(ea.argt & EX_COUNT)) {
+      VALIDATION_ERROR("Command cannot accept a count");
+    } else if (cmd->count.type != kObjectTypeInteger || cmd->count.data.integer < 0) {
+      VALIDATION_ERROR("'count' must be a non-negative Integer");
+    }
+    set_cmd_count(&ea, cmd->count.data.integer, true);
+  }
+
+  if (HAS_KEY(cmd->reg)) {
+    if (!(ea.argt & EX_REGSTR)) {
+      VALIDATION_ERROR("Command cannot accept a register");
+    } else if (cmd->reg.type != kObjectTypeString || cmd->reg.data.string.size != 1) {
+      VALIDATION_ERROR("'reg' must be a single character");
+    }
+    char regname = cmd->reg.data.string.data[0];
+    if (regname == '=') {
+      VALIDATION_ERROR("Cannot use register \"=");
+    } else if (!valid_yank_reg(regname, ea.cmdidx != CMD_put && !IS_USER_CMDIDX(ea.cmdidx))) {
+      VALIDATION_ERROR("Invalid register: \"%c", regname);
+    }
+    ea.regname = (uint8_t)regname;
+  }
+
+  OBJ_TO_BOOL(ea.forceit, cmd->bang, false, "'bang'");
+  if (ea.forceit && !(ea.argt & EX_BANG)) {
+    VALIDATION_ERROR("Command cannot accept a bang");
+  }
+
+  if (HAS_KEY(cmd->magic)) {
+    if (cmd->magic.type != kObjectTypeDictionary) {
+      VALIDATION_ERROR("'magic' must be a Dictionary");
+    }
+
+    Dict(cmd_magic) magic = { 0 };
+    if (!api_dict_to_keydict(&magic, KeyDict_cmd_magic_get_field,
+                             cmd->magic.data.dictionary, err)) {
+      goto end;
+    }
+
+    OBJ_TO_BOOL(cmdinfo.magic.file, magic.file, ea.argt & EX_XFILE, "'magic.file'");
+    OBJ_TO_BOOL(cmdinfo.magic.bar, magic.bar, ea.argt & EX_TRLBAR, "'magic.bar'");
+  } else {
+    cmdinfo.magic.file = ea.argt & EX_XFILE;
+    cmdinfo.magic.bar = ea.argt & EX_TRLBAR;
+  }
+
+  if (HAS_KEY(cmd->mods)) {
+    if (cmd->mods.type != kObjectTypeDictionary) {
+      VALIDATION_ERROR("'mods' must be a Dictionary");
+    }
+
+    Dict(cmd_mods) mods = { 0 };
+    if (!api_dict_to_keydict(&mods, KeyDict_cmd_mods_get_field, cmd->mods.data.dictionary, err)) {
+      goto end;
+    }
+
+    if (HAS_KEY(mods.tab)) {
+      if (mods.tab.type != kObjectTypeInteger || mods.tab.data.integer < 0) {
+        VALIDATION_ERROR("'mods.tab' must be a non-negative Integer");
+      }
+      cmdinfo.cmdmod.tab = (int)mods.tab.data.integer + 1;
+    }
+
+    if (HAS_KEY(mods.verbose)) {
+      if (mods.verbose.type != kObjectTypeInteger || mods.verbose.data.integer <= 0) {
+        VALIDATION_ERROR("'mods.verbose' must be a non-negative Integer");
+      }
+      cmdinfo.verbose = mods.verbose.data.integer;
+    }
+
+    bool vertical;
+    OBJ_TO_BOOL(vertical, mods.vertical, false, "'mods.vertical'");
+    cmdinfo.cmdmod.split |= (vertical ? WSP_VERT : 0);
+
+    if (HAS_KEY(mods.split)) {
+      if (mods.split.type != kObjectTypeString) {
+        VALIDATION_ERROR("'mods.split' must be a String");
+      }
+
+      if (STRCMP(mods.split.data.string.data, "aboveleft") == 0
+          || STRCMP(mods.split.data.string.data, "leftabove") == 0) {
+        cmdinfo.cmdmod.split |= WSP_ABOVE;
+      } else if (STRCMP(mods.split.data.string.data, "belowright") == 0
+                 || STRCMP(mods.split.data.string.data, "rightbelow") == 0) {
+        cmdinfo.cmdmod.split |= WSP_BELOW;
+      } else if (STRCMP(mods.split.data.string.data, "topleft") == 0) {
+        cmdinfo.cmdmod.split |= WSP_TOP;
+      } else if (STRCMP(mods.split.data.string.data, "botright") == 0) {
+        cmdinfo.cmdmod.split |= WSP_BOT;
+      } else {
+        VALIDATION_ERROR("Invalid value for 'mods.split'");
+      }
+    }
+
+    OBJ_TO_BOOL(cmdinfo.silent, mods.silent, false, "'mods.silent'");
+    OBJ_TO_BOOL(cmdinfo.emsg_silent, mods.emsg_silent, false, "'mods.emsg_silent'");
+    OBJ_TO_BOOL(cmdinfo.sandbox, mods.sandbox, false, "'mods.sandbox'");
+    OBJ_TO_BOOL(cmdinfo.noautocmd, mods.noautocmd, false, "'mods.noautocmd'");
+    OBJ_TO_BOOL(cmdinfo.cmdmod.browse, mods.browse, false, "'mods.browse'");
+    OBJ_TO_BOOL(cmdinfo.cmdmod.confirm, mods.confirm, false, "'mods.confirm'");
+    OBJ_TO_BOOL(cmdinfo.cmdmod.hide, mods.hide, false, "'mods.hide'");
+    OBJ_TO_BOOL(cmdinfo.cmdmod.keepalt, mods.keepalt, false, "'mods.keepalt'");
+    OBJ_TO_BOOL(cmdinfo.cmdmod.keepjumps, mods.keepjumps, false, "'mods.keepjumps'");
+    OBJ_TO_BOOL(cmdinfo.cmdmod.keepmarks, mods.keepmarks, false, "'mods.keepmarks'");
+    OBJ_TO_BOOL(cmdinfo.cmdmod.keeppatterns, mods.keeppatterns, false, "'mods.keeppatterns'");
+    OBJ_TO_BOOL(cmdinfo.cmdmod.lockmarks, mods.lockmarks, false, "'mods.lockmarks'");
+    OBJ_TO_BOOL(cmdinfo.cmdmod.noswapfile, mods.noswapfile, false, "'mods.noswapfile'");
+
+    if (cmdinfo.sandbox && !(ea.argt & EX_SBOXOK)) {
+      VALIDATION_ERROR("Command cannot be run in sandbox");
+    }
+  }
+
+  // Finally, build the command line string that will be stored inside ea.cmdlinep.
+  // This also sets the values of ea.cmd, ea.arg, ea.args and ea.arglens.
+  if (build_cmdline_str(&cmdline, &ea, &cmdinfo, args, argc) == FAIL) {
+    goto end;
+  }
+  ea.cmdlinep = &cmdline;
+
+  garray_T capture_local;
+  const int save_msg_silent = msg_silent;
+  garray_T * const save_capture_ga = capture_ga;
+
+  if (output) {
+    ga_init(&capture_local, 1, 80);
+    capture_ga = &capture_local;
+  }
+
+  try_start();
+  if (output) {
+    msg_silent++;
+  }
+
+  WITH_SCRIPT_CONTEXT(channel_id, {
+    execute_cmd(&ea, &cmdinfo);
+  });
+
+  if (output) {
+    capture_ga = save_capture_ga;
+    msg_silent = save_msg_silent;
+  }
+  try_end(err);
+
+  if (ERROR_SET(err)) {
+    goto clear_ga;
+  }
+
+  if (output && capture_local.ga_len > 1) {
+    retv = (String){
+      .data = capture_local.ga_data,
+      .size = (size_t)capture_local.ga_len,
+    };
+    // redir usually (except :echon) prepends a newline.
+    if (retv.data[0] == '\n') {
+      memmove(retv.data, retv.data + 1, retv.size - 1);
+      retv.data[retv.size - 1] = '\0';
+      retv.size = retv.size - 1;
+    }
+    goto end;
+  }
+clear_ga:
+  if (output) {
+    ga_clear(&capture_local);
+  }
+end:
+  xfree(cmdline);
+  xfree(cmdname);
+  xfree(ea.args);
+  xfree(ea.arglens);
+  for (size_t i = 0; i < argc; i++) {
+    xfree(args[i]);
+  }
+  xfree(args);
+
+  return retv;
+
+#undef OBJ_TO_BOOL
+#undef VALIDATION_ERROR
+}
-- 
cgit 


From cf68f0a51202342163825015602bdf78b783c777 Mon Sep 17 00:00:00 2001
From: Famiu Haque 
Date: Wed, 11 May 2022 22:51:53 +0600
Subject: fix(api): make `nvim_cmd` work correctly with empty arguments list
 (#18527)

Closes #18526.
---
 src/nvim/api/private/helpers.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index ccf1896f08..542e5c4953 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1843,7 +1843,8 @@ int build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdinfo, char
     CMDLINE_APPEND(" %s", args[i]);
   }
   eap->argc = argc;
-  eap->arg = argc > 0 ? eap->args[0] : NULL;
+  // If there isn't an argument, make eap->arg point to end of cmd
+  eap->arg = argc > 0 ? eap->args[0] : cmdline + pos;
 
   // Replace, :make and :grep with 'makeprg' and 'grepprg'.
   char *p = replace_makeprg(eap, eap->arg, cmdlinep);
-- 
cgit 


From 60b1e314ed6e0f8bbfddb3f12863a666294c07dc Mon Sep 17 00:00:00 2001
From: Andrey Mishchenko 
Date: Wed, 11 May 2022 20:05:56 -0400
Subject: docs: nvim_buf_[get|set]_[text|lines] (#18404)

- Use consistent formatting for args docs.
- Clarify inclusivity/exclusivity in `nvim_buf_[get|set]_text`.
---
 src/nvim/api/buffer.c | 34 ++++++++++++++++++----------------
 1 file changed, 18 insertions(+), 16 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 45dadae1dd..9842975d62 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -262,7 +262,7 @@ void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last, Error *e
 /// @param channel_id
 /// @param buffer           Buffer handle, or 0 for current buffer
 /// @param start            First line index
-/// @param end              Last line index (exclusive)
+/// @param end              Last line index, exclusive
 /// @param strict_indexing  Whether out-of-bounds should be an error.
 /// @param[out] err         Error details, if any
 /// @return Array of lines, or empty array for unloaded buffer.
@@ -358,7 +358,7 @@ static bool check_string_array(Array arr, bool disallow_nl, Error *err)
 /// @param channel_id
 /// @param buffer           Buffer handle, or 0 for current buffer
 /// @param start            First line index
-/// @param end              Last line index (exclusive)
+/// @param end              Last line index, exclusive
 /// @param strict_indexing  Whether out-of-bounds should be an error.
 /// @param replacement      Array of lines to use as replacement
 /// @param[out] err         Error details, if any
@@ -514,24 +514,25 @@ end:
 
 /// Sets (replaces) a range in the buffer
 ///
-/// This is recommended over nvim_buf_set_lines when only modifying parts of a
-/// line, as extmarks will be preserved on non-modified parts of the touched
+/// This is recommended over |nvim_buf_set_lines()| when only modifying parts of
+/// a line, as extmarks will be preserved on non-modified parts of the touched
 /// lines.
 ///
-/// Indexing is zero-based and end-exclusive.
+/// Indexing is zero-based. Row indices are end-inclusive, and column indices
+/// are end-exclusive.
 ///
-/// To insert text at a given index, set `start` and `end` ranges to the same
-/// index. To delete a range, set `replacement` to an array containing
-/// an empty string, or simply an empty array.
+/// To insert text at a given `(row, column)` location, use `start_row = end_row
+/// = row` and `start_col = end_col = col`. To delete the text in a range, use
+/// `replacement = {}`.
 ///
-/// Prefer nvim_buf_set_lines when adding or deleting entire lines only.
+/// Prefer |nvim_buf_set_lines()| if you are only adding or deleting entire lines.
 ///
 /// @param channel_id
 /// @param buffer           Buffer handle, or 0 for current buffer
 /// @param start_row        First line index
-/// @param start_col        First column
-/// @param end_row          Last line index
-/// @param end_col          Last column
+/// @param start_col        Starting column (byte offset) on first line
+/// @param end_row          Last line index, inclusive
+/// @param end_col          Ending column (byte offset) on last line, exclusive
 /// @param replacement      Array of lines to use as replacement
 /// @param[out] err         Error details, if any
 void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, Integer start_col,
@@ -760,16 +761,17 @@ end:
 /// This differs from |nvim_buf_get_lines()| in that it allows retrieving only
 /// portions of a line.
 ///
-/// Indexing is zero-based. Column indices are end-exclusive.
+/// Indexing is zero-based. Row indices are end-inclusive, and column indices
+/// are end-exclusive.
 ///
 /// Prefer |nvim_buf_get_lines()| when retrieving entire lines.
 ///
 /// @param channel_id
 /// @param buffer     Buffer handle, or 0 for current buffer
 /// @param start_row  First line index
-/// @param start_col  Starting byte offset of first line
-/// @param end_row    Last line index
-/// @param end_col    Ending byte offset of last line (exclusive)
+/// @param start_col  Starting column (byte offset) on first line
+/// @param end_row    Last line index, inclusive
+/// @param end_col    Ending column (byte offset) on last line, exclusive
 /// @param opts       Optional parameters. Currently unused.
 /// @param[out] err   Error details, if any
 /// @return Array of lines, or empty array for unloaded buffer.
-- 
cgit 


From 566f8f80d6cb7ef2df8366e5f092b0841ee757ce Mon Sep 17 00:00:00 2001
From: Famiu Haque 
Date: Thu, 12 May 2022 10:57:43 +0200
Subject: refactor(api/nvim_cmd): use `kvec_t` for constructing cmdline string
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Björn Linse 
---
 src/nvim/api/private/helpers.c | 159 ++++++++++++++++-------------------------
 src/nvim/api/vimscript.c       |   4 +-
 2 files changed, 61 insertions(+), 102 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 542e5c4953..dcede27bcb 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1707,144 +1707,113 @@ bool string_iswhite(String str)
   return true;
 }
 
-// Add modifier string for command into the command line. Includes trailing whitespace if non-empty.
-// @return OK or FAIL.
-static int add_cmd_modifier_str(char *cmdline, size_t *pos, const size_t bufsize,
-                                CmdParseInfo *cmdinfo)
+/// Build cmdline string for command, used by `nvim_cmd()`.
+///
+/// @return OK or FAIL.
+void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdinfo, char **args,
+                       size_t argc)
 {
-#define APPEND_MODIFIER(...) \
-  do { \
-    if (*pos < bufsize) { \
-      *pos += (size_t)snprintf(cmdline + *pos, bufsize - *pos, __VA_ARGS__); \
-    } \
-    if (*pos < bufsize) { \
-      cmdline[*pos] = ' '; \
-      *pos += 1; \
-    } else { \
-      goto err; \
-    } \
-  } while (0)
-
-#define APPEND_MODIFIER_IF(cond, mod) \
-  do { \
-    if (cond) { \
-      APPEND_MODIFIER(mod); \
-    } \
-  } while (0)
+  StringBuilder cmdline = KV_INITIAL_VALUE;
 
+  // Add command modifiers
   if (cmdinfo->cmdmod.tab != 0) {
-    APPEND_MODIFIER("%dtab", cmdinfo->cmdmod.tab - 1);
+    kv_printf(cmdline, "%dtab ", cmdinfo->cmdmod.tab - 1);
   }
   if (cmdinfo->verbose != -1) {
-    APPEND_MODIFIER("%ldverbose", cmdinfo->verbose);
+    kv_printf(cmdline, "%ldverbose ", cmdinfo->verbose);
+  }
+
+  if (cmdinfo->emsg_silent) {
+    kv_concat(cmdline, "silent! ");
+  } else if (cmdinfo->silent) {
+    kv_concat(cmdline, "silent ");
   }
 
   switch (cmdinfo->cmdmod.split & (WSP_ABOVE | WSP_BELOW | WSP_TOP | WSP_BOT)) {
   case WSP_ABOVE:
-    APPEND_MODIFIER("aboveleft");
+    kv_concat(cmdline, "aboveleft ");
     break;
   case WSP_BELOW:
-    APPEND_MODIFIER("belowright");
+    kv_concat(cmdline, "belowright ");
     break;
   case WSP_TOP:
-    APPEND_MODIFIER("topleft");
+    kv_concat(cmdline, "topleft ");
     break;
   case WSP_BOT:
-    APPEND_MODIFIER("botright");
+    kv_concat(cmdline, "botright ");
     break;
   default:
     break;
   }
 
-  APPEND_MODIFIER_IF(cmdinfo->cmdmod.split & WSP_VERT, "vertical");
-
-  if (cmdinfo->emsg_silent) {
-    APPEND_MODIFIER("silent!");
-  } else if (cmdinfo->silent) {
-    APPEND_MODIFIER("silent");
-  }
-
-  APPEND_MODIFIER_IF(cmdinfo->sandbox, "sandbox");
-  APPEND_MODIFIER_IF(cmdinfo->noautocmd, "noautocmd");
-  APPEND_MODIFIER_IF(cmdinfo->cmdmod.browse, "browse");
-  APPEND_MODIFIER_IF(cmdinfo->cmdmod.confirm, "confirm");
-  APPEND_MODIFIER_IF(cmdinfo->cmdmod.hide, "hide");
-  APPEND_MODIFIER_IF(cmdinfo->cmdmod.keepalt, "keepalt");
-  APPEND_MODIFIER_IF(cmdinfo->cmdmod.keepjumps, "keepjumps");
-  APPEND_MODIFIER_IF(cmdinfo->cmdmod.keepmarks, "keepmarks");
-  APPEND_MODIFIER_IF(cmdinfo->cmdmod.keeppatterns, "keeppatterns");
-  APPEND_MODIFIER_IF(cmdinfo->cmdmod.lockmarks, "lockmarks");
-  APPEND_MODIFIER_IF(cmdinfo->cmdmod.noswapfile, "noswapfile");
-
-  return OK;
-err:
-  return FAIL;
-
-#undef APPEND_MODIFIER
-#undef APPEND_MODIFIER_IF
-}
-
-/// Build cmdline string for command, used by `nvim_cmd()`.
-///
-/// @return OK or FAIL.
-int build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdinfo, char **args,
-                      size_t argc)
-{
-  const size_t bufsize = IOSIZE;
-  size_t pos = 0;
-  char *cmdline = xcalloc(bufsize, sizeof(char));
-
-#define CMDLINE_APPEND(...) \
+#define CMDLINE_APPEND_IF(cond, str) \
   do { \
-    if (pos < bufsize) { \
-      pos += (size_t)snprintf(cmdline + pos, bufsize - pos, __VA_ARGS__); \
-    } else { \
-      goto err; \
+    if (cond) { \
+      kv_concat(cmdline, str); \
     } \
   } while (0)
 
-  // Command modifiers.
-  if (add_cmd_modifier_str(cmdline, &pos, bufsize, cmdinfo) == FAIL) {
-    goto err;
-  }
+  CMDLINE_APPEND_IF(cmdinfo->cmdmod.split & WSP_VERT, "vertical ");
+  CMDLINE_APPEND_IF(cmdinfo->sandbox, "sandbox ");
+  CMDLINE_APPEND_IF(cmdinfo->noautocmd, "noautocmd ");
+  CMDLINE_APPEND_IF(cmdinfo->cmdmod.browse, "browse ");
+  CMDLINE_APPEND_IF(cmdinfo->cmdmod.confirm, "confirm ");
+  CMDLINE_APPEND_IF(cmdinfo->cmdmod.hide, "hide ");
+  CMDLINE_APPEND_IF(cmdinfo->cmdmod.keepalt, "keepalt ");
+  CMDLINE_APPEND_IF(cmdinfo->cmdmod.keepjumps, "keepjumps ");
+  CMDLINE_APPEND_IF(cmdinfo->cmdmod.keepmarks, "keepmarks ");
+  CMDLINE_APPEND_IF(cmdinfo->cmdmod.keeppatterns, "keeppatterns ");
+  CMDLINE_APPEND_IF(cmdinfo->cmdmod.lockmarks, "lockmarks ");
+  CMDLINE_APPEND_IF(cmdinfo->cmdmod.noswapfile, "noswapfile ");
+#undef CMDLINE_APPEND_IF
 
   // Command range / count.
   if (eap->argt & EX_RANGE) {
     if (eap->addr_count == 1) {
-      CMDLINE_APPEND("%ld", eap->line2);
+      kv_printf(cmdline, "%ld", eap->line2);
     } else if (eap->addr_count > 1) {
-      CMDLINE_APPEND("%ld,%ld", eap->line1, eap->line2);
+      kv_printf(cmdline, "%ld,%ld", eap->line1, eap->line2);
       eap->addr_count = 2;  // Make sure address count is not greater than 2
     }
   }
 
   // Keep the index of the position where command name starts, so eap->cmd can point to it.
-  size_t cmdname_idx = pos;
-  CMDLINE_APPEND("%s", eap->cmd);
-  eap->cmd = cmdline + cmdname_idx;
+  size_t cmdname_idx = cmdline.size;
+  kv_printf(cmdline, "%s", eap->cmd);
 
   // Command bang.
   if (eap->argt & EX_BANG && eap->forceit) {
-    CMDLINE_APPEND("!");
+    kv_printf(cmdline, "!");
   }
 
   // Command register.
   if (eap->argt & EX_REGSTR && eap->regname) {
-    CMDLINE_APPEND(" %c", eap->regname);
+    kv_printf(cmdline, " %c", eap->regname);
   }
 
-  // Iterate through each argument and store the starting position and length of each argument in
-  // the cmdline string in `eap->args` and `eap->arglens`, respectively.
-  eap->args = xcalloc(argc, sizeof(char *));
+  // Iterate through each argument and store the starting index and length of each argument
+  size_t *argidx = xcalloc(argc, sizeof(size_t));
+  eap->argc = argc;
   eap->arglens = xcalloc(argc, sizeof(size_t));
   for (size_t i = 0; i < argc; i++) {
-    eap->args[i] = cmdline + pos + 1;  // add 1 to skip the leading space.
+    argidx[i] = cmdline.size + 1;  // add 1 to account for the space.
     eap->arglens[i] = STRLEN(args[i]);
-    CMDLINE_APPEND(" %s", args[i]);
+    kv_printf(cmdline, " %s", args[i]);
   }
-  eap->argc = argc;
-  // If there isn't an argument, make eap->arg point to end of cmd
-  eap->arg = argc > 0 ? eap->args[0] : cmdline + pos;
+
+  // Now that all the arguments are appended, use the command index and argument indices to set the
+  // values of eap->cmd, eap->arg and eap->args.
+  eap->cmd = cmdline.items + cmdname_idx;
+  eap->args = xcalloc(argc, sizeof(char *));
+  for (size_t i = 0; i < argc; i++) {
+    eap->args[i] = cmdline.items + argidx[i];
+  }
+  // If there isn't an argument, make eap->arg point to end of cmdline.
+  eap->arg = argc > 0 ? eap->args[0] : cmdline.items + cmdline.size;
+
+  // Finally, make cmdlinep point to the cmdline string.
+  *cmdlinep = cmdline.items;
+  xfree(argidx);
 
   // Replace, :make and :grep with 'makeprg' and 'grepprg'.
   char *p = replace_makeprg(eap, eap->arg, cmdlinep);
@@ -1854,12 +1823,4 @@ int build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdinfo, char
     eap->arg = p;
     eap->args[0] = p;
   }
-
-  *cmdlinep = cmdline;
-  return OK;
-err:
-  xfree(cmdline);
-  return FAIL;
-
-#undef CMDLINE_APPEND
 }
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index 02f406d686..506b3e7f10 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -1290,9 +1290,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
 
   // Finally, build the command line string that will be stored inside ea.cmdlinep.
   // This also sets the values of ea.cmd, ea.arg, ea.args and ea.arglens.
-  if (build_cmdline_str(&cmdline, &ea, &cmdinfo, args, argc) == FAIL) {
-    goto end;
-  }
+  build_cmdline_str(&cmdline, &ea, &cmdinfo, args, argc);
   ea.cmdlinep = &cmdline;
 
   garray_T capture_local;
-- 
cgit 


From 0a66c4a72a9912f71cc8c3b1795ac2797dd5dbb9 Mon Sep 17 00:00:00 2001
From: dundargoc <33953936+dundargoc@users.noreply.github.com>
Date: Sun, 15 May 2022 10:44:48 +0200
Subject: docs(nvim_set_keymap): specify that optional arguments defaults to
 false (#18177)

Closes: https://github.com/neovim/neovim/issues/16919
---
 src/nvim/api/vim.c | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 09bf032daa..d1da3312e1 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -1601,12 +1601,13 @@ ArrayOf(Dictionary) nvim_get_keymap(uint64_t channel_id, String mode)
 ///               or "!" for |:map!|, or empty string for |:map|.
 /// @param  lhs   Left-hand-side |{lhs}| of the mapping.
 /// @param  rhs   Right-hand-side |{rhs}| of the mapping.
-/// @param  opts  Optional parameters map. Accepts all |:map-arguments|
-///               as keys excluding || but including |noremap| and "desc".
-///               "desc" can be used to give a description to keymap.
-///               When called from Lua, also accepts a "callback" key that takes
-///               a Lua function to call when the mapping is executed.
-///               Values are Booleans. Unknown key is an error.
+/// @param  opts  Optional parameters map: keys are |:map-arguments|, values
+///               are booleans (default false). Accepts all |:map-arguments| as
+///               keys excluding || but including |noremap| and "desc".
+///               Unknown key is an error. "desc" can be used to give a
+///               description to the mapping. When called from Lua, also accepts a
+///               "callback" key that takes a Lua function to call when the
+///               mapping is executed.
 /// @param[out]   err   Error details, if any.
 void nvim_set_keymap(uint64_t channel_id, String mode, String lhs, String rhs, Dict(keymap) *opts,
                      Error *err)
-- 
cgit 


From 0a3d615b1ca17cda978b89d66acef39b90ee7c81 Mon Sep 17 00:00:00 2001
From: deforde <7503504+deforde@users.noreply.github.com>
Date: Sun, 15 May 2022 22:06:23 +0200
Subject: fix(api): nvim_eval_statusline should validate input #18347

Fix #18112

Make an exception for strings starting with "%!".
---
 src/nvim/api/vim.c | 8 ++++++++
 1 file changed, 8 insertions(+)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index d1da3312e1..fca86fe440 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -2276,6 +2276,14 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
   bool use_tabline = false;
   bool highlights = false;
 
+  if (str.size < 2 || memcmp(str.data, "%!", 2)) {
+    const char *const errmsg = check_stl_option((char_u *)str.data);
+    if (errmsg) {
+      api_set_error(err, kErrorTypeValidation, "%s", errmsg);
+      return result;
+    }
+  }
+
   if (HAS_KEY(opts->winid)) {
     if (opts->winid.type != kObjectTypeInteger) {
       api_set_error(err, kErrorTypeValidation, "winid must be an integer");
-- 
cgit 


From 5e3b16836a333d48f8977fc201c5e666767d61f1 Mon Sep 17 00:00:00 2001
From: Oliver Marriott 
Date: Mon, 16 May 2022 07:06:06 +1000
Subject: docs(api): nvim_set_hl attributes #18558

---
 src/nvim/api/vim.c | 39 ++++++++++++++++++++++++++++++---------
 1 file changed, 30 insertions(+), 9 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index fca86fe440..a257dd6478 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -126,22 +126,43 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err)
 
 /// Sets a highlight group.
 ///
-/// Note: Unlike the `:highlight` command which can update a highlight group,
-/// this function completely replaces the definition. For example:
-/// ``nvim_set_hl(0, 'Visual', {})`` will clear the highlight group 'Visual'.
+/// @note Unlike the `:highlight` command which can update a highlight group,
+///       this function completely replaces the definition. For example:
+///       ``nvim_set_hl(0, 'Visual', {})`` will clear the highlight group
+///       'Visual'.
+///
+/// @note The fg and bg keys also accept the string values `"fg"` or `"bg"`
+///       which act as aliases to the corresponding foreground and background
+///       values of the Normal group. If the Normal group has not been defined,
+///       using these values results in an error.
 ///
 /// @param ns_id Namespace id for this highlight |nvim_create_namespace()|.
 ///              Use 0 to set a highlight group globally |:highlight|.
 /// @param name  Highlight group name, e.g. "ErrorMsg"
-/// @param val   Highlight definition map, like |synIDattr()|. In
-///              addition, the following keys are recognized:
+/// @param val   Highlight definition map, accepts the following keys:
+///                - fg (or foreground): color name or "#RRGGBB", see note.
+///                - bg (or background): color name or "#RRGGBB", see note.
+///                - sp (or special): color name or "#RRGGBB"
+///                - blend: integer between 0 and 100
+///                - bold: boolean
+///                - standout: boolean
+///                - underline: boolean
+///                - underlineline: boolean
+///                - undercurl: boolean
+///                - underdot: boolean
+///                - underdash: boolean
+///                - strikethrough: boolean
+///                - italic: boolean
+///                - reverse: boolean
+///                - nocombine: boolean
+///                - link: name of another highlight group to link to, see |:hi-link|.
+///              Additionally, the following keys are recognized:
 ///                - default: Don't override existing definition |:hi-default|
 ///                - ctermfg: Sets foreground of cterm color |highlight-ctermfg|
 ///                - ctermbg: Sets background of cterm color |highlight-ctermbg|
-///                - cterm: cterm attribute map, like
-///                  |highlight-args|.
-///                  Note: Attributes default to those set for `gui`
-///                        if not set.
+///                - cterm: cterm attribute map, like |highlight-args|. If not set,
+///                         cterm attributes will match those from the attribute map
+///                         documented above.
 /// @param[out] err Error details, if any
 ///
 // TODO(bfredl): val should take update vs reset flag
-- 
cgit 


From f0148de7907ec297647816d51c79745be729439e Mon Sep 17 00:00:00 2001
From: Dundar Goc 
Date: Mon, 9 May 2022 11:49:32 +0200
Subject: refactor: replace char_u variables and functions with char

Work on https://github.com/neovim/neovim/issues/459
---
 src/nvim/api/vimscript.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index 506b3e7f10..42101af7f0 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -1058,7 +1058,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
   if (p != NULL && ea.cmdidx == CMD_SIZE && ASCII_ISUPPER(*ea.cmd)
       && has_event(EVENT_CMDUNDEFINED)) {
     p = xstrdup(cmdname);
-    int ret = apply_autocmds(EVENT_CMDUNDEFINED, (char_u *)p, (char_u *)p, true, NULL);
+    int ret = apply_autocmds(EVENT_CMDUNDEFINED, p, p, true, NULL);
     xfree(p);
     // If the autocommands did something and didn't cause an error, try
     // finding the command again.
-- 
cgit 


From 54b5222fbbaed6d283c6160f5d18713718a16eb2 Mon Sep 17 00:00:00 2001
From: Famiu Haque 
Date: Tue, 17 May 2022 18:27:33 +0600
Subject: docs(api): update v:errmsg behavior #18593

---
 src/nvim/api/vimscript.c | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index 42101af7f0..b8f7b33cd5 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -34,7 +34,7 @@
 /// Unlike |nvim_command()| this function supports heredocs, script-scope (s:),
 /// etc.
 ///
-/// On execution error: fails with VimL error, does not update v:errmsg.
+/// On execution error: fails with VimL error, updates v:errmsg.
 ///
 /// @see |execute()|
 /// @see |nvim_command()|
@@ -98,7 +98,7 @@ theend:
 
 /// Executes an Ex command.
 ///
-/// On execution error: fails with VimL error, does not update v:errmsg.
+/// On execution error: fails with VimL error, updates v:errmsg.
 ///
 /// Prefer using |nvim_cmd()| or |nvim_exec()| over this. To evaluate multiple lines of Vim script
 /// or an Ex command directly, use |nvim_exec()|. To construct an Ex command using a structured
@@ -118,7 +118,7 @@ void nvim_command(String command, Error *err)
 /// Evaluates a VimL |expression|.
 /// Dictionaries and Lists are recursively expanded.
 ///
-/// On execution error: fails with VimL error, does not update v:errmsg.
+/// On execution error: fails with VimL error, updates v:errmsg.
 ///
 /// @param expr     VimL expression string
 /// @param[out] err Error details, if any
@@ -226,7 +226,7 @@ free_vim_args:
 
 /// Calls a VimL function with the given arguments.
 ///
-/// On execution error: fails with VimL error, does not update v:errmsg.
+/// On execution error: fails with VimL error, updates v:errmsg.
 ///
 /// @param fn       Function to call
 /// @param args     Function arguments packed in an Array
@@ -240,7 +240,7 @@ Object nvim_call_function(String fn, Array args, Error *err)
 
 /// Calls a VimL |Dictionary-function| with the given arguments.
 ///
-/// On execution error: fails with VimL error, does not update v:errmsg.
+/// On execution error: fails with VimL error, updates v:errmsg.
 ///
 /// @param dict Dictionary, or String evaluating to a VimL |self| dict
 /// @param fn Name of the function defined on the VimL dict
@@ -996,6 +996,8 @@ end:
 /// such as having spaces inside a command argument, expanding filenames in a command that otherwise
 /// doesn't expand filenames, etc.
 ///
+/// On execution error: fails with VimL error, updates v:errmsg.
+///
 /// @see |nvim_exec()|
 /// @see |nvim_command()|
 ///
-- 
cgit 


From 6219331c4d333e5a76129746021f972c21a040db Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Wed, 11 May 2022 13:49:43 +0100
Subject: feat(api): add win and buf to nvim_set_option_value

Co-authored-by: Gregory Anders <8965202+gpanders@users.noreply.github.com>
---
 src/nvim/api/keysets.lua       |  2 ++
 src/nvim/api/private/helpers.c |  6 +++---
 src/nvim/api/vim.c             | 39 +++++++++++++++++++++++++++++++++++----
 3 files changed, 40 insertions(+), 7 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
index 78f8ed3cca..b351d8f720 100644
--- a/src/nvim/api/keysets.lua
+++ b/src/nvim/api/keysets.lua
@@ -84,6 +84,8 @@ return {
   };
   option = {
     "scope";
+    "win";
+    "buf";
   };
   highlight = {
     "bold";
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index dcede27bcb..7be55595cf 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1040,8 +1040,8 @@ Object copy_object(Object obj)
   }
 }
 
-static void set_option_value_for(char *key, int numval, char *stringval, int opt_flags,
-                                 int opt_type, void *from, Error *err)
+void set_option_value_for(char *key, long numval, char *stringval, int opt_flags,
+                          int opt_type, void *from, Error *err)
 {
   switchwin_T switchwin;
   aco_save_T aco;
@@ -1081,7 +1081,7 @@ static void set_option_value_for(char *key, int numval, char *stringval, int opt
 }
 
 
-static void set_option_value_err(char *key, int numval, char *stringval, int opt_flags, Error *err)
+static void set_option_value_err(char *key, long numval, char *stringval, int opt_flags, Error *err)
 {
   char *errmsg;
 
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index a257dd6478..f6ca5a5e00 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -775,11 +775,15 @@ end:
 /// |:set|: for global-local options, both the global and local value are set
 /// unless otherwise specified with {scope}.
 ///
+/// Note the options {win} and {buf} cannot be used together.
+///
 /// @param name      Option name
 /// @param value     New option value
 /// @param opts      Optional parameters
 ///                  - scope: One of 'global' or 'local'. Analogous to
 ///                  |:setglobal| and |:setlocal|, respectively.
+///                  - win: |window-ID|. Used for setting window local option.
+///                  - buf: Buffer number. Used for setting buffer local option.
 /// @param[out] err  Error details, if any
 void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error *err)
   FUNC_API_SINCE(9)
@@ -799,6 +803,36 @@ void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error
     return;
   }
 
+  int opt_type = SREQ_GLOBAL;
+  void *to = NULL;
+
+  if (opts->win.type == kObjectTypeInteger) {
+    opt_type = SREQ_WIN;
+    to = find_window_by_handle((int)opts->win.data.integer, err);
+  } else if (HAS_KEY(opts->win)) {
+    api_set_error(err, kErrorTypeValidation, "invalid value for key: win");
+    return;
+  }
+
+  if (opts->buf.type == kObjectTypeInteger) {
+    scope = OPT_LOCAL;
+    opt_type = SREQ_BUF;
+    to = find_buffer_by_handle((int)opts->buf.data.integer, err);
+  } else if (HAS_KEY(opts->buf)) {
+    api_set_error(err, kErrorTypeValidation, "invalid value for key: buf");
+    return;
+  }
+
+  if (HAS_KEY(opts->scope) && HAS_KEY(opts->buf)) {
+    api_set_error(err, kErrorTypeValidation, "scope and buf cannot be used together");
+    return;
+  }
+
+  if (HAS_KEY(opts->win) && HAS_KEY(opts->buf)) {
+    api_set_error(err, kErrorTypeValidation, "buf and win cannot be used together");
+    return;
+  }
+
   long numval = 0;
   char *stringval = NULL;
 
@@ -820,10 +854,7 @@ void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error
     return;
   }
 
-  char *e = set_option_value(name.data, numval, stringval, scope);
-  if (e) {
-    api_set_error(err, kErrorTypeException, "%s", e);
-  }
+  set_option_value_for(name.data, numval, stringval, scope, opt_type, to, err);
 }
 
 /// Gets the option information for all options.
-- 
cgit 


From e1bdb2a258cbe6c5cb981acc6bac82cd9e7706fb Mon Sep 17 00:00:00 2001
From: Famiu Haque 
Date: Fri, 13 May 2022 20:47:11 +0600
Subject: feat(ui): add `'winbar'`
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Adds support for a bar at the top of each window, enabled through the
`'winbar'` option.

Co-authored-by: Björn Linse 
---
 src/nvim/api/keysets.lua       |  1 +
 src/nvim/api/private/helpers.c |  8 ++++----
 src/nvim/api/vim.c             | 35 ++++++++++++++++++++++-------------
 3 files changed, 27 insertions(+), 17 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
index 78f8ed3cca..27228204e0 100644
--- a/src/nvim/api/keysets.lua
+++ b/src/nvim/api/keysets.lua
@@ -80,6 +80,7 @@ return {
     "maxwidth";
     "fillchar";
     "highlights";
+    "use_winbar";
     "use_tabline";
   };
   option = {
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index dcede27bcb..7bd68f277b 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1418,14 +1418,14 @@ bool set_mark(buf_T *buf, String name, Integer line, Integer col, Error *err)
 }
 
 /// Get default statusline highlight for window
-const char *get_default_stl_hl(win_T *wp)
+const char *get_default_stl_hl(win_T *wp, bool use_winbar)
 {
   if (wp == NULL) {
     return "TabLineFill";
-  } else if (wp == curwin) {
-    return "StatusLine";
+  } else if (use_winbar) {
+    return (wp == curwin) ? "WinBar" : "WinBarNC";
   } else {
-    return "StatusLineNC";
+    return (wp == curwin) ? "StatusLine" : "StatusLineNC";
   }
 }
 
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index a257dd6478..3c378e73e1 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -2274,8 +2274,9 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err)
 ///           - fillchar: (string) Character to fill blank spaces in the statusline (see
 ///                                'fillchars'). Treated as single-width even if it isn't.
 ///           - highlights: (boolean) Return highlight information.
+///           - use_winbar: (boolean) Evaluate winbar instead of statusline.
 ///           - use_tabline: (boolean) Evaluate tabline instead of statusline. When |TRUE|, {winid}
-///                                    is ignored.
+///                                    is ignored. Mutually exclusive with {use_winbar}.
 ///
 /// @param[out] err Error details, if any.
 /// @return Dictionary containing statusline information, with these keys:
@@ -2294,6 +2295,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
   int maxwidth;
   int fillchar = 0;
   Window window = 0;
+  bool use_winbar = false;
   bool use_tabline = false;
   bool highlights = false;
 
@@ -2313,7 +2315,6 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
 
     window = (Window)opts->winid.data.integer;
   }
-
   if (HAS_KEY(opts->fillchar)) {
     if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size == 0
         || ((size_t)utf_ptr2len(opts->fillchar.data.string.data)
@@ -2323,7 +2324,6 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
     }
     fillchar = utf_ptr2char(opts->fillchar.data.string.data);
   }
-
   if (HAS_KEY(opts->highlights)) {
     highlights = api_object_to_bool(opts->highlights, "highlights", false, err);
 
@@ -2331,7 +2331,13 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
       return result;
     }
   }
+  if (HAS_KEY(opts->use_winbar)) {
+    use_winbar = api_object_to_bool(opts->use_winbar, "use_winbar", false, err);
 
+    if (ERROR_SET(err)) {
+      return result;
+    }
+  }
   if (HAS_KEY(opts->use_tabline)) {
     use_tabline = api_object_to_bool(opts->use_tabline, "use_tabline", false, err);
 
@@ -2339,6 +2345,10 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
       return result;
     }
   }
+  if (use_winbar && use_tabline) {
+    api_set_error(err, kErrorTypeValidation, "use_winbar and use_tabline are mutually exclusive");
+    return result;
+  }
 
   win_T *wp, *ewp;
 
@@ -2348,7 +2358,6 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
     fillchar = ' ';
   } else {
     wp = find_window_by_handle(window, err);
-
     if (wp == NULL) {
       api_set_error(err, kErrorTypeException, "unknown winid %d", window);
       return result;
@@ -2356,8 +2365,12 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
     ewp = wp;
 
     if (fillchar == 0) {
-      int attr;
-      fillchar = fillchar_status(&attr, wp);
+      if (use_winbar) {
+        fillchar = wp->w_p_fcs_chars.wbr;
+      } else {
+        int attr;
+        fillchar = fillchar_status(&attr, wp);
+      }
     }
   }
 
@@ -2369,7 +2382,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
 
     maxwidth = (int)opts->maxwidth.data.integer;
   } else {
-    maxwidth = (use_tabline || global_stl_height() > 0) ? Columns : wp->w_width;
+    maxwidth = (use_tabline || (!use_winbar && global_stl_height() > 0)) ? Columns : wp->w_width;
   }
 
   char buf[MAXPATHL];
@@ -2404,7 +2417,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
     // add the default highlight at the beginning of the highlight list
     if (hltab->start == NULL || ((char *)hltab->start - buf) != 0) {
       Dictionary hl_info = ARRAY_DICT_INIT;
-      grpname = get_default_stl_hl(wp);
+      grpname = get_default_stl_hl(wp, use_winbar);
 
       PUT(hl_info, "start", INTEGER_OBJ(0));
       PUT(hl_info, "group", CSTR_TO_OBJ(grpname));
@@ -2418,22 +2431,18 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
       PUT(hl_info, "start", INTEGER_OBJ((char *)sp->start - buf));
 
       if (sp->userhl == 0) {
-        grpname = get_default_stl_hl(wp);
+        grpname = get_default_stl_hl(wp, use_winbar);
       } else if (sp->userhl < 0) {
         grpname = (char *)syn_id2name(-sp->userhl);
       } else {
         snprintf(user_group, sizeof(user_group), "User%d", sp->userhl);
         grpname = user_group;
       }
-
       PUT(hl_info, "group", CSTR_TO_OBJ(grpname));
-
       ADD(hl_values, DICTIONARY_OBJ(hl_info));
     }
-
     PUT(result, "highlights", ARRAY_OBJ(hl_values));
   }
-
   PUT(result, "str", CSTR_TO_OBJ((char *)buf));
 
   return result;
-- 
cgit 


From 028329850e4a4fc9171518566ba7947d9e435f83 Mon Sep 17 00:00:00 2001
From: bfredl 
Date: Wed, 18 May 2022 13:06:02 +0200
Subject: refactor: grid->rows and grid->cols

---
 src/nvim/api/vim.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 3c378e73e1..15992a98be 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -2140,8 +2140,8 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err)
     }
   }
 
-  if (row < 0 || row >= g->Rows
-      || col < 0 || col >= g->Columns) {
+  if (row < 0 || row >= g->rows
+      || col < 0 || col >= g->cols) {
     return ret;
   }
   size_t off = g->line_offset[(size_t)row] + (size_t)col;
-- 
cgit 


From 8a9ab88945cdabcbd23f23406353ec6110fefa83 Mon Sep 17 00:00:00 2001
From: Gregory Anders <8965202+gpanders@users.noreply.github.com>
Date: Wed, 18 May 2022 09:51:26 -0600
Subject: feat(api): enable nvim_exec_autocmds to pass arbitrary data (#18613)

Add a "data" key to nvim_exec_autocmds that passes arbitrary data (API
objects) to autocommand callbacks.
---
 src/nvim/api/autocmd.c   | 10 +++++++++-
 src/nvim/api/keysets.lua |  1 +
 2 files changed, 10 insertions(+), 1 deletion(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
index 76e531e7aa..30a5d60c39 100644
--- a/src/nvim/api/autocmd.c
+++ b/src/nvim/api/autocmd.c
@@ -405,6 +405,7 @@ cleanup:
 ///                 - match: (string) the expanded value of ||
 ///                 - buf: (number) the expanded value of ||
 ///                 - file: (string) the expanded value of ||
+///                 - data: (any) arbitrary data passed to |nvim_exec_autocmds()|
 ///             - command (string) optional: Vim command to execute on event. Cannot be used with
 ///             {callback}
 ///             - once (boolean) optional: defaults to false. Run the autocommand
@@ -749,6 +750,8 @@ void nvim_del_augroup_by_name(String name, Error *err)
 ///             {pattern}.
 ///             - modeline (bool) optional: defaults to true. Process the
 ///             modeline after the autocommands ||.
+///             - data (any): arbitrary data to send to the autocommand callback. See
+///             |nvim_create_autocmd()| for details.
 /// @see |:doautocmd|
 void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err)
   FUNC_API_SINCE(9)
@@ -760,6 +763,7 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err)
   bool set_buf = false;
 
   char *pattern = NULL;
+  Object *data = NULL;
   bool set_pattern = false;
 
   Array event_array = ARRAY_DICT_INIT;
@@ -818,6 +822,10 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err)
     set_pattern = true;
   }
 
+  if (opts->data.type != kObjectTypeNil) {
+    data = &opts->data;
+  }
+
   modeline = api_object_to_bool(opts->modeline, "modeline", true, err);
 
   if (set_pattern && set_buf) {
@@ -829,7 +837,7 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err)
   FOREACH_ITEM(event_array, event_str, {
     GET_ONE_EVENT(event_nr, event_str, cleanup)
 
-    did_aucmd |= apply_autocmds_group(event_nr, pattern, NULL, true, au_group, buf, NULL);
+    did_aucmd |= apply_autocmds_group(event_nr, pattern, NULL, true, au_group, buf, NULL, data);
   })
 
   if (did_aucmd && modeline) {
diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
index 27228204e0..6924e2ef8f 100644
--- a/src/nvim/api/keysets.lua
+++ b/src/nvim/api/keysets.lua
@@ -146,6 +146,7 @@ return {
     "group";
     "modeline";
     "pattern";
+    "data";
   };
   get_autocmds = {
     "event";
-- 
cgit 


From c28192e6f9c16de3add78c5ecf8e732c241f945a Mon Sep 17 00:00:00 2001
From: bfredl 
Date: Wed, 18 May 2022 13:29:02 +0200
Subject: refactor: move more grid functions to grid.c. Clean up some variables

---
 src/nvim/api/ui.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index b4d20ed975..52f76f4650 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -349,7 +349,11 @@ void nvim_ui_try_resize_grid(uint64_t channel_id, Integer grid, Integer width, I
     return;
   }
 
-  ui_grid_resize((handle_T)grid, (int)width, (int)height, err);
+  if (grid == DEFAULT_GRID_HANDLE) {
+    nvim_ui_try_resize(channel_id, width, height, err);
+  } else {
+    ui_grid_resize((handle_T)grid, (int)width, (int)height, err);
+  }
 }
 
 /// Tells Nvim the number of elements displaying in the popumenu, to decide
-- 
cgit 


From 86db222469d7f20acfca7c69fab129577deb9291 Mon Sep 17 00:00:00 2001
From: ckipp01 
Date: Tue, 17 May 2022 15:35:53 +0200
Subject: docs(extmark): remove message about passing in id=0

The docs for `nvim_buf_set_extmark` mention that you can create a new
extmark when passing in `id=0`, however if you do this you'll get an
error since the code checks that id is positive.

```
id is not a positive integer
```

This change re-words the sentence to make it clearer.
---
 src/nvim/api/extmark.c | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index e05d80812d..9143f593b1 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -366,12 +366,11 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
 
 /// 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.)
+/// By default a new extmark is created when no id is passed in, but it is also
+/// possible to create a new mark by passing in a previously unused id or move
+/// an existing mark by passing in its id. 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.
-- 
cgit 


From fb8fa004d8c91b7b591509539a228e97ebc57d9d Mon Sep 17 00:00:00 2001
From: Famiu Haque 
Date: Thu, 19 May 2022 21:49:12 +0600
Subject: fix: make `nvim_cmd` not suppress errors inside key mapping

Closes #18632
---
 src/nvim/api/vimscript.c | 27 +++++++++++++++------------
 1 file changed, 15 insertions(+), 12 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index b8f7b33cd5..e71f1a11ec 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -1304,20 +1304,23 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
     capture_ga = &capture_local;
   }
 
-  try_start();
-  if (output) {
-    msg_silent++;
-  }
+  TRY_WRAP({
+    try_start();
+    if (output) {
+      msg_silent++;
+    }
 
-  WITH_SCRIPT_CONTEXT(channel_id, {
-    execute_cmd(&ea, &cmdinfo);
-  });
+    WITH_SCRIPT_CONTEXT(channel_id, {
+      execute_cmd(&ea, &cmdinfo);
+    });
 
-  if (output) {
-    capture_ga = save_capture_ga;
-    msg_silent = save_msg_silent;
-  }
-  try_end(err);
+    if (output) {
+      capture_ga = save_capture_ga;
+      msg_silent = save_msg_silent;
+    }
+
+    try_end(err);
+  });
 
   if (ERROR_SET(err)) {
     goto clear_ga;
-- 
cgit 


From f15122e8a2938b0a440aa3d834f6648537f1951f Mon Sep 17 00:00:00 2001
From: James McCoy 
Date: Thu, 19 May 2022 22:12:48 -0400
Subject: fix(cid/351940): free compl_arg in create_user_commands()'s error
 path exit

---
 src/nvim/api/private/helpers.c | 1 +
 1 file changed, 1 insertion(+)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 7bd68f277b..5d4b84482b 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1624,6 +1624,7 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
 err:
   NLUA_CLEAR_REF(luaref);
   NLUA_CLEAR_REF(compl_luaref);
+  xfree(compl_arg);
 }
 
 int find_sid(uint64_t channel_id)
-- 
cgit 


From 83f42e086ac76dbdb9fdd19ba82b3bd20c986fb3 Mon Sep 17 00:00:00 2001
From: James McCoy 
Date: Fri, 20 May 2022 07:09:19 -0400
Subject: perf(cid/350479): avoid copying ExtmarkInfo when calling
 extmark_to_array()

---
 src/nvim/api/extmark.c | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index e05d80812d..d80bec2f70 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -95,27 +95,27 @@ static bool ns_initialized(uint32_t ns)
 }
 
 
-static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict)
+static Array extmark_to_array(const 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((Integer)extmark->mark_id));
   }
-  ADD(rv, INTEGER_OBJ(extmark.row));
-  ADD(rv, INTEGER_OBJ(extmark.col));
+  ADD(rv, INTEGER_OBJ(extmark->row));
+  ADD(rv, INTEGER_OBJ(extmark->col));
 
   if (add_dict) {
     Dictionary dict = ARRAY_DICT_INIT;
 
-    PUT(dict, "right_gravity", BOOLEAN_OBJ(extmark.right_gravity));
+    PUT(dict, "right_gravity", BOOLEAN_OBJ(extmark->right_gravity));
 
-    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));
+    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));
     }
 
-    Decoration *decor = &extmark.decor;
+    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));
@@ -238,7 +238,7 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
   if (extmark.row < 0) {
     return rv;
   }
-  return extmark_to_array(extmark, false, details);
+  return extmark_to_array(&extmark, false, details);
 }
 
 /// Gets extmarks in "traversal order" from a |charwise| region defined by
@@ -357,7 +357,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
                                        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)));
+    ADD(rv, ARRAY_OBJ(extmark_to_array(&kv_A(marks, i), true, (bool)details)));
   }
 
   kv_destroy(marks);
-- 
cgit 


From 55246d44f92beb17898d006e5324bf2d44f44c31 Mon Sep 17 00:00:00 2001
From: kylo252 <59826753+kylo252@users.noreply.github.com>
Date: Sat, 21 May 2022 15:55:48 +0200
Subject: fix(autocmds): separate command from desc (#18617)

---
 src/nvim/api/autocmd.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
index 30a5d60c39..4ee834d75a 100644
--- a/src/nvim/api/autocmd.c
+++ b/src/nvim/api/autocmd.c
@@ -283,7 +283,7 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
 
         PUT(autocmd_info,
             "command",
-            STRING_OBJ(cstr_to_string(aucmd_exec_to_string(ac, ac->exec))));
+            STRING_OBJ(cstr_as_string(aucmd_exec_to_string(ac, ac->exec))));
 
         PUT(autocmd_info,
             "pattern",
-- 
cgit 


From 646e1c3a3adf89928959c3a0d6a1bd7c55ca932f Mon Sep 17 00:00:00 2001
From: devbhan singh 
Date: Wed, 20 Jan 2021 14:55:52 +0530
Subject: feat(ui): clear message history explicitly with msg_history_clear
 event

---
 src/nvim/api/ui_events.in.h | 2 ++
 1 file changed, 2 insertions(+)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index 63aaaae38a..0030f9edf7 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -174,4 +174,6 @@ void msg_ruler(Array content)
   FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
 void msg_history_show(Array entries)
   FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
+void msg_history_clear(void)
+  FUNC_API_SINCE(10) FUNC_API_REMOTE_ONLY;
 #endif  // NVIM_API_UI_EVENTS_IN_H
-- 
cgit 


From 47d9a393de766d8e444043f5bc40dc774d45fece Mon Sep 17 00:00:00 2001
From: James McCoy 
Date: Sun, 22 May 2022 13:29:15 -0400
Subject: fix(cid/352839): USE_AFTER_FREE in create_user_command #18669

---
 src/nvim/api/private/helpers.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 5d4b84482b..adabb1471e 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1616,7 +1616,7 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
   if (uc_add_command(name.data, name.size, rep, argt, def, flags, compl, compl_arg, compl_luaref,
                      addr_type_arg, luaref, force) != OK) {
     api_set_error(err, kErrorTypeException, "Failed to create user command");
-    goto err;
+    // Do not goto err, since uc_add_command now owns luaref, compl_luaref, and compl_arg
   }
 
   return;
-- 
cgit 


From 9fec6dc9a25b5cf9c9a444ac2bd0728e8af3229e Mon Sep 17 00:00:00 2001
From: dundargoc <33953936+dundargoc@users.noreply.github.com>
Date: Wed, 25 May 2022 20:31:14 +0200
Subject: refactor(uncrustify): set maximum number of consecutive newlines to 2
 (#18695)

---
 src/nvim/api/autocmd.c         | 3 ---
 src/nvim/api/buffer.c          | 5 -----
 src/nvim/api/deprecated.c      | 3 ---
 src/nvim/api/extmark.c         | 6 ------
 src/nvim/api/private/helpers.c | 7 ++-----
 src/nvim/api/private/helpers.h | 2 --
 src/nvim/api/ui.c              | 3 ---
 src/nvim/api/vim.c             | 5 -----
 src/nvim/api/win_config.c      | 2 --
 src/nvim/api/window.c          | 1 -
 10 files changed, 2 insertions(+), 35 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
index 4ee834d75a..19b4119344 100644
--- a/src/nvim/api/autocmd.c
+++ b/src/nvim/api/autocmd.c
@@ -31,7 +31,6 @@
     goto goto_name; \
   }
 
-
 // ID for associating autocmds created via nvim_create_autocmd
 // Used to delete autocmds from nvim_del_autocmd
 static int64_t next_autocmd_id = 1;
@@ -429,7 +428,6 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
   AucmdExecutable aucmd = AUCMD_EXECUTABLE_INIT;
   Callback cb = CALLBACK_NONE;
 
-
   if (!unpack_string_or_array(&event_array, &event, "event", true, err)) {
     goto cleanup;
   }
@@ -550,7 +548,6 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
     }
   });
 
-
 cleanup:
   aucmd_exec_free(&aucmd);
   api_free_array(event_array);
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 9842975d62..4fa8b13c54 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -35,7 +35,6 @@
 # include "api/buffer.c.generated.h"
 #endif
 
-
 /// \defgroup api-buffer
 ///
 /// \brief For more information on buffers, see |buffers|
@@ -51,7 +50,6 @@
 /// You can use |nvim_buf_is_loaded()| or |nvim_buf_line_count()| to check
 /// whether a buffer is loaded.
 
-
 /// Returns the number of lines in the given buffer.
 ///
 /// @param buffer   Buffer handle, or 0 for current buffer
@@ -737,7 +735,6 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
                  (int)new_len - 1, (colnr_T)last_item.size, new_byte,
                  kExtmarkUndo);
 
-
   changed_lines((linenr_T)start_row, 0, (linenr_T)end_row + 1,
                 (long)extra, true);
 
@@ -1039,7 +1036,6 @@ void nvim_buf_del_var(Buffer buffer, String name, Error *err)
   dict_set_var(buf->b_vars, name, NIL, true, false, err);
 }
 
-
 /// Gets a buffer option value
 ///
 /// @param buffer     Buffer handle, or 0 for current buffer
@@ -1336,7 +1332,6 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
   return rv;
 }
 
-
 /// call a function with buffer as temporary current buffer
 ///
 /// This temporarily switches current buffer to "buffer".
diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c
index f968593e47..abaac07755 100644
--- a/src/nvim/api/deprecated.c
+++ b/src/nvim/api/deprecated.c
@@ -78,7 +78,6 @@ void nvim_buf_clear_highlight(Buffer buffer, Integer ns_id, Integer line_start,
   nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end, err);
 }
 
-
 /// Set the virtual text (annotation) for a buffer line.
 ///
 /// @deprecated use nvim_buf_set_extmark to use full virtual text
@@ -137,7 +136,6 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A
     return 0;
   }
 
-
   Decoration *existing = decor_find_virttext(buf, (int)line, ns_id);
 
   if (existing) {
@@ -292,7 +290,6 @@ void buffer_set_line_slice(Buffer buffer, Integer start, Integer end, Boolean in
   nvim_buf_set_lines(0, buffer, start, end, false, replacement, err);
 }
 
-
 /// Sets a buffer-scoped (b:) variable
 ///
 /// @deprecated
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index 94b2af9a6f..9d80a5be5f 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -94,7 +94,6 @@ static 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 rv = ARRAY_DICT_INIT;
@@ -233,7 +232,6 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
     }
   }
 
-
   ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id);
   if (extmark.row < 0) {
     return rv;
@@ -333,7 +331,6 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
     limit = INT64_MAX;
   }
 
-
   bool reverse = false;
 
   int l_row;
@@ -352,7 +349,6 @@ 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);
 
@@ -675,7 +671,6 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
     goto error;
   }
 
-
   OPTION_TO_BOOL(decor.virt_lines_above, virt_lines_above, false);
 
   if (opts->priority.type == kObjectTypeInteger) {
@@ -777,7 +772,6 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
     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);
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index c251a4a25c..3cccbc3cdf 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -407,7 +407,6 @@ void set_option_to(uint64_t channel_id, void *to, int type, String name, Object
   });
 }
 
-
 buf_T *find_buffer_by_handle(Buffer buffer, Error *err)
 {
   if (buffer == 0) {
@@ -1040,8 +1039,8 @@ Object copy_object(Object obj)
   }
 }
 
-void set_option_value_for(char *key, long numval, char *stringval, int opt_flags,
-                          int opt_type, void *from, Error *err)
+void set_option_value_for(char *key, long numval, char *stringval, int opt_flags, int opt_type,
+                          void *from, Error *err)
 {
   switchwin_T switchwin;
   aco_save_T aco;
@@ -1080,7 +1079,6 @@ void set_option_value_for(char *key, long numval, char *stringval, int opt_flags
   try_end(err);
 }
 
-
 static void set_option_value_err(char *key, long numval, char *stringval, int opt_flags, Error *err)
 {
   char *errmsg;
@@ -1562,7 +1560,6 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
     goto err;
   }
 
-
   if (api_object_to_bool(opts->register_, "register", false, err)) {
     argt |= EX_REGSTR;
   } else if (ERROR_SET(err)) {
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index bbbc3de7d5..8423db4970 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -146,7 +146,6 @@ typedef struct {
     code; \
   }
 
-
 #ifdef INCLUDE_GENERATED_DECLARATIONS
 # include "api/private/helpers.h.generated.h"
 # include "keysets.h.generated.h"
@@ -163,5 +162,4 @@ typedef struct {
     current_sctx = save_current_sctx; \
   } while (0);
 
-
 #endif  // NVIM_API_PRIVATE_HELPERS_H
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index 52f76f4650..dc04eedebc 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -196,7 +196,6 @@ void nvim_ui_detach(uint64_t channel_id, Error *err)
   remote_ui_disconnect(channel_id);
 }
 
-
 void nvim_ui_try_resize(uint64_t channel_id, Integer width, Integer height, Error *err)
   FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
 {
@@ -571,7 +570,6 @@ static void remote_ui_highlight_set(UI *ui, int id)
   Array args = ARRAY_DICT_INIT;
   UIData *data = ui->data;
 
-
   if (data->hl_id == id) {
     return;
   }
@@ -794,7 +792,6 @@ static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed)
     }
   }
 
-
   Array my_args = ARRAY_DICT_INIT;
   // Objects are currently single-reference
   // make a copy, but only if necessary
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 8e216c2031..7ca0912f82 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -209,7 +209,6 @@ static void on_redraw_event(void **argv)
   redraw_all_later(NOT_VALID);
 }
 
-
 /// Sends input-keys to Nvim, subject to various quirks controlled by `mode`
 /// flags. This is a blocking call, unlike |nvim_input()|.
 ///
@@ -441,7 +440,6 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, Bool
   return cstr_as_string(ptr);
 }
 
-
 /// Execute Lua code. Parameters (if any) are available as `...` inside the
 /// chunk. The chunk can return a value.
 ///
@@ -570,7 +568,6 @@ ArrayOf(String) nvim__get_runtime(Array pat, Boolean all, Dict(runtime) *opts, E
   return runtime_get_named(is_lua, pat, all);
 }
 
-
 /// Changes the global working directory.
 ///
 /// @param dir      Directory path
@@ -1245,7 +1242,6 @@ static void term_close(void *data)
   channel_decref(chan);
 }
 
-
 /// Send data to channel `id`. For a job, it writes it to the
 /// stdin of the process. For the stdio channel |channel-stdio|,
 /// it writes to Nvim's stdout.  For an internal terminal instance
@@ -2192,7 +2188,6 @@ void nvim__screenshot(String path)
   ui_call_screenshot(path);
 }
 
-
 /// Deletes an uppercase/file named mark. See |mark-motions|.
 ///
 /// @note fails with error if a lowercase or buffer local named mark is used.
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
index 0d8bdbec61..898d95f49a 100644
--- a/src/nvim/api/win_config.c
+++ b/src/nvim/api/win_config.c
@@ -22,7 +22,6 @@
 # include "api/win_config.c.generated.h"
 #endif
 
-
 /// Open a new window.
 ///
 /// Currently this is used to open floating and external windows.
@@ -590,7 +589,6 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
     return false;
   }
 
-
   if (HAS_KEY(config->focusable)) {
     fconfig->focusable = api_object_to_bool(config->focusable, "'focusable' key", false, err);
     if (ERROR_SET(err)) {
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index 3a3a65f812..9f2afc67a6 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -373,7 +373,6 @@ Boolean nvim_win_is_valid(Window window)
   return ret;
 }
 
-
 /// Closes the window and hide the buffer it contains (like |:hide| with a
 /// |window-ID|).
 ///
-- 
cgit 


From 7b952793d5c46e862a9cdec3d6ac4762370296ed Mon Sep 17 00:00:00 2001
From: kylo252 <59826753+kylo252@users.noreply.github.com>
Date: Thu, 26 May 2022 04:49:25 +0200
Subject: refactor: missing parenthesis may cause unexpected problems (#17443)

related vim-8.2.{4402,4639}
---
 src/nvim/api/vim.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 7ca0912f82..dd0b75bbfb 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -1931,13 +1931,13 @@ static void write_msg(String message, bool to_err)
   static char out_line_buf[LINE_BUFFER_SIZE], err_line_buf[LINE_BUFFER_SIZE];
 
 #define PUSH_CHAR(i, pos, line_buf, msg) \
-  if (message.data[i] == NL || pos == LINE_BUFFER_SIZE - 1) { \
-    line_buf[pos] = NUL; \
+  if (message.data[i] == NL || (pos) == LINE_BUFFER_SIZE - 1) { \
+    (line_buf)[pos] = NUL; \
     msg(line_buf); \
-    pos = 0; \
+    (pos) = 0; \
     continue; \
   } \
-  line_buf[pos++] = message.data[i];
+  (line_buf)[(pos)++] = message.data[i];
 
   no_wait_return++;
   for (uint32_t i = 0; i < message.size; i++) {
-- 
cgit 


From 9988d2f214963b3cac70026d6ad4bb058837afd9 Mon Sep 17 00:00:00 2001
From: Famiu Haque 
Date: Sun, 29 May 2022 10:42:00 +0600
Subject: feat(nvim_create_user_command): pass structured modifiers to commands

Adds an `smods` key to `nvim_create_user_command` Lua command callbacks,
which has command modifiers but in a structured format. This removes the
need to manually parse command modifiers. It also reduces friction in
using `nvim_cmd` inside a Lua command callback.
---
 src/nvim/api/vim.c | 2 ++
 1 file changed, 2 insertions(+)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index dd0b75bbfb..5c3c16d6b0 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -2501,6 +2501,8 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
 ///                 - count: (number) Any count supplied ||
 ///                 - reg: (string) The optional register, if specified ||
 ///                 - mods: (string) Command modifiers, if any ||
+///                 - smods: (table) Command modifiers in a structured format. Has the same
+///                 structure as the "mods" key of |nvim_parse_cmd()|.
 /// @param  opts    Optional command attributes. See |command-attributes| for more details. To use
 ///                 boolean attributes (such as |:command-bang| or |:command-bar|) set the value to
 ///                 "true". In addition to the string options listed in |:command-complete|, the
-- 
cgit 


From 1f63052b682a6019d7f092553747272195fbfffd Mon Sep 17 00:00:00 2001
From: bfredl 
Date: Mon, 30 May 2022 00:59:06 +0200
Subject: refactor(api): use hashy hash for looking up api method and event
 names

This avoids generating khash tables at runtime, and is consistent with
how evalfuncs lookup work.
---
 src/nvim/api/private/dispatch.c | 33 +++++++++------------------------
 src/nvim/api/private/dispatch.h |  1 +
 2 files changed, 10 insertions(+), 24 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c
index ba2e560d63..3da2c2cde4 100644
--- a/src/nvim/api/private/dispatch.c
+++ b/src/nvim/api/private/dispatch.c
@@ -32,37 +32,22 @@
 #include "nvim/api/window.h"
 #include "nvim/ui_client.h"
 
-static Map(String, MsgpackRpcRequestHandler) methods = MAP_INIT;
-
-static void msgpack_rpc_add_method_handler(String method, MsgpackRpcRequestHandler handler)
-{
-  map_put(String, MsgpackRpcRequestHandler)(&methods, method, handler);
-}
-
-void msgpack_rpc_add_redraw(void)
-{
-  msgpack_rpc_add_method_handler(STATIC_CSTR_AS_STRING("redraw"),
-                                 (MsgpackRpcRequestHandler) { .fn = ui_client_handle_redraw,
-                                                              .fast = true });
-}
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/private/dispatch_wrappers.generated.h"
+#endif
 
 /// @param name API method name
 /// @param name_len name size (includes terminating NUL)
 MsgpackRpcRequestHandler msgpack_rpc_get_handler_for(const char *name, size_t name_len,
                                                      Error *error)
 {
-  String m = { .data = (char *)name, .size = name_len };
-  MsgpackRpcRequestHandler rv =
-    map_get(String, MsgpackRpcRequestHandler)(&methods, m);
+  int hash = msgpack_rpc_get_handler_for_hash(name, name_len);
 
-  if (!rv.fn) {
+  if (hash < 0) {
     api_set_error(error, kErrorTypeException, "Invalid method: %.*s",
-                  m.size > 0 ? (int)m.size : (int)sizeof(""),
-                  m.size > 0 ? m.data : "");
+                  name_len > 0 ? (int)name_len : (int)sizeof(""),
+                  name_len > 0 ? name : "");
+    return (MsgpackRpcRequestHandler){ 0 };
   }
-  return rv;
+  return method_handlers[hash];
 }
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "api/private/dispatch_wrappers.generated.h"
-#endif
diff --git a/src/nvim/api/private/dispatch.h b/src/nvim/api/private/dispatch.h
index bad5a13934..4b7c394944 100644
--- a/src/nvim/api/private/dispatch.h
+++ b/src/nvim/api/private/dispatch.h
@@ -10,6 +10,7 @@ typedef Object (*ApiDispatchWrapper)(uint64_t channel_id,
 /// The rpc_method_handlers table, used in msgpack_rpc_dispatch(), stores
 /// functions of this type.
 typedef struct {
+  const char *name;
   ApiDispatchWrapper fn;
   bool fast;  // Function is safe to be executed immediately while running the
               // uv loop (the loop is run very frequently due to breakcheck).
-- 
cgit 


From d404d68c929a272eb7ccbc3cc370673e005e2a4b Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Tue, 31 May 2022 07:06:34 +0800
Subject: docs: clarify that nvim_strwidth counts control characters as one
 cell (#18802)

---
 src/nvim/api/vim.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 5c3c16d6b0..fc3d0549b9 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -480,7 +480,7 @@ Object nvim_notify(String msg, Integer log_level, Dictionary opts, Error *err)
 }
 
 /// Calculates the number of display cells occupied by `text`.
-///  counts as one cell.
+/// Control characters including  count as one cell.
 ///
 /// @param text       Some text
 /// @param[out] err   Error details, if any
-- 
cgit 


From 46536f53e82967dcac8d030ee3394cdb156f9603 Mon Sep 17 00:00:00 2001
From: Famiu Haque 
Date: Wed, 20 Apr 2022 17:02:18 +0600
Subject: feat: add preview functionality to user commands

Adds a Lua-only `preview` flag to user commands which allows the command to be incrementally previewed like `:substitute` when 'inccommand' is set.
---
 src/nvim/api/keysets.lua       |  1 +
 src/nvim/api/private/helpers.c | 11 ++++++++++-
 src/nvim/api/vim.c             |  1 +
 src/nvim/api/vimscript.c       |  2 +-
 4 files changed, 13 insertions(+), 2 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
index d4882abffe..881a83e606 100644
--- a/src/nvim/api/keysets.lua
+++ b/src/nvim/api/keysets.lua
@@ -53,6 +53,7 @@ return {
     "force";
     "keepscript";
     "nargs";
+    "preview";
     "range";
     "register";
   };
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 3cccbc3cdf..6981ecc455 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1438,6 +1438,7 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
   char *rep = NULL;
   LuaRef luaref = LUA_NOREF;
   LuaRef compl_luaref = LUA_NOREF;
+  LuaRef preview_luaref = LUA_NOREF;
 
   if (!uc_validate_name(name.data)) {
     api_set_error(err, kErrorTypeValidation, "Invalid command name");
@@ -1592,6 +1593,14 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
     goto err;
   }
 
+  if (opts->preview.type == kObjectTypeLuaRef) {
+    argt |= EX_PREVIEW;
+    preview_luaref = api_new_luaref(opts->preview.data.luaref);
+  } else if (HAS_KEY(opts->preview)) {
+    api_set_error(err, kErrorTypeValidation, "Invalid value for 'preview'");
+    goto err;
+  }
+
   switch (command.type) {
   case kObjectTypeLuaRef:
     luaref = api_new_luaref(command.data.luaref);
@@ -1611,7 +1620,7 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
   }
 
   if (uc_add_command(name.data, name.size, rep, argt, def, flags, compl, compl_arg, compl_luaref,
-                     addr_type_arg, luaref, force) != OK) {
+                     preview_luaref, addr_type_arg, luaref, force) != OK) {
     api_set_error(err, kErrorTypeException, "Failed to create user command");
     // Do not goto err, since uc_add_command now owns luaref, compl_luaref, and compl_arg
   }
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 5c3c16d6b0..8555d1bb71 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -2511,6 +2511,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
 ///                 - desc: (string) Used for listing the command when a Lua function is used for
 ///                                  {command}.
 ///                 - force: (boolean, default true) Override any previous definition.
+///                 - preview: (function) Preview callback for 'inccommand' |:command-preview|
 /// @param[out] err Error details, if any.
 void nvim_create_user_command(String name, Object command, Dict(user_command) *opts, Error *err)
   FUNC_API_SINCE(9)
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index e71f1a11ec..99ab247c2a 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -1311,7 +1311,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
     }
 
     WITH_SCRIPT_CONTEXT(channel_id, {
-      execute_cmd(&ea, &cmdinfo);
+      execute_cmd(&ea, &cmdinfo, false);
     });
 
     if (output) {
-- 
cgit 


From 96c494dec345f9a3dd2676048292267552e3f5f8 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Wed, 1 Jun 2022 21:37:01 +0800
Subject: refactor: correct comments and assertions about mapping rhs 
 (#18821)

Also avoid referring to mappings as "keymaps" in commands and docs.

						*map_empty_rhs* *map-empty-rhs*
You can create an empty {rhs} by typing nothing after a single CTRL-V (you
have to type CTRL-V two times).  Unfortunately, you cannot do this in a vimrc
file.
---
 src/nvim/api/private/helpers.c | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 6981ecc455..af4aaf01aa 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -678,11 +678,7 @@ void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mod
     if (rhs.size == 0) {  // assume that the user wants RHS to be a 
       parsed_args.rhs_is_noop = true;
     } else {
-      // the given RHS was nonempty and not a , but was parsed as if it
-      // were empty?
-      assert(false && "Failed to parse nonempty RHS!");
-      api_set_error(err, kErrorTypeValidation, "Parsing of nonempty RHS failed: %s", rhs.data);
-      goto fail_and_free;
+      abort();  // should never happen
     }
   } else if (is_unmap && (parsed_args.rhs_len || parsed_args.rhs_lua != LUA_NOREF)) {
     if (parsed_args.rhs_len) {
-- 
cgit 


From d5f047bee04a42f40425c34061c84b39af846e1f Mon Sep 17 00:00:00 2001
From: bfredl 
Date: Mon, 23 May 2022 19:53:19 +0200
Subject: refactor(api): use a unpacker based on libmpack instead of msgpack-c

Currently this is more or less a straight off reimplementation,
but this allow further optimizations down the line, especially
for avoiding memory allocations of rpc objects.

Current score for "make functionaltest; make oldtest" on a -DEXITFREE build:

is 117 055 352 xfree(ptr != NULL) calls (that's NUMBERWANG!).
---
 src/nvim/api/vim.c | 7 +++++++
 1 file changed, 7 insertions(+)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 228d114376..2320ae62af 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -45,6 +45,7 @@
 #include "nvim/move.h"
 #include "nvim/msgpack_rpc/channel.h"
 #include "nvim/msgpack_rpc/helpers.h"
+#include "nvim/msgpack_rpc/unpacker.h"
 #include "nvim/ops.h"
 #include "nvim/option.h"
 #include "nvim/os/input.h"
@@ -2188,6 +2189,12 @@ void nvim__screenshot(String path)
   ui_call_screenshot(path);
 }
 
+Object nvim__unpack(String str, Error *err)
+  FUNC_API_FAST
+{
+  return unpack(str.data, str.size, err);
+}
+
 /// Deletes an uppercase/file named mark. See |mark-motions|.
 ///
 /// @note fails with error if a lowercase or buffer local named mark is used.
-- 
cgit 


From ff20d40321399fa187bd350f9619cf6418d7eb6e Mon Sep 17 00:00:00 2001
From: dundargoc <33953936+dundargoc@users.noreply.github.com>
Date: Sat, 4 Jun 2022 05:56:36 +0200
Subject: docs: fix typos (#18269)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: zeertzjq 
Co-authored-by: Dan Sully 
Co-authored-by: saher 
Co-authored-by: Stephan Seitz 
Co-authored-by: Benedikt Müller 
Co-authored-by: Andrey Mishchenko 
Co-authored-by: Famiu Haque 
Co-authored-by: Oliver Marriott 
---
 src/nvim/api/vim.c | 1 -
 1 file changed, 1 deletion(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 2320ae62af..9430a37d27 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -157,7 +157,6 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err)
 ///                - reverse: boolean
 ///                - nocombine: boolean
 ///                - link: name of another highlight group to link to, see |:hi-link|.
-///              Additionally, the following keys are recognized:
 ///                - default: Don't override existing definition |:hi-default|
 ///                - ctermfg: Sets foreground of cterm color |highlight-ctermfg|
 ///                - ctermbg: Sets background of cterm color |highlight-ctermbg|
-- 
cgit 


From c84bd9e21fb1e5c55c9c5370b07271a6ae96f19c Mon Sep 17 00:00:00 2001
From: Famiu Haque 
Date: Wed, 8 Jun 2022 09:06:36 +0600
Subject: fix(nvim_create_user_command): make `smods` work with `nvim_cmd`

Closes #18876.
---
 src/nvim/api/vimscript.c | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index 99ab247c2a..4b4404ea09 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -1241,10 +1241,12 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
     }
 
     if (HAS_KEY(mods.verbose)) {
-      if (mods.verbose.type != kObjectTypeInteger || mods.verbose.data.integer <= 0) {
-        VALIDATION_ERROR("'mods.verbose' must be a non-negative Integer");
+      if (mods.verbose.type != kObjectTypeInteger) {
+        VALIDATION_ERROR("'mods.verbose' must be a Integer");
+      } else if (mods.verbose.data.integer >= 0) {
+        // Silently ignore negative integers to allow mods.verbose to be set to -1.
+        cmdinfo.verbose = mods.verbose.data.integer;
       }
-      cmdinfo.verbose = mods.verbose.data.integer;
     }
 
     bool vertical;
@@ -1256,8 +1258,10 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
         VALIDATION_ERROR("'mods.split' must be a String");
       }
 
-      if (STRCMP(mods.split.data.string.data, "aboveleft") == 0
-          || STRCMP(mods.split.data.string.data, "leftabove") == 0) {
+      if (*mods.split.data.string.data == NUL) {
+        // Empty string, do nothing.
+      } else if (STRCMP(mods.split.data.string.data, "aboveleft") == 0
+                 || STRCMP(mods.split.data.string.data, "leftabove") == 0) {
         cmdinfo.cmdmod.split |= WSP_ABOVE;
       } else if (STRCMP(mods.split.data.string.data, "belowright") == 0
                  || STRCMP(mods.split.data.string.data, "rightbelow") == 0) {
-- 
cgit 


From 3da3cfc864e89a2dca6917183915683373c85af8 Mon Sep 17 00:00:00 2001
From: kylo252 <59826753+kylo252@users.noreply.github.com>
Date: Thu, 9 Jun 2022 15:18:56 +0200
Subject: feat(autocmds): retrieve lua callback (#18642)

add a new `callback` field to `nvim_get_autocmds`
---
 src/nvim/api/autocmd.c | 38 +++++++++++++++++++++++++++++++-------
 1 file changed, 31 insertions(+), 7 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
index 19b4119344..3a35e49dc8 100644
--- a/src/nvim/api/autocmd.c
+++ b/src/nvim/api/autocmd.c
@@ -66,7 +66,9 @@ static int64_t next_autocmd_id = 1;
 ///             - group_name (string): the autocommand group name.
 ///             - desc (string): the autocommand description.
 ///             - event (string): the autocommand event.
-///             - command (string): the autocommand command.
+///             - command (string): the autocommand command. Note: this will be empty if a callback is set.
+///             - callback (function|string|nil): Lua function or name of a Vim script function
+///             which is executed when this autocommand is triggered.
 ///             - once (boolean): whether the autocommand is only run once.
 ///             - pattern (string): the autocommand pattern.
 ///             If the autocommand is buffer local |autocmd-buffer-local|:
@@ -280,9 +282,28 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
           PUT(autocmd_info, "desc", CSTR_TO_OBJ(ac->desc));
         }
 
-        PUT(autocmd_info,
-            "command",
-            STRING_OBJ(cstr_as_string(aucmd_exec_to_string(ac, ac->exec))));
+        if (ac->exec.type == CALLABLE_CB) {
+          PUT(autocmd_info, "command", STRING_OBJ(STRING_INIT));
+
+          Callback *cb = &ac->exec.callable.cb;
+          switch (cb->type) {
+          case kCallbackLua:
+            if (nlua_ref_is_function(cb->data.luaref)) {
+              PUT(autocmd_info, "callback", LUAREF_OBJ(api_new_luaref(cb->data.luaref)));
+            }
+            break;
+          case kCallbackFuncref:
+          case kCallbackPartial:
+            PUT(autocmd_info, "callback", STRING_OBJ(cstr_as_string(callback_to_string(cb))));
+            break;
+          default:
+            abort();
+          }
+        } else {
+          PUT(autocmd_info,
+              "command",
+              STRING_OBJ(cstr_as_string(xstrdup(ac->exec.callable.cmd))));
+        }
 
         PUT(autocmd_info,
             "pattern",
@@ -442,7 +463,8 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
     // not do that.
 
     Object *callback = &opts->callback;
-    if (callback->type == kObjectTypeLuaRef) {
+    switch (callback->type) {
+    case kObjectTypeLuaRef:
       if (callback->data.luaref == LUA_NOREF) {
         api_set_error(err,
                       kErrorTypeValidation,
@@ -459,10 +481,12 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
 
       cb.type = kCallbackLua;
       cb.data.luaref = api_new_luaref(callback->data.luaref);
-    } else if (callback->type == kObjectTypeString) {
+      break;
+    case kObjectTypeString:
       cb.type = kCallbackFuncref;
       cb.data.funcref = string_to_cstr(callback->data.string);
-    } else {
+      break;
+    default:
       api_set_error(err,
                     kErrorTypeException,
                     "'callback' must be a lua function or name of vim function");
-- 
cgit 


From a732c253b71f89702285d5ec6fd7803045f6add9 Mon Sep 17 00:00:00 2001
From: Dundar Goc 
Date: Sat, 7 May 2022 12:53:37 +0200
Subject: refactor: change type of linenr_T from long to int32_t

The size of long varies depending on architecture, in contrast to the
MAXLNUM constant which sets the maximum allowable number of lines to
2^32-1. This discrepancy may lead to hard to detect bugs, for example
https://github.com/neovim/neovim/issues/18454. Setting linenr_T to a
fix maximum size of 2^32-1 will prevent this type of errors in the
future.

Also change the variables `amount` and `amount_after` to be linenr_T
since they're referring to "the line number difference" between two
texts.
---
 src/nvim/api/buffer.c          | 17 ++++++++---------
 src/nvim/api/private/helpers.c |  7 ++++---
 src/nvim/api/vimscript.c       |  4 ++--
 src/nvim/api/win_config.c      |  2 +-
 4 files changed, 15 insertions(+), 15 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 4fa8b13c54..536be1d832 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -420,7 +420,7 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
     goto end;
   }
 
-  bcount_t deleted_bytes = get_region_bytecount(curbuf, start, end, 0, 0);
+  bcount_t deleted_bytes = get_region_bytecount(curbuf, (linenr_T)start, (linenr_T)end, 0, 0);
 
   // If the size of the range is reducing (ie, new_len < old_len) we
   // need to delete some old_len. We do this at the start, by
@@ -490,14 +490,14 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
   mark_adjust((linenr_T)start,
               (linenr_T)(end - 1),
               MAXLNUM,
-              (long)extra,
+              (linenr_T)extra,
               kExtmarkNOOP);
 
   extmark_splice(curbuf, (int)start - 1, 0, (int)(end - start), 0,
                  deleted_bytes, (int)new_len, 0, inserted_bytes,
                  kExtmarkUndo);
 
-  changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra, true);
+  changed_lines((linenr_T)start, 0, (linenr_T)end, (linenr_T)extra, true);
   fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra);
 
 end:
@@ -564,13 +564,13 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
     return;
   }
 
-  char *str_at_start = (char *)ml_get_buf(buf, start_row, false);
+  char *str_at_start = (char *)ml_get_buf(buf, (linenr_T)start_row, false);
   if (start_col < 0 || (size_t)start_col > strlen(str_at_start)) {
     api_set_error(err, kErrorTypeValidation, "start_col out of bounds");
     return;
   }
 
-  char *str_at_end = (char *)ml_get_buf(buf, end_row, false);
+  char *str_at_end = (char *)ml_get_buf(buf, (linenr_T)end_row, false);
   size_t len_at_end = strlen(str_at_end);
   if (end_col < 0 || (size_t)end_col > len_at_end) {
     api_set_error(err, kErrorTypeValidation, "end_col out of bounds");
@@ -600,7 +600,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
     for (int64_t i = 1; i < end_row - start_row; i++) {
       int64_t lnum = start_row + i;
 
-      const char *bufline = (char *)ml_get_buf(buf, lnum, false);
+      const char *bufline = (char *)ml_get_buf(buf, (linenr_T)lnum, false);
       old_byte += (bcount_t)(strlen(bufline)) + 1;
     }
     old_byte += (bcount_t)end_col + 1;
@@ -725,7 +725,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
   mark_adjust((linenr_T)start_row,
               (linenr_T)end_row,
               MAXLNUM,
-              (long)extra,
+              (linenr_T)extra,
               kExtmarkNOOP);
 
   colnr_T col_extent = (colnr_T)(end_col
@@ -735,8 +735,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
                  (int)new_len - 1, (colnr_T)last_item.size, new_byte,
                  kExtmarkUndo);
 
-  changed_lines((linenr_T)start_row, 0, (linenr_T)end_row + 1,
-                (long)extra, true);
+  changed_lines((linenr_T)start_row, 0, (linenr_T)end_row + 1, (linenr_T)extra, true);
 
   // adjust cursor like an extmark ( i e it was inside last_part_len)
   if (curwin->w_cursor.lnum == end_row && curwin->w_cursor.col > end_col) {
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index af4aaf01aa..1ddaf63743 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1397,7 +1397,8 @@ bool set_mark(buf_T *buf, String name, Integer line, Integer col, Error *err)
       return res;
     }
   }
-  pos_T pos = { line, (int)col, (int)col };
+  assert(INT32_MIN <= line && line <= INT32_MAX);
+  pos_T pos = { (linenr_T)line, (int)col, (int)col };
   res = setmark_pos(*name.data, &pos, buf->handle);
   if (!res) {
     if (deleting) {
@@ -1773,9 +1774,9 @@ void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdinfo, cha
   // Command range / count.
   if (eap->argt & EX_RANGE) {
     if (eap->addr_count == 1) {
-      kv_printf(cmdline, "%ld", eap->line2);
+      kv_printf(cmdline, "%" PRIdLINENR, eap->line2);
     } else if (eap->addr_count > 1) {
-      kv_printf(cmdline, "%ld,%ld", eap->line1, eap->line2);
+      kv_printf(cmdline, "%" PRIdLINENR ",%" PRIdLINENR, eap->line1, eap->line2);
       eap->addr_count = 2;  // Make sure address count is not greater than 2
     }
   }
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index 4b4404ea09..6d0b9c7d99 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -1155,8 +1155,8 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
     }
 
     if (range.size > 0) {
-      ea.line1 = range.items[0].data.integer;
-      ea.line2 = range.items[range.size - 1].data.integer;
+      ea.line1 = (linenr_T)range.items[0].data.integer;
+      ea.line2 = (linenr_T)range.items[range.size - 1].data.integer;
     }
 
     if (invalid_range(&ea) != NULL) {
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
index 898d95f49a..969643eeef 100644
--- a/src/nvim/api/win_config.c
+++ b/src/nvim/api/win_config.c
@@ -325,7 +325,7 @@ static bool parse_float_bufpos(Array bufpos, lpos_T *out)
       || bufpos.items[1].type != kObjectTypeInteger) {
     return false;
   }
-  out->lnum = bufpos.items[0].data.integer;
+  out->lnum = (linenr_T)bufpos.items[0].data.integer;
   out->col = (colnr_T)bufpos.items[1].data.integer;
   return true;
 }
-- 
cgit 


From e92fcdbab204f49c542ba4cf330aba1258fb2968 Mon Sep 17 00:00:00 2001
From: bfredl 
Date: Thu, 5 May 2022 12:33:35 +0200
Subject: feat(api): nvim__get_runtime do_source

---
 src/nvim/api/keysets.lua |  1 +
 src/nvim/api/vim.c       | 17 ++++++++++++++++-
 2 files changed, 17 insertions(+), 1 deletion(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
index 881a83e606..70e91dd844 100644
--- a/src/nvim/api/keysets.lua
+++ b/src/nvim/api/keysets.lua
@@ -75,6 +75,7 @@ return {
   };
   runtime = {
     "is_lua";
+    "do_source";
   };
   eval_statusline = {
     "winid";
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 9430a37d27..1e44250ec3 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -562,10 +562,25 @@ ArrayOf(String) nvim__get_runtime(Array pat, Boolean all, Dict(runtime) *opts, E
   FUNC_API_FAST
 {
   bool is_lua = api_object_to_bool(opts->is_lua, "is_lua", false, err);
+  bool source = api_object_to_bool(opts->do_source, "do_source", false, err);
+  if (source && !nlua_is_deferred_safe()) {
+    api_set_error(err, kErrorTypeValidation, "'do_source' cannot be used in fast callback");
+  }
+
   if (ERROR_SET(err)) {
     return (Array)ARRAY_DICT_INIT;
   }
-  return runtime_get_named(is_lua, pat, all);
+
+  ArrayOf(String) res = runtime_get_named(is_lua, pat, all);
+
+  if (source) {
+    for (size_t i = 0; i < res.size; i++) {
+      String name = res.items[i].data.string;
+      (void)do_source(name.data, false, DOSO_NONE);
+    }
+  }
+
+  return res;
 }
 
 /// Changes the global working directory.
-- 
cgit 


From f4121c52b95d4b79c0de95412c8447614a2f8960 Mon Sep 17 00:00:00 2001
From: bfredl 
Date: Fri, 10 Jun 2022 11:23:17 +0200
Subject: fix(messages): add color when showing nvim_echo in :messages history

---
 src/nvim/api/private/helpers.c |  4 ++--
 src/nvim/api/vim.c             | 21 +++++----------------
 2 files changed, 7 insertions(+), 18 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 1ddaf63743..bdbbe9aa88 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1344,8 +1344,8 @@ HlMessage parse_hl_msg(Array chunks, Error *err)
   return hl_msg;
 
 free_exit:
-  clear_hl_msg(&hl_msg);
-  return hl_msg;
+  hl_msg_free(hl_msg);
+  return (HlMessage)KV_INITIAL_VALUE;
 }
 
 bool api_dict_to_keydict(void *rv, field_hash hashy, Dictionary dict, Error *err)
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 1e44250ec3..5f7162cdd6 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -942,26 +942,15 @@ void nvim_echo(Array chunks, Boolean history, Dictionary opts, Error *err)
     goto error;
   }
 
-  no_wait_return++;
-  msg_start();
-  msg_clr_eos();
-  bool need_clear = false;
-  for (uint32_t i = 0; i < kv_size(hl_msg); i++) {
-    HlMessageChunk chunk = kv_A(hl_msg, i);
-    msg_multiline_attr((const char *)chunk.text.data, chunk.attr,
-                       true, &need_clear);
-  }
+  msg_multiattr(hl_msg, history ? "echomsg" : "echo", history);
+
   if (history) {
-    msg_ext_set_kind("echomsg");
-    add_hl_msg_hist(hl_msg);
-  } else {
-    msg_ext_set_kind("echo");
+    // history takes ownership
+    return;
   }
-  no_wait_return--;
-  msg_end();
 
 error:
-  clear_hl_msg(&hl_msg);
+  hl_msg_free(hl_msg);
 }
 
 /// Writes a message to the Vim output buffer. Does not append "\n", the
-- 
cgit 


From 4a275e3291d3eade38490801d9842640df3de202 Mon Sep 17 00:00:00 2001
From: bfredl 
Date: Sun, 12 Jun 2022 00:37:39 +0200
Subject: refactor(api): move option code to own file

---
 src/nvim/api/buffer.c           |  38 ---
 src/nvim/api/options.c          | 509 ++++++++++++++++++++++++++++++++++++++++
 src/nvim/api/options.h          |   9 +
 src/nvim/api/private/dispatch.c |   1 +
 src/nvim/api/private/helpers.c  | 200 ----------------
 src/nvim/api/vim.c              | 214 -----------------
 src/nvim/api/window.c           |  38 ---
 7 files changed, 519 insertions(+), 490 deletions(-)
 create mode 100644 src/nvim/api/options.c
 create mode 100644 src/nvim/api/options.h

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 536be1d832..49a33d6039 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -1035,44 +1035,6 @@ void nvim_buf_del_var(Buffer buffer, String name, Error *err)
   dict_set_var(buf->b_vars, name, NIL, true, false, err);
 }
 
-/// Gets a buffer option value
-///
-/// @param buffer     Buffer handle, or 0 for current buffer
-/// @param name       Option name
-/// @param[out] err   Error details, if any
-/// @return Option value
-Object nvim_buf_get_option(Buffer buffer, String name, Error *err)
-  FUNC_API_SINCE(1)
-{
-  buf_T *buf = find_buffer_by_handle(buffer, err);
-
-  if (!buf) {
-    return (Object)OBJECT_INIT;
-  }
-
-  return get_option_from(buf, SREQ_BUF, name, err);
-}
-
-/// Sets a buffer option value. Passing 'nil' as value deletes the option (only
-/// works if there's a global fallback)
-///
-/// @param channel_id
-/// @param buffer     Buffer handle, or 0 for current buffer
-/// @param name       Option name
-/// @param value      Option value
-/// @param[out] err   Error details, if any
-void nvim_buf_set_option(uint64_t channel_id, Buffer buffer, String name, Object value, Error *err)
-  FUNC_API_SINCE(1)
-{
-  buf_T *buf = find_buffer_by_handle(buffer, err);
-
-  if (!buf) {
-    return;
-  }
-
-  set_option_to(channel_id, buf, SREQ_BUF, name, value, err);
-}
-
 /// Gets the full file name for the buffer
 ///
 /// @param buffer     Buffer handle, or 0 for current buffer
diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c
new file mode 100644
index 0000000000..61d4becd32
--- /dev/null
+++ b/src/nvim/api/options.c
@@ -0,0 +1,509 @@
+// 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 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "nvim/api/options.h"
+#include "nvim/api/private/converter.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/autocmd.h"
+#include "nvim/buffer.h"
+#include "nvim/option.h"
+#include "nvim/option_defs.h"
+#include "nvim/window.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/options.c.generated.h"
+#endif
+
+/// Gets the value of an option. The behavior of this function matches that of
+/// |:set|: the local value of an option is returned if it exists; otherwise,
+/// the global value is returned. Local values always correspond to the current
+/// buffer or window. To get a buffer-local or window-local option for a
+/// specific buffer or window, use |nvim_buf_get_option()| or
+/// |nvim_win_get_option()|.
+///
+/// @param name      Option name
+/// @param opts      Optional parameters
+///                  - scope: One of 'global' or 'local'. Analogous to
+///                  |:setglobal| and |:setlocal|, respectively.
+/// @param[out] err  Error details, if any
+/// @return          Option value
+Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
+  FUNC_API_SINCE(9)
+{
+  Object rv = OBJECT_INIT;
+
+  int scope = 0;
+  if (opts->scope.type == kObjectTypeString) {
+    if (!strcmp(opts->scope.data.string.data, "local")) {
+      scope = OPT_LOCAL;
+    } else if (!strcmp(opts->scope.data.string.data, "global")) {
+      scope = OPT_GLOBAL;
+    } else {
+      api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'");
+      goto end;
+    }
+  } else if (HAS_KEY(opts->scope)) {
+    api_set_error(err, kErrorTypeValidation, "invalid value for key: scope");
+    goto end;
+  }
+
+  long numval = 0;
+  char *stringval = NULL;
+  switch (get_option_value(name.data, &numval, &stringval, scope)) {
+  case 0:
+    rv = STRING_OBJ(cstr_as_string(stringval));
+    break;
+  case 1:
+    rv = INTEGER_OBJ(numval);
+    break;
+  case 2:
+    switch (numval) {
+    case 0:
+    case 1:
+      rv = BOOLEAN_OBJ(numval);
+      break;
+    default:
+      // Boolean options that return something other than 0 or 1 should return nil. Currently this
+      // only applies to 'autoread' which uses -1 as a local value to indicate "unset"
+      rv = NIL;
+      break;
+    }
+    break;
+  default:
+    api_set_error(err, kErrorTypeValidation, "unknown option '%s'", name.data);
+    goto end;
+  }
+
+end:
+  return rv;
+}
+
+/// Sets the value of an option. The behavior of this function matches that of
+/// |:set|: for global-local options, both the global and local value are set
+/// unless otherwise specified with {scope}.
+///
+/// Note the options {win} and {buf} cannot be used together.
+///
+/// @param name      Option name
+/// @param value     New option value
+/// @param opts      Optional parameters
+///                  - scope: One of 'global' or 'local'. Analogous to
+///                  |:setglobal| and |:setlocal|, respectively.
+///                  - win: |window-ID|. Used for setting window local option.
+///                  - buf: Buffer number. Used for setting buffer local option.
+/// @param[out] err  Error details, if any
+void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error *err)
+  FUNC_API_SINCE(9)
+{
+  int scope = 0;
+  if (opts->scope.type == kObjectTypeString) {
+    if (!strcmp(opts->scope.data.string.data, "local")) {
+      scope = OPT_LOCAL;
+    } else if (!strcmp(opts->scope.data.string.data, "global")) {
+      scope = OPT_GLOBAL;
+    } else {
+      api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'");
+      return;
+    }
+  } else if (HAS_KEY(opts->scope)) {
+    api_set_error(err, kErrorTypeValidation, "invalid value for key: scope");
+    return;
+  }
+
+  int opt_type = SREQ_GLOBAL;
+  void *to = NULL;
+
+  if (opts->win.type == kObjectTypeInteger) {
+    opt_type = SREQ_WIN;
+    to = find_window_by_handle((int)opts->win.data.integer, err);
+  } else if (HAS_KEY(opts->win)) {
+    api_set_error(err, kErrorTypeValidation, "invalid value for key: win");
+    return;
+  }
+
+  if (opts->buf.type == kObjectTypeInteger) {
+    scope = OPT_LOCAL;
+    opt_type = SREQ_BUF;
+    to = find_buffer_by_handle((int)opts->buf.data.integer, err);
+  } else if (HAS_KEY(opts->buf)) {
+    api_set_error(err, kErrorTypeValidation, "invalid value for key: buf");
+    return;
+  }
+
+  if (HAS_KEY(opts->scope) && HAS_KEY(opts->buf)) {
+    api_set_error(err, kErrorTypeValidation, "scope and buf cannot be used together");
+    return;
+  }
+
+  if (HAS_KEY(opts->win) && HAS_KEY(opts->buf)) {
+    api_set_error(err, kErrorTypeValidation, "buf and win cannot be used together");
+    return;
+  }
+
+  long numval = 0;
+  char *stringval = NULL;
+
+  switch (value.type) {
+  case kObjectTypeInteger:
+    numval = value.data.integer;
+    break;
+  case kObjectTypeBoolean:
+    numval = value.data.boolean ? 1 : 0;
+    break;
+  case kObjectTypeString:
+    stringval = value.data.string.data;
+    break;
+  case kObjectTypeNil:
+    scope |= OPT_CLEAR;
+    break;
+  default:
+    api_set_error(err, kErrorTypeValidation, "invalid value for option");
+    return;
+  }
+
+  set_option_value_for(name.data, numval, stringval, scope, opt_type, to, err);
+}
+
+/// Gets the option information for all options.
+///
+/// The dictionary has the full option names as keys and option metadata
+/// dictionaries as detailed at |nvim_get_option_info|.
+///
+/// @return dictionary of all options
+Dictionary nvim_get_all_options_info(Error *err)
+  FUNC_API_SINCE(7)
+{
+  return get_all_vimoptions();
+}
+
+/// Gets the option information for one option
+///
+/// Resulting dictionary has keys:
+///     - name: Name of the option (like 'filetype')
+///     - shortname: Shortened name of the option (like 'ft')
+///     - type: type of option ("string", "number" or "boolean")
+///     - default: The default value for the option
+///     - was_set: Whether the option was set.
+///
+///     - last_set_sid: Last set script id (if any)
+///     - last_set_linenr: line number where option was set
+///     - last_set_chan: Channel where option was set (0 for local)
+///
+///     - scope: one of "global", "win", or "buf"
+///     - global_local: whether win or buf option has a global value
+///
+///     - commalist: List of comma separated values
+///     - flaglist: List of single char flags
+///
+///
+/// @param          name Option name
+/// @param[out] err Error details, if any
+/// @return         Option Information
+Dictionary nvim_get_option_info(String name, Error *err)
+  FUNC_API_SINCE(7)
+{
+  return get_vimoption(name, err);
+}
+/// Sets the global value of an option.
+///
+/// @param channel_id
+/// @param name     Option name
+/// @param value    New option value
+/// @param[out] err Error details, if any
+void nvim_set_option(uint64_t channel_id, String name, Object value, Error *err)
+  FUNC_API_SINCE(1)
+{
+  set_option_to(channel_id, NULL, SREQ_GLOBAL, name, value, err);
+}
+
+/// Gets the global value of an option.
+///
+/// @param name     Option name
+/// @param[out] err Error details, if any
+/// @return         Option value (global)
+Object nvim_get_option(String name, Error *err)
+  FUNC_API_SINCE(1)
+{
+  return get_option_from(NULL, SREQ_GLOBAL, name, err);
+}
+
+/// Gets a buffer option value
+///
+/// @param buffer     Buffer handle, or 0 for current buffer
+/// @param name       Option name
+/// @param[out] err   Error details, if any
+/// @return Option value
+Object nvim_buf_get_option(Buffer buffer, String name, Error *err)
+  FUNC_API_SINCE(1)
+{
+  buf_T *buf = find_buffer_by_handle(buffer, err);
+
+  if (!buf) {
+    return (Object)OBJECT_INIT;
+  }
+
+  return get_option_from(buf, SREQ_BUF, name, err);
+}
+
+/// Sets a buffer option value. Passing 'nil' as value deletes the option (only
+/// works if there's a global fallback)
+///
+/// @param channel_id
+/// @param buffer     Buffer handle, or 0 for current buffer
+/// @param name       Option name
+/// @param value      Option value
+/// @param[out] err   Error details, if any
+void nvim_buf_set_option(uint64_t channel_id, Buffer buffer, String name, Object value, Error *err)
+  FUNC_API_SINCE(1)
+{
+  buf_T *buf = find_buffer_by_handle(buffer, err);
+
+  if (!buf) {
+    return;
+  }
+
+  set_option_to(channel_id, buf, SREQ_BUF, name, value, err);
+}
+
+/// Gets a window option value
+///
+/// @param window   Window handle, or 0 for current window
+/// @param name     Option name
+/// @param[out] err Error details, if any
+/// @return Option value
+Object nvim_win_get_option(Window window, String name, Error *err)
+  FUNC_API_SINCE(1)
+{
+  win_T *win = find_window_by_handle(window, err);
+
+  if (!win) {
+    return (Object)OBJECT_INIT;
+  }
+
+  return get_option_from(win, SREQ_WIN, name, err);
+}
+
+/// Sets a window option value. Passing 'nil' as value deletes the option(only
+/// works if there's a global fallback)
+///
+/// @param channel_id
+/// @param window   Window handle, or 0 for current window
+/// @param name     Option name
+/// @param value    Option value
+/// @param[out] err Error details, if any
+void nvim_win_set_option(uint64_t channel_id, Window window, String name, Object value, Error *err)
+  FUNC_API_SINCE(1)
+{
+  win_T *win = find_window_by_handle(window, err);
+
+  if (!win) {
+    return;
+  }
+
+  set_option_to(channel_id, win, SREQ_WIN, name, value, err);
+}
+
+/// Gets the value of a global or local(buffer, window) option.
+///
+/// @param from If `type` is `SREQ_WIN` or `SREQ_BUF`, this must be a pointer
+///        to the window or buffer.
+/// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF`
+/// @param name The option name
+/// @param[out] err Details of an error that may have occurred
+/// @return the option value
+Object get_option_from(void *from, int type, String name, Error *err)
+{
+  Object rv = OBJECT_INIT;
+
+  if (name.size == 0) {
+    api_set_error(err, kErrorTypeValidation, "Empty option name");
+    return rv;
+  }
+
+  // Return values
+  int64_t numval;
+  char *stringval = NULL;
+  int flags = get_option_value_strict(name.data, &numval, &stringval,
+                                      type, from);
+
+  if (!flags) {
+    api_set_error(err, kErrorTypeValidation, "Invalid option name: '%s'",
+                  name.data);
+    return rv;
+  }
+
+  if (flags & SOPT_BOOL) {
+    rv.type = kObjectTypeBoolean;
+    rv.data.boolean = numval ? true : false;
+  } else if (flags & SOPT_NUM) {
+    rv.type = kObjectTypeInteger;
+    rv.data.integer = numval;
+  } else if (flags & SOPT_STRING) {
+    if (stringval) {
+      rv.type = kObjectTypeString;
+      rv.data.string.data = stringval;
+      rv.data.string.size = strlen(stringval);
+    } else {
+      api_set_error(err, kErrorTypeException,
+                    "Failed to get value for option '%s'",
+                    name.data);
+    }
+  } else {
+    api_set_error(err,
+                  kErrorTypeException,
+                  "Unknown type for option '%s'",
+                  name.data);
+  }
+
+  return rv;
+}
+
+/// Sets the value of a global or local(buffer, window) option.
+///
+/// @param to If `type` is `SREQ_WIN` or `SREQ_BUF`, this must be a pointer
+///        to the window or buffer.
+/// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF`
+/// @param name The option name
+/// @param[out] err Details of an error that may have occurred
+void set_option_to(uint64_t channel_id, void *to, int type, String name, Object value, Error *err)
+{
+  if (name.size == 0) {
+    api_set_error(err, kErrorTypeValidation, "Empty option name");
+    return;
+  }
+
+  int flags = get_option_value_strict(name.data, NULL, NULL, type, to);
+
+  if (flags == 0) {
+    api_set_error(err, kErrorTypeValidation, "Invalid option name '%s'",
+                  name.data);
+    return;
+  }
+
+  if (value.type == kObjectTypeNil) {
+    if (type == SREQ_GLOBAL) {
+      api_set_error(err, kErrorTypeException, "Cannot unset option '%s'",
+                    name.data);
+      return;
+    } else if (!(flags & SOPT_GLOBAL)) {
+      api_set_error(err,
+                    kErrorTypeException,
+                    "Cannot unset option '%s' "
+                    "because it doesn't have a global value",
+                    name.data);
+      return;
+    } else {
+      unset_global_local_option(name.data, to);
+      return;
+    }
+  }
+
+  int numval = 0;
+  char *stringval = NULL;
+
+  if (flags & SOPT_BOOL) {
+    if (value.type != kObjectTypeBoolean) {
+      api_set_error(err,
+                    kErrorTypeValidation,
+                    "Option '%s' requires a Boolean value",
+                    name.data);
+      return;
+    }
+
+    numval = value.data.boolean;
+  } else if (flags & SOPT_NUM) {
+    if (value.type != kObjectTypeInteger) {
+      api_set_error(err, kErrorTypeValidation,
+                    "Option '%s' requires an integer value",
+                    name.data);
+      return;
+    }
+
+    if (value.data.integer > INT_MAX || value.data.integer < INT_MIN) {
+      api_set_error(err, kErrorTypeValidation,
+                    "Value for option '%s' is out of range",
+                    name.data);
+      return;
+    }
+
+    numval = (int)value.data.integer;
+  } else {
+    if (value.type != kObjectTypeString) {
+      api_set_error(err, kErrorTypeValidation,
+                    "Option '%s' requires a string value",
+                    name.data);
+      return;
+    }
+
+    stringval = value.data.string.data;
+  }
+
+  WITH_SCRIPT_CONTEXT(channel_id, {
+    const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL))
+                          ? 0 : (type == SREQ_GLOBAL)
+                                ? OPT_GLOBAL : OPT_LOCAL;
+
+    set_option_value_for(name.data, numval, stringval,
+                         opt_flags, type, to, err);
+  });
+}
+
+void set_option_value_for(char *key, long numval, char *stringval, int opt_flags, int opt_type,
+                          void *from, Error *err)
+{
+  switchwin_T switchwin;
+  aco_save_T aco;
+
+  try_start();
+  switch (opt_type) {
+  case SREQ_WIN:
+    if (switch_win_noblock(&switchwin, (win_T *)from, win_find_tabpage((win_T *)from), true)
+        == FAIL) {
+      restore_win_noblock(&switchwin, true);
+      if (try_end(err)) {
+        return;
+      }
+      api_set_error(err,
+                    kErrorTypeException,
+                    "Problem while switching windows");
+      return;
+    }
+    set_option_value_err(key, numval, stringval, opt_flags, err);
+    restore_win_noblock(&switchwin, true);
+    break;
+  case SREQ_BUF:
+    aucmd_prepbuf(&aco, (buf_T *)from);
+    set_option_value_err(key, numval, stringval, opt_flags, err);
+    aucmd_restbuf(&aco);
+    break;
+  case SREQ_GLOBAL:
+    set_option_value_err(key, numval, stringval, opt_flags, err);
+    break;
+  }
+
+  if (ERROR_SET(err)) {
+    return;
+  }
+
+  try_end(err);
+}
+
+static void set_option_value_err(char *key, long numval, char *stringval, int opt_flags, Error *err)
+{
+  char *errmsg;
+
+  if ((errmsg = set_option_value(key, numval, stringval, opt_flags))) {
+    if (try_end(err)) {
+      return;
+    }
+
+    api_set_error(err, kErrorTypeException, "%s", errmsg);
+  }
+}
diff --git a/src/nvim/api/options.h b/src/nvim/api/options.h
new file mode 100644
index 0000000000..efbfec3a6c
--- /dev/null
+++ b/src/nvim/api/options.h
@@ -0,0 +1,9 @@
+#ifndef NVIM_API_OPTIONS_H
+#define NVIM_API_OPTIONS_H
+
+#include "nvim/api/private/defs.h"
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/options.h.generated.h"
+#endif
+
+#endif  // NVIM_API_OPTIONS_H
diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c
index 3da2c2cde4..d2ffd8879a 100644
--- a/src/nvim/api/private/dispatch.c
+++ b/src/nvim/api/private/dispatch.c
@@ -24,6 +24,7 @@
 #include "nvim/api/autocmd.h"
 #include "nvim/api/buffer.h"
 #include "nvim/api/extmark.h"
+#include "nvim/api/options.h"
 #include "nvim/api/tabpage.h"
 #include "nvim/api/ui.h"
 #include "nvim/api/vim.h"
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index bdbbe9aa88..13e3f70ce1 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -32,8 +32,6 @@
 #include "nvim/memline.h"
 #include "nvim/memory.h"
 #include "nvim/msgpack_rpc/helpers.h"
-#include "nvim/option.h"
-#include "nvim/option_defs.h"
 #include "nvim/ui.h"
 #include "nvim/version.h"
 #include "nvim/vim.h"
@@ -262,151 +260,6 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva
   return rv;
 }
 
-/// Gets the value of a global or local(buffer, window) option.
-///
-/// @param from If `type` is `SREQ_WIN` or `SREQ_BUF`, this must be a pointer
-///        to the window or buffer.
-/// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF`
-/// @param name The option name
-/// @param[out] err Details of an error that may have occurred
-/// @return the option value
-Object get_option_from(void *from, int type, String name, Error *err)
-{
-  Object rv = OBJECT_INIT;
-
-  if (name.size == 0) {
-    api_set_error(err, kErrorTypeValidation, "Empty option name");
-    return rv;
-  }
-
-  // Return values
-  int64_t numval;
-  char *stringval = NULL;
-  int flags = get_option_value_strict(name.data, &numval, &stringval,
-                                      type, from);
-
-  if (!flags) {
-    api_set_error(err, kErrorTypeValidation, "Invalid option name: '%s'",
-                  name.data);
-    return rv;
-  }
-
-  if (flags & SOPT_BOOL) {
-    rv.type = kObjectTypeBoolean;
-    rv.data.boolean = numval ? true : false;
-  } else if (flags & SOPT_NUM) {
-    rv.type = kObjectTypeInteger;
-    rv.data.integer = numval;
-  } else if (flags & SOPT_STRING) {
-    if (stringval) {
-      rv.type = kObjectTypeString;
-      rv.data.string.data = stringval;
-      rv.data.string.size = strlen(stringval);
-    } else {
-      api_set_error(err, kErrorTypeException,
-                    "Failed to get value for option '%s'",
-                    name.data);
-    }
-  } else {
-    api_set_error(err,
-                  kErrorTypeException,
-                  "Unknown type for option '%s'",
-                  name.data);
-  }
-
-  return rv;
-}
-
-/// Sets the value of a global or local(buffer, window) option.
-///
-/// @param to If `type` is `SREQ_WIN` or `SREQ_BUF`, this must be a pointer
-///        to the window or buffer.
-/// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF`
-/// @param name The option name
-/// @param[out] err Details of an error that may have occurred
-void set_option_to(uint64_t channel_id, void *to, int type, String name, Object value, Error *err)
-{
-  if (name.size == 0) {
-    api_set_error(err, kErrorTypeValidation, "Empty option name");
-    return;
-  }
-
-  int flags = get_option_value_strict(name.data, NULL, NULL, type, to);
-
-  if (flags == 0) {
-    api_set_error(err, kErrorTypeValidation, "Invalid option name '%s'",
-                  name.data);
-    return;
-  }
-
-  if (value.type == kObjectTypeNil) {
-    if (type == SREQ_GLOBAL) {
-      api_set_error(err, kErrorTypeException, "Cannot unset option '%s'",
-                    name.data);
-      return;
-    } else if (!(flags & SOPT_GLOBAL)) {
-      api_set_error(err,
-                    kErrorTypeException,
-                    "Cannot unset option '%s' "
-                    "because it doesn't have a global value",
-                    name.data);
-      return;
-    } else {
-      unset_global_local_option(name.data, to);
-      return;
-    }
-  }
-
-  int numval = 0;
-  char *stringval = NULL;
-
-  if (flags & SOPT_BOOL) {
-    if (value.type != kObjectTypeBoolean) {
-      api_set_error(err,
-                    kErrorTypeValidation,
-                    "Option '%s' requires a Boolean value",
-                    name.data);
-      return;
-    }
-
-    numval = value.data.boolean;
-  } else if (flags & SOPT_NUM) {
-    if (value.type != kObjectTypeInteger) {
-      api_set_error(err, kErrorTypeValidation,
-                    "Option '%s' requires an integer value",
-                    name.data);
-      return;
-    }
-
-    if (value.data.integer > INT_MAX || value.data.integer < INT_MIN) {
-      api_set_error(err, kErrorTypeValidation,
-                    "Value for option '%s' is out of range",
-                    name.data);
-      return;
-    }
-
-    numval = (int)value.data.integer;
-  } else {
-    if (value.type != kObjectTypeString) {
-      api_set_error(err, kErrorTypeValidation,
-                    "Option '%s' requires a string value",
-                    name.data);
-      return;
-    }
-
-    stringval = value.data.string.data;
-  }
-
-  WITH_SCRIPT_CONTEXT(channel_id, {
-    const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL))
-                          ? 0 : (type == SREQ_GLOBAL)
-                                ? OPT_GLOBAL : OPT_LOCAL;
-
-    set_option_value_for(name.data, numval, stringval,
-                         opt_flags, type, to, err);
-  });
-}
-
 buf_T *find_buffer_by_handle(Buffer buffer, Error *err)
 {
   if (buffer == 0) {
@@ -1035,59 +888,6 @@ Object copy_object(Object obj)
   }
 }
 
-void set_option_value_for(char *key, long numval, char *stringval, int opt_flags, int opt_type,
-                          void *from, Error *err)
-{
-  switchwin_T switchwin;
-  aco_save_T aco;
-
-  try_start();
-  switch (opt_type) {
-  case SREQ_WIN:
-    if (switch_win_noblock(&switchwin, (win_T *)from, win_find_tabpage((win_T *)from), true)
-        == FAIL) {
-      restore_win_noblock(&switchwin, true);
-      if (try_end(err)) {
-        return;
-      }
-      api_set_error(err,
-                    kErrorTypeException,
-                    "Problem while switching windows");
-      return;
-    }
-    set_option_value_err(key, numval, stringval, opt_flags, err);
-    restore_win_noblock(&switchwin, true);
-    break;
-  case SREQ_BUF:
-    aucmd_prepbuf(&aco, (buf_T *)from);
-    set_option_value_err(key, numval, stringval, opt_flags, err);
-    aucmd_restbuf(&aco);
-    break;
-  case SREQ_GLOBAL:
-    set_option_value_err(key, numval, stringval, opt_flags, err);
-    break;
-  }
-
-  if (ERROR_SET(err)) {
-    return;
-  }
-
-  try_end(err);
-}
-
-static void set_option_value_err(char *key, long numval, char *stringval, int opt_flags, Error *err)
-{
-  char *errmsg;
-
-  if ((errmsg = set_option_value(key, numval, stringval, opt_flags))) {
-    if (try_end(err)) {
-      return;
-    }
-
-    api_set_error(err, kErrorTypeException, "%s", errmsg);
-  }
-}
-
 void api_set_error(Error *err, ErrorType errType, const char *format, ...)
   FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PRINTF(3, 4)
 {
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 5f7162cdd6..f4a6c5e9e3 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -708,220 +708,6 @@ void nvim_set_vvar(String name, Object value, Error *err)
   dict_set_var(&vimvardict, name, value, false, false, err);
 }
 
-/// Gets the global value of an option.
-///
-/// @param name     Option name
-/// @param[out] err Error details, if any
-/// @return         Option value (global)
-Object nvim_get_option(String name, Error *err)
-  FUNC_API_SINCE(1)
-{
-  return get_option_from(NULL, SREQ_GLOBAL, name, err);
-}
-
-/// Gets the value of an option. The behavior of this function matches that of
-/// |:set|: the local value of an option is returned if it exists; otherwise,
-/// the global value is returned. Local values always correspond to the current
-/// buffer or window. To get a buffer-local or window-local option for a
-/// specific buffer or window, use |nvim_buf_get_option()| or
-/// |nvim_win_get_option()|.
-///
-/// @param name      Option name
-/// @param opts      Optional parameters
-///                  - scope: One of 'global' or 'local'. Analogous to
-///                  |:setglobal| and |:setlocal|, respectively.
-/// @param[out] err  Error details, if any
-/// @return          Option value
-Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
-  FUNC_API_SINCE(9)
-{
-  Object rv = OBJECT_INIT;
-
-  int scope = 0;
-  if (opts->scope.type == kObjectTypeString) {
-    if (!strcmp(opts->scope.data.string.data, "local")) {
-      scope = OPT_LOCAL;
-    } else if (!strcmp(opts->scope.data.string.data, "global")) {
-      scope = OPT_GLOBAL;
-    } else {
-      api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'");
-      goto end;
-    }
-  } else if (HAS_KEY(opts->scope)) {
-    api_set_error(err, kErrorTypeValidation, "invalid value for key: scope");
-    goto end;
-  }
-
-  long numval = 0;
-  char *stringval = NULL;
-  switch (get_option_value(name.data, &numval, &stringval, scope)) {
-  case 0:
-    rv = STRING_OBJ(cstr_as_string(stringval));
-    break;
-  case 1:
-    rv = INTEGER_OBJ(numval);
-    break;
-  case 2:
-    switch (numval) {
-    case 0:
-    case 1:
-      rv = BOOLEAN_OBJ(numval);
-      break;
-    default:
-      // Boolean options that return something other than 0 or 1 should return nil. Currently this
-      // only applies to 'autoread' which uses -1 as a local value to indicate "unset"
-      rv = NIL;
-      break;
-    }
-    break;
-  default:
-    api_set_error(err, kErrorTypeValidation, "unknown option '%s'", name.data);
-    goto end;
-  }
-
-end:
-  return rv;
-}
-
-/// Sets the value of an option. The behavior of this function matches that of
-/// |:set|: for global-local options, both the global and local value are set
-/// unless otherwise specified with {scope}.
-///
-/// Note the options {win} and {buf} cannot be used together.
-///
-/// @param name      Option name
-/// @param value     New option value
-/// @param opts      Optional parameters
-///                  - scope: One of 'global' or 'local'. Analogous to
-///                  |:setglobal| and |:setlocal|, respectively.
-///                  - win: |window-ID|. Used for setting window local option.
-///                  - buf: Buffer number. Used for setting buffer local option.
-/// @param[out] err  Error details, if any
-void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error *err)
-  FUNC_API_SINCE(9)
-{
-  int scope = 0;
-  if (opts->scope.type == kObjectTypeString) {
-    if (!strcmp(opts->scope.data.string.data, "local")) {
-      scope = OPT_LOCAL;
-    } else if (!strcmp(opts->scope.data.string.data, "global")) {
-      scope = OPT_GLOBAL;
-    } else {
-      api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'");
-      return;
-    }
-  } else if (HAS_KEY(opts->scope)) {
-    api_set_error(err, kErrorTypeValidation, "invalid value for key: scope");
-    return;
-  }
-
-  int opt_type = SREQ_GLOBAL;
-  void *to = NULL;
-
-  if (opts->win.type == kObjectTypeInteger) {
-    opt_type = SREQ_WIN;
-    to = find_window_by_handle((int)opts->win.data.integer, err);
-  } else if (HAS_KEY(opts->win)) {
-    api_set_error(err, kErrorTypeValidation, "invalid value for key: win");
-    return;
-  }
-
-  if (opts->buf.type == kObjectTypeInteger) {
-    scope = OPT_LOCAL;
-    opt_type = SREQ_BUF;
-    to = find_buffer_by_handle((int)opts->buf.data.integer, err);
-  } else if (HAS_KEY(opts->buf)) {
-    api_set_error(err, kErrorTypeValidation, "invalid value for key: buf");
-    return;
-  }
-
-  if (HAS_KEY(opts->scope) && HAS_KEY(opts->buf)) {
-    api_set_error(err, kErrorTypeValidation, "scope and buf cannot be used together");
-    return;
-  }
-
-  if (HAS_KEY(opts->win) && HAS_KEY(opts->buf)) {
-    api_set_error(err, kErrorTypeValidation, "buf and win cannot be used together");
-    return;
-  }
-
-  long numval = 0;
-  char *stringval = NULL;
-
-  switch (value.type) {
-  case kObjectTypeInteger:
-    numval = value.data.integer;
-    break;
-  case kObjectTypeBoolean:
-    numval = value.data.boolean ? 1 : 0;
-    break;
-  case kObjectTypeString:
-    stringval = value.data.string.data;
-    break;
-  case kObjectTypeNil:
-    scope |= OPT_CLEAR;
-    break;
-  default:
-    api_set_error(err, kErrorTypeValidation, "invalid value for option");
-    return;
-  }
-
-  set_option_value_for(name.data, numval, stringval, scope, opt_type, to, err);
-}
-
-/// Gets the option information for all options.
-///
-/// The dictionary has the full option names as keys and option metadata
-/// dictionaries as detailed at |nvim_get_option_info|.
-///
-/// @return dictionary of all options
-Dictionary nvim_get_all_options_info(Error *err)
-  FUNC_API_SINCE(7)
-{
-  return get_all_vimoptions();
-}
-
-/// Gets the option information for one option
-///
-/// Resulting dictionary has keys:
-///     - name: Name of the option (like 'filetype')
-///     - shortname: Shortened name of the option (like 'ft')
-///     - type: type of option ("string", "number" or "boolean")
-///     - default: The default value for the option
-///     - was_set: Whether the option was set.
-///
-///     - last_set_sid: Last set script id (if any)
-///     - last_set_linenr: line number where option was set
-///     - last_set_chan: Channel where option was set (0 for local)
-///
-///     - scope: one of "global", "win", or "buf"
-///     - global_local: whether win or buf option has a global value
-///
-///     - commalist: List of comma separated values
-///     - flaglist: List of single char flags
-///
-///
-/// @param          name Option name
-/// @param[out] err Error details, if any
-/// @return         Option Information
-Dictionary nvim_get_option_info(String name, Error *err)
-  FUNC_API_SINCE(7)
-{
-  return get_vimoption(name, err);
-}
-
-/// Sets the global value of an option.
-///
-/// @param channel_id
-/// @param name     Option name
-/// @param value    New option value
-/// @param[out] err Error details, if any
-void nvim_set_option(uint64_t channel_id, String name, Object value, Error *err)
-  FUNC_API_SINCE(1)
-{
-  set_option_to(channel_id, NULL, SREQ_GLOBAL, name, value, err);
-}
-
 /// Echo a message.
 ///
 /// @param chunks  A list of [text, hl_group] arrays, each representing a
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index 9f2afc67a6..5a4ff70257 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -264,44 +264,6 @@ void nvim_win_del_var(Window window, String name, Error *err)
   dict_set_var(win->w_vars, name, NIL, true, false, err);
 }
 
-/// Gets a window option value
-///
-/// @param window   Window handle, or 0 for current window
-/// @param name     Option name
-/// @param[out] err Error details, if any
-/// @return Option value
-Object nvim_win_get_option(Window window, String name, Error *err)
-  FUNC_API_SINCE(1)
-{
-  win_T *win = find_window_by_handle(window, err);
-
-  if (!win) {
-    return (Object)OBJECT_INIT;
-  }
-
-  return get_option_from(win, SREQ_WIN, name, err);
-}
-
-/// Sets a window option value. Passing 'nil' as value deletes the option(only
-/// works if there's a global fallback)
-///
-/// @param channel_id
-/// @param window   Window handle, or 0 for current window
-/// @param name     Option name
-/// @param value    Option value
-/// @param[out] err Error details, if any
-void nvim_win_set_option(uint64_t channel_id, Window window, String name, Object value, Error *err)
-  FUNC_API_SINCE(1)
-{
-  win_T *win = find_window_by_handle(window, err);
-
-  if (!win) {
-    return;
-  }
-
-  set_option_to(channel_id, win, SREQ_WIN, name, value, err);
-}
-
 /// Gets the window position in display cells. First position is zero.
 ///
 /// @param window   Window handle, or 0 for current window
-- 
cgit 


From a907d6f51761a18b22495cba933de478101644f5 Mon Sep 17 00:00:00 2001
From: bfredl 
Date: Sun, 12 Jun 2022 16:21:30 +0200
Subject: refactor(api): move extmark specific functions to api/extmark.c

---
 src/nvim/api/extmark.c         | 152 +++++++++++++++++++++++++++++++++++++++++
 src/nvim/api/extmark.h         |   1 +
 src/nvim/api/private/helpers.c | 152 -----------------------------------------
 3 files changed, 153 insertions(+), 152 deletions(-)

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index 9d80a5be5f..da1b6beeda 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -8,6 +8,7 @@
 #include "nvim/api/extmark.h"
 #include "nvim/api/private/defs.h"
 #include "nvim/api/private/helpers.h"
+#include "nvim/charset.h"
 #include "nvim/decoration_provider.h"
 #include "nvim/extmark.h"
 #include "nvim/highlight_group.h"
@@ -1033,3 +1034,154 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Erro
 error:
   decor_provider_clear(p);
 }
+
+/// Gets the line and column of an extmark.
+///
+/// Extmarks may be queried by position, name or even special names
+/// in the future such as "cursor".
+///
+/// @param[out] lnum extmark line
+/// @param[out] colnr extmark column
+///
+/// @return true if the extmark was found, else false
+static bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int *row,
+                                       colnr_T *col, Error *err)
+{
+  // Check if it is mark id
+  if (obj.type == kObjectTypeInteger) {
+    Integer id = obj.data.integer;
+    if (id == 0) {
+      *row = 0;
+      *col = 0;
+      return true;
+    } else if (id == -1) {
+      *row = MAXLNUM;
+      *col = MAXCOL;
+      return true;
+    } else if (id < 0) {
+      api_set_error(err, kErrorTypeValidation, "Mark id must be positive");
+      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");
+      return false;
+    }
+
+    // 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");
+      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);
+    *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, " ");
+  }
+
+  return OK;
+}
+
+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");
+      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");
+      goto free_exit;
+    }
+
+    String str = chunk.items[0].data.string;
+
+    int hl_id = 0;
+    if (chunk.size == 2) {
+      Object hl = chunk.items[1];
+      if (hl.type == kObjectTypeArray) {
+        Array arr = hl.data.array;
+        for (size_t j = 0; j < arr.size; j++) {
+          hl_id = object_to_hl_id(arr.items[j], "virt_text highlight", err);
+          if (ERROR_SET(err)) {
+            goto free_exit;
+          }
+          if (j < arr.size - 1) {
+            kv_push(virt_text, ((VirtTextChunk){ .text = NULL,
+                                                 .hl_id = hl_id }));
+          }
+        }
+      } else {
+        hl_id = object_to_hl_id(hl, "virt_text highlight", err);
+        if (ERROR_SET(err)) {
+          goto free_exit;
+        }
+      }
+    }
+
+    char *text = transstr(str.size > 0 ? str.data : "", false);  // allocates
+    w += (int)mb_string2cells(text);
+
+    kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
+  }
+
+  *width = w;
+  return virt_text;
+
+free_exit:
+  clear_virttext(&virt_text);
+  return virt_text;
+}
diff --git a/src/nvim/api/extmark.h b/src/nvim/api/extmark.h
index c5e463cd86..74802c6efb 100644
--- a/src/nvim/api/extmark.h
+++ b/src/nvim/api/extmark.h
@@ -2,6 +2,7 @@
 #define NVIM_API_EXTMARK_H
 
 #include "nvim/api/private/defs.h"
+#include "nvim/decoration.h"
 #include "nvim/map.h"
 
 EXTERN Map(String, handle_T) namespace_ids INIT(= MAP_INIT);
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 13e3f70ce1..a1f5d70a66 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -16,7 +16,6 @@
 #include "nvim/assert.h"
 #include "nvim/buffer.h"
 #include "nvim/charset.h"
-#include "nvim/decoration.h"
 #include "nvim/eval.h"
 #include "nvim/eval/typval.h"
 #include "nvim/ex_cmds_defs.h"
@@ -960,121 +959,6 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf, bool from_lua)
   return mappings;
 }
 
-/// Gets the line and column of an extmark.
-///
-/// Extmarks may be queried by position, name or even special names
-/// in the future such as "cursor".
-///
-/// @param[out] lnum extmark line
-/// @param[out] colnr extmark column
-///
-/// @return true if the extmark was found, else false
-bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int
-                                *row, colnr_T *col, Error *err)
-{
-  // Check if it is mark id
-  if (obj.type == kObjectTypeInteger) {
-    Integer id = obj.data.integer;
-    if (id == 0) {
-      *row = 0;
-      *col = 0;
-      return true;
-    } else if (id == -1) {
-      *row = MAXLNUM;
-      *col = MAXCOL;
-      return true;
-    } else if (id < 0) {
-      api_set_error(err, kErrorTypeValidation, "Mark id must be positive");
-      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");
-      return false;
-    }
-
-    // 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");
-      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);
-    *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;
-  }
-}
-
-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");
-      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");
-      goto free_exit;
-    }
-
-    String str = chunk.items[0].data.string;
-
-    int hl_id = 0;
-    if (chunk.size == 2) {
-      Object hl = chunk.items[1];
-      if (hl.type == kObjectTypeArray) {
-        Array arr = hl.data.array;
-        for (size_t j = 0; j < arr.size; j++) {
-          hl_id = object_to_hl_id(arr.items[j], "virt_text highlight", err);
-          if (ERROR_SET(err)) {
-            goto free_exit;
-          }
-          if (j < arr.size - 1) {
-            kv_push(virt_text, ((VirtTextChunk){ .text = NULL,
-                                                 .hl_id = hl_id }));
-          }
-        }
-      } else {
-        hl_id = object_to_hl_id(hl, "virt_text highlight", err);
-        if (ERROR_SET(err)) {
-          goto free_exit;
-        }
-      }
-    }
-
-    char *text = transstr(str.size > 0 ? str.data : "", false);  // allocates
-    w += (int)mb_string2cells(text);
-
-    kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
-  }
-
-  *width = w;
-  return virt_text;
-
-free_exit:
-  clear_virttext(&virt_text);
-  return virt_text;
-}
 
 /// Force obj to bool.
 /// If it fails, returns false and sets err
@@ -1460,42 +1344,6 @@ sctx_T api_set_sctx(uint64_t channel_id)
   return old_current_sctx;
 }
 
-// adapted from sign.c:sign_define_init_text.
-// TODO(lewis6991): Consider merging
-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, " ");
-  }
-
-  return OK;
-}
-
 /// Check if a string contains only whitespace characters.
 bool string_iswhite(String str)
 {
-- 
cgit 


From 0d63fafcda3847a6ec8a9da42db7bf10ac917d14 Mon Sep 17 00:00:00 2001
From: bfredl 
Date: Sun, 12 Jun 2022 16:38:31 +0200
Subject: refactor(api): move command related API to separate file

---
 src/nvim/api/buffer.c           |   88 ---
 src/nvim/api/command.c          | 1131 +++++++++++++++++++++++++++++++++++++++
 src/nvim/api/command.h          |   11 +
 src/nvim/api/private/dispatch.c |    1 +
 src/nvim/api/private/helpers.c  |  340 ------------
 src/nvim/api/vim.c              |   70 ---
 src/nvim/api/vimscript.c        |  617 ---------------------
 7 files changed, 1143 insertions(+), 1115 deletions(-)
 create mode 100644 src/nvim/api/command.c
 create mode 100644 src/nvim/api/command.h

(limited to 'src/nvim/api')

diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 49a33d6039..9dc95de243 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -969,37 +969,6 @@ void nvim_buf_del_keymap(uint64_t channel_id, Buffer buffer, String mode, String
   modify_keymap(channel_id, buffer, true, mode, lhs, rhs, NULL, err);
 }
 
-/// Gets a map of buffer-local |user-commands|.
-///
-/// @param  buffer  Buffer handle, or 0 for current buffer
-/// @param  opts  Optional parameters. Currently not used.
-/// @param[out]  err   Error details, if any.
-///
-/// @returns Map of maps describing commands.
-Dictionary nvim_buf_get_commands(Buffer buffer, Dict(get_commands) *opts, Error *err)
-  FUNC_API_SINCE(4)
-{
-  bool global = (buffer == -1);
-  bool builtin = api_object_to_bool(opts->builtin, "builtin", false, err);
-  if (ERROR_SET(err)) {
-    return (Dictionary)ARRAY_DICT_INIT;
-  }
-
-  if (global) {
-    if (builtin) {
-      api_set_error(err, kErrorTypeValidation, "builtin=true not implemented");
-      return (Dictionary)ARRAY_DICT_INIT;
-    }
-    return commands_array(NULL);
-  }
-
-  buf_T *buf = find_buffer_by_handle(buffer, err);
-  if (builtin || !buf) {
-    return (Dictionary)ARRAY_DICT_INIT;
-  }
-  return commands_array(buf);
-}
-
 /// Sets a buffer-scoped (b:) variable
 ///
 /// @param buffer     Buffer handle, or 0 for current buffer
@@ -1331,63 +1300,6 @@ Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err)
   return res;
 }
 
-/// Create a new user command |user-commands| in the given buffer.
-///
-/// @param  buffer  Buffer handle, or 0 for current buffer.
-/// @param[out] err Error details, if any.
-/// @see nvim_create_user_command
-void nvim_buf_create_user_command(Buffer buffer, String name, Object command,
-                                  Dict(user_command) *opts, Error *err)
-  FUNC_API_SINCE(9)
-{
-  buf_T *target_buf = find_buffer_by_handle(buffer, err);
-  if (ERROR_SET(err)) {
-    return;
-  }
-
-  buf_T *save_curbuf = curbuf;
-  curbuf = target_buf;
-  create_user_command(name, command, opts, UC_BUFFER, err);
-  curbuf = save_curbuf;
-}
-
-/// Delete a buffer-local user-defined command.
-///
-/// Only commands created with |:command-buffer| or
-/// |nvim_buf_create_user_command()| can be deleted with this function.
-///
-/// @param  buffer  Buffer handle, or 0 for current buffer.
-/// @param  name    Name of the command to delete.
-/// @param[out] err Error details, if any.
-void nvim_buf_del_user_command(Buffer buffer, String name, Error *err)
-  FUNC_API_SINCE(9)
-{
-  garray_T *gap;
-  if (buffer == -1) {
-    gap = &ucmds;
-  } else {
-    buf_T *buf = find_buffer_by_handle(buffer, err);
-    gap = &buf->b_ucmds;
-  }
-
-  for (int i = 0; i < gap->ga_len; i++) {
-    ucmd_T *cmd = USER_CMD_GA(gap, i);
-    if (!STRCMP(name.data, cmd->uc_name)) {
-      free_ucmd(cmd);
-
-      gap->ga_len -= 1;
-
-      if (i < gap->ga_len) {
-        memmove(cmd, cmd + 1, (size_t)(gap->ga_len - i) * sizeof(ucmd_T));
-      }
-
-      return;
-    }
-  }
-
-  api_set_error(err, kErrorTypeException, "No such user-defined command: %s", name.data);
-}
-
 Dictionary nvim__buf_stats(Buffer buffer, Error *err)
 {
   Dictionary rv = ARRAY_DICT_INIT;
diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c
new file mode 100644
index 0000000000..a7115c09a3
--- /dev/null
+++ b/src/nvim/api/command.c
@@ -0,0 +1,1131 @@
+// 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 
+#include 
+#include 
+
+#include "nvim/api/command.h"
+#include "nvim/api/private/converter.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/autocmd.h"
+#include "nvim/ex_docmd.h"
+#include "nvim/lua/executor.h"
+#include "nvim/ops.h"
+#include "nvim/window.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/command.c.generated.h"
+#endif
+
+/// Parse command line.
+///
+/// Doesn't check the validity of command arguments.
+///
+/// @param str       Command line string to parse. Cannot contain "\n".
+/// @param opts      Optional parameters. Reserved for future use.
+/// @param[out] err  Error details, if any.
+/// @return Dictionary containing command information, with these keys:
+///         - cmd: (string) Command name.
+///         - range: (array) Command . Can have 0-2 elements depending on how many items the
+///                          range contains. Has no elements if command doesn't accept a range or if
+///                          no range was specified, one element if only a single range item was
+///                          specified and two elements if both range items were specified.
+///         - count: (number) Any || that was supplied to the command. -1 if command cannot
+///                           take a count.
+///         - reg: (number) The optional command ||, if specified. Empty string if not
+///                         specified or if command cannot take a register.
+///         - bang: (boolean) Whether command contains a || (!) modifier.
+///         - args: (array) Command arguments.
+///         - addr: (string) Value of |:command-addr|. Uses short name.
+///         - nargs: (string) Value of |:command-nargs|.
+///         - nextcmd: (string) Next command if there are multiple commands separated by a |:bar|.
+///                             Empty if there isn't a next command.
+///         - magic: (dictionary) Which characters have special meaning in the command arguments.
+///             - file: (boolean) The command expands filenames. Which means characters such as "%",
+///                               "#" and wildcards are expanded.
+///             - bar: (boolean) The "|" character is treated as a command separator and the double
+///                              quote character (\") is treated as the start of a comment.
+///         - mods: (dictionary) |:command-modifiers|.
+///             - silent: (boolean) |:silent|.
+///             - emsg_silent: (boolean) |:silent!|.
+///             - sandbox: (boolean) |:sandbox|.
+///             - noautocmd: (boolean) |:noautocmd|.
+///             - browse: (boolean) |:browse|.
+///             - confirm: (boolean) |:confirm|.
+///             - hide: (boolean) |:hide|.
+///             - keepalt: (boolean) |:keepalt|.
+///             - keepjumps: (boolean) |:keepjumps|.
+///             - keepmarks: (boolean) |:keepmarks|.
+///             - keeppatterns: (boolean) |:keeppatterns|.
+///             - lockmarks: (boolean) |:lockmarks|.
+///             - noswapfile: (boolean) |:noswapfile|.
+///             - tab: (integer) |:tab|.
+///             - verbose: (integer) |:verbose|. -1 when omitted.
+///             - vertical: (boolean) |:vertical|.
+///             - split: (string) Split modifier string, is an empty string when there's no split
+///                               modifier. If there is a split modifier it can be one of:
+///               - "aboveleft": |:aboveleft|.
+///               - "belowright": |:belowright|.
+///               - "topleft": |:topleft|.
+///               - "botright": |:botright|.
+Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
+  FUNC_API_SINCE(10) FUNC_API_FAST
+{
+  Dictionary result = ARRAY_DICT_INIT;
+
+  if (opts.size > 0) {
+    api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+    return result;
+  }
+
+  // Parse command line
+  exarg_T ea;
+  CmdParseInfo cmdinfo;
+  char *cmdline = string_to_cstr(str);
+  char *errormsg = NULL;
+
+  if (!parse_cmdline(cmdline, &ea, &cmdinfo, &errormsg)) {
+    if (errormsg != NULL) {
+      api_set_error(err, kErrorTypeException, "Error while parsing command line: %s", errormsg);
+    } else {
+      api_set_error(err, kErrorTypeException, "Error while parsing command line");
+    }
+    goto end;
+  }
+
+  // Parse arguments
+  Array args = ARRAY_DICT_INIT;
+  size_t length = STRLEN(ea.arg);
+
+  // For nargs = 1 or '?', pass the entire argument list as a single argument,
+  // otherwise split arguments by whitespace.
+  if (ea.argt & EX_NOSPC) {
+    if (*ea.arg != NUL) {
+      ADD(args, STRING_OBJ(cstrn_to_string((char *)ea.arg, length)));
+    }
+  } else {
+    size_t end = 0;
+    size_t len = 0;
+    char *buf = xcalloc(length, sizeof(char));
+    bool done = false;
+
+    while (!done) {
+      done = uc_split_args_iter(ea.arg, length, &end, buf, &len);
+      if (len > 0) {
+        ADD(args, STRING_OBJ(cstrn_to_string(buf, len)));
+      }
+    }
+
+    xfree(buf);
+  }
+
+  ucmd_T *cmd = NULL;
+  if (ea.cmdidx == CMD_USER) {
+    cmd = USER_CMD(ea.useridx);
+  } else if (ea.cmdidx == CMD_USER_BUF) {
+    cmd = USER_CMD_GA(&curbuf->b_ucmds, ea.useridx);
+  }
+
+  if (cmd != NULL) {
+    PUT(result, "cmd", CSTR_TO_OBJ((char *)cmd->uc_name));
+  } else {
+    PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx)));
+  }
+
+  if ((ea.argt & EX_RANGE) && ea.addr_count > 0) {
+    Array range = ARRAY_DICT_INIT;
+    if (ea.addr_count > 1) {
+      ADD(range, INTEGER_OBJ(ea.line1));
+    }
+    ADD(range, INTEGER_OBJ(ea.line2));
+    PUT(result, "range", ARRAY_OBJ(range));
+  } else {
+    PUT(result, "range", ARRAY_OBJ(ARRAY_DICT_INIT));
+  }
+
+  if (ea.argt & EX_COUNT) {
+    if (ea.addr_count > 0) {
+      PUT(result, "count", INTEGER_OBJ(ea.line2));
+    } else if (cmd != NULL) {
+      PUT(result, "count", INTEGER_OBJ(cmd->uc_def));
+    } else {
+      PUT(result, "count", INTEGER_OBJ(0));
+    }
+  } else {
+    PUT(result, "count", INTEGER_OBJ(-1));
+  }
+
+  char reg[2];
+  reg[0] = (char)ea.regname;
+  reg[1] = '\0';
+  PUT(result, "reg", CSTR_TO_OBJ(reg));
+
+  PUT(result, "bang", BOOLEAN_OBJ(ea.forceit));
+  PUT(result, "args", ARRAY_OBJ(args));
+
+  char nargs[2];
+  if (ea.argt & EX_EXTRA) {
+    if (ea.argt & EX_NOSPC) {
+      if (ea.argt & EX_NEEDARG) {
+        nargs[0] = '1';
+      } else {
+        nargs[0] = '?';
+      }
+    } else if (ea.argt & EX_NEEDARG) {
+      nargs[0] = '+';
+    } else {
+      nargs[0] = '*';
+    }
+  } else {
+    nargs[0] = '0';
+  }
+  nargs[1] = '\0';
+  PUT(result, "nargs", CSTR_TO_OBJ(nargs));
+
+  const char *addr;
+  switch (ea.addr_type) {
+  case ADDR_LINES:
+    addr = "line";
+    break;
+  case ADDR_ARGUMENTS:
+    addr = "arg";
+    break;
+  case ADDR_BUFFERS:
+    addr = "buf";
+    break;
+  case ADDR_LOADED_BUFFERS:
+    addr = "load";
+    break;
+  case ADDR_WINDOWS:
+    addr = "win";
+    break;
+  case ADDR_TABS:
+    addr = "tab";
+    break;
+  case ADDR_QUICKFIX:
+    addr = "qf";
+    break;
+  case ADDR_NONE:
+    addr = "none";
+    break;
+  default:
+    addr = "?";
+    break;
+  }
+  PUT(result, "addr", CSTR_TO_OBJ(addr));
+  PUT(result, "nextcmd", CSTR_TO_OBJ((char *)ea.nextcmd));
+
+  Dictionary mods = ARRAY_DICT_INIT;
+  PUT(mods, "silent", BOOLEAN_OBJ(cmdinfo.silent));
+  PUT(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.emsg_silent));
+  PUT(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.sandbox));
+  PUT(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.noautocmd));
+  PUT(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.tab));
+  PUT(mods, "verbose", INTEGER_OBJ(cmdinfo.verbose));
+  PUT(mods, "browse", BOOLEAN_OBJ(cmdinfo.cmdmod.browse));
+  PUT(mods, "confirm", BOOLEAN_OBJ(cmdinfo.cmdmod.confirm));
+  PUT(mods, "hide", BOOLEAN_OBJ(cmdinfo.cmdmod.hide));
+  PUT(mods, "keepalt", BOOLEAN_OBJ(cmdinfo.cmdmod.keepalt));
+  PUT(mods, "keepjumps", BOOLEAN_OBJ(cmdinfo.cmdmod.keepjumps));
+  PUT(mods, "keepmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.keepmarks));
+  PUT(mods, "keeppatterns", BOOLEAN_OBJ(cmdinfo.cmdmod.keeppatterns));
+  PUT(mods, "lockmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.lockmarks));
+  PUT(mods, "noswapfile", BOOLEAN_OBJ(cmdinfo.cmdmod.noswapfile));
+  PUT(mods, "vertical", BOOLEAN_OBJ(cmdinfo.cmdmod.split & WSP_VERT));
+
+  const char *split;
+  if (cmdinfo.cmdmod.split & WSP_BOT) {
+    split = "botright";
+  } else if (cmdinfo.cmdmod.split & WSP_TOP) {
+    split = "topleft";
+  } else if (cmdinfo.cmdmod.split & WSP_BELOW) {
+    split = "belowright";
+  } else if (cmdinfo.cmdmod.split & WSP_ABOVE) {
+    split = "aboveleft";
+  } else {
+    split = "";
+  }
+  PUT(mods, "split", CSTR_TO_OBJ(split));
+
+  PUT(result, "mods", DICTIONARY_OBJ(mods));
+
+  Dictionary magic = ARRAY_DICT_INIT;
+  PUT(magic, "file", BOOLEAN_OBJ(cmdinfo.magic.file));
+  PUT(magic, "bar", BOOLEAN_OBJ(cmdinfo.magic.bar));
+  PUT(result, "magic", DICTIONARY_OBJ(magic));
+end:
+  xfree(cmdline);
+  return result;
+}
+
+/// Executes an Ex command.
+///
+/// Unlike |nvim_command()| this command takes a structured Dictionary instead of a String. This
+/// allows for easier construction and manipulation of an Ex command. This also allows for things
+/// such as having spaces inside a command argument, expanding filenames in a command that otherwise
+/// doesn't expand filenames, etc.
+///
+/// On execution error: fails with VimL error, updates v:errmsg.
+///
+/// @see |nvim_exec()|
+/// @see |nvim_command()|
+///
+/// @param cmd       Command to execute. Must be a Dictionary that can contain the same values as
+///                  the return value of |nvim_parse_cmd()| except "addr", "nargs" and "nextcmd"
+///                  which are ignored if provided. All values except for "cmd" are optional.
+/// @param opts      Optional parameters.
+///                  - output: (boolean, default false) Whether to return command output.
+/// @param[out] err  Error details, if any.
+/// @return Command output (non-error, non-shell |:!|) if `output` is true, else empty string.
+String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error *err)
+  FUNC_API_SINCE(10)
+{
+  exarg_T ea;
+  memset(&ea, 0, sizeof(ea));
+  ea.verbose_save = -1;
+  ea.save_msg_silent = -1;
+
+  CmdParseInfo cmdinfo;
+  memset(&cmdinfo, 0, sizeof(cmdinfo));
+  cmdinfo.verbose = -1;
+
+  char *cmdline = NULL;
+  char *cmdname = NULL;
+  char **args = NULL;
+  size_t argc = 0;
+
+  String retv = (String)STRING_INIT;
+
+#define OBJ_TO_BOOL(var, value, default, varname) \
+  do { \
+    var = api_object_to_bool(value, varname, default, err); \
+    if (ERROR_SET(err)) { \
+      goto end; \
+    } \
+  } while (0)
+
+#define VALIDATION_ERROR(...) \
+  do { \
+    api_set_error(err, kErrorTypeValidation, __VA_ARGS__); \
+    goto end; \
+  } while (0)
+
+  bool output;
+  OBJ_TO_BOOL(output, opts->output, false, "'output'");
+
+  // First, parse the command name and check if it exists and is valid.
+  if (!HAS_KEY(cmd->cmd) || cmd->cmd.type != kObjectTypeString
+      || cmd->cmd.data.string.data[0] == NUL) {
+    VALIDATION_ERROR("'cmd' must be a non-empty String");
+  }
+
+  cmdname = string_to_cstr(cmd->cmd.data.string);
+  ea.cmd = cmdname;
+
+  char *p = find_ex_command(&ea, NULL);
+
+  // If this looks like an undefined user command and there are CmdUndefined
+  // autocommands defined, trigger the matching autocommands.
+  if (p != NULL && ea.cmdidx == CMD_SIZE && ASCII_ISUPPER(*ea.cmd)
+      && has_event(EVENT_CMDUNDEFINED)) {
+    p = xstrdup(cmdname);
+    int ret = apply_autocmds(EVENT_CMDUNDEFINED, p, p, true, NULL);
+    xfree(p);
+    // If the autocommands did something and didn't cause an error, try
+    // finding the command again.
+    p = (ret && !aborting()) ? find_ex_command(&ea, NULL) : ea.cmd;
+  }
+
+  if (p == NULL || ea.cmdidx == CMD_SIZE) {
+    VALIDATION_ERROR("Command not found: %s", cmdname);
+  }
+  if (is_cmd_ni(ea.cmdidx)) {
+    VALIDATION_ERROR("Command not implemented: %s", cmdname);
+  }
+
+  // Get the command flags so that we can know what type of arguments the command uses.
+  // Not required for a user command since `find_ex_command` already deals with it in that case.
+  if (!IS_USER_CMDIDX(ea.cmdidx)) {
+    ea.argt = get_cmd_argt(ea.cmdidx);
+  }
+
+  // Parse command arguments since it's needed to get the command address type.
+  if (HAS_KEY(cmd->args)) {
+    if (cmd->args.type != kObjectTypeArray) {
+      VALIDATION_ERROR("'args' must be an Array");
+    }
+    // Check if every argument is valid
+    for (size_t i = 0; i < cmd->args.data.array.size; i++) {
+      Object elem = cmd->args.data.array.items[i];
+      if (elem.type != kObjectTypeString) {
+        VALIDATION_ERROR("Command argument must be a String");
+      } else if (string_iswhite(elem.data.string)) {
+        VALIDATION_ERROR("Command argument must have non-whitespace characters");
+      }
+    }
+
+    argc = cmd->args.data.array.size;
+    bool argc_valid;
+
+    // Check if correct number of arguments is used.
+    switch (ea.argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) {
+    case EX_EXTRA | EX_NOSPC | EX_NEEDARG:
+      argc_valid = argc == 1;
+      break;
+    case EX_EXTRA | EX_NOSPC:
+      argc_valid = argc <= 1;
+      break;
+    case EX_EXTRA | EX_NEEDARG:
+      argc_valid = argc >= 1;
+      break;
+    case EX_EXTRA:
+      argc_valid = true;
+      break;
+    default:
+      argc_valid = argc == 0;
+      break;
+    }
+
+    if (!argc_valid) {
+      argc = 0;  // Ensure that args array isn't erroneously freed at the end.
+      VALIDATION_ERROR("Incorrect number of arguments supplied");
+    }
+
+    if (argc != 0) {
+      args = xcalloc(argc, sizeof(char *));
+
+      for (size_t i = 0; i < argc; i++) {
+        args[i] = string_to_cstr(cmd->args.data.array.items[i].data.string);
+      }
+    }
+  }
+
+  // Simply pass the first argument (if it exists) as the arg pointer to `set_cmd_addr_type()`
+  // since it only ever checks the first argument.
+  set_cmd_addr_type(&ea, argc > 0 ? (char_u *)args[0] : NULL);
+
+  if (HAS_KEY(cmd->range)) {
+    if (!(ea.argt & EX_RANGE)) {
+      VALIDATION_ERROR("Command cannot accept a range");
+    } else if (cmd->range.type != kObjectTypeArray) {
+      VALIDATION_ERROR("'range' must be an Array");
+    } else if (cmd->range.data.array.size > 2) {
+      VALIDATION_ERROR("'range' cannot contain more than two elements");
+    }
+
+    Array range = cmd->range.data.array;
+    ea.addr_count = (int)range.size;
+
+    for (size_t i = 0; i < range.size; i++) {
+      Object elem = range.items[i];
+      if (elem.type != kObjectTypeInteger || elem.data.integer < 0) {
+        VALIDATION_ERROR("'range' element must be a non-negative Integer");
+      }
+    }
+
+    if (range.size > 0) {
+      ea.line1 = (linenr_T)range.items[0].data.integer;
+      ea.line2 = (linenr_T)range.items[range.size - 1].data.integer;
+    }
+
+    if (invalid_range(&ea) != NULL) {
+      VALIDATION_ERROR("Invalid range provided");
+    }
+  }
+  if (ea.addr_count == 0) {
+    if (ea.argt & EX_DFLALL) {
+      set_cmd_dflall_range(&ea);  // Default range for range=%
+    } else {
+      ea.line1 = ea.line2 = get_cmd_default_range(&ea);  // Default range.
+
+      if (ea.addr_type == ADDR_OTHER) {
+        // Default is 1, not cursor.
+        ea.line2 = 1;
+      }
+    }
+  }
+
+  if (HAS_KEY(cmd->count)) {
+    if (!(ea.argt & EX_COUNT)) {
+      VALIDATION_ERROR("Command cannot accept a count");
+    } else if (cmd->count.type != kObjectTypeInteger || cmd->count.data.integer < 0) {
+      VALIDATION_ERROR("'count' must be a non-negative Integer");
+    }
+    set_cmd_count(&ea, cmd->count.data.integer, true);
+  }
+
+  if (HAS_KEY(cmd->reg)) {
+    if (!(ea.argt & EX_REGSTR)) {
+      VALIDATION_ERROR("Command cannot accept a register");
+    } else if (cmd->reg.type != kObjectTypeString || cmd->reg.data.string.size != 1) {
+      VALIDATION_ERROR("'reg' must be a single character");
+    }
+    char regname = cmd->reg.data.string.data[0];
+    if (regname == '=') {
+      VALIDATION_ERROR("Cannot use register \"=");
+    } else if (!valid_yank_reg(regname, ea.cmdidx != CMD_put && !IS_USER_CMDIDX(ea.cmdidx))) {
+      VALIDATION_ERROR("Invalid register: \"%c", regname);
+    }
+    ea.regname = (uint8_t)regname;
+  }
+
+  OBJ_TO_BOOL(ea.forceit, cmd->bang, false, "'bang'");
+  if (ea.forceit && !(ea.argt & EX_BANG)) {
+    VALIDATION_ERROR("Command cannot accept a bang");
+  }
+
+  if (HAS_KEY(cmd->magic)) {
+    if (cmd->magic.type != kObjectTypeDictionary) {
+      VALIDATION_ERROR("'magic' must be a Dictionary");
+    }
+
+    Dict(cmd_magic) magic = { 0 };
+    if (!api_dict_to_keydict(&magic, KeyDict_cmd_magic_get_field,
+                             cmd->magic.data.dictionary, err)) {
+      goto end;
+    }
+
+    OBJ_TO_BOOL(cmdinfo.magic.file, magic.file, ea.argt & EX_XFILE, "'magic.file'");
+    OBJ_TO_BOOL(cmdinfo.magic.bar, magic.bar, ea.argt & EX_TRLBAR, "'magic.bar'");
+  } else {
+    cmdinfo.magic.file = ea.argt & EX_XFILE;
+    cmdinfo.magic.bar = ea.argt & EX_TRLBAR;
+  }
+
+  if (HAS_KEY(cmd->mods)) {
+    if (cmd->mods.type != kObjectTypeDictionary) {
+      VALIDATION_ERROR("'mods' must be a Dictionary");
+    }
+
+    Dict(cmd_mods) mods = { 0 };
+    if (!api_dict_to_keydict(&mods, KeyDict_cmd_mods_get_field, cmd->mods.data.dictionary, err)) {
+      goto end;
+    }
+
+    if (HAS_KEY(mods.tab)) {
+      if (mods.tab.type != kObjectTypeInteger || mods.tab.data.integer < 0) {
+        VALIDATION_ERROR("'mods.tab' must be a non-negative Integer");
+      }
+      cmdinfo.cmdmod.tab = (int)mods.tab.data.integer + 1;
+    }
+
+    if (HAS_KEY(mods.verbose)) {
+      if (mods.verbose.type != kObjectTypeInteger) {
+        VALIDATION_ERROR("'mods.verbose' must be a Integer");
+      } else if (mods.verbose.data.integer >= 0) {
+        // Silently ignore negative integers to allow mods.verbose to be set to -1.
+        cmdinfo.verbose = mods.verbose.data.integer;
+      }
+    }
+
+    bool vertical;
+    OBJ_TO_BOOL(vertical, mods.vertical, false, "'mods.vertical'");
+    cmdinfo.cmdmod.split |= (vertical ? WSP_VERT : 0);
+
+    if (HAS_KEY(mods.split)) {
+      if (mods.split.type != kObjectTypeString) {
+        VALIDATION_ERROR("'mods.split' must be a String");
+      }
+
+      if (*mods.split.data.string.data == NUL) {
+        // Empty string, do nothing.
+      } else if (STRCMP(mods.split.data.string.data, "aboveleft") == 0
+                 || STRCMP(mods.split.data.string.data, "leftabove") == 0) {
+        cmdinfo.cmdmod.split |= WSP_ABOVE;
+      } else if (STRCMP(mods.split.data.string.data, "belowright") == 0
+                 || STRCMP(mods.split.data.string.data, "rightbelow") == 0) {
+        cmdinfo.cmdmod.split |= WSP_BELOW;
+      } else if (STRCMP(mods.split.data.string.data, "topleft") == 0) {
+        cmdinfo.cmdmod.split |= WSP_TOP;
+      } else if (STRCMP(mods.split.data.string.data, "botright") == 0) {
+        cmdinfo.cmdmod.split |= WSP_BOT;
+      } else {
+        VALIDATION_ERROR("Invalid value for 'mods.split'");
+      }
+    }
+
+    OBJ_TO_BOOL(cmdinfo.silent, mods.silent, false, "'mods.silent'");
+    OBJ_TO_BOOL(cmdinfo.emsg_silent, mods.emsg_silent, false, "'mods.emsg_silent'");
+    OBJ_TO_BOOL(cmdinfo.sandbox, mods.sandbox, false, "'mods.sandbox'");
+    OBJ_TO_BOOL(cmdinfo.noautocmd, mods.noautocmd, false, "'mods.noautocmd'");
+    OBJ_TO_BOOL(cmdinfo.cmdmod.browse, mods.browse, false, "'mods.browse'");
+    OBJ_TO_BOOL(cmdinfo.cmdmod.confirm, mods.confirm, false, "'mods.confirm'");
+    OBJ_TO_BOOL(cmdinfo.cmdmod.hide, mods.hide, false, "'mods.hide'");
+    OBJ_TO_BOOL(cmdinfo.cmdmod.keepalt, mods.keepalt, false, "'mods.keepalt'");
+    OBJ_TO_BOOL(cmdinfo.cmdmod.keepjumps, mods.keepjumps, false, "'mods.keepjumps'");
+    OBJ_TO_BOOL(cmdinfo.cmdmod.keepmarks, mods.keepmarks, false, "'mods.keepmarks'");
+    OBJ_TO_BOOL(cmdinfo.cmdmod.keeppatterns, mods.keeppatterns, false, "'mods.keeppatterns'");
+    OBJ_TO_BOOL(cmdinfo.cmdmod.lockmarks, mods.lockmarks, false, "'mods.lockmarks'");
+    OBJ_TO_BOOL(cmdinfo.cmdmod.noswapfile, mods.noswapfile, false, "'mods.noswapfile'");
+
+    if (cmdinfo.sandbox && !(ea.argt & EX_SBOXOK)) {
+      VALIDATION_ERROR("Command cannot be run in sandbox");
+    }
+  }
+
+  // Finally, build the command line string that will be stored inside ea.cmdlinep.
+  // This also sets the values of ea.cmd, ea.arg, ea.args and ea.arglens.
+  build_cmdline_str(&cmdline, &ea, &cmdinfo, args, argc);
+  ea.cmdlinep = &cmdline;
+
+  garray_T capture_local;
+  const int save_msg_silent = msg_silent;
+  garray_T * const save_capture_ga = capture_ga;
+
+  if (output) {
+    ga_init(&capture_local, 1, 80);
+    capture_ga = &capture_local;
+  }
+
+  TRY_WRAP({
+    try_start();
+    if (output) {
+      msg_silent++;
+    }
+
+    WITH_SCRIPT_CONTEXT(channel_id, {
+      execute_cmd(&ea, &cmdinfo, false);
+    });
+
+    if (output) {
+      capture_ga = save_capture_ga;
+      msg_silent = save_msg_silent;
+    }
+
+    try_end(err);
+  });
+
+  if (ERROR_SET(err)) {
+    goto clear_ga;
+  }
+
+  if (output && capture_local.ga_len > 1) {
+    retv = (String){
+      .data = capture_local.ga_data,
+      .size = (size_t)capture_local.ga_len,
+    };
+    // redir usually (except :echon) prepends a newline.
+    if (retv.data[0] == '\n') {
+      memmove(retv.data, retv.data + 1, retv.size - 1);
+      retv.data[retv.size - 1] = '\0';
+      retv.size = retv.size - 1;
+    }
+    goto end;
+  }
+clear_ga:
+  if (output) {
+    ga_clear(&capture_local);
+  }
+end:
+  xfree(cmdline);
+  xfree(cmdname);
+  xfree(ea.args);
+  xfree(ea.arglens);
+  for (size_t i = 0; i < argc; i++) {
+    xfree(args[i]);
+  }
+  xfree(args);
+
+  return retv;
+
+#undef OBJ_TO_BOOL
+#undef VALIDATION_ERROR
+}
+
+/// Check if a string contains only whitespace characters.
+static bool string_iswhite(String str)
+{
+  for (size_t i = 0; i < str.size; i++) {
+    if (!ascii_iswhite(str.data[i])) {
+      // Found a non-whitespace character
+      return false;
+    } else if (str.data[i] == NUL) {
+      // Terminate at first occurence of a NUL character
+      break;
+    }
+  }
+  return true;
+}
+
+/// Build cmdline string for command, used by `nvim_cmd()`.
+///
+/// @return OK or FAIL.
+static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdinfo, char **args,
+                              size_t argc)
+{
+  StringBuilder cmdline = KV_INITIAL_VALUE;
+
+  // Add command modifiers
+  if (cmdinfo->cmdmod.tab != 0) {
+    kv_printf(cmdline, "%dtab ", cmdinfo->cmdmod.tab - 1);
+  }
+  if (cmdinfo->verbose != -1) {
+    kv_printf(cmdline, "%ldverbose ", cmdinfo->verbose);
+  }
+
+  if (cmdinfo->emsg_silent) {
+    kv_concat(cmdline, "silent! ");
+  } else if (cmdinfo->silent) {
+    kv_concat(cmdline, "silent ");
+  }
+
+  switch (cmdinfo->cmdmod.split & (WSP_ABOVE | WSP_BELOW | WSP_TOP | WSP_BOT)) {
+  case WSP_ABOVE:
+    kv_concat(cmdline, "aboveleft ");
+    break;
+  case WSP_BELOW:
+    kv_concat(cmdline, "belowright ");
+    break;
+  case WSP_TOP:
+    kv_concat(cmdline, "topleft ");
+    break;
+  case WSP_BOT:
+    kv_concat(cmdline, "botright ");
+    break;
+  default:
+    break;
+  }
+
+#define CMDLINE_APPEND_IF(cond, str) \
+  do { \
+    if (cond) { \
+      kv_concat(cmdline, str); \
+    } \
+  } while (0)
+
+  CMDLINE_APPEND_IF(cmdinfo->cmdmod.split & WSP_VERT, "vertical ");
+  CMDLINE_APPEND_IF(cmdinfo->sandbox, "sandbox ");
+  CMDLINE_APPEND_IF(cmdinfo->noautocmd, "noautocmd ");
+  CMDLINE_APPEND_IF(cmdinfo->cmdmod.browse, "browse ");
+  CMDLINE_APPEND_IF(cmdinfo->cmdmod.confirm, "confirm ");
+  CMDLINE_APPEND_IF(cmdinfo->cmdmod.hide, "hide ");
+  CMDLINE_APPEND_IF(cmdinfo->cmdmod.keepalt, "keepalt ");
+  CMDLINE_APPEND_IF(cmdinfo->cmdmod.keepjumps, "keepjumps ");
+  CMDLINE_APPEND_IF(cmdinfo->cmdmod.keepmarks, "keepmarks ");
+  CMDLINE_APPEND_IF(cmdinfo->cmdmod.keeppatterns, "keeppatterns ");
+  CMDLINE_APPEND_IF(cmdinfo->cmdmod.lockmarks, "lockmarks ");
+  CMDLINE_APPEND_IF(cmdinfo->cmdmod.noswapfile, "noswapfile ");
+#undef CMDLINE_APPEND_IF
+
+  // Command range / count.
+  if (eap->argt & EX_RANGE) {
+    if (eap->addr_count == 1) {
+      kv_printf(cmdline, "%" PRIdLINENR, eap->line2);
+    } else if (eap->addr_count > 1) {
+      kv_printf(cmdline, "%" PRIdLINENR ",%" PRIdLINENR, eap->line1, eap->line2);
+      eap->addr_count = 2;  // Make sure address count is not greater than 2
+    }
+  }
+
+  // Keep the index of the position where command name starts, so eap->cmd can point to it.
+  size_t cmdname_idx = cmdline.size;
+  kv_printf(cmdline, "%s", eap->cmd);
+
+  // Command bang.
+  if (eap->argt & EX_BANG && eap->forceit) {
+    kv_printf(cmdline, "!");
+  }
+
+  // Command register.
+  if (eap->argt & EX_REGSTR && eap->regname) {
+    kv_printf(cmdline, " %c", eap->regname);
+  }
+
+  // Iterate through each argument and store the starting index and length of each argument
+  size_t *argidx = xcalloc(argc, sizeof(size_t));
+  eap->argc = argc;
+  eap->arglens = xcalloc(argc, sizeof(size_t));
+  for (size_t i = 0; i < argc; i++) {
+    argidx[i] = cmdline.size + 1;  // add 1 to account for the space.
+    eap->arglens[i] = STRLEN(args[i]);
+    kv_printf(cmdline, " %s", args[i]);
+  }
+
+  // Now that all the arguments are appended, use the command index and argument indices to set the
+  // values of eap->cmd, eap->arg and eap->args.
+  eap->cmd = cmdline.items + cmdname_idx;
+  eap->args = xcalloc(argc, sizeof(char *));
+  for (size_t i = 0; i < argc; i++) {
+    eap->args[i] = cmdline.items + argidx[i];
+  }
+  // If there isn't an argument, make eap->arg point to end of cmdline.
+  eap->arg = argc > 0 ? eap->args[0] : cmdline.items + cmdline.size;
+
+  // Finally, make cmdlinep point to the cmdline string.
+  *cmdlinep = cmdline.items;
+  xfree(argidx);
+
+  // Replace, :make and :grep with 'makeprg' and 'grepprg'.
+  char *p = replace_makeprg(eap, eap->arg, cmdlinep);
+  if (p != eap->arg) {
+    // If replace_makeprg modified the cmdline string, correct the argument pointers.
+    assert(argc == 1);
+    eap->arg = p;
+    eap->args[0] = p;
+  }
+}
+
+/// Create a new user command |user-commands|
+///
+/// {name} is the name of the new command. The name must begin with an uppercase letter.
+///
+/// {command} is the replacement text or Lua function to execute.
+///
+/// Example:
+/// 
+///    :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {})
+///    :SayHello
+///    Hello world!
+/// 
+/// +/// @param name Name of the new user command. Must begin with an uppercase letter. +/// @param command Replacement command to execute when this user command is executed. When called +/// from Lua, the command can also be a Lua function. The function is called with a +/// single table argument that contains the following keys: +/// - args: (string) The args passed to the command, if any || +/// - fargs: (table) The args split by unescaped whitespace (when more than one +/// argument is allowed), if any || +/// - bang: (boolean) "true" if the command was executed with a ! modifier || +/// - line1: (number) The starting line of the command range || +/// - line2: (number) The final line of the command range || +/// - range: (number) The number of items in the command range: 0, 1, or 2 || +/// - count: (number) Any count supplied || +/// - reg: (string) The optional register, if specified || +/// - mods: (string) Command modifiers, if any || +/// - smods: (table) Command modifiers in a structured format. Has the same +/// structure as the "mods" key of |nvim_parse_cmd()|. +/// @param opts Optional command attributes. See |command-attributes| for more details. To use +/// boolean attributes (such as |:command-bang| or |:command-bar|) set the value to +/// "true". In addition to the string options listed in |:command-complete|, the +/// "complete" key also accepts a Lua function which works like the "customlist" +/// completion mode |:command-completion-customlist|. Additional parameters: +/// - desc: (string) Used for listing the command when a Lua function is used for +/// {command}. +/// - force: (boolean, default true) Override any previous definition. +/// - preview: (function) Preview callback for 'inccommand' |:command-preview| +/// @param[out] err Error details, if any. +void nvim_create_user_command(String name, Object command, Dict(user_command) *opts, Error *err) + FUNC_API_SINCE(9) +{ + create_user_command(name, command, opts, 0, err); +} + +/// Delete a user-defined command. +/// +/// @param name Name of the command to delete. +/// @param[out] err Error details, if any. +void nvim_del_user_command(String name, Error *err) + FUNC_API_SINCE(9) +{ + nvim_buf_del_user_command(-1, name, err); +} + +/// Create a new user command |user-commands| in the given buffer. +/// +/// @param buffer Buffer handle, or 0 for current buffer. +/// @param[out] err Error details, if any. +/// @see nvim_create_user_command +void nvim_buf_create_user_command(Buffer buffer, String name, Object command, + Dict(user_command) *opts, Error *err) + FUNC_API_SINCE(9) +{ + buf_T *target_buf = find_buffer_by_handle(buffer, err); + if (ERROR_SET(err)) { + return; + } + + buf_T *save_curbuf = curbuf; + curbuf = target_buf; + create_user_command(name, command, opts, UC_BUFFER, err); + curbuf = save_curbuf; +} + +/// Delete a buffer-local user-defined command. +/// +/// Only commands created with |:command-buffer| or +/// |nvim_buf_create_user_command()| can be deleted with this function. +/// +/// @param buffer Buffer handle, or 0 for current buffer. +/// @param name Name of the command to delete. +/// @param[out] err Error details, if any. +void nvim_buf_del_user_command(Buffer buffer, String name, Error *err) + FUNC_API_SINCE(9) +{ + garray_T *gap; + if (buffer == -1) { + gap = &ucmds; + } else { + buf_T *buf = find_buffer_by_handle(buffer, err); + gap = &buf->b_ucmds; + } + + for (int i = 0; i < gap->ga_len; i++) { + ucmd_T *cmd = USER_CMD_GA(gap, i); + if (!STRCMP(name.data, cmd->uc_name)) { + free_ucmd(cmd); + + gap->ga_len -= 1; + + if (i < gap->ga_len) { + memmove(cmd, cmd + 1, (size_t)(gap->ga_len - i) * sizeof(ucmd_T)); + } + + return; + } + } + + api_set_error(err, kErrorTypeException, "No such user-defined command: %s", name.data); +} + +void create_user_command(String name, Object command, Dict(user_command) *opts, int flags, + Error *err) +{ + uint32_t argt = 0; + long def = -1; + cmd_addr_T addr_type_arg = ADDR_NONE; + int compl = EXPAND_NOTHING; + char *compl_arg = NULL; + char *rep = NULL; + LuaRef luaref = LUA_NOREF; + LuaRef compl_luaref = LUA_NOREF; + LuaRef preview_luaref = LUA_NOREF; + + if (!uc_validate_name(name.data)) { + api_set_error(err, kErrorTypeValidation, "Invalid command name"); + goto err; + } + + if (mb_islower(name.data[0])) { + api_set_error(err, kErrorTypeValidation, "'name' must begin with an uppercase letter"); + goto err; + } + + if (HAS_KEY(opts->range) && HAS_KEY(opts->count)) { + api_set_error(err, kErrorTypeValidation, "'range' and 'count' are mutually exclusive"); + goto err; + } + + if (opts->nargs.type == kObjectTypeInteger) { + switch (opts->nargs.data.integer) { + case 0: + // Default value, nothing to do + break; + case 1: + argt |= EX_EXTRA | EX_NOSPC | EX_NEEDARG; + break; + default: + api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'"); + goto err; + } + } else if (opts->nargs.type == kObjectTypeString) { + if (opts->nargs.data.string.size > 1) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'"); + goto err; + } + + switch (opts->nargs.data.string.data[0]) { + case '*': + argt |= EX_EXTRA; + break; + case '?': + argt |= EX_EXTRA | EX_NOSPC; + break; + case '+': + argt |= EX_EXTRA | EX_NEEDARG; + break; + default: + api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'"); + goto err; + } + } else if (HAS_KEY(opts->nargs)) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'"); + goto err; + } + + if (HAS_KEY(opts->complete) && !argt) { + api_set_error(err, kErrorTypeValidation, "'complete' used without 'nargs'"); + goto err; + } + + if (opts->range.type == kObjectTypeBoolean) { + if (opts->range.data.boolean) { + argt |= EX_RANGE; + addr_type_arg = ADDR_LINES; + } + } else if (opts->range.type == kObjectTypeString) { + if (opts->range.data.string.data[0] == '%' && opts->range.data.string.size == 1) { + argt |= EX_RANGE | EX_DFLALL; + addr_type_arg = ADDR_LINES; + } else { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'"); + goto err; + } + } else if (opts->range.type == kObjectTypeInteger) { + argt |= EX_RANGE | EX_ZEROR; + def = opts->range.data.integer; + addr_type_arg = ADDR_LINES; + } else if (HAS_KEY(opts->range)) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'"); + goto err; + } + + if (opts->count.type == kObjectTypeBoolean) { + if (opts->count.data.boolean) { + argt |= EX_COUNT | EX_ZEROR | EX_RANGE; + addr_type_arg = ADDR_OTHER; + def = 0; + } + } else if (opts->count.type == kObjectTypeInteger) { + argt |= EX_COUNT | EX_ZEROR | EX_RANGE; + addr_type_arg = ADDR_OTHER; + def = opts->count.data.integer; + } else if (HAS_KEY(opts->count)) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'count'"); + goto err; + } + + if (opts->addr.type == kObjectTypeString) { + if (parse_addr_type_arg(opts->addr.data.string.data, (int)opts->addr.data.string.size, + &addr_type_arg) != OK) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'"); + goto err; + } + + if (addr_type_arg != ADDR_LINES) { + argt |= EX_ZEROR; + } + } else if (HAS_KEY(opts->addr)) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'"); + goto err; + } + + if (api_object_to_bool(opts->bang, "bang", false, err)) { + argt |= EX_BANG; + } else if (ERROR_SET(err)) { + goto err; + } + + if (api_object_to_bool(opts->bar, "bar", false, err)) { + argt |= EX_TRLBAR; + } else if (ERROR_SET(err)) { + goto err; + } + + if (api_object_to_bool(opts->register_, "register", false, err)) { + argt |= EX_REGSTR; + } else if (ERROR_SET(err)) { + goto err; + } + + if (api_object_to_bool(opts->keepscript, "keepscript", false, err)) { + argt |= EX_KEEPSCRIPT; + } else if (ERROR_SET(err)) { + goto err; + } + + bool force = api_object_to_bool(opts->force, "force", true, err); + if (ERROR_SET(err)) { + goto err; + } + + if (opts->complete.type == kObjectTypeLuaRef) { + compl = EXPAND_USER_LUA; + compl_luaref = api_new_luaref(opts->complete.data.luaref); + } else if (opts->complete.type == kObjectTypeString) { + if (parse_compl_arg(opts->complete.data.string.data, + (int)opts->complete.data.string.size, &compl, &argt, + &compl_arg) != OK) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'"); + goto err; + } + } else if (HAS_KEY(opts->complete)) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'"); + goto err; + } + + if (opts->preview.type == kObjectTypeLuaRef) { + argt |= EX_PREVIEW; + preview_luaref = api_new_luaref(opts->preview.data.luaref); + } else if (HAS_KEY(opts->preview)) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'preview'"); + goto err; + } + + switch (command.type) { + case kObjectTypeLuaRef: + luaref = api_new_luaref(command.data.luaref); + if (opts->desc.type == kObjectTypeString) { + rep = opts->desc.data.string.data; + } else { + snprintf((char *)IObuff, IOSIZE, "", luaref); + rep = (char *)IObuff; + } + break; + case kObjectTypeString: + rep = command.data.string.data; + break; + default: + api_set_error(err, kErrorTypeValidation, "'command' must be a string or Lua function"); + goto err; + } + + if (uc_add_command(name.data, name.size, rep, argt, def, flags, compl, compl_arg, compl_luaref, + preview_luaref, addr_type_arg, luaref, force) != OK) { + api_set_error(err, kErrorTypeException, "Failed to create user command"); + // Do not goto err, since uc_add_command now owns luaref, compl_luaref, and compl_arg + } + + return; + +err: + NLUA_CLEAR_REF(luaref); + NLUA_CLEAR_REF(compl_luaref); + xfree(compl_arg); +} +/// Gets a map of global (non-buffer-local) Ex commands. +/// +/// Currently only |user-commands| are supported, not builtin Ex commands. +/// +/// @param opts Optional parameters. Currently only supports +/// {"builtin":false} +/// @param[out] err Error details, if any. +/// +/// @returns Map of maps describing commands. +Dictionary nvim_get_commands(Dict(get_commands) *opts, Error *err) + FUNC_API_SINCE(4) +{ + return nvim_buf_get_commands(-1, opts, err); +} + +/// Gets a map of buffer-local |user-commands|. +/// +/// @param buffer Buffer handle, or 0 for current buffer +/// @param opts Optional parameters. Currently not used. +/// @param[out] err Error details, if any. +/// +/// @returns Map of maps describing commands. +Dictionary nvim_buf_get_commands(Buffer buffer, Dict(get_commands) *opts, Error *err) + FUNC_API_SINCE(4) +{ + bool global = (buffer == -1); + bool builtin = api_object_to_bool(opts->builtin, "builtin", false, err); + if (ERROR_SET(err)) { + return (Dictionary)ARRAY_DICT_INIT; + } + + if (global) { + if (builtin) { + api_set_error(err, kErrorTypeValidation, "builtin=true not implemented"); + return (Dictionary)ARRAY_DICT_INIT; + } + return commands_array(NULL); + } + + buf_T *buf = find_buffer_by_handle(buffer, err); + if (builtin || !buf) { + return (Dictionary)ARRAY_DICT_INIT; + } + return commands_array(buf); +} diff --git a/src/nvim/api/command.h b/src/nvim/api/command.h new file mode 100644 index 0000000000..b1c9230551 --- /dev/null +++ b/src/nvim/api/command.h @@ -0,0 +1,11 @@ +#ifndef NVIM_API_COMMAND_H +#define NVIM_API_COMMAND_H + +#include "nvim/api/private/defs.h" +#include "nvim/decoration.h" +#include "nvim/ex_cmds.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "api/command.h.generated.h" +#endif +#endif // NVIM_API_COMMAND_H diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c index d2ffd8879a..d6a6fc1219 100644 --- a/src/nvim/api/private/dispatch.c +++ b/src/nvim/api/private/dispatch.c @@ -23,6 +23,7 @@ // =========================================================================== #include "nvim/api/autocmd.h" #include "nvim/api/buffer.h" +#include "nvim/api/command.h" #include "nvim/api/extmark.h" #include "nvim/api/options.h" #include "nvim/api/tabpage.h" diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index a1f5d70a66..ff6a4c37e8 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -959,7 +959,6 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf, bool from_lua) return mappings; } - /// Force obj to bool. /// If it fails, returns false and sets err /// @param obj The object to coerce to a boolean @@ -1108,212 +1107,6 @@ const char *get_default_stl_hl(win_T *wp, bool use_winbar) } } -void create_user_command(String name, Object command, Dict(user_command) *opts, int flags, - Error *err) -{ - uint32_t argt = 0; - long def = -1; - cmd_addr_T addr_type_arg = ADDR_NONE; - int compl = EXPAND_NOTHING; - char *compl_arg = NULL; - char *rep = NULL; - LuaRef luaref = LUA_NOREF; - LuaRef compl_luaref = LUA_NOREF; - LuaRef preview_luaref = LUA_NOREF; - - if (!uc_validate_name(name.data)) { - api_set_error(err, kErrorTypeValidation, "Invalid command name"); - goto err; - } - - if (mb_islower(name.data[0])) { - api_set_error(err, kErrorTypeValidation, "'name' must begin with an uppercase letter"); - goto err; - } - - if (HAS_KEY(opts->range) && HAS_KEY(opts->count)) { - api_set_error(err, kErrorTypeValidation, "'range' and 'count' are mutually exclusive"); - goto err; - } - - if (opts->nargs.type == kObjectTypeInteger) { - switch (opts->nargs.data.integer) { - case 0: - // Default value, nothing to do - break; - case 1: - argt |= EX_EXTRA | EX_NOSPC | EX_NEEDARG; - break; - default: - api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'"); - goto err; - } - } else if (opts->nargs.type == kObjectTypeString) { - if (opts->nargs.data.string.size > 1) { - api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'"); - goto err; - } - - switch (opts->nargs.data.string.data[0]) { - case '*': - argt |= EX_EXTRA; - break; - case '?': - argt |= EX_EXTRA | EX_NOSPC; - break; - case '+': - argt |= EX_EXTRA | EX_NEEDARG; - break; - default: - api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'"); - goto err; - } - } else if (HAS_KEY(opts->nargs)) { - api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'"); - goto err; - } - - if (HAS_KEY(opts->complete) && !argt) { - api_set_error(err, kErrorTypeValidation, "'complete' used without 'nargs'"); - goto err; - } - - if (opts->range.type == kObjectTypeBoolean) { - if (opts->range.data.boolean) { - argt |= EX_RANGE; - addr_type_arg = ADDR_LINES; - } - } else if (opts->range.type == kObjectTypeString) { - if (opts->range.data.string.data[0] == '%' && opts->range.data.string.size == 1) { - argt |= EX_RANGE | EX_DFLALL; - addr_type_arg = ADDR_LINES; - } else { - api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'"); - goto err; - } - } else if (opts->range.type == kObjectTypeInteger) { - argt |= EX_RANGE | EX_ZEROR; - def = opts->range.data.integer; - addr_type_arg = ADDR_LINES; - } else if (HAS_KEY(opts->range)) { - api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'"); - goto err; - } - - if (opts->count.type == kObjectTypeBoolean) { - if (opts->count.data.boolean) { - argt |= EX_COUNT | EX_ZEROR | EX_RANGE; - addr_type_arg = ADDR_OTHER; - def = 0; - } - } else if (opts->count.type == kObjectTypeInteger) { - argt |= EX_COUNT | EX_ZEROR | EX_RANGE; - addr_type_arg = ADDR_OTHER; - def = opts->count.data.integer; - } else if (HAS_KEY(opts->count)) { - api_set_error(err, kErrorTypeValidation, "Invalid value for 'count'"); - goto err; - } - - if (opts->addr.type == kObjectTypeString) { - if (parse_addr_type_arg(opts->addr.data.string.data, (int)opts->addr.data.string.size, - &addr_type_arg) != OK) { - api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'"); - goto err; - } - - if (addr_type_arg != ADDR_LINES) { - argt |= EX_ZEROR; - } - } else if (HAS_KEY(opts->addr)) { - api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'"); - goto err; - } - - if (api_object_to_bool(opts->bang, "bang", false, err)) { - argt |= EX_BANG; - } else if (ERROR_SET(err)) { - goto err; - } - - if (api_object_to_bool(opts->bar, "bar", false, err)) { - argt |= EX_TRLBAR; - } else if (ERROR_SET(err)) { - goto err; - } - - if (api_object_to_bool(opts->register_, "register", false, err)) { - argt |= EX_REGSTR; - } else if (ERROR_SET(err)) { - goto err; - } - - if (api_object_to_bool(opts->keepscript, "keepscript", false, err)) { - argt |= EX_KEEPSCRIPT; - } else if (ERROR_SET(err)) { - goto err; - } - - bool force = api_object_to_bool(opts->force, "force", true, err); - if (ERROR_SET(err)) { - goto err; - } - - if (opts->complete.type == kObjectTypeLuaRef) { - compl = EXPAND_USER_LUA; - compl_luaref = api_new_luaref(opts->complete.data.luaref); - } else if (opts->complete.type == kObjectTypeString) { - if (parse_compl_arg(opts->complete.data.string.data, - (int)opts->complete.data.string.size, &compl, &argt, - &compl_arg) != OK) { - api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'"); - goto err; - } - } else if (HAS_KEY(opts->complete)) { - api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'"); - goto err; - } - - if (opts->preview.type == kObjectTypeLuaRef) { - argt |= EX_PREVIEW; - preview_luaref = api_new_luaref(opts->preview.data.luaref); - } else if (HAS_KEY(opts->preview)) { - api_set_error(err, kErrorTypeValidation, "Invalid value for 'preview'"); - goto err; - } - - switch (command.type) { - case kObjectTypeLuaRef: - luaref = api_new_luaref(command.data.luaref); - if (opts->desc.type == kObjectTypeString) { - rep = opts->desc.data.string.data; - } else { - snprintf((char *)IObuff, IOSIZE, "", luaref); - rep = (char *)IObuff; - } - break; - case kObjectTypeString: - rep = command.data.string.data; - break; - default: - api_set_error(err, kErrorTypeValidation, "'command' must be a string or Lua function"); - goto err; - } - - if (uc_add_command(name.data, name.size, rep, argt, def, flags, compl, compl_arg, compl_luaref, - preview_luaref, addr_type_arg, luaref, force) != OK) { - api_set_error(err, kErrorTypeException, "Failed to create user command"); - // Do not goto err, since uc_add_command now owns luaref, compl_luaref, and compl_arg - } - - return; - -err: - NLUA_CLEAR_REF(luaref); - NLUA_CLEAR_REF(compl_luaref); - xfree(compl_arg); -} - int find_sid(uint64_t channel_id) { switch (channel_id) { @@ -1343,136 +1136,3 @@ sctx_T api_set_sctx(uint64_t channel_id) } return old_current_sctx; } - -/// Check if a string contains only whitespace characters. -bool string_iswhite(String str) -{ - for (size_t i = 0; i < str.size; i++) { - if (!ascii_iswhite(str.data[i])) { - // Found a non-whitespace character - return false; - } else if (str.data[i] == NUL) { - // Terminate at first occurence of a NUL character - break; - } - } - return true; -} - -/// Build cmdline string for command, used by `nvim_cmd()`. -/// -/// @return OK or FAIL. -void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdinfo, char **args, - size_t argc) -{ - StringBuilder cmdline = KV_INITIAL_VALUE; - - // Add command modifiers - if (cmdinfo->cmdmod.tab != 0) { - kv_printf(cmdline, "%dtab ", cmdinfo->cmdmod.tab - 1); - } - if (cmdinfo->verbose != -1) { - kv_printf(cmdline, "%ldverbose ", cmdinfo->verbose); - } - - if (cmdinfo->emsg_silent) { - kv_concat(cmdline, "silent! "); - } else if (cmdinfo->silent) { - kv_concat(cmdline, "silent "); - } - - switch (cmdinfo->cmdmod.split & (WSP_ABOVE | WSP_BELOW | WSP_TOP | WSP_BOT)) { - case WSP_ABOVE: - kv_concat(cmdline, "aboveleft "); - break; - case WSP_BELOW: - kv_concat(cmdline, "belowright "); - break; - case WSP_TOP: - kv_concat(cmdline, "topleft "); - break; - case WSP_BOT: - kv_concat(cmdline, "botright "); - break; - default: - break; - } - -#define CMDLINE_APPEND_IF(cond, str) \ - do { \ - if (cond) { \ - kv_concat(cmdline, str); \ - } \ - } while (0) - - CMDLINE_APPEND_IF(cmdinfo->cmdmod.split & WSP_VERT, "vertical "); - CMDLINE_APPEND_IF(cmdinfo->sandbox, "sandbox "); - CMDLINE_APPEND_IF(cmdinfo->noautocmd, "noautocmd "); - CMDLINE_APPEND_IF(cmdinfo->cmdmod.browse, "browse "); - CMDLINE_APPEND_IF(cmdinfo->cmdmod.confirm, "confirm "); - CMDLINE_APPEND_IF(cmdinfo->cmdmod.hide, "hide "); - CMDLINE_APPEND_IF(cmdinfo->cmdmod.keepalt, "keepalt "); - CMDLINE_APPEND_IF(cmdinfo->cmdmod.keepjumps, "keepjumps "); - CMDLINE_APPEND_IF(cmdinfo->cmdmod.keepmarks, "keepmarks "); - CMDLINE_APPEND_IF(cmdinfo->cmdmod.keeppatterns, "keeppatterns "); - CMDLINE_APPEND_IF(cmdinfo->cmdmod.lockmarks, "lockmarks "); - CMDLINE_APPEND_IF(cmdinfo->cmdmod.noswapfile, "noswapfile "); -#undef CMDLINE_APPEND_IF - - // Command range / count. - if (eap->argt & EX_RANGE) { - if (eap->addr_count == 1) { - kv_printf(cmdline, "%" PRIdLINENR, eap->line2); - } else if (eap->addr_count > 1) { - kv_printf(cmdline, "%" PRIdLINENR ",%" PRIdLINENR, eap->line1, eap->line2); - eap->addr_count = 2; // Make sure address count is not greater than 2 - } - } - - // Keep the index of the position where command name starts, so eap->cmd can point to it. - size_t cmdname_idx = cmdline.size; - kv_printf(cmdline, "%s", eap->cmd); - - // Command bang. - if (eap->argt & EX_BANG && eap->forceit) { - kv_printf(cmdline, "!"); - } - - // Command register. - if (eap->argt & EX_REGSTR && eap->regname) { - kv_printf(cmdline, " %c", eap->regname); - } - - // Iterate through each argument and store the starting index and length of each argument - size_t *argidx = xcalloc(argc, sizeof(size_t)); - eap->argc = argc; - eap->arglens = xcalloc(argc, sizeof(size_t)); - for (size_t i = 0; i < argc; i++) { - argidx[i] = cmdline.size + 1; // add 1 to account for the space. - eap->arglens[i] = STRLEN(args[i]); - kv_printf(cmdline, " %s", args[i]); - } - - // Now that all the arguments are appended, use the command index and argument indices to set the - // values of eap->cmd, eap->arg and eap->args. - eap->cmd = cmdline.items + cmdname_idx; - eap->args = xcalloc(argc, sizeof(char *)); - for (size_t i = 0; i < argc; i++) { - eap->args[i] = cmdline.items + argidx[i]; - } - // If there isn't an argument, make eap->arg point to end of cmdline. - eap->arg = argc > 0 ? eap->args[0] : cmdline.items + cmdline.size; - - // Finally, make cmdlinep point to the cmdline string. - *cmdlinep = cmdline.items; - xfree(argidx); - - // Replace, :make and :grep with 'makeprg' and 'grepprg'. - char *p = replace_makeprg(eap, eap->arg, cmdlinep); - if (p != eap->arg) { - // If replace_makeprg modified the cmdline string, correct the argument pointers. - assert(argc == 1); - eap->arg = p; - eap->args[0] = p; - } -} diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index f4a6c5e9e3..77b4900f4f 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1465,21 +1465,6 @@ void nvim_del_keymap(uint64_t channel_id, String mode, String lhs, Error *err) nvim_buf_del_keymap(channel_id, -1, mode, lhs, err); } -/// Gets a map of global (non-buffer-local) Ex commands. -/// -/// Currently only |user-commands| are supported, not builtin Ex commands. -/// -/// @param opts Optional parameters. Currently only supports -/// {"builtin":false} -/// @param[out] err Error details, if any. -/// -/// @returns Map of maps describing commands. -Dictionary nvim_get_commands(Dict(get_commands) *opts, Error *err) - FUNC_API_SINCE(4) -{ - return nvim_buf_get_commands(-1, opts, err); -} - /// Returns a 2-tuple (Array), where item 0 is the current channel id and item /// 1 is the |api-metadata| map (Dictionary). /// @@ -2269,58 +2254,3 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * return result; } - -/// Create a new user command |user-commands| -/// -/// {name} is the name of the new command. The name must begin with an uppercase letter. -/// -/// {command} is the replacement text or Lua function to execute. -/// -/// Example: -///
-///    :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {})
-///    :SayHello
-///    Hello world!
-/// 
-/// -/// @param name Name of the new user command. Must begin with an uppercase letter. -/// @param command Replacement command to execute when this user command is executed. When called -/// from Lua, the command can also be a Lua function. The function is called with a -/// single table argument that contains the following keys: -/// - args: (string) The args passed to the command, if any || -/// - fargs: (table) The args split by unescaped whitespace (when more than one -/// argument is allowed), if any || -/// - bang: (boolean) "true" if the command was executed with a ! modifier || -/// - line1: (number) The starting line of the command range || -/// - line2: (number) The final line of the command range || -/// - range: (number) The number of items in the command range: 0, 1, or 2 || -/// - count: (number) Any count supplied || -/// - reg: (string) The optional register, if specified || -/// - mods: (string) Command modifiers, if any || -/// - smods: (table) Command modifiers in a structured format. Has the same -/// structure as the "mods" key of |nvim_parse_cmd()|. -/// @param opts Optional command attributes. See |command-attributes| for more details. To use -/// boolean attributes (such as |:command-bang| or |:command-bar|) set the value to -/// "true". In addition to the string options listed in |:command-complete|, the -/// "complete" key also accepts a Lua function which works like the "customlist" -/// completion mode |:command-completion-customlist|. Additional parameters: -/// - desc: (string) Used for listing the command when a Lua function is used for -/// {command}. -/// - force: (boolean, default true) Override any previous definition. -/// - preview: (function) Preview callback for 'inccommand' |:command-preview| -/// @param[out] err Error details, if any. -void nvim_create_user_command(String name, Object command, Dict(user_command) *opts, Error *err) - FUNC_API_SINCE(9) -{ - create_user_command(name, command, opts, 0, err); -} - -/// Delete a user-defined command. -/// -/// @param name Name of the command to delete. -/// @param[out] err Error details, if any. -void nvim_del_user_command(String name, Error *err) - FUNC_API_SINCE(9) -{ - nvim_buf_del_user_command(-1, name, err); -} diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 6d0b9c7d99..478e146781 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -26,8 +26,6 @@ # include "api/vimscript.c.generated.h" #endif -#define IS_USER_CMDIDX(idx) ((int)(idx) < 0) - /// Executes Vimscript (multiline block of Ex commands), like anonymous /// |:source|. /// @@ -747,618 +745,3 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E viml_parser_destroy(&pstate); return ret; } - -/// Parse command line. -/// -/// Doesn't check the validity of command arguments. -/// -/// @param str Command line string to parse. Cannot contain "\n". -/// @param opts Optional parameters. Reserved for future use. -/// @param[out] err Error details, if any. -/// @return Dictionary containing command information, with these keys: -/// - cmd: (string) Command name. -/// - range: (array) Command . Can have 0-2 elements depending on how many items the -/// range contains. Has no elements if command doesn't accept a range or if -/// no range was specified, one element if only a single range item was -/// specified and two elements if both range items were specified. -/// - count: (number) Any || that was supplied to the command. -1 if command cannot -/// take a count. -/// - reg: (number) The optional command ||, if specified. Empty string if not -/// specified or if command cannot take a register. -/// - bang: (boolean) Whether command contains a || (!) modifier. -/// - args: (array) Command arguments. -/// - addr: (string) Value of |:command-addr|. Uses short name. -/// - nargs: (string) Value of |:command-nargs|. -/// - nextcmd: (string) Next command if there are multiple commands separated by a |:bar|. -/// Empty if there isn't a next command. -/// - magic: (dictionary) Which characters have special meaning in the command arguments. -/// - file: (boolean) The command expands filenames. Which means characters such as "%", -/// "#" and wildcards are expanded. -/// - bar: (boolean) The "|" character is treated as a command separator and the double -/// quote character (\") is treated as the start of a comment. -/// - mods: (dictionary) |:command-modifiers|. -/// - silent: (boolean) |:silent|. -/// - emsg_silent: (boolean) |:silent!|. -/// - sandbox: (boolean) |:sandbox|. -/// - noautocmd: (boolean) |:noautocmd|. -/// - browse: (boolean) |:browse|. -/// - confirm: (boolean) |:confirm|. -/// - hide: (boolean) |:hide|. -/// - keepalt: (boolean) |:keepalt|. -/// - keepjumps: (boolean) |:keepjumps|. -/// - keepmarks: (boolean) |:keepmarks|. -/// - keeppatterns: (boolean) |:keeppatterns|. -/// - lockmarks: (boolean) |:lockmarks|. -/// - noswapfile: (boolean) |:noswapfile|. -/// - tab: (integer) |:tab|. -/// - verbose: (integer) |:verbose|. -1 when omitted. -/// - vertical: (boolean) |:vertical|. -/// - split: (string) Split modifier string, is an empty string when there's no split -/// modifier. If there is a split modifier it can be one of: -/// - "aboveleft": |:aboveleft|. -/// - "belowright": |:belowright|. -/// - "topleft": |:topleft|. -/// - "botright": |:botright|. -Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) - FUNC_API_SINCE(10) FUNC_API_FAST -{ - Dictionary result = ARRAY_DICT_INIT; - - if (opts.size > 0) { - api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); - return result; - } - - // Parse command line - exarg_T ea; - CmdParseInfo cmdinfo; - char *cmdline = string_to_cstr(str); - char *errormsg = NULL; - - if (!parse_cmdline(cmdline, &ea, &cmdinfo, &errormsg)) { - if (errormsg != NULL) { - api_set_error(err, kErrorTypeException, "Error while parsing command line: %s", errormsg); - } else { - api_set_error(err, kErrorTypeException, "Error while parsing command line"); - } - goto end; - } - - // Parse arguments - Array args = ARRAY_DICT_INIT; - size_t length = STRLEN(ea.arg); - - // For nargs = 1 or '?', pass the entire argument list as a single argument, - // otherwise split arguments by whitespace. - if (ea.argt & EX_NOSPC) { - if (*ea.arg != NUL) { - ADD(args, STRING_OBJ(cstrn_to_string((char *)ea.arg, length))); - } - } else { - size_t end = 0; - size_t len = 0; - char *buf = xcalloc(length, sizeof(char)); - bool done = false; - - while (!done) { - done = uc_split_args_iter(ea.arg, length, &end, buf, &len); - if (len > 0) { - ADD(args, STRING_OBJ(cstrn_to_string(buf, len))); - } - } - - xfree(buf); - } - - ucmd_T *cmd = NULL; - if (ea.cmdidx == CMD_USER) { - cmd = USER_CMD(ea.useridx); - } else if (ea.cmdidx == CMD_USER_BUF) { - cmd = USER_CMD_GA(&curbuf->b_ucmds, ea.useridx); - } - - if (cmd != NULL) { - PUT(result, "cmd", CSTR_TO_OBJ((char *)cmd->uc_name)); - } else { - PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx))); - } - - if ((ea.argt & EX_RANGE) && ea.addr_count > 0) { - Array range = ARRAY_DICT_INIT; - if (ea.addr_count > 1) { - ADD(range, INTEGER_OBJ(ea.line1)); - } - ADD(range, INTEGER_OBJ(ea.line2)); - PUT(result, "range", ARRAY_OBJ(range)); - } else { - PUT(result, "range", ARRAY_OBJ(ARRAY_DICT_INIT)); - } - - if (ea.argt & EX_COUNT) { - if (ea.addr_count > 0) { - PUT(result, "count", INTEGER_OBJ(ea.line2)); - } else if (cmd != NULL) { - PUT(result, "count", INTEGER_OBJ(cmd->uc_def)); - } else { - PUT(result, "count", INTEGER_OBJ(0)); - } - } else { - PUT(result, "count", INTEGER_OBJ(-1)); - } - - char reg[2]; - reg[0] = (char)ea.regname; - reg[1] = '\0'; - PUT(result, "reg", CSTR_TO_OBJ(reg)); - - PUT(result, "bang", BOOLEAN_OBJ(ea.forceit)); - PUT(result, "args", ARRAY_OBJ(args)); - - char nargs[2]; - if (ea.argt & EX_EXTRA) { - if (ea.argt & EX_NOSPC) { - if (ea.argt & EX_NEEDARG) { - nargs[0] = '1'; - } else { - nargs[0] = '?'; - } - } else if (ea.argt & EX_NEEDARG) { - nargs[0] = '+'; - } else { - nargs[0] = '*'; - } - } else { - nargs[0] = '0'; - } - nargs[1] = '\0'; - PUT(result, "nargs", CSTR_TO_OBJ(nargs)); - - const char *addr; - switch (ea.addr_type) { - case ADDR_LINES: - addr = "line"; - break; - case ADDR_ARGUMENTS: - addr = "arg"; - break; - case ADDR_BUFFERS: - addr = "buf"; - break; - case ADDR_LOADED_BUFFERS: - addr = "load"; - break; - case ADDR_WINDOWS: - addr = "win"; - break; - case ADDR_TABS: - addr = "tab"; - break; - case ADDR_QUICKFIX: - addr = "qf"; - break; - case ADDR_NONE: - addr = "none"; - break; - default: - addr = "?"; - break; - } - PUT(result, "addr", CSTR_TO_OBJ(addr)); - PUT(result, "nextcmd", CSTR_TO_OBJ((char *)ea.nextcmd)); - - Dictionary mods = ARRAY_DICT_INIT; - PUT(mods, "silent", BOOLEAN_OBJ(cmdinfo.silent)); - PUT(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.emsg_silent)); - PUT(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.sandbox)); - PUT(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.noautocmd)); - PUT(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.tab)); - PUT(mods, "verbose", INTEGER_OBJ(cmdinfo.verbose)); - PUT(mods, "browse", BOOLEAN_OBJ(cmdinfo.cmdmod.browse)); - PUT(mods, "confirm", BOOLEAN_OBJ(cmdinfo.cmdmod.confirm)); - PUT(mods, "hide", BOOLEAN_OBJ(cmdinfo.cmdmod.hide)); - PUT(mods, "keepalt", BOOLEAN_OBJ(cmdinfo.cmdmod.keepalt)); - PUT(mods, "keepjumps", BOOLEAN_OBJ(cmdinfo.cmdmod.keepjumps)); - PUT(mods, "keepmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.keepmarks)); - PUT(mods, "keeppatterns", BOOLEAN_OBJ(cmdinfo.cmdmod.keeppatterns)); - PUT(mods, "lockmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.lockmarks)); - PUT(mods, "noswapfile", BOOLEAN_OBJ(cmdinfo.cmdmod.noswapfile)); - PUT(mods, "vertical", BOOLEAN_OBJ(cmdinfo.cmdmod.split & WSP_VERT)); - - const char *split; - if (cmdinfo.cmdmod.split & WSP_BOT) { - split = "botright"; - } else if (cmdinfo.cmdmod.split & WSP_TOP) { - split = "topleft"; - } else if (cmdinfo.cmdmod.split & WSP_BELOW) { - split = "belowright"; - } else if (cmdinfo.cmdmod.split & WSP_ABOVE) { - split = "aboveleft"; - } else { - split = ""; - } - PUT(mods, "split", CSTR_TO_OBJ(split)); - - PUT(result, "mods", DICTIONARY_OBJ(mods)); - - Dictionary magic = ARRAY_DICT_INIT; - PUT(magic, "file", BOOLEAN_OBJ(cmdinfo.magic.file)); - PUT(magic, "bar", BOOLEAN_OBJ(cmdinfo.magic.bar)); - PUT(result, "magic", DICTIONARY_OBJ(magic)); -end: - xfree(cmdline); - return result; -} - -/// Executes an Ex command. -/// -/// Unlike |nvim_command()| this command takes a structured Dictionary instead of a String. This -/// allows for easier construction and manipulation of an Ex command. This also allows for things -/// such as having spaces inside a command argument, expanding filenames in a command that otherwise -/// doesn't expand filenames, etc. -/// -/// On execution error: fails with VimL error, updates v:errmsg. -/// -/// @see |nvim_exec()| -/// @see |nvim_command()| -/// -/// @param cmd Command to execute. Must be a Dictionary that can contain the same values as -/// the return value of |nvim_parse_cmd()| except "addr", "nargs" and "nextcmd" -/// which are ignored if provided. All values except for "cmd" are optional. -/// @param opts Optional parameters. -/// - output: (boolean, default false) Whether to return command output. -/// @param[out] err Error details, if any. -/// @return Command output (non-error, non-shell |:!|) if `output` is true, else empty string. -String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error *err) - FUNC_API_SINCE(10) -{ - exarg_T ea; - memset(&ea, 0, sizeof(ea)); - ea.verbose_save = -1; - ea.save_msg_silent = -1; - - CmdParseInfo cmdinfo; - memset(&cmdinfo, 0, sizeof(cmdinfo)); - cmdinfo.verbose = -1; - - char *cmdline = NULL; - char *cmdname = NULL; - char **args = NULL; - size_t argc = 0; - - String retv = (String)STRING_INIT; - -#define OBJ_TO_BOOL(var, value, default, varname) \ - do { \ - var = api_object_to_bool(value, varname, default, err); \ - if (ERROR_SET(err)) { \ - goto end; \ - } \ - } while (0) - -#define VALIDATION_ERROR(...) \ - do { \ - api_set_error(err, kErrorTypeValidation, __VA_ARGS__); \ - goto end; \ - } while (0) - - bool output; - OBJ_TO_BOOL(output, opts->output, false, "'output'"); - - // First, parse the command name and check if it exists and is valid. - if (!HAS_KEY(cmd->cmd) || cmd->cmd.type != kObjectTypeString - || cmd->cmd.data.string.data[0] == NUL) { - VALIDATION_ERROR("'cmd' must be a non-empty String"); - } - - cmdname = string_to_cstr(cmd->cmd.data.string); - ea.cmd = cmdname; - - char *p = find_ex_command(&ea, NULL); - - // If this looks like an undefined user command and there are CmdUndefined - // autocommands defined, trigger the matching autocommands. - if (p != NULL && ea.cmdidx == CMD_SIZE && ASCII_ISUPPER(*ea.cmd) - && has_event(EVENT_CMDUNDEFINED)) { - p = xstrdup(cmdname); - int ret = apply_autocmds(EVENT_CMDUNDEFINED, p, p, true, NULL); - xfree(p); - // If the autocommands did something and didn't cause an error, try - // finding the command again. - p = (ret && !aborting()) ? find_ex_command(&ea, NULL) : ea.cmd; - } - - if (p == NULL || ea.cmdidx == CMD_SIZE) { - VALIDATION_ERROR("Command not found: %s", cmdname); - } - if (is_cmd_ni(ea.cmdidx)) { - VALIDATION_ERROR("Command not implemented: %s", cmdname); - } - - // Get the command flags so that we can know what type of arguments the command uses. - // Not required for a user command since `find_ex_command` already deals with it in that case. - if (!IS_USER_CMDIDX(ea.cmdidx)) { - ea.argt = get_cmd_argt(ea.cmdidx); - } - - // Parse command arguments since it's needed to get the command address type. - if (HAS_KEY(cmd->args)) { - if (cmd->args.type != kObjectTypeArray) { - VALIDATION_ERROR("'args' must be an Array"); - } - // Check if every argument is valid - for (size_t i = 0; i < cmd->args.data.array.size; i++) { - Object elem = cmd->args.data.array.items[i]; - if (elem.type != kObjectTypeString) { - VALIDATION_ERROR("Command argument must be a String"); - } else if (string_iswhite(elem.data.string)) { - VALIDATION_ERROR("Command argument must have non-whitespace characters"); - } - } - - argc = cmd->args.data.array.size; - bool argc_valid; - - // Check if correct number of arguments is used. - switch (ea.argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) { - case EX_EXTRA | EX_NOSPC | EX_NEEDARG: - argc_valid = argc == 1; - break; - case EX_EXTRA | EX_NOSPC: - argc_valid = argc <= 1; - break; - case EX_EXTRA | EX_NEEDARG: - argc_valid = argc >= 1; - break; - case EX_EXTRA: - argc_valid = true; - break; - default: - argc_valid = argc == 0; - break; - } - - if (!argc_valid) { - argc = 0; // Ensure that args array isn't erroneously freed at the end. - VALIDATION_ERROR("Incorrect number of arguments supplied"); - } - - if (argc != 0) { - args = xcalloc(argc, sizeof(char *)); - - for (size_t i = 0; i < argc; i++) { - args[i] = string_to_cstr(cmd->args.data.array.items[i].data.string); - } - } - } - - // Simply pass the first argument (if it exists) as the arg pointer to `set_cmd_addr_type()` - // since it only ever checks the first argument. - set_cmd_addr_type(&ea, argc > 0 ? (char_u *)args[0] : NULL); - - if (HAS_KEY(cmd->range)) { - if (!(ea.argt & EX_RANGE)) { - VALIDATION_ERROR("Command cannot accept a range"); - } else if (cmd->range.type != kObjectTypeArray) { - VALIDATION_ERROR("'range' must be an Array"); - } else if (cmd->range.data.array.size > 2) { - VALIDATION_ERROR("'range' cannot contain more than two elements"); - } - - Array range = cmd->range.data.array; - ea.addr_count = (int)range.size; - - for (size_t i = 0; i < range.size; i++) { - Object elem = range.items[i]; - if (elem.type != kObjectTypeInteger || elem.data.integer < 0) { - VALIDATION_ERROR("'range' element must be a non-negative Integer"); - } - } - - if (range.size > 0) { - ea.line1 = (linenr_T)range.items[0].data.integer; - ea.line2 = (linenr_T)range.items[range.size - 1].data.integer; - } - - if (invalid_range(&ea) != NULL) { - VALIDATION_ERROR("Invalid range provided"); - } - } - if (ea.addr_count == 0) { - if (ea.argt & EX_DFLALL) { - set_cmd_dflall_range(&ea); // Default range for range=% - } else { - ea.line1 = ea.line2 = get_cmd_default_range(&ea); // Default range. - - if (ea.addr_type == ADDR_OTHER) { - // Default is 1, not cursor. - ea.line2 = 1; - } - } - } - - if (HAS_KEY(cmd->count)) { - if (!(ea.argt & EX_COUNT)) { - VALIDATION_ERROR("Command cannot accept a count"); - } else if (cmd->count.type != kObjectTypeInteger || cmd->count.data.integer < 0) { - VALIDATION_ERROR("'count' must be a non-negative Integer"); - } - set_cmd_count(&ea, cmd->count.data.integer, true); - } - - if (HAS_KEY(cmd->reg)) { - if (!(ea.argt & EX_REGSTR)) { - VALIDATION_ERROR("Command cannot accept a register"); - } else if (cmd->reg.type != kObjectTypeString || cmd->reg.data.string.size != 1) { - VALIDATION_ERROR("'reg' must be a single character"); - } - char regname = cmd->reg.data.string.data[0]; - if (regname == '=') { - VALIDATION_ERROR("Cannot use register \"="); - } else if (!valid_yank_reg(regname, ea.cmdidx != CMD_put && !IS_USER_CMDIDX(ea.cmdidx))) { - VALIDATION_ERROR("Invalid register: \"%c", regname); - } - ea.regname = (uint8_t)regname; - } - - OBJ_TO_BOOL(ea.forceit, cmd->bang, false, "'bang'"); - if (ea.forceit && !(ea.argt & EX_BANG)) { - VALIDATION_ERROR("Command cannot accept a bang"); - } - - if (HAS_KEY(cmd->magic)) { - if (cmd->magic.type != kObjectTypeDictionary) { - VALIDATION_ERROR("'magic' must be a Dictionary"); - } - - Dict(cmd_magic) magic = { 0 }; - if (!api_dict_to_keydict(&magic, KeyDict_cmd_magic_get_field, - cmd->magic.data.dictionary, err)) { - goto end; - } - - OBJ_TO_BOOL(cmdinfo.magic.file, magic.file, ea.argt & EX_XFILE, "'magic.file'"); - OBJ_TO_BOOL(cmdinfo.magic.bar, magic.bar, ea.argt & EX_TRLBAR, "'magic.bar'"); - } else { - cmdinfo.magic.file = ea.argt & EX_XFILE; - cmdinfo.magic.bar = ea.argt & EX_TRLBAR; - } - - if (HAS_KEY(cmd->mods)) { - if (cmd->mods.type != kObjectTypeDictionary) { - VALIDATION_ERROR("'mods' must be a Dictionary"); - } - - Dict(cmd_mods) mods = { 0 }; - if (!api_dict_to_keydict(&mods, KeyDict_cmd_mods_get_field, cmd->mods.data.dictionary, err)) { - goto end; - } - - if (HAS_KEY(mods.tab)) { - if (mods.tab.type != kObjectTypeInteger || mods.tab.data.integer < 0) { - VALIDATION_ERROR("'mods.tab' must be a non-negative Integer"); - } - cmdinfo.cmdmod.tab = (int)mods.tab.data.integer + 1; - } - - if (HAS_KEY(mods.verbose)) { - if (mods.verbose.type != kObjectTypeInteger) { - VALIDATION_ERROR("'mods.verbose' must be a Integer"); - } else if (mods.verbose.data.integer >= 0) { - // Silently ignore negative integers to allow mods.verbose to be set to -1. - cmdinfo.verbose = mods.verbose.data.integer; - } - } - - bool vertical; - OBJ_TO_BOOL(vertical, mods.vertical, false, "'mods.vertical'"); - cmdinfo.cmdmod.split |= (vertical ? WSP_VERT : 0); - - if (HAS_KEY(mods.split)) { - if (mods.split.type != kObjectTypeString) { - VALIDATION_ERROR("'mods.split' must be a String"); - } - - if (*mods.split.data.string.data == NUL) { - // Empty string, do nothing. - } else if (STRCMP(mods.split.data.string.data, "aboveleft") == 0 - || STRCMP(mods.split.data.string.data, "leftabove") == 0) { - cmdinfo.cmdmod.split |= WSP_ABOVE; - } else if (STRCMP(mods.split.data.string.data, "belowright") == 0 - || STRCMP(mods.split.data.string.data, "rightbelow") == 0) { - cmdinfo.cmdmod.split |= WSP_BELOW; - } else if (STRCMP(mods.split.data.string.data, "topleft") == 0) { - cmdinfo.cmdmod.split |= WSP_TOP; - } else if (STRCMP(mods.split.data.string.data, "botright") == 0) { - cmdinfo.cmdmod.split |= WSP_BOT; - } else { - VALIDATION_ERROR("Invalid value for 'mods.split'"); - } - } - - OBJ_TO_BOOL(cmdinfo.silent, mods.silent, false, "'mods.silent'"); - OBJ_TO_BOOL(cmdinfo.emsg_silent, mods.emsg_silent, false, "'mods.emsg_silent'"); - OBJ_TO_BOOL(cmdinfo.sandbox, mods.sandbox, false, "'mods.sandbox'"); - OBJ_TO_BOOL(cmdinfo.noautocmd, mods.noautocmd, false, "'mods.noautocmd'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.browse, mods.browse, false, "'mods.browse'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.confirm, mods.confirm, false, "'mods.confirm'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.hide, mods.hide, false, "'mods.hide'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.keepalt, mods.keepalt, false, "'mods.keepalt'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.keepjumps, mods.keepjumps, false, "'mods.keepjumps'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.keepmarks, mods.keepmarks, false, "'mods.keepmarks'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.keeppatterns, mods.keeppatterns, false, "'mods.keeppatterns'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.lockmarks, mods.lockmarks, false, "'mods.lockmarks'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.noswapfile, mods.noswapfile, false, "'mods.noswapfile'"); - - if (cmdinfo.sandbox && !(ea.argt & EX_SBOXOK)) { - VALIDATION_ERROR("Command cannot be run in sandbox"); - } - } - - // Finally, build the command line string that will be stored inside ea.cmdlinep. - // This also sets the values of ea.cmd, ea.arg, ea.args and ea.arglens. - build_cmdline_str(&cmdline, &ea, &cmdinfo, args, argc); - ea.cmdlinep = &cmdline; - - garray_T capture_local; - const int save_msg_silent = msg_silent; - garray_T * const save_capture_ga = capture_ga; - - if (output) { - ga_init(&capture_local, 1, 80); - capture_ga = &capture_local; - } - - TRY_WRAP({ - try_start(); - if (output) { - msg_silent++; - } - - WITH_SCRIPT_CONTEXT(channel_id, { - execute_cmd(&ea, &cmdinfo, false); - }); - - if (output) { - capture_ga = save_capture_ga; - msg_silent = save_msg_silent; - } - - try_end(err); - }); - - if (ERROR_SET(err)) { - goto clear_ga; - } - - if (output && capture_local.ga_len > 1) { - retv = (String){ - .data = capture_local.ga_data, - .size = (size_t)capture_local.ga_len, - }; - // redir usually (except :echon) prepends a newline. - if (retv.data[0] == '\n') { - memmove(retv.data, retv.data + 1, retv.size - 1); - retv.data[retv.size - 1] = '\0'; - retv.size = retv.size - 1; - } - goto end; - } -clear_ga: - if (output) { - ga_clear(&capture_local); - } -end: - xfree(cmdline); - xfree(cmdname); - xfree(ea.args); - xfree(ea.arglens); - for (size_t i = 0; i < argc; i++) { - xfree(args[i]); - } - xfree(args); - - return retv; - -#undef OBJ_TO_BOOL -#undef VALIDATION_ERROR -} -- cgit From da41ca299f52d4e08a34344359c250a3058fd3c6 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 13 Jun 2022 17:13:46 +0800 Subject: vim-patch:8.2.1897: command modifiers are saved and set inconsistently Problem: Command modifiers are saved and set inconsistently. Solution: Separate parsing and applying command modifiers. Save values in cmdmod_T. https://github.com/vim/vim/commit/5661ed6c833e05467cab33cb9b1c535e7e5cc570 Cherry-pick: :0verbose fix from patch 8.2.4741 --- src/nvim/api/command.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index a7115c09a3..26aa26004f 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -284,8 +284,6 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error { exarg_T ea; memset(&ea, 0, sizeof(ea)); - ea.verbose_save = -1; - ea.save_msg_silent = -1; CmdParseInfo cmdinfo; memset(&cmdinfo, 0, sizeof(cmdinfo)); @@ -514,9 +512,9 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error if (HAS_KEY(mods.verbose)) { if (mods.verbose.type != kObjectTypeInteger) { VALIDATION_ERROR("'mods.verbose' must be a Integer"); - } else if (mods.verbose.data.integer >= 0) { + } else if ((int)mods.verbose.data.integer >= 0) { // Silently ignore negative integers to allow mods.verbose to be set to -1. - cmdinfo.verbose = mods.verbose.data.integer; + cmdinfo.verbose = (int)mods.verbose.data.integer; } } @@ -662,7 +660,7 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin kv_printf(cmdline, "%dtab ", cmdinfo->cmdmod.tab - 1); } if (cmdinfo->verbose != -1) { - kv_printf(cmdline, "%ldverbose ", cmdinfo->verbose); + kv_printf(cmdline, "%dverbose ", cmdinfo->verbose); } if (cmdinfo->emsg_silent) { -- cgit From dad898b665b77feaec57acc2812faa7ff0ab619e Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 13 Jun 2022 20:04:31 +0800 Subject: refactor(api): remove redundant fields of CmdParseInfo --- src/nvim/api/command.c | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index 26aa26004f..699f4f157f 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -218,10 +218,10 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) PUT(result, "nextcmd", CSTR_TO_OBJ((char *)ea.nextcmd)); Dictionary mods = ARRAY_DICT_INIT; - PUT(mods, "silent", BOOLEAN_OBJ(cmdinfo.silent)); - PUT(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.emsg_silent)); - PUT(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.sandbox)); - PUT(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.noautocmd)); + PUT(mods, "silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SILENT)); + PUT(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_ERRSILENT)); + PUT(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX)); + PUT(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_NOAUTOCMD)); PUT(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.tab)); PUT(mods, "verbose", INTEGER_OBJ(cmdinfo.verbose)); PUT(mods, "browse", BOOLEAN_OBJ(cmdinfo.cmdmod.browse)); @@ -298,7 +298,17 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error #define OBJ_TO_BOOL(var, value, default, varname) \ do { \ - var = api_object_to_bool(value, varname, default, err); \ + (var) = api_object_to_bool(value, varname, default, err); \ + if (ERROR_SET(err)) { \ + goto end; \ + } \ + } while (0) + +#define OBJ_TO_CMOD_FLAG(flag, value, default, varname) \ + do { \ + if (api_object_to_bool(value, varname, default, err)) { \ + cmdinfo.cmdmod.cmod_flags |= (flag); \ + } \ if (ERROR_SET(err)) { \ goto end; \ } \ @@ -544,10 +554,10 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error } } - OBJ_TO_BOOL(cmdinfo.silent, mods.silent, false, "'mods.silent'"); - OBJ_TO_BOOL(cmdinfo.emsg_silent, mods.emsg_silent, false, "'mods.emsg_silent'"); - OBJ_TO_BOOL(cmdinfo.sandbox, mods.sandbox, false, "'mods.sandbox'"); - OBJ_TO_BOOL(cmdinfo.noautocmd, mods.noautocmd, false, "'mods.noautocmd'"); + OBJ_TO_CMOD_FLAG(CMOD_SILENT, mods.silent, false, "'mods.silent'"); + OBJ_TO_CMOD_FLAG(CMOD_ERRSILENT, mods.emsg_silent, false, "'mods.emsg_silent'"); + OBJ_TO_CMOD_FLAG(CMOD_SANDBOX, mods.sandbox, false, "'mods.sandbox'"); + OBJ_TO_CMOD_FLAG(CMOD_NOAUTOCMD, mods.noautocmd, false, "'mods.noautocmd'"); OBJ_TO_BOOL(cmdinfo.cmdmod.browse, mods.browse, false, "'mods.browse'"); OBJ_TO_BOOL(cmdinfo.cmdmod.confirm, mods.confirm, false, "'mods.confirm'"); OBJ_TO_BOOL(cmdinfo.cmdmod.hide, mods.hide, false, "'mods.hide'"); @@ -558,7 +568,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error OBJ_TO_BOOL(cmdinfo.cmdmod.lockmarks, mods.lockmarks, false, "'mods.lockmarks'"); OBJ_TO_BOOL(cmdinfo.cmdmod.noswapfile, mods.noswapfile, false, "'mods.noswapfile'"); - if (cmdinfo.sandbox && !(ea.argt & EX_SBOXOK)) { + if ((cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX) && !(ea.argt & EX_SBOXOK)) { VALIDATION_ERROR("Command cannot be run in sandbox"); } } @@ -629,6 +639,7 @@ end: return retv; #undef OBJ_TO_BOOL +#undef OBJ_TO_CMOD_FLAG #undef VALIDATION_ERROR } @@ -663,9 +674,9 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin kv_printf(cmdline, "%dverbose ", cmdinfo->verbose); } - if (cmdinfo->emsg_silent) { + if (cmdinfo->cmdmod.cmod_flags & CMOD_ERRSILENT) { kv_concat(cmdline, "silent! "); - } else if (cmdinfo->silent) { + } else if (cmdinfo->cmdmod.cmod_flags & CMOD_SILENT) { kv_concat(cmdline, "silent "); } @@ -694,8 +705,8 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin } while (0) CMDLINE_APPEND_IF(cmdinfo->cmdmod.split & WSP_VERT, "vertical "); - CMDLINE_APPEND_IF(cmdinfo->sandbox, "sandbox "); - CMDLINE_APPEND_IF(cmdinfo->noautocmd, "noautocmd "); + CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_SANDBOX, "sandbox "); + CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_NOAUTOCMD, "noautocmd "); CMDLINE_APPEND_IF(cmdinfo->cmdmod.browse, "browse "); CMDLINE_APPEND_IF(cmdinfo->cmdmod.confirm, "confirm "); CMDLINE_APPEND_IF(cmdinfo->cmdmod.hide, "hide "); -- cgit From 6130b4a84b41b71f4c0c58093a29585c6c67ff16 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 13 Jun 2022 20:20:34 +0800 Subject: vim-patch:8.2.1898: command modifier parsing always uses global cmdmod Problem: Command modifier parsing always uses global cmdmod. Solution: Pass in cmdmod_T to use. Rename struct fields consistently. https://github.com/vim/vim/commit/e10044015841711b989f9a898d427bcc1fdb4c32 --- src/nvim/api/command.c | 86 +++++++++++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 43 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index 699f4f157f..f48843cbbb 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -222,27 +222,27 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) PUT(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_ERRSILENT)); PUT(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX)); PUT(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_NOAUTOCMD)); - PUT(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.tab)); + PUT(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.cmod_tab)); PUT(mods, "verbose", INTEGER_OBJ(cmdinfo.verbose)); - PUT(mods, "browse", BOOLEAN_OBJ(cmdinfo.cmdmod.browse)); - PUT(mods, "confirm", BOOLEAN_OBJ(cmdinfo.cmdmod.confirm)); - PUT(mods, "hide", BOOLEAN_OBJ(cmdinfo.cmdmod.hide)); - PUT(mods, "keepalt", BOOLEAN_OBJ(cmdinfo.cmdmod.keepalt)); - PUT(mods, "keepjumps", BOOLEAN_OBJ(cmdinfo.cmdmod.keepjumps)); - PUT(mods, "keepmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.keepmarks)); - PUT(mods, "keeppatterns", BOOLEAN_OBJ(cmdinfo.cmdmod.keeppatterns)); - PUT(mods, "lockmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.lockmarks)); - PUT(mods, "noswapfile", BOOLEAN_OBJ(cmdinfo.cmdmod.noswapfile)); - PUT(mods, "vertical", BOOLEAN_OBJ(cmdinfo.cmdmod.split & WSP_VERT)); + PUT(mods, "browse", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_BROWSE)); + PUT(mods, "confirm", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_CONFIRM)); + PUT(mods, "hide", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_HIDE)); + PUT(mods, "keepalt", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPALT)); + PUT(mods, "keepjumps", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPJUMPS)); + PUT(mods, "keepmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPMARKS)); + PUT(mods, "keeppatterns", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPPATTERNS)); + PUT(mods, "lockmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_LOCKMARKS)); + PUT(mods, "noswapfile", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_NOSWAPFILE)); + PUT(mods, "vertical", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_split & WSP_VERT)); const char *split; - if (cmdinfo.cmdmod.split & WSP_BOT) { + if (cmdinfo.cmdmod.cmod_split & WSP_BOT) { split = "botright"; - } else if (cmdinfo.cmdmod.split & WSP_TOP) { + } else if (cmdinfo.cmdmod.cmod_split & WSP_TOP) { split = "topleft"; - } else if (cmdinfo.cmdmod.split & WSP_BELOW) { + } else if (cmdinfo.cmdmod.cmod_split & WSP_BELOW) { split = "belowright"; - } else if (cmdinfo.cmdmod.split & WSP_ABOVE) { + } else if (cmdinfo.cmdmod.cmod_split & WSP_ABOVE) { split = "aboveleft"; } else { split = ""; @@ -516,7 +516,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error if (mods.tab.type != kObjectTypeInteger || mods.tab.data.integer < 0) { VALIDATION_ERROR("'mods.tab' must be a non-negative Integer"); } - cmdinfo.cmdmod.tab = (int)mods.tab.data.integer + 1; + cmdinfo.cmdmod.cmod_tab = (int)mods.tab.data.integer + 1; } if (HAS_KEY(mods.verbose)) { @@ -530,7 +530,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error bool vertical; OBJ_TO_BOOL(vertical, mods.vertical, false, "'mods.vertical'"); - cmdinfo.cmdmod.split |= (vertical ? WSP_VERT : 0); + cmdinfo.cmdmod.cmod_split |= (vertical ? WSP_VERT : 0); if (HAS_KEY(mods.split)) { if (mods.split.type != kObjectTypeString) { @@ -541,14 +541,14 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error // Empty string, do nothing. } else if (STRCMP(mods.split.data.string.data, "aboveleft") == 0 || STRCMP(mods.split.data.string.data, "leftabove") == 0) { - cmdinfo.cmdmod.split |= WSP_ABOVE; + cmdinfo.cmdmod.cmod_split |= WSP_ABOVE; } else if (STRCMP(mods.split.data.string.data, "belowright") == 0 || STRCMP(mods.split.data.string.data, "rightbelow") == 0) { - cmdinfo.cmdmod.split |= WSP_BELOW; + cmdinfo.cmdmod.cmod_split |= WSP_BELOW; } else if (STRCMP(mods.split.data.string.data, "topleft") == 0) { - cmdinfo.cmdmod.split |= WSP_TOP; + cmdinfo.cmdmod.cmod_split |= WSP_TOP; } else if (STRCMP(mods.split.data.string.data, "botright") == 0) { - cmdinfo.cmdmod.split |= WSP_BOT; + cmdinfo.cmdmod.cmod_split |= WSP_BOT; } else { VALIDATION_ERROR("Invalid value for 'mods.split'"); } @@ -558,15 +558,15 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error OBJ_TO_CMOD_FLAG(CMOD_ERRSILENT, mods.emsg_silent, false, "'mods.emsg_silent'"); OBJ_TO_CMOD_FLAG(CMOD_SANDBOX, mods.sandbox, false, "'mods.sandbox'"); OBJ_TO_CMOD_FLAG(CMOD_NOAUTOCMD, mods.noautocmd, false, "'mods.noautocmd'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.browse, mods.browse, false, "'mods.browse'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.confirm, mods.confirm, false, "'mods.confirm'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.hide, mods.hide, false, "'mods.hide'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.keepalt, mods.keepalt, false, "'mods.keepalt'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.keepjumps, mods.keepjumps, false, "'mods.keepjumps'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.keepmarks, mods.keepmarks, false, "'mods.keepmarks'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.keeppatterns, mods.keeppatterns, false, "'mods.keeppatterns'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.lockmarks, mods.lockmarks, false, "'mods.lockmarks'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.noswapfile, mods.noswapfile, false, "'mods.noswapfile'"); + OBJ_TO_CMOD_FLAG(CMOD_BROWSE, mods.browse, false, "'mods.browse'"); + OBJ_TO_CMOD_FLAG(CMOD_CONFIRM, mods.confirm, false, "'mods.confirm'"); + OBJ_TO_CMOD_FLAG(CMOD_HIDE, mods.hide, false, "'mods.hide'"); + OBJ_TO_CMOD_FLAG(CMOD_KEEPALT, mods.keepalt, false, "'mods.keepalt'"); + OBJ_TO_CMOD_FLAG(CMOD_KEEPJUMPS, mods.keepjumps, false, "'mods.keepjumps'"); + OBJ_TO_CMOD_FLAG(CMOD_KEEPMARKS, mods.keepmarks, false, "'mods.keepmarks'"); + OBJ_TO_CMOD_FLAG(CMOD_KEEPPATTERNS, mods.keeppatterns, false, "'mods.keeppatterns'"); + OBJ_TO_CMOD_FLAG(CMOD_LOCKMARKS, mods.lockmarks, false, "'mods.lockmarks'"); + OBJ_TO_CMOD_FLAG(CMOD_NOSWAPFILE, mods.noswapfile, false, "'mods.noswapfile'"); if ((cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX) && !(ea.argt & EX_SBOXOK)) { VALIDATION_ERROR("Command cannot be run in sandbox"); @@ -667,8 +667,8 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin StringBuilder cmdline = KV_INITIAL_VALUE; // Add command modifiers - if (cmdinfo->cmdmod.tab != 0) { - kv_printf(cmdline, "%dtab ", cmdinfo->cmdmod.tab - 1); + if (cmdinfo->cmdmod.cmod_tab != 0) { + kv_printf(cmdline, "%dtab ", cmdinfo->cmdmod.cmod_tab - 1); } if (cmdinfo->verbose != -1) { kv_printf(cmdline, "%dverbose ", cmdinfo->verbose); @@ -680,7 +680,7 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin kv_concat(cmdline, "silent "); } - switch (cmdinfo->cmdmod.split & (WSP_ABOVE | WSP_BELOW | WSP_TOP | WSP_BOT)) { + switch (cmdinfo->cmdmod.cmod_split & (WSP_ABOVE | WSP_BELOW | WSP_TOP | WSP_BOT)) { case WSP_ABOVE: kv_concat(cmdline, "aboveleft "); break; @@ -704,18 +704,18 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin } \ } while (0) - CMDLINE_APPEND_IF(cmdinfo->cmdmod.split & WSP_VERT, "vertical "); + CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_split & WSP_VERT, "vertical "); CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_SANDBOX, "sandbox "); CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_NOAUTOCMD, "noautocmd "); - CMDLINE_APPEND_IF(cmdinfo->cmdmod.browse, "browse "); - CMDLINE_APPEND_IF(cmdinfo->cmdmod.confirm, "confirm "); - CMDLINE_APPEND_IF(cmdinfo->cmdmod.hide, "hide "); - CMDLINE_APPEND_IF(cmdinfo->cmdmod.keepalt, "keepalt "); - CMDLINE_APPEND_IF(cmdinfo->cmdmod.keepjumps, "keepjumps "); - CMDLINE_APPEND_IF(cmdinfo->cmdmod.keepmarks, "keepmarks "); - CMDLINE_APPEND_IF(cmdinfo->cmdmod.keeppatterns, "keeppatterns "); - CMDLINE_APPEND_IF(cmdinfo->cmdmod.lockmarks, "lockmarks "); - CMDLINE_APPEND_IF(cmdinfo->cmdmod.noswapfile, "noswapfile "); + CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_BROWSE, "browse "); + CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_CONFIRM, "confirm "); + CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_HIDE, "hide "); + CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_KEEPALT, "keepalt "); + CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_KEEPJUMPS, "keepjumps "); + CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_KEEPMARKS, "keepmarks "); + CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_KEEPPATTERNS, "keeppatterns "); + CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_LOCKMARKS, "lockmarks "); + CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_NOSWAPFILE, "noswapfile "); #undef CMDLINE_APPEND_IF // Command range / count. -- cgit From 0a0cda95286bf62fbce2776a0c0081cea39a88a8 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 14 Jun 2022 20:46:18 +0800 Subject: vim-patch:8.2.5088: value of cmod_verbose is a bit complicated to use Problem: Value of cmod_verbose is a bit complicated to use. Solution: Use zero for not set, value + 1 when set. (closes vim/vim#10564) https://github.com/vim/vim/commit/cd7496382efc9e6748326c6cda7f01003fa07063 Omit has_cmdmod(): only used for Vim9 script --- src/nvim/api/command.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index f48843cbbb..0d2e013aac 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -223,7 +223,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) PUT(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX)); PUT(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_NOAUTOCMD)); PUT(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.cmod_tab)); - PUT(mods, "verbose", INTEGER_OBJ(cmdinfo.verbose)); + PUT(mods, "verbose", INTEGER_OBJ(cmdinfo.cmdmod.cmod_verbose - 1)); PUT(mods, "browse", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_BROWSE)); PUT(mods, "confirm", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_CONFIRM)); PUT(mods, "hide", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_HIDE)); @@ -287,7 +287,6 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error CmdParseInfo cmdinfo; memset(&cmdinfo, 0, sizeof(cmdinfo)); - cmdinfo.verbose = -1; char *cmdline = NULL; char *cmdname = NULL; @@ -524,7 +523,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error VALIDATION_ERROR("'mods.verbose' must be a Integer"); } else if ((int)mods.verbose.data.integer >= 0) { // Silently ignore negative integers to allow mods.verbose to be set to -1. - cmdinfo.verbose = (int)mods.verbose.data.integer; + cmdinfo.cmdmod.cmod_verbose = (int)mods.verbose.data.integer + 1; } } @@ -670,8 +669,8 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin if (cmdinfo->cmdmod.cmod_tab != 0) { kv_printf(cmdline, "%dtab ", cmdinfo->cmdmod.cmod_tab - 1); } - if (cmdinfo->verbose != -1) { - kv_printf(cmdline, "%dverbose ", cmdinfo->verbose); + if (cmdinfo->cmdmod.cmod_verbose > 0) { + kv_printf(cmdline, "%dverbose ", cmdinfo->cmdmod.cmod_verbose - 1); } if (cmdinfo->cmdmod.cmod_flags & CMOD_ERRSILENT) { -- cgit From 6de7f32d52822c3c09d24720efc65efe97a6e698 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Wed, 15 Jun 2022 02:49:54 +0200 Subject: docs: fix typos (#18866) docs: fix typos and similarly insignificant changes Co-authored-by: zeertzjq Co-authored-by: smjonas Co-authored-by: kanreki <32443233+kanreki@users.noreply.github.com> --- src/nvim/api/command.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index 0d2e013aac..141e1256ff 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -650,7 +650,7 @@ static bool string_iswhite(String str) // Found a non-whitespace character return false; } else if (str.data[i] == NUL) { - // Terminate at first occurence of a NUL character + // Terminate at first occurrence of a NUL character break; } } -- cgit From 8f065205946844d87f00d6c55517521e3809f821 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 23 May 2022 21:44:15 -0700 Subject: feat(logging): include test-id in log messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: 1. Log messages (especially in CI) are hard to correlate with tests. 2. Since b353a5c05f02 #11886, dumplog() prints the logs next to test failures. This is noisy and gets in the way of the test results. Solution: 1. Associate an incrementing id with each test and include it in log messages. - FUTURE: add v:name so Nvim instances can be formally "named"? 2. Mention "child" in log messages if the current Nvim is a child (based on the presence of $NVIM). BEFORE: DBG … 12345 UI: event DBG … 12345 log_server_msg:722: RPC ->ch 1: … DBG … 12345 UI: flush DBG … 12345 inbuf_poll:444: blocking... events_enabled=1 events_pending=0 DBG … 23454 UI: stop INF … 23454 os_exit:594: Nvim exit: 0 AFTER: DBG … T57 UI: event DBG … T57 log_server_msg:722: RPC ->ch 1: … DBG … T57 UI: flush DBG … T57 inbuf_poll:444: blocking... events_enabled=1 events_pending=0 DBG … T57/child UI: stop INF … T57/child os_exit:594: Nvim exit: 0 --- src/nvim/api/vim.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 77b4900f4f..a60a069fae 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1790,8 +1790,9 @@ Dictionary nvim__stats(void) { Dictionary rv = ARRAY_DICT_INIT; PUT(rv, "fsync", INTEGER_OBJ(g_stats.fsync)); - PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw)); + PUT(rv, "log_skip", INTEGER_OBJ(g_stats.log_skip)); PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_get_global_ref_count())); + PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw)); return rv; } -- cgit From ff6b8f54359037790b300cb06a025f84f11d829a Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 18 Jun 2022 18:53:12 +0200 Subject: fix(terminal): coverity USE_AFTER_FREE #18978 Problem: Coverity reports use after free: *** CID 352784: Memory - illegal accesses (USE_AFTER_FREE) /src/nvim/buffer.c: 1508 in set_curbuf() 1502 if (old_tw != curbuf->b_p_tw) { 1503 check_colorcolumn(curwin); 1504 } 1505 } 1506 1507 if (bufref_valid(&prevbufref) && prevbuf->terminal != NULL) { >>> CID 352784: Memory - illegal accesses (USE_AFTER_FREE) >>> Calling "terminal_check_size" dereferences freed pointer "prevbuf->terminal". 1508 terminal_check_size(prevbuf->terminal); 1509 } 1510 } 1511 1512 /// Enter a new current buffer. 1513 /// Old curbuf must have been abandoned already! This also means "curbuf" may Solution: Change terminal_destroy and terminal_close to set caller storage to NULL, similar to XFREE_CLEAR. This aligns with the pattern found already in: terminal_destroy e897ccad3eb1e term_delayed_free 3e59c1e20d605 --- src/nvim/api/vim.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index a60a069fae..3a24f2b405 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1025,8 +1025,7 @@ static void term_resize(uint16_t width, uint16_t height, void *data) static void term_close(void *data) { Channel *chan = data; - terminal_destroy(chan->term); - chan->term = NULL; + terminal_destroy(&chan->term); api_free_luaref(chan->stream.internal.cb); chan->stream.internal.cb = LUA_NOREF; channel_decref(chan); -- cgit From 5d6987210578f5f1c3151988b99a9411f9603374 Mon Sep 17 00:00:00 2001 From: bfredl Date: Wed, 8 Jun 2022 22:02:02 +0200 Subject: perf(ui): reduce allocation overhead when encoding "redraw" events Note for external UIs: Nvim can now emit multiple "redraw" event batches before a final "flush" event is received. To retain existing behavior, clients should make sure to update visible state at an explicit "flush" event, not just the end of a "redraw" batch of event. * Get rid of copy_object() blizzard in the auto-generated ui_event layer * Special case "grid_line" by encoding screen state directly to msgpack events with no intermediate API events. * Get rid of the arcane notion of referring to the screen as the "shell" * Array and Dictionary are kvec_t:s, so define them as such. * Allow kvec_t:s, such as Arrays and Dictionaries, to be allocated with a predetermined size within an arena. * Eliminate redundant capacity checking when filling such kvec_t:s with values. --- src/nvim/api/private/defs.h | 15 +- src/nvim/api/private/helpers.c | 26 +++ src/nvim/api/private/helpers.h | 14 ++ src/nvim/api/ui.c | 491 +++++++++++++++++++++++++++++++---------- src/nvim/api/ui_events.in.h | 2 +- 5 files changed, 414 insertions(+), 134 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index 396fab721d..b1e0dd364c 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -6,9 +6,10 @@ #include #include "nvim/func_attr.h" +#include "nvim/lib/kvec.h" #include "nvim/types.h" -#define ARRAY_DICT_INIT { .size = 0, .capacity = 0, .items = NULL } +#define ARRAY_DICT_INIT KV_INITIAL_VALUE #define STRING_INIT { .data = NULL, .size = 0 } #define OBJECT_INIT { .type = kObjectTypeNil } #define ERROR_INIT { .type = kErrorTypeNone, .msg = NULL } @@ -84,18 +85,10 @@ REMOTE_TYPE(Window); REMOTE_TYPE(Tabpage); typedef struct object Object; - -typedef struct { - Object *items; - size_t size, capacity; -} Array; +typedef kvec_t(Object) Array; typedef struct key_value_pair KeyValuePair; - -typedef struct { - KeyValuePair *items; - size_t size, capacity; -} Dictionary; +typedef kvec_t(KeyValuePair) Dictionary; typedef enum { kObjectTypeNil = 0, diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index ff6a4c37e8..693d946088 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -669,6 +669,32 @@ void api_free_string(String value) xfree(value.data); } +Array arena_array(Arena *arena, size_t max_size) +{ + Array arr = ARRAY_DICT_INIT; + kv_fixsize_arena(arena, arr, max_size); + return arr; +} + +Dictionary arena_dict(Arena *arena, size_t max_size) +{ + Dictionary dict = ARRAY_DICT_INIT; + kv_fixsize_arena(arena, dict, max_size); + return dict; +} + +String arena_string(Arena *arena, String str) +{ + if (str.size) { + char *mem = arena_alloc(arena, str.size + 1, false); + memcpy(mem, str.data, str.size); + mem[str.size] = NUL; + return cbuf_as_string(mem, str.size); + } else { + return (String)STRING_INIT; + } +} + void api_free_object(Object value) { switch (value.type) { diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 8423db4970..a4348d8b44 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -63,17 +63,31 @@ #define PUT(dict, k, v) \ kv_push(dict, ((KeyValuePair) { .key = cstr_to_string(k), .value = v })) +#define PUT_C(dict, k, v) \ + kv_push_c(dict, ((KeyValuePair) { .key = cstr_as_string(k), .value = v })) + #define PUT_BOOL(dict, name, condition) PUT(dict, name, BOOLEAN_OBJ(condition)); #define ADD(array, item) \ kv_push(array, item) +#define ADD_C(array, item) \ + kv_push_c(array, item) + #define FIXED_TEMP_ARRAY(name, fixsize) \ Array name = ARRAY_DICT_INIT; \ Object name##__items[fixsize]; \ name.size = fixsize; \ name.items = name##__items; \ +#define MAXSIZE_TEMP_ARRAY(name, maxsize) \ + Array name = ARRAY_DICT_INIT; \ + Object name##__items[maxsize]; \ + name.capacity = maxsize; \ + name.items = name##__items; \ + +#define cbuf_as_string(d, s) ((String) { .data = d, .size = s }) + #define STATIC_CSTR_AS_STRING(s) ((String) { .data = s, .size = sizeof(s) - 1 }) /// Create a new String instance, putting data in allocated memory diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index dc04eedebc..54ce838b9b 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -9,11 +9,13 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/api/ui.h" +#include "nvim/channel.h" #include "nvim/cursor_shape.h" #include "nvim/highlight.h" #include "nvim/map.h" #include "nvim/memory.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/msgpack_rpc/helpers.h" #include "nvim/option.h" #include "nvim/popupmnu.h" #include "nvim/screen.h" @@ -21,14 +23,32 @@ #include "nvim/vim.h" #include "nvim/window.h" -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "api/ui.c.generated.h" -# include "ui_events_remote.generated.h" -#endif - typedef struct { uint64_t channel_id; - Array buffer; + +#define UI_BUF_SIZE 4096 ///< total buffer size for pending msgpack data. + /// guranteed size available for each new event (so packing of simple events + /// and the header of grid_line will never fail) +#define EVENT_BUF_SIZE 256 + char buf[UI_BUF_SIZE]; ///< buffer of packed but not yet sent msgpack data + char *buf_wptr; ///< write head of buffer + const char *cur_event; ///< name of current event (might get multiple arglists) + Array call_buf; ///< buffer for constructing a single arg list (max 16 elements!) + + // state for write_cb, while packing a single arglist to msgpack. This + // might fail due to buffer overflow. + size_t pack_totlen; + bool buf_overflow; + char *temp_buf; + + // We start packing the two outermost msgpack arrays before knowing the total + // number of elements. Thus track the location where array size will need + // to be written in the msgpack buffer, once the specifc array is finished. + char *nevents_pos; + char *ncalls_pos; + uint32_t nevents; ///< number of distinct events (top-level args to "redraw" + uint32_t ncalls; ///< number of calls made to the current event (plus one for the name!) + bool flushed_events; ///< events where sent to client without "flush" event int hl_id; // Current highlight for legacy put event. Integer cursor_row, cursor_col; // Intended visible cursor position. @@ -38,8 +58,76 @@ typedef struct { bool wildmenu_active; } UIData; +#define BUF_POS(data) ((size_t)((data)->buf_wptr - (data)->buf)) + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "api/ui.c.generated.h" +# include "ui_events_remote.generated.h" +#endif + static PMap(uint64_t) connected_uis = MAP_INIT; +#define mpack_w(b, byte) *(*b)++ = (char)(byte); +static void mpack_w2(char **b, uint32_t v) +{ + *(*b)++ = (char)((v >> 8) & 0xff); + *(*b)++ = (char)(v & 0xff); +} + +static void mpack_w4(char **b, uint32_t v) +{ + *(*b)++ = (char)((v >> 24) & 0xff); + *(*b)++ = (char)((v >> 16) & 0xff); + *(*b)++ = (char)((v >> 8) & 0xff); + *(*b)++ = (char)(v & 0xff); +} + +static void mpack_uint(char **buf, uint32_t val) +{ + if (val > 0xffff) { + mpack_w(buf, 0xce); + mpack_w4(buf, val); + } else if (val > 0xff) { + mpack_w(buf, 0xcd); + mpack_w2(buf, val); + } else if (val > 0x7f) { + mpack_w(buf, 0xcc); + mpack_w(buf, val); + } else { + mpack_w(buf, val); + } +} + +static void mpack_array(char **buf, uint32_t len) +{ + if (len < 0x10) { + mpack_w(buf, 0x90 | len); + } else if (len < 0x10000) { + mpack_w(buf, 0xdc); + mpack_w2(buf, len); + } else { + mpack_w(buf, 0xdd); + mpack_w4(buf, len); + } +} + +static char *mpack_array_dyn16(char **buf) +{ + mpack_w(buf, 0xdc); + char *pos = *buf; + mpack_w2(buf, 0xFFEF); + return pos; +} + +static void mpack_str(char **buf, const char *str) +{ + assert(sizeof(schar_T) - 1 < 0x20); + size_t len = STRLEN(str); + mpack_w(buf, 0xa0 | len); + memcpy(*buf, str, len); + *buf += len; +} + void remote_ui_disconnect(uint64_t channel_id) { UI *ui = pmap_get(uint64_t)(&connected_uis, channel_id); @@ -47,9 +135,9 @@ void remote_ui_disconnect(uint64_t channel_id) return; } UIData *data = ui->data; - api_free_array(data->buffer); // Destroy pending screen updates. + kv_destroy(data->call_buf); pmap_del(uint64_t)(&connected_uis, channel_id); - xfree(ui->data); + xfree(data); ui->data = NULL; // Flag UI as "stopped". ui_detach_impl(ui, channel_id); xfree(ui); @@ -159,10 +247,19 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona UIData *data = xmalloc(sizeof(UIData)); data->channel_id = channel_id; - data->buffer = (Array)ARRAY_DICT_INIT; + data->cur_event = NULL; data->hl_id = 0; data->client_col = -1; + data->nevents_pos = NULL; + data->nevents = 0; + data->flushed_events = false; + data->ncalls_pos = NULL; + data->ncalls = 0; + data->buf_wptr = data->buf; + data->temp_buf = NULL; data->wildmenu_active = false; + data->call_buf = (Array)ARRAY_DICT_INIT; + kv_ensure_space(data->call_buf, 16); ui->data = data; pmap_put(uint64_t)(&connected_uis, channel_id, ui); @@ -433,34 +530,128 @@ void nvim_ui_pum_set_bounds(uint64_t channel_id, Float width, Float height, Floa ui->pum_pos = true; } -/// Pushes data into UI.UIData, to be consumed later by remote_ui_flush(). -static void push_call(UI *ui, const char *name, Array args) +static void flush_event(UIData *data) +{ + if (data->cur_event) { + mpack_w2(&data->ncalls_pos, data->ncalls); + data->cur_event = NULL; + } + if (!data->nevents_pos) { + assert(BUF_POS(data) == 0); + char **buf = &data->buf_wptr; + // [2, "redraw", [...]] + mpack_array(buf, 3); + mpack_uint(buf, 2); + mpack_str(buf, "redraw"); + data->nevents_pos = mpack_array_dyn16(buf); + } +} + +static inline int write_cb(void *vdata, const char *buf, size_t len) +{ + UIData *data = (UIData *)vdata; + if (!buf) { + return 0; + } + + data->pack_totlen += len; + if (!data->temp_buf && UI_BUF_SIZE - BUF_POS(data) < len) { + data->buf_overflow = true; + return 0; + } + + memcpy(data->buf_wptr, buf, len); + data->buf_wptr += len; + + return 0; +} + +static bool prepare_call(UI *ui, const char *name) { - Array call = ARRAY_DICT_INIT; UIData *data = ui->data; - // To optimize data transfer(especially for "put"), we bundle adjacent + if (BUF_POS(data) > UI_BUF_SIZE - EVENT_BUF_SIZE) { + remote_ui_flush_buf(ui); + } + + // To optimize data transfer(especially for "grid_line"), we bundle adjacent // calls to same method together, so only add a new call entry if the last // method call is different from "name" - if (kv_size(data->buffer)) { - call = kv_A(data->buffer, kv_size(data->buffer) - 1).data.array; - } - if (!kv_size(call) || strcmp(kv_A(call, 0).data.string.data, name)) { - call = (Array)ARRAY_DICT_INIT; - ADD(data->buffer, ARRAY_OBJ(call)); - ADD(call, STRING_OBJ(cstr_to_string(name))); + if (!data->cur_event || !strequal(data->cur_event, name)) { + flush_event(data); + data->cur_event = name; + char **buf = &data->buf_wptr; + data->ncalls_pos = mpack_array_dyn16(buf); + mpack_str(buf, name); + data->nevents++; + data->ncalls = 1; + return true; } - ADD(call, ARRAY_OBJ(args)); - kv_A(data->buffer, kv_size(data->buffer) - 1).data.array = call; + return false; +} + +/// Pushes data into UI.UIData, to be consumed later by remote_ui_flush(). +static void push_call(UI *ui, const char *name, Array args) +{ + UIData *data = ui->data; + bool pending = data->nevents_pos; + char *buf_pos_save = data->buf_wptr; + + bool new_event = prepare_call(ui, name); + + msgpack_packer pac; + data->pack_totlen = 0; + data->buf_overflow = false; + msgpack_packer_init(&pac, data, write_cb); + msgpack_rpc_from_array(args, &pac); + if (data->buf_overflow) { + data->buf_wptr = buf_pos_save; + if (new_event) { + data->cur_event = NULL; + data->nevents--; + } + if (pending) { + remote_ui_flush_buf(ui); + } + + if (data->pack_totlen > UI_BUF_SIZE - strlen(name) - 20) { + // TODO(bfredl): manually testable by setting UI_BUF_SIZE to 1024 (mode_info_set) + data->temp_buf = xmalloc(20 + strlen(name) + data->pack_totlen); + data->buf_wptr = data->temp_buf; + char **buf = &data->buf_wptr; + mpack_array(buf, 3); + mpack_uint(buf, 2); + mpack_str(buf, "redraw"); + mpack_array(buf, 1); + mpack_array(buf, 2); + mpack_str(buf, name); + } else { + prepare_call(ui, name); + } + data->pack_totlen = 0; + data->buf_overflow = false; + msgpack_rpc_from_array(args, &pac); + + if (data->temp_buf) { + size_t size = (size_t)(data->buf_wptr - data->temp_buf); + WBuffer *buf = wstream_new_buffer(data->temp_buf, size, 1, xfree); + rpc_write_raw(data->channel_id, buf); + data->temp_buf = NULL; + data->buf_wptr = data->buf; + data->nevents_pos = NULL; + } + } + data->ncalls++; } static void remote_ui_grid_clear(UI *ui, Integer grid) { - Array args = ARRAY_DICT_INIT; + UIData *data = ui->data; + Array args = data->call_buf; if (ui->ui_ext[kUILinegrid]) { - ADD(args, INTEGER_OBJ(grid)); + ADD_C(args, INTEGER_OBJ(grid)); } const char *name = ui->ui_ext[kUILinegrid] ? "grid_clear" : "clear"; push_call(ui, name, args); @@ -468,12 +659,13 @@ static void remote_ui_grid_clear(UI *ui, Integer grid) static void remote_ui_grid_resize(UI *ui, Integer grid, Integer width, Integer height) { - Array args = ARRAY_DICT_INIT; + UIData *data = ui->data; + Array args = data->call_buf; if (ui->ui_ext[kUILinegrid]) { - ADD(args, INTEGER_OBJ(grid)); + ADD_C(args, INTEGER_OBJ(grid)); } - ADD(args, INTEGER_OBJ(width)); - ADD(args, INTEGER_OBJ(height)); + ADD_C(args, INTEGER_OBJ(width)); + ADD_C(args, INTEGER_OBJ(height)); const char *name = ui->ui_ext[kUILinegrid] ? "grid_resize" : "resize"; push_call(ui, name, args); } @@ -481,35 +673,36 @@ static void remote_ui_grid_resize(UI *ui, Integer grid, Integer width, Integer h static void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot, Integer left, Integer right, Integer rows, Integer cols) { + UIData *data = ui->data; if (ui->ui_ext[kUILinegrid]) { - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(grid)); - ADD(args, INTEGER_OBJ(top)); - ADD(args, INTEGER_OBJ(bot)); - ADD(args, INTEGER_OBJ(left)); - ADD(args, INTEGER_OBJ(right)); - ADD(args, INTEGER_OBJ(rows)); - ADD(args, INTEGER_OBJ(cols)); + Array args = data->call_buf; + ADD_C(args, INTEGER_OBJ(grid)); + ADD_C(args, INTEGER_OBJ(top)); + ADD_C(args, INTEGER_OBJ(bot)); + ADD_C(args, INTEGER_OBJ(left)); + ADD_C(args, INTEGER_OBJ(right)); + ADD_C(args, INTEGER_OBJ(rows)); + ADD_C(args, INTEGER_OBJ(cols)); push_call(ui, "grid_scroll", args); } else { - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(top)); - ADD(args, INTEGER_OBJ(bot - 1)); - ADD(args, INTEGER_OBJ(left)); - ADD(args, INTEGER_OBJ(right - 1)); + Array args = data->call_buf; + ADD_C(args, INTEGER_OBJ(top)); + ADD_C(args, INTEGER_OBJ(bot - 1)); + ADD_C(args, INTEGER_OBJ(left)); + ADD_C(args, INTEGER_OBJ(right - 1)); push_call(ui, "set_scroll_region", args); - args = (Array)ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(rows)); + args = data->call_buf; + ADD_C(args, INTEGER_OBJ(rows)); push_call(ui, "scroll", args); // some clients have "clear" being affected by scroll region, // so reset it. - args = (Array)ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(0)); - ADD(args, INTEGER_OBJ(ui->height - 1)); - ADD(args, INTEGER_OBJ(0)); - ADD(args, INTEGER_OBJ(ui->width - 1)); + args = data->call_buf; + ADD_C(args, INTEGER_OBJ(0)); + ADD_C(args, INTEGER_OBJ(ui->height - 1)); + ADD_C(args, INTEGER_OBJ(0)); + ADD_C(args, INTEGER_OBJ(ui->width - 1)); push_call(ui, "set_scroll_region", args); } } @@ -520,26 +713,27 @@ static void remote_ui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg, if (!ui->ui_ext[kUITermColors]) { HL_SET_DEFAULT_COLORS(rgb_fg, rgb_bg, rgb_sp); } - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(rgb_fg)); - ADD(args, INTEGER_OBJ(rgb_bg)); - ADD(args, INTEGER_OBJ(rgb_sp)); - ADD(args, INTEGER_OBJ(cterm_fg)); - ADD(args, INTEGER_OBJ(cterm_bg)); + UIData *data = ui->data; + Array args = data->call_buf; + ADD_C(args, INTEGER_OBJ(rgb_fg)); + ADD_C(args, INTEGER_OBJ(rgb_bg)); + ADD_C(args, INTEGER_OBJ(rgb_sp)); + ADD_C(args, INTEGER_OBJ(cterm_fg)); + ADD_C(args, INTEGER_OBJ(cterm_bg)); push_call(ui, "default_colors_set", args); // Deprecated if (!ui->ui_ext[kUILinegrid]) { - args = (Array)ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(ui->rgb ? rgb_fg : cterm_fg - 1)); + args = data->call_buf; + ADD_C(args, INTEGER_OBJ(ui->rgb ? rgb_fg : cterm_fg - 1)); push_call(ui, "update_fg", args); - args = (Array)ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(ui->rgb ? rgb_bg : cterm_bg - 1)); + args = data->call_buf; + ADD_C(args, INTEGER_OBJ(ui->rgb ? rgb_bg : cterm_bg - 1)); push_call(ui, "update_bg", args); - args = (Array)ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(ui->rgb ? rgb_sp : -1)); + args = data->call_buf; + ADD_C(args, INTEGER_OBJ(ui->rgb ? rgb_sp : -1)); push_call(ui, "update_sp", args); } } @@ -550,25 +744,29 @@ static void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAt if (!ui->ui_ext[kUILinegrid]) { return; } - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(id)); - ADD(args, DICTIONARY_OBJ(hlattrs2dict(rgb_attrs, true))); - ADD(args, DICTIONARY_OBJ(hlattrs2dict(cterm_attrs, false))); + UIData *data = ui->data; + Array args = data->call_buf; + ADD_C(args, INTEGER_OBJ(id)); + ADD_C(args, DICTIONARY_OBJ(hlattrs2dict(rgb_attrs, true))); + ADD_C(args, DICTIONARY_OBJ(hlattrs2dict(cterm_attrs, false))); if (ui->ui_ext[kUIHlState]) { - ADD(args, ARRAY_OBJ(copy_array(info))); + ADD_C(args, ARRAY_OBJ(info)); } else { - ADD(args, ARRAY_OBJ((Array)ARRAY_DICT_INIT)); + ADD_C(args, ARRAY_OBJ((Array)ARRAY_DICT_INIT)); } push_call(ui, "hl_attr_define", args); + // TODO(bfredl): could be elided + api_free_dictionary(kv_A(args, 1).data.dictionary); + api_free_dictionary(kv_A(args, 2).data.dictionary); } static void remote_ui_highlight_set(UI *ui, int id) { - Array args = ARRAY_DICT_INIT; UIData *data = ui->data; + Array args = data->call_buf; if (data->hl_id == id) { return; @@ -576,18 +774,20 @@ static void remote_ui_highlight_set(UI *ui, int id) data->hl_id = id; Dictionary hl = hlattrs2dict(syn_attr2entry(id), ui->rgb); - ADD(args, DICTIONARY_OBJ(hl)); + ADD_C(args, DICTIONARY_OBJ(hl)); push_call(ui, "highlight_set", args); + api_free_dictionary(kv_A(args, 0).data.dictionary); } /// "true" cursor used only for input focus static void remote_ui_grid_cursor_goto(UI *ui, Integer grid, Integer row, Integer col) { if (ui->ui_ext[kUILinegrid]) { - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(grid)); - ADD(args, INTEGER_OBJ(row)); - ADD(args, INTEGER_OBJ(col)); + UIData *data = ui->data; + Array args = data->call_buf; + ADD_C(args, INTEGER_OBJ(grid)); + ADD_C(args, INTEGER_OBJ(row)); + ADD_C(args, INTEGER_OBJ(col)); push_call(ui, "grid_cursor_goto", args); } else { UIData *data = ui->data; @@ -606,9 +806,9 @@ static void remote_ui_cursor_goto(UI *ui, Integer row, Integer col) } data->client_row = row; data->client_col = col; - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(row)); - ADD(args, INTEGER_OBJ(col)); + Array args = data->call_buf; + ADD_C(args, INTEGER_OBJ(row)); + ADD_C(args, INTEGER_OBJ(col)); push_call(ui, "cursor_goto", args); } @@ -616,8 +816,8 @@ static void remote_ui_put(UI *ui, const char *cell) { UIData *data = ui->data; data->client_col++; - Array args = ARRAY_DICT_INIT; - ADD(args, STRING_OBJ(cstr_to_string(cell))); + Array args = data->call_buf; + ADD_C(args, STRING_OBJ(cstr_as_string((char *)cell))); push_call(ui, "put", args); } @@ -627,41 +827,64 @@ static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startc { UIData *data = ui->data; if (ui->ui_ext[kUILinegrid]) { - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(grid)); - ADD(args, INTEGER_OBJ(row)); - ADD(args, INTEGER_OBJ(startcol)); - Array cells = ARRAY_DICT_INIT; - int repeat = 0; + prepare_call(ui, "grid_line"); + data->ncalls++; + + char **buf = &data->buf_wptr; + mpack_array(buf, 4); + mpack_uint(buf, (uint32_t)grid); + mpack_uint(buf, (uint32_t)row); + mpack_uint(buf, (uint32_t)startcol); + char *lenpos = mpack_array_dyn16(buf); + + uint32_t repeat = 0; size_t ncells = (size_t)(endcol - startcol); int last_hl = -1; + uint32_t nelem = 0; for (size_t i = 0; i < ncells; i++) { repeat++; if (i == ncells - 1 || attrs[i] != attrs[i + 1] || STRCMP(chunk[i], chunk[i + 1])) { - Array cell = ARRAY_DICT_INIT; - ADD(cell, STRING_OBJ(cstr_to_string((const char *)chunk[i]))); - if (attrs[i] != last_hl || repeat > 1) { - ADD(cell, INTEGER_OBJ(attrs[i])); - last_hl = attrs[i]; + if (UI_BUF_SIZE - BUF_POS(data) < 2 * (1 + 2 + sizeof(schar_T) + 5 + 5)) { + // close to overflowing the redraw buffer. finish this event, + // flush, and start a new "grid_line" event at the current position. + // For simplicity leave place for the final "clear" element + // as well, hence the factor of 2 in the check. + mpack_w2(&lenpos, nelem); + remote_ui_flush_buf(ui); + + prepare_call(ui, "grid_line"); + data->ncalls++; + mpack_array(buf, 4); + mpack_uint(buf, (uint32_t)grid); + mpack_uint(buf, (uint32_t)row); + mpack_uint(buf, (uint32_t)startcol + (uint32_t)i - repeat + 1); + lenpos = mpack_array_dyn16(buf); + nelem = 0; + last_hl = -1; } - if (repeat > 1) { - ADD(cell, INTEGER_OBJ(repeat)); + uint32_t csize = (repeat > 1) ? 3 : ((attrs[i] != last_hl) ? 2 : 1); + nelem++; + mpack_array(buf, csize); + mpack_str(buf, (const char *)chunk[i]); + if (csize >= 2) { + mpack_uint(buf, (uint32_t)attrs[i]); + if (csize >= 3) { + mpack_uint(buf, repeat); + } } - ADD(cells, ARRAY_OBJ(cell)); + last_hl = attrs[i]; repeat = 0; } } if (endcol < clearcol) { - Array cell = ARRAY_DICT_INIT; - ADD(cell, STRING_OBJ(cstr_to_string(" "))); - ADD(cell, INTEGER_OBJ(clearattr)); - ADD(cell, INTEGER_OBJ(clearcol - endcol)); - ADD(cells, ARRAY_OBJ(cell)); + nelem++; + mpack_array(buf, 3); + mpack_str(buf, " "); + mpack_uint(buf, (uint32_t)clearattr); + mpack_uint(buf, (uint32_t)(clearcol - endcol)); } - ADD(args, ARRAY_OBJ(cells)); - - push_call(ui, "grid_line", args); + mpack_w2(&lenpos, nelem); } else { for (int i = 0; i < endcol - startcol; i++) { remote_ui_cursor_goto(ui, row, startcol + i); @@ -688,16 +911,47 @@ static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startc } } +/// Flush the internal packing buffer to the client. +/// +/// This might happen multiple times before the actual ui_flush, if the +/// total redraw size is large! +static void remote_ui_flush_buf(UI *ui) +{ + UIData *data = ui->data; + if (!data->nevents_pos) { + return; + } + if (data->cur_event) { + flush_event(data); + } + mpack_w2(&data->nevents_pos, data->nevents); + data->nevents = 0; + data->nevents_pos = NULL; + + // TODO(bfredl): elide copy by a length one free-list like the arena + size_t size = BUF_POS(data); + WBuffer *buf = wstream_new_buffer(xmemdup(data->buf, size), size, 1, xfree); + rpc_write_raw(data->channel_id, buf); + data->buf_wptr = data->buf; + // we have sent events to the client, but possibly not yet the final "flush" + // event. + data->flushed_events = true; +} + +/// An intentional flush (vsync) when Nvim is finished redrawing the screen +/// +/// Clients can know this happened by a final "flush" event at the end of the +/// "redraw" batch. static void remote_ui_flush(UI *ui) { UIData *data = ui->data; - if (data->buffer.size > 0) { + if (data->nevents > 0 || data->flushed_events) { if (!ui->ui_ext[kUILinegrid]) { remote_ui_cursor_goto(ui, data->cursor_row, data->cursor_col); } push_call(ui, "flush", (Array)ARRAY_DICT_INIT); - rpc_send_event(data->channel_id, "redraw", data->buffer); - data->buffer = (Array)ARRAY_DICT_INIT; + remote_ui_flush_buf(ui); + data->flushed_events = false; } } @@ -732,7 +986,7 @@ static Array translate_firstarg(UI *ui, Array args) return new_args; } -static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed) +static void remote_ui_event(UI *ui, char *name, Array args) { UIData *data = ui->data; if (!ui->ui_ext[kUILinegrid]) { @@ -741,21 +995,24 @@ static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed) if (strequal(name, "cmdline_show")) { Array new_args = translate_firstarg(ui, args); push_call(ui, name, new_args); + api_free_array(new_args); return; } else if (strequal(name, "cmdline_block_show")) { - Array new_args = ARRAY_DICT_INIT; + Array new_args = data->call_buf; Array block = args.items[0].data.array; Array new_block = ARRAY_DICT_INIT; for (size_t i = 0; i < block.size; i++) { ADD(new_block, ARRAY_OBJ(translate_contents(ui, block.items[i].data.array))); } - ADD(new_args, ARRAY_OBJ(new_block)); + ADD_C(new_args, ARRAY_OBJ(new_block)); push_call(ui, name, new_args); + api_free_array(new_block); return; } else if (strequal(name, "cmdline_block_append")) { Array new_args = translate_firstarg(ui, args); push_call(ui, name, new_args); + api_free_array(new_args); return; } } @@ -766,18 +1023,19 @@ static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed) data->wildmenu_active = (args.items[4].data.integer == -1) || !ui->ui_ext[kUIPopupmenu]; if (data->wildmenu_active) { - Array new_args = ARRAY_DICT_INIT; + Array new_args = data->call_buf; Array items = args.items[0].data.array; Array new_items = ARRAY_DICT_INIT; for (size_t i = 0; i < items.size; i++) { ADD(new_items, copy_object(items.items[i].data.array.items[0])); } - ADD(new_args, ARRAY_OBJ(new_items)); + ADD_C(new_args, ARRAY_OBJ(new_items)); push_call(ui, "wildmenu_show", new_args); + api_free_array(new_items); if (args.items[1].data.integer != -1) { - Array new_args2 = ARRAY_DICT_INIT; - ADD(new_args2, args.items[1]); - push_call(ui, "wildmenu_select", new_args); + Array new_args2 = data->call_buf; + ADD_C(new_args2, args.items[1]); + push_call(ui, "wildmenu_select", new_args2); } return; } @@ -792,18 +1050,7 @@ static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed) } } - Array my_args = ARRAY_DICT_INIT; - // Objects are currently single-reference - // make a copy, but only if necessary - if (*args_consumed) { - for (size_t i = 0; i < args.size; i++) { - ADD(my_args, copy_object(args.items[i])); - } - } else { - my_args = args; - *args_consumed = true; - } - push_call(ui, name, my_args); + push_call(ui, name, args); } static void remote_ui_inspect(UI *ui, Dictionary *info) diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 0030f9edf7..8b7e01e1c3 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -99,7 +99,7 @@ void raw_line(Integer grid, Integer row, Integer startcol, LineFlags flags, const schar_T *chunk, const sattr_T *attrs) FUNC_API_NOEXPORT FUNC_API_COMPOSITOR_IMPL; -void event(char *name, Array args, bool *args_consumed) +void event(char *name, Array args) FUNC_API_NOEXPORT; void win_pos(Integer grid, Window win, Integer startrow, -- cgit From 58d028f64bb0ddc80b6201906880182d14ad9a3c Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Mon, 20 Jun 2022 08:20:06 -0600 Subject: feat(api): add "buf" and "win" to nvim_get_option_value These mirror their counterparts in nvim_set_option_value. --- src/nvim/api/options.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 80 insertions(+), 5 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index 61d4becd32..a35c7222ed 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -24,14 +24,15 @@ /// Gets the value of an option. The behavior of this function matches that of /// |:set|: the local value of an option is returned if it exists; otherwise, /// the global value is returned. Local values always correspond to the current -/// buffer or window. To get a buffer-local or window-local option for a -/// specific buffer or window, use |nvim_buf_get_option()| or -/// |nvim_win_get_option()|. +/// buffer or window, unless "buf" or "win" is set in {opts}. /// /// @param name Option name /// @param opts Optional parameters -/// - scope: One of 'global' or 'local'. Analogous to +/// - scope: One of "global" or "local". Analogous to /// |:setglobal| and |:setlocal|, respectively. +/// - win: |window-ID|. Used for getting window local options. +/// - buf: Buffer number. Used for getting buffer local options. +/// Implies {scope} is "local". /// @param[out] err Error details, if any /// @return Option value Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) @@ -54,9 +55,44 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) goto end; } + int opt_type = SREQ_GLOBAL; + void *from = NULL; + + if (opts->win.type == kObjectTypeInteger) { + opt_type = SREQ_WIN; + from = find_window_by_handle((int)opts->win.data.integer, err); + } else if (HAS_KEY(opts->win)) { + api_set_error(err, kErrorTypeValidation, "invalid value for key: win"); + goto end; + } + + if (opts->buf.type == kObjectTypeInteger) { + scope = OPT_LOCAL; + opt_type = SREQ_BUF; + from = find_buffer_by_handle((int)opts->buf.data.integer, err); + } else if (HAS_KEY(opts->buf)) { + api_set_error(err, kErrorTypeValidation, "invalid value for key: buf"); + goto end; + } + + if (HAS_KEY(opts->scope) && HAS_KEY(opts->buf)) { + api_set_error(err, kErrorTypeValidation, "scope and buf cannot be used together"); + goto end; + } + + if (HAS_KEY(opts->win) && HAS_KEY(opts->buf)) { + api_set_error(err, kErrorTypeValidation, "buf and win cannot be used together"); + goto end; + } + long numval = 0; char *stringval = NULL; - switch (get_option_value(name.data, &numval, &stringval, scope)) { + int result = get_option_value_for(name.data, &numval, &stringval, scope, opt_type, from, err); + if (ERROR_SET(err)) { + goto end; + } + + switch (result) { case 0: rv = STRING_OBJ(cstr_as_string(stringval)); break; @@ -507,3 +543,42 @@ static void set_option_value_err(char *key, long numval, char *stringval, int op api_set_error(err, kErrorTypeException, "%s", errmsg); } } + +int get_option_value_for(char *key, long *numval, char **stringval, int opt_flags, int opt_type, + void *from, Error *err) +{ + switchwin_T switchwin; + aco_save_T aco; + int result = 0; + + try_start(); + switch (opt_type) { + case SREQ_WIN: + if (switch_win_noblock(&switchwin, (win_T *)from, win_find_tabpage((win_T *)from), true) + == FAIL) { + restore_win_noblock(&switchwin, true); + if (try_end(err)) { + return result; + } + api_set_error(err, + kErrorTypeException, + "Problem while switching windows"); + return result; + } + result = get_option_value(key, numval, stringval, opt_flags); + restore_win_noblock(&switchwin, true); + break; + case SREQ_BUF: + aucmd_prepbuf(&aco, (buf_T *)from); + result = get_option_value(key, numval, stringval, opt_flags); + aucmd_restbuf(&aco); + break; + case SREQ_GLOBAL: + result = get_option_value(key, numval, stringval, opt_flags); + break; + } + + try_end(err); + + return result; +} -- cgit From 374e0b6678b21105bd5a26265e483cc4d9dbcaad Mon Sep 17 00:00:00 2001 From: bfredl Date: Tue, 21 Jun 2022 12:29:49 +0200 Subject: perf(highlight): don't allocate duplicates for color names --- src/nvim/api/vim.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 3a24f2b405..b7df1398f5 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1293,7 +1293,8 @@ void nvim_unsubscribe(uint64_t channel_id, String event) Integer nvim_get_color_by_name(String name) FUNC_API_SINCE(1) { - return name_to_color(name.data); + int dummy; + return name_to_color(name.data, &dummy); } /// Returns a map of color names and RGB values. -- cgit From a9442c532e9af45fc5c79d0207ab837cb715941f Mon Sep 17 00:00:00 2001 From: bfredl Date: Tue, 21 Jun 2022 13:03:35 +0200 Subject: perf(highlight): allocate permanent names in an arena for fun and cache locality --- src/nvim/api/private/helpers.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 693d946088..9cadef0385 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -686,10 +686,7 @@ Dictionary arena_dict(Arena *arena, size_t max_size) String arena_string(Arena *arena, String str) { if (str.size) { - char *mem = arena_alloc(arena, str.size + 1, false); - memcpy(mem, str.data, str.size); - mem[str.size] = NUL; - return cbuf_as_string(mem, str.size); + return cbuf_as_string(arena_memdupz(arena, str.data, str.size), str.size); } else { return (String)STRING_INIT; } -- cgit From 34ae896b8221d73677ed353736f611e81ee6798c Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 22 Jun 2022 03:09:50 +0100 Subject: refactor(option): DRY get/set option value #19038 The main motivation for this is for the buf and win cases which need to set up and restore context, and it's what specifically makes the semantics of options nuanced, and thus this should not be repeated more than once. - nvim_get/set_option_value now share argument validation. --- src/nvim/api/options.c | 203 ++++++++++++++++++------------------------------- 1 file changed, 72 insertions(+), 131 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index a35c7222ed..3067a6e6b4 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -21,75 +21,87 @@ # include "api/options.c.generated.h" #endif -/// Gets the value of an option. The behavior of this function matches that of -/// |:set|: the local value of an option is returned if it exists; otherwise, -/// the global value is returned. Local values always correspond to the current -/// buffer or window, unless "buf" or "win" is set in {opts}. -/// -/// @param name Option name -/// @param opts Optional parameters -/// - scope: One of "global" or "local". Analogous to -/// |:setglobal| and |:setlocal|, respectively. -/// - win: |window-ID|. Used for getting window local options. -/// - buf: Buffer number. Used for getting buffer local options. -/// Implies {scope} is "local". -/// @param[out] err Error details, if any -/// @return Option value -Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) - FUNC_API_SINCE(9) +static int validate_option_value_args(Dict(option) *opts, int *scope, int *opt_type, void **from, + Error *err) { - Object rv = OBJECT_INIT; - - int scope = 0; if (opts->scope.type == kObjectTypeString) { if (!strcmp(opts->scope.data.string.data, "local")) { - scope = OPT_LOCAL; + *scope = OPT_LOCAL; } else if (!strcmp(opts->scope.data.string.data, "global")) { - scope = OPT_GLOBAL; + *scope = OPT_GLOBAL; } else { api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'"); - goto end; + return FAIL; } } else if (HAS_KEY(opts->scope)) { api_set_error(err, kErrorTypeValidation, "invalid value for key: scope"); - goto end; + return FAIL; } - int opt_type = SREQ_GLOBAL; - void *from = NULL; + *opt_type = SREQ_GLOBAL; if (opts->win.type == kObjectTypeInteger) { - opt_type = SREQ_WIN; - from = find_window_by_handle((int)opts->win.data.integer, err); + *opt_type = SREQ_WIN; + *from = find_window_by_handle((int)opts->win.data.integer, err); } else if (HAS_KEY(opts->win)) { api_set_error(err, kErrorTypeValidation, "invalid value for key: win"); - goto end; + return FAIL; } if (opts->buf.type == kObjectTypeInteger) { - scope = OPT_LOCAL; - opt_type = SREQ_BUF; - from = find_buffer_by_handle((int)opts->buf.data.integer, err); + *scope = OPT_LOCAL; + *opt_type = SREQ_BUF; + *from = find_buffer_by_handle((int)opts->buf.data.integer, err); } else if (HAS_KEY(opts->buf)) { api_set_error(err, kErrorTypeValidation, "invalid value for key: buf"); - goto end; + return FAIL; } if (HAS_KEY(opts->scope) && HAS_KEY(opts->buf)) { api_set_error(err, kErrorTypeValidation, "scope and buf cannot be used together"); - goto end; + return FAIL; } if (HAS_KEY(opts->win) && HAS_KEY(opts->buf)) { api_set_error(err, kErrorTypeValidation, "buf and win cannot be used together"); - goto end; + return FAIL; + } + + return OK; +} + +/// Gets the value of an option. The behavior of this function matches that of +/// |:set|: the local value of an option is returned if it exists; otherwise, +/// the global value is returned. Local values always correspond to the current +/// buffer or window, unless "buf" or "win" is set in {opts}. +/// +/// @param name Option name +/// @param opts Optional parameters +/// - scope: One of "global" or "local". Analogous to +/// |:setglobal| and |:setlocal|, respectively. +/// - win: |window-ID|. Used for getting window local options. +/// - buf: Buffer number. Used for getting buffer local options. +/// Implies {scope} is "local". +/// @param[out] err Error details, if any +/// @return Option value +Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) + FUNC_API_SINCE(9) +{ + Object rv = OBJECT_INIT; + + int scope = 0; + int opt_type = SREQ_GLOBAL; + void *from = NULL; + if (!validate_option_value_args(opts, &scope, &opt_type, &from, err)) { + return rv; } long numval = 0; char *stringval = NULL; - int result = get_option_value_for(name.data, &numval, &stringval, scope, opt_type, from, err); + int result = access_option_value_for(name.data, &numval, &stringval, scope, opt_type, from, + true, err); if (ERROR_SET(err)) { - goto end; + return rv; } switch (result) { @@ -114,10 +126,9 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) break; default: api_set_error(err, kErrorTypeValidation, "unknown option '%s'", name.data); - goto end; + return rv; } -end: return rv; } @@ -139,47 +150,9 @@ void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error FUNC_API_SINCE(9) { int scope = 0; - if (opts->scope.type == kObjectTypeString) { - if (!strcmp(opts->scope.data.string.data, "local")) { - scope = OPT_LOCAL; - } else if (!strcmp(opts->scope.data.string.data, "global")) { - scope = OPT_GLOBAL; - } else { - api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'"); - return; - } - } else if (HAS_KEY(opts->scope)) { - api_set_error(err, kErrorTypeValidation, "invalid value for key: scope"); - return; - } - int opt_type = SREQ_GLOBAL; void *to = NULL; - - if (opts->win.type == kObjectTypeInteger) { - opt_type = SREQ_WIN; - to = find_window_by_handle((int)opts->win.data.integer, err); - } else if (HAS_KEY(opts->win)) { - api_set_error(err, kErrorTypeValidation, "invalid value for key: win"); - return; - } - - if (opts->buf.type == kObjectTypeInteger) { - scope = OPT_LOCAL; - opt_type = SREQ_BUF; - to = find_buffer_by_handle((int)opts->buf.data.integer, err); - } else if (HAS_KEY(opts->buf)) { - api_set_error(err, kErrorTypeValidation, "invalid value for key: buf"); - return; - } - - if (HAS_KEY(opts->scope) && HAS_KEY(opts->buf)) { - api_set_error(err, kErrorTypeValidation, "scope and buf cannot be used together"); - return; - } - - if (HAS_KEY(opts->win) && HAS_KEY(opts->buf)) { - api_set_error(err, kErrorTypeValidation, "buf and win cannot be used together"); + if (!validate_option_value_args(opts, &scope, &opt_type, &to, err)) { return; } @@ -204,7 +177,7 @@ void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error return; } - set_option_value_for(name.data, numval, stringval, scope, opt_type, to, err); + access_option_value_for(name.data, &numval, &stringval, scope, opt_type, to, false, err); } /// Gets the option information for all options. @@ -441,7 +414,7 @@ void set_option_to(uint64_t channel_id, void *to, int type, String name, Object } } - int numval = 0; + long numval = 0; char *stringval = NULL; if (flags & SOPT_BOOL) { @@ -486,66 +459,30 @@ void set_option_to(uint64_t channel_id, void *to, int type, String name, Object ? 0 : (type == SREQ_GLOBAL) ? OPT_GLOBAL : OPT_LOCAL; - set_option_value_for(name.data, numval, stringval, - opt_flags, type, to, err); + access_option_value_for(name.data, &numval, &stringval, opt_flags, type, to, false, err); }); } -void set_option_value_for(char *key, long numval, char *stringval, int opt_flags, int opt_type, - void *from, Error *err) +static int access_option_value(char *key, long *numval, char **stringval, int opt_flags, bool get, + Error *err) { - switchwin_T switchwin; - aco_save_T aco; - - try_start(); - switch (opt_type) { - case SREQ_WIN: - if (switch_win_noblock(&switchwin, (win_T *)from, win_find_tabpage((win_T *)from), true) - == FAIL) { - restore_win_noblock(&switchwin, true); + if (get) { + return get_option_value(key, numval, stringval, opt_flags); + } else { + char *errmsg; + if ((errmsg = set_option_value(key, *numval, *stringval, opt_flags))) { if (try_end(err)) { - return; + return 0; } - api_set_error(err, - kErrorTypeException, - "Problem while switching windows"); - return; - } - set_option_value_err(key, numval, stringval, opt_flags, err); - restore_win_noblock(&switchwin, true); - break; - case SREQ_BUF: - aucmd_prepbuf(&aco, (buf_T *)from); - set_option_value_err(key, numval, stringval, opt_flags, err); - aucmd_restbuf(&aco); - break; - case SREQ_GLOBAL: - set_option_value_err(key, numval, stringval, opt_flags, err); - break; - } - - if (ERROR_SET(err)) { - return; - } - - try_end(err); -} -static void set_option_value_err(char *key, long numval, char *stringval, int opt_flags, Error *err) -{ - char *errmsg; - - if ((errmsg = set_option_value(key, numval, stringval, opt_flags))) { - if (try_end(err)) { - return; + api_set_error(err, kErrorTypeException, "%s", errmsg); } - - api_set_error(err, kErrorTypeException, "%s", errmsg); + return 0; } } -int get_option_value_for(char *key, long *numval, char **stringval, int opt_flags, int opt_type, - void *from, Error *err) +static int access_option_value_for(char *key, long *numval, char **stringval, int opt_flags, + int opt_type, void *from, bool get, Error *err) { switchwin_T switchwin; aco_save_T aco; @@ -565,19 +502,23 @@ int get_option_value_for(char *key, long *numval, char **stringval, int opt_flag "Problem while switching windows"); return result; } - result = get_option_value(key, numval, stringval, opt_flags); + result = access_option_value(key, numval, stringval, opt_flags, get, err); restore_win_noblock(&switchwin, true); break; case SREQ_BUF: aucmd_prepbuf(&aco, (buf_T *)from); - result = get_option_value(key, numval, stringval, opt_flags); + result = access_option_value(key, numval, stringval, opt_flags, get, err); aucmd_restbuf(&aco); break; case SREQ_GLOBAL: - result = get_option_value(key, numval, stringval, opt_flags); + result = access_option_value(key, numval, stringval, opt_flags, get, err); break; } + if (ERROR_SET(err)) { + return result; + } + try_end(err); return result; -- cgit From d23465534a8ba5dac1758ffebdc7746138ee5210 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 21 Jun 2022 17:13:01 +0100 Subject: fix(api): nvim_set_option_value for global-local options global-local window options need to be handled specially. When `win` is given but `scope` is not, then we want to set the local version of the option but not the global one, therefore we need to force `scope='local'`. Note this does not apply to window-local only options (e.g. 'number') Example: nvim_set_option_value('scrolloff', 10, {}) -- global-local window option; set global value nvim_set_option_value('scrolloff', 20, {win=0}) -- global-local window option; set local value nvim_set_option_value('number', true, {}) -- local window option is now equivalent to: nvim_set_option_value('scrolloff', 10, {}) nvim_set_option_value('scrolloff', 20, {win=0, scope='local'}) -- changed from before nvim_set_option_value('number', true, {win=0}) -- unchanged from before Only the global-local option with a `win` provided gets forced to local scope. --- src/nvim/api/options.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index 3067a6e6b4..3766f47b20 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -156,6 +156,19 @@ void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error return; } + // If: + // - window id is provided + // - scope is not provided + // - option is global or local to window (global-local) + // + // Then force scope to local since we don't want to change the global option + if (opt_type == SREQ_WIN && scope == 0) { + int flags = get_option_value_strict(name.data, NULL, NULL, opt_type, to); + if (flags & SOPT_GLOBAL) { + scope = OPT_LOCAL; + } + } + long numval = 0; char *stringval = NULL; @@ -454,11 +467,12 @@ void set_option_to(uint64_t channel_id, void *to, int type, String name, Object stringval = value.data.string.data; } - WITH_SCRIPT_CONTEXT(channel_id, { - const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL)) - ? 0 : (type == SREQ_GLOBAL) - ? OPT_GLOBAL : OPT_LOCAL; + // For global-win-local options -> setlocal + // For win-local options -> setglobal and setlocal (opt_flags == 0) + const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL)) ? 0 : + (type == SREQ_GLOBAL) ? OPT_GLOBAL : OPT_LOCAL; + WITH_SCRIPT_CONTEXT(channel_id, { access_option_value_for(name.data, &numval, &stringval, opt_flags, type, to, false, err); }); } -- cgit From c94325288a1008b5eabe7ae0fd03aa906686ee1b Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Wed, 22 Jun 2022 13:19:03 -0600 Subject: fix(api): check error after getting win/buf handle (#19052) --- src/nvim/api/options.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index 3067a6e6b4..19ce25f676 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -43,6 +43,9 @@ static int validate_option_value_args(Dict(option) *opts, int *scope, int *opt_t if (opts->win.type == kObjectTypeInteger) { *opt_type = SREQ_WIN; *from = find_window_by_handle((int)opts->win.data.integer, err); + if (ERROR_SET(err)) { + return FAIL; + } } else if (HAS_KEY(opts->win)) { api_set_error(err, kErrorTypeValidation, "invalid value for key: win"); return FAIL; @@ -52,6 +55,9 @@ static int validate_option_value_args(Dict(option) *opts, int *scope, int *opt_t *scope = OPT_LOCAL; *opt_type = SREQ_BUF; *from = find_buffer_by_handle((int)opts->buf.data.integer, err); + if (ERROR_SET(err)) { + return FAIL; + } } else if (HAS_KEY(opts->buf)) { api_set_error(err, kErrorTypeValidation, "invalid value for key: buf"); return FAIL; -- cgit From 7718b758461265d8966468c104ce5454538471e2 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 23 Jun 2022 21:17:11 +0800 Subject: refactor: move some mapping-related code to a separate file (#19061) This marks the following Vim patches as ported: vim-patch:8.1.1785: map functionality mixed with character input Problem: Map functionality mixed with character input. Solution: Move the map functionality to a separate file. (Yegappan Lakshmanan, closes vim/vim#4740) Graduate the +localmap feature. https://github.com/vim/vim/commit/b66bab381c8ba71fd6e92327d1d34c6f8a65f2a7 vim-patch:8.2.3643: header for source file is outdated Problem: Header for source file is outdated. Solution: Make the header more accurate. (closes vim/vim#9186) https://github.com/vim/vim/commit/a3f83feb63eae5464a620ae793c002eb45f7a838 Also cherry-pick a change for mappings from patch 8.2.0807. Rename map_clear_mode() to do_mapclear(). --- src/nvim/api/buffer.c | 1 + src/nvim/api/private/helpers.c | 189 ----------------------------------------- src/nvim/api/vim.c | 1 + 3 files changed, 2 insertions(+), 189 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 9dc95de243..0810d155f6 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -22,6 +22,7 @@ #include "nvim/ex_docmd.h" #include "nvim/extmark.h" #include "nvim/lua/executor.h" +#include "nvim/mapping.h" #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 9cadef0385..436bcd5212 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -21,7 +21,6 @@ #include "nvim/ex_cmds_defs.h" #include "nvim/extmark.h" #include "nvim/fileio.h" -#include "nvim/getchar.h" #include "nvim/highlight_group.h" #include "nvim/lib/kvec.h" #include "nvim/lua/executor.h" @@ -441,142 +440,6 @@ Array string_to_array(const String input, bool crlf) return ret; } -/// Set, tweak, or remove a mapping in a mode. Acts as the implementation for -/// functions like @ref nvim_buf_set_keymap. -/// -/// Arguments are handled like @ref nvim_set_keymap unless noted. -/// @param buffer Buffer handle for a specific buffer, or 0 for the current -/// buffer, or -1 to signify global behavior ("all buffers") -/// @param is_unmap When true, removes the mapping that matches {lhs}. -void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mode, String lhs, - String rhs, Dict(keymap) *opts, Error *err) -{ - LuaRef lua_funcref = LUA_NOREF; - bool global = (buffer == -1); - if (global) { - buffer = 0; - } - buf_T *target_buf = find_buffer_by_handle(buffer, err); - - if (!target_buf) { - return; - } - - const sctx_T save_current_sctx = api_set_sctx(channel_id); - - if (opts != NULL && opts->callback.type == kObjectTypeLuaRef) { - lua_funcref = opts->callback.data.luaref; - opts->callback.data.luaref = LUA_NOREF; - } - MapArguments parsed_args = MAP_ARGUMENTS_INIT; - if (opts) { -#define KEY_TO_BOOL(name) \ - parsed_args.name = api_object_to_bool(opts->name, #name, false, err); \ - if (ERROR_SET(err)) { \ - goto fail_and_free; \ - } - - KEY_TO_BOOL(nowait); - KEY_TO_BOOL(noremap); - KEY_TO_BOOL(silent); - KEY_TO_BOOL(script); - KEY_TO_BOOL(expr); - KEY_TO_BOOL(unique); -#undef KEY_TO_BOOL - } - parsed_args.buffer = !global; - - set_maparg_lhs_rhs(lhs.data, lhs.size, - rhs.data, rhs.size, lua_funcref, - CPO_TO_CPO_FLAGS, &parsed_args); - if (opts != NULL && opts->desc.type == kObjectTypeString) { - parsed_args.desc = string_to_cstr(opts->desc.data.string); - } else { - parsed_args.desc = NULL; - } - if (parsed_args.lhs_len > MAXMAPLEN || parsed_args.alt_lhs_len > MAXMAPLEN) { - api_set_error(err, kErrorTypeValidation, "LHS exceeds maximum map length: %s", lhs.data); - goto fail_and_free; - } - - if (mode.size > 1) { - api_set_error(err, kErrorTypeValidation, "Shortname is too long: %s", mode.data); - goto fail_and_free; - } - int mode_val; // integer value of the mapping mode, to be passed to do_map() - char *p = (mode.size) ? mode.data : "m"; - if (STRNCMP(p, "!", 2) == 0) { - mode_val = get_map_mode(&p, true); // mapmode-ic - } else { - mode_val = get_map_mode(&p, false); - if (mode_val == (MODE_VISUAL | MODE_SELECT | MODE_NORMAL | MODE_OP_PENDING) && mode.size > 0) { - // get_map_mode() treats unrecognized mode shortnames as ":map". - // This is an error unless the given shortname was empty string "". - api_set_error(err, kErrorTypeValidation, "Invalid mode shortname: \"%s\"", p); - goto fail_and_free; - } - } - - if (parsed_args.lhs_len == 0) { - api_set_error(err, kErrorTypeValidation, "Invalid (empty) LHS"); - goto fail_and_free; - } - - bool is_noremap = parsed_args.noremap; - assert(!(is_unmap && is_noremap)); - - if (!is_unmap && lua_funcref == LUA_NOREF - && (parsed_args.rhs_len == 0 && !parsed_args.rhs_is_noop)) { - if (rhs.size == 0) { // assume that the user wants RHS to be a - parsed_args.rhs_is_noop = true; - } else { - abort(); // should never happen - } - } else if (is_unmap && (parsed_args.rhs_len || parsed_args.rhs_lua != LUA_NOREF)) { - if (parsed_args.rhs_len) { - api_set_error(err, kErrorTypeValidation, - "Gave nonempty RHS in unmap command: %s", parsed_args.rhs); - } else { - api_set_error(err, kErrorTypeValidation, "Gave nonempty RHS for unmap"); - } - goto fail_and_free; - } - - // buf_do_map() reads noremap/unmap as its own argument. - int maptype_val = 0; - if (is_unmap) { - maptype_val = 1; - } else if (is_noremap) { - maptype_val = 2; - } - - switch (buf_do_map(maptype_val, &parsed_args, mode_val, 0, target_buf)) { - case 0: - break; - case 1: - api_set_error(err, kErrorTypeException, (char *)e_invarg, 0); - goto fail_and_free; - case 2: - api_set_error(err, kErrorTypeException, (char *)e_nomap, 0); - goto fail_and_free; - case 5: - api_set_error(err, kErrorTypeException, - "E227: mapping already exists for %s", parsed_args.lhs); - goto fail_and_free; - default: - assert(false && "Unrecognized return code!"); - goto fail_and_free; - } // switch - - parsed_args.rhs_lua = LUA_NOREF; // don't clear ref on success -fail_and_free: - current_sctx = save_current_sctx; - NLUA_CLEAR_REF(parsed_args.rhs_lua); - xfree(parsed_args.rhs); - xfree(parsed_args.orig_rhs); - XFREE_CLEAR(parsed_args.desc); -} - /// Collects `n` buffer lines into array `l`, optionally replacing newlines /// with NUL. /// @@ -930,58 +793,6 @@ void api_set_error(Error *err, ErrorType errType, const char *format, ...) err->type = errType; } -/// Get an array containing dictionaries describing mappings -/// based on mode and buffer id -/// -/// @param mode The abbreviation for the mode -/// @param buf The buffer to get the mapping array. NULL for global -/// @param from_lua Whether it is called from internal lua api. -/// @returns Array of maparg()-like dictionaries describing mappings -ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf, bool from_lua) -{ - Array mappings = ARRAY_DICT_INIT; - dict_T *const dict = tv_dict_alloc(); - - // Convert the string mode to the integer mode - // that is stored within each mapblock - char *p = mode.data; - int int_mode = get_map_mode(&p, 0); - - // Determine the desired buffer value - long buffer_value = (buf == NULL) ? 0 : buf->handle; - - for (int i = 0; i < MAX_MAPHASH; i++) { - for (const mapblock_T *current_maphash = get_maphash(i, buf); - current_maphash; - current_maphash = current_maphash->m_next) { - if (current_maphash->m_simplified) { - continue; - } - // Check for correct mode - if (int_mode & current_maphash->m_mode) { - mapblock_fill_dict(dict, current_maphash, buffer_value, false); - Object api_dict = vim_to_object((typval_T[]) { { .v_type = VAR_DICT, - .vval.v_dict = dict } }); - if (from_lua) { - Dictionary d = api_dict.data.dictionary; - for (size_t j = 0; j < d.size; j++) { - if (strequal("callback", d.items[j].key.data)) { - d.items[j].value.type = kObjectTypeLuaRef; - d.items[j].value.data.luaref = api_new_luaref((LuaRef)d.items[j].value.data.integer); - break; - } - } - } - ADD(mappings, api_dict); - tv_dict_clear(dict); - } - } - } - tv_dict_free(dict); - - return mappings; -} - /// Force obj to bool. /// If it fails, returns false and sets err /// @param obj The object to coerce to a boolean diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index b7df1398f5..c5881dbc5f 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -38,6 +38,7 @@ #include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" #include "nvim/lua/executor.h" +#include "nvim/mapping.h" #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" -- cgit From 3c85fd817ee57793503b29202ce07166f0a48480 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 23 Jun 2022 21:38:00 +0800 Subject: fix(api): check for inclusive buffer line index out of bounds correctly (#19056) --- src/nvim/api/buffer.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 0810d155f6..1504004c6c 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -554,13 +554,13 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In // check range is ordered and everything! // start_row, end_row within buffer len (except add text past the end?) start_row = normalize_index(buf, start_row, false, &oob); - if (oob || start_row == buf->b_ml.ml_line_count + 1) { + if (oob) { api_set_error(err, kErrorTypeValidation, "start_row out of bounds"); return; } end_row = normalize_index(buf, end_row, false, &oob); - if (oob || end_row == buf->b_ml.ml_line_count + 1) { + if (oob) { api_set_error(err, kErrorTypeValidation, "end_row out of bounds"); return; } @@ -1359,14 +1359,15 @@ static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra) // Normalizes 0-based indexes to buffer line numbers static int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bool *oob) { - int64_t line_count = buf->b_ml.ml_line_count; + assert(buf->b_ml.ml_line_count > 0); + int64_t max_index = buf->b_ml.ml_line_count + (int)end_exclusive - 1; // Fix if < 0 - index = index < 0 ? line_count + index + (int)end_exclusive : index; + index = index < 0 ? max_index + index + 1 : index; // Check for oob - if (index > line_count) { + if (index > max_index) { *oob = true; - index = line_count; + index = max_index; } else if (index < 0) { *oob = true; index = 0; -- cgit From da358d452186277ba2feb26f1a90a9f5ccc31539 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 25 Jun 2022 19:50:06 +0200 Subject: feat(api): support pattern array for exec_autocmds --- src/nvim/api/autocmd.c | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 3a35e49dc8..a4f98b3ef2 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -781,14 +781,12 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err) bool modeline = true; buf_T *buf = curbuf; - bool set_buf = false; - - char *pattern = NULL; - Object *data = NULL; - bool set_pattern = false; + Array patterns = ARRAY_DICT_INIT; Array event_array = ARRAY_DICT_INIT; + Object *data = NULL; + if (!unpack_string_or_array(&event_array, &event, "event", true, err)) { goto cleanup; } @@ -826,21 +824,18 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err) } buf = find_buffer_by_handle((Buffer)buf_obj.data.integer, err); - set_buf = true; if (ERROR_SET(err)) { goto cleanup; } } - if (opts->pattern.type != kObjectTypeNil) { - if (opts->pattern.type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, "'pattern' must be a string"); - goto cleanup; - } + if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) { + goto cleanup; + } - pattern = string_to_cstr(opts->pattern.data.string); - set_pattern = true; + if (patterns.size == 0) { + ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("*"))); } if (opts->data.type != kObjectTypeNil) { @@ -849,16 +844,16 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err) modeline = api_object_to_bool(opts->modeline, "modeline", true, err); - if (set_pattern && set_buf) { - api_set_error(err, kErrorTypeValidation, "must not set 'buffer' and 'pattern'"); - goto cleanup; - } - bool did_aucmd = false; FOREACH_ITEM(event_array, event_str, { GET_ONE_EVENT(event_nr, event_str, cleanup) - did_aucmd |= apply_autocmds_group(event_nr, pattern, NULL, true, au_group, buf, NULL, data); + for (size_t i = 0; i < patterns.size; i++) { + Object pat = patterns.items[i]; + char *fname = opts->buffer.type == kObjectTypeNil ? pat.data.string.data : NULL; + did_aucmd |= + apply_autocmds_group(event_nr, fname, NULL, true, au_group, buf, NULL, data); + } }) if (did_aucmd && modeline) { @@ -867,7 +862,7 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err) cleanup: api_free_array(event_array); - XFREE_CLEAR(pattern); + api_free_array(patterns); } static bool check_autocmd_string_array(Array arr, char *k, Error *err) @@ -988,11 +983,11 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob } else { api_set_error(err, kErrorTypeValidation, - "'pattern' must be a string"); + "'pattern' must be a string or table"); return false; } } else if (buffer.type != kObjectTypeNil) { - if (buffer.type != kObjectTypeInteger) { + if (buffer.type != kObjectTypeInteger && buffer.type != kObjectTypeBuffer) { api_set_error(err, kErrorTypeValidation, "'buffer' must be an integer"); -- cgit From 5c8025967eb31d85ff68bced2eff967f4794fe30 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 26 Jun 2022 17:57:45 +0200 Subject: refactor(api): use FOREACH_ITEM macro in autocmd --- src/nvim/api/autocmd.c | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index a4f98b3ef2..6827bad72d 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -549,9 +549,7 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc int retval; - for (size_t i = 0; i < patterns.size; i++) { - Object pat = patterns.items[i]; - + FOREACH_ITEM(patterns, pat, { // See: TODO(sctx) WITH_SCRIPT_CONTEXT(channel_id, { retval = autocmd_register(autocmd_id, @@ -569,7 +567,7 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc api_set_error(err, kErrorTypeException, "Failed to set autocmd"); goto cleanup; } - } + }) }); cleanup: @@ -848,12 +846,11 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err) FOREACH_ITEM(event_array, event_str, { GET_ONE_EVENT(event_nr, event_str, cleanup) - for (size_t i = 0; i < patterns.size; i++) { - Object pat = patterns.items[i]; + FOREACH_ITEM(patterns, pat, { char *fname = opts->buffer.type == kObjectTypeNil ? pat.data.string.data : NULL; did_aucmd |= apply_autocmds_group(event_nr, fname, NULL, true, au_group, buf, NULL, data); - } + }) }) if (did_aucmd && modeline) { @@ -867,8 +864,8 @@ cleanup: static bool check_autocmd_string_array(Array arr, char *k, Error *err) { - for (size_t i = 0; i < arr.size; i++) { - if (arr.items[i].type != kObjectTypeString) { + FOREACH_ITEM(arr, entry, { + if (entry.type != kObjectTypeString) { api_set_error(err, kErrorTypeValidation, "All entries in '%s' must be strings", @@ -877,13 +874,13 @@ static bool check_autocmd_string_array(Array arr, char *k, Error *err) } // Disallow newlines in the middle of the line. - const String l = arr.items[i].data.string; + const String l = entry.data.string; if (memchr(l.data, NL, l.size)) { api_set_error(err, kErrorTypeValidation, "String cannot contain newlines"); return false; } - } + }) return true; } @@ -970,8 +967,8 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob } Array array = v->data.array; - for (size_t i = 0; i < array.size; i++) { - char *pat = array.items[i].data.string.data; + FOREACH_ITEM(array, entry, { + char *pat = entry.data.string.data; size_t patlen = aucmd_pattern_length(pat); while (patlen) { ADD(*patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen))); @@ -979,7 +976,7 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob pat = aucmd_next_pattern(pat, patlen); patlen = aucmd_pattern_length(pat); } - } + }) } else { api_set_error(err, kErrorTypeValidation, -- cgit From 8c2b8705445e7f9b2e638f8dbb50b6d715970cda Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Mon, 27 Jun 2022 01:51:33 -0600 Subject: fix(api): change default value of 'pattern' in nvim_exec_autocmds (#19115) Omitting 'pattern' in nvim_exec_autocmds should be equivalent to omitting the 'fname' argument in :doautoall, which is equivalent to using an empty string as the pattern. Fixes regression introduced in #19091. --- src/nvim/api/autocmd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 6827bad72d..bf6402f938 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -833,7 +833,7 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err) } if (patterns.size == 0) { - ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("*"))); + ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING(""))); } if (opts->data.type != kObjectTypeNil) { -- cgit From 7e1cf6b7642f0ab14656d853d44f4409b2987b9c Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Tue, 28 Jun 2022 11:00:05 +0600 Subject: fix(inccommand): parse the command to check if it is previewable Free regprog if command isn't previewable Co-authored-by: zeertzjq --- src/nvim/api/command.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index 141e1256ff..2b2fb446a0 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -13,6 +13,7 @@ #include "nvim/ex_docmd.h" #include "nvim/lua/executor.h" #include "nvim/ops.h" +#include "nvim/regexp.h" #include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -94,6 +95,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) } goto end; } + vim_regfree(cmdinfo.cmdmod.cmod_filter_regmatch.regprog); // Parse arguments Array args = ARRAY_DICT_INIT; -- cgit From 014a88799a1d175ad121c520c9cc5bd0bb2d8813 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Tue, 28 Jun 2022 11:31:54 +0200 Subject: refactor: replace char_u #18429 Work on https://github.com/neovim/neovim/issues/459 --- src/nvim/api/command.c | 2 +- src/nvim/api/vim.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index 2b2fb446a0..80a5449d29 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -413,7 +413,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error // Simply pass the first argument (if it exists) as the arg pointer to `set_cmd_addr_type()` // since it only ever checks the first argument. - set_cmd_addr_type(&ea, argc > 0 ? (char_u *)args[0] : NULL); + set_cmd_addr_type(&ea, argc > 0 ? args[0] : NULL); if (HAS_KEY(cmd->range)) { if (!(ea.argt & EX_RANGE)) { diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index c5881dbc5f..bf3fb04a18 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -2109,7 +2109,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * bool highlights = false; if (str.size < 2 || memcmp(str.data, "%!", 2)) { - const char *const errmsg = check_stl_option((char_u *)str.data); + const char *const errmsg = check_stl_option(str.data); if (errmsg) { api_set_error(err, kErrorTypeValidation, "%s", errmsg); return result; -- cgit From 606ec8b70874095a62289f1df3934eca78625742 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Tue, 28 Jun 2022 16:22:29 +0600 Subject: feat(api): make `nvim_parse_cmd` and `nvim_cmd` support :filter Also fixes a memory leak in `parse_cmdline`. Closes #18954. --- src/nvim/api/command.c | 43 ++++++++++++++++++++++++++++++++++++++++++- src/nvim/api/keysets.lua | 5 +++++ 2 files changed, 47 insertions(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index 80a5449d29..e6a055995e 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -49,6 +49,9 @@ /// - bar: (boolean) The "|" character is treated as a command separator and the double /// quote character (\") is treated as the start of a comment. /// - mods: (dictionary) |:command-modifiers|. +/// - filter: (dictionary) |:filter|. +/// - pattern: (string) Filter pattern. Empty string if there is no filter. +/// - force: (boolean) Whether filter is inverted or not. /// - silent: (boolean) |:silent|. /// - emsg_silent: (boolean) |:silent!|. /// - sandbox: (boolean) |:sandbox|. @@ -95,7 +98,6 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) } goto end; } - vim_regfree(cmdinfo.cmdmod.cmod_filter_regmatch.regprog); // Parse arguments Array args = ARRAY_DICT_INIT; @@ -220,6 +222,14 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) PUT(result, "nextcmd", CSTR_TO_OBJ((char *)ea.nextcmd)); Dictionary mods = ARRAY_DICT_INIT; + + Dictionary filter = ARRAY_DICT_INIT; + PUT(filter, "pattern", cmdinfo.cmdmod.cmod_filter_pat + ? CSTR_TO_OBJ(cmdinfo.cmdmod.cmod_filter_pat) + : STRING_OBJ(STATIC_CSTR_TO_STRING(""))); + PUT(filter, "force", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_filter_force)); + PUT(mods, "filter", DICTIONARY_OBJ(filter)); + PUT(mods, "silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SILENT)); PUT(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_ERRSILENT)); PUT(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX)); @@ -257,6 +267,8 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) PUT(magic, "file", BOOLEAN_OBJ(cmdinfo.magic.file)); PUT(magic, "bar", BOOLEAN_OBJ(cmdinfo.magic.bar)); PUT(result, "magic", DICTIONARY_OBJ(magic)); + + undo_cmdmod(&cmdinfo.cmdmod); end: xfree(cmdline); return result; @@ -513,6 +525,35 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error goto end; } + if (HAS_KEY(mods.filter)) { + if (mods.filter.type != kObjectTypeDictionary) { + VALIDATION_ERROR("'mods.filter' must be a Dictionary"); + } + + Dict(cmd_mods_filter) filter = { 0 }; + + if (!api_dict_to_keydict(&filter, KeyDict_cmd_mods_filter_get_field, + mods.filter.data.dictionary, err)) { + goto end; + } + + if (HAS_KEY(filter.pattern)) { + if (filter.pattern.type != kObjectTypeString) { + VALIDATION_ERROR("'mods.filter.pattern' must be a String"); + } + + OBJ_TO_BOOL(cmdinfo.cmdmod.cmod_filter_force, filter.force, false, "'mods.filter.force'"); + + // "filter! // is not no-op, so add a filter if either the pattern is non-empty or if filter + // is inverted. + if (*filter.pattern.data.string.data != NUL || cmdinfo.cmdmod.cmod_filter_force) { + cmdinfo.cmdmod.cmod_filter_pat = string_to_cstr(filter.pattern.data.string); + cmdinfo.cmdmod.cmod_filter_regmatch.regprog = vim_regcomp(cmdinfo.cmdmod.cmod_filter_pat, + RE_MAGIC); + } + } + } + if (HAS_KEY(mods.tab)) { if (mods.tab.type != kObjectTypeInteger || mods.tab.data.integer < 0) { VALIDATION_ERROR("'mods.tab' must be a non-negative Integer"); diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index 70e91dd844..1c071eaf48 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -181,6 +181,7 @@ return { cmd_mods = { "silent"; "emsg_silent"; + "filter"; "sandbox"; "noautocmd"; "browse"; @@ -197,6 +198,10 @@ return { "vertical"; "split"; }; + cmd_mods_filter = { + "pattern"; + "force"; + }; cmd_opts = { "output"; }; -- cgit From 995e4879153d0f4ea72dff446c175754a1873425 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 30 Jun 2022 16:57:44 +0800 Subject: refactor(highlight)!: rename attributes to match Vim (#19159) Ref: https://github.com/vim/vim/commit/84f546363068e4ddfe14a8a2a2322bb8d3a25417 Rename: - `underlineline` to `underdouble` - `underdot` to `underdotted` - `underdash` to `underdashed` `underdouble` also now takes higher precedence than `undercurl`. --- src/nvim/api/keysets.lua | 12 ++++++------ src/nvim/api/vim.c | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index 1c071eaf48..918fe028a8 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -95,10 +95,10 @@ return { "standout"; "strikethrough"; "underline"; - "underlineline"; "undercurl"; - "underdot"; - "underdash"; + "underdouble"; + "underdotted"; + "underdashed"; "italic"; "reverse"; "nocombine"; @@ -120,10 +120,10 @@ return { "standout"; "strikethrough"; "underline"; - "underlineline"; "undercurl"; - "underdot"; - "underdash"; + "underdouble"; + "underdotted"; + "underdashed"; "italic"; "reverse"; "nocombine"; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index bf3fb04a18..f91b74cd31 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -149,10 +149,10 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) /// - bold: boolean /// - standout: boolean /// - underline: boolean -/// - underlineline: boolean /// - undercurl: boolean -/// - underdot: boolean -/// - underdash: boolean +/// - underdouble: boolean +/// - underdotted: boolean +/// - underdashed: boolean /// - strikethrough: boolean /// - italic: boolean /// - reverse: boolean -- cgit From 565f72b9689e0c440ff72c712a090224aaf7631b Mon Sep 17 00:00:00 2001 From: Javier Lopez Date: Thu, 30 Jun 2022 07:59:52 -0500 Subject: feat(marks): restore viewport on jump #15831 ** Refactor Previously most functions used to "get" a mark returned a position, changed the line number and sometimes changed even the current buffer. Now functions return a {x}fmark_T making calling context aware whether the mark is in another buffer without arcane casting. A new function is provided for switching to the mark buffer and returning a flag style Enum to convey what happen in the movement. If the cursor changed, line, columns, if it changed buffer, etc. The function to get named mark was split into multiple functions. - mark_get() -> fmark_T - mark_get_global() -> xfmark_T - mark_get_local() -> fmark_T - mark_get_motion() -> fmark_T - mark_get_visual() -> fmark_T Functions that manage the changelist and jumplist were also modified to return mark types. - get_jumplist -> fmark_T - get_changelist -> fmark_T The refactor is also seen mainly on normal.c, where all the mark movement has been siphoned through one function nv_gomark, while the other functions handle getting the mark and setting their movement flags. To handle whether context marks should be left, etc. ** Mark View While doing the refactor the concept of a mark view was also implemented: The view of a mark currently implemented as the number of lines between the mark position on creation and the window topline. This allows for moving not only back to the position of a mark but having the window look similar to when the mark was defined. This is done by carrying and extra element in the fmark_T struct, which can be extended later to also restore horizontal shift. *** User space features 1. There's a new option, jumpoptions+=view enables the mark view restoring automatically when using the jumplist, changelist, alternate-file and mark motions. g; g, '[mark] `[mark] ** Limitations - The view information is not saved in shada. - Calls to get_mark should copy the value in the pointer since we are using pos_to_mark() to wrap and provide a homogeneous interfaces. This was also a limitation in the previous state of things. --- src/nvim/api/buffer.c | 37 ++++++++++++++++++------------------- src/nvim/api/private/helpers.c | 2 +- src/nvim/api/vim.c | 10 +++++----- 3 files changed, 24 insertions(+), 25 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 1504004c6c..806b649ce6 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1156,17 +1156,17 @@ Boolean nvim_buf_del_mark(Buffer buffer, String name, Error *err) return res; } - pos_T *pos = getmark_buf(buf, *name.data, false); + fmark_T *fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, *name.data); - // pos point to NULL when there's no mark with name - if (pos == NULL) { + // fm is NULL when there's no mark with the given name + if (fm == NULL) { api_set_error(err, kErrorTypeValidation, "Invalid mark name: '%c'", *name.data); return res; } - // pos->lnum is 0 when the mark is not valid in the buffer, or is not set. - if (pos->lnum != 0) { + // mark.lnum is 0 when the mark is not valid in the buffer, or is not set. + if (fm->mark.lnum != 0 && fm->fnum == buf->handle) { // since the mark belongs to the buffer delete it. res = set_mark(buf, name, 0, 0, err); } @@ -1239,26 +1239,25 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) return rv; } - pos_T *posp; + fmark_T *fm; + pos_T pos; char mark = *name.data; - try_start(); - bufref_T save_buf; - switch_buffer(&save_buf, buf); - posp = getmark(mark, false); - restore_buffer(&save_buf); - - if (try_end(err)) { - return rv; - } - - if (posp == NULL) { + fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, mark); + if (fm == NULL) { api_set_error(err, kErrorTypeValidation, "Invalid mark name"); return rv; } + // (0, 0) uppercase/file mark set in another buffer. + if (fm->fnum != buf->handle) { + pos.lnum = 0; + pos.col = 0; + } else { + pos = fm->mark; + } - ADD(rv, INTEGER_OBJ(posp->lnum)); - ADD(rv, INTEGER_OBJ(posp->col)); + ADD(rv, INTEGER_OBJ(pos.lnum)); + ADD(rv, INTEGER_OBJ(pos.col)); return rv; } diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 436bcd5212..fad75d55be 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -916,7 +916,7 @@ bool set_mark(buf_T *buf, String name, Integer line, Integer col, Error *err) } assert(INT32_MIN <= line && line <= INT32_MAX); pos_T pos = { (linenr_T)line, (int)col, (int)col }; - res = setmark_pos(*name.data, &pos, buf->handle); + res = setmark_pos(*name.data, &pos, buf->handle, NULL); if (!res) { if (deleting) { api_set_error(err, kErrorTypeException, diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index f91b74cd31..56516b2ac7 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -2027,20 +2027,20 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err) return rv; } - xfmark_T mark = get_global_mark(*name.data); - pos_T pos = mark.fmark.mark; + xfmark_T *mark = mark_get_global(false, *name.data); // false avoids loading the mark buffer + pos_T pos = mark->fmark.mark; bool allocated = false; int bufnr; char *filename; // Marks are from an open buffer it fnum is non zero - if (mark.fmark.fnum != 0) { - bufnr = mark.fmark.fnum; + if (mark->fmark.fnum != 0) { + bufnr = mark->fmark.fnum; filename = (char *)buflist_nr2name(bufnr, true, true); allocated = true; // Marks comes from shada } else { - filename = mark.fname; + filename = mark->fname; bufnr = 0; } -- cgit From 7a907c3314f939a3d2983ac07edc5c9672957352 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 2 Jul 2022 19:35:11 +0800 Subject: feat(api): add `unsilent` to command APIs --- src/nvim/api/command.c | 7 +++++++ src/nvim/api/keysets.lua | 1 + 2 files changed, 8 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index e6a055995e..4c2404a0d8 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -54,6 +54,7 @@ /// - force: (boolean) Whether filter is inverted or not. /// - silent: (boolean) |:silent|. /// - emsg_silent: (boolean) |:silent!|. +/// - unsilent: (boolean) |:unsilent|. /// - sandbox: (boolean) |:sandbox|. /// - noautocmd: (boolean) |:noautocmd|. /// - browse: (boolean) |:browse|. @@ -232,6 +233,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) PUT(mods, "silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SILENT)); PUT(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_ERRSILENT)); + PUT(mods, "unsilent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_UNSILENT)); PUT(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX)); PUT(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_NOAUTOCMD)); PUT(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.cmod_tab)); @@ -598,6 +600,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error OBJ_TO_CMOD_FLAG(CMOD_SILENT, mods.silent, false, "'mods.silent'"); OBJ_TO_CMOD_FLAG(CMOD_ERRSILENT, mods.emsg_silent, false, "'mods.emsg_silent'"); + OBJ_TO_CMOD_FLAG(CMOD_UNSILENT, mods.silent, false, "'mods.unsilent'"); OBJ_TO_CMOD_FLAG(CMOD_SANDBOX, mods.sandbox, false, "'mods.sandbox'"); OBJ_TO_CMOD_FLAG(CMOD_NOAUTOCMD, mods.noautocmd, false, "'mods.noautocmd'"); OBJ_TO_CMOD_FLAG(CMOD_BROWSE, mods.browse, false, "'mods.browse'"); @@ -722,6 +725,10 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin kv_concat(cmdline, "silent "); } + if (cmdinfo->cmdmod.cmod_flags & CMOD_UNSILENT) { + kv_concat(cmdline, "unsilent "); + } + switch (cmdinfo->cmdmod.cmod_split & (WSP_ABOVE | WSP_BELOW | WSP_TOP | WSP_BOT)) { case WSP_ABOVE: kv_concat(cmdline, "aboveleft "); diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index 918fe028a8..21319fb7a6 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -181,6 +181,7 @@ return { cmd_mods = { "silent"; "emsg_silent"; + "unsilent"; "filter"; "sandbox"; "noautocmd"; -- cgit From 73526abbbdf08e68e572b8e54c583de5a0323484 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 16 Jul 2022 09:31:05 +0800 Subject: fix(api): do not switch win/buf if getting option in current win/buf (#19383) --- src/nvim/api/options.c | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index 8c174fc129..4ed676e613 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -504,6 +504,7 @@ static int access_option_value(char *key, long *numval, char **stringval, int op static int access_option_value_for(char *key, long *numval, char **stringval, int opt_flags, int opt_type, void *from, bool get, Error *err) { + bool need_switch = false; switchwin_T switchwin; aco_save_T aco; int result = 0; @@ -511,24 +512,32 @@ static int access_option_value_for(char *key, long *numval, char **stringval, in try_start(); switch (opt_type) { case SREQ_WIN: - if (switch_win_noblock(&switchwin, (win_T *)from, win_find_tabpage((win_T *)from), true) - == FAIL) { - restore_win_noblock(&switchwin, true); - if (try_end(err)) { + need_switch = (win_T *)from != curwin; + if (need_switch) { + if (switch_win_noblock(&switchwin, (win_T *)from, win_find_tabpage((win_T *)from), true) + == FAIL) { + restore_win_noblock(&switchwin, true); + if (try_end(err)) { + return result; + } + api_set_error(err, kErrorTypeException, "Problem while switching windows"); return result; } - api_set_error(err, - kErrorTypeException, - "Problem while switching windows"); - return result; } result = access_option_value(key, numval, stringval, opt_flags, get, err); - restore_win_noblock(&switchwin, true); + if (need_switch) { + restore_win_noblock(&switchwin, true); + } break; case SREQ_BUF: - aucmd_prepbuf(&aco, (buf_T *)from); + need_switch = (buf_T *)from != curbuf; + if (need_switch) { + aucmd_prepbuf(&aco, (buf_T *)from); + } result = access_option_value(key, numval, stringval, opt_flags, get, err); - aucmd_restbuf(&aco); + if (need_switch) { + aucmd_restbuf(&aco); + } break; case SREQ_GLOBAL: result = access_option_value(key, numval, stringval, opt_flags, get, err); -- cgit