From ff75f345ab5fa57c6560db021e8eb099aff90472 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 22 Nov 2024 06:52:32 +0800 Subject: fix(highlight): 'winhl' shouldn't take priority over API (#31288) --- src/nvim/api/window.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 5a4972ef23..ee7729ce81 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -476,6 +476,7 @@ void nvim_win_set_hl_ns(Window window, Integer ns_id, Error *err) } win->w_ns_hl = (NS)ns_id; + win->w_ns_hl_winhl = -1; win->w_hl_needs_update = true; redraw_later(win, UPD_NOT_VALID); } -- cgit From bff07f6dd0d8e58748f36670685dd6157a67976b Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Fri, 22 Nov 2024 18:32:51 +0600 Subject: fix(api): don't try to get/set option for invalid option name (#31302) Problem: `validate_option_value_args()` returns `OK` even if option name is invalid or if option doesn't have the supported scope, which leads to Neovim still trying to erroneously get/set the option in those cases, which can lead to an assertion failure when `option_has_scope()` is invoked. This issue miraculously doesn't exist in release builds since the assertion is skipped and `(get/set)_option_value_for` returns if there is an error set, but that is not the intended location for that error to be caught. Solution: Make `validate_option_value_args()` return `FAIL` if there is an error set, which causes the API option functions to return early instead of trying to get/set an invalid option. --- src/nvim/api/options.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index 3289daeb6f..8fd9904f7a 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -95,7 +95,7 @@ static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex * } } - return OK; + return ERROR_SET(err) ? FAIL : OK; #undef HAS_KEY_X } -- cgit From 8516c2dc1f301c439695629fff771227dbe00d30 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Sat, 23 Nov 2024 14:22:06 +0600 Subject: refactor(options): autogenerate valid values and flag enums for options (#31089) Problem: Option metadata like list of valid values for an option and option flags are not listed in the `options.lua` file and are instead manually defined in C, which means option metadata is split between several places. Solution: Put metadata such as list of valid values for an option and option flags in `options.lua`, and autogenerate the corresponding C variables and enums. Supersedes #28659 Co-authored-by: glepnir --- 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 83f9aa573d..ab52612f9f 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -2156,7 +2156,7 @@ Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena, if (statuscol.foldinfo.fi_level != 0 && statuscol.foldinfo.fi_lines > 0) { wp->w_cursorline = statuscol.foldinfo.fi_lnum; } - statuscol.use_cul = lnum == wp->w_cursorline && (wp->w_p_culopt_flags & CULOPT_NBR); + statuscol.use_cul = lnum == wp->w_cursorline && (wp->w_p_culopt_flags & kOptCuloptFlagNumber); } statuscol.sign_cul_id = statuscol.use_cul ? cul_id : 0; -- cgit From beec377e905baca73e772080c4f276c800ad2a40 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Mon, 25 Nov 2024 15:07:56 +0600 Subject: refactor(options): fix confusing naming of `scope` and `req_scope` (#31317) Problem: The name `scope` is often used to refer to option flags because `OPT_LOCAL` and `OPT_GLOBAL` are often used to determine the option scope. This leads to the name `req_scope` being used for actual option scopes instead. Solution: Since the end-goal is to remove `OPT_LOCAL` and `OPT_GLOBAL` entirely and replace them with `OptScope`, rename `OptScope` variables to `scope` and the old scope flag variables to `opt_flags`. --- src/nvim/api/deprecated.c | 22 +++++++++--------- src/nvim/api/options.c | 57 ++++++++++++++++++++++++----------------------- 2 files changed, 40 insertions(+), 39 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index b38a7d4173..493856905f 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -636,12 +636,12 @@ void nvim_win_set_option(uint64_t channel_id, Window window, String name, Object /// Gets the value of a global or local (buffer, window) option. /// /// @param[in] from Pointer to buffer or window for local option value. -/// @param req_scope Requested option scope. See OptScope in option.h. +/// @param scope Option scope. See OptScope in option.h. /// @param name The option name. /// @param[out] err Details of an error that may have occurred. /// /// @return the option value. -static Object get_option_from(void *from, OptScope req_scope, String name, Error *err) +static Object get_option_from(void *from, OptScope scope, String name, Error *err) { VALIDATE_S(name.size > 0, "option name", "", { return (Object)OBJECT_INIT; @@ -650,9 +650,9 @@ static Object get_option_from(void *from, OptScope req_scope, String name, Error OptIndex opt_idx = find_option(name.data); OptVal value = NIL_OPTVAL; - if (option_has_scope(opt_idx, req_scope)) { - value = get_option_value_for(opt_idx, req_scope == kOptScopeGlobal ? OPT_GLOBAL : OPT_LOCAL, - req_scope, from, err); + if (option_has_scope(opt_idx, scope)) { + value = get_option_value_for(opt_idx, scope == kOptScopeGlobal ? OPT_GLOBAL : OPT_LOCAL, + scope, from, err); if (ERROR_SET(err)) { return (Object)OBJECT_INIT; } @@ -668,12 +668,12 @@ static Object get_option_from(void *from, OptScope req_scope, String name, Error /// Sets the value of a global or local (buffer, window) option. /// /// @param[in] to Pointer to buffer or window for local option value. -/// @param req_scope Requested option scope. See OptScope in option.h. +/// @param scope Option scope. See OptScope in option.h. /// @param name The option name. /// @param value New option value. /// @param[out] err Details of an error that may have occurred. -static void set_option_to(uint64_t channel_id, void *to, OptScope req_scope, String name, - Object value, Error *err) +static void set_option_to(uint64_t channel_id, void *to, OptScope scope, String name, Object value, + Error *err) { VALIDATE_S(name.size > 0, "option name", "", { return; @@ -698,12 +698,12 @@ static void set_option_to(uint64_t channel_id, void *to, OptScope req_scope, Str // For global-win-local options -> setlocal // For win-local options -> setglobal and setlocal (opt_flags == 0) const int opt_flags - = (req_scope == kOptScopeWin && !option_has_scope(opt_idx, kOptScopeGlobal)) + = (scope == kOptScopeWin && !option_has_scope(opt_idx, kOptScopeGlobal)) ? 0 - : ((req_scope == kOptScopeGlobal) ? OPT_GLOBAL : OPT_LOCAL); + : ((scope == kOptScopeGlobal) ? OPT_GLOBAL : OPT_LOCAL); WITH_SCRIPT_CONTEXT(channel_id, { - set_option_value_for(name.data, opt_idx, optval, opt_flags, req_scope, to, err); + set_option_value_for(name.data, opt_idx, optval, opt_flags, scope, to, err); }); } diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index 8fd9904f7a..bfd63ca489 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -23,15 +23,15 @@ #endif static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex *opt_idxp, - int *scope, OptScope *req_scope, void **from, char **filetype, + int *opt_flags, OptScope *scope, void **from, char **filetype, Error *err) { #define HAS_KEY_X(d, v) HAS_KEY(d, option, v) if (HAS_KEY_X(opts, scope)) { if (!strcmp(opts->scope.data, "local")) { - *scope = OPT_LOCAL; + *opt_flags = OPT_LOCAL; } else if (!strcmp(opts->scope.data, "global")) { - *scope = OPT_GLOBAL; + *opt_flags = OPT_GLOBAL; } else { VALIDATE_EXP(false, "scope", "'local' or 'global'", NULL, { return FAIL; @@ -39,14 +39,14 @@ static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex * } } - *req_scope = kOptScopeGlobal; + *scope = kOptScopeGlobal; if (filetype != NULL && HAS_KEY_X(opts, filetype)) { *filetype = opts->filetype.data; } if (HAS_KEY_X(opts, win)) { - *req_scope = kOptScopeWin; + *scope = kOptScopeWin; *from = find_window_by_handle(opts->win, err); if (ERROR_SET(err)) { return FAIL; @@ -54,12 +54,12 @@ static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex * } if (HAS_KEY_X(opts, buf)) { - VALIDATE(!(HAS_KEY_X(opts, scope) && *scope == OPT_GLOBAL), "%s", + VALIDATE(!(HAS_KEY_X(opts, scope) && *opt_flags == OPT_GLOBAL), "%s", "cannot use both global 'scope' and 'buf'", { return FAIL; }); - *scope = OPT_LOCAL; - *req_scope = kOptScopeBuf; + *opt_flags = OPT_LOCAL; + *scope = kOptScopeBuf; *from = find_buffer_by_handle(opts->buf, err); if (ERROR_SET(err)) { return FAIL; @@ -81,10 +81,10 @@ static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex * if (*opt_idxp == kOptInvalid) { // unknown option api_set_error(err, kErrorTypeValidation, "Unknown option '%s'", name); - } else if (*req_scope == kOptScopeBuf || *req_scope == kOptScopeWin) { + } else if (*scope == kOptScopeBuf || *scope == kOptScopeWin) { // if 'buf' or 'win' is passed, make sure the option supports it - if (!option_has_scope(*opt_idxp, *req_scope)) { - char *tgt = *req_scope == kOptScopeBuf ? "buf" : "win"; + if (!option_has_scope(*opt_idxp, *scope)) { + char *tgt = *scope == kOptScopeBuf ? "buf" : "win"; char *global = option_has_scope(*opt_idxp, kOptScopeGlobal) ? "global " : ""; char *req = option_has_scope(*opt_idxp, kOptScopeBuf) ? "buffer-local " @@ -151,13 +151,13 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) FUNC_API_SINCE(9) FUNC_API_RET_ALLOC { OptIndex opt_idx = 0; - int scope = 0; - OptScope req_scope = kOptScopeGlobal; + int opt_flags = 0; + OptScope scope = kOptScopeGlobal; void *from = NULL; char *filetype = NULL; - if (!validate_option_value_args(opts, name.data, &opt_idx, &scope, &req_scope, &from, &filetype, - err)) { + if (!validate_option_value_args(opts, name.data, &opt_idx, &opt_flags, &scope, &from, + &filetype, err)) { return (Object)OBJECT_INIT; } @@ -181,7 +181,7 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) from = ftbuf; } - OptVal value = get_option_value_for(opt_idx, scope, req_scope, from, err); + OptVal value = get_option_value_for(opt_idx, opt_flags, scope, from, err); if (ftbuf != NULL) { // restore curwin/curbuf and a few other things @@ -224,10 +224,11 @@ void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict( FUNC_API_SINCE(9) { OptIndex opt_idx = 0; - int scope = 0; - OptScope req_scope = kOptScopeGlobal; + int opt_flags = 0; + OptScope scope = kOptScopeGlobal; void *to = NULL; - if (!validate_option_value_args(opts, name.data, &opt_idx, &scope, &req_scope, &to, NULL, err)) { + if (!validate_option_value_args(opts, name.data, &opt_idx, &opt_flags, &scope, &to, NULL, + err)) { return; } @@ -237,9 +238,9 @@ void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict( // - 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 (req_scope == kOptScopeWin && scope == 0) { + if (scope == kOptScopeWin && opt_flags == 0) { if (option_has_scope(opt_idx, kOptScopeGlobal)) { - scope = OPT_LOCAL; + opt_flags = OPT_LOCAL; } } @@ -255,7 +256,7 @@ void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict( }); WITH_SCRIPT_CONTEXT(channel_id, { - set_option_value_for(name.data, opt_idx, optval, scope, req_scope, to, err); + set_option_value_for(name.data, opt_idx, optval, opt_flags, scope, to, err); }); } @@ -310,16 +311,16 @@ Dict nvim_get_option_info2(String name, Dict(option) *opts, Arena *arena, Error FUNC_API_SINCE(11) { OptIndex opt_idx = 0; - int scope = 0; - OptScope req_scope = kOptScopeGlobal; + int opt_flags = 0; + OptScope scope = kOptScopeGlobal; void *from = NULL; - if (!validate_option_value_args(opts, name.data, &opt_idx, &scope, &req_scope, &from, NULL, + if (!validate_option_value_args(opts, name.data, &opt_idx, &opt_flags, &scope, &from, NULL, err)) { return (Dict)ARRAY_DICT_INIT; } - buf_T *buf = (req_scope == kOptScopeBuf) ? (buf_T *)from : curbuf; - win_T *win = (req_scope == kOptScopeWin) ? (win_T *)from : curwin; + buf_T *buf = (scope == kOptScopeBuf) ? (buf_T *)from : curbuf; + win_T *win = (scope == kOptScopeWin) ? (win_T *)from : curwin; - return get_vimoption(name, scope, buf, win, arena, err); + return get_vimoption(name, opt_flags, buf, win, arena, err); } -- cgit From 8d55cc218cfed54136677398ca76c45987b15f29 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sun, 24 Nov 2024 10:40:56 +0000 Subject: feat(keysets): teach Union and LuaRefOf --- src/nvim/api/autocmd.c | 2 +- src/nvim/api/command.c | 4 +- src/nvim/api/keysets_defs.h | 109 ++++++++++++++++++++++++++------------------ src/nvim/api/private/defs.h | 2 + 4 files changed, 70 insertions(+), 47 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 22932fd1a2..db87500d08 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -631,7 +631,7 @@ Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augrou FUNC_API_SINCE(9) { char *augroup_name = name.data; - bool clear_autocmds = api_object_to_bool(opts->clear, "clear", true, err); + bool clear_autocmds = GET_BOOL_OR_TRUE(opts, create_augroup, clear); int augroup = -1; WITH_SCRIPT_CONTEXT(channel_id, { diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index ab57d5c009..709e18af73 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -226,8 +226,8 @@ Dict(cmd) nvim_parse_cmd(String str, Dict(empty) *opts, Arena *arena, Error *err addr = "?"; break; } - PUT_KEY(result, cmd, addr, CSTR_AS_OBJ(addr)); - PUT_KEY(result, cmd, nextcmd, CSTR_AS_OBJ(ea.nextcmd)); + PUT_KEY(result, cmd, addr, cstr_as_string(addr)); + PUT_KEY(result, cmd, nextcmd, cstr_as_string(ea.nextcmd)); // TODO(bfredl): nested keydict would be nice.. Dict mods = arena_dict(arena, 20); diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h index 96aabb851f..48f5f7246c 100644 --- a/src/nvim/api/keysets_defs.h +++ b/src/nvim/api/keysets_defs.h @@ -8,18 +8,18 @@ typedef struct { typedef struct { OptionalKeys is_set__context_; - Array types; + ArrayOf(String) types; } Dict(context); typedef struct { OptionalKeys is_set__set_decoration_provider_; - LuaRef on_start; - LuaRef on_buf; - LuaRef on_win; - LuaRef on_line; - LuaRef on_end; - LuaRef _on_hl_def; - LuaRef _on_spell_nav; + LuaRefOf(("start" _, Integer tick)) on_start; + LuaRefOf(("buf" _, Integer bufnr, Integer tick)) on_buf; + LuaRefOf(("win" _, Integer winid, Integer bufnr, Integer toprow, Integer botrow)) on_win; + LuaRefOf(("line" _, Integer winid, Integer bufnr, Integer row)) on_line; + LuaRefOf(("end" _, Integer tick)) on_end; + LuaRefOf(("hl_def" _)) _on_hl_def; + LuaRefOf(("spell_nav" _)) _on_spell_nav; } Dict(set_decoration_provider); typedef struct { @@ -116,7 +116,7 @@ typedef struct { String relative; String split; Window win; - Array bufpos; + ArrayOf(Integer) bufpos; Boolean external; Boolean focusable; Boolean mouse; @@ -172,17 +172,17 @@ typedef struct { Boolean altfont; Boolean nocombine; Boolean default_ DictKey(default); - Object cterm; - Object foreground; - Object fg; - Object background; - Object bg; - Object ctermfg; - Object ctermbg; - Object special; - Object sp; - Object link; - Object global_link; + Union(Integer, String) cterm; + Union(Integer, String) foreground; + Union(Integer, String) fg; + Union(Integer, String) background; + Union(Integer, String) bg; + Union(Integer, String) ctermfg; + Union(Integer, String) ctermbg; + Union(Integer, String) special; + Union(Integer, String) sp; + HLGroupID link; + HLGroupID global_link; Boolean fallback; Integer blend; Boolean fg_indexed; @@ -230,9 +230,9 @@ typedef struct { typedef struct { OptionalKeys is_set__clear_autocmds_; Buffer buffer; - Object event; - Object group; - Object pattern; + Union(String, ArrayOf(String)) event; + Union(Integer, String) group; + Union(String, ArrayOf(String)) pattern; } Dict(clear_autocmds); typedef struct { @@ -241,31 +241,32 @@ typedef struct { Object callback; String command; String desc; - Object group; + Union(Integer, String) group; Boolean nested; Boolean once; - Object pattern; + Union(String, ArrayOf(String)) pattern; } Dict(create_autocmd); typedef struct { OptionalKeys is_set__exec_autocmds_; Buffer buffer; - Object group; + Union(Integer, String) group; Boolean modeline; - Object pattern; + Union(String, ArrayOf(String)) pattern; Object data; } Dict(exec_autocmds); typedef struct { OptionalKeys is_set__get_autocmds_; - Object event; - Object group; - Object pattern; - Object buffer; + Union(String, ArrayOf(String)) event; + Union(Integer, String) group; + Union(String, ArrayOf(String)) pattern; + Union(Integer, ArrayOf(Integer)) buffer; } Dict(get_autocmds); typedef struct { - Object clear; + OptionalKeys is_set__create_augroup_; + Boolean clear; } Dict(create_augroup); typedef struct { @@ -275,12 +276,12 @@ typedef struct { Integer count; String reg; Boolean bang; - Array args; + ArrayOf(String) args; Dict magic; Dict mods; - Object nargs; - Object addr; - Object nextcmd; + Union(Integer, String) nargs; + String addr; + String nextcmd; } Dict(cmd); typedef struct { @@ -333,11 +334,30 @@ typedef struct { typedef struct { OptionalKeys is_set__buf_attach_; - LuaRef on_lines; - LuaRef on_bytes; - LuaRef on_changedtick; - LuaRef on_detach; - LuaRef on_reload; + LuaRefOf(("lines" _, + Integer bufnr, + Integer changedtick, + Integer first, + Integer last_old, + Integer last_new, + Integer byte_count, + Integer *deleted_codepoints, + Integer *deleted_codeunits), *Boolean) on_lines; + LuaRefOf(("bytes" _, + Integer bufnr, + Integer changedtick, + Integer start_row, + Integer start_col, + Integer start_byte, + Integer old_end_row, + Integer old_end_col, + Integer old_end_byte, + Integer new_end_row, + Integer new_end_col, + Integer new_end_byte), *Boolean) on_bytes; + LuaRefOf(("changedtick" _, Integer bufnr, Integer changedtick)) on_changedtick; + LuaRefOf(("detach" _, Integer bufnr)) on_detach; + LuaRefOf(("reload" _, Integer bufnr)) on_reload; Boolean utf_sizes; Boolean preview; } Dict(buf_attach); @@ -350,7 +370,7 @@ typedef struct { typedef struct { OptionalKeys is_set__open_term_; - LuaRef on_input; + LuaRefOf(("input" _, Integer term, Integer bufnr, any data)) on_input; Boolean force_crlf; } Dict(open_term); @@ -361,12 +381,13 @@ typedef struct { typedef struct { OptionalKeys is_set__xdl_diff_; - LuaRef on_hunk; + LuaRefOf((Integer start_a, Integer count_a, Integer start_b, Integer count_b), + *Integer) on_hunk; String result_type; String algorithm; Integer ctxlen; Integer interhunkctxlen; - Object linematch; + Union(Boolean, Integer) linematch; Boolean ignore_whitespace; Boolean ignore_whitespace_change; Boolean ignore_whitespace_change_at_eol; diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index 26d5ac09a8..6dee86dcf5 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -21,6 +21,8 @@ # define Dict(name) KeyDict_##name # define DictHash(name) KeyDict_##name##_get_field # define DictKey(name) +# define LuaRefOf(...) LuaRef +# define Union(...) Object # include "api/private/defs.h.inline.generated.h" #endif -- cgit From 716adbcc4563f5b4d1b7bc0301530296c538a33c Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 2 Dec 2024 04:16:44 -0800 Subject: fix(api): deprecate nvim_subscribe, nvim_unsubscribe #30456 Problem: - nvim_subscribe, nvim_unsubscribe were deprecated in aec4938a21a02d279d13a9eb64ef3b7cc592c374 but this wasn't set in the API metadata. - The function annotations ``` FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY FUNC_API_DEPRECATED_SINCE(13) ``` cause this test to fail: ``` RUN T3 api metadata functions are compatible with old metadata or have new level: 3.00 ms ERR test/functional/api/version_spec.lua:135: function vim_subscribe was removed but exists in level 0 which nvim should be compatible with stack traceback: test/functional/api/version_spec.lua:135: in function ``` Solution: - Set the API metadata. - Rearrange the annotations so that FUNC_API_DEPRECATED_SINCE is 2nd: ``` FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) FUNC_API_REMOTE_ONLY ``` --- src/nvim/api/deprecated.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index 493856905f..51ed6eaa41 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -798,7 +798,8 @@ theend: /// @param channel_id Channel id (passed automatically by the dispatcher) /// @param event Event type string void nvim_subscribe(uint64_t channel_id, String event) - FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY +// XXX: c_grammar.lua is order-sensitive. + FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) FUNC_API_REMOTE_ONLY { // Does nothing. `rpcnotify(0,…)` broadcasts to all channels, there are no "subscriptions". } @@ -808,7 +809,8 @@ void nvim_subscribe(uint64_t channel_id, String event) /// @param channel_id Channel id (passed automatically by the dispatcher) /// @param event Event type string void nvim_unsubscribe(uint64_t channel_id, String event) - FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY +// XXX: c_grammar.lua is order-sensitive. + FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) FUNC_API_REMOTE_ONLY { // Does nothing. `rpcnotify(0,…)` broadcasts to all channels, there are no "subscriptions". } -- cgit From ae93c7f369a174f3d738ab55030de2c9dfc10c57 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 3 Dec 2024 09:44:28 -0800 Subject: docs: misc, help tags for neovim.io searches #31428 Problem: Various keywords are commonly searched-for on https://neovim.io, but don't have help tags. Solution: Add help tags. fix #31327 --- 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 ab52612f9f..1262af5e40 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1040,7 +1040,7 @@ fail: /// Open a terminal instance in a buffer /// /// By default (and currently the only option) the terminal will not be -/// connected to an external process. Instead, input send on the channel +/// connected to an external process. Instead, input sent on the channel /// will be echoed directly by the terminal. This is useful to display /// ANSI terminal sequences returned as part of a rpc message, or similar. /// @@ -1051,6 +1051,18 @@ fail: /// Then |nvim_chan_send()| can be called immediately to process sequences /// in a virtual terminal having the intended size. /// +/// Example: this `TermHl` command can be used to display and highlight raw ANSI termcodes, so you +/// can use Nvim as a "scrollback pager" (for terminals like kitty): [terminal-scrollback-pager]() +/// +/// ```lua +/// vim.api.nvim_create_user_command('TermHl', function() +/// local b = vim.api.nvim_create_buf(false, true) +/// local chan = vim.api.nvim_open_term(b, {}) +/// vim.api.nvim_chan_send(chan, table.concat(vim.api.nvim_buf_get_lines(0, 0, -1, false), '\n')) +/// vim.api.nvim_win_set_buf(0, b) +/// end, { desc = 'Highlights ANSI termcodes in curbuf' }) +/// ``` +/// /// @param buffer the buffer to use (expected to be empty) /// @param opts Optional parameters. /// - on_input: Lua callback for input sent, i e keypresses in terminal -- cgit From e9f4ceeb7467364554ecef770fd3380e89457abb Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 6 Dec 2024 20:01:28 +0800 Subject: fix(events): don't expand `args.file` for Lua callback (#31473) Problem: In an autocommand Lua callback whether `args.file` is expanded depends on whether `expand('')` has been called. Solution: Always use the unexpanded file name for `args.file`. Related to #31306 and vim/vim#16106. This doesn't provide `sfname`, but at least makes `args.file` have a consistent value. --- src/nvim/api/autocmd.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index db87500d08..45e2de69e0 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -386,9 +386,9 @@ cleanup: /// - id: (number) autocommand id /// - event: (string) name of the triggered event |autocmd-events| /// - group: (number|nil) autocommand group id, if any -/// - match: (string) expanded value of [] -/// - buf: (number) expanded value of [] -/// - file: (string) expanded value of [] +/// - file: (string) [] (not expanded to a full path) +/// - match: (string) [] (expanded to a full path) +/// - buf: (number) [] /// - data: (any) arbitrary data passed from [nvim_exec_autocmds()] [event-data]() /// - command (string) optional: Vim command to execute on event. Cannot be used with /// {callback} -- cgit From 01a97d2ad75a459cad850d542f9ad7c4467cb380 Mon Sep 17 00:00:00 2001 From: phanium <91544758+phanen@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:59:24 +0800 Subject: fix(api): nvim_win_set_buf(0, 0) fails if 'winfixbuf' is set #31576 ## Problem With 'winfixbuf' enabled, `nvim_win_set_buf` and `nvim_set_current_buf` fail even if targeting the already-current buffer. vim.wo.winfixbuf = true vim.api.nvim_win_set_buf(0, 0) vim.api.nvim_set_current_buf(0) Solution: Check for this condition. --- 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 1262af5e40..42fc21deac 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -888,7 +888,7 @@ void nvim_set_current_buf(Buffer buffer, Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); - if (!buf) { + if (!buf || curwin->w_buffer == buf) { return; } diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index ee7729ce81..f415415fa7 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -59,7 +59,7 @@ void nvim_win_set_buf(Window window, Buffer buffer, Error *err) { win_T *win = find_window_by_handle(window, err); buf_T *buf = find_buffer_by_handle(buffer, err); - if (!win || !buf) { + if (!win || !buf || win->w_buffer == buf) { return; } -- cgit From 167a2383b9966ac227a77b0221088246e14ce75a Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 16 Dec 2024 04:00:20 -0800 Subject: fix(api): not using TRY_WRAP, generic error messages #31595 Problem: - API functions using `try_start` directly instead of `TRY_WRAP`, do not surface the underlying error message, and instead show generic things like "Failed to set buffer". - Error handling code is duplicated in the API impl, instead of delegating to the vim buffer/window handling logic. Solution: - Use `TRY_WRAP`. --- src/nvim/api/private/helpers.c | 9 +++++---- src/nvim/api/vim.c | 19 +++++-------------- src/nvim/api/window.c | 7 +------ 3 files changed, 11 insertions(+), 24 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 8ddaecc58e..5bf66a092f 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -101,9 +101,9 @@ bool try_leave(const TryState *const tstate, Error *const err) return ret; } -/// Start block that may cause vimscript exceptions +/// Starts a block that may cause Vimscript exceptions; must be mirrored by `try_end()` call. /// -/// Each try_start() call should be mirrored by try_end() call. +/// Note: use `TRY_WRAP` instead (except in `FUNC_API_FAST` functions such as nvim_get_runtime_file). /// /// To be used as a replacement of `:try … catch … endtry` in C code, in cases /// when error flag could not already be set. If there may be pending error @@ -114,8 +114,9 @@ void try_start(void) trylevel++; } -/// End try block, set the error message if any and return true if an error -/// occurred. +/// Ends a `try_start` block; sets error message if any and returns true if an error occurred. +/// +/// Note: use `TRY_WRAP` instead (except in `FUNC_API_FAST` functions such as nvim_get_runtime_file). /// /// @param err Pointer to the stack-allocated error object /// @return true if an error occurred diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 42fc21deac..1acfa0d34b 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -596,6 +596,7 @@ ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Arena *arena, Er int flags = DIP_DIRFILE | (all ? DIP_ALL : 0); TryState tstate; + // XXX: intentionally not using `TRY_WRAP`, to avoid `did_emsg=false` in `try_end`. try_enter(&tstate); do_in_runtimepath((name.size ? name.data : ""), flags, find_runtime_cb, &cookie); vim_ignored = try_leave(&tstate, err); @@ -888,23 +889,13 @@ void nvim_set_current_buf(Buffer buffer, Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); - if (!buf || curwin->w_buffer == buf) { - return; - } - - if (curwin->w_p_wfb) { - api_set_error(err, kErrorTypeException, "%s", e_winfixbuf_cannot_go_to_buffer); + if (!buf) { return; } - try_start(); - int result = do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0); - if (!try_end(err) && result == FAIL) { - api_set_error(err, - kErrorTypeException, - "Failed to switch to buffer %d", - buffer); - } + TRY_WRAP(err, { + do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0); + }); } /// Gets the current list of window handles. diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index f415415fa7..170d5c6cdb 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -59,12 +59,7 @@ void nvim_win_set_buf(Window window, Buffer buffer, Error *err) { win_T *win = find_window_by_handle(window, err); buf_T *buf = find_buffer_by_handle(buffer, err); - if (!win || !buf || win->w_buffer == buf) { - return; - } - - if (win->w_p_wfb) { - api_set_error(err, kErrorTypeException, "%s", e_winfixbuf_cannot_go_to_buffer); + if (!win || !buf) { return; } -- cgit From 022449b5223659d515b78bada7de2fac8718820a Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 16 Dec 2024 08:34:16 -0800 Subject: fix(api): generic error messages, not using TRY_WRAP #31596 Problem: - API functions using `try_start` directly, do not surface the underlying error message, and instead show generic messages. - Error-handling code is duplicated in the API impl. - Failure modes are not tested. Solution: - Use `TRY_WRAP`. - Add tests. --- src/nvim/api/vim.c | 35 +++++++++-------------------------- src/nvim/api/vimscript.c | 24 ++++++++++-------------- src/nvim/api/window.c | 38 ++++++++++++++------------------------ 3 files changed, 33 insertions(+), 64 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 1acfa0d34b..d82f90d1dd 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -669,16 +669,9 @@ void nvim_set_current_dir(String dir, Error *err) memcpy(string, dir.data, dir.size); string[dir.size] = NUL; - try_start(); - - if (!changedir_func(string, kCdScopeGlobal)) { - if (!try_end(err)) { - api_set_error(err, kErrorTypeException, "Failed to change directory"); - } - return; - } - - try_end(err); + TRY_WRAP(err, { + changedir_func(string, kCdScopeGlobal); + }); } /// Gets the current line. @@ -942,14 +935,9 @@ void nvim_set_current_win(Window window, Error *err) return; } - try_start(); - goto_tabpage_win(win_find_tabpage(win), win); - if (!try_end(err) && win != curwin) { - api_set_error(err, - kErrorTypeException, - "Failed to switch to window %d", - window); - } + TRY_WRAP(err, { + goto_tabpage_win(win_find_tabpage(win), win); + }); } /// Creates a new, empty, unnamed buffer. @@ -1208,14 +1196,9 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err) return; } - try_start(); - goto_tabpage_tp(tp, true, true); - if (!try_end(err) && tp != curtab) { - api_set_error(err, - kErrorTypeException, - "Failed to switch to tabpage %d", - tabpage); - } + TRY_WRAP(err, { + goto_tabpage_tp(tp, true, true); + }); } /// Pastes at cursor (in any mode), and sets "redo" so dot (|.|) will repeat the input. UIs call diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 165cc93fbe..0ff2b037ce 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -125,19 +125,17 @@ theend: /// /// On execution error: fails with Vimscript error, updates v:errmsg. /// -/// Prefer using |nvim_cmd()| or |nvim_exec2()| over this. To evaluate multiple lines of Vim script -/// or an Ex command directly, use |nvim_exec2()|. 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()|. +/// Prefer |nvim_cmd()| or |nvim_exec2()| instead. To modify an Ex command in a structured way +/// before executing it, modify the result of |nvim_parse_cmd()| then pass it to |nvim_cmd()|. /// /// @param command Ex command string /// @param[out] err Error details (Vim error), if any void nvim_command(String command, Error *err) FUNC_API_SINCE(1) { - try_start(); - do_cmdline_cmd(command.data); - try_end(err); + TRY_WRAP(err, { + do_cmdline_cmd(command.data); + }); } /// Evaluates a Vimscript |expression|. Dicts and Lists are recursively expanded. @@ -283,13 +281,11 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Arena *arena, bool mustfree = false; switch (dict.type) { case kObjectTypeString: - try_start(); - if (eval0(dict.data.string.data, &rettv, NULL, &EVALARG_EVALUATE) == FAIL) { - api_set_error(err, kErrorTypeException, - "Failed to evaluate dict expression"); - } - clear_evalarg(&EVALARG_EVALUATE, NULL); - if (try_end(err)) { + TRY_WRAP(err, { + eval0(dict.data.string.data, &rettv, NULL, &EVALARG_EVALUATE); + clear_evalarg(&EVALARG_EVALUATE, NULL); + }); + if (ERROR_SET(err)) { return rv; } // Evaluation of the string arg created a new dict or increased the diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 170d5c6cdb..f5e8d8f086 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -182,14 +182,9 @@ void nvim_win_set_height(Window window, Integer height, Error *err) return; } - if (height > INT_MAX || height < INT_MIN) { - api_set_error(err, kErrorTypeValidation, "Height value outside range"); - return; - } - - try_start(); - win_setheight_win((int)height, win); - try_end(err); + TRY_WRAP(err, { + win_setheight_win((int)height, win); + }); } /// Gets the window width @@ -224,14 +219,9 @@ void nvim_win_set_width(Window window, Integer width, Error *err) return; } - if (width > INT_MAX || width < INT_MIN) { - api_set_error(err, kErrorTypeValidation, "Width value outside range"); - return; - } - - try_start(); - win_setwidth_win((int)width, win); - try_end(err); + TRY_WRAP(err, { + win_setwidth_win((int)width, win); + }); } /// Gets a window-scoped (w:) variable @@ -436,15 +426,15 @@ Object nvim_win_call(Window window, LuaRef fun, Error *err) } tabpage_T *tabpage = win_find_tabpage(win); - try_start(); Object res = OBJECT_INIT; - win_execute_T win_execute_args; - if (win_execute_before(&win_execute_args, win, tabpage)) { - Array args = ARRAY_DICT_INIT; - res = nlua_call_ref(fun, NULL, args, kRetLuaref, NULL, err); - } - win_execute_after(&win_execute_args); - try_end(err); + TRY_WRAP(err, { + win_execute_T win_execute_args; + if (win_execute_before(&win_execute_args, win, tabpage)) { + Array args = ARRAY_DICT_INIT; + res = nlua_call_ref(fun, NULL, args, kRetLuaref, NULL, err); + } + win_execute_after(&win_execute_args); + }); return res; } -- cgit From 6bf2a6fc5bb395b67c88cb26d332f882a106c7ab Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Tue, 17 Dec 2024 13:12:22 +0100 Subject: refactor(api): always use TRY_WRAP #31600 Problem: Two separate try/end wrappers, that only marginally differ by restoring a few variables. Wrappers that don't restore previous state are dangerous to use in "api-fast" functions. Solution: Remove wrappers that don't restore the previous state. Always use TRY_WRAP. --- src/nvim/api/buffer.c | 346 ++++++++++++++++++++--------------------- src/nvim/api/private/helpers.c | 79 ++++------ src/nvim/api/private/helpers.h | 14 +- src/nvim/api/tabpage.c | 8 +- src/nvim/api/vim.c | 116 +++++++------- src/nvim/api/vimscript.c | 30 ++-- src/nvim/api/window.c | 30 ++-- 7 files changed, 291 insertions(+), 332 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 9480292d9a..2b6aa8b371 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -360,93 +360,91 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ memchrsub(lines[i], NUL, NL, l.size); } - try_start(); - - if (!MODIFIABLE(buf)) { - api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'"); - goto end; - } - - if (u_save_buf(buf, (linenr_T)(start - 1), (linenr_T)end) == FAIL) { - api_set_error(err, kErrorTypeException, "Failed to save undo information"); - goto end; - } - - bcount_t deleted_bytes = get_region_bytecount(buf, (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 - // repeatedly deleting line "start". - size_t to_delete = (new_len < old_len) ? old_len - new_len : 0; - for (size_t i = 0; i < to_delete; i++) { - if (ml_delete_buf(buf, (linenr_T)start, false) == FAIL) { - api_set_error(err, kErrorTypeException, "Failed to delete line"); + TRY_WRAP(err, { + if (!MODIFIABLE(buf)) { + api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'"); goto end; } - } - if (to_delete > 0) { - extra -= (ptrdiff_t)to_delete; - } + if (u_save_buf(buf, (linenr_T)(start - 1), (linenr_T)end) == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to save undo information"); + goto end; + } - // For as long as possible, replace the existing old_len with the - // new old_len. This is a more efficient operation, as it requires - // less memory allocation and freeing. - size_t to_replace = old_len < new_len ? old_len : new_len; - bcount_t inserted_bytes = 0; - for (size_t i = 0; i < to_replace; i++) { - int64_t lnum = start + (int64_t)i; + bcount_t deleted_bytes = get_region_bytecount(buf, (linenr_T)start, (linenr_T)end, 0, 0); - VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", { - goto end; - }); + // 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 + // repeatedly deleting line "start". + size_t to_delete = (new_len < old_len) ? old_len - new_len : 0; + for (size_t i = 0; i < to_delete; i++) { + if (ml_delete_buf(buf, (linenr_T)start, false) == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to delete line"); + goto end; + } + } - if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false, true) == FAIL) { - api_set_error(err, kErrorTypeException, "Failed to replace line"); - goto end; + if (to_delete > 0) { + extra -= (ptrdiff_t)to_delete; } - inserted_bytes += (bcount_t)strlen(lines[i]) + 1; - } + // For as long as possible, replace the existing old_len with the + // new old_len. This is a more efficient operation, as it requires + // less memory allocation and freeing. + size_t to_replace = old_len < new_len ? old_len : new_len; + bcount_t inserted_bytes = 0; + for (size_t i = 0; i < to_replace; i++) { + int64_t lnum = start + (int64_t)i; + + VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", { + goto end; + }); + + if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false, true) == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to replace line"); + goto end; + } - // Now we may need to insert the remaining new old_len - for (size_t i = to_replace; i < new_len; i++) { - int64_t lnum = start + (int64_t)i - 1; + inserted_bytes += (bcount_t)strlen(lines[i]) + 1; + } - VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", { - goto end; - }); + // Now we may need to insert the remaining new old_len + for (size_t i = to_replace; i < new_len; i++) { + int64_t lnum = start + (int64_t)i - 1; - if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) { - api_set_error(err, kErrorTypeException, "Failed to insert line"); - goto end; - } + VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", { + goto end; + }); - inserted_bytes += (bcount_t)strlen(lines[i]) + 1; + if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to insert line"); + goto end; + } - extra++; - } + inserted_bytes += (bcount_t)strlen(lines[i]) + 1; - // Adjust marks. Invalidate any which lie in the - // changed range, and move any in the remainder of the buffer. - linenr_T adjust = end > start ? MAXLNUM : 0; - mark_adjust_buf(buf, (linenr_T)start, (linenr_T)(end - 1), adjust, (linenr_T)extra, - true, true, kExtmarkNOOP); + extra++; + } - extmark_splice(buf, (int)start - 1, 0, (int)(end - start), 0, - deleted_bytes, (int)new_len, 0, inserted_bytes, - kExtmarkUndo); + // Adjust marks. Invalidate any which lie in the + // changed range, and move any in the remainder of the buffer. + linenr_T adjust = end > start ? MAXLNUM : 0; + mark_adjust_buf(buf, (linenr_T)start, (linenr_T)(end - 1), adjust, (linenr_T)extra, + true, true, kExtmarkNOOP); - changed_lines(buf, (linenr_T)start, 0, (linenr_T)end, (linenr_T)extra, true); + extmark_splice(buf, (int)start - 1, 0, (int)(end - start), 0, + deleted_bytes, (int)new_len, 0, inserted_bytes, + kExtmarkUndo); - FOR_ALL_TAB_WINDOWS(tp, win) { - if (win->w_buffer == buf) { - fix_cursor(win, (linenr_T)start, (linenr_T)end, (linenr_T)extra); - } - } + changed_lines(buf, (linenr_T)start, 0, (linenr_T)end, (linenr_T)extra, true); -end: - try_end(err); + FOR_ALL_TAB_WINDOWS(tp, win) { + if (win->w_buffer == buf) { + fix_cursor(win, (linenr_T)start, (linenr_T)end, (linenr_T)extra); + } + } + end:; + }); } /// Sets (replaces) a range in the buffer @@ -593,101 +591,99 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In new_byte += (bcount_t)(last_item.size) + 1; } - try_start(); - - if (!MODIFIABLE(buf)) { - api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'"); - goto end; - } - - // Small note about undo states: unlike set_lines, we want to save the - // undo state of one past the end_row, since end_row is inclusive. - if (u_save_buf(buf, (linenr_T)start_row - 1, (linenr_T)end_row + 1) == FAIL) { - api_set_error(err, kErrorTypeException, "Failed to save undo information"); - goto end; - } - - ptrdiff_t extra = 0; // lines added to text, can be negative - 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 - // repeatedly deleting line "start". - size_t to_delete = (new_len < old_len) ? old_len - new_len : 0; - for (size_t i = 0; i < to_delete; i++) { - if (ml_delete_buf(buf, (linenr_T)start_row, false) == FAIL) { - api_set_error(err, kErrorTypeException, "Failed to delete line"); + TRY_WRAP(err, { + if (!MODIFIABLE(buf)) { + api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'"); goto end; } - } - if (to_delete > 0) { - extra -= (ptrdiff_t)to_delete; - } - - // For as long as possible, replace the existing old_len with the - // new old_len. This is a more efficient operation, as it requires - // less memory allocation and freeing. - size_t to_replace = old_len < new_len ? old_len : new_len; - for (size_t i = 0; i < to_replace; i++) { - int64_t lnum = start_row + (int64_t)i; - - VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", { + // Small note about undo states: unlike set_lines, we want to save the + // undo state of one past the end_row, since end_row is inclusive. + if (u_save_buf(buf, (linenr_T)start_row - 1, (linenr_T)end_row + 1) == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to save undo information"); goto end; - }); + } - if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false, true) == FAIL) { - api_set_error(err, kErrorTypeException, "Failed to replace line"); - goto end; + ptrdiff_t extra = 0; // lines added to text, can be negative + 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 + // repeatedly deleting line "start". + size_t to_delete = (new_len < old_len) ? old_len - new_len : 0; + for (size_t i = 0; i < to_delete; i++) { + if (ml_delete_buf(buf, (linenr_T)start_row, false) == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to delete line"); + goto end; + } } - } - // Now we may need to insert the remaining new old_len - for (size_t i = to_replace; i < new_len; i++) { - int64_t lnum = start_row + (int64_t)i - 1; + if (to_delete > 0) { + extra -= (ptrdiff_t)to_delete; + } - VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", { - goto end; - }); + // For as long as possible, replace the existing old_len with the + // new old_len. This is a more efficient operation, as it requires + // less memory allocation and freeing. + size_t to_replace = old_len < new_len ? old_len : new_len; + for (size_t i = 0; i < to_replace; i++) { + int64_t lnum = start_row + (int64_t)i; - if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) { - api_set_error(err, kErrorTypeException, "Failed to insert line"); - goto end; + VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", { + goto end; + }); + + if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false, true) == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to replace line"); + goto end; + } } - extra++; - } + // Now we may need to insert the remaining new old_len + for (size_t i = to_replace; i < new_len; i++) { + int64_t lnum = start_row + (int64_t)i - 1; - colnr_T col_extent = (colnr_T)(end_col - - ((end_row == start_row) ? start_col : 0)); - - // Adjust marks. Invalidate any which lie in the - // changed range, and move any in the remainder of the buffer. - // Do not adjust any cursors. need to use column-aware logic (below) - linenr_T adjust = end_row >= start_row ? MAXLNUM : 0; - mark_adjust_buf(buf, (linenr_T)start_row, (linenr_T)end_row, adjust, (linenr_T)extra, - true, true, kExtmarkNOOP); - - 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); - - changed_lines(buf, (linenr_T)start_row, 0, (linenr_T)end_row + 1, (linenr_T)extra, true); - - FOR_ALL_TAB_WINDOWS(tp, win) { - if (win->w_buffer == buf) { - if (win->w_cursor.lnum >= start_row && win->w_cursor.lnum <= end_row) { - fix_cursor_cols(win, (linenr_T)start_row, (colnr_T)start_col, (linenr_T)end_row, - (colnr_T)end_col, (linenr_T)new_len, (colnr_T)last_item.size); - } else { - fix_cursor(win, (linenr_T)start_row, (linenr_T)end_row, (linenr_T)extra); + VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", { + goto end; + }); + + if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to insert line"); + goto end; } + + extra++; } - } -end: - try_end(err); + colnr_T col_extent = (colnr_T)(end_col + - ((end_row == start_row) ? start_col : 0)); + + // Adjust marks. Invalidate any which lie in the + // changed range, and move any in the remainder of the buffer. + // Do not adjust any cursors. need to use column-aware logic (below) + linenr_T adjust = end_row >= start_row ? MAXLNUM : 0; + mark_adjust_buf(buf, (linenr_T)start_row, (linenr_T)end_row, adjust, (linenr_T)extra, + true, true, kExtmarkNOOP); + + 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); + + changed_lines(buf, (linenr_T)start_row, 0, (linenr_T)end_row + 1, (linenr_T)extra, true); + + FOR_ALL_TAB_WINDOWS(tp, win) { + if (win->w_buffer == buf) { + if (win->w_cursor.lnum >= start_row && win->w_cursor.lnum <= end_row) { + fix_cursor_cols(win, (linenr_T)start_row, (colnr_T)start_col, (linenr_T)end_row, + (colnr_T)end_col, (linenr_T)new_len, (colnr_T)last_item.size); + } else { + fix_cursor(win, (linenr_T)start_row, (linenr_T)end_row, (linenr_T)extra); + } + } + } + end:; + }); } /// Gets a range from the buffer. @@ -965,26 +961,27 @@ void nvim_buf_set_name(Buffer buffer, String name, Error *err) return; } - try_start(); - - const bool is_curbuf = buf == curbuf; - const int save_acd = p_acd; - if (!is_curbuf) { - // Temporarily disable 'autochdir' when setting file name for another buffer. - p_acd = false; - } + int ren_ret = OK; + TRY_WRAP(err, { + const bool is_curbuf = buf == curbuf; + const int save_acd = p_acd; + if (!is_curbuf) { + // Temporarily disable 'autochdir' when setting file name for another buffer. + p_acd = false; + } - // Using aucmd_*: autocommands will be executed by rename_buffer - aco_save_T aco; - aucmd_prepbuf(&aco, buf); - int ren_ret = rename_buffer(name.data); - aucmd_restbuf(&aco); + // Using aucmd_*: autocommands will be executed by rename_buffer + aco_save_T aco; + aucmd_prepbuf(&aco, buf); + ren_ret = rename_buffer(name.data); + aucmd_restbuf(&aco); - if (!is_curbuf) { - p_acd = save_acd; - } + if (!is_curbuf) { + p_acd = save_acd; + } + }); - if (try_end(err)) { + if (ERROR_SET(err)) { return; } @@ -1204,15 +1201,18 @@ Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err) if (!buf) { return NIL; } - try_start(); - aco_save_T aco; - aucmd_prepbuf(&aco, buf); - Array args = ARRAY_DICT_INIT; - Object res = nlua_call_ref(fun, NULL, args, kRetLuaref, NULL, err); + Object res = OBJECT_INIT; + TRY_WRAP(err, { + aco_save_T aco; + aucmd_prepbuf(&aco, buf); + + Array args = ARRAY_DICT_INIT; + res = nlua_call_ref(fun, NULL, args, kRetLuaref, NULL, err); + + aucmd_restbuf(&aco); + }); - aucmd_restbuf(&aco); - try_end(err); return res; } diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 5bf66a092f..4389ae3b35 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -42,8 +42,10 @@ /// Start block that may cause Vimscript exceptions while evaluating another code /// -/// Used when caller is supposed to be operating when other Vimscript code is being -/// processed and that “other Vimscript code” must not be affected. +/// Used just in case caller is supposed to be operating when other Vimscript code +/// is being processed and that “other Vimscript code” must not be affected. +/// +/// @warning Avoid calling directly; use TRY_WRAP instead. /// /// @param[out] tstate Location where try state should be saved. void try_enter(TryState *const tstate) @@ -55,75 +57,33 @@ void try_enter(TryState *const tstate) .current_exception = current_exception, .msg_list = (const msglist_T *const *)msg_list, .private_msg_list = NULL, - .trylevel = trylevel, .got_int = got_int, .did_throw = did_throw, .need_rethrow = need_rethrow, .did_emsg = did_emsg, }; + // `msg_list` controls the collection of abort-causing non-exception errors, + // which would otherwise be ignored. This pattern is from do_cmdline(). msg_list = &tstate->private_msg_list; current_exception = NULL; - trylevel = 1; got_int = false; did_throw = false; need_rethrow = false; did_emsg = false; -} - -/// End try block, set the error message if any and restore previous state -/// -/// @warning Return is consistent with most functions (false on error), not with -/// try_end (true on error). -/// -/// @param[in] tstate Previous state to restore. -/// @param[out] err Location where error should be saved. -/// -/// @return false if error occurred, true otherwise. -bool try_leave(const TryState *const tstate, Error *const err) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT -{ - const bool ret = !try_end(err); - assert(trylevel == 0); - assert(!need_rethrow); - assert(!got_int); - assert(!did_throw); - assert(!did_emsg); - assert(msg_list == &tstate->private_msg_list); - assert(*msg_list == NULL); - assert(current_exception == NULL); - msg_list = (msglist_T **)tstate->msg_list; - current_exception = tstate->current_exception; - trylevel = tstate->trylevel; - got_int = tstate->got_int; - did_throw = tstate->did_throw; - need_rethrow = tstate->need_rethrow; - did_emsg = tstate->did_emsg; - return ret; -} - -/// Starts a block that may cause Vimscript exceptions; must be mirrored by `try_end()` call. -/// -/// Note: use `TRY_WRAP` instead (except in `FUNC_API_FAST` functions such as nvim_get_runtime_file). -/// -/// To be used as a replacement of `:try … catch … endtry` in C code, in cases -/// when error flag could not already be set. If there may be pending error -/// state at the time try_start() is executed which needs to be preserved, -/// try_enter()/try_leave() pair should be used instead. -void try_start(void) -{ trylevel++; } -/// Ends a `try_start` block; sets error message if any and returns true if an error occurred. +/// Ends a `try_enter` block; sets error message if any. /// -/// Note: use `TRY_WRAP` instead (except in `FUNC_API_FAST` functions such as nvim_get_runtime_file). +/// @warning Avoid calling directly; use TRY_WRAP instead. /// -/// @param err Pointer to the stack-allocated error object -/// @return true if an error occurred -bool try_end(Error *err) +/// @param[out] err Pointer to the stack-allocated error object +void try_leave(const TryState *const tstate, Error *const err) + FUNC_ATTR_NONNULL_ALL { // Note: all globals manipulated here should be saved/restored in // try_enter/try_leave. + assert(trylevel > 0); trylevel--; // Set by emsg(), affects aborting(). See also enter_cleanup(). @@ -166,7 +126,20 @@ bool try_end(Error *err) discard_current_exception(); } - return ERROR_SET(err); + assert(msg_list == &tstate->private_msg_list); + assert(*msg_list == NULL); + assert(current_exception == NULL); + assert(!got_int); + assert(!did_throw); + assert(!need_rethrow); + assert(!did_emsg); + // Restore the exception context. + msg_list = (msglist_T **)tstate->msg_list; + current_exception = tstate->current_exception; + got_int = tstate->got_int; + did_throw = tstate->did_throw; + need_rethrow = tstate->need_rethrow; + did_emsg = tstate->did_emsg; } /// Recursively expands a vimscript value in a dict diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index d06f5c9c65..03ff811449 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -147,27 +147,19 @@ typedef struct { except_T *current_exception; msglist_T *private_msg_list; const msglist_T *const *msg_list; - int trylevel; int got_int; bool did_throw; int need_rethrow; int did_emsg; } TryState; -// `msg_list` controls the collection of abort-causing non-exception errors, -// which would otherwise be ignored. This pattern is from do_cmdline(). -// // TODO(bfredl): prepare error-handling at "top level" (nv_event). #define TRY_WRAP(err, code) \ do { \ - msglist_T **saved_msg_list = msg_list; \ - msglist_T *private_msg_list; \ - msg_list = &private_msg_list; \ - private_msg_list = NULL; \ - try_start(); \ + TryState tstate; \ + try_enter(&tstate); \ code; \ - try_end(err); \ - msg_list = saved_msg_list; /* Restore the exception context. */ \ + try_leave(&tstate, err); \ } while (0) // Execute code with cursor position saved and restored and textlock active. diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c index 56a3f1cf23..b4d519dc98 100644 --- a/src/nvim/api/tabpage.c +++ b/src/nvim/api/tabpage.c @@ -146,11 +146,9 @@ void nvim_tabpage_set_win(Tabpage tabpage, Window win, Error *err) } if (tp == curtab) { - try_start(); - win_goto(wp); - if (!try_end(err) && curwin != wp) { - api_set_error(err, kErrorTypeException, "Failed to switch to window %d", win); - } + TRY_WRAP(err, { + win_goto(wp); + }); } else if (tp->tp_curwin != wp) { tp->tp_prevwin = tp->tp_curwin; tp->tp_curwin = wp; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index d82f90d1dd..fce7a86245 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -594,12 +594,10 @@ ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Arena *arena, Er kvi_init(cookie.rv); int flags = DIP_DIRFILE | (all ? DIP_ALL : 0); - TryState tstate; - // XXX: intentionally not using `TRY_WRAP`, to avoid `did_emsg=false` in `try_end`. - try_enter(&tstate); - do_in_runtimepath((name.size ? name.data : ""), flags, find_runtime_cb, &cookie); - vim_ignored = try_leave(&tstate, err); + TRY_WRAP(err, { + do_in_runtimepath((name.size ? name.data : ""), flags, find_runtime_cb, &cookie); + }); return arena_take_arraybuilder(arena, &cookie.rv); } @@ -952,68 +950,70 @@ void nvim_set_current_win(Window window, Error *err) Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err) FUNC_API_SINCE(6) { - try_start(); - // Block autocommands for now so they don't mess with the buffer before we - // finish configuring it. - block_autocmds(); - - buf_T *buf = buflist_new(NULL, NULL, 0, - BLN_NOOPT | BLN_NEW | (listed ? BLN_LISTED : 0)); - if (buf == NULL) { - unblock_autocmds(); - goto fail; - } + Buffer ret = 0; - // Open the memline for the buffer. This will avoid spurious autocmds when - // a later nvim_buf_set_lines call would have needed to "open" the buffer. - if (ml_open(buf) == FAIL) { - unblock_autocmds(); - goto fail; - } - - // Set last_changedtick to avoid triggering a TextChanged autocommand right - // after it was added. - buf->b_last_changedtick = buf_get_changedtick(buf); - buf->b_last_changedtick_i = buf_get_changedtick(buf); - buf->b_last_changedtick_pum = buf_get_changedtick(buf); + TRY_WRAP(err, { + // Block autocommands for now so they don't mess with the buffer before we + // finish configuring it. + block_autocmds(); + + buf_T *buf = buflist_new(NULL, NULL, 0, + BLN_NOOPT | BLN_NEW | (listed ? BLN_LISTED : 0)); + if (buf == NULL) { + unblock_autocmds(); + goto fail; + } - // Only strictly needed for scratch, but could just as well be consistent - // and do this now. Buffer is created NOW, not when it later first happens - // to reach a window or aucmd_prepbuf() .. - buf_copy_options(buf, BCO_ENTER | BCO_NOHELP); + // Open the memline for the buffer. This will avoid spurious autocmds when + // a later nvim_buf_set_lines call would have needed to "open" the buffer. + if (ml_open(buf) == FAIL) { + unblock_autocmds(); + goto fail; + } - if (scratch) { - set_option_direct_for(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL, 0, - kOptScopeBuf, buf); - set_option_direct_for(kOptBuftype, STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL, 0, - kOptScopeBuf, buf); - assert(buf->b_ml.ml_mfp->mf_fd < 0); // ml_open() should not have opened swapfile already - buf->b_p_swf = false; - buf->b_p_ml = false; - } + // Set last_changedtick to avoid triggering a TextChanged autocommand right + // after it was added. + buf->b_last_changedtick = buf_get_changedtick(buf); + buf->b_last_changedtick_i = buf_get_changedtick(buf); + buf->b_last_changedtick_pum = buf_get_changedtick(buf); + + // Only strictly needed for scratch, but could just as well be consistent + // and do this now. Buffer is created NOW, not when it later first happens + // to reach a window or aucmd_prepbuf() .. + buf_copy_options(buf, BCO_ENTER | BCO_NOHELP); + + if (scratch) { + set_option_direct_for(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL, 0, + kOptScopeBuf, buf); + set_option_direct_for(kOptBuftype, STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL, 0, + kOptScopeBuf, buf); + assert(buf->b_ml.ml_mfp->mf_fd < 0); // ml_open() should not have opened swapfile already + buf->b_p_swf = false; + buf->b_p_ml = false; + } - unblock_autocmds(); + unblock_autocmds(); - bufref_T bufref; - set_bufref(&bufref, buf); - if (apply_autocmds(EVENT_BUFNEW, NULL, NULL, false, buf) - && !bufref_valid(&bufref)) { - goto fail; - } - if (listed - && apply_autocmds(EVENT_BUFADD, NULL, NULL, false, buf) - && !bufref_valid(&bufref)) { - goto fail; - } + bufref_T bufref; + set_bufref(&bufref, buf); + if (apply_autocmds(EVENT_BUFNEW, NULL, NULL, false, buf) + && !bufref_valid(&bufref)) { + goto fail; + } + if (listed + && apply_autocmds(EVENT_BUFADD, NULL, NULL, false, buf) + && !bufref_valid(&bufref)) { + goto fail; + } - try_end(err); - return buf->b_fnum; + ret = buf->b_fnum; + fail:; + }); -fail: - if (!try_end(err)) { + if (ret == 0 && !ERROR_SET(err)) { api_set_error(err, kErrorTypeException, "Failed to create buffer"); } - return 0; + return ret; } /// Open a terminal instance in a buffer diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 0ff2b037ce..67db615b20 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -78,24 +78,24 @@ String exec_impl(uint64_t channel_id, String src, Dict(exec_opts) *opts, Error * capture_ga = &capture_local; } - try_start(); - if (opts->output) { - msg_silent++; - msg_col = 0; // prevent leading spaces - } + TRY_WRAP(err, { + if (opts->output) { + msg_silent++; + msg_col = 0; // prevent leading spaces + } - const sctx_T save_current_sctx = api_set_sctx(channel_id); + const sctx_T save_current_sctx = api_set_sctx(channel_id); - do_source_str(src.data, "nvim_exec2()"); - if (opts->output) { - capture_ga = save_capture_ga; - msg_silent = save_msg_silent; - // Put msg_col back where it was, since nothing should have been written. - msg_col = save_msg_col; - } + do_source_str(src.data, "nvim_exec2()"); + if (opts->output) { + capture_ga = save_capture_ga; + msg_silent = save_msg_silent; + // Put msg_col back where it was, since nothing should have been written. + msg_col = save_msg_col; + } - current_sctx = save_current_sctx; - try_end(err); + current_sctx = save_current_sctx; + }); if (ERROR_SET(err)) { goto theend; diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index f5e8d8f086..387dad899e 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -368,19 +368,16 @@ void nvim_win_hide(Window window, Error *err) } tabpage_T *tabpage = win_find_tabpage(win); - TryState tstate; - try_enter(&tstate); - - // Never close the autocommand window. - if (is_aucmd_win(win)) { - emsg(_(e_autocmd_close)); - } else if (tabpage == curtab) { - win_close(win, false, false); - } else { - win_close_othertab(win, false, tabpage); - } - - vim_ignored = try_leave(&tstate, err); + TRY_WRAP(err, { + // Never close the autocommand window. + if (is_aucmd_win(win)) { + emsg(_(e_autocmd_close)); + } else if (tabpage == curtab) { + win_close(win, false, false); + } else { + win_close_othertab(win, false, tabpage); + } + }); } /// Closes the window (like |:close| with a |window-ID|). @@ -400,10 +397,9 @@ void nvim_win_close(Window window, Boolean force, Error *err) } tabpage_T *tabpage = win_find_tabpage(win); - TryState tstate; - try_enter(&tstate); - ex_win_close(force, win, tabpage == curtab ? NULL : tabpage); - vim_ignored = try_leave(&tstate, err); + TRY_WRAP(err, { + ex_win_close(force, win, tabpage == curtab ? NULL : tabpage); + }); } /// Calls a function with window as temporary current window. -- cgit From f9eb68f340f9c0dbf3b6b2da3ddbab2d5be21b61 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 18 Dec 2024 06:05:37 -0800 Subject: fix(coverity): error handling CHECKED_RETURN #31618 CID 516406: Error handling issues (CHECKED_RETURN) /src/nvim/api/vimscript.c: 284 in nvim_call_dict_function() 278 Object rv = OBJECT_INIT; 279 280 typval_T rettv; 281 bool mustfree = false; 282 switch (dict.type) { 283 case kObjectTypeString: >>> CID 516406: Error handling issues (CHECKED_RETURN) >>> Calling "eval0" without checking return value (as is done elsewhere 10 out of 12 times). 284 TRY_WRAP(err, { 285 eval0(dict.data.string.data, &rettv, NULL, &EVALARG_EVALUATE); 286 clear_evalarg(&EVALARG_EVALUATE, NULL); 287 }); 288 if (ERROR_SET(err)) { 289 return rv; --- src/nvim/api/vimscript.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 67db615b20..e2aac07258 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -229,10 +229,9 @@ static Object _call_function(String fn, Array args, dict_T *self, Arena *arena, funcexe.fe_selfdict = self; TRY_WRAP(err, { - // call_func() retval is deceptive, ignore it. Instead we set `msg_list` - // (see above) to capture abort-causing non-exception errors. - call_func(fn.data, (int)fn.size, &rettv, (int)args.size, - vim_args, &funcexe); + // call_func() retval is deceptive, ignore it. Instead TRY_WRAP sets `msg_list` to capture + // abort-causing non-exception errors. + (void)call_func(fn.data, (int)fn.size, &rettv, (int)args.size, vim_args, &funcexe); }); if (!ERROR_SET(err)) { @@ -280,18 +279,23 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Arena *arena, typval_T rettv; bool mustfree = false; switch (dict.type) { - case kObjectTypeString: + case kObjectTypeString: { + int eval_ret; TRY_WRAP(err, { - eval0(dict.data.string.data, &rettv, NULL, &EVALARG_EVALUATE); - clear_evalarg(&EVALARG_EVALUATE, NULL); - }); + eval_ret = eval0(dict.data.string.data, &rettv, NULL, &EVALARG_EVALUATE); + clear_evalarg(&EVALARG_EVALUATE, NULL); + }); if (ERROR_SET(err)) { return rv; } + if (eval_ret != OK) { + abort(); // Should not happen. + } // Evaluation of the string arg created a new dict or increased the // refcount of a dict. Not necessary for a RPC dict. mustfree = true; break; + } case kObjectTypeDict: object_to_vim(dict, &rettv, err); break; -- cgit From a5a4149e9754a96c063a357c18397aa7906edf53 Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Thu, 19 Dec 2024 16:04:33 +0100 Subject: docs(api): specify when decor provider on_buf is called #31634 --- 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 c94b8df9ea..55f537e9c9 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -1012,8 +1012,8 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start, /// ``` /// ["start", tick] /// ``` -/// - on_buf: called for each buffer being redrawn (before -/// window callbacks) +/// - on_buf: called for each buffer being redrawn (once per edit, +/// before window callbacks) /// ``` /// ["buf", bufnr, tick] /// ``` -- cgit From 8ef41f590224dfeea2e51d9fec150e363fd72ee0 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 19 Dec 2024 07:07:04 -0800 Subject: feat(jobs): jobstart(…,{term=true}), deprecate termopen() #31343 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: `termopen` has long been a superficial wrapper around `jobstart`, and has no real purpose. Also, `vim.system` and `nvim_open_term` presumably will replace all features of `jobstart` and `termopen`, so centralizing the logic will help with that. Solution: - Introduce `eval/deprecated.c`, where all deprecated eval funcs will live. - Introduce "term" flag of `jobstart`. - Deprecate `termopen`. --- 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 2b6aa8b371..6c40eb9b20 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1181,12 +1181,12 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Arena *arena, /// This temporarily switches current buffer to "buffer". /// If the current window already shows "buffer", the window is not switched. /// If a window inside the current tabpage (including a float) already shows the -/// buffer, then one of these windows will be set as current window temporarily. +/// buffer, then one of those windows will be set as current window temporarily. /// Otherwise a temporary scratch window (called the "autocmd window" for /// historical reasons) will be used. /// /// This is useful e.g. to call Vimscript functions that only work with the -/// current buffer/window currently, like |termopen()|. +/// current buffer/window currently, like `jobstart(…, {'term': v:true})`. /// /// @param buffer Buffer handle, or 0 for current buffer /// @param fun Function to call inside the buffer (currently Lua callable -- cgit From 394f69a25dc32c5b101ba2d34ac6376b0c75b2a2 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Fri, 20 Dec 2024 21:11:38 +0100 Subject: feat(ui): additional arguments for cmdline_show/hide events Problem: Unable to tell what highlight the prompt part of a cmdline_show event should have, and whether cmdline_hide was emitted after aborting. Solution: Add additional arguments hl_id to cmdline_show, and abort to cmdline_hide. --- src/nvim/api/ui_events.in.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 0ed208fc1a..603f9b2005 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -136,13 +136,13 @@ void tabline_update(Tabpage current, Array tabs, Buffer current_buffer, Array bu FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void cmdline_show(Array content, Integer pos, String firstc, String prompt, Integer indent, - Integer level) + Integer level, Integer hl_id) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void cmdline_pos(Integer pos, Integer level) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void cmdline_special_char(String c, Boolean shift, Integer level) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; -void cmdline_hide(Integer level) +void cmdline_hide(Integer level, Boolean abort) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void cmdline_block_show(Array lines) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; -- cgit From a10636fbe7bb4dba45c42c64548e5e32fe8f8d12 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Sun, 22 Dec 2024 13:21:57 +0100 Subject: feat(ui): specify whether msg_show event is added to history Pass along whether message in msg_show event is added to the internal :messages history. --- 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 603f9b2005..74718e7ac5 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -158,7 +158,7 @@ void wildmenu_select(Integer selected) void wildmenu_hide(void) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; -void msg_show(String kind, Array content, Boolean replace_last) +void msg_show(String kind, Array content, Boolean replace_last, Boolean history) FUNC_API_SINCE(6) FUNC_API_FAST FUNC_API_REMOTE_ONLY; void msg_clear(void) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; -- cgit From 2a7d0ed6145bf3f8b139c2694563f460f829813a Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 23 Dec 2024 05:43:52 -0800 Subject: refactor: iwyu #31637 Result of `make iwyu` (after some "fixups"). --- src/nvim/api/autocmd.c | 2 +- src/nvim/api/buffer.c | 1 - src/nvim/api/command.c | 1 + src/nvim/api/deprecated.c | 4 +--- src/nvim/api/extmark.c | 1 + src/nvim/api/extmark.h | 1 + src/nvim/api/options.c | 1 + src/nvim/api/private/converter.c | 3 ++- src/nvim/api/private/helpers.c | 5 +---- src/nvim/api/private/helpers.h | 2 +- src/nvim/api/tabpage.c | 5 +++-- src/nvim/api/ui.c | 2 +- src/nvim/api/ui.h | 1 + src/nvim/api/vim.c | 3 +-- src/nvim/api/vimscript.c | 2 ++ src/nvim/api/win_config.c | 7 ++----- src/nvim/api/window.c | 3 +-- 17 files changed, 21 insertions(+), 23 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 45e2de69e0..ae349fdb95 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -2,7 +2,6 @@ #include #include #include -#include #include #include @@ -23,6 +22,7 @@ #include "nvim/globals.h" #include "nvim/lua/executor.h" #include "nvim/memory.h" +#include "nvim/memory_defs.h" #include "nvim/strings.h" #include "nvim/types_defs.h" #include "nvim/vim_defs.h" diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 6c40eb9b20..aa349790b3 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -23,7 +23,6 @@ #include "nvim/buffer_updates.h" #include "nvim/change.h" #include "nvim/cursor.h" -#include "nvim/drawscreen.h" #include "nvim/ex_cmds.h" #include "nvim/extmark.h" #include "nvim/extmark_defs.h" diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index 709e18af73..7e79133ed4 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -26,6 +26,7 @@ #include "nvim/macros_defs.h" #include "nvim/mbyte.h" #include "nvim/memory.h" +#include "nvim/memory_defs.h" #include "nvim/ops.h" #include "nvim/pos_defs.h" #include "nvim/regexp.h" diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index 51ed6eaa41..d5eddb74de 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -7,6 +7,7 @@ #include "nvim/api/extmark.h" #include "nvim/api/keysets_defs.h" #include "nvim/api/private/defs.h" +#include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" #include "nvim/api/private/validate.h" #include "nvim/api/vimscript.h" @@ -20,9 +21,6 @@ #include "nvim/lua/executor.h" #include "nvim/memory.h" #include "nvim/memory_defs.h" -#include "nvim/msgpack_rpc/channel.h" -#include "nvim/msgpack_rpc/channel_defs.h" -#include "nvim/msgpack_rpc/unpacker.h" #include "nvim/option.h" #include "nvim/option_defs.h" #include "nvim/pos_defs.h" diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 55f537e9c9..a237e7531a 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -27,6 +27,7 @@ #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" +#include "nvim/memory_defs.h" #include "nvim/move.h" #include "nvim/pos_defs.h" #include "nvim/sign.h" diff --git a/src/nvim/api/extmark.h b/src/nvim/api/extmark.h index af2d51c95c..0b4d84110b 100644 --- a/src/nvim/api/extmark.h +++ b/src/nvim/api/extmark.h @@ -1,5 +1,6 @@ #pragma once +#include #include // IWYU pragma: keep #include "nvim/api/keysets_defs.h" // IWYU pragma: keep diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index bfd63ca489..64f8a35d54 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -14,6 +14,7 @@ #include "nvim/buffer_defs.h" #include "nvim/globals.h" #include "nvim/memory.h" +#include "nvim/memory_defs.h" #include "nvim/option.h" #include "nvim/types_defs.h" #include "nvim/vim_defs.h" diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c index 59e7373f68..5f9d20ee73 100644 --- a/src/nvim/api/private/converter.c +++ b/src/nvim/api/private/converter.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -7,7 +8,6 @@ #include "nvim/api/private/converter.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/ascii_defs.h" #include "nvim/assert_defs.h" #include "nvim/eval/decode.h" #include "nvim/eval/typval.h" @@ -15,6 +15,7 @@ #include "nvim/eval/userfunc.h" #include "nvim/lua/executor.h" #include "nvim/memory.h" +#include "nvim/memory_defs.h" #include "nvim/types_defs.h" #include "nvim/vim_defs.h" diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 4389ae3b35..78aa7c00f7 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -31,13 +31,10 @@ #include "nvim/msgpack_rpc/unpacker.h" #include "nvim/pos_defs.h" #include "nvim/types_defs.h" -#include "nvim/ui.h" -#include "nvim/ui_defs.h" -#include "nvim/version.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/api_metadata.generated.h" -# include "api/private/helpers.c.generated.h" +# include "api/private/helpers.c.generated.h" // IWYU pragma: keep #endif /// Start block that may cause Vimscript exceptions while evaluating another code diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 03ff811449..d581c6bc10 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include // IWYU pragma: keep #include "klib/kvec.h" #include "nvim/api/private/defs.h" // IWYU pragma: keep diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c index b4d519dc98..dce47cd99f 100644 --- a/src/nvim/api/tabpage.c +++ b/src/nvim/api/tabpage.c @@ -7,11 +7,12 @@ #include "nvim/api/vim.h" #include "nvim/buffer_defs.h" #include "nvim/globals.h" -#include "nvim/memory.h" +#include "nvim/memory_defs.h" +#include "nvim/types_defs.h" #include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "api/tabpage.c.generated.h" +# include "api/tabpage.c.generated.h" // IWYU pragma: keep #endif /// Gets the windows in a tabpage diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index b09a9ed253..db8a5d93d0 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -23,7 +23,6 @@ #include "nvim/event/wstream.h" #include "nvim/globals.h" #include "nvim/grid.h" -#include "nvim/grid_defs.h" #include "nvim/highlight.h" #include "nvim/macros_defs.h" #include "nvim/main.h" @@ -34,6 +33,7 @@ #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/channel_defs.h" #include "nvim/msgpack_rpc/packer.h" +#include "nvim/msgpack_rpc/packer_defs.h" #include "nvim/option.h" #include "nvim/types_defs.h" #include "nvim/ui.h" diff --git a/src/nvim/api/ui.h b/src/nvim/api/ui.h index cdccc27ba4..3f996ec219 100644 --- a/src/nvim/api/ui.h +++ b/src/nvim/api/ui.h @@ -4,6 +4,7 @@ #include "nvim/api/private/defs.h" // IWYU pragma: keep #include "nvim/highlight_defs.h" // IWYU pragma: keep +#include "nvim/macros_defs.h" #include "nvim/types_defs.h" // IWYU pragma: keep #include "nvim/ui_defs.h" // IWYU pragma: keep diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index fce7a86245..332c5bc15c 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -20,6 +20,7 @@ #include "nvim/api/vim.h" #include "nvim/ascii_defs.h" #include "nvim/autocmd.h" +#include "nvim/autocmd_defs.h" #include "nvim/buffer.h" #include "nvim/buffer_defs.h" #include "nvim/channel.h" @@ -28,7 +29,6 @@ #include "nvim/cursor.h" #include "nvim/decoration.h" #include "nvim/drawscreen.h" -#include "nvim/edit.h" #include "nvim/errors.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" @@ -76,7 +76,6 @@ #include "nvim/runtime.h" #include "nvim/sign_defs.h" #include "nvim/state.h" -#include "nvim/state_defs.h" #include "nvim/statusline.h" #include "nvim/statusline_defs.h" #include "nvim/strings.h" diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index e2aac07258..fc7e7e1a06 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include "klib/kvec.h" @@ -21,6 +22,7 @@ #include "nvim/garray_defs.h" #include "nvim/globals.h" #include "nvim/memory.h" +#include "nvim/memory_defs.h" #include "nvim/runtime.h" #include "nvim/vim_defs.h" #include "nvim/viml/parser/expressions.h" diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 6f5a9a90c0..45c9e3f56c 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -1,3 +1,4 @@ +#include #include #include @@ -7,25 +8,22 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/tabpage.h" #include "nvim/api/win_config.h" #include "nvim/ascii_defs.h" #include "nvim/autocmd.h" #include "nvim/autocmd_defs.h" #include "nvim/buffer.h" #include "nvim/buffer_defs.h" -#include "nvim/decoration.h" #include "nvim/decoration_defs.h" #include "nvim/drawscreen.h" #include "nvim/errors.h" #include "nvim/eval/window.h" -#include "nvim/extmark_defs.h" #include "nvim/globals.h" -#include "nvim/grid_defs.h" #include "nvim/highlight_group.h" #include "nvim/macros_defs.h" #include "nvim/mbyte.h" #include "nvim/memory.h" +#include "nvim/memory_defs.h" #include "nvim/option.h" #include "nvim/option_vars.h" #include "nvim/pos_defs.h" @@ -33,7 +31,6 @@ #include "nvim/syntax.h" #include "nvim/types_defs.h" #include "nvim/ui.h" -#include "nvim/ui_compositor.h" #include "nvim/ui_defs.h" #include "nvim/vim_defs.h" #include "nvim/window.h" diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 387dad899e..d968af421d 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -1,4 +1,3 @@ -#include #include #include #include @@ -28,7 +27,7 @@ #include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "api/window.c.generated.h" +# include "api/window.c.generated.h" // IWYU pragma: keep #endif /// Gets the current buffer in a window -- cgit From 487c48ec8689b865bad04fdb87b61f5ada25da97 Mon Sep 17 00:00:00 2001 From: Artem Date: Wed, 25 Dec 2024 14:12:40 -0600 Subject: fix(api): clamp range lines in `nvim__redraw()` (#31710) Problem: `nvim__redraw()` doesn't clamp the lines in the `range` parameter before truncating to int. The resulting range may be empty when the original range contained buffer lines and vice versa. E.g. for a buffer with 4 lines, these are the redrawn lines: ```lua { 2, 2 ^ 31 } -> none (should be { 2, 3 }) { 2, 2 ^ 32 } -> none (should be { 2, 3 }) { 2 ^ 32 - 1, 2 } -> { 0, 1 } (should be none) ``` Solution: Clamp `range` values before truncating to int. --- src/nvim/api/vim.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 332c5bc15c..25f44bb4eb 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -2374,13 +2374,23 @@ void nvim__redraw(Dict(redraw) *opts, Error *err) "%s", "Invalid 'range': Expected 2-tuple of Integers", { return; }); - linenr_T first = (linenr_T)kv_A(opts->range, 0).data.integer + 1; - linenr_T last = (linenr_T)kv_A(opts->range, 1).data.integer; + int64_t begin_raw = kv_A(opts->range, 0).data.integer; + int64_t end_raw = kv_A(opts->range, 1).data.integer; + buf_T *rbuf = win ? win->w_buffer : (buf ? buf : curbuf); - if (last == -1) { - last = rbuf->b_ml.ml_line_count; + linenr_T line_count = rbuf->b_ml.ml_line_count; + + int begin = (int)MIN(begin_raw, line_count); + int end; + if (end_raw == -1) { + end = line_count; + } else { + end = (int)MIN(MAX(begin, end_raw), line_count); + } + + if (begin < end) { + redraw_buf_range_later(rbuf, 1 + begin, end); } - redraw_buf_range_later(rbuf, first, last); } // Redraw later types require update_screen() so call implicitly unless set to false. -- cgit From 6257270040bc5c61a489f7fb9d4102223c36cf89 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Fri, 8 Nov 2024 11:57:59 +0600 Subject: refactor(options): set option value for non-current context directly Problem: Currently, we use `switch_option_context` to temporarily switch the current option context before setting an option for a different buffer / window. This is not ideal because we already support getting and setting option values for non-current contexts in the underlying implementation. Solution: Set option value for non-current context by passing the context directly to the lower level functions. Also introduce a new `OptCtx` struct to store option context information, this will scale much better if we add more option scopes and other context information in the future. --- src/nvim/api/deprecated.c | 10 +++++++--- src/nvim/api/options.c | 12 ++++++++---- src/nvim/api/vim.c | 8 ++++---- 3 files changed, 19 insertions(+), 11 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index d5eddb74de..47a49436ab 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -649,8 +649,8 @@ static Object get_option_from(void *from, OptScope scope, String name, Error *er OptVal value = NIL_OPTVAL; if (option_has_scope(opt_idx, scope)) { - value = get_option_value_for(opt_idx, scope == kOptScopeGlobal ? OPT_GLOBAL : OPT_LOCAL, - scope, from, err); + value = get_option_value_from(opt_idx, option_ctx_from(scope, from), + scope == kOptScopeGlobal ? OPT_GLOBAL : OPT_LOCAL); if (ERROR_SET(err)) { return (Object)OBJECT_INIT; } @@ -701,7 +701,11 @@ static void set_option_to(uint64_t channel_id, void *to, OptScope scope, String : ((scope == kOptScopeGlobal) ? OPT_GLOBAL : OPT_LOCAL); WITH_SCRIPT_CONTEXT(channel_id, { - set_option_value_for(name.data, opt_idx, optval, opt_flags, scope, to, err); + const char *errmsg + = set_option_value_for(opt_idx, optval, option_ctx_from(scope, to), opt_flags); + if (errmsg) { + api_set_error(err, kErrorTypeException, "%s", errmsg); + } }); } diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index 64f8a35d54..2bbbdbbe8c 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -157,8 +157,8 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) void *from = NULL; char *filetype = NULL; - if (!validate_option_value_args(opts, name.data, &opt_idx, &opt_flags, &scope, &from, - &filetype, err)) { + if (!validate_option_value_args(opts, name.data, &opt_idx, &opt_flags, &scope, &from, &filetype, + err)) { return (Object)OBJECT_INIT; } @@ -182,7 +182,7 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) from = ftbuf; } - OptVal value = get_option_value_for(opt_idx, opt_flags, scope, from, err); + OptVal value = get_option_value_from(opt_idx, option_ctx_from(scope, from), opt_flags); if (ftbuf != NULL) { // restore curwin/curbuf and a few other things @@ -257,7 +257,11 @@ void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict( }); WITH_SCRIPT_CONTEXT(channel_id, { - set_option_value_for(name.data, opt_idx, optval, opt_flags, scope, to, err); + const char *errmsg + = set_option_value_for(opt_idx, optval, option_ctx_from(scope, to), opt_flags); + if (errmsg) { + api_set_error(err, kErrorTypeException, "%s", errmsg); + } }); } diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 25f44bb4eb..108d1ee98d 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -982,10 +982,10 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err) buf_copy_options(buf, BCO_ENTER | BCO_NOHELP); if (scratch) { - set_option_direct_for(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL, 0, - kOptScopeBuf, buf); - set_option_direct_for(kOptBuftype, STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL, 0, - kOptScopeBuf, buf); + set_option_direct_for(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("hide"), + option_ctx_from(kOptScopeBuf, buf), OPT_LOCAL, 0); + set_option_direct_for(kOptBuftype, STATIC_CSTR_AS_OPTVAL("nofile"), + option_ctx_from(kOptScopeBuf, buf), OPT_LOCAL, 0); assert(buf->b_ml.ml_mfp->mf_fd < 0); // ml_open() should not have opened swapfile already buf->b_p_swf = false; buf->b_p_ml = false; -- cgit From bf48dfadeccc37527e9b59b1c0f529ea889bf735 Mon Sep 17 00:00:00 2001 From: glepnir Date: Sun, 5 Jan 2025 21:57:53 +0800 Subject: fix(api): nvim__complete_set requires completeopt=popup #31177 Problem: If completeopt does not include "popup" flag, nvim__complete_set still auto-creates a floating preview window. Solution: Fail if completeopt does not include the "popup" flag. --- src/nvim/api/vim.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 25f44bb4eb..e3e69f4ff6 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -44,6 +44,7 @@ #include "nvim/highlight.h" #include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" +#include "nvim/insexpand.h" #include "nvim/keycodes.h" #include "nvim/log.h" #include "nvim/lua/executor.h" @@ -2255,9 +2256,13 @@ void nvim_error_event(uint64_t channel_id, Integer lvl, String data) /// @return Dict containing these keys: /// - winid: (number) floating window id /// - bufnr: (number) buffer id in floating window -Dict nvim__complete_set(Integer index, Dict(complete_set) *opts, Arena *arena) +Dict nvim__complete_set(Integer index, Dict(complete_set) *opts, Arena *arena, Error *err) { Dict rv = arena_dict(arena, 2); + if ((get_cot_flags() & kOptCotFlagPopup) == 0) { + api_set_error(err, kErrorTypeException, "completeopt option does not include popup"); + return rv; + } if (HAS_KEY(opts, complete_set, info)) { win_T *wp = pum_set_info((int)index, opts->info.data); if (wp) { -- cgit From 141114c170d6dd9a7457b7822184970fd6ca0a43 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 8 Jan 2025 13:16:41 +0800 Subject: fix(api): crash on invalid buffer to nvim_buf_del_user_command (#31908) --- src/nvim/api/command.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index 7e79133ed4..23e08bd3fe 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -931,6 +931,9 @@ void nvim_buf_del_user_command(Buffer buffer, String name, Error *err) gap = &ucmds; } else { buf_T *buf = find_buffer_by_handle(buffer, err); + if (ERROR_SET(err)) { + return; + } gap = &buf->b_ucmds; } -- cgit From 5b9518b43663f9e77e5f041006df921350bf5061 Mon Sep 17 00:00:00 2001 From: notomo <18519692+notomo@users.noreply.github.com> Date: Wed, 8 Jan 2025 21:37:29 +0900 Subject: fix(api): nvim_set_decoration_provider callback return type #31912 Problem: incorrect return type doc causes luals `Annotations specify that at most 0 return value(s) are required, found 1 returned here instead.` diagnosis Solution: correct return type doc --- src/nvim/api/keysets_defs.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h index 48f5f7246c..40e89d230d 100644 --- a/src/nvim/api/keysets_defs.h +++ b/src/nvim/api/keysets_defs.h @@ -13,10 +13,11 @@ typedef struct { typedef struct { OptionalKeys is_set__set_decoration_provider_; - LuaRefOf(("start" _, Integer tick)) on_start; + LuaRefOf(("start" _, Integer tick), *Boolean) on_start; LuaRefOf(("buf" _, Integer bufnr, Integer tick)) on_buf; - LuaRefOf(("win" _, Integer winid, Integer bufnr, Integer toprow, Integer botrow)) on_win; - LuaRefOf(("line" _, Integer winid, Integer bufnr, Integer row)) on_line; + LuaRefOf(("win" _, Integer winid, Integer bufnr, Integer toprow, Integer botrow), + *Boolean) on_win; + LuaRefOf(("line" _, Integer winid, Integer bufnr, Integer row), *Boolean) on_line; LuaRefOf(("end" _, Integer tick)) on_end; LuaRefOf(("hl_def" _)) _on_hl_def; LuaRefOf(("spell_nav" _)) _on_spell_nav; -- cgit From 19c9572d3626cde8503ee9061fa334b73f257b03 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 9 Jan 2025 12:32:25 +0800 Subject: Revert "refactor(options): set option value for non-current context directly" (#31924) Reverts #31112 --- src/nvim/api/deprecated.c | 10 +++------- src/nvim/api/options.c | 12 ++++-------- src/nvim/api/vim.c | 8 ++++---- 3 files changed, 11 insertions(+), 19 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index 47a49436ab..d5eddb74de 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -649,8 +649,8 @@ static Object get_option_from(void *from, OptScope scope, String name, Error *er OptVal value = NIL_OPTVAL; if (option_has_scope(opt_idx, scope)) { - value = get_option_value_from(opt_idx, option_ctx_from(scope, from), - scope == kOptScopeGlobal ? OPT_GLOBAL : OPT_LOCAL); + value = get_option_value_for(opt_idx, scope == kOptScopeGlobal ? OPT_GLOBAL : OPT_LOCAL, + scope, from, err); if (ERROR_SET(err)) { return (Object)OBJECT_INIT; } @@ -701,11 +701,7 @@ static void set_option_to(uint64_t channel_id, void *to, OptScope scope, String : ((scope == kOptScopeGlobal) ? OPT_GLOBAL : OPT_LOCAL); WITH_SCRIPT_CONTEXT(channel_id, { - const char *errmsg - = set_option_value_for(opt_idx, optval, option_ctx_from(scope, to), opt_flags); - if (errmsg) { - api_set_error(err, kErrorTypeException, "%s", errmsg); - } + set_option_value_for(name.data, opt_idx, optval, opt_flags, scope, to, err); }); } diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index 2bbbdbbe8c..64f8a35d54 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -157,8 +157,8 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) void *from = NULL; char *filetype = NULL; - if (!validate_option_value_args(opts, name.data, &opt_idx, &opt_flags, &scope, &from, &filetype, - err)) { + if (!validate_option_value_args(opts, name.data, &opt_idx, &opt_flags, &scope, &from, + &filetype, err)) { return (Object)OBJECT_INIT; } @@ -182,7 +182,7 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) from = ftbuf; } - OptVal value = get_option_value_from(opt_idx, option_ctx_from(scope, from), opt_flags); + OptVal value = get_option_value_for(opt_idx, opt_flags, scope, from, err); if (ftbuf != NULL) { // restore curwin/curbuf and a few other things @@ -257,11 +257,7 @@ void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict( }); WITH_SCRIPT_CONTEXT(channel_id, { - const char *errmsg - = set_option_value_for(opt_idx, optval, option_ctx_from(scope, to), opt_flags); - if (errmsg) { - api_set_error(err, kErrorTypeException, "%s", errmsg); - } + set_option_value_for(name.data, opt_idx, optval, opt_flags, scope, to, err); }); } diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 46e6b24c75..e3e69f4ff6 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -983,10 +983,10 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err) buf_copy_options(buf, BCO_ENTER | BCO_NOHELP); if (scratch) { - set_option_direct_for(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("hide"), - option_ctx_from(kOptScopeBuf, buf), OPT_LOCAL, 0); - set_option_direct_for(kOptBuftype, STATIC_CSTR_AS_OPTVAL("nofile"), - option_ctx_from(kOptScopeBuf, buf), OPT_LOCAL, 0); + set_option_direct_for(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL, 0, + kOptScopeBuf, buf); + set_option_direct_for(kOptBuftype, STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL, 0, + kOptScopeBuf, buf); assert(buf->b_ml.ml_mfp->mf_fd < 0); // ml_open() should not have opened swapfile already buf->b_p_swf = false; buf->b_p_ml = false; -- cgit From ead5683ff9994c0fbfc6c38e0911d9455777550b Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Tue, 7 Jan 2025 14:20:45 +0100 Subject: feat(api): add err field to nvim_echo() opts Problem: We want to deprecate `nvim_err_write(ln)()` but there is no obvious replacement (from Lua). Meanwhile we already have `nvim_echo()` with an `opts` argument. Solution: Add `err` argument to `nvim_echo()` that directly maps to `:echoerr`. --- src/nvim/api/keysets_defs.h | 1 + src/nvim/api/private/helpers.c | 4 ++-- src/nvim/api/vim.c | 6 ++++-- 3 files changed, 7 insertions(+), 4 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h index 48f5f7246c..17287e083b 100644 --- a/src/nvim/api/keysets_defs.h +++ b/src/nvim/api/keysets_defs.h @@ -325,6 +325,7 @@ typedef struct { } Dict(cmd_opts); typedef struct { + Boolean err; Boolean verbose; } Dict(echo_opts); diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 78aa7c00f7..c98635f8fd 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -776,7 +776,7 @@ char *api_typename(ObjectType t) UNREACHABLE; } -HlMessage parse_hl_msg(Array chunks, Error *err) +HlMessage parse_hl_msg(Array chunks, bool is_err, Error *err) { HlMessage hl_msg = KV_INITIAL_VALUE; for (size_t i = 0; i < chunks.size; i++) { @@ -791,7 +791,7 @@ HlMessage parse_hl_msg(Array chunks, Error *err) String str = copy_string(chunk.items[0].data.string, NULL); - int hl_id = 0; + int hl_id = is_err ? HLF_E : 0; if (chunk.size == 2) { hl_id = object_to_hl_id(chunk.items[1], "text highlight", err); } diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index e3e69f4ff6..f0848b7e04 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -775,13 +775,15 @@ void nvim_set_vvar(String name, Object value, Error *err) /// `hl_group` element can be omitted for no highlight. /// @param history if true, add to |message-history|. /// @param opts Optional parameters. +/// - err: Treat the message like |:echoerr|. Omitted `hlgroup` +/// uses |hl-ErrorMsg| instead. /// - verbose: Message is printed as a result of 'verbose' option. /// If Nvim was invoked with -V3log_file, the message will be /// redirected to the log_file and suppressed from direct output. void nvim_echo(Array chunks, Boolean history, Dict(echo_opts) *opts, Error *err) FUNC_API_SINCE(7) { - HlMessage hl_msg = parse_hl_msg(chunks, err); + HlMessage hl_msg = parse_hl_msg(chunks, opts->err, err); if (ERROR_SET(err)) { goto error; } @@ -790,7 +792,7 @@ void nvim_echo(Array chunks, Boolean history, Dict(echo_opts) *opts, Error *err) verbose_enter(); } - msg_multihl(hl_msg, history ? "echomsg" : "echo", history); + msg_multihl(hl_msg, opts->err ? "echoerr" : history ? "echomsg" : "echo", history, opts->err); if (opts->verbose) { verbose_leave(); -- cgit From 5c92b40b4b173c7d85106689fef811e41994abb0 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Tue, 7 Jan 2025 16:38:29 +0100 Subject: feat(api): deprecate nvim_out/err_write(ln) --- src/nvim/api/deprecated.c | 80 +++++++++++++++++++++++++++++++++++++++++++++ src/nvim/api/vim.c | 82 ----------------------------------------------- 2 files changed, 80 insertions(+), 82 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index d5eddb74de..406d5e7b4f 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -21,9 +21,11 @@ #include "nvim/lua/executor.h" #include "nvim/memory.h" #include "nvim/memory_defs.h" +#include "nvim/message.h" #include "nvim/option.h" #include "nvim/option_defs.h" #include "nvim/pos_defs.h" +#include "nvim/strings.h" #include "nvim/types_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -812,3 +814,81 @@ void nvim_unsubscribe(uint64_t channel_id, String event) { // Does nothing. `rpcnotify(0,…)` broadcasts to all channels, there are no "subscriptions". } + +enum { LINE_BUFFER_MIN_SIZE = 4096, }; + +/// Writes a message to vim output or error buffer. The string is split +/// and flushed after each newline. Incomplete lines are kept for writing +/// later. +/// +/// @param message Message to write +/// @param to_err true: message is an error (uses `emsg` instead of `msg`) +/// @param writeln Append a trailing newline +static void write_msg(String message, bool to_err, bool writeln) +{ + static StringBuilder out_line_buf = KV_INITIAL_VALUE; + static StringBuilder err_line_buf = KV_INITIAL_VALUE; + StringBuilder *line_buf = to_err ? &err_line_buf : &out_line_buf; + +#define PUSH_CHAR(c) \ + if (kv_max(*line_buf) == 0) { \ + kv_resize(*line_buf, LINE_BUFFER_MIN_SIZE); \ + } \ + if (c == NL) { \ + kv_push(*line_buf, NUL); \ + if (to_err) { \ + emsg(line_buf->items); \ + } else { \ + msg(line_buf->items, 0); \ + } \ + if (msg_silent == 0) { \ + msg_didout = true; \ + } \ + kv_drop(*line_buf, kv_size(*line_buf)); \ + kv_resize(*line_buf, LINE_BUFFER_MIN_SIZE); \ + } else if (c == NUL) { \ + kv_push(*line_buf, NL); \ + } else { \ + kv_push(*line_buf, c); \ + } + + no_wait_return++; + for (uint32_t i = 0; i < message.size; i++) { + if (got_int) { + break; + } + PUSH_CHAR(message.data[i]); + } + if (writeln) { + PUSH_CHAR(NL); + } + no_wait_return--; + msg_end(); +} + +/// @deprecated +/// +/// @param str Message +void nvim_out_write(String str) + FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) +{ + write_msg(str, false, false); +} + +/// @deprecated +/// +/// @param str Message +void nvim_err_write(String str) + FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) +{ + write_msg(str, true, false); +} + +/// @deprecated +/// +/// @param str Message +void nvim_err_writeln(String str) + FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) +{ + write_msg(str, true, true); +} diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index f0848b7e04..ed9edb1beb 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -86,8 +86,6 @@ #include "nvim/vim_defs.h" #include "nvim/window.h" -#define LINE_BUFFER_MIN_SIZE 4096 - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/vim.c.generated.h" #endif @@ -808,37 +806,6 @@ error: hl_msg_free(hl_msg); } -/// Writes a message to the Vim output buffer. Does not append "\n", the -/// message is buffered (won't display) until a linefeed is written. -/// -/// @param str Message -void nvim_out_write(String str) - FUNC_API_SINCE(1) -{ - write_msg(str, false, false); -} - -/// Writes a message to the Vim error buffer. Does not append "\n", the -/// message is buffered (won't display) until a linefeed is written. -/// -/// @param str Message -void nvim_err_write(String str) - FUNC_API_SINCE(1) -{ - write_msg(str, true, false); -} - -/// Writes a message to the Vim error buffer. Appends "\n", so the buffer is -/// flushed (and displayed). -/// -/// @param str Message -/// @see nvim_err_write() -void nvim_err_writeln(String str) - FUNC_API_SINCE(1) -{ - write_msg(str, true, true); -} - /// Gets the current list of buffer handles /// /// Includes unlisted (unloaded/deleted) buffers, like `:ls!`. @@ -1664,55 +1631,6 @@ Array nvim_list_chans(Arena *arena) return channel_all_info(arena); } -/// Writes a message to vim output or error buffer. The string is split -/// and flushed after each newline. Incomplete lines are kept for writing -/// later. -/// -/// @param message Message to write -/// @param to_err true: message is an error (uses `emsg` instead of `msg`) -/// @param writeln Append a trailing newline -static void write_msg(String message, bool to_err, bool writeln) -{ - static StringBuilder out_line_buf = KV_INITIAL_VALUE; - static StringBuilder err_line_buf = KV_INITIAL_VALUE; - StringBuilder *line_buf = to_err ? &err_line_buf : &out_line_buf; - -#define PUSH_CHAR(c) \ - if (kv_max(*line_buf) == 0) { \ - kv_resize(*line_buf, LINE_BUFFER_MIN_SIZE); \ - } \ - if (c == NL) { \ - kv_push(*line_buf, NUL); \ - if (to_err) { \ - emsg(line_buf->items); \ - } else { \ - msg(line_buf->items, 0); \ - } \ - if (msg_silent == 0) { \ - msg_didout = true; \ - } \ - kv_drop(*line_buf, kv_size(*line_buf)); \ - kv_resize(*line_buf, LINE_BUFFER_MIN_SIZE); \ - } else if (c == NUL) { \ - kv_push(*line_buf, NL); \ - } else { \ - kv_push(*line_buf, c); \ - } - - no_wait_return++; - for (uint32_t i = 0; i < message.size; i++) { - if (got_int) { - break; - } - PUSH_CHAR(message.data[i]); - } - if (writeln) { - PUSH_CHAR(NL); - } - no_wait_return--; - msg_end(); -} - // Functions used for testing purposes /// Returns object given as argument. -- cgit From 1e47aa677a24231ec771137dadc7c2b65be775b4 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Thu, 9 Jan 2025 21:32:27 +0600 Subject: fix(api): deprecated API nvim_get_option does not validate option name #31919 Problem: Deprecated API `nvim_get_option()` doesn't validate the option name, which leads to an assertion failure. Solution: Validate option name in `nvim_get_option()`. Ref: #31894 --- src/nvim/api/deprecated.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index 406d5e7b4f..5150aeebec 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -648,6 +648,10 @@ static Object get_option_from(void *from, OptScope scope, String name, Error *er }); OptIndex opt_idx = find_option(name.data); + VALIDATE_S(opt_idx != kOptInvalid, "option name", name.data, { + return (Object)OBJECT_INIT; + }); + OptVal value = NIL_OPTVAL; if (option_has_scope(opt_idx, scope)) { -- cgit From 7c00e0efbb18e8627ac59eaadf564a9f1b2bafcd Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 9 Jan 2025 09:26:45 -0800 Subject: docs: misc #31867 --- src/nvim/api/vim.c | 54 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 22 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index ed9edb1beb..e03a260280 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -766,18 +766,20 @@ void nvim_set_vvar(String name, Object value, Error *err) dict_set_var(&vimvardict, name, value, false, false, NULL, err); } -/// Echo a message. +/// Prints a message given by a list of `[text, hl_group]` "chunks". /// -/// @param chunks A list of `[text, hl_group]` arrays, each representing a -/// text chunk with specified highlight group name or ID. -/// `hl_group` element can be omitted for no highlight. +/// Example: +/// ```lua +/// vim.api.nvim_echo({ { 'chunk1-line1\nchunk1-line2\n' }, { 'chunk2-line1' } }, true, {}) +/// ``` +/// +/// @param chunks List of `[text, hl_group]` pairs, where each is a `text` string highlighted by +/// the (optional) name or ID `hl_group`. /// @param history if true, add to |message-history|. /// @param opts Optional parameters. -/// - err: Treat the message like |:echoerr|. Omitted `hlgroup` -/// uses |hl-ErrorMsg| instead. -/// - verbose: Message is printed as a result of 'verbose' option. -/// If Nvim was invoked with -V3log_file, the message will be -/// redirected to the log_file and suppressed from direct output. +/// - err: Treat the message like `:echoerr`. Sets `hl_group` to |hl-ErrorMsg| by default. +/// - verbose: Message is controlled by the 'verbose' option. Nvim invoked with `-V3log` +/// will write the message to the "log" file instead of standard output. void nvim_echo(Array chunks, Boolean history, Dict(echo_opts) *opts, Error *err) FUNC_API_SINCE(7) { @@ -1000,7 +1002,8 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err) /// in a virtual terminal having the intended size. /// /// Example: this `TermHl` command can be used to display and highlight raw ANSI termcodes, so you -/// can use Nvim as a "scrollback pager" (for terminals like kitty): [terminal-scrollback-pager]() +/// can use Nvim as a "scrollback pager" (for terminals like kitty): [ansi-colorize]() +/// [terminal-scrollback-pager]() /// /// ```lua /// vim.api.nvim_create_user_command('TermHl', function() @@ -1500,20 +1503,17 @@ Array nvim_get_api_info(uint64_t channel_id, Arena *arena) return rv; } -/// Self-identifies the client. -/// -/// The client/plugin/application should call this after connecting, to provide -/// hints about its identity and purpose, for debugging and orchestration. +/// Self-identifies the client. Sets the `client` object returned by |nvim_get_chan_info()|. /// -/// Can be called more than once; the caller should merge old info if -/// appropriate. Example: library first identifies the channel, then a plugin -/// using that library later identifies itself. +/// Clients should call this just after connecting, to provide hints for debugging and +/// orchestration. (Note: Something is better than nothing! Fields are optional, but at least set +/// `name`.) /// -/// @note "Something is better than nothing". You don't need to include all the -/// fields. +/// Can be called more than once; the caller should merge old info if appropriate. Example: library +/// first identifies the channel, then a plugin using that library later identifies itself. /// /// @param channel_id -/// @param name Short name for the connected client +/// @param name Client short-name. Sets the `client.name` field of |nvim_get_chan_info()|. /// @param version Dict describing the version, with these /// (optional) keys: /// - "major" major version (defaults to 0 if not set, for no release yet) @@ -1587,6 +1587,8 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dict version, String /// Gets information about a channel. /// +/// See |nvim_list_uis()| for an example of how to get channel info. +/// /// @param chan channel_id, or 0 for current channel /// @returns Channel info dict with these keys: /// - "id" Channel id. @@ -1604,8 +1606,8 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dict version, String /// "/dev/pts/1". If unknown, the key will still be present if a pty is used (e.g. /// for conpty on Windows). /// - "buffer" (optional) Buffer connected to |terminal| instance. -/// - "client" (optional) Info about the peer (client on the other end of the RPC channel), -/// which it provided via |nvim_set_client_info()|. +/// - "client" (optional) Info about the peer (client on the other end of the channel), as set +/// by |nvim_set_client_info()|. /// Dict nvim_get_chan_info(uint64_t channel_id, Integer chan, Arena *arena, Error *err) FUNC_API_SINCE(4) @@ -1702,6 +1704,14 @@ Dict nvim__stats(Arena *arena) /// Gets a list of dictionaries representing attached UIs. /// +/// Example: The Nvim builtin |TUI| sets its channel info as described in |startup-tui|. In +/// particular, it sets `client.name` to "nvim-tui". So you can check if the TUI is running by +/// inspecting the client name of each UI: +/// +/// ```lua +/// vim.print(vim.api.nvim_get_chan_info(vim.api.nvim_list_uis()[1].chan).client.name) +/// ``` +/// /// @return Array of UI dictionaries, each with these keys: /// - "height" Requested height of the UI /// - "width" Requested width of the UI -- cgit From 0717dfbfaf36887dab277527eb0a93bf2182297c Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 10 Jan 2025 11:42:04 -0800 Subject: refactor(api): deprecate nvim_notify #31938 Problem: The `nvim_notify` API (note: unrelated to `vim.notify()` Lua API) was not given any real motivation in https://github.com/neovim/neovim/pull/13843 There are, and were, idiomatic and ergonomic alternatives already. Solution: Deprecate `nvim_notify`. --- src/nvim/api/deprecated.c | 21 +++++++++++++++++++++ src/nvim/api/vim.c | 20 -------------------- 2 files changed, 21 insertions(+), 20 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index 5150aeebec..b9e7d7143a 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -1,3 +1,5 @@ +// Island of misfit toys. + #include #include #include @@ -896,3 +898,22 @@ void nvim_err_writeln(String str) { write_msg(str, true, true); } + +/// @deprecated +/// +/// Use `nvim_echo` or `nvim_exec_lua("vim.notify(...)", ...)` instead. +/// +/// @param msg Message to display to the user +/// @param log_level The log level +/// @param opts Reserved for future use. +/// @param[out] err Error details, if any +Object nvim_notify(String msg, Integer log_level, Dict opts, Arena *arena, Error *err) + FUNC_API_SINCE(7) FUNC_API_DEPRECATED_SINCE(13) +{ + MAXSIZE_TEMP_ARRAY(args, 3); + ADD_C(args, STRING_OBJ(msg)); + ADD_C(args, INTEGER_OBJ(log_level)); + ADD_C(args, DICT_OBJ(opts)); + + return NLUA_EXEC_STATIC("return vim.notify(...)", args, kRetObject, arena, err); +} diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index e03a260280..faf6c0567c 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -516,26 +516,6 @@ Object nvim_exec_lua(String code, Array args, Arena *arena, Error *err) return nlua_exec(code, args, kRetObject, arena, err); } -/// Notify the user with a message -/// -/// Relays the call to vim.notify . By default forwards your message in the -/// echo area but can be overridden to trigger desktop notifications. -/// -/// @param msg Message to display to the user -/// @param log_level The log level -/// @param opts Reserved for future use. -/// @param[out] err Error details, if any -Object nvim_notify(String msg, Integer log_level, Dict opts, Arena *arena, Error *err) - FUNC_API_SINCE(7) -{ - MAXSIZE_TEMP_ARRAY(args, 3); - ADD_C(args, STRING_OBJ(msg)); - ADD_C(args, INTEGER_OBJ(log_level)); - ADD_C(args, DICT_OBJ(opts)); - - return NLUA_EXEC_STATIC("return vim.notify(...)", args, kRetObject, arena, err); -} - /// Calculates the number of display cells occupied by `text`. /// Control characters including [] count as one cell. /// -- cgit From 25d8c3a5ad7e9c5668841e66540ebe34ceda73a7 Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Tue, 14 Jan 2025 14:02:46 +0100 Subject: feat(api): nvim_open_win() relative to tabline and laststatus #32006 Problem: Anchoring a floating window to the tabline and laststatus is cumbersome; requiring autocommands and looping over all windows/tabpages. Solution: Add new "tabline" and "laststatus" options to the `relative` field of nvim_open_win() to place a window relative to. --- src/nvim/api/win_config.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 45c9e3f56c..225189a3f9 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -101,10 +101,12 @@ /// @param config Map defining the window configuration. Keys: /// - relative: Sets the window layout to "floating", placed at (row,col) /// coordinates relative to: -/// - "editor" The global editor grid -/// - "win" Window given by the `win` field, or current window. -/// - "cursor" Cursor position in current window. -/// - "mouse" Mouse position +/// - "cursor" Cursor position in current window. +/// - "editor" The global editor grid. +/// - "laststatus" 'laststatus' if present, or last row. +/// - "mouse" Mouse position. +/// - "tabline" Tabline if present, or first row. +/// - "win" Window given by the `win` field, or current window. /// - win: |window-ID| window to split, or relative window when creating a /// float (relative="win"). /// - anchor: Decides which corner of the float to place at (row,col): @@ -699,7 +701,9 @@ Dict(win_config) nvim_win_get_config(Window window, Arena *arena, Error *err) FUNC_API_SINCE(6) { /// Keep in sync with FloatRelative in buffer_defs.h - static const char *const float_relative_str[] = { "editor", "win", "cursor", "mouse" }; + static const char *const float_relative_str[] = { + "editor", "win", "cursor", "mouse", "tabline", "laststatus" + }; /// Keep in sync with WinSplit in buffer_defs.h static const char *const win_split_str[] = { "left", "right", "above", "below" }; @@ -805,6 +809,10 @@ static bool parse_float_relative(String relative, FloatRelative *out) *out = kFloatRelativeCursor; } else if (striequal(str, "mouse")) { *out = kFloatRelativeMouse; + } else if (striequal(str, "tabline")) { + *out = kFloatRelativeTabline; + } else if (striequal(str, "laststatus")) { + *out = kFloatRelativeLaststatus; } else { return false; } -- cgit From d55b17e2b4e061fd8b330f928785a217c99c9d11 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Tue, 14 Jan 2025 17:23:11 +0100 Subject: fix(messages): verbose kind for nvim_echo() Problem: No "verbose" kind for nvim_echo() opts->verbose. Solution: Pass NULL "kind" to indicate no new kind. --- 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 faf6c0567c..950c70026b 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -772,7 +772,8 @@ void nvim_echo(Array chunks, Boolean history, Dict(echo_opts) *opts, Error *err) verbose_enter(); } - msg_multihl(hl_msg, opts->err ? "echoerr" : history ? "echomsg" : "echo", history, opts->err); + char *kind = opts->verbose ? NULL : opts->err ? "echoerr" : history ? "echomsg" : "echo"; + msg_multihl(hl_msg, kind, history, opts->err); if (opts->verbose) { verbose_leave(); -- cgit From 19b25f3feacfedc18a57eb2a1368a1ed07ac5faa Mon Sep 17 00:00:00 2001 From: bfredl Date: Sun, 19 Jan 2025 13:30:11 +0100 Subject: feat(api): deprecate nvim_buf_add_highlight() This was kept for a while as it was a useful short hand and initially matched what highlights what actually properly implemented. But now |vim.hl.range()| is a better high-level shorthand with full support for native multi-line ranges. --- src/nvim/api/deprecated.c | 86 ++++++++++++++++++++++++++++++++++++++++++++ src/nvim/api/extmark.c | 91 +---------------------------------------------- 2 files changed, 87 insertions(+), 90 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index b9e7d7143a..1d81b21be6 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -21,6 +21,7 @@ #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/lua/executor.h" +#include "nvim/marktree.h" #include "nvim/memory.h" #include "nvim/memory_defs.h" #include "nvim/message.h" @@ -84,6 +85,17 @@ Integer nvim_buf_get_number(Buffer buffer, Error *err) return buf->b_fnum; } +static uint32_t src2ns(Integer *src_id) +{ + if (*src_id == 0) { + *src_id = nvim_create_namespace((String)STRING_INIT); + } + if (*src_id < 0) { + return (((uint32_t)1) << 31) - 1; + } + return (uint32_t)(*src_id); +} + /// Clears highlights and virtual text from namespace and range of lines /// /// @deprecated use |nvim_buf_clear_namespace()|. @@ -102,6 +114,80 @@ 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); } +/// Adds a highlight to buffer. +/// +/// @deprecated use |nvim_buf_set_extmark()| or |vim.hl.range()| +/// +/// Namespaces are used for batch deletion/updating of a set of highlights. To +/// create a namespace, use |nvim_create_namespace()| which returns a namespace +/// id. Pass it in to this function as `ns_id` to add highlights to the +/// namespace. All highlights in the same namespace can then be cleared with +/// single call to |nvim_buf_clear_namespace()|. If the highlight never will be +/// deleted by an API call, pass `ns_id = -1`. +/// +/// As a shorthand, `ns_id = 0` can be used to create a new namespace for the +/// highlight, the allocated id is then returned. If `hl_group` is the empty +/// string no highlight is added, but a new `ns_id` is still returned. This is +/// supported for backwards compatibility, new code should use +/// |nvim_create_namespace()| to create a new empty namespace. +/// +/// @param buffer Buffer handle, or 0 for current buffer +/// @param ns_id namespace to use or -1 for ungrouped highlight +/// @param hl_group Name of the highlight group to use +/// @param line Line to highlight (zero-indexed) +/// @param col_start Start of (byte-indexed) column range to highlight +/// @param col_end End of (byte-indexed) column range to highlight, +/// or -1 to highlight to end of line +/// @param[out] err Error details, if any +/// @return The ns_id that was used +Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, Integer line, + Integer col_start, Integer col_end, Error *err) + FUNC_API_SINCE(1) + FUNC_API_DEPRECATED_SINCE(13) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return 0; + } + + VALIDATE_RANGE((line >= 0 && line < MAXLNUM), "line number", { + return 0; + }); + VALIDATE_RANGE((col_start >= 0 && col_start <= MAXCOL), "column", { + return 0; + }); + + if (col_end < 0 || col_end > MAXCOL) { + col_end = MAXCOL; + } + + uint32_t ns = src2ns(&ns_id); + + if (!(line < buf->b_ml.ml_line_count)) { + // safety check, we can't add marks outside the range + return ns_id; + } + + int hl_id = 0; + if (hl_group.size > 0) { + hl_id = syn_check_group(hl_group.data, hl_group.size); + } else { + return ns_id; + } + + int end_line = (int)line; + if (col_end == MAXCOL) { + col_end = 0; + end_line++; + } + + DecorInline decor = DECOR_INLINE_INIT; + decor.data.hl.hl_id = hl_id; + + extmark_set(buf, ns, NULL, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end, + decor, MT_FLAG_DECOR_HL, true, false, false, false, NULL); + return ns_id; +} /// Set the virtual text (annotation) for a buffer line. /// /// @deprecated use nvim_buf_set_extmark to use full virtual text functionality. diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index a237e7531a..aa07bf39b3 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -49,7 +49,7 @@ void api_extmark_free_all_mem(void) /// Creates a new namespace or gets an existing one. [namespace]() /// /// Namespaces are used for buffer highlights and virtual text, see -/// |nvim_buf_add_highlight()| and |nvim_buf_set_extmark()|. +/// |nvim_buf_set_extmark()|. /// /// Namespaces can be named or anonymous. If `name` matches an existing /// namespace, the associated id is returned. If `name` is an empty string @@ -857,95 +857,6 @@ Boolean nvim_buf_del_extmark(Buffer buffer, Integer ns_id, Integer id, Error *er return extmark_del_id(buf, (uint32_t)ns_id, (uint32_t)id); } -uint32_t src2ns(Integer *src_id) -{ - if (*src_id == 0) { - *src_id = nvim_create_namespace((String)STRING_INIT); - } - if (*src_id < 0) { - return (((uint32_t)1) << 31) - 1; - } - return (uint32_t)(*src_id); -} - -/// Adds a highlight to buffer. -/// -/// Useful for plugins that dynamically generate highlights to a buffer -/// (like a semantic highlighter or linter). The function adds a single -/// highlight to a buffer. Unlike |matchaddpos()| highlights follow changes to -/// line numbering (as lines are inserted/removed above the highlighted line), -/// like signs and marks do. -/// -/// Namespaces are used for batch deletion/updating of a set of highlights. To -/// create a namespace, use |nvim_create_namespace()| which returns a namespace -/// id. Pass it in to this function as `ns_id` to add highlights to the -/// namespace. All highlights in the same namespace can then be cleared with -/// single call to |nvim_buf_clear_namespace()|. If the highlight never will be -/// deleted by an API call, pass `ns_id = -1`. -/// -/// As a shorthand, `ns_id = 0` can be used to create a new namespace for the -/// highlight, the allocated id is then returned. If `hl_group` is the empty -/// string no highlight is added, but a new `ns_id` is still returned. This is -/// supported for backwards compatibility, new code should use -/// |nvim_create_namespace()| to create a new empty namespace. -/// -/// @param buffer Buffer handle, or 0 for current buffer -/// @param ns_id namespace to use or -1 for ungrouped highlight -/// @param hl_group Name of the highlight group to use -/// @param line Line to highlight (zero-indexed) -/// @param col_start Start of (byte-indexed) column range to highlight -/// @param col_end End of (byte-indexed) column range to highlight, -/// or -1 to highlight to end of line -/// @param[out] err Error details, if any -/// @return The ns_id that was used -Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, Integer line, - Integer col_start, Integer col_end, Error *err) - FUNC_API_SINCE(1) -{ - buf_T *buf = find_buffer_by_handle(buffer, err); - if (!buf) { - return 0; - } - - VALIDATE_RANGE((line >= 0 && line < MAXLNUM), "line number", { - return 0; - }); - VALIDATE_RANGE((col_start >= 0 && col_start <= MAXCOL), "column", { - return 0; - }); - - if (col_end < 0 || col_end > MAXCOL) { - col_end = MAXCOL; - } - - uint32_t ns = src2ns(&ns_id); - - if (!(line < buf->b_ml.ml_line_count)) { - // safety check, we can't add marks outside the range - return ns_id; - } - - int hl_id = 0; - if (hl_group.size > 0) { - hl_id = syn_check_group(hl_group.data, hl_group.size); - } else { - return ns_id; - } - - int end_line = (int)line; - if (col_end == MAXCOL) { - col_end = 0; - end_line++; - } - - DecorInline decor = DECOR_INLINE_INIT; - decor.data.hl.hl_id = hl_id; - - extmark_set(buf, ns, NULL, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end, - decor, MT_FLAG_DECOR_HL, true, false, false, false, NULL); - return ns_id; -} - /// Clears |namespace|d objects (highlights, |extmarks|, virtual text) from /// a region. /// -- cgit From 4cced601c8cdfd6253266b035667dd0383a07ebe Mon Sep 17 00:00:00 2001 From: bfredl Date: Fri, 17 Jan 2025 13:44:07 +0100 Subject: feat(extmark): stack multiple highlight groups in `hl_group` This has been possible in the "backend" for a while but API was missing. Followup: we will need a `details2=true` mode for `nvim_get_hl_id_by_name` to return information in a way forward compatible with even further enhancements. --- src/nvim/api/extmark.c | 48 +++++++++++++++++++++++++++++++++++++++++++-- src/nvim/api/keysets_defs.h | 2 +- 2 files changed, 47 insertions(+), 3 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index a237e7531a..18e37012ee 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -385,6 +385,9 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// - hl_group : highlight group used for the text range. This and below /// highlight groups can be supplied either as a string or as an integer, /// the latter of which can be obtained using |nvim_get_hl_id_by_name()|. +/// +/// Multiple highlight groups can be stacked by passing an array (highest +/// priority last). /// - hl_eol : when true, for a multiline highlight covering the /// EOL of a line, continue the highlight for the rest /// of the screen line (just like for diff and @@ -499,6 +502,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer DecorVirtText virt_lines = DECOR_VIRT_LINES_INIT; char *url = NULL; bool has_hl = false; + bool has_hl_multiple = false; buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { @@ -551,8 +555,33 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer col2 = (int)val; } - hl.hl_id = (int)opts->hl_group; - has_hl = hl.hl_id > 0; + if (HAS_KEY(opts, set_extmark, hl_group)) { + if (opts->hl_group.type == kObjectTypeArray) { + Array arr = opts->hl_group.data.array; + if (arr.size >= 1) { + hl.hl_id = object_to_hl_id(arr.items[0], "hl_group item", err); + if (ERROR_SET(err)) { + goto error; + } + } + for (size_t i = 1; i < arr.size; i++) { + int hl_id = object_to_hl_id(arr.items[i], "hl_group item", err); + if (ERROR_SET(err)) { + goto error; + } + if (hl_id) { + has_hl_multiple = true; + } + } + } else { + hl.hl_id = object_to_hl_id(opts->hl_group, "hl_group", err); + if (ERROR_SET(err)) { + goto error; + } + } + has_hl = hl.hl_id > 0; + } + sign.hl_id = (int)opts->sign_hl_group; sign.cursorline_hl_id = (int)opts->cursorline_hl_group; sign.number_hl_id = (int)opts->number_hl_group; @@ -794,6 +823,21 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer } } + if (has_hl_multiple) { + Array arr = opts->hl_group.data.array; + for (size_t i = arr.size - 1; i > 0; i--) { // skip hl_group[0], handled as hl.hl_id below + int hl_id = object_to_hl_id(arr.items[i], "hl_group item", err); + if (hl_id > 0) { + DecorSignHighlight sh = DECOR_SIGN_HIGHLIGHT_INIT; + sh.hl_id = hl_id; + sh.flags = opts->hl_eol ? kSHHlEol : 0; + sh.next = decor_indexed; + decor_indexed = decor_put_sh(sh); + decor_flags |= MT_FLAG_DECOR_HL; + } + } + } + DecorInline decor = DECOR_INLINE_INIT; if (decor_alloc || decor_indexed != DECOR_ID_INVALID || url != NULL || schar_high(hl.conceal_char)) { diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h index 664406ab6e..953e467f1e 100644 --- a/src/nvim/api/keysets_defs.h +++ b/src/nvim/api/keysets_defs.h @@ -29,7 +29,7 @@ typedef struct { Integer end_line; Integer end_row; Integer end_col; - HLGroupID hl_group; + Object hl_group; Array virt_text; String virt_text_pos; Integer virt_text_win_col; -- cgit From 34d808b73cbcb0a43636d826282193ab1ca8c148 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Thu, 16 Jan 2025 18:10:22 +0100 Subject: feat(api): combined highlights in nvim_eval_statusline() Problem: Combined highlighting was not applied to nvim_eval_statusline(), and 'statuscolumn' sign segment/numhl highlights. Solution: Add an additional `groups` element to the return value of `nvim_eval_statusline()->highlights`. This is an array of stacked highlight groups (highest priority last). Also resolve combined highlights for the 'statuscolumn' sign segment/numhl highlights. Expose/synchronize some drawline.c logic that is now mimicked in three different places. --- src/nvim/api/vim.c | 49 ++++++++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 21 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 950c70026b..c103a56032 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -28,6 +28,7 @@ #include "nvim/context.h" #include "nvim/cursor.h" #include "nvim/decoration.h" +#include "nvim/drawline.h" #include "nvim/drawscreen.h" #include "nvim/errors.h" #include "nvim/eval.h" @@ -1983,7 +1984,9 @@ Array nvim_get_mark(String name, Dict(empty) *opts, Arena *arena, Error *err) /// the "highlights" key in {opts} is true. Each element of the array is a /// |Dict| with these keys: /// - start: (number) Byte index (0-based) of first character that uses the highlight. -/// - group: (string) Name of highlight group. +/// - group: (string) Name of highlight group. May be removed in the future, use +/// `groups` instead. +/// - groups: (array) Names of stacked highlight groups (highest priority last). Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena, Error *err) FUNC_API_SINCE(8) FUNC_API_FAST { @@ -2035,6 +2038,7 @@ Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena, }); int stc_hl_id = 0; + int scl_hl_id = 0; statuscol_T statuscol = { 0 }; SignTextAttrs sattrs[SIGN_SHOW_MAX] = { 0 }; @@ -2043,23 +2047,18 @@ Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena, int cul_id = 0; int num_id = 0; linenr_T lnum = statuscol_lnum; + foldinfo_T cursorline_fi = { 0 }; decor_redraw_signs(wp, wp->w_buffer, lnum - 1, sattrs, &line_id, &cul_id, &num_id); statuscol.sattrs = sattrs; statuscol.foldinfo = fold_info(wp, lnum); - wp->w_cursorline = win_cursorline_standout(wp) ? wp->w_cursor.lnum : 0; + win_update_cursorline(wp, &cursorline_fi); + statuscol.sign_cul_id = use_cursor_line_highlight(wp, lnum) ? cul_id : 0; + scl_hl_id = use_cursor_line_highlight(wp, lnum) ? HLF_CLS : HLF_SC; - if (wp->w_p_cul) { - if (statuscol.foldinfo.fi_level != 0 && statuscol.foldinfo.fi_lines > 0) { - wp->w_cursorline = statuscol.foldinfo.fi_lnum; - } - statuscol.use_cul = lnum == wp->w_cursorline && (wp->w_p_culopt_flags & kOptCuloptFlagNumber); - } - - statuscol.sign_cul_id = statuscol.use_cul ? cul_id : 0; if (num_id) { stc_hl_id = num_id; - } else if (statuscol.use_cul) { + } else if (use_cursor_line_highlight(wp, lnum)) { stc_hl_id = HLF_CLN; } else if (wp->w_p_rnu) { stc_hl_id = (lnum < wp->w_cursor.lnum ? HLF_LNA : HLF_LNB); @@ -2112,22 +2111,19 @@ Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena, // If first character doesn't have a defined highlight, // add the default highlight at the beginning of the highlight list + const char *dfltname = get_default_stl_hl(opts->use_tabline ? NULL : wp, + opts->use_winbar, stc_hl_id); if (hltab->start == NULL || (hltab->start - buf) != 0) { - Dict hl_info = arena_dict(arena, 2); - const char *grpname = get_default_stl_hl(opts->use_tabline ? NULL : wp, - opts->use_winbar, stc_hl_id); - + Dict hl_info = arena_dict(arena, 3); PUT_C(hl_info, "start", INTEGER_OBJ(0)); - PUT_C(hl_info, "group", CSTR_AS_OBJ(grpname)); - + PUT_C(hl_info, "group", CSTR_AS_OBJ(dfltname)); + Array groups = arena_array(arena, 1); + ADD_C(groups, CSTR_AS_OBJ(dfltname)); + PUT_C(hl_info, "groups", ARRAY_OBJ(groups)); ADD_C(hl_values, DICT_OBJ(hl_info)); } for (stl_hlrec_t *sp = hltab; sp->start != NULL; sp++) { - Dict hl_info = arena_dict(arena, 2); - - PUT_C(hl_info, "start", INTEGER_OBJ(sp->start - buf)); - const char *grpname; if (sp->userhl == 0) { grpname = get_default_stl_hl(opts->use_tabline ? NULL : wp, opts->use_winbar, stc_hl_id); @@ -2137,7 +2133,18 @@ Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena, snprintf(user_group, sizeof(user_group), "User%d", sp->userhl); grpname = arena_memdupz(arena, user_group, strlen(user_group)); } + + const char *combine = sp->item == STL_SIGNCOL ? syn_id2name(scl_hl_id) + : sp->item == STL_FOLDCOL ? grpname : dfltname; + Dict hl_info = arena_dict(arena, 3); + PUT_C(hl_info, "start", INTEGER_OBJ(sp->start - buf)); PUT_C(hl_info, "group", CSTR_AS_OBJ(grpname)); + Array groups = arena_array(arena, 1 + (combine != grpname)); + if (combine != grpname) { + ADD_C(groups, CSTR_AS_OBJ(combine)); + } + ADD_C(groups, CSTR_AS_OBJ(grpname)); + PUT_C(hl_info, "groups", ARRAY_OBJ(groups)); ADD_C(hl_values, DICT_OBJ(hl_info)); } PUT_C(result, "highlights", ARRAY_OBJ(hl_values)); -- cgit From 931ee5591fa764a769946318e05062098baf7c21 Mon Sep 17 00:00:00 2001 From: georgev93 <39860568+georgev93@users.noreply.github.com> Date: Fri, 24 Jan 2025 22:57:45 -0500 Subject: feat(extmarks): virtual text can be right-aligned, truncated #31921 Problem: Right aligned virtual text can cover up buffer text if virtual text is too long Solution: An additional option for `virt_text_pos` called `eol_right_align` has been added to truncate virtual text if it would have otherwise covered up buffer text. This ensures the virtual text extends no further left than EOL. --- src/nvim/api/extmark.c | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index e66140da5a..778e857057 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -400,6 +400,15 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// (highest priority last). /// - virt_text_pos : position of virtual text. Possible values: /// - "eol": right after eol character (default). +/// - "eol_right_align": display right aligned in the window +/// unless the virtual text is longer than +/// the space available. If the virtual +/// text is too long, it is truncated to +/// fit in the window after the EOL +/// character. If the line is wrapped, the +/// virtual text is shown after the end of +/// the line rather than the previous +/// screen line. /// - "overlay": display over the specified column, without /// shifting the underlying text. /// - "right_align": display right aligned in the window. @@ -620,6 +629,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer virt_text.pos = kVPosOverlay; } else if (strequal("right_align", str.data)) { virt_text.pos = kVPosRightAlign; + } else if (strequal("eol_right_align", str.data)) { + virt_text.pos = kVPosEndOfLineRightAlign; } else if (strequal("inline", str.data)) { virt_text.pos = kVPosInline; } else { -- cgit From d84a95da7e11555376a0ce60a0d4b5fbe15892d3 Mon Sep 17 00:00:00 2001 From: glepnir Date: Mon, 27 Jan 2025 07:28:33 +0800 Subject: feat(api): nvim_get_autocmds filter by id#31549 Problem: nvim_get_autocmds cannot filter by id. Solution: Support it. --- src/nvim/api/autocmd.c | 41 +++++++++++++++++++++++------------------ src/nvim/api/keysets_defs.h | 1 + 2 files changed, 24 insertions(+), 18 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index ae349fdb95..1e19e90151 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -68,32 +68,31 @@ static int64_t next_autocmd_id = 1; /// match any combination of them. /// /// @param opts Dict 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|. -/// Cannot be used with {buffer} -/// - buffer: Buffer number or list of buffer numbers for buffer local autocommands +/// - buffer: (integer) Buffer number or list of buffer numbers for buffer local autocommands /// |autocmd-buflocal|. Cannot be used with {pattern} +/// - event: (string|table) event or events to match against |autocmd-events|. +/// - id: (integer) Autocommand ID to match. +/// - group: (string|table) the autocommand group name or id to match against. +/// - pattern: (string|table) pattern or patterns to match against |autocmd-pattern|. +/// Cannot be used with {buffer} /// @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. -/// - group_name (string): the autocommand group name. -/// - desc (string): the autocommand description. -/// - event (string): the autocommand event. -/// - 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 +/// - buffer: (integer) the buffer number. +/// - buflocal: (boolean) true if the autocommand is buffer local. +/// - 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. +/// - desc: (string) the autocommand description. +/// - event: (string) the autocommand event. +/// - id: (integer) the autocommand id (only when defined with the API). +/// - group: (integer) the autocommand group id. +/// - group_name: (string) the autocommand group name. +/// - 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, Arena *arena, Error *err) FUNC_API_SINCE(9) { - // TODO(tjdevries): Would be cool to add nvim_get_autocmds({ id = ... }) - ArrayBuilder autocmd_list = KV_INITIAL_VALUE; kvi_init(autocmd_list); char *pattern_filters[AUCMD_MAX_PATTERNS]; @@ -127,6 +126,8 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Arena *arena, Error *err) }); } + int id = (HAS_KEY(opts, get_autocmds, id)) ? (int)opts->id : -1; + if (HAS_KEY(opts, get_autocmds, event)) { check_event = true; @@ -237,6 +238,10 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Arena *arena, Error *err) continue; } + if (id != -1 && ac->id != id) { + continue; + } + // Skip autocmds from invalid groups if passed. if (group != 0 && ap->group != group) { continue; diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h index 953e467f1e..6625908cda 100644 --- a/src/nvim/api/keysets_defs.h +++ b/src/nvim/api/keysets_defs.h @@ -263,6 +263,7 @@ typedef struct { Union(Integer, String) group; Union(String, ArrayOf(String)) pattern; Union(Integer, ArrayOf(Integer)) buffer; + Integer id; } Dict(get_autocmds); typedef struct { -- cgit From c7d4a77ff9040c8b242f9d12a98472fe6217328c Mon Sep 17 00:00:00 2001 From: glepnir Date: Mon, 27 Jan 2025 08:02:33 +0800 Subject: refactor(api): add missing cast #31960 --- src/nvim/api/extmark.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 778e857057..8b31196eef 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -62,7 +62,7 @@ Integer nvim_create_namespace(String name) { handle_T id = map_get(String, int)(&namespace_ids, name); if (id > 0) { - return id; + return (Integer)id; } id = next_namespace_id++; if (name.size > 0) { -- cgit From be01b361d8ff0e8bf8f93cdf4e4f1055ecd341c2 Mon Sep 17 00:00:00 2001 From: glepnir Date: Mon, 27 Jan 2025 23:52:27 +0800 Subject: fix(float): cannot set title/footer independently #31993 Problem: `nvim_win_set_config` cannot set the title and footer independently. When only one is given, the other is reset to the default of "left". Solution: Reuse existing title/footer value if not provided. --- src/nvim/api/win_config.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 225189a3f9..1132452faf 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -895,7 +895,7 @@ static void parse_bordertext(Object bordertext, BorderTextType bordertext_type, *is_present = true; } -static bool parse_bordertext_pos(String bordertext_pos, BorderTextType bordertext_type, +static bool parse_bordertext_pos(win_T *wp, String bordertext_pos, BorderTextType bordertext_type, WinConfig *fconfig, Error *err) { AlignTextPos *align; @@ -909,7 +909,9 @@ static bool parse_bordertext_pos(String bordertext_pos, BorderTextType bordertex } if (bordertext_pos.size == 0) { - *align = kAlignLeft; + if (!wp) { + *align = kAlignLeft; + } return true; } @@ -1250,7 +1252,7 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco } // handles unset 'title_pos' same as empty string - if (!parse_bordertext_pos(config->title_pos, kBorderTextTitle, fconfig, err)) { + if (!parse_bordertext_pos(wp, config->title_pos, kBorderTextTitle, fconfig, err)) { goto fail; } } else { @@ -1277,7 +1279,7 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco } // handles unset 'footer_pos' same as empty string - if (!parse_bordertext_pos(config->footer_pos, kBorderTextFooter, fconfig, err)) { + if (!parse_bordertext_pos(wp, config->footer_pos, kBorderTextFooter, fconfig, err)) { goto fail; } } else { -- cgit From e71d2c817d1a2475551f58a98e411f6b39a5be3f Mon Sep 17 00:00:00 2001 From: dundargoc Date: Mon, 13 Jan 2025 15:48:02 +0100 Subject: docs: misc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dustin S. Co-authored-by: Ferenc Fejes Co-authored-by: Maria José Solano Co-authored-by: Yochem van Rosmalen Co-authored-by: brianhuster Co-authored-by: zeertzjq --- src/nvim/api/ui.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index db8a5d93d0..f6f32a73ed 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -537,7 +537,7 @@ static void prepare_call(RemoteUI *ui, const char *name) ui_alloc_buf(ui); } - // To optimize data transfer(especially for "grid_line"), we bundle adjacent + // 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" -- cgit From 0ce0e93bd93d1a203cbb58ab5a029a4260d66798 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 1 Feb 2025 23:43:31 -0800 Subject: refactor(autocmds): remove indirection #32291 Problem: `AucmdExecutable` adds 2 layers of indirection. Although formalizing a `union` is good practice for shared interfaces, this struct is mainly for `autocmd_register` which is internal to this module. Solution: - Store the cmd/fn fields directly on the `AutoCmd` struct. - Drop `AucmdExecutable` and related structures. --- src/nvim/api/autocmd.c | 53 +++++++++++++++++--------------------------------- 1 file changed, 18 insertions(+), 35 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 1e19e90151..d436dbb7f1 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -290,10 +290,12 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Arena *arena, Error *err) PUT_C(autocmd_info, "desc", CSTR_AS_OBJ(ac->desc)); } - if (ac->exec.type == CALLABLE_CB) { + if (ac->handler_cmd) { + PUT_C(autocmd_info, "command", CSTR_AS_OBJ(ac->handler_cmd)); + } else { PUT_C(autocmd_info, "command", STRING_OBJ(STRING_INIT)); - Callback *cb = &ac->exec.callable.cb; + Callback *cb = &ac->handler_fn; switch (cb->type) { case kCallbackLua: if (nlua_ref_is_function(cb->data.luaref)) { @@ -307,8 +309,6 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Arena *arena, Error *err) case kCallbackNone: abort(); } - } else { - PUT_C(autocmd_info, "command", CSTR_AS_OBJ(ac->exec.callable.cmd)); } PUT_C(autocmd_info, "pattern", CSTR_AS_OBJ(ap->pat)); @@ -322,23 +322,6 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Arena *arena, Error *err) PUT_C(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_C(autocmd_info, "sid", INTEGER_OBJ(ac->script_ctx.sc_sid)); - // PUT_C(autocmd_info, "lnum", INTEGER_OBJ(ac->script_ctx.sc_lnum)); - kvi_push(autocmd_list, DICT_OBJ(autocmd_info)); } } @@ -411,8 +394,8 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc { int64_t autocmd_id = -1; char *desc = NULL; - AucmdExecutable aucmd = AUCMD_EXECUTABLE_INIT; - Callback cb = CALLBACK_NONE; + char *handler_cmd = NULL; + Callback handler_fn = CALLBACK_NONE; Array event_array = unpack_string_or_array(event, "event", true, arena, err); if (ERROR_SET(err)) { @@ -437,13 +420,13 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc goto cleanup; }); - cb.type = kCallbackLua; - cb.data.luaref = callback->data.luaref; + handler_fn.type = kCallbackLua; + handler_fn.data.luaref = callback->data.luaref; callback->data.luaref = LUA_NOREF; break; case kObjectTypeString: - cb.type = kCallbackFuncref; - cb.data.funcref = string_to_cstr(callback->data.string); + handler_fn.type = kCallbackFuncref; + handler_fn.data.funcref = string_to_cstr(callback->data.string); break; default: VALIDATE_EXP(false, "callback", "Lua function or Vim function name", @@ -451,12 +434,8 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc goto cleanup; }); } - - aucmd.type = CALLABLE_CB; - aucmd.callable.cb = cb; } else if (HAS_KEY(opts, create_autocmd, command)) { - aucmd.type = CALLABLE_EX; - aucmd.callable.cmd = string_to_cstr(opts->command); + handler_cmd = string_to_cstr(opts->command); } else { VALIDATE(false, "%s", "Required: 'command' or 'callback'", { goto cleanup; @@ -496,7 +475,6 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc int retval; FOREACH_ITEM(patterns, pat, { - // See: TODO(sctx) WITH_SCRIPT_CONTEXT(channel_id, { retval = autocmd_register(autocmd_id, event_nr, @@ -506,7 +484,8 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc opts->once, opts->nested, desc, - aucmd); + handler_cmd, + &handler_fn); }); if (retval == FAIL) { @@ -517,7 +496,11 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc }); cleanup: - aucmd_exec_free(&aucmd); + if (handler_cmd) { + XFREE_CLEAR(handler_cmd); + } else { + callback_free(&handler_fn); + } return autocmd_id; } -- cgit