diff options
| author | Josh Rahm <joshuarahm@gmail.com> | 2025-02-05 23:09:29 +0000 |
|---|---|---|
| committer | Josh Rahm <joshuarahm@gmail.com> | 2025-02-05 23:09:29 +0000 |
| commit | d5f194ce780c95821a855aca3c19426576d28ae0 (patch) | |
| tree | d45f461b19f9118ad2bb1f440a7a08973ad18832 /src/nvim/eval | |
| parent | c5d770d311841ea5230426cc4c868e8db27300a8 (diff) | |
| parent | 44740e561fc93afe3ebecfd3618bda2d2abeafb0 (diff) | |
| download | rneovim-rahm.tar.gz rneovim-rahm.tar.bz2 rneovim-rahm.zip | |
Diffstat (limited to 'src/nvim/eval')
| -rw-r--r-- | src/nvim/eval/buffer.c | 6 | ||||
| -rw-r--r-- | src/nvim/eval/decode.c | 3 | ||||
| -rw-r--r-- | src/nvim/eval/decode.h | 2 | ||||
| -rw-r--r-- | src/nvim/eval/deprecated.c | 158 | ||||
| -rw-r--r-- | src/nvim/eval/deprecated.h | 8 | ||||
| -rw-r--r-- | src/nvim/eval/executor.c | 1 | ||||
| -rw-r--r-- | src/nvim/eval/funcs.c | 397 | ||||
| -rw-r--r-- | src/nvim/eval/typval.c | 38 | ||||
| -rw-r--r-- | src/nvim/eval/typval_defs.h | 7 | ||||
| -rw-r--r-- | src/nvim/eval/userfunc.c | 793 | ||||
| -rw-r--r-- | src/nvim/eval/vars.c | 35 | ||||
| -rw-r--r-- | src/nvim/eval/window.c | 2 |
12 files changed, 807 insertions, 643 deletions
diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c index 73bfd6db2a..41ed17598b 100644 --- a/src/nvim/eval/buffer.c +++ b/src/nvim/eval/buffer.c @@ -3,6 +3,7 @@ #include <stdbool.h> #include <string.h> +#include "klib/kvec.h" #include "nvim/ascii_defs.h" #include "nvim/autocmd.h" #include "nvim/autocmd_defs.h" @@ -66,12 +67,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/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 <string.h> #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 <stddef.h> // 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 new file mode 100644 index 0000000000..0fc16b605d --- /dev/null +++ b/src/nvim/eval/deprecated.c @@ -0,0 +1,158 @@ +#include <stdbool.h> // 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" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# 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) +{ + 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..e2e3ee436e --- /dev/null +++ b/src/nvim/eval/deprecated.h @@ -0,0 +1,8 @@ +#pragma once + +#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/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 9e9e36d3c5..8db50a53b3 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -13,6 +13,8 @@ #include <uv.h> #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" @@ -89,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" @@ -545,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) { @@ -572,22 +574,32 @@ 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 *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; + } 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 @@ -2402,14 +2414,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); @@ -3541,6 +3554,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 @@ -3553,10 +3567,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; @@ -3839,8 +3853,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(); @@ -3932,7 +3946,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; @@ -3941,9 +3955,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. @@ -3960,6 +3974,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; @@ -3973,7 +3988,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; @@ -3988,6 +4004,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); @@ -4031,29 +4055,92 @@ 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); } } /// "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; @@ -4100,8 +4187,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); @@ -4138,6 +4223,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. @@ -4179,7 +4271,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; @@ -4240,20 +4334,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) { @@ -6348,103 +6428,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() @@ -8132,134 +8115,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) { diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index e7b6a0feee..f9cf245e50 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" @@ -2232,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. @@ -2631,6 +2645,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 = xmemdupz(fp->uf_name, fp->uf_namelen); + 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. 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 ///< "<SNR>" as a string, otherwise NULL - char uf_name[]; ///< Name of function (actual size equals name); - ///< can start with <SNR>123_ - ///< (<SNR> 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 <SNR>123_ + ///< (<SNR> 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 0050a77fe3..84ee27493c 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"); @@ -105,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) @@ -135,36 +176,11 @@ static int get_function_args(char **argp, char endchar, garray_T *newargs, int * mustend = true; |