diff options
-rw-r--r-- | runtime/doc/eval.txt | 19 | ||||
-rw-r--r-- | src/nvim/eval.c | 53 | ||||
-rw-r--r-- | src/nvim/eval.lua | 2 | ||||
-rw-r--r-- | src/nvim/globals.h | 1 | ||||
-rw-r--r-- | src/nvim/message.c | 30 | ||||
-rw-r--r-- | src/nvim/testdir/test_alot.vim | 1 | ||||
-rw-r--r-- | src/nvim/testdir/test_execute_func.vim | 55 | ||||
-rw-r--r-- | test/functional/eval/execute_spec.lua | 91 |
8 files changed, 207 insertions, 45 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 091941669c..78124debe1 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -3070,7 +3070,7 @@ executable({expr}) *executable()* 0 does not exist -1 not implemented on this system -execute({command}) *execute()* +execute({command} [, {silent}]) *execute()* Execute {command} and capture its output. If {command} is a |String|, returns {command} output. If {command} is a |List|, returns concatenated outputs. @@ -3079,10 +3079,17 @@ execute({command}) *execute()* < foo > echo execute(['echon "foo"', 'echon "bar"']) < foobar + + The optional {silent} argument can have these values: + "" no `:silent` used + "silent" `:silent` used + "silent!" `:silent!` used + The default is 'silent'. Note that with "silent!", unlike + `:redir`, error messages are dropped. + This function is not available in the |sandbox|. - Note: {command} executes as if prepended with |:silent| - (output is collected but not displayed). If nested, an outer - execute() will not observe output of the inner calls. + Note: If nested, an outer execute() will not observe output of + the inner calls. Note: Text attributes (highlights) are not captured. exepath({expr}) *exepath()* @@ -7007,9 +7014,9 @@ synID({lnum}, {col}, {trans}) *synID()* that's where the cursor can be in Insert mode, synID() returns zero. - When {trans} is non-zero, transparent items are reduced to the + When {trans} is |TRUE|, transparent items are reduced to the item that they reveal. This is useful when wanting to know - the effective color. When {trans} is zero, the transparent + the effective color. When {trans} is |FALSE|, the transparent item is returned. This is useful when wanting to know which syntax item is effective (e.g. inside parens). Warning: This function can be very slow. Best speed is diff --git a/src/nvim/eval.c b/src/nvim/eval.c index a046b2a288..2e86da5046 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -8828,10 +8828,27 @@ static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr fptr) || (gettail_dir(name) != name && os_can_exe(name, NULL, false)); } +static char_u * get_list_line(int c, void *cookie, int indent) +{ + listitem_T **p = (listitem_T **)cookie; + listitem_T *item = *p; + char_u buf[NUMBUFLEN]; + char_u *s; + + if (item == NULL) { + return NULL; + } + s = get_tv_string_buf_chk(&item->li_tv, buf); + *p = item->li_next; + return s == NULL ? NULL : vim_strsave(s); +} + // "execute(command)" function static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int save_msg_silent = msg_silent; + int save_emsg_silent = emsg_silent; + bool save_emsg_noredir = emsg_noredir; garray_T *save_capture_ga = capture_ga; if (check_secure()) { @@ -8839,23 +8856,45 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) } garray_T capture_local; + ga_init(&capture_local, (int)sizeof(char), 80); capture_ga = &capture_local; - ga_init(capture_ga, (int)sizeof(char), 80); - msg_silent++; + if (argvars[1].v_type != VAR_UNKNOWN) { + char_u buf[NUMBUFLEN]; + char_u *s = get_tv_string_buf_chk(&argvars[1], buf); + + if (s == NULL) { + return; + } + if (STRNCMP(s, "silent", 6) == 0) { + msg_silent++; + } + if (STRCMP(s, "silent!") == 0) { + emsg_silent = true; + emsg_noredir = true; + } + } else { + msg_silent++; + } + if (argvars[0].v_type != VAR_LIST) { do_cmdline_cmd((char *)get_tv_string(&argvars[0])); } else if (argvars[0].vval.v_list != NULL) { - for (listitem_T *li = argvars[0].vval.v_list->lv_first; - li != NULL; li = li->li_next) { - do_cmdline_cmd((char *)get_tv_string(&li->li_tv)); - } + list_T *list = argvars[0].vval.v_list; + list->lv_refcount++; + listitem_T *item = list->lv_first; + do_cmdline(NULL, get_list_line, (void *)&item, + DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT|DOCMD_KEYTYPED); + list->lv_refcount--; } msg_silent = save_msg_silent; + emsg_silent = save_emsg_silent; + emsg_noredir = save_emsg_noredir; ga_append(capture_ga, NUL); rettv->v_type = VAR_STRING; - rettv->vval.v_string = capture_ga->ga_data; + rettv->vval.v_string = vim_strsave(capture_ga->ga_data); + ga_clear(capture_ga); capture_ga = save_capture_ga; } diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 5fb99fecc6..e0b72feb19 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -79,7 +79,7 @@ return { eval={args=1}, eventhandler={}, executable={args=1}, - execute={args=1}, + execute={args={1, 2}}, exepath={args=1}, exists={args=1}, exp={args=1, func="float_op_wrapper", data="&exp"}, diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 5ee04ad982..692b3b761c 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -871,6 +871,7 @@ EXTERN cmdmod_T cmdmod; /* Ex command modifiers */ EXTERN int msg_silent INIT(= 0); /* don't print messages */ EXTERN int emsg_silent INIT(= 0); /* don't print error messages */ +EXTERN bool emsg_noredir INIT(= false); // don't redirect error messages EXTERN int cmd_silent INIT(= FALSE); /* don't echo the command line */ /* Values for swap_exists_action: what to do when swap file already exists */ diff --git a/src/nvim/message.c b/src/nvim/message.c index 637b89ccbe..8c164111f2 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -508,20 +508,22 @@ int emsg(char_u *s) * But do write it to the redirection file. */ if (emsg_silent != 0) { - msg_start(); - p = get_emsg_source(); - if (p != NULL) { - STRCAT(p, "\n"); - redir_write(p, STRLEN(p)); - xfree(p); - } - p = get_emsg_lnum(); - if (p != NULL) { - STRCAT(p, "\n"); - redir_write(p, STRLEN(p)); - xfree(p); + if (!emsg_noredir) { + msg_start(); + p = get_emsg_source(); + if (p != NULL) { + STRCAT(p, "\n"); + redir_write(p, STRLEN(p)); + xfree(p); + } + p = get_emsg_lnum(); + if (p != NULL) { + STRCAT(p, "\n"); + redir_write(p, STRLEN(p)); + xfree(p); + } + redir_write(s, STRLEN(s)); } - redir_write(s, STRLEN(s)); return true; } @@ -2508,7 +2510,7 @@ static void redir_write(char_u *str, int maxlen) int redirecting(void) { return redir_fd != NULL || *p_vfile != NUL - || redir_reg || redir_vname + || redir_reg || redir_vname || capture_ga != NULL ; } diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index 818ff7cf54..60248bf430 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -4,6 +4,7 @@ source test_assign.vim source test_autocmd.vim source test_cursor_func.vim +source test_execute_func.vim source test_ex_undo.vim source test_expr.vim source test_expr_utf8.vim diff --git a/src/nvim/testdir/test_execute_func.vim b/src/nvim/testdir/test_execute_func.vim new file mode 100644 index 0000000000..6f61bede93 --- /dev/null +++ b/src/nvim/testdir/test_execute_func.vim @@ -0,0 +1,55 @@ +" test execute() + +func NestedEval() + let nested = execute('echo "nested\nlines"') + echo 'got: "' . nested . '"' +endfunc + +func NestedRedir() + redir => var + echo 'broken' + redir END +endfunc + +func Test_execute_string() + call assert_equal("\nnocompatible", execute('set compatible?')) + call assert_equal("\nsomething\nnice", execute('echo "something\nnice"')) + call assert_equal("noendofline", execute('echon "noendofline"')) + call assert_equal("", execute(123)) + + call assert_equal("\ngot: \"\nnested\nlines\"", execute('call NestedEval()')) + redir => redired + echo 'this' + let evaled = execute('echo "that"') + echo 'theend' + redir END +" Nvim supports execute('... :redir ...'), so this test is intentionally +" disabled. +" call assert_equal("\nthis\ntheend", redired) + call assert_equal("\nthat", evaled) + + call assert_fails('call execute("doesnotexist")', 'E492:') + call assert_fails('call execute(3.4)', 'E806:') +" Nvim supports execute('... :redir ...'), so this test is intentionally +" disabled. +" call assert_fails('call execute("call NestedRedir()")', 'E930:') + + call assert_equal("\nsomething", execute('echo "something"', '')) + call assert_equal("\nsomething", execute('echo "something"', 'silent')) + call assert_equal("\nsomething", execute('echo "something"', 'silent!')) + call assert_equal("", execute('burp', 'silent!')) + call assert_fails('call execute("echo \"x\"", 3.4)', 'E806:') + + call assert_equal("", execute("")) +endfunc + +func Test_execute_list() + call assert_equal("\nsomething\nnice", execute(['echo "something"', 'echo "nice"'])) + let l = ['for n in range(0, 3)', + \ 'echo n', + \ 'endfor'] + call assert_equal("\n0\n1\n2\n3", execute(l)) + + call assert_equal("", execute([])) + call assert_equal("", execute(v:_null_list)) +endfunc diff --git a/test/functional/eval/execute_spec.lua b/test/functional/eval/execute_spec.lua index fc13c0a72b..cc9b61b842 100644 --- a/test/functional/eval/execute_spec.lua +++ b/test/functional/eval/execute_spec.lua @@ -7,7 +7,7 @@ local redir_exec = helpers.redir_exec local exc_exec = helpers.exc_exec local funcs = helpers.funcs local Screen = require('test.functional.ui.screen') -local feed = helpers.feed +local command = helpers.command describe('execute()', function() before_each(clear) @@ -62,11 +62,11 @@ describe('execute()', function() ret = exc_exec('call execute(function("tr"))') eq('Vim(call):E729: using Funcref as a String', ret) ret = exc_exec('call execute(["echo 42", 0.0, "echo 44"])') - eq('Vim(call):E806: using Float as a String', ret) + eq('Vim:E806: using Float as a String', ret) ret = exc_exec('call execute(["echo 42", v:_null_dict, "echo 44"])') - eq('Vim(call):E731: using Dictionary as a String', ret) + eq('Vim:E731: using Dictionary as a String', ret) ret = exc_exec('call execute(["echo 42", function("tr"), "echo 44"])') - eq('Vim(call):E729: using Funcref as a String', ret) + eq('Vim:E729: using Funcref as a String', ret) end) -- This matches Vim behavior. @@ -74,18 +74,75 @@ describe('execute()', function() eq('\n:!echo "foo"\13\n', funcs.execute('!echo "foo"')) end) - it('silences command run inside', function() - local screen = Screen.new(40, 5) - screen:attach() - screen:set_default_attr_ids( {[0] = {bold=true, foreground=255}} ) - feed(':let g:mes = execute("echon 42")<CR>') - screen:expect([[ - ^ | - {0:~ }| - {0:~ }| - {0:~ }| - :let g:mes = execute("echon 42") | - ]]) - eq('42', eval('g:mes')) + describe('{silent} argument', function() + it('captures & displays output for ""', function() + local screen = Screen.new(40, 5) + screen:attach() + command('let g:mes = execute("echon 42", "")') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + 42 | + ]]) + eq('42', eval('g:mes')) + end) + + it('captures but does not display output for "silent"', function() + local screen = Screen.new(40, 5) + screen:attach() + command('let g:mes = execute("echon 42")') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]]) + eq('42', eval('g:mes')) + + command('let g:mes = execute("echon 13", "silent")') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]]) + eq('13', eval('g:mes')) + end) + + it('suppresses errors for "silent!"', function() + eq(0, exc_exec('let g:mes = execute(0.0, "silent!")')) + eq('', eval('g:mes')) + + eq(0, exc_exec('let g:mes = execute("echon add(1, 1)", "silent!")')) + eq('1', eval('g:mes')) + + eq(0, exc_exec('let g:mes = execute(["echon 42", "echon add(1, 1)"], "silent!")')) + eq('421', eval('g:mes')) + end) + + it('propagates errors for "" and "silent"', function() + local ret + ret = exc_exec('call execute(0.0, "")') + eq('Vim(call):E806: using Float as a String', ret) + + ret = exc_exec('call execute(v:_null_dict, "silent")') + eq('Vim(call):E731: using Dictionary as a String', ret) + + ret = exc_exec('call execute("echo add(1, 1)", "")') + eq('Vim(echo):E714: List required', ret) + + ret = exc_exec('call execute(["echon 42", "echo add(1, 1)"], "")') + eq('Vim(echo):E714: List required', ret) + + ret = exc_exec('call execute("echo add(1, 1)", "silent")') + eq('Vim(echo):E714: List required', ret) + + ret = exc_exec('call execute(["echon 42", "echo add(1, 1)"], "silent")') + eq('Vim(echo):E714: List required', ret) + end) end) end) |