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:
Scott Prager 2015-04-15 13:05:30 -04:00
parent 74aef89720
commit 1eb3396922
8 changed files with 139 additions and 96 deletions

View File

@ -2013,8 +2013,8 @@ synIDattr( {synID}, {what} [, {mode}])
synIDtrans( {synID}) Number translated syntax ID of {synID} synIDtrans( {synID}) Number translated syntax ID of {synID}
synconcealed( {lnum}, {col}) List info about concealing synconcealed( {lnum}, {col}) List info about concealing
synstack( {lnum}, {col}) List stack of syntax IDs at {lnum} and {col} synstack( {lnum}, {col}) List stack of syntax IDs at {lnum} and {col}
system( {expr} [, {input}]) String output of shell command/filter {expr} system( {cmd} [, {input}]) String output of shell command/filter {cmd}
systemlist( {expr} [, {input}]) List output of shell command/filter {expr} systemlist( {cmd} [, {input}]) List output of shell command/filter {cmd}
tabpagebuflist( [{arg}]) List list of buffer numbers in tab page tabpagebuflist( [{arg}]) List list of buffer numbers in tab page
tabpagenr( [{arg}]) Number number of current or last tab page tabpagenr( [{arg}]) Number number of current or last tab page
tabpagewinnr( {tabarg}[, {arg}]) tabpagewinnr( {tabarg}[, {arg}])
@ -4031,9 +4031,12 @@ jobsend({job}, {data}) {Nvim} *jobsend()*
:call jobsend(j, ["abc", "123\n456", ""]) :call jobsend(j, ["abc", "123\n456", ""])
< will send "abc<NL>123<NUL>456<NL>". < will send "abc<NL>123<NUL>456<NL>".
jobstart({argv}[, {opts}]) {Nvim} *jobstart()* jobstart({cmd}[, {opts}]) {Nvim} *jobstart()*
Spawns {argv}(list) as a job. If passed, {opts} must be a Spawns {cmd} as a job. If {cmd} is a |List|, it will be run
dictionary with any of the following keys: 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_stdout: stdout event handler
- on_stderr: stderr event handler - on_stderr: stderr event handler
- on_exit: exit 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 - The job ID on success, which is used by |jobsend()| and
|jobstop()| |jobstop()|
- 0 when the job table is full or on invalid arguments - 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. See |job-control| for more information.
jobstop({job}) {Nvim} *jobstop()* 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 character in a line and the first column in an empty line are
valid positions. valid positions.
system({expr} [, {input}]) *system()* *E677* system({cmd} [, {input}]) *system()* *E677*
Get the output of the shell command {expr} as a string. See Get the output of the shell command {cmd} as a |String|. {cmd}
|systemlist()| to get the output as a List. 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 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 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 Note: Use |shellescape()| or |::S| with |expand()| or
|fnamemodify()| to escape special characters in a command |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 The characters in 'shellquote' and 'shellxquote' may also
cause trouble. cause trouble.
This is not to be used for interactive commands. 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 To avoid the string being truncated at a NUL, all NUL
characters are replaced with SOH (0x01). characters are replaced with SOH (0x01).
The command executed is constructed using several options: The command executed is constructed using several options when
'shell' 'shellcmdflag' 'shellxquote' {expr} 'shellredir' {tmp} 'shellxquote' {cmd} is a string: 'shell' 'shellcmdflag' {cmd}
({tmp} is an automatically generated file name).
For Unix braces are put around {expr} to allow for
concatenated commands.
The command will be executed in "cooked" mode, so that a The command will be executed in "cooked" mode, so that a
CTRL-C will interrupt the command (on Unix at least). 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. 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 Same as |system()|, but returns a |List| with lines (parts of
output separated by NL) with NULs transformed into NLs. Output output separated by NL) with NULs transformed into NLs. Output
is the same as |readfile()| will output with {binary} argument 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' For MS-Windows forward slashes are used when the 'shellslash'
option is set or when 'shellcmdflag' starts with '-'. option is set or when 'shellcmdflag' starts with '-'.
termopen({argv}[, {opts}]) {Nvim} *termopen()* termopen({cmd}[, {opts}]) {Nvim} *termopen()*
Spawns {argv}(list) in a new pseudo-terminal session connected Spawns {cmd} in a new pseudo-terminal session connected
to the current buffer. This function fails if the current to the current buffer. {cmd} is the same as the one passed to
buffer is modified (all buffer contents are destroyed). |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()|, The {opts} dict is similar to the one passed to |jobstart()|,
but the `pty`, `width`, `height`, and `TERM` fields are but the `pty`, `width`, `height`, and `TERM` fields are
ignored: `height`/`width` are taken from the current window ignored: `height`/`width` are taken from the current window
and `$TERM` is set to "xterm-256color". An additional option, and `$TERM` is set to "xterm-256color", and it may have a
`name`, can be used to give the terminal a specific name. `name` field. `name`, if present, sets the buffer's name to
"term://{cwd}/{pid}:{name}".
Returns the same values as |jobstart()|. Returns the same values as |jobstart()|.
See |nvim-terminal-emulator| for more information. See |nvim-terminal-emulator| for more information.

View File

@ -229,7 +229,7 @@ g8 Print the hex values of the bytes used in the
:enew :enew
:call termopen([&sh, &shcf, '{cmd}'], :call termopen([&sh, &shcf, '{cmd}'],
\{'name':'{cmd}'}) \{'name':'{cmd}'})
:startinsert :startinsert
< <
If no {cmd} is given, 'shellcmdflag' will not be sent If no {cmd} is given, 'shellcmdflag' will not be sent

View File

@ -10779,35 +10779,54 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv)
rettv->vval.v_number = 1; 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 (cmd_tv->v_type == VAR_STRING) {
if (arg->li_tv.v_type != VAR_STRING) { char *cmd_str = (char *)get_tv_string(cmd_tv);
EMSG(_(e_invarg)); if (cmd) {
return NULL; *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) { if (!argc) {
EMSG(_("Argument vector must have at least one item")); EMSG(_("Argument vector must have at least one item"));
return NULL; return NULL;
} }
assert(args->lv_first); assert(argl->lv_first);
const char_u *exe = get_tv_string(&args->lv_first->li_tv); const char_u *exe = get_tv_string_chk(&argl->lv_first->li_tv);
if (!os_can_exe(exe, NULL)) { if (!exe || !os_can_exe(exe, NULL)) {
// String is not executable // String is not executable
EMSG2(e_jobexe, exe); if (exe) {
EMSG2(e_jobexe, exe);
}
return NULL; return NULL;
} }
if (cmd) {
*cmd = (char *)exe;
}
// Build the argument vector // Build the argument vector
int i = 0; int i = 0;
char **argv = xcalloc(argc + 1, sizeof(char *)); char **argv = xcalloc(argc + 1, sizeof(char *));
for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { for (listitem_T *arg = argl->lv_first; arg != NULL; arg = arg->li_next) {
argv[i++] = xstrdup((char *) get_tv_string(&arg->li_tv)); 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; return argv;
@ -10823,16 +10842,16 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv)
return; return;
} }
if (argvars[0].v_type != VAR_LIST char **argv = tv_to_argv(&argvars[0], NULL);
|| (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN)) { if (!argv) {
// Wrong argument types return; // Did error message in tv_to_argv.
EMSG(_(e_invarg));
return;
} }
char **argv = list_to_argv(argvars[0].vval.v_list); if (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN) {
if (!argv) { // Wrong argument types
return; // Did error message in list_to_argv. EMSG2(_(e_invarg2), "expected dictionary");
shell_free_argv(argv);
return;
} }
dict_T *job_opts = NULL; 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) { if (argvars[1].v_type == VAR_DICT) {
job_opts = argvars[1].vval.v_dict; job_opts = argvars[1].vval.v_dict;
if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) { if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
shell_free_argv(argv);
return; return;
} }
} }
@ -14935,12 +14955,16 @@ static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv,
} }
// get shell command to execute // 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 // execute the command
size_t nread = 0; size_t nread = 0;
char *res = NULL; 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); xfree(input);
@ -15163,16 +15187,17 @@ static void f_termopen(typval_T *argvars, typval_T *rettv)
return; return;
} }
if (argvars[0].v_type != VAR_LIST char *cmd;
|| (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN)) { char **argv = tv_to_argv(&argvars[0], &cmd);
// Wrong argument types if (!argv) {
EMSG(_(e_invarg)); return; // Did error message in tv_to_argv.
return;
} }
char **argv = list_to_argv(argvars[0].vval.v_list); if (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN) {
if (!argv) { // Wrong argument type
return; // Did error message in list_to_argv. EMSG2(_(e_invarg2), "expected dictionary");
shell_free_argv(argv);
return;
} }
ufunc_T *on_stdout = NULL, *on_stderr = NULL, *on_exit = NULL; 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) { if (argvars[1].v_type == VAR_DICT) {
job_opts = argvars[1].vval.v_dict; job_opts = argvars[1].vval.v_dict;
if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) { if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
shell_free_argv(argv);
return; return;
} }
} }
@ -15192,6 +15218,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv)
opts.term_name = xstrdup("xterm-256color"); opts.term_name = xstrdup("xterm-256color");
Job *job = common_job_start(opts, rettv); Job *job = common_job_start(opts, rettv);
if (!job) { if (!job) {
shell_free_argv(argv);
return; return;
} }
TerminalJobData *data = opts.data; 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. // Get the desired name of the buffer.
char *name = job_opts ? char *name = job_opts ?
(char *)get_dict_string(job_opts, (char_u *)"name", false) : NULL; (char *)get_dict_string(job_opts, (char_u *)"name", false) : cmd;
if (!name) {
name = argv[0];
}
char buf[1024]; char buf[1024];
// format the title with the pid to conform with the term:// URI // format the title with the pid to conform with the term:// URI
snprintf(buf, sizeof(buf), "term://%s//%d:%s", cwd, pid, name); 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); Terminal *term = terminal_open(topts);
data->term = term; data->term = term;
data->refcount++; data->refcount++;
return;
} }
/* /*

View File

@ -9410,23 +9410,19 @@ static void ex_folddo(exarg_T *eap)
static void ex_terminal(exarg_T *eap) static void ex_terminal(exarg_T *eap)
{ {
char *name = NULL; // We will call termopen() with ['shell'] if not given a {cmd}.
char cmd[512]; char *name = (char *)p_sh;
if (strcmp((char *)eap->arg, "") == 0) { char *lquote = "['";
snprintf(cmd, sizeof(cmd), "['%s']", (char *)p_sh); char *rquote = "']";
name = (char *)p_sh; if (*eap->arg != NUL) {
} else {
// Escape quotes and slashes so they get sent literally.
name = (char *)vim_strsave_escaped(eap->arg, (char_u *)"\"\\"); name = (char *)vim_strsave_escaped(eap->arg, (char_u *)"\"\\");
snprintf(cmd, sizeof(cmd), "['%s','%s',\"%s\"]", lquote = rquote = "\"";
(char *)p_sh, (char *)p_shcf, name);
} }
char ex_cmd[512]; char ex_cmd[512];
snprintf(ex_cmd, sizeof(ex_cmd), snprintf(ex_cmd, sizeof(ex_cmd),
":enew%s | call termopen(%s, {'name':\"%s\"}) | startinsert", ":enew%s | call termopen(%s%s%s) | startinsert",
eap->forceit==TRUE ? "!" : "", cmd, name); eap->forceit==TRUE ? "!" : "", lquote, name, rquote);
do_cmdline_cmd((uint8_t *)ex_cmd); do_cmdline_cmd((uint8_t *)ex_cmd);
if (name != (char *)p_sh) { if (name != (char *)p_sh) {

View File

@ -119,14 +119,14 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args)
} }
size_t nread; size_t nread;
int status = shell((const char *)cmd,
(const char *)extra_args, int status = do_os_system(shell_build_argv((char *)cmd, (char *)extra_args),
input.data, input.data,
input.len, input.len,
output_ptr, output_ptr,
&nread, &nread,
emsg_silent, emsg_silent,
forward_output); forward_output);
xfree(input.data); xfree(input.data);
@ -152,9 +152,11 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args)
/// example: /// example:
/// char *output = NULL; /// char *output = NULL;
/// size_t nread = 0; /// 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 /// @param input The input to the shell (NULL for no input), passed to the
/// stdin of the resulting process. /// stdin of the resulting process.
/// @param len The length of the input buffer (not used if `input` == NULL) /// @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) /// returned buffer is not NULL)
/// @return the return code of the process, -1 if the process couldn't be /// @return the return code of the process, -1 if the process couldn't be
/// started properly /// started properly
int os_system(const char *cmd, int os_system(char **argv,
const char *input, const char *input,
size_t len, size_t len,
char **output, char **output,
size_t *nread) FUNC_ATTR_NONNULL_ARG(1) 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, static int do_os_system(char **argv,
const char *extra_args, const char *input,
const char *input, size_t len,
size_t len, char **output,
char **output, size_t *nread,
size_t *nread, bool silent,
bool silent, bool forward_output)
bool forward_output)
{ {
// the output buffer // the output buffer
DynamicBuffer buf = DYNAMIC_BUFFER_INIT; DynamicBuffer buf = DYNAMIC_BUFFER_INIT;
@ -197,7 +198,9 @@ static int shell(const char *cmd,
data_cb = NULL; 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; int status;
JobOptions opts = JOB_OPTIONS_INIT; JobOptions opts = JOB_OPTIONS_INIT;
@ -211,11 +214,9 @@ static int shell(const char *cmd,
if (status <= 0) { if (status <= 0) {
// Failed, probably due to `sh` not being executable // 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) { if (!silent) {
MSG_PUTS(_("\nCannot execute shell ")); MSG_PUTS(_("\nCannot execute "));
msg_outtrans(p_sh); msg_outtrans((char_u *)prog);
msg_putchar('\n'); msg_putchar('\n');
} }
return -1; return -1;

View File

@ -29,6 +29,13 @@ describe('jobs', function()
]]) ]])
end) 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() it('returns 0 when it fails to start', function()
local status, rv = pcall(eval, "jobstart([])") local status, rv = pcall(eval, "jobstart([])")
eq(false, status) eq(false, status)

View File

@ -44,7 +44,7 @@ describe('system()', function()
eq(127, eval('v:shell_error')) eq(127, eval('v:shell_error'))
end) end)
describe('executes shell function', function() describe('executes shell function if passed a string', function()
local screen local screen
before_each(function() before_each(function()
@ -192,6 +192,13 @@ describe('system()', function()
end) end)
end) 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) end)
describe('systemlist()', function() describe('systemlist()', function()

View File

@ -40,7 +40,9 @@ describe('shell functions', function()
local output = ffi.new('char *[1]') local output = ffi.new('char *[1]')
local nread = ffi.new('size_t[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]) return status, intern(output[0], nread[0])
end end