aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/eval
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/eval')
-rw-r--r--src/nvim/eval/buffer.c6
-rw-r--r--src/nvim/eval/decode.c3
-rw-r--r--src/nvim/eval/decode.h2
-rw-r--r--src/nvim/eval/deprecated.c158
-rw-r--r--src/nvim/eval/deprecated.h8
-rw-r--r--src/nvim/eval/executor.c1
-rw-r--r--src/nvim/eval/funcs.c397
-rw-r--r--src/nvim/eval/typval.c38
-rw-r--r--src/nvim/eval/typval_defs.h7
-rw-r--r--src/nvim/eval/userfunc.c793
-rw-r--r--src/nvim/eval/vars.c35
-rw-r--r--src/nvim/eval/window.c2
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;
} 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;