aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/eval.txt21
-rw-r--r--runtime/doc/various.txt5
-rw-r--r--src/nvim/eval.c33
-rw-r--r--src/nvim/globals.h9
-rw-r--r--src/nvim/message.c13
-rw-r--r--test/functional/eval/capture_spec.lua86
6 files changed, 161 insertions, 6 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 8b23d2ff5f..b5fe9967b2 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1808,6 +1808,7 @@ byteidx({expr}, {nr}) Number byte index of {nr}'th char in {expr}
byteidxcomp({expr}, {nr}) Number byte index of {nr}'th char in {expr}
call({func}, {arglist} [, {dict}])
any call {func} with arguments {arglist}
+capture({command}) String capture output of {command}
ceil({expr}) Float round {expr} up
changenr() Number current change number
char2nr({expr}[, {utf8}]) Number ASCII/UTF8 value of first char in {expr}
@@ -2481,6 +2482,26 @@ call({func}, {arglist} [, {dict}]) *call()* *E699*
{dict} is for functions with the "dict" attribute. It will be
used to set the local variable "self". |Dictionary-function|
+capture({command}) *capture()*
+ Capture output of {command}.
+ If {command} is a |String|, it returns {command} output.
+ If {command} is a |List|, it returns concatenated outputs of
+ each item.
+ Examples: >
+ let a = capture('echon "foo"')
+ echo a
+< foo >
+ let a = capture(['echon "foo"', 'echon "bar"'])
+ echo a
+< foobar
+ This function is not available in the |sandbox|.
+ Note: The commands are run as if they were prepended with
+ |:silent| modifier. |:redir| and |capture()| work together.
+ Unlike nested |:redir|s, outer capture will not catch
+ commands' output of the inner one, but the inner capture will
+ not cancel the outer.
+ Note: highlighting is ignored.
+
ceil({expr}) *ceil()*
Return the smallest integral value greater than or equal to
{expr} as a |Float| (round up).
diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt
index af4224993f..02c5fad22f 100644
--- a/runtime/doc/various.txt
+++ b/runtime/doc/various.txt
@@ -416,8 +416,9 @@ m *+xpm_w32* Win32 GUI only: pixmap support |w32-xpm-support|
To stop the messages and commands from being echoed to
the screen, put the commands in a function and call it
with ":silent call Function()".
- An alternative is to use the 'verbosefile' option,
- this can be used in combination with ":redir".
+ The alternatives are to use the 'verbosefile' option
+ or |capture()| function, these can be used in
+ combination with ":redir".
:redi[r] >> {file} Redirect messages to file {file}. Append if {file}
already exists.
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 0d060e5b70..e950dfb5c4 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -6699,6 +6699,7 @@ static struct fst {
{ "byteidx", 2, 2, f_byteidx },
{ "byteidxcomp", 2, 2, f_byteidxcomp },
{ "call", 2, 3, f_call },
+ { "capture", 1, 1, f_capture },
{ "ceil", 1, 1, f_ceil },
{ "changenr", 0, 0, f_changenr },
{ "char2nr", 1, 2, f_char2nr },
@@ -8039,6 +8040,38 @@ static void f_call(typval_T *argvars, typval_T *rettv)
(void)func_call(func, &argvars[1], selfdict, rettv);
}
+// "capture(command)" function
+static void f_capture(typval_T *argvars, typval_T *rettv)
+{
+ int save_msg_silent = msg_silent;
+ garray_T *save_capture_ga = capture_ga;
+
+ if (check_secure()) {
+ return;
+ }
+
+ garray_T capture_local;
+ capture_ga = &capture_local;
+ ga_init(capture_ga, (int)sizeof(char), 80);
+
+ 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));
+ }
+ }
+ msg_silent = save_msg_silent;
+
+ ga_append(capture_ga, NUL);
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = capture_ga->ga_data;
+
+ capture_ga = save_capture_ga;
+}
+
/*
* "ceil({float})" function
*/
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index dafb75ca87..d399db1381 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -985,10 +985,11 @@ EXTERN int keep_help_flag INIT(= FALSE); /* doing :ta from help file */
*/
EXTERN char_u *empty_option INIT(= (char_u *)"");
-EXTERN int redir_off INIT(= FALSE); /* no redirection for a moment */
-EXTERN FILE *redir_fd INIT(= NULL); /* message redirection file */
-EXTERN int redir_reg INIT(= 0); /* message redirection register */
-EXTERN int redir_vname INIT(= 0); /* message redirection variable */
+EXTERN int redir_off INIT(= false); // no redirection for a moment
+EXTERN FILE *redir_fd INIT(= NULL); // message redirection file
+EXTERN int redir_reg INIT(= 0); // message redirection register
+EXTERN int redir_vname INIT(= 0); // message redirection variable
+EXTERN garray_T *capture_ga INIT(= NULL); // capture() buffer
EXTERN char_u langmap_mapchar[256]; /* mapping for language keys */
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 521db85cf0..02201107db 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -2389,6 +2389,19 @@ static void redir_write(char_u *str, int maxlen)
char_u *s = str;
static int cur_col = 0;
+ if (maxlen == 0) {
+ return;
+ }
+
+ // Append output to capture().
+ if (capture_ga) {
+ size_t len = 0;
+ while (str[len] && (maxlen < 0 ? 1 : (len < (size_t)maxlen))) {
+ len++;
+ }
+ ga_concat_len(capture_ga, (const char *)str, len);
+ }
+
/* Don't do anything for displaying prompts and the like. */
if (redir_off)
return;
diff --git a/test/functional/eval/capture_spec.lua b/test/functional/eval/capture_spec.lua
new file mode 100644
index 0000000000..1ee5a49df2
--- /dev/null
+++ b/test/functional/eval/capture_spec.lua
@@ -0,0 +1,86 @@
+local helpers = require('test.functional.helpers')
+local eq = helpers.eq
+local eval = helpers.eval
+local clear = helpers.clear
+local source = helpers.source
+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
+
+describe('capture()', function()
+ before_each(clear)
+
+ it('returns the same result with :redir', function()
+ eq(redir_exec('messages'), funcs.capture('messages'))
+ end)
+
+ it('returns the output of the commands if the argument is List', function()
+ eq("foobar", funcs.capture({'echon "foo"', 'echon "bar"'}))
+ eq("\nfoo\nbar", funcs.capture({'echo "foo"', 'echo "bar"'}))
+ end)
+
+ it('supports the nested redirection', function()
+ source([[
+ function! g:Foo()
+ let a = ''
+ redir => a
+ silent echon "foo"
+ redir END
+ return a
+ endfunction
+ function! g:Bar()
+ let a = ''
+ redir => a
+ call g:Foo()
+ redir END
+ return a
+ endfunction
+ ]])
+ eq('foo', funcs.capture('call g:Bar()'))
+
+ eq('42', funcs.capture([[echon capture("echon capture('echon 42')")]]))
+ end)
+
+ it('returns the transformed string', function()
+ eq('^A', funcs.capture('echon "\\<C-a>"'))
+ end)
+
+ it('returns the empty string if the argument list is empty', function()
+ eq('', funcs.capture({}))
+ eq(0, exc_exec('let g:ret = capture(v:_null_list)'))
+ eq('', eval('g:ret'))
+ end)
+
+ it('returns the errors', function()
+ local ret
+ ret = exc_exec('call capture(0.0)')
+ eq('Vim(call):E806: using Float as a String', ret)
+ ret = exc_exec('call capture(v:_null_dict)')
+ eq('Vim(call):E731: using Dictionary as a String', ret)
+ ret = exc_exec('call capture(function("tr"))')
+ eq('Vim(call):E729: using Funcref as a String', ret)
+ ret = exc_exec('call capture(["echo 42", 0.0, "echo 44"])')
+ eq('Vim(call):E806: using Float as a String', ret)
+ ret = exc_exec('call capture(["echo 42", v:_null_dict, "echo 44"])')
+ eq('Vim(call):E731: using Dictionary as a String', ret)
+ ret = exc_exec('call capture(["echo 42", function("tr"), "echo 44"])')
+ eq('Vim(call):E729: using Funcref as a String', ret)
+ end)
+
+ it('silences command run inside', function()
+ local screen = Screen.new(20, 5)
+ screen:attach()
+ screen:set_default_attr_ignore({{bold=true, foreground=255}})
+ feed(':let g:mes = capture("echon 42")<CR>')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ eq('42', eval('g:mes'))
+ end)
+end)