From 1b6442034f6a821d357fe59cd75fdae47a7f7cff Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Wed, 20 Nov 2024 21:11:20 +0100 Subject: fix(messages): more ext_messages kinds #31279 Add kinds for various commands that output a list, the 'wildmode' list, and for number prompts. --- src/nvim/eval/funcs.c | 1 + src/nvim/eval/vars.c | 1 + 2 files changed, 2 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 717280642d..f700e732a9 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -3540,6 +3540,7 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } + msg_ext_set_kind("list_cmd"); msg_start(); msg_row = Rows - 1; // for when 'cmdheight' > 1 lines_left = Rows; // avoid more prompt diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 35ad00f373..3ecb446cd6 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -1403,6 +1403,7 @@ static void list_one_var(dictitem_T *v, const char *prefix, int *first) static void list_one_var_a(const char *prefix, const char *name, const ptrdiff_t name_len, const VarType type, const char *string, int *first) { + msg_ext_set_kind("list_cmd"); // don't use msg() to avoid overwriting "v:statusmsg" msg_start(); msg_puts(prefix); -- 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/eval/vars.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 3ecb446cd6..8c8a8ac5e0 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -810,9 +810,9 @@ static char *ex_let_option(char *arg, typval_T *const tv, const bool is_const, // Find the end of the name. char *arg_end = NULL; OptIndex opt_idx; - int scope; + int opt_flags; - char *const p = (char *)find_option_var_end((const char **)&arg, &opt_idx, &scope); + char *const p = (char *)find_option_var_end((const char **)&arg, &opt_idx, &opt_flags); if (p == NULL || (endchars != NULL && vim_strchr(endchars, (uint8_t)(*skipwhite(p))) == NULL)) { emsg(_(e_letunexp)); @@ -824,7 +824,7 @@ static char *ex_let_option(char *arg, typval_T *const tv, const bool is_const, bool is_tty_opt = is_tty_option(arg); bool hidden = is_option_hidden(opt_idx); - OptVal curval = is_tty_opt ? get_tty_option(arg) : get_option_value(opt_idx, scope); + OptVal curval = is_tty_opt ? get_tty_option(arg) : get_option_value(opt_idx, opt_flags); OptVal newval = NIL_OPTVAL; if (curval.type == kOptValTypeNil) { @@ -881,7 +881,7 @@ static char *ex_let_option(char *arg, typval_T *const tv, const bool is_const, } } - const char *err = set_option_value_handle_tty(arg, opt_idx, newval, scope); + const char *err = set_option_value_handle_tty(arg, opt_idx, newval, opt_flags); arg_end = p; if (err != NULL) { emsg(_(err)); -- cgit From 66bb1e577c96d8eb63c04dcc737394b4ce2b0f5d Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 26 Nov 2024 07:53:07 +0800 Subject: vim-patch:9.1.0888: leftcol property not available in getwininfo() (#31349) Problem: leftcol property not available in getwininfo() Solution: add leftcol property property (glepnir) closes: vim/vim#16119 https://github.com/vim/vim/commit/0a850673e3d4193d55f47bcbbc0b0da5f155307d Co-authored-by: glepnir --- src/nvim/eval/window.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c index 86495f1cb6..a9d3e89177 100644 --- a/src/nvim/eval/window.c +++ b/src/nvim/eval/window.c @@ -326,6 +326,7 @@ static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) tv_dict_add_nr(dict, S_LEN("winrow"), wp->w_winrow + 1); tv_dict_add_nr(dict, S_LEN("topline"), wp->w_topline); tv_dict_add_nr(dict, S_LEN("botline"), wp->w_botline - 1); + tv_dict_add_nr(dict, S_LEN("leftcol"), wp->w_leftcol); tv_dict_add_nr(dict, S_LEN("winbar"), wp->w_winbar_height); tv_dict_add_nr(dict, S_LEN("width"), wp->w_width_inner); tv_dict_add_nr(dict, S_LEN("bufnr"), wp->w_buffer->b_fnum); -- cgit From 2d6f57b2891247f9ca0f6fb75c4b93fb2c714dc4 Mon Sep 17 00:00:00 2001 From: bfredl Date: Tue, 10 Dec 2024 14:03:44 +0100 Subject: refactor(wininfo): change wininfo from a linked list to an array "wininfo" is going to be my next victim. The main problem with wininfo is that it is "all or nothing", i e either all state about a buffer in a window is considered valid or none of it is. This needs to be fixed to address some long running grievances. For now this is just a warmup: refactor it from a linked list to a vector. --- src/nvim/eval/buffer.c | 5 ++--- src/nvim/eval/funcs.c | 7 ++++--- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c index 73bfd6db2a..b4181eb186 100644 --- a/src/nvim/eval/buffer.c +++ b/src/nvim/eval/buffer.c @@ -66,12 +66,11 @@ buf_T *find_buffer(typval_T *avar) /// If there is a window for "curbuf", make it the current window. static void find_win_for_curbuf(void) { - wininfo_T *wip; - // The b_wininfo list should have the windows that recently contained the // buffer, going over this is faster than going over all the windows. // Do check the buffer is still there. - FOR_ALL_BUF_WININFO(curbuf, wip) { + for (size_t i = 0; i < kv_size(curbuf->b_wininfo); i++) { + WinInfo *wip = kv_A(curbuf->b_wininfo, i); if (wip->wi_win != NULL && wip->wi_win->w_buffer == curbuf) { curwin = wip->wi_win; break; diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index f700e732a9..768d7664f7 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -2402,14 +2402,15 @@ static void f_getchangelist(typval_T *argvars, typval_T *rettv, EvalFuncData fpt if (buf == curwin->w_buffer) { changelistindex = curwin->w_changelistidx; } else { - wininfo_T *wip; + changelistindex = buf->b_changelistlen; - FOR_ALL_BUF_WININFO(buf, wip) { + for (size_t i = 0; i < kv_size(buf->b_wininfo); i++) { + WinInfo *wip = kv_A(buf->b_wininfo, i); if (wip->wi_win == curwin) { + changelistindex = wip->wi_changelistidx; break; } } - changelistindex = wip != NULL ? wip->wi_changelistidx : buf->b_changelistlen; } tv_list_append_number(rettv->vval.v_list, (varnumber_T)changelistindex); -- 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/eval/deprecated.c | 44 +++++++++ src/nvim/eval/deprecated.h | 10 ++ src/nvim/eval/funcs.c | 234 +++++++++++++++++---------------------------- 3 files changed, 144 insertions(+), 144 deletions(-) create mode 100644 src/nvim/eval/deprecated.c create mode 100644 src/nvim/eval/deprecated.h (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/deprecated.c b/src/nvim/eval/deprecated.c new file mode 100644 index 0000000000..67c254dac9 --- /dev/null +++ b/src/nvim/eval/deprecated.c @@ -0,0 +1,44 @@ +#include // for true + +#include "nvim/errors.h" +#include "nvim/eval/deprecated.h" +#include "nvim/eval/funcs.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds.h" +#include "nvim/gettext_defs.h" // for _ +#include "nvim/macros_defs.h" // for S_LEN +#include "nvim/message.h" // for semsg +#include "nvim/types_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/deprecated.c.generated.h" +#endif + +/// "termopen(cmd[, cwd])" function +void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + if (check_secure()) { + return; + } + + bool must_free = false; + + if (argvars[1].v_type == VAR_UNKNOWN) { + must_free = true; + argvars[1].v_type = VAR_DICT; + argvars[1].vval.v_dict = tv_dict_alloc(); + } + + if (argvars[1].v_type != VAR_DICT) { + // Wrong argument types + semsg(_(e_invarg2), "expected dictionary"); + return; + } + + tv_dict_add_bool(argvars[1].vval.v_dict, S_LEN("term"), true); + f_jobstart(argvars, rettv, fptr); + if (must_free) { + tv_dict_free(argvars[1].vval.v_dict); + } +} diff --git a/src/nvim/eval/deprecated.h b/src/nvim/eval/deprecated.h new file mode 100644 index 0000000000..b870403aa4 --- /dev/null +++ b/src/nvim/eval/deprecated.h @@ -0,0 +1,10 @@ +#pragma once + +#include // for true + +#include "nvim/eval/typval_defs.h" // IWYU pragma: keep +#include "nvim/types_defs.h" // IWYU pragma: keep + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/deprecated.h.generated.h" +#endif diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 768d7664f7..23bfcff406 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -38,6 +38,7 @@ #include "nvim/eval.h" #include "nvim/eval/buffer.h" #include "nvim/eval/decode.h" +#include "nvim/eval/deprecated.h" #include "nvim/eval/encode.h" #include "nvim/eval/executor.h" #include "nvim/eval/funcs.h" @@ -3840,8 +3841,8 @@ static const char *required_env_vars[] = { NULL }; -static dict_T *create_environment(const dictitem_T *job_env, const bool clear_env, const bool pty, - const char * const pty_term_name) +dict_T *create_environment(const dictitem_T *job_env, const bool clear_env, const bool pty, + const char * const pty_term_name) { dict_T *env = tv_dict_alloc(); @@ -3933,7 +3934,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en } /// "jobstart()" function -static void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -3942,9 +3943,9 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } + const char *cmd; bool executable = true; - char **argv = tv_to_argv(&argvars[0], NULL, &executable); - dict_T *env = NULL; + char **argv = tv_to_argv(&argvars[0], &cmd, &executable); if (!argv) { rettv->vval.v_number = executable ? 0 : -1; return; // Did error message in tv_to_argv. @@ -3961,6 +3962,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) bool detach = false; bool rpc = false; bool pty = false; + bool term = false; bool clear_env = false; bool overlapped = false; ChannelStdinMode stdin_mode = kChannelStdinPipe; @@ -3974,7 +3976,8 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) detach = tv_dict_get_number(job_opts, "detach") != 0; rpc = tv_dict_get_number(job_opts, "rpc") != 0; - pty = tv_dict_get_number(job_opts, "pty") != 0; + term = tv_dict_get_number(job_opts, "term") != 0; + pty = term || tv_dict_get_number(job_opts, "pty") != 0; clear_env = tv_dict_get_number(job_opts, "clear_env") != 0; overlapped = tv_dict_get_number(job_opts, "overlapped") != 0; @@ -3989,6 +3992,14 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } + dictitem_T *const job_term = tv_dict_find(job_opts, S_LEN("term")); + if (job_term && VAR_BOOL != job_term->di_tv.v_type) { + // Restrict "term" field to boolean, in case we want to allow buffer numbers in the future. + semsg(_(e_invarg2), "'term' must be Boolean"); + shell_free_argv(argv); + return; + } + if (pty && rpc) { semsg(_(e_invarg2), "job cannot have both 'pty' and 'rpc' options set"); shell_free_argv(argv); @@ -4032,24 +4043,87 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) uint16_t height = 0; char *term_name = NULL; - if (pty) { - width = (uint16_t)tv_dict_get_number(job_opts, "width"); - height = (uint16_t)tv_dict_get_number(job_opts, "height"); - // Legacy method, before env option existed, to specify $TERM. No longer - // documented, but still usable to avoid breaking scripts. - term_name = tv_dict_get_string(job_opts, "TERM", false); - if (!term_name) { - term_name = "ansi"; + if (term) { + if (text_locked()) { + text_locked_msg(); + shell_free_argv(argv); + return; } + if (curbuf->b_changed) { + emsg(_("jobstart(...,{term=true}) requires unmodified buffer")); + shell_free_argv(argv); + return; + } + assert(!rpc); + term_name = "xterm-256color"; + cwd = cwd ? cwd : "."; + overlapped = false; + detach = false; + stdin_mode = kChannelStdinPipe; + width = (uint16_t)MAX(0, curwin->w_width_inner - win_col_off(curwin)); + height = (uint16_t)curwin->w_height_inner; } - env = create_environment(job_env, clear_env, pty, term_name); + if (pty) { + width = width ? width : (uint16_t)tv_dict_get_number(job_opts, "width"); + height = height ? height : (uint16_t)tv_dict_get_number(job_opts, "height"); + // Deprecated TERM field is from before `env` option existed. + term_name = term_name ? term_name : tv_dict_get_string(job_opts, "TERM", false); + term_name = term_name ? term_name : "ansi"; + } + dict_T *env = create_environment(job_env, clear_env, pty, term_name); Channel *chan = channel_job_start(argv, NULL, on_stdout, on_stderr, on_exit, pty, rpc, overlapped, detach, stdin_mode, cwd, width, height, env, &rettv->vval.v_number); - if (chan) { + if (!chan) { + return; + } else if (!term) { channel_create_event(chan, NULL); + } else { + if (rettv->vval.v_number <= 0) { + return; + } + + int pid = chan->stream.pty.proc.pid; + + // "./…" => "/home/foo/…" + vim_FullName(cwd, NameBuff, sizeof(NameBuff), false); + // "/home/foo/…" => "~/…" + size_t len = home_replace(NULL, NameBuff, IObuff, sizeof(IObuff), true); + // Trim slash. + if (len != 1 && (IObuff[len - 1] == '\\' || IObuff[len - 1] == '/')) { + IObuff[len - 1] = NUL; + } + + if (len == 1 && IObuff[0] == '/') { + // Avoid ambiguity in the URI when CWD is root directory. + IObuff[1] = '.'; + IObuff[2] = NUL; + } + + // Terminal URI: "term://$CWD//$PID:$CMD" + snprintf(NameBuff, sizeof(NameBuff), "term://%s//%d:%s", IObuff, pid, cmd); + // Buffer has no terminal associated yet; unset 'swapfile' to ensure no swapfile is created. + curbuf->b_p_swf = false; + + apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, false, curbuf); + setfname(curbuf, NameBuff, NULL, true); + apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, curbuf); + + Error err = ERROR_INIT; + // Set (deprecated) buffer-local vars (prefer 'channel' buffer-local option). + dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_id"), + INTEGER_OBJ((Integer)chan->id), false, false, NULL, &err); + api_clear_error(&err); + dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_pid"), + INTEGER_OBJ(pid), false, false, NULL, &err); + api_clear_error(&err); + + channel_incref(chan); + channel_terminal_open(curbuf, chan); + channel_create_event(chan, NULL); + channel_decref(chan); } } @@ -8133,134 +8207,6 @@ static void f_taglist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) (char *)tag_pattern, (char *)fname); } -/// "termopen(cmd[, cwd])" function -static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - if (check_secure()) { - return; - } - if (text_locked()) { - text_locked_msg(); - return; - } - if (curbuf->b_changed) { - emsg(_("Can only call this function in an unmodified buffer")); - return; - } - - const char *cmd; - bool executable = true; - char **argv = tv_to_argv(&argvars[0], &cmd, &executable); - if (!argv) { - rettv->vval.v_number = executable ? 0 : -1; - return; // Did error message in tv_to_argv. - } - - if (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN) { - // Wrong argument type - semsg(_(e_invarg2), "expected dictionary"); - shell_free_argv(argv); - return; - } - - CallbackReader on_stdout = CALLBACK_READER_INIT; - CallbackReader on_stderr = CALLBACK_READER_INIT; - Callback on_exit = CALLBACK_NONE; - dict_T *job_opts = NULL; - const char *cwd = "."; - dict_T *env = NULL; - const bool pty = true; - bool clear_env = false; - dictitem_T *job_env = NULL; - - if (argvars[1].v_type == VAR_DICT) { - job_opts = argvars[1].vval.v_dict; - - const char *const new_cwd = tv_dict_get_string(job_opts, "cwd", false); - if (new_cwd && *new_cwd != NUL) { - cwd = new_cwd; - // The new cwd must be a directory. - if (!os_isdir(cwd)) { - semsg(_(e_invarg2), "expected valid directory"); - shell_free_argv(argv); - return; - } - } - - job_env = tv_dict_find(job_opts, S_LEN("env")); - if (job_env && job_env->di_tv.v_type != VAR_DICT) { - semsg(_(e_invarg2), "env"); - shell_free_argv(argv); - return; - } - - clear_env = tv_dict_get_number(job_opts, "clear_env") != 0; - - if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) { - shell_free_argv(argv); - return; - } - } - - env = create_environment(job_env, clear_env, pty, "xterm-256color"); - - const bool rpc = false; - const bool overlapped = false; - const bool detach = false; - ChannelStdinMode stdin_mode = kChannelStdinPipe; - uint16_t term_width = (uint16_t)MAX(0, curwin->w_width_inner - win_col_off(curwin)); - Channel *chan = channel_job_start(argv, NULL, on_stdout, on_stderr, on_exit, - pty, rpc, overlapped, detach, stdin_mode, - cwd, term_width, (uint16_t)curwin->w_height_inner, - env, &rettv->vval.v_number); - if (rettv->vval.v_number <= 0) { - return; - } - - int pid = chan->stream.pty.proc.pid; - - // "./…" => "/home/foo/…" - vim_FullName(cwd, NameBuff, sizeof(NameBuff), false); - // "/home/foo/…" => "~/…" - size_t len = home_replace(NULL, NameBuff, IObuff, sizeof(IObuff), true); - // Trim slash. - if (len != 1 && (IObuff[len - 1] == '\\' || IObuff[len - 1] == '/')) { - IObuff[len - 1] = NUL; - } - - if (len == 1 && IObuff[0] == '/') { - // Avoid ambiguity in the URI when CWD is root directory. - IObuff[1] = '.'; - IObuff[2] = NUL; - } - - // Terminal URI: "term://$CWD//$PID:$CMD" - snprintf(NameBuff, sizeof(NameBuff), "term://%s//%d:%s", - IObuff, pid, cmd); - // at this point the buffer has no terminal instance associated yet, so unset - // the 'swapfile' option to ensure no swap file will be created - curbuf->b_p_swf = false; - - apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, false, curbuf); - setfname(curbuf, NameBuff, NULL, true); - apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, curbuf); - - // Save the job id and pid in b:terminal_job_{id,pid} - Error err = ERROR_INIT; - // deprecated: use 'channel' buffer option - dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_id"), - INTEGER_OBJ((Integer)chan->id), false, false, NULL, &err); - api_clear_error(&err); - dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_pid"), - INTEGER_OBJ(pid), false, false, NULL, &err); - api_clear_error(&err); - - channel_incref(chan); - channel_terminal_open(curbuf, chan); - channel_create_event(chan, NULL); - channel_decref(chan); -} - /// "timer_info([timer])" function static void f_timer_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { -- 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/eval/buffer.c | 1 + src/nvim/eval/decode.c | 3 ++- src/nvim/eval/decode.h | 2 +- src/nvim/eval/deprecated.c | 2 +- src/nvim/eval/deprecated.h | 2 -- src/nvim/eval/executor.c | 1 - src/nvim/eval/funcs.c | 4 +++- src/nvim/eval/typval.c | 2 ++ src/nvim/eval/vars.c | 1 - src/nvim/eval/window.c | 1 - 10 files changed, 10 insertions(+), 9 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c index b4181eb186..41ed17598b 100644 --- a/src/nvim/eval/buffer.c +++ b/src/nvim/eval/buffer.c @@ -3,6 +3,7 @@ #include #include +#include "klib/kvec.h" #include "nvim/ascii_defs.h" #include "nvim/autocmd.h" #include "nvim/autocmd_defs.h" diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index afc2efddf6..cfcd415219 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -6,6 +6,8 @@ #include #include "klib/kvec.h" +#include "mpack/conv.h" +#include "mpack/mpack_core.h" #include "mpack/object.h" #include "nvim/ascii_defs.h" #include "nvim/charset.h" @@ -21,7 +23,6 @@ #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/types_defs.h" #include "nvim/vim_defs.h" /// Helper structure for container_struct diff --git a/src/nvim/eval/decode.h b/src/nvim/eval/decode.h index 485cc65561..af5fd3979c 100644 --- a/src/nvim/eval/decode.h +++ b/src/nvim/eval/decode.h @@ -2,7 +2,7 @@ #include // IWYU pragma: keep -#include "mpack/object.h" +#include "mpack/object.h" // IWYU pragma: keep #include "nvim/eval/typval_defs.h" // IWYU pragma: keep #include "nvim/types_defs.h" // IWYU pragma: keep diff --git a/src/nvim/eval/deprecated.c b/src/nvim/eval/deprecated.c index 67c254dac9..79874f6647 100644 --- a/src/nvim/eval/deprecated.c +++ b/src/nvim/eval/deprecated.c @@ -12,7 +12,7 @@ #include "nvim/types_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "eval/deprecated.c.generated.h" +# include "eval/deprecated.c.generated.h" // IWYU pragma: keep #endif /// "termopen(cmd[, cwd])" function diff --git a/src/nvim/eval/deprecated.h b/src/nvim/eval/deprecated.h index b870403aa4..e2e3ee436e 100644 --- a/src/nvim/eval/deprecated.h +++ b/src/nvim/eval/deprecated.h @@ -1,7 +1,5 @@ #pragma once -#include // for true - #include "nvim/eval/typval_defs.h" // IWYU pragma: keep #include "nvim/types_defs.h" // IWYU pragma: keep diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c index 5b92f217d1..691fd405e9 100644 --- a/src/nvim/eval/executor.c +++ b/src/nvim/eval/executor.c @@ -8,7 +8,6 @@ #include "nvim/eval/typval_defs.h" #include "nvim/garray.h" #include "nvim/gettext_defs.h" -#include "nvim/globals.h" #include "nvim/message.h" #include "nvim/strings.h" #include "nvim/types_defs.h" diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 23bfcff406..c52c915f76 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -13,6 +13,8 @@ #include #include "auto/config.h" +#include "klib/kvec.h" +#include "mpack/mpack_core.h" #include "mpack/object.h" #include "nvim/api/private/converter.h" #include "nvim/api/private/defs.h" @@ -38,7 +40,6 @@ #include "nvim/eval.h" #include "nvim/eval/buffer.h" #include "nvim/eval/decode.h" -#include "nvim/eval/deprecated.h" #include "nvim/eval/encode.h" #include "nvim/eval/executor.h" #include "nvim/eval/funcs.h" @@ -90,6 +91,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/msgpack_rpc/server.h" #include "nvim/normal.h" #include "nvim/normal_defs.h" diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index e7b6a0feee..cbb6b5644f 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -9,6 +9,7 @@ #include "nvim/ascii_defs.h" #include "nvim/assert_defs.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/errors.h" #include "nvim/eval.h" @@ -32,6 +33,7 @@ #include "nvim/mbyte.h" #include "nvim/mbyte_defs.h" #include "nvim/memory.h" +#include "nvim/memory_defs.h" #include "nvim/message.h" #include "nvim/os/input.h" #include "nvim/pos_defs.h" diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 8c8a8ac5e0..6504d44eb6 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -39,7 +39,6 @@ #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/option_defs.h" -#include "nvim/option_vars.h" #include "nvim/os/os.h" #include "nvim/search.h" #include "nvim/strings.h" diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c index a9d3e89177..2e2758d3bc 100644 --- a/src/nvim/eval/window.c +++ b/src/nvim/eval/window.c @@ -31,7 +31,6 @@ #include "nvim/types_defs.h" #include "nvim/vim_defs.h" #include "nvim/window.h" -#include "nvim/winfloat.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/window.c.generated.h" -- cgit From a1fa2b3a4e5c8e13423e2c656bb5d4a5fab72acc Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 23 Dec 2024 07:39:25 -0800 Subject: refactor(eval): move funcs to deprecated.c #31650 --- src/nvim/eval/deprecated.c | 114 +++++++++++++++++++++++++++++++++++++++++++++ src/nvim/eval/funcs.c | 113 +------------------------------------------- 2 files changed, 115 insertions(+), 112 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/deprecated.c b/src/nvim/eval/deprecated.c index 79874f6647..0fc16b605d 100644 --- a/src/nvim/eval/deprecated.c +++ b/src/nvim/eval/deprecated.c @@ -1,12 +1,15 @@ #include // for true +#include "nvim/channel.h" #include "nvim/errors.h" +#include "nvim/eval.h" #include "nvim/eval/deprecated.h" #include "nvim/eval/funcs.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds.h" #include "nvim/gettext_defs.h" // for _ +#include "nvim/globals.h" #include "nvim/macros_defs.h" // for S_LEN #include "nvim/message.h" // for semsg #include "nvim/types_defs.h" @@ -15,6 +18,117 @@ # include "eval/deprecated.c.generated.h" // IWYU pragma: keep #endif +/// "rpcstart()" function (DEPRECATED) +void f_rpcstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + if (check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_STRING + || (argvars[1].v_type != VAR_LIST && argvars[1].v_type != VAR_UNKNOWN)) { + // Wrong argument types + emsg(_(e_invarg)); + return; + } + + list_T *args = NULL; + int argsl = 0; + if (argvars[1].v_type == VAR_LIST) { + args = argvars[1].vval.v_list; + argsl = tv_list_len(args); + // Assert that all list items are strings + int i = 0; + TV_LIST_ITER_CONST(args, arg, { + if (TV_LIST_ITEM_TV(arg)->v_type != VAR_STRING) { + semsg(_("E5010: List item %d of the second argument is not a string"), + i); + return; + } + i++; + }); + } + + if (argvars[0].vval.v_string == NULL || argvars[0].vval.v_string[0] == NUL) { + emsg(_(e_api_spawn_failed)); + return; + } + + // Allocate extra memory for the argument vector and the NULL pointer + int argvl = argsl + 2; + char **argv = xmalloc(sizeof(char *) * (size_t)argvl); + + // Copy program name + argv[0] = xstrdup(argvars[0].vval.v_string); + + int i = 1; + // Copy arguments to the vector + if (argsl > 0) { + TV_LIST_ITER_CONST(args, arg, { + argv[i++] = xstrdup(tv_get_string(TV_LIST_ITEM_TV(arg))); + }); + } + + // The last item of argv must be NULL + argv[i] = NULL; + + Channel *chan = channel_job_start(argv, NULL, CALLBACK_READER_INIT, + CALLBACK_READER_INIT, CALLBACK_NONE, + false, true, false, false, + kChannelStdinPipe, NULL, 0, 0, NULL, + &rettv->vval.v_number); + if (chan) { + channel_create_event(chan, NULL); + } +} + +/// "rpcstop()" function +void f_rpcstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + if (check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_NUMBER) { + // Wrong argument types + emsg(_(e_invarg)); + return; + } + + // if called with a job, stop it, else closes the channel + uint64_t id = (uint64_t)argvars[0].vval.v_number; + if (find_job(id, false)) { + f_jobstop(argvars, rettv, fptr); + } else { + const char *error; + rettv->vval.v_number = + channel_close((uint64_t)argvars[0].vval.v_number, kChannelPartRpc, &error); + if (!rettv->vval.v_number) { + emsg(error); + } + } +} + +/// "last_buffer_nr()" function. +void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + int n = 0; + + FOR_ALL_BUFFERS(buf) { + if (n < buf->b_fnum) { + n = buf->b_fnum; + } + } + + rettv->vval.v_number = n; +} + /// "termopen(cmd[, cwd])" function void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index c52c915f76..a0c18a4c95 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -4130,7 +4130,7 @@ void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } /// "jobstop()" function -static void f_jobstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +void f_jobstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -4317,20 +4317,6 @@ static void f_keytrans(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) xfree(escaped); } -/// "last_buffer_nr()" function. -static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - int n = 0; - - FOR_ALL_BUFFERS(buf) { - if (n < buf->b_fnum) { - n = buf->b_fnum; - } - } - - rettv->vval.v_number = n; -} - /// "len()" function static void f_len(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -6425,103 +6411,6 @@ end: api_clear_error(&err); } -/// "rpcstart()" function (DEPRECATED) -static void f_rpcstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_STRING - || (argvars[1].v_type != VAR_LIST && argvars[1].v_type != VAR_UNKNOWN)) { - // Wrong argument types - emsg(_(e_invarg)); - return; - } - - list_T *args = NULL; - int argsl = 0; - if (argvars[1].v_type == VAR_LIST) { - args = argvars[1].vval.v_list; - argsl = tv_list_len(args); - // Assert that all list items are strings - int i = 0; - TV_LIST_ITER_CONST(args, arg, { - if (TV_LIST_ITEM_TV(arg)->v_type != VAR_STRING) { - semsg(_("E5010: List item %d of the second argument is not a string"), - i); - return; - } - i++; - }); - } - - if (argvars[0].vval.v_string == NULL || argvars[0].vval.v_string[0] == NUL) { - emsg(_(e_api_spawn_failed)); - return; - } - - // Allocate extra memory for the argument vector and the NULL pointer - int argvl = argsl + 2; - char **argv = xmalloc(sizeof(char *) * (size_t)argvl); - - // Copy program name - argv[0] = xstrdup(argvars[0].vval.v_string); - - int i = 1; - // Copy arguments to the vector - if (argsl > 0) { - TV_LIST_ITER_CONST(args, arg, { - argv[i++] = xstrdup(tv_get_string(TV_LIST_ITEM_TV(arg))); - }); - } - - // The last item of argv must be NULL - argv[i] = NULL; - - Channel *chan = channel_job_start(argv, NULL, CALLBACK_READER_INIT, - CALLBACK_READER_INIT, CALLBACK_NONE, - false, true, false, false, - kChannelStdinPipe, NULL, 0, 0, NULL, - &rettv->vval.v_number); - if (chan) { - channel_create_event(chan, NULL); - } -} - -/// "rpcstop()" function -static void f_rpcstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_NUMBER) { - // Wrong argument types - emsg(_(e_invarg)); - return; - } - - // if called with a job, stop it, else closes the channel - uint64_t id = (uint64_t)argvars[0].vval.v_number; - if (find_job(id, false)) { - f_jobstop(argvars, rettv, fptr); - } else { - const char *error; - rettv->vval.v_number = - channel_close((uint64_t)argvars[0].vval.v_number, kChannelPartRpc, &error); - if (!rettv->vval.v_number) { - emsg(error); - } - } -} - static void screenchar_adjust(ScreenGrid **grid, int *row, int *col) { // TODO(bfredl): this is a hack for legacy tests which use screenchar() -- cgit From 9d114b720514b0bde96dbdd9b6ef328d232a1589 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Wed, 1 Jan 2025 19:45:14 +0600 Subject: refactor(options): use `const` in more places (#31791) --- src/nvim/eval/vars.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 6504d44eb6..b9b5a055fb 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -872,11 +872,15 @@ static char *ex_let_option(char *arg, typval_T *const tv, const bool is_const, } else { newval = BOOLEAN_OPTVAL(TRISTATE_FROM_INT(new_n)); } - } else if (!hidden && is_string - && curval.data.string.data != NULL && newval.data.string.data != NULL) { // string - OptVal newval_old = newval; - newval = CSTR_AS_OPTVAL(concat_str(curval.data.string.data, newval.data.string.data)); - optval_free(newval_old); + } else if (!hidden && is_string) { // string + const char *curval_data = curval.data.string.data; + const char *newval_data = newval.data.string.data; + + if (curval_data != NULL && newval_data != NULL) { + OptVal newval_old = newval; + newval = CSTR_AS_OPTVAL(concat_str(curval_data, newval_data)); + optval_free(newval_old); + } } } -- cgit From efe1732c6fd802ca8fdccc1f0a26be87427f1e70 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Wed, 1 Jan 2025 18:43:16 -0600 Subject: fix(jobs): do not block UI when jobwait() doesn't block (#31803) --- src/nvim/eval/funcs.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index a0c18a4c95..fbaa6e679f 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -4177,8 +4177,6 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } - ui_busy_start(); - ui_flush(); list_T *args = argvars[0].vval.v_list; Channel **jobs = xcalloc((size_t)tv_list_len(args), sizeof(*jobs)); MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop); @@ -4215,6 +4213,13 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) before = os_hrtime(); } + // Only mark the UI as busy when jobwait() blocks + const bool busy = remaining != 0; + if (busy) { + ui_busy_start(); + ui_flush(); + } + for (i = 0; i < tv_list_len(args); i++) { if (remaining == 0) { break; // Timeout. @@ -4256,7 +4261,9 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) multiqueue_free(waiting_jobs); xfree(jobs); - ui_busy_stop(); + if (busy) { + ui_busy_stop(); + } tv_list_ref(rv); rettv->v_type = VAR_LIST; rettv->vval.v_list = rv; -- cgit From 48e2a73610ca5639408f79b3d8eebd3e5f57a327 Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Thu, 2 Jan 2025 14:51:03 +0100 Subject: feat(ui)!: emit prompt "messages" as cmdline events #31525 Problem: Prompts are emitted as messages events, where cmdline events are more appropriate. The user input is also emitted as message events in fast context, so cannot be displayed with vim.ui_attach(). Solution: Prompt for user input through cmdline prompts. --- src/nvim/eval/funcs.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index fbaa6e679f..ed3efca0d7 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -3557,10 +3557,10 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) }); // Ask for choice. - bool mouse_used; - int selected = prompt_for_number(&mouse_used); + bool mouse_used = false; + int selected = prompt_for_input(NULL, 0, false, &mouse_used); if (mouse_used) { - selected -= lines_left; + selected = tv_list_len(argvars[0].vval.v_list) - (cmdline_row - mouse_row); } rettv->vval.v_number = selected; -- cgit From d5308637bf1aac2b97fccf73a0ffdef304eaa1d6 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 3 Jan 2025 20:12:15 +0800 Subject: vim-patch:9.1.0984: exception handling can be improved Problem: exception handling can be improved Solution: add v:stacktrace and getstacktrace() closes: vim/vim#16360 https://github.com/vim/vim/commit/663d18d6102f40d14e36096ec590445e61026ed6 Co-authored-by: ichizok Co-authored-by: Naruhiko Nishino --- src/nvim/eval/typval.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index cbb6b5644f..ed1031577c 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -2633,6 +2633,30 @@ int tv_dict_add_allocated_str(dict_T *const d, const char *const key, const size return OK; } +/// Add a function entry to dictionary. +/// +/// @param[out] d Dictionary to add entry to. +/// @param[in] key Key to add. +/// @param[in] key_len Key length. +/// @param[in] fp Function to add. +/// +/// @return OK in case of success, FAIL when key already exists. +int tv_dict_add_func(dict_T *const d, const char *const key, const size_t key_len, + ufunc_T *const fp) + FUNC_ATTR_NONNULL_ARG(1, 2, 4) +{ + dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); + + item->di_tv.v_type = VAR_FUNC; + item->di_tv.vval.v_string = xstrdup(fp->uf_name); + if (tv_dict_add(d, item) == FAIL) { + tv_dict_item_free(item); + return FAIL; + } + func_ref(item->di_tv.vval.v_string); + return OK; +} + //{{{2 Operations on the whole dict /// Clear all the keys of a Dictionary. "d" remains a valid empty Dictionary. -- cgit From c5f93d7ab04f93db1470d58ca1f70e947e716c2b Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Sat, 28 Dec 2024 14:55:22 +0600 Subject: refactor(options): remove code for multitype options Problem: It was decided on Matrix chat that multitype options won't be necessary for Neovim options, and that options should only have a single canonical type. Therefore the code for supporting multitype options is unnecessary. Solution: Remove the additional code that's used to provide multitype option support. --- src/nvim/eval/vars.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index b9b5a055fb..012d23b567 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -843,11 +843,10 @@ static char *ex_let_option(char *arg, typval_T *const tv, const bool is_const, goto theend; } - // Don't assume current and new values are of the same type in order to future-proof the code for - // when an option can have multiple types. - const bool is_num = ((curval.type == kOptValTypeNumber || curval.type == kOptValTypeBoolean) - && (newval.type == kOptValTypeNumber || newval.type == kOptValTypeBoolean)); - const bool is_string = curval.type == kOptValTypeString && newval.type == kOptValTypeString; + // Current value and new value must have the same type. + assert(curval.type == newval.type); + const bool is_num = curval.type == kOptValTypeNumber || curval.type == kOptValTypeBoolean; + const bool is_string = curval.type == kOptValTypeString; if (op != NULL && *op != '=') { if (!hidden && is_num) { // number or bool @@ -1900,8 +1899,6 @@ static void getwinvar(typval_T *argvars, typval_T *rettv, int off) /// /// @return Typval converted to OptVal. Must be freed by caller. /// Returns NIL_OPTVAL for invalid option name. -/// -/// TODO(famiu): Refactor this to support multitype options. static OptVal tv_to_optval(typval_T *tv, OptIndex opt_idx, const char *option, bool *error) { OptVal value = NIL_OPTVAL; -- cgit From f8680d009741d01e137aeb2232aa7e033cd70d7b Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 16 Jan 2025 09:27:08 +0800 Subject: vim-patch:9.1.1013: Vim9: Regression caused by patch v9.1.0646 Problem: Vim9: Regression caused by patch v9.1.0646 Solution: Translate the function name before invoking it in call() (Yegappan Lakshmanan) fixes: vim/vim#16430 closes: vim/vim#16445 https://github.com/vim/vim/commit/6289f9159102e0855bedc566636b5e7ca6ced72c N/A patch: vim-patch:8.2.4176: Vim9: cannot use imported function with call() Co-authored-by: Yegappan Lakshmanan --- src/nvim/eval/funcs.c | 15 +++++++++++---- src/nvim/eval/userfunc.c | 1 - 2 files changed, 11 insertions(+), 5 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index ed3efca0d7..0d2653ef21 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -575,22 +575,29 @@ static void f_call(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (func == NULL || *func == NUL) { return; // type error, empty name or null function } + char *p = func; + char *tofree = trans_function_name(&p, false, TFN_INT|TFN_QUIET, NULL, NULL); + if (tofree == NULL) { + emsg_funcname(e_unknown_function_str, func); + return; + } + func = tofree; dict_T *selfdict = NULL; if (argvars[2].v_type != VAR_UNKNOWN) { if (tv_check_for_dict_arg(argvars, 2) == FAIL) { - if (owned) { - func_unref(func); - } - return; + goto done; } selfdict = argvars[2].vval.v_dict; } func_call(func, &argvars[1], partial, selfdict, rettv); + +done: if (owned) { func_unref(func); } + xfree(tofree); } /// "changenr()" function diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 4f1098632c..a24a3d5622 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -78,7 +78,6 @@ static funccall_T *current_funccal = NULL; // item in it is still being used. static funccall_T *previous_funccal = NULL; -static const char *e_unknown_function_str = N_("E117: Unknown function: %s"); static const char *e_funcexts = N_("E122: Function %s already exists, add ! to replace it"); static const char *e_funcdict = N_("E717: Dictionary entry already exists"); static const char *e_funcref = N_("E718: Funcref required"); -- cgit From 47a4e4239203fe96d404874bdc1ea6910f72b695 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 16 Jan 2025 09:35:52 +0800 Subject: vim-patch:9.1.1017: Vim9: Patch 9.1.1013 causes a few problems Problem: Vim9: Patch 9.1.1013 causes a few problems Solution: Translate the function name only when it is a string (Yegappan Lakshmanan) fixes: vim/vim#16453 closes: vim/vim#16450 https://github.com/vim/vim/commit/9904cbca4132f7376246a1a31305eb53e9530023 Cherry-pick call() change from patch 9.0.0345. Co-authored-by: Yegappan Lakshmanan --- src/nvim/eval/funcs.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 0d2653ef21..c125bd8893 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -548,8 +548,7 @@ static void f_byte2line(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "call(func, arglist [, dict])" function static void f_call(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - if (argvars[1].v_type != VAR_LIST) { - emsg(_(e_listreq)); + if (tv_check_for_list_arg(argvars, 1) == FAIL) { return; } if (argvars[1].vval.v_list == NULL) { @@ -575,13 +574,16 @@ static void f_call(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (func == NULL || *func == NUL) { return; // type error, empty name or null function } - char *p = func; - char *tofree = trans_function_name(&p, false, TFN_INT|TFN_QUIET, NULL, NULL); - if (tofree == NULL) { - emsg_funcname(e_unknown_function_str, func); - return; + char *tofree = NULL; + if (argvars[0].v_type == VAR_STRING) { + char *p = func; + tofree = trans_function_name(&p, false, TFN_INT|TFN_QUIET, NULL, NULL); + if (tofree == NULL) { + emsg_funcname(e_unknown_function_str, func); + return; + } + func = tofree; } - func = tofree; dict_T *selfdict = NULL; if (argvars[2].v_type != VAR_UNKNOWN) { -- cgit From a857b251d123112eda78945e163fe7fd0438ff59 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 2 Feb 2025 14:24:38 +0800 Subject: vim-patch: port some userfunc.c refactorings from Vim (#32292) Port one_function_arg() and get_function_body() from Vim. vim-patch:8.2.2865: skipping over function body fails Problem: Skipping over function body fails. Solution: Do not define the function when skipping. https://github.com/vim/vim/commit/d87c21a918d8d611750f22d68fc638bf7a79b1d5 Co-authored-by: Bram Moolenaar --- src/nvim/eval/userfunc.c | 764 +++++++++++++++++++++++++---------------------- 1 file changed, 401 insertions(+), 363 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index a24a3d5622..2e549fcf37 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -104,6 +104,48 @@ hashtab_T *func_tbl_get(void) return &func_hashtab; } +/// Get one function argument. +/// Return a pointer to after the type. +/// When something is wrong return "arg". +static char *one_function_arg(char *arg, garray_T *newargs, bool skip) +{ + char *p = arg; + + while (ASCII_ISALNUM(*p) || *p == '_') { + p++; + } + if (arg == p || isdigit((uint8_t)(*arg)) + || (p - arg == 9 && strncmp(arg, "firstline", 9) == 0) + || (p - arg == 8 && strncmp(arg, "lastline", 8) == 0)) { + if (!skip) { + semsg(_("E125: Illegal argument: %s"), arg); + } + return arg; + } + + if (newargs != NULL) { + ga_grow(newargs, 1); + uint8_t c = (uint8_t)(*p); + *p = NUL; + char *arg_copy = xstrdup(arg); + + // Check for duplicate argument name. + for (int i = 0; i < newargs->ga_len; i++) { + if (strcmp(((char **)(newargs->ga_data))[i], arg_copy) == 0) { + semsg(_("E853: Duplicate argument name: %s"), arg_copy); + xfree(arg_copy); + return arg; + } + } + ((char **)(newargs->ga_data))[newargs->ga_len] = arg_copy; + newargs->ga_len++; + + *p = (char)c; + } + + return p; +} + /// Get function arguments. static int get_function_args(char **argp, char endchar, garray_T *newargs, int *varargs, garray_T *default_args, bool skip) @@ -134,36 +176,11 @@ static int get_function_args(char **argp, char endchar, garray_T *newargs, int * mustend = true; } else { arg = p; - while (ASCII_ISALNUM(*p) || *p == '_') { - p++; - } - if (arg == p || isdigit((uint8_t)(*arg)) - || (p - arg == 9 && strncmp(arg, "firstline", 9) == 0) - || (p - arg == 8 && strncmp(arg, "lastline", 8) == 0)) { - if (!skip) { - semsg(_("E125: Illegal argument: %s"), arg); - } + p = one_function_arg(p, newargs, skip); + if (p == arg) { break; } - if (newargs != NULL) { - ga_grow(newargs, 1); - uint8_t c = (uint8_t)(*p); - *p = NUL; - arg = xstrdup(arg); - - // Check for duplicate argument name. - for (int i = 0; i < newargs->ga_len; i++) { - if (strcmp(((char **)(newargs->ga_data))[i], arg) == 0) { - semsg(_("E853: Duplicate argument name: %s"), arg); - xfree(arg); - goto err_ret; - } - } - ((char **)(newargs->ga_data))[newargs->ga_len] = arg; - newargs->ga_len++; - *p = (char)c; - } if (*skipwhite(p) == '=' && default_args != NULL) { typval_T rettv; @@ -2186,8 +2203,6 @@ char *save_function_name(char **name, bool skip, int flags, funcdict_T *fudi) return saved; } -#define MAX_FUNC_NESTING 50 - /// List functions. /// /// @param regmatch When NULL, all of them. @@ -2218,349 +2233,100 @@ static void list_functions(regmatch_T *regmatch) } } -/// ":function" -void ex_function(exarg_T *eap) +#define MAX_FUNC_NESTING 50 + +/// Read the body of a function, put every line in "newlines". +/// This stops at "endfunction". +/// "newlines" must already have been initialized. +static int get_function_body(exarg_T *eap, garray_T *newlines, char *line_arg_in, + char **line_to_free, bool show_block) { - char *theline; - char *line_to_free = NULL; bool saved_wait_return = need_wait_return; - char *arg; - char *line_arg = NULL; - garray_T newargs; - garray_T default_args; - garray_T newlines; - int varargs = false; - int flags = 0; - ufunc_T *fp; - bool overwrite = false; - funcdict_T fudi; - static int func_nr = 0; // number for nameless function - hashtab_T *ht; - bool is_heredoc = false; + char *line_arg = line_arg_in; + int indent = 2; + int nesting = 0; char *skip_until = NULL; + int ret = FAIL; + bool is_heredoc = false; char *heredoc_trimmed = NULL; - bool show_block = false; bool do_concat = true; - // ":function" without argument: list functions. - if (ends_excmd(*eap->arg)) { - if (!eap->skip) { - list_functions(NULL); + while (true) { + if (KeyTyped) { + msg_scroll = true; + saved_wait_return = false; } - eap->nextcmd = check_nextcmd(eap->arg); - return; - } + need_wait_return = false; - // ":function /pat": list functions matching pattern. - if (*eap->arg == '/') { - char *p = skip_regexp(eap->arg + 1, '/', true); - if (!eap->skip) { - regmatch_T regmatch; + char *theline; + char *p; + char *arg; - char c = *p; - *p = NUL; - regmatch.regprog = vim_regcomp(eap->arg + 1, RE_MAGIC); - *p = c; - if (regmatch.regprog != NULL) { - regmatch.rm_ic = p_ic; - list_functions(®match); - vim_regfree(regmatch.regprog); + if (line_arg != NULL) { + // Use eap->arg, split up in parts by line breaks. + theline = line_arg; + p = vim_strchr(theline, '\n'); + if (p == NULL) { + line_arg += strlen(line_arg); + } else { + *p = NUL; + line_arg = p + 1; } - } - if (*p == '/') { - p++; - } - eap->nextcmd = check_nextcmd(p); - return; - } - - // Get the function name. There are these situations: - // func function name - // "name" == func, "fudi.fd_dict" == NULL - // dict.func new dictionary entry - // "name" == NULL, "fudi.fd_dict" set, - // "fudi.fd_di" == NULL, "fudi.fd_newkey" == func - // dict.func existing dict entry with a Funcref - // "name" == func, "fudi.fd_dict" set, - // "fudi.fd_di" set, "fudi.fd_newkey" == NULL - // dict.func existing dict entry that's not a Funcref - // "name" == NULL, "fudi.fd_dict" set, - // "fudi.fd_di" set, "fudi.fd_newkey" == NULL - // s:func script-local function name - // g:func global function name, same as "func" - char *p = eap->arg; - char *name = save_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi); - int paren = (vim_strchr(p, '(') != NULL); - if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { - // Return on an invalid expression in braces, unless the expression - // evaluation has been cancelled due to an aborting error, an - // interrupt, or an exception. - if (!aborting()) { - if (fudi.fd_newkey != NULL) { - semsg(_(e_dictkey), fudi.fd_newkey); + } else { + xfree(*line_to_free); + if (eap->ea_getline == NULL) { + theline = getcmdline(':', 0, indent, do_concat); + } else { + theline = eap->ea_getline(':', eap->cookie, indent, do_concat); } - xfree(fudi.fd_newkey); - return; - } - eap->skip = true; - } - - // An error in a function call during evaluation of an expression in magic - // braces should not cause the function not to be defined. - const int saved_did_emsg = did_emsg; - did_emsg = false; - - // - // ":function func" with only function name: list function. - // If bang is given: - // - include "!" in function head - // - exclude line numbers from function body - // - if (!paren) { - if (!ends_excmd(*skipwhite(p))) { - semsg(_(e_trailing_arg), p); - goto ret_free; + *line_to_free = theline; } - eap->nextcmd = check_nextcmd(p); - if (eap->nextcmd != NULL) { - *p = NUL; + if (KeyTyped) { + lines_left = Rows - 1; } - if (!eap->skip && !got_int) { - fp = find_func(name); - if (fp != NULL) { - // Check no function was added or removed from a callback, e.g. at - // the more prompt. "fp" may then be invalid. - const int prev_ht_changed = func_hashtab.ht_changed; - - if (list_func_head(fp, !eap->forceit, eap->forceit) == OK) { - for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) { - if (FUNCLINE(fp, j) == NULL) { - continue; - } - msg_putchar('\n'); - if (!eap->forceit) { - msg_outnum(j + 1); - if (j < 9) { - msg_putchar(' '); - } - if (j < 99) { - msg_putchar(' '); - } - if (function_list_modified(prev_ht_changed)) { - break; - } - } - msg_prt_line(FUNCLINE(fp, j), false); - line_breakcheck(); // show multiple lines at a time! - } - if (!got_int) { - msg_putchar('\n'); - if (!function_list_modified(prev_ht_changed)) { - msg_puts(eap->forceit ? "endfunction" : " endfunction"); - } - } - } + if (theline == NULL) { + if (skip_until != NULL) { + semsg(_(e_missing_heredoc_end_marker_str), skip_until); } else { - emsg_funcname(N_("E123: Undefined function: %s"), name); + emsg(_("E126: Missing :endfunction")); } + goto theend; } - goto ret_free; - } - - // ":function name(arg1, arg2)" Define function. - p = skipwhite(p); - if (*p != '(') { - if (!eap->skip) { - semsg(_("E124: Missing '(': %s"), eap->arg); - goto ret_free; - } - // attempt to continue by skipping some text - if (vim_strchr(p, '(') != NULL) { - p = vim_strchr(p, '('); + if (show_block) { + assert(indent >= 0); + ui_ext_cmdline_block_append((size_t)indent, theline); } - } - p = skipwhite(p + 1); - - ga_init(&newargs, (int)sizeof(char *), 3); - ga_init(&newlines, (int)sizeof(char *), 3); - if (!eap->skip) { - // Check the name of the function. Unless it's a dictionary function - // (that we are overwriting). - if (name != NULL) { - arg = name; + // Detect line continuation: SOURCING_LNUM increased more than one. + linenr_T sourcing_lnum_off = get_sourced_lnum(eap->ea_getline, eap->cookie); + if (SOURCING_LNUM < sourcing_lnum_off) { + sourcing_lnum_off -= SOURCING_LNUM; } else { - arg = fudi.fd_newkey; + sourcing_lnum_off = 0; } - if (arg != NULL && (fudi.fd_di == NULL || !tv_is_func(fudi.fd_di->di_tv))) { - char *name_base = arg; - if ((uint8_t)(*arg) == K_SPECIAL) { - name_base = vim_strchr(arg, '_'); - if (name_base == NULL) { - name_base = arg + 3; + + if (skip_until != NULL) { + // Don't check for ":endfunc" between + // * ":append" and "." + // * ":python <dv_scope == VAR_DEF_SCOPE) { - emsg(_("E862: Cannot use g: here")); - goto ret_free; - } - } - - if (get_function_args(&p, ')', &newargs, &varargs, - &default_args, eap->skip) == FAIL) { - goto errret_2; - } - - if (KeyTyped && ui_has(kUICmdline)) { - show_block = true; - ui_ext_cmdline_block_append(0, eap->cmd); - } - - // find extra arguments "range", "dict", "abort" and "closure" - while (true) { - p = skipwhite(p); - if (strncmp(p, "range", 5) == 0) { - flags |= FC_RANGE; - p += 5; - } else if (strncmp(p, "dict", 4) == 0) { - flags |= FC_DICT; - p += 4; - } else if (strncmp(p, "abort", 5) == 0) { - flags |= FC_ABORT; - p += 5; - } else if (strncmp(p, "closure", 7) == 0) { - flags |= FC_CLOSURE; - p += 7; - if (current_funccal == NULL) { - emsg_funcname(N_("E932: Closure function should not be at top level: %s"), - name == NULL ? "" : name); - goto erret; - } - } else { - break; - } - } - - // When there is a line break use what follows for the function body. - // Makes 'exe "func Test()\n...\nendfunc"' work. - if (*p == '\n') { - line_arg = p + 1; - } else if (*p != NUL && *p != '"' && !eap->skip && !did_emsg) { - semsg(_(e_trailing_arg), p); - } - - // Read the body of the function, until ":endfunction" is found. - if (KeyTyped) { - // Check if the function already exists, don't let the user type the - // whole function before telling them it doesn't work! For a script we - // need to skip the body to be able to find what follows. - if (!eap->skip && !eap->forceit) { - if (fudi.fd_dict != NULL && fudi.fd_newkey == NULL) { - emsg(_(e_funcdict)); - } else if (name != NULL && find_func(name) != NULL) { - emsg_funcname(e_funcexts, name); - } - } - - if (!eap->skip && did_emsg) { - goto erret; - } - - if (!ui_has(kUICmdline)) { - msg_putchar('\n'); // don't overwrite the function name - } - cmdline_row = msg_row; - } - - // Save the starting line number. - linenr_T sourcing_lnum_top = SOURCING_LNUM; - - int indent = 2; - int nesting = 0; - while (true) { - if (KeyTyped) { - msg_scroll = true; - saved_wait_return = false; - } - need_wait_return = false; - - if (line_arg != NULL) { - // Use eap->arg, split up in parts by line breaks. - theline = line_arg; - p = vim_strchr(theline, '\n'); - if (p == NULL) { - line_arg += strlen(line_arg); - } else { - *p = NUL; - line_arg = p + 1; - } - } else { - xfree(line_to_free); - if (eap->ea_getline == NULL) { - theline = getcmdline(':', 0, indent, do_concat); - } else { - theline = eap->ea_getline(':', eap->cookie, indent, do_concat); - } - line_to_free = theline; - } - if (KeyTyped) { - lines_left = Rows - 1; - } - if (theline == NULL) { - if (skip_until != NULL) { - semsg(_(e_missing_heredoc_end_marker_str), skip_until); - } else { - emsg(_("E126: Missing :endfunction")); - } - goto erret; - } - if (show_block) { - assert(indent >= 0); - ui_ext_cmdline_block_append((size_t)indent, theline); - } - - // Detect line continuation: SOURCING_LNUM increased more than one. - linenr_T sourcing_lnum_off = get_sourced_lnum(eap->ea_getline, eap->cookie); - if (SOURCING_LNUM < sourcing_lnum_off) { - sourcing_lnum_off -= SOURCING_LNUM; - } else { - sourcing_lnum_off = 0; - } - - if (skip_until != NULL) { - // Don't check for ":endfunc" between - // * ":append" and "." - // * ":python <cmdlinep". eap->nextcmd = nextcmd; - if (line_to_free != NULL) { + if (*line_to_free != NULL) { xfree(*eap->cmdlinep); - *eap->cmdlinep = line_to_free; - line_to_free = NULL; + *eap->cmdlinep = *line_to_free; + *line_to_free = NULL; } } break; @@ -2705,18 +2471,18 @@ void ex_function(exarg_T *eap) } // Add the line to the function. - ga_grow(&newlines, 1 + (int)sourcing_lnum_off); + ga_grow(newlines, 1 + (int)sourcing_lnum_off); // Copy the line to newly allocated memory. get_one_sourceline() // allocates 250 bytes per line, this saves 80% on average. The cost // is an extra alloc/free. p = xstrdup(theline); - ((char **)(newlines.ga_data))[newlines.ga_len++] = p; + ((char **)(newlines->ga_data))[newlines->ga_len++] = p; // Add NULL lines for continuation lines, so that the line count is // equal to the index in the growarray. while (sourcing_lnum_off-- > 0) { - ((char **)(newlines.ga_data))[newlines.ga_len++] = NULL; + ((char **)(newlines->ga_data))[newlines->ga_len++] = NULL; } // Check for end of eap->arg. @@ -2725,9 +2491,284 @@ void ex_function(exarg_T *eap) } } - // Don't define the function when skipping commands or when an error was - // detected. - if (eap->skip || did_emsg) { + // Return OK when no error was detected. + if (!did_emsg) { + ret = OK; + } + +theend: + xfree(skip_until); + xfree(heredoc_trimmed); + need_wait_return |= saved_wait_return; + return ret; +} + +/// ":function" +void ex_function(exarg_T *eap) +{ + char *line_to_free = NULL; + char *arg; + char *line_arg = NULL; + garray_T newargs; + garray_T default_args; + garray_T newlines; + int varargs = false; + int flags = 0; + ufunc_T *fp; + bool overwrite = false; + funcdict_T fudi; + static int func_nr = 0; // number for nameless function + hashtab_T *ht; + bool show_block = false; + + // ":function" without argument: list functions. + if (ends_excmd(*eap->arg)) { + if (!eap->skip) { + list_functions(NULL); + } + eap->nextcmd = check_nextcmd(eap->arg); + return; + } + + // ":function /pat": list functions matching pattern. + if (*eap->arg == '/') { + char *p = skip_regexp(eap->arg + 1, '/', true); + if (!eap->skip) { + regmatch_T regmatch; + + char c = *p; + *p = NUL; + regmatch.regprog = vim_regcomp(eap->arg + 1, RE_MAGIC); + *p = c; + if (regmatch.regprog != NULL) { + regmatch.rm_ic = p_ic; + list_functions(®match); + vim_regfree(regmatch.regprog); + } + } + if (*p == '/') { + p++; + } + eap->nextcmd = check_nextcmd(p); + return; + } + + // Get the function name. There are these situations: + // func function name + // "name" == func, "fudi.fd_dict" == NULL + // dict.func new dictionary entry + // "name" == NULL, "fudi.fd_dict" set, + // "fudi.fd_di" == NULL, "fudi.fd_newkey" == func + // dict.func existing dict entry with a Funcref + // "name" == func, "fudi.fd_dict" set, + // "fudi.fd_di" set, "fudi.fd_newkey" == NULL + // dict.func existing dict entry that's not a Funcref + // "name" == NULL, "fudi.fd_dict" set, + // "fudi.fd_di" set, "fudi.fd_newkey" == NULL + // s:func script-local function name + // g:func global function name, same as "func" + char *p = eap->arg; + char *name = save_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi); + int paren = (vim_strchr(p, '(') != NULL); + if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { + // Return on an invalid expression in braces, unless the expression + // evaluation has been cancelled due to an aborting error, an + // interrupt, or an exception. + if (!aborting()) { + if (fudi.fd_newkey != NULL) { + semsg(_(e_dictkey), fudi.fd_newkey); + } + xfree(fudi.fd_newkey); + return; + } + eap->skip = true; + } + + // An error in a function call during evaluation of an expression in magic + // braces should not cause the function not to be defined. + const int saved_did_emsg = did_emsg; + did_emsg = false; + + // + // ":function func" with only function name: list function. + // If bang is given: + // - include "!" in function head + // - exclude line numbers from function body + // + if (!paren) { + if (!ends_excmd(*skipwhite(p))) { + semsg(_(e_trailing_arg), p); + goto ret_free; + } + eap->nextcmd = check_nextcmd(p); + if (eap->nextcmd != NULL) { + *p = NUL; + } + if (!eap->skip && !got_int) { + fp = find_func(name); + if (fp != NULL) { + // Check no function was added or removed from a callback, e.g. at + // the more prompt. "fp" may then be invalid. + const int prev_ht_changed = func_hashtab.ht_changed; + + if (list_func_head(fp, !eap->forceit, eap->forceit) == OK) { + for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) { + if (FUNCLINE(fp, j) == NULL) { + continue; + } + msg_putchar('\n'); + if (!eap->forceit) { + msg_outnum(j + 1); + if (j < 9) { + msg_putchar(' '); + } + if (j < 99) { + msg_putchar(' '); + } + if (function_list_modified(prev_ht_changed)) { + break; + } + } + msg_prt_line(FUNCLINE(fp, j), false); + line_breakcheck(); // show multiple lines at a time! + } + if (!got_int) { + msg_putchar('\n'); + if (!function_list_modified(prev_ht_changed)) { + msg_puts(eap->forceit ? "endfunction" : " endfunction"); + } + } + } + } else { + emsg_funcname(N_("E123: Undefined function: %s"), name); + } + } + goto ret_free; + } + + // ":function name(arg1, arg2)" Define function. + p = skipwhite(p); + if (*p != '(') { + if (!eap->skip) { + semsg(_("E124: Missing '(': %s"), eap->arg); + goto ret_free; + } + // attempt to continue by skipping some text + if (vim_strchr(p, '(') != NULL) { + p = vim_strchr(p, '('); + } + } + p = skipwhite(p + 1); + + ga_init(&newargs, (int)sizeof(char *), 3); + ga_init(&newlines, (int)sizeof(char *), 3); + + if (!eap->skip) { + // Check the name of the function. Unless it's a dictionary function + // (that we are overwriting). + if (name != NULL) { + arg = name; + } else { + arg = fudi.fd_newkey; + } + if (arg != NULL && (fudi.fd_di == NULL || !tv_is_func(fudi.fd_di->di_tv))) { + char *name_base = arg; + if ((uint8_t)(*arg) == K_SPECIAL) { + name_base = vim_strchr(arg, '_'); + if (name_base == NULL) { + name_base = arg + 3; + } else { + name_base++; + } + } + int i; + for (i = 0; name_base[i] != NUL && (i == 0 + ? eval_isnamec1(name_base[i]) + : eval_isnamec(name_base[i])); i++) {} + if (name_base[i] != NUL) { + emsg_funcname(e_invarg2, arg); + goto ret_free; + } + } + // Disallow using the g: dict. + if (fudi.fd_dict != NULL && fudi.fd_dict->dv_scope == VAR_DEF_SCOPE) { + emsg(_("E862: Cannot use g: here")); + goto ret_free; + } + } + + if (get_function_args(&p, ')', &newargs, &varargs, + &default_args, eap->skip) == FAIL) { + goto errret_2; + } + + if (KeyTyped && ui_has(kUICmdline)) { + show_block = true; + ui_ext_cmdline_block_append(0, eap->cmd); + } + + // find extra arguments "range", "dict", "abort" and "closure" + while (true) { + p = skipwhite(p); + if (strncmp(p, "range", 5) == 0) { + flags |= FC_RANGE; + p += 5; + } else if (strncmp(p, "dict", 4) == 0) { + flags |= FC_DICT; + p += 4; + } else if (strncmp(p, "abort", 5) == 0) { + flags |= FC_ABORT; + p += 5; + } else if (strncmp(p, "closure", 7) == 0) { + flags |= FC_CLOSURE; + p += 7; + if (current_funccal == NULL) { + emsg_funcname(N_("E932: Closure function should not be at top level: %s"), + name == NULL ? "" : name); + goto erret; + } + } else { + break; + } + } + + // When there is a line break use what follows for the function body. + // Makes 'exe "func Test()\n...\nendfunc"' work. + if (*p == '\n') { + line_arg = p + 1; + } else if (*p != NUL && *p != '"' && !eap->skip && !did_emsg) { + semsg(_(e_trailing_arg), p); + } + + // Read the body of the function, until ":endfunction" is found. + if (KeyTyped) { + // Check if the function already exists, don't let the user type the + // whole function before telling them it doesn't work! For a script we + // need to skip the body to be able to find what follows. + if (!eap->skip && !eap->forceit) { + if (fudi.fd_dict != NULL && fudi.fd_newkey == NULL) { + emsg(_(e_funcdict)); + } else if (name != NULL && find_func(name) != NULL) { + emsg_funcname(e_funcexts, name); + } + } + + if (!eap->skip && did_emsg) { + goto erret; + } + + if (!ui_has(kUICmdline)) { + msg_putchar('\n'); // don't overwrite the function name + } + cmdline_row = msg_row; + } + + // Save the starting line number. + linenr_T sourcing_lnum_top = SOURCING_LNUM; + + // Do not define the function when getting the body fails and when skipping. + if (get_function_body(eap, &newlines, line_arg, &line_to_free, show_block) == FAIL + || eap->skip) { goto erret; } @@ -2879,13 +2920,10 @@ erret: errret_2: ga_clear_strings(&newlines); ret_free: - xfree(skip_until); - xfree(heredoc_trimmed); xfree(line_to_free); xfree(fudi.fd_newkey); xfree(name); did_emsg |= saved_did_emsg; - need_wait_return |= saved_wait_return; if (show_block) { ui_ext_cmdline_block_leave(); } -- cgit From 4bdabf9b1ae52134f50a0b75dc2c73a40c0c252f Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 2 Feb 2025 17:32:51 +0800 Subject: vim-patch:9.1.1068: getchar() can't distinguish between C-I and Tab (#32295) Problem: getchar() can't distinguish between C-I and Tab. Solution: Add {opts} to pass extra flags to getchar() and getcharstr(), with "number" and "simplify" keys. related: vim/vim#10603 closes: vim/vim#16554 https://github.com/vim/vim/commit/e0a2ab397fd13a71efec85b017d5d4d62baf7f63 Cherry-pick tv_dict_has_key() from patch 8.2.4683. --- src/nvim/eval/typval.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index ed1031577c..48b2e82c0a 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -2234,6 +2234,18 @@ dictitem_T *tv_dict_find(const dict_T *const d, const char *const key, const ptr return TV_DICT_HI2DI(hi); } +/// Check if a key is present in a dictionary. +/// +/// @param[in] d Dictionary to check. +/// @param[in] key Dictionary key. +/// +/// @return whether the key is present in the dictionary. +bool tv_dict_has_key(const dict_T *const d, const char *const key) + FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return tv_dict_find(d, key, -1) != NULL; +} + /// Get a typval item from a dictionary and copy it into "rettv". /// /// @param[in] d Dictionary to check. -- cgit From 0309d3fbf0edc5ac958964f85dff76719340c4c7 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 3 Feb 2025 11:11:46 +0800 Subject: vim-patch:8.2.0825: def_function() may return pointer that was freed Problem: def_function() may return pointer that was freed. Solution: Set "fp" to NULL after freeing it. https://github.com/vim/vim/commit/a14e6975478adeddcc2161edc1ec611016aa89f3 Co-authored-by: Bram Moolenaar --- src/nvim/eval/userfunc.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 2e549fcf37..da91de4650 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -2867,6 +2867,7 @@ void ex_function(exarg_T *eap) if (tv_dict_add(fudi.fd_dict, fudi.fd_di) == FAIL) { xfree(fudi.fd_di); xfree(fp); + fp = NULL; goto erret; } } else { @@ -2887,6 +2888,7 @@ void ex_function(exarg_T *eap) hi->hi_key = UF2HIKEY(fp); } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) { xfree(fp); + fp = NULL; goto erret; } fp->uf_refcount = 1; -- cgit From cd42740245b5dd25ef9c7e116656d6da630f5db0 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 3 Feb 2025 10:15:09 +0800 Subject: vim-patch:8.2.1445: Vim9: function expanded name is cleared when sourcing again Problem: Vim9: function expanded name is cleared when sourcing a script again. Solution: Only clear the expanded name when deleting the function. (closes vim/vim#6707) https://github.com/vim/vim/commit/c4ce36d48698669f81ec90f7c9dc9ab8c362e538 Co-authored-by: Bram Moolenaar --- src/nvim/eval/userfunc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index da91de4650..5156e431db 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -891,7 +891,6 @@ static void func_clear_items(ufunc_T *fp) ga_clear_strings(&(fp->uf_args)); ga_clear_strings(&(fp->uf_def_args)); ga_clear_strings(&(fp->uf_lines)); - XFREE_CLEAR(fp->uf_name_exp); if (fp->uf_flags & FC_LUAREF) { api_free_luaref(fp->uf_luaref); @@ -930,6 +929,8 @@ static void func_free(ufunc_T *fp) if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0) { func_remove(fp); } + + XFREE_CLEAR(fp->uf_name_exp); xfree(fp); } -- cgit From 638c6b406bc41d4fed5ef282bae526888de8229a Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 3 Feb 2025 10:21:57 +0800 Subject: vim-patch:8.2.2505: Vim9: crash after defining function with invalid return type Problem: Vim9: crash after defining function with invalid return type. Solution: Clear function growarrays. Fix memory leak. https://github.com/vim/vim/commit/31842cd0772b557eb9584a13740430db29de8a51 Cherry-pick free_fp from patch 8.2.3812. Co-authored-by: Bram Moolenaar --- src/nvim/eval/userfunc.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 5156e431db..3538493159 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -2515,7 +2515,8 @@ void ex_function(exarg_T *eap) garray_T newlines; int varargs = false; int flags = 0; - ufunc_T *fp; + ufunc_T *fp = NULL; + bool free_fp = false; bool overwrite = false; funcdict_T fudi; static int func_nr = 0; // number for nameless function @@ -2888,8 +2889,7 @@ void ex_function(exarg_T *eap) hashitem_T *hi = hash_find(&func_hashtab, name); hi->hi_key = UF2HIKEY(fp); } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) { - xfree(fp); - fp = NULL; + free_fp = true; goto erret; } fp->uf_refcount = 1; @@ -2920,8 +2920,16 @@ void ex_function(exarg_T *eap) erret: ga_clear_strings(&newargs); ga_clear_strings(&default_args); + if (fp != NULL) { + ga_init(&fp->uf_args, (int)sizeof(char *), 1); + ga_init(&fp->uf_def_args, (int)sizeof(char *), 1); + } errret_2: ga_clear_strings(&newlines); + if (free_fp) { + xfree(fp); + fp = NULL; + } ret_free: xfree(line_to_free); xfree(fudi.fd_newkey); -- cgit From 82b029cbb00dbe9649824f7795c177974955d683 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 3 Feb 2025 10:45:50 +0800 Subject: vim-patch:9.0.1142: crash and/or memory leak when redefining function Problem: Crash and/or memory leak when redefining function after error. Solution: Clear pointer after making a copy. Clear arrays on failure. (closes vim/vim#11774) https://github.com/vim/vim/commit/f057171d8b562c72334fd7c15c89ff787358ce3a Co-authored-by: Bram Moolenaar --- src/nvim/eval/userfunc.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 3538493159..6eaccbae62 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -2918,13 +2918,14 @@ void ex_function(exarg_T *eap) goto ret_free; erret: - ga_clear_strings(&newargs); - ga_clear_strings(&default_args); if (fp != NULL) { + // these were set to "newargs" and "default_args", which are cleared below ga_init(&fp->uf_args, (int)sizeof(char *), 1); ga_init(&fp->uf_def_args, (int)sizeof(char *), 1); } errret_2: + ga_clear_strings(&newargs); + ga_clear_strings(&default_args); ga_clear_strings(&newlines); if (free_fp) { xfree(fp); -- cgit From b853ef770a25fcd91def6c5016d65c336897c2cc Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 2 Feb 2025 08:23:48 +0800 Subject: vim-patch:9.1.1063: too many strlen() calls in userfunc.c Problem: too many strlen() calls in userfunc.c Solution: refactor userfunc.c and remove calls to strlen(), drop set_ufunc_name() and roll it into alloc_ufunc(), check for out-of-memory condition in trans_function_name_ext() (John Marriott) closes: vim/vim#16537 https://github.com/vim/vim/commit/b32800f7c51c866dc0e87244eb4902540982309d Add missing change to call_user_func() from patch 8.1.1007. Consistently use PRIdSCID instead of PRId64 for script IDs. Co-authored-by: John Marriott --- src/nvim/eval/typval.c | 2 +- src/nvim/eval/typval_defs.h | 7 +- src/nvim/eval/userfunc.c | 184 +++++++++++++++++++++++++++----------------- 3 files changed, 120 insertions(+), 73 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 48b2e82c0a..f9cf245e50 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -2660,7 +2660,7 @@ int tv_dict_add_func(dict_T *const d, const char *const key, const size_t key_le dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); item->di_tv.v_type = VAR_FUNC; - item->di_tv.vval.v_string = xstrdup(fp->uf_name); + item->di_tv.vval.v_string = xmemdupz(fp->uf_name, fp->uf_namelen); if (tv_dict_add(d, item) == FAIL) { tv_dict_item_free(item); return FAIL; diff --git a/src/nvim/eval/typval_defs.h b/src/nvim/eval/typval_defs.h index 1ec7631c4b..90b0d4e4a9 100644 --- a/src/nvim/eval/typval_defs.h +++ b/src/nvim/eval/typval_defs.h @@ -357,9 +357,10 @@ struct ufunc { funccall_T *uf_scoped; ///< l: local variables for closure char *uf_name_exp; ///< if "uf_name[]" starts with SNR the name with ///< "" as a string, otherwise NULL - char uf_name[]; ///< Name of function (actual size equals name); - ///< can start with 123_ - ///< ( is K_SPECIAL KS_EXTRA KE_SNR) + size_t uf_namelen; ///< Length of uf_name (excluding the NUL) + char uf_name[]; ///< Name of function (actual size equals name); + ///< can start with 123_ + ///< ( is K_SPECIAL KS_EXTRA KE_SNR) }; struct partial_S { diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 6eaccbae62..6e7d921068 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -264,25 +264,47 @@ static void register_closure(ufunc_T *fp) [current_funccal->fc_ufuncs.ga_len++] = fp; } +static char lambda_name[8 + NUMBUFLEN]; +static size_t lambda_namelen = 0; + /// @return a name for a lambda. Returned in static memory. char *get_lambda_name(void) { - static char name[30]; static int lambda_no = 0; - snprintf(name, sizeof(name), "%d", ++lambda_no); - return name; + int n = snprintf(lambda_name, sizeof(lambda_name), "%d", ++lambda_no); + if (n < 1) { + lambda_namelen = 0; + } else if (n >= (int)sizeof(lambda_name)) { + lambda_namelen = sizeof(lambda_name) - 1; + } else { + lambda_namelen = (size_t)n; + } + + return lambda_name; +} + +/// Get the length of the last lambda name. +size_t get_lambda_name_len(void) +{ + return lambda_namelen; } -static void set_ufunc_name(ufunc_T *fp, char *name) +/// Allocate a "ufunc_T" for a function called "name". +static ufunc_T *alloc_ufunc(const char *name, size_t namelen) { + size_t len = offsetof(ufunc_T, uf_name) + namelen + 1; + ufunc_T *fp = xcalloc(1, len); STRCPY(fp->uf_name, name); + fp->uf_namelen = namelen; if ((uint8_t)name[0] == K_SPECIAL) { - fp->uf_name_exp = xmalloc(strlen(name) + 3); - STRCPY(fp->uf_name_exp, ""); - strcat(fp->uf_name_exp, fp->uf_name + 3); + len = namelen + 3; + fp->uf_name_exp = xmalloc(len); + snprintf(fp->uf_name_exp, len, "%s", fp->uf_name + 3); } + + return fp; } /// Parse a lambda expression and get a Funcref from "*arg". @@ -350,8 +372,9 @@ int get_lambda_tv(char **arg, typval_T *rettv, evalarg_T *evalarg) garray_T newlines; char *name = get_lambda_name(); + size_t namelen = get_lambda_name_len(); - fp = xcalloc(1, offsetof(ufunc_T, uf_name) + strlen(name) + 1); + fp = alloc_ufunc(name, namelen); pt = xcalloc(1, sizeof(partial_T)); ga_init(&newlines, (int)sizeof(char *), 1); @@ -369,7 +392,6 @@ int get_lambda_tv(char **arg, typval_T *rettv, evalarg_T *evalarg) } fp->uf_refcount = 1; - set_ufunc_name(fp, name); hash_add(&func_hashtab, UF2HIKEY(fp)); fp->uf_args = newargs; ga_init(&fp->uf_def_args, (int)sizeof(char *), 1); @@ -409,7 +431,10 @@ int get_lambda_tv(char **arg, typval_T *rettv, evalarg_T *evalarg) errret: ga_clear_strings(&newargs); - xfree(fp); + if (fp != NULL) { + xfree(fp->uf_name_exp); + xfree(fp); + } xfree(pt); if (evalarg != NULL && evalarg->eval_tofree == NULL) { evalarg->eval_tofree = tofree; @@ -627,33 +652,36 @@ static char *fname_trans_sid(const char *const name, char *const fname_buf, char int *const error) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - const int llen = eval_fname_script(name); - if (llen == 0) { + const char *script_name = name + eval_fname_script(name); + if (script_name == name) { return (char *)name; // no prefix } fname_buf[0] = (char)K_SPECIAL; fname_buf[1] = (char)KS_EXTRA; fname_buf[2] = KE_SNR; - int i = 3; - if (eval_fname_sid(name)) { // "" or "s:" + size_t fname_buflen = 3; + if (!eval_fname_sid(name)) { // "" or "s:" + fname_buf[fname_buflen] = NUL; + } else { if (current_sctx.sc_sid <= 0) { *error = FCERR_SCRIPT; } else { - snprintf(fname_buf + i, (size_t)(FLEN_FIXED + 1 - i), "%" PRId64 "_", - (int64_t)current_sctx.sc_sid); - i = (int)strlen(fname_buf); + fname_buflen += (size_t)snprintf(fname_buf + fname_buflen, + FLEN_FIXED + 1 - fname_buflen, + "%" PRIdSCID "_", + current_sctx.sc_sid); } } + size_t fnamelen = fname_buflen + strlen(script_name); char *fname; - if ((size_t)i + strlen(name + llen) < FLEN_FIXED) { - STRCPY(fname_buf + i, name + llen); + if (fnamelen < FLEN_FIXED) { + STRCPY(fname_buf + fname_buflen, script_name); fname = fname_buf; } else { - fname = xmalloc((size_t)i + strlen(name + llen) + 1); + fname = xmalloc(fnamelen + 1); *tofree = fname; - memmove(fname, fname_buf, (size_t)i); - STRCPY(fname + i, name + llen); + snprintf(fname, fnamelen + 1, "%s%s", fname_buf, script_name); } return fname; } @@ -711,20 +739,20 @@ ufunc_T *find_func(const char *name) /// Copy the function name of "fp" to buffer "buf". /// "buf" must be able to hold the function name plus three bytes. /// Takes care of script-local function names. -static void cat_func_name(char *buf, size_t buflen, ufunc_T *fp) +static int cat_func_name(char *buf, size_t bufsize, ufunc_T *fp) { int len = -1; - size_t uflen = strlen(fp->uf_name); + size_t uflen = fp->uf_namelen; assert(uflen > 0); if ((uint8_t)fp->uf_name[0] == K_SPECIAL && uflen > 3) { - len = snprintf(buf, buflen, "%s", fp->uf_name + 3); + len = snprintf(buf, bufsize, "%s", fp->uf_name + 3); } else { - len = snprintf(buf, buflen, "%s", fp->uf_name); + len = snprintf(buf, bufsize, "%s", fp->uf_name); } - (void)len; // Avoid unused warning on release builds assert(len > 0); + return (len >= (int)bufsize) ? (int)bufsize - 1 : len; } /// Add a number variable "name" to dict "dp" with value "nr". @@ -984,6 +1012,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett bool islambda = false; char numbuf[NUMBUFLEN]; char *name; + size_t namelen; typval_T *tv_to_free[MAX_FUNC_ARGS]; int tv_to_free_len = 0; proftime_T wait_start; @@ -1105,23 +1134,25 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett break; } } + + namelen = strlen(name); } else { if ((fp->uf_flags & FC_NOARGS) != 0) { // Bail out if no a: arguments used (in lambda). break; } // "..." argument a:1, a:2, etc. - snprintf(numbuf, sizeof(numbuf), "%d", ai + 1); + namelen = (size_t)snprintf(numbuf, sizeof(numbuf), "%d", ai + 1); name = numbuf; } - if (fixvar_idx < FIXVAR_CNT && strlen(name) <= VAR_SHORT_LEN) { + if (fixvar_idx < FIXVAR_CNT && namelen <= VAR_SHORT_LEN) { v = (dictitem_T *)&fc->fc_fixvar[fixvar_idx++]; v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + STRCPY(v->di_key, name); } else { - v = xmalloc(sizeof(dictitem_T) + strlen(name)); - v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC; + v = tv_dict_item_alloc_len(name, namelen); + v->di_flags |= DI_FLAGS_RO | DI_FLAGS_FIX; } - STRCPY(v->di_key, name); // Note: the values are copied directly to avoid alloc/free. // "argvars" must have VAR_FIXED for v_lock. @@ -2096,7 +2127,7 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part len = (int)(end - lv.ll_name); } - size_t sid_buf_len = 0; + size_t sid_buflen = 0; char sid_buf[20]; // Copy the function name to allocated memory. @@ -2106,15 +2137,16 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part lead = 0; // do nothing } else if (lead > 0) { lead = 3; - if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) || eval_fname_sid(*pp)) { + if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) + || eval_fname_sid(*pp)) { // It's "s:" or "". if (current_sctx.sc_sid <= 0) { emsg(_(e_usingsid)); goto theend; } - sid_buf_len = - (size_t)snprintf(sid_buf, sizeof(sid_buf), "%" PRIdSCID "_", current_sctx.sc_sid); - lead += (int)sid_buf_len; + sid_buflen = (size_t)snprintf(sid_buf, sizeof(sid_buf), "%" PRIdSCID "_", + current_sctx.sc_sid); + lead += (int)sid_buflen; } } else if (!(flags & TFN_INT) && builtin_function(lv.ll_name, (int)lv.ll_name_len)) { semsg(_("E128: Function name must start with a capital or \"s:\": %s"), @@ -2136,8 +2168,8 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part name[0] = (char)K_SPECIAL; name[1] = (char)KS_EXTRA; name[2] = KE_SNR; - if (sid_buf_len > 0) { // If it's "" - memcpy(name + 3, sid_buf, sid_buf_len); + if (sid_buflen > 0) { // If it's "" + memcpy(name + 3, sid_buf, sid_buflen); } } memmove(name + lead, lv.ll_name, (size_t)len); @@ -2173,12 +2205,12 @@ char *get_scriptlocal_funcname(char *funcname) char sid_buf[25]; // Expand s: and prefix into nr_ - snprintf(sid_buf, sizeof(sid_buf), "%" PRId64 "_", - (int64_t)current_sctx.sc_sid); + size_t sid_buflen = (size_t)snprintf(sid_buf, sizeof(sid_buf), "%" PRIdSCID "_", + current_sctx.sc_sid); const int off = *funcname == 's' ? 2 : 5; - char *newname = xmalloc(strlen(sid_buf) + strlen(funcname + off) + 1); - STRCPY(newname, sid_buf); - strcat(newname, funcname + off); + size_t newnamesize = sid_buflen + strlen(funcname + off) + 1; + char *newname = xmalloc(newnamesize); + snprintf(newname, newnamesize, "%s%s", sid_buf, funcname + off); return newname; } @@ -2250,6 +2282,7 @@ static int get_function_body(exarg_T *eap, garray_T *newlines, char *line_arg_in int ret = FAIL; bool is_heredoc = false; char *heredoc_trimmed = NULL; + size_t heredoc_trimmedlen = 0; bool do_concat = true; while (true) { @@ -2313,19 +2346,18 @@ static int get_function_body(exarg_T *eap, garray_T *newlines, char *line_arg_in // * ":let {var-name} =<< [trim] {marker}" and "{marker}" if (heredoc_trimmed == NULL || (is_heredoc && skipwhite(theline) == theline) - || strncmp(theline, heredoc_trimmed, - strlen(heredoc_trimmed)) == 0) { + || strncmp(theline, heredoc_trimmed, heredoc_trimmedlen) == 0) { if (heredoc_trimmed == NULL) { p = theline; } else if (is_heredoc) { - p = skipwhite(theline) == theline - ? theline : theline + strlen(heredoc_trimmed); + p = skipwhite(theline) == theline ? theline : theline + heredoc_trimmedlen; } else { - p = theline + strlen(heredoc_trimmed); + p = theline + heredoc_trimmedlen; } if (strcmp(p, skip_until) == 0) { XFREE_CLEAR(skip_until); XFREE_CLEAR(heredoc_trimmed); + heredoc_trimmedlen = 0; do_concat = true; is_heredoc = false; } @@ -2402,7 +2434,7 @@ static int get_function_body(exarg_T *eap, garray_T *newlines, char *line_arg_in && (!ASCII_ISALPHA(p[1]) || (p[1] == 'n' && (!ASCII_ISALPHA(p[2]) || (p[2] == 's')))))) { - skip_until = xstrdup("."); + skip_until = xmemdupz(".", 1); } // heredoc: Check for ":python <di_tv.v_type == VAR_FUNC) { @@ -2815,7 +2850,7 @@ void ex_function(exarg_T *eap) } } } else { - char numbuf[20]; + char numbuf[NUMBUFLEN]; fp = NULL; if (fudi.fd_newkey == NULL && !eap->forceit) { @@ -2835,8 +2870,8 @@ void ex_function(exarg_T *eap) // Give the function a sequential number. Can only be used with a // Funcref! xfree(name); - snprintf(numbuf, sizeof(numbuf), "%d", ++func_nr); - name = xstrdup(numbuf); + namelen = (size_t)snprintf(numbuf, sizeof(numbuf), "%d", ++func_nr); + name = xmemdupz(numbuf, namelen); } if (fp == NULL) { @@ -2860,7 +2895,10 @@ void ex_function(exarg_T *eap) } } - fp = xcalloc(1, offsetof(ufunc_T, uf_name) + strlen(name) + 1); + if (namelen == 0) { + namelen = strlen(name); + } + fp = alloc_ufunc(name, namelen); if (fudi.fd_dict != NULL) { if (fudi.fd_di == NULL) { @@ -2877,14 +2915,13 @@ void ex_function(exarg_T *eap) tv_clear(&fudi.fd_di->di_tv); } fudi.fd_di->di_tv.v_type = VAR_FUNC; - fudi.fd_di->di_tv.vval.v_string = xstrdup(name); + fudi.fd_di->di_tv.vval.v_string = xmemdupz(name, namelen); // behave like "dict" was used flags |= FC_DICT; } // insert the new function in the function list - set_ufunc_name(fp, name); if (overwrite) { hashitem_T *hi = hash_find(&func_hashtab, name); hi->hi_key = UF2HIKEY(fp); @@ -2927,6 +2964,9 @@ errret_2: ga_clear_strings(&newargs); ga_clear_strings(&default_args); ga_clear_strings(&newlines); + if (fp != NULL) { + XFREE_CLEAR(fp->uf_name_exp); + } if (free_fp) { xfree(fp); fp = NULL; @@ -3022,15 +3062,16 @@ char *get_user_func_name(expand_T *xp, int idx) return ""; // don't show dict and lambda functions } - if (strlen(fp->uf_name) + 4 >= IOSIZE) { + if (fp->uf_namelen + 4 >= IOSIZE) { return fp->uf_name; // Prevent overflow. } - cat_func_name(IObuff, IOSIZE, fp); + int len = cat_func_name(IObuff, IOSIZE, fp); if (xp->xp_context != EXPAND_USER_FUNC) { - xstrlcat(IObuff, "(", IOSIZE); + xstrlcpy(IObuff + len, "(", IOSIZE - (size_t)len); if (!fp->uf_varargs && GA_EMPTY(&fp->uf_args)) { - xstrlcat(IObuff, ")", IOSIZE); + len++; + xstrlcpy(IObuff + len, ")", IOSIZE - (size_t)len); } } return IObuff; @@ -3631,22 +3672,27 @@ bool do_return(exarg_T *eap, bool reanimate, bool is_cmd, void *rettv) char *get_return_cmd(void *rettv) { char *s = NULL; - char *tofree = NULL; + size_t slen = 0; if (rettv != NULL) { + char *tofree = NULL; tofree = s = encode_tv2echo((typval_T *)rettv, NULL); + xfree(tofree); } if (s == NULL) { s = ""; + } else { + slen = strlen(s); } xstrlcpy(IObuff, ":return ", IOSIZE); xstrlcpy(IObuff + 8, s, IOSIZE - 8); - if (strlen(s) + 8 >= IOSIZE) { + size_t IObufflen = 8 + slen; + if (slen + 8 >= IOSIZE) { STRCPY(IObuff + IOSIZE - 4, "..."); + IObufflen += 3; } - xfree(tofree); - return xstrdup(IObuff); + return xstrnsave(IObuff, IObufflen); } /// Get next function line. @@ -4089,7 +4135,8 @@ bool set_ref_in_func(char *name, ufunc_T *fp_in, int copyID) char *register_luafunc(LuaRef ref) { char *name = get_lambda_name(); - ufunc_T *fp = xcalloc(1, offsetof(ufunc_T, uf_name) + strlen(name) + 1); + size_t namelen = get_lambda_name_len(); + ufunc_T *fp = alloc_ufunc(name, namelen); fp->uf_refcount = 1; fp->uf_varargs = true; @@ -4098,7 +4145,6 @@ char *register_luafunc(LuaRef ref) fp->uf_script_ctx = current_sctx; fp->uf_luaref = ref; - STRCPY(fp->uf_name, name); hash_add(&func_hashtab, UF2HIKEY(fp)); // coverity[leaked_storage] -- cgit From 82ac8294c22a15899548a1cb408ca114a598f434 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 2 Feb 2025 16:00:04 +0800 Subject: vim-patch:9.1.1066: heap-use-after-free and stack-use-after-scope with :14verbose Problem: heap-use-after-free and stack-use-after-scope with :14verbose when using :return and :try (after 9.1.1063). Solution: Move back the vim_free(tofree) and the scope of numbuf[]. (zeertzjq) closes: vim/vim#16563 https://github.com/vim/vim/commit/2101230f4013860dbafcb0cab3f4e6bc92fb6f35 --- src/nvim/eval/userfunc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 6e7d921068..402798cafa 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -3672,12 +3672,11 @@ bool do_return(exarg_T *eap, bool reanimate, bool is_cmd, void *rettv) char *get_return_cmd(void *rettv) { char *s = NULL; + char *tofree = NULL; size_t slen = 0; if (rettv != NULL) { - char *tofree = NULL; tofree = s = encode_tv2echo((typval_T *)rettv, NULL); - xfree(tofree); } if (s == NULL) { s = ""; @@ -3688,10 +3687,11 @@ char *get_return_cmd(void *rettv) xstrlcpy(IObuff, ":return ", IOSIZE); xstrlcpy(IObuff + 8, s, IOSIZE - 8); size_t IObufflen = 8 + slen; - if (slen + 8 >= IOSIZE) { + if (IObufflen >= IOSIZE) { STRCPY(IObuff + IOSIZE - 4, "..."); - IObufflen += 3; + IObufflen = IOSIZE - 1; } + xfree(tofree); return xstrnsave(IObuff, IObufflen); } -- cgit From db7db783a2d634d5589ebe12605e3989cb30650c Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 3 Feb 2025 10:49:06 +0800 Subject: vim-patch:9.1.1071: args missing after failing to redefine a function Problem: Arguments of a function are missing after failing to redefine it (after 8.2.2505), and heap-use-after-free with script-local function (after 9.1.1063). Solution: Don't clear arguments or free uf_name_exp when failing to redefine an existing function (zeertzjq) closes: vim/vim#16567 https://github.com/vim/vim/commit/04d2a3fdc051d6a419dc0ea4de7a9640cefccd31 --- src/nvim/eval/userfunc.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 402798cafa..8022b37f6b 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -2825,11 +2825,11 @@ void ex_function(exarg_T *eap) && (fp->uf_script_ctx.sc_sid != current_sctx.sc_sid || fp->uf_script_ctx.sc_seq == current_sctx.sc_seq)) { emsg_funcname(e_funcexts, name); - goto erret; + goto errret_keep; } if (fp->uf_calls > 0) { emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"), name); - goto erret; + goto errret_keep; } if (fp->uf_refcount > 1) { // This function is referenced somewhere, don't redefine it but @@ -2961,9 +2961,6 @@ erret: ga_init(&fp->uf_def_args, (int)sizeof(char *), 1); } errret_2: - ga_clear_strings(&newargs); - ga_clear_strings(&default_args); - ga_clear_strings(&newlines); if (fp != NULL) { XFREE_CLEAR(fp->uf_name_exp); } @@ -2971,6 +2968,10 @@ errret_2: xfree(fp); fp = NULL; } +errret_keep: + ga_clear_strings(&newargs); + ga_clear_strings(&default_args); + ga_clear_strings(&newlines); ret_free: xfree(line_to_free); xfree(fudi.fd_newkey); -- cgit From 0c8890e7a771ca26c75a767b9851aaf7bf2c0f90 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 3 Feb 2025 12:49:33 +0800 Subject: vim-patch:8.2.4948: cannot use Perl heredoc in nested :def function (#32311) Problem: Cannot use Perl heredoc in nested :def function. (Virginia Senioria) Solution: Only concatenate heredoc lines when not in a nested function. (closes vim/vim#10415) https://github.com/vim/vim/commit/d881d1598467d88808bafd2fa86982ebbca7dcc1 Co-authored-by: Bram Moolenaar --- src/nvim/eval/userfunc.c | 58 +++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 28 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 8022b37f6b..225880d731 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -2471,36 +2471,38 @@ static int get_function_body(exarg_T *eap, garray_T *newlines, char *line_arg_in is_heredoc = true; } - // Check for ":let v =<< [trim] EOF" - // and ":let [a, b] =<< [trim] EOF" - arg = p; - if (checkforcmd(&arg, "let", 2)) { - int var_count = 0; - int semicolon = 0; - arg = (char *)skip_var_list(arg, &var_count, &semicolon, true); - if (arg != NULL) { - arg = skipwhite(arg); - } - if (arg != NULL && strncmp(arg, "=<<", 3) == 0) { - p = skipwhite(arg + 3); - while (true) { - if (strncmp(p, "trim", 4) == 0) { - // Ignore leading white space. - p = skipwhite(p + 4); - heredoc_trimmedlen = (size_t)(skipwhite(theline) - theline); - heredoc_trimmed = xmemdupz(theline, heredoc_trimmedlen); - continue; - } - if (strncmp(p, "eval", 4) == 0) { - // Ignore leading white space. - p = skipwhite(p + 4); - continue; + if (!is_heredoc) { + // Check for ":let v =<< [trim] EOF" + // and ":let [a, b] =<< [trim] EOF" + arg = p; + if (checkforcmd(&arg, "let", 2)) { + int var_count = 0; + int semicolon = 0; + arg = (char *)skip_var_list(arg, &var_count, &semicolon, true); + if (arg != NULL) { + arg = skipwhite(arg); + } + if (arg != NULL && strncmp(arg, "=<<", 3) == 0) { + p = skipwhite(arg + 3); + while (true) { + if (strncmp(p, "trim", 4) == 0) { + // Ignore leading white space. + p = skipwhite(p + 4); + heredoc_trimmedlen = (size_t)(skipwhite(theline) - theline); + heredoc_trimmed = xmemdupz(theline, heredoc_trimmedlen); + continue; + } + if (strncmp(p, "eval", 4) == 0) { + // Ignore leading white space. + p = skipwhite(p + 4); + continue; + } + break; } - break; + skip_until = xmemdupz(p, (size_t)(skiptowhite(p) - p)); + do_concat = false; + is_heredoc = true; } - skip_until = xmemdupz(p, (size_t)(skiptowhite(p) - p)); - do_concat = false; - is_heredoc = true; } } } -- cgit From 3a2893015759396e5345a1a85e0427705e0570b4 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 3 Feb 2025 13:20:48 +0800 Subject: vim-patch:9.0.1391: "clear" macros are not always used (#32312) Problem: "clear" macros are not always used. Solution: Use ALLOC_ONE, VIM_CLEAR, CLEAR_POINTER and CLEAR_FIELD in more places. (Yegappan Lakshmanan, closes vim/vim#12104) https://github.com/vim/vim/commit/960dcbd098c761dd623bec9492d5391ff6e8dceb Co-authored-by: Yegappan Lakshmanan --- src/nvim/eval/userfunc.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 225880d731..68bbf76043 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -2908,8 +2908,7 @@ void ex_function(exarg_T *eap) fudi.fd_di = tv_dict_item_alloc(fudi.fd_newkey); if (tv_dict_add(fudi.fd_dict, fudi.fd_di) == FAIL) { xfree(fudi.fd_di); - xfree(fp); - fp = NULL; + XFREE_CLEAR(fp); goto erret; } } else { @@ -2967,8 +2966,7 @@ errret_2: XFREE_CLEAR(fp->uf_name_exp); } if (free_fp) { - xfree(fp); - fp = NULL; + XFREE_CLEAR(fp); } errret_keep: ga_clear_strings(&newargs); -- cgit From 5a7cf85c2c7e452563a4bce9195e9a3426ca3050 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 4 Feb 2025 07:44:41 +0800 Subject: vim-patch:9.1.1074: Strange error when heredoc marker starts with "trim" (#32317) Problem: Strange error when heredoc marker starts with "trim". Solution: Check for whitespace after "trim" or "eval" (zeertzjq) For :python3 etc., a heredoc marker that starts with a lower-case letter is valid, and when it starts with "trim" it works in a script but not in a function, and this PR makes it works in a function. For :let, a heredoc marker that starts with a lower-case letter is not valid, but when it starts with "trim" or "eval" the error can be a bit confusing in a function, and this PR make it less confusing. closes: vim/vim#16574 https://github.com/vim/vim/commit/449c2e5454735fe1cfc8c21b2c6880d6bdf4cd6e --- src/nvim/eval/userfunc.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 68bbf76043..f386dd28b9 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -2456,7 +2456,8 @@ static int get_function_body(exarg_T *eap, garray_T *newlines, char *line_arg_in && (!ASCII_ISALPHA(p[2]) || p[2] == 's')))) { // ":python <<" continues until a dot, like ":append" p = skipwhite(arg + 2); - if (strncmp(p, "trim", 4) == 0) { + if (strncmp(p, "trim", 4) == 0 + && (p[4] == NUL || ascii_iswhite(p[4]))) { // Ignore leading white space. p = skipwhite(p + 4); heredoc_trimmedlen = (size_t)(skipwhite(theline) - theline); @@ -2484,21 +2485,27 @@ static int get_function_body(exarg_T *eap, garray_T *newlines, char *line_arg_in } if (arg != NULL && strncmp(arg, "=<<", 3) == 0) { p = skipwhite(arg + 3); + bool has_trim = false; while (true) { - if (strncmp(p, "trim", 4) == 0) { + if (strncmp(p, "trim", 4) == 0 + && (p[4] == NUL || ascii_iswhite(p[4]))) { // Ignore leading white space. p = skipwhite(p + 4); - heredoc_trimmedlen = (size_t)(skipwhite(theline) - theline); - heredoc_trimmed = xmemdupz(theline, heredoc_trimmedlen); + has_trim = true; continue; } - if (strncmp(p, "eval", 4) == 0) { + if (strncmp(p, "eval", 4) == 0 + && (p[4] == NUL || ascii_iswhite(p[4]))) { // Ignore leading white space. p = skipwhite(p + 4); continue; } break; } + if (has_trim) { + heredoc_trimmedlen = (size_t)(skipwhite(theline) - theline); + heredoc_trimmed = xmemdupz(theline, heredoc_trimmedlen); + } skip_until = xmemdupz(p, (size_t)(skiptowhite(p) - p)); do_concat = false; is_heredoc = true; -- cgit