mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
unify jobstart, termopen, and system interfaces
For any of these functions, if {cmd} is a string, execute "&shell &shellcmdflag '{cmd}'", or simply {cmd} if it's a list. In termopen(), if the 'name' option is not supplied, try to guess using '{cmd}' (string) or {cmd}[0] (list). Simplify ex_terminal to use the string form of termopen(). termopen: get name from argument Convert list_to_argv to tv_to_argv. Helped-by: Björn Linse <@bfredl> Helped-by: oni-link <knil.ino@gmail.com> Helped-by: Thiago de Arruda <@tarruda>
This commit is contained in:
parent
74aef89720
commit
1eb3396922
@ -2013,8 +2013,8 @@ synIDattr( {synID}, {what} [, {mode}])
|
||||
synIDtrans( {synID}) Number translated syntax ID of {synID}
|
||||
synconcealed( {lnum}, {col}) List info about concealing
|
||||
synstack( {lnum}, {col}) List stack of syntax IDs at {lnum} and {col}
|
||||
system( {expr} [, {input}]) String output of shell command/filter {expr}
|
||||
systemlist( {expr} [, {input}]) List output of shell command/filter {expr}
|
||||
system( {cmd} [, {input}]) String output of shell command/filter {cmd}
|
||||
systemlist( {cmd} [, {input}]) List output of shell command/filter {cmd}
|
||||
tabpagebuflist( [{arg}]) List list of buffer numbers in tab page
|
||||
tabpagenr( [{arg}]) Number number of current or last tab page
|
||||
tabpagewinnr( {tabarg}[, {arg}])
|
||||
@ -4031,9 +4031,12 @@ jobsend({job}, {data}) {Nvim} *jobsend()*
|
||||
:call jobsend(j, ["abc", "123\n456", ""])
|
||||
< will send "abc<NL>123<NUL>456<NL>".
|
||||
|
||||
jobstart({argv}[, {opts}]) {Nvim} *jobstart()*
|
||||
Spawns {argv}(list) as a job. If passed, {opts} must be a
|
||||
dictionary with any of the following keys:
|
||||
jobstart({cmd}[, {opts}]) {Nvim} *jobstart()*
|
||||
Spawns {cmd} as a job. If {cmd} is a |List|, it will be run
|
||||
directly. If {cmd} is a |String|, it will be equivalent to >
|
||||
:call jobstart([&shell, &shellcmdflag, '{cmd}'])
|
||||
< If passed, {opts} must be a dictionary with any of the
|
||||
following keys:
|
||||
- on_stdout: stdout event handler
|
||||
- on_stderr: stderr event handler
|
||||
- on_exit: exit event handler
|
||||
@ -4052,7 +4055,8 @@ jobstart({argv}[, {opts}]) {Nvim} *jobstart()*
|
||||
- The job ID on success, which is used by |jobsend()| and
|
||||
|jobstop()|
|
||||
- 0 when the job table is full or on invalid arguments
|
||||
- -1 when {argv}[0] is not executable
|
||||
- -1 when {cmd}[0] is not executable. Will never fail if
|
||||
{cmd} is a string unless 'shell' is not executable.
|
||||
See |job-control| for more information.
|
||||
|
||||
jobstop({job}) {Nvim} *jobstop()*
|
||||
@ -6151,9 +6155,10 @@ synstack({lnum}, {col}) *synstack()*
|
||||
character in a line and the first column in an empty line are
|
||||
valid positions.
|
||||
|
||||
system({expr} [, {input}]) *system()* *E677*
|
||||
Get the output of the shell command {expr} as a string. See
|
||||
|systemlist()| to get the output as a List.
|
||||
system({cmd} [, {input}]) *system()* *E677*
|
||||
Get the output of the shell command {cmd} as a |String|. {cmd}
|
||||
will be run the same as in |jobstart()|. See |systemlist()|
|
||||
to get the output as a |List|.
|
||||
|
||||
When {input} is given and is a string this string is written
|
||||
to a file and passed as stdin to the command. The string is
|
||||
@ -6167,7 +6172,7 @@ system({expr} [, {input}]) *system()* *E677*
|
||||
|
||||
Note: Use |shellescape()| or |::S| with |expand()| or
|
||||
|fnamemodify()| to escape special characters in a command
|
||||
argument. Newlines in {expr} may cause the command to fail.
|
||||
argument. Newlines in {cmd} may cause the command to fail.
|
||||
The characters in 'shellquote' and 'shellxquote' may also
|
||||
cause trouble.
|
||||
This is not to be used for interactive commands.
|
||||
@ -6182,11 +6187,8 @@ system({expr} [, {input}]) *system()* *E677*
|
||||
To avoid the string being truncated at a NUL, all NUL
|
||||
characters are replaced with SOH (0x01).
|
||||
|
||||
The command executed is constructed using several options:
|
||||
'shell' 'shellcmdflag' 'shellxquote' {expr} 'shellredir' {tmp} 'shellxquote'
|
||||
({tmp} is an automatically generated file name).
|
||||
For Unix braces are put around {expr} to allow for
|
||||
concatenated commands.
|
||||
The command executed is constructed using several options when
|
||||
{cmd} is a string: 'shell' 'shellcmdflag' {cmd}
|
||||
|
||||
The command will be executed in "cooked" mode, so that a
|
||||
CTRL-C will interrupt the command (on Unix at least).
|
||||
@ -6201,7 +6203,7 @@ system({expr} [, {input}]) *system()* *E677*
|
||||
Use |:checktime| to force a check.
|
||||
|
||||
|
||||
systemlist({expr} [, {input} [, {keepempty}]]) *systemlist()*
|
||||
systemlist({cmd} [, {input} [, {keepempty}]]) *systemlist()*
|
||||
Same as |system()|, but returns a |List| with lines (parts of
|
||||
output separated by NL) with NULs transformed into NLs. Output
|
||||
is the same as |readfile()| will output with {binary} argument
|
||||
@ -6299,16 +6301,18 @@ tempname() *tempname()* *temp-file-name*
|
||||
For MS-Windows forward slashes are used when the 'shellslash'
|
||||
option is set or when 'shellcmdflag' starts with '-'.
|
||||
|
||||
termopen({argv}[, {opts}]) {Nvim} *termopen()*
|
||||
Spawns {argv}(list) in a new pseudo-terminal session connected
|
||||
to the current buffer. This function fails if the current
|
||||
buffer is modified (all buffer contents are destroyed).
|
||||
termopen({cmd}[, {opts}]) {Nvim} *termopen()*
|
||||
Spawns {cmd} in a new pseudo-terminal session connected
|
||||
to the current buffer. {cmd} is the same as the one passed to
|
||||
|jobstart()|. This function fails if the current buffer is
|
||||
modified (all buffer contents are destroyed).
|
||||
|
||||
The {opts} dict is similar to the one passed to |jobstart()|,
|
||||
but the `pty`, `width`, `height`, and `TERM` fields are
|
||||
ignored: `height`/`width` are taken from the current window
|
||||
and `$TERM` is set to "xterm-256color". An additional option,
|
||||
`name`, can be used to give the terminal a specific name.
|
||||
and `$TERM` is set to "xterm-256color", and it may have a
|
||||
`name` field. `name`, if present, sets the buffer's name to
|
||||
"term://{cwd}/{pid}:{name}".
|
||||
Returns the same values as |jobstart()|.
|
||||
|
||||
See |nvim-terminal-emulator| for more information.
|
||||
|
@ -229,7 +229,7 @@ g8 Print the hex values of the bytes used in the
|
||||
|
||||
:enew
|
||||
:call termopen([&sh, &shcf, '{cmd}'],
|
||||
\{'name':'{cmd}'})
|
||||
\{'name':'{cmd}'})
|
||||
:startinsert
|
||||
<
|
||||
If no {cmd} is given, 'shellcmdflag' will not be sent
|
||||
|
@ -10779,35 +10779,54 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv)
|
||||
rettv->vval.v_number = 1;
|
||||
}
|
||||
|
||||
static char **list_to_argv(list_T *args)
|
||||
static char **tv_to_argv(typval_T *cmd_tv, char **cmd)
|
||||
{
|
||||
for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) {
|
||||
if (arg->li_tv.v_type != VAR_STRING) {
|
||||
EMSG(_(e_invarg));
|
||||
return NULL;
|
||||
if (cmd_tv->v_type == VAR_STRING) {
|
||||
char *cmd_str = (char *)get_tv_string(cmd_tv);
|
||||
if (cmd) {
|
||||
*cmd = cmd_str;
|
||||
}
|
||||
return shell_build_argv(cmd_str, NULL);
|
||||
}
|
||||
|
||||
int argc = args->lv_len;
|
||||
if (cmd_tv->v_type != VAR_LIST) {
|
||||
EMSG2(_(e_invarg2), "expected String or List");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
list_T *argl = cmd_tv->vval.v_list;
|
||||
int argc = argl->lv_len;
|
||||
if (!argc) {
|
||||
EMSG(_("Argument vector must have at least one item"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
assert(args->lv_first);
|
||||
assert(argl->lv_first);
|
||||
|
||||
const char_u *exe = get_tv_string(&args->lv_first->li_tv);
|
||||
if (!os_can_exe(exe, NULL)) {
|
||||
const char_u *exe = get_tv_string_chk(&argl->lv_first->li_tv);
|
||||
if (!exe || !os_can_exe(exe, NULL)) {
|
||||
// String is not executable
|
||||
EMSG2(e_jobexe, exe);
|
||||
if (exe) {
|
||||
EMSG2(e_jobexe, exe);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (cmd) {
|
||||
*cmd = (char *)exe;
|
||||
}
|
||||
|
||||
// Build the argument vector
|
||||
int i = 0;
|
||||
char **argv = xcalloc(argc + 1, sizeof(char *));
|
||||
for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) {
|
||||
argv[i++] = xstrdup((char *) get_tv_string(&arg->li_tv));
|
||||
for (listitem_T *arg = argl->lv_first; arg != NULL; arg = arg->li_next) {
|
||||
char *a = (char *)get_tv_string_chk(&arg->li_tv);
|
||||
if (!a) {
|
||||
// Did emsg in get_tv_string; just deallocate argv.
|
||||
shell_free_argv(argv);
|
||||
return NULL;
|
||||
}
|
||||
argv[i++] = xstrdup(a);
|
||||
}
|
||||
|
||||
return argv;
|
||||
@ -10823,16 +10842,16 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv)
|
||||
return;
|
||||
}
|
||||
|
||||
if (argvars[0].v_type != VAR_LIST
|
||||
|| (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN)) {
|
||||
// Wrong argument types
|
||||
EMSG(_(e_invarg));
|
||||
return;
|
||||
char **argv = tv_to_argv(&argvars[0], NULL);
|
||||
if (!argv) {
|
||||
return; // Did error message in tv_to_argv.
|
||||
}
|
||||
|
||||
char **argv = list_to_argv(argvars[0].vval.v_list);
|
||||
if (!argv) {
|
||||
return; // Did error message in list_to_argv.
|
||||
if (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN) {
|
||||
// Wrong argument types
|
||||
EMSG2(_(e_invarg2), "expected dictionary");
|
||||
shell_free_argv(argv);
|
||||
return;
|
||||
}
|
||||
|
||||
dict_T *job_opts = NULL;
|
||||
@ -10840,6 +10859,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv)
|
||||
if (argvars[1].v_type == VAR_DICT) {
|
||||
job_opts = argvars[1].vval.v_dict;
|
||||
if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
|
||||
shell_free_argv(argv);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -14935,12 +14955,16 @@ static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv,
|
||||
}
|
||||
|
||||
// get shell command to execute
|
||||
const char *cmd = (char *) get_tv_string(&argvars[0]);
|
||||
char **argv = tv_to_argv(&argvars[0], NULL);
|
||||
if (!argv) {
|
||||
xfree(input);
|
||||
return; // Already did emsg.
|
||||
}
|
||||
|
||||
// execute the command
|
||||
size_t nread = 0;
|
||||
char *res = NULL;
|
||||
int status = os_system(cmd, input, input_len, &res, &nread);
|
||||
int status = os_system(argv, input, input_len, &res, &nread);
|
||||
|
||||
xfree(input);
|
||||
|
||||
@ -15163,16 +15187,17 @@ static void f_termopen(typval_T *argvars, typval_T *rettv)
|
||||
return;
|
||||
}
|
||||
|
||||
if (argvars[0].v_type != VAR_LIST
|
||||
|| (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN)) {
|
||||
// Wrong argument types
|
||||
EMSG(_(e_invarg));
|
||||
return;
|
||||
char *cmd;
|
||||
char **argv = tv_to_argv(&argvars[0], &cmd);
|
||||
if (!argv) {
|
||||
return; // Did error message in tv_to_argv.
|
||||
}
|
||||
|
||||
char **argv = list_to_argv(argvars[0].vval.v_list);
|
||||
if (!argv) {
|
||||
return; // Did error message in list_to_argv.
|
||||
if (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN) {
|
||||
// Wrong argument type
|
||||
EMSG2(_(e_invarg2), "expected dictionary");
|
||||
shell_free_argv(argv);
|
||||
return;
|
||||
}
|
||||
|
||||
ufunc_T *on_stdout = NULL, *on_stderr = NULL, *on_exit = NULL;
|
||||
@ -15180,6 +15205,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv)
|
||||
if (argvars[1].v_type == VAR_DICT) {
|
||||
job_opts = argvars[1].vval.v_dict;
|
||||
if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
|
||||
shell_free_argv(argv);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -15192,6 +15218,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv)
|
||||
opts.term_name = xstrdup("xterm-256color");
|
||||
Job *job = common_job_start(opts, rettv);
|
||||
if (!job) {
|
||||
shell_free_argv(argv);
|
||||
return;
|
||||
}
|
||||
TerminalJobData *data = opts.data;
|
||||
@ -15212,10 +15239,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv)
|
||||
|
||||
// Get the desired name of the buffer.
|
||||
char *name = job_opts ?
|
||||
(char *)get_dict_string(job_opts, (char_u *)"name", false) : NULL;
|
||||
if (!name) {
|
||||
name = argv[0];
|
||||
}
|
||||
(char *)get_dict_string(job_opts, (char_u *)"name", false) : cmd;
|
||||
char buf[1024];
|
||||
// format the title with the pid to conform with the term:// URI
|
||||
snprintf(buf, sizeof(buf), "term://%s//%d:%s", cwd, pid, name);
|
||||
@ -15233,6 +15257,8 @@ static void f_termopen(typval_T *argvars, typval_T *rettv)
|
||||
Terminal *term = terminal_open(topts);
|
||||
data->term = term;
|
||||
data->refcount++;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -9410,23 +9410,19 @@ static void ex_folddo(exarg_T *eap)
|
||||
|
||||
static void ex_terminal(exarg_T *eap)
|
||||
{
|
||||
char *name = NULL;
|
||||
char cmd[512];
|
||||
if (strcmp((char *)eap->arg, "") == 0) {
|
||||
snprintf(cmd, sizeof(cmd), "['%s']", (char *)p_sh);
|
||||
name = (char *)p_sh;
|
||||
} else {
|
||||
// Escape quotes and slashes so they get sent literally.
|
||||
// We will call termopen() with ['shell'] if not given a {cmd}.
|
||||
char *name = (char *)p_sh;
|
||||
char *lquote = "['";
|
||||
char *rquote = "']";
|
||||
if (*eap->arg != NUL) {
|
||||
name = (char *)vim_strsave_escaped(eap->arg, (char_u *)"\"\\");
|
||||
snprintf(cmd, sizeof(cmd), "['%s','%s',\"%s\"]",
|
||||
(char *)p_sh, (char *)p_shcf, name);
|
||||
lquote = rquote = "\"";
|
||||
}
|
||||
|
||||
char ex_cmd[512];
|
||||
snprintf(ex_cmd, sizeof(ex_cmd),
|
||||
":enew%s | call termopen(%s, {'name':\"%s\"}) | startinsert",
|
||||
eap->forceit==TRUE ? "!" : "", cmd, name);
|
||||
|
||||
":enew%s | call termopen(%s%s%s) | startinsert",
|
||||
eap->forceit==TRUE ? "!" : "", lquote, name, rquote);
|
||||
do_cmdline_cmd((uint8_t *)ex_cmd);
|
||||
|
||||
if (name != (char *)p_sh) {
|
||||
|
@ -119,14 +119,14 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args)
|
||||
}
|
||||
|
||||
size_t nread;
|
||||
int status = shell((const char *)cmd,
|
||||
(const char *)extra_args,
|
||||
input.data,
|
||||
input.len,
|
||||
output_ptr,
|
||||
&nread,
|
||||
emsg_silent,
|
||||
forward_output);
|
||||
|
||||
int status = do_os_system(shell_build_argv((char *)cmd, (char *)extra_args),
|
||||
input.data,
|
||||
input.len,
|
||||
output_ptr,
|
||||
&nread,
|
||||
emsg_silent,
|
||||
forward_output);
|
||||
|
||||
xfree(input.data);
|
||||
|
||||
@ -152,9 +152,11 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args)
|
||||
/// example:
|
||||
/// char *output = NULL;
|
||||
/// size_t nread = 0;
|
||||
/// int status = os_sytem("ls -la", NULL, 0, &output, &nread);
|
||||
/// char *argv[] = {"ls", "-la", NULL};
|
||||
/// int status = os_sytem(argv, NULL, 0, &output, &nread);
|
||||
///
|
||||
/// @param cmd The full commandline to be passed to the shell
|
||||
/// @param argv The commandline arguments to be passed to the shell. `argv`
|
||||
/// will be consumed.
|
||||
/// @param input The input to the shell (NULL for no input), passed to the
|
||||
/// stdin of the resulting process.
|
||||
/// @param len The length of the input buffer (not used if `input` == NULL)
|
||||
@ -166,23 +168,22 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args)
|
||||
/// returned buffer is not NULL)
|
||||
/// @return the return code of the process, -1 if the process couldn't be
|
||||
/// started properly
|
||||
int os_system(const char *cmd,
|
||||
int os_system(char **argv,
|
||||
const char *input,
|
||||
size_t len,
|
||||
char **output,
|
||||
size_t *nread) FUNC_ATTR_NONNULL_ARG(1)
|
||||
{
|
||||
return shell(cmd, NULL, input, len, output, nread, true, false);
|
||||
return do_os_system(argv, input, len, output, nread, true, false);
|
||||
}
|
||||
|
||||
static int shell(const char *cmd,
|
||||
const char *extra_args,
|
||||
const char *input,
|
||||
size_t len,
|
||||
char **output,
|
||||
size_t *nread,
|
||||
bool silent,
|
||||
bool forward_output)
|
||||
static int do_os_system(char **argv,
|
||||
const char *input,
|
||||
size_t len,
|
||||
char **output,
|
||||
size_t *nread,
|
||||
bool silent,
|
||||
bool forward_output)
|
||||
{
|
||||
// the output buffer
|
||||
DynamicBuffer buf = DYNAMIC_BUFFER_INIT;
|
||||
@ -197,7 +198,9 @@ static int shell(const char *cmd,
|
||||
data_cb = NULL;
|
||||
}
|
||||
|
||||
char **argv = shell_build_argv(cmd, extra_args);
|
||||
// Copy the program name in case we need to report an error.
|
||||
char prog[MAXPATHL];
|
||||
xstrlcpy(prog, argv[0], MAXPATHL);
|
||||
|
||||
int status;
|
||||
JobOptions opts = JOB_OPTIONS_INIT;
|
||||
@ -211,11 +214,9 @@ static int shell(const char *cmd,
|
||||
|
||||
if (status <= 0) {
|
||||
// Failed, probably due to `sh` not being executable
|
||||
ELOG("Couldn't start job, command: '%s', error code: '%d'",
|
||||
(cmd ? cmd : (char *)p_sh), status);
|
||||
if (!silent) {
|
||||
MSG_PUTS(_("\nCannot execute shell "));
|
||||
msg_outtrans(p_sh);
|
||||
MSG_PUTS(_("\nCannot execute "));
|
||||
msg_outtrans((char_u *)prog);
|
||||
msg_putchar('\n');
|
||||
}
|
||||
return -1;
|
||||
|
@ -29,6 +29,13 @@ describe('jobs', function()
|
||||
]])
|
||||
end)
|
||||
|
||||
it('uses &shell and &shellcmdflag if passed a string', function()
|
||||
nvim('command', "let $VAR = 'abc'")
|
||||
nvim('command', "let j = jobstart('echo $VAR', g:job_opts)")
|
||||
eq({'notification', 'stdout', {0, {'abc', ''}}}, next_msg())
|
||||
eq({'notification', 'exit', {0, 0}}, next_msg())
|
||||
end)
|
||||
|
||||
it('returns 0 when it fails to start', function()
|
||||
local status, rv = pcall(eval, "jobstart([])")
|
||||
eq(false, status)
|
||||
|
@ -44,7 +44,7 @@ describe('system()', function()
|
||||
eq(127, eval('v:shell_error'))
|
||||
end)
|
||||
|
||||
describe('executes shell function', function()
|
||||
describe('executes shell function if passed a string', function()
|
||||
local screen
|
||||
|
||||
before_each(function()
|
||||
@ -192,6 +192,13 @@ describe('system()', function()
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
describe('command passed as a list', function()
|
||||
it('does not execute &shell', function()
|
||||
eq('* $NOTHING ~/file',
|
||||
eval("system(['echo', '-n', '*', '$NOTHING', '~/file'])"))
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('systemlist()', function()
|
||||
|
@ -40,7 +40,9 @@ describe('shell functions', function()
|
||||
local output = ffi.new('char *[1]')
|
||||
local nread = ffi.new('size_t[1]')
|
||||
|
||||
local status = shell.os_system(to_cstr(cmd), input_or, input_len, output, nread)
|
||||
local argv = ffi.cast('char**',
|
||||
shell.shell_build_argv(to_cstr(cmd), nil))
|
||||
local status = shell.os_system(argv, input_or, input_len, output, nread)
|
||||
|
||||
return status, intern(output[0], nread[0])
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user