api: change nvim_command_output behavior

Implement nvim_command_output with `execute({cmd},"silent")`.

Behavior changes:
- does not provoke any hit-enter prompt
- no longer prepends a newline char
- does not capture some noise (like the "[New File]" message, see the
  change to tabnewentered_spec.lua)

Technically ("bug-for-bug") this a breaking change.  But the previous
behavior of nvim_command_output meant that it probably wasn't used for
anything outside of tests.

Also remove the undocumented `v:command_output` variable which was
a hack introduced only for the purposes of nvim_command_output.

closes #7726
This commit is contained in:
Justin M. Keyes 2018-01-05 11:17:21 +01:00
parent f0845197d8
commit c095f83116
9 changed files with 105 additions and 36 deletions

View File

@ -43,14 +43,15 @@
#endif
/// Executes an ex-command.
/// On VimL error: Returns the VimL error; v:errmsg is not updated.
///
/// On parse error: forwards the Vim error; does not update v:errmsg.
/// On runtime error: forwards the Vim error; does not update v:errmsg.
///
/// @param command Ex-command string
/// @param[out] err Error details (including actual VimL error), if any
/// @param[out] err Error details (Vim error), if any
void nvim_command(String command, Error *err)
FUNC_API_SINCE(1)
{
// Run the command
try_start();
do_cmdline_cmd(command.data);
update_screen(VALID);
@ -207,18 +208,39 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt,
return cstr_as_string(ptr);
}
String nvim_command_output(String str, Error *err)
/// Executes an ex-command and returns its (non-error) output.
/// Shell |:!| output is not captured.
///
/// On parse error: forwards the Vim error; does not update v:errmsg.
/// On runtime error: forwards the Vim error; does not update v:errmsg.
///
/// @param command Ex-command string
/// @param[out] err Error details (Vim error), if any
String nvim_command_output(String command, Error *err)
FUNC_API_SINCE(1)
{
do_cmdline_cmd("redir => v:command_output");
nvim_command(str, err);
do_cmdline_cmd("redir END");
Array args = ARRAY_DICT_INIT;
ADD(args, STRING_OBJ(copy_string(command)));
ADD(args, STRING_OBJ(cstr_to_string("silent")));
String fn = cstr_to_string("execute");
Object rv = nvim_call_function(fn, args, err);
api_free_string(fn);
api_free_array(args);
if (ERROR_SET(err)) {
assert(rv.type == kObjectTypeNil);
return (String)STRING_INIT;
}
return cstr_to_string((char *)get_vim_var_str(VV_COMMAND_OUTPUT));
assert(rv.type == kObjectTypeString);
// execute() always(?) prepends a newline; remove it.
if (rv.data.string.size > 1) {
assert(rv.data.string.data[0] == '\n');
String *s = &rv.data.string;
s->size--;
memmove(s->data, s->data + 1, s->size);
s->data[s->size] = '\0';
}
return rv.data.string;
}
/// Evaluates a VimL expression (:help expression).

View File

@ -402,7 +402,6 @@ static struct vimvar {
VV(VV_OLDFILES, "oldfiles", VAR_LIST, 0),
VV(VV_WINDOWID, "windowid", VAR_NUMBER, VV_RO_SBX),
VV(VV_PROGPATH, "progpath", VAR_STRING, VV_RO),
VV(VV_COMMAND_OUTPUT, "command_output", VAR_STRING, 0),
VV(VV_COMPLETED_ITEM, "completed_item", VAR_DICT, VV_RO),
VV(VV_OPTION_NEW, "option_new", VAR_STRING, VV_RO),
VV(VV_OPTION_OLD, "option_old", VAR_STRING, VV_RO),

View File

@ -86,7 +86,6 @@ typedef enum {
VV_OLDFILES,
VV_WINDOWID,
VV_PROGPATH,
VV_COMMAND_OUTPUT,
VV_COMPLETED_ITEM,
VV_OPTION_NEW,
VV_OPTION_OLD,

View File

@ -37,7 +37,7 @@ describe('api', function()
os.remove(fname)
end)
it("VimL error: fails (VimL error), does NOT update v:errmsg", function()
it("parse error: fails (specific error), does NOT update v:errmsg", function()
-- Most API methods return generic errors (or no error) if a VimL
-- expression fails; nvim_command returns the VimL error details.
local status, rv = pcall(nvim, "command", "bogus_command")
@ -45,6 +45,57 @@ describe('api', function()
eq("E492:", string.match(rv, "E%d*:")) -- VimL error was returned.
eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated.
end)
it("runtime error: fails (specific error)", function()
local status, rv = pcall(nvim, "command_output", "buffer 23487")
eq(false, status) -- nvim_command() failed.
eq("E86: Buffer 23487 does not exist", string.match(rv, "E%d*:.*"))
eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated.
end)
end)
describe('nvim_command_output', function()
it('does not induce hit-enter prompt', function()
-- Induce a hit-enter prompt use nvim_input (non-blocking).
nvim('command', 'set cmdheight=1')
nvim('input', [[:echo "hi\nhi2"<CR>]])
-- Verify hit-enter prompt.
eq({mode='r', blocking=true}, nvim("get_mode"))
nvim('input', [[<C-c>]])
-- Verify NO hit-enter prompt.
nvim('command_output', [[echo "hi\nhi2"]])
eq({mode='n', blocking=false}, nvim("get_mode"))
end)
it('returns command output', function()
eq('this is\nspinal tap',
nvim('command_output', [[echo "this is\nspinal tap"]]))
end)
it('does not return shell |:!| output', function()
eq(':!echo "foo"\r\n', nvim('command_output', [[!echo "foo"]]))
end)
it("parse error: fails (specific error), does NOT update v:errmsg", function()
local status, rv = pcall(nvim, "command_output", "bogus commannnd")
eq(false, status) -- nvim_command_output() failed.
eq("E492: Not an editor command: bogus commannnd",
string.match(rv, "E%d*:.*"))
eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated.
-- Verify NO hit-enter prompt.
eq({mode='n', blocking=false}, nvim("get_mode"))
end)
it("runtime error: fails (specific error)", function()
local status, rv = pcall(nvim, "command_output", "buffer 42")
eq(false, status) -- nvim_command_output() failed.
eq("E86: Buffer 42 does not exist", string.match(rv, "E%d*:.*"))
eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated.
-- Verify NO hit-enter prompt.
eq({mode='n', blocking=false}, nvim("get_mode"))
end)
end)
describe('nvim_eval', function()

View File

@ -11,10 +11,10 @@ describe('TabClosed', function()
repeat
nvim('command', 'tabnew')
until nvim('eval', 'tabpagenr()') == 6 -- current tab is now 6
eq("\ntabclosed:6:6:5", nvim('command_output', 'tabclose')) -- close last 6, current tab is now 5
eq("\ntabclosed:5:5:4", nvim('command_output', 'close')) -- close last window on tab, closes tab
eq("\ntabclosed:2:2:3", nvim('command_output', '2tabclose')) -- close tab 2, current tab is now 3
eq("\ntabclosed:1:1:2\ntabclosed:1:1:1", nvim('command_output', 'tabonly')) -- close tabs 1 and 2
eq("tabclosed:6:6:5", nvim('command_output', 'tabclose')) -- close last 6, current tab is now 5
eq("tabclosed:5:5:4", nvim('command_output', 'close')) -- close last window on tab, closes tab
eq("tabclosed:2:2:3", nvim('command_output', '2tabclose')) -- close tab 2, current tab is now 3
eq("tabclosed:1:1:2\ntabclosed:1:1:1", nvim('command_output', 'tabonly')) -- close tabs 1 and 2
end)
it('is triggered when closing a window via bdelete from another tab', function()
@ -23,7 +23,7 @@ describe('TabClosed', function()
nvim('command', '1tabedit Xtestfile')
nvim('command', 'normal! 1gt')
eq({1, 3}, nvim('eval', '[tabpagenr(), tabpagenr("$")]'))
eq("\ntabclosed:2:2:1\ntabclosed:2:2:1", nvim('command_output', 'bdelete Xtestfile'))
eq("tabclosed:2:2:1\ntabclosed:2:2:1", nvim('command_output', 'bdelete Xtestfile'))
eq({1, 1}, nvim('eval', '[tabpagenr(), tabpagenr("$")]'))
end)
@ -35,7 +35,7 @@ describe('TabClosed', function()
-- Only one tab is closed, and the alternate file is used for the other.
eq({2, 3}, nvim('eval', '[tabpagenr(), tabpagenr("$")]'))
eq("\ntabclosed:2:2:2", nvim('command_output', 'bdelete Xtestfile2'))
eq("tabclosed:2:2:2", nvim('command_output', 'bdelete Xtestfile2'))
eq('Xtestfile1', nvim('eval', 'bufname("")'))
end)
end)
@ -48,9 +48,9 @@ describe('TabClosed', function()
nvim('command', 'tabnew')
until nvim('eval', 'tabpagenr()') == 7 -- current tab is now 7
-- sanity check, we shouldn't match on tabs with numbers other than 2
eq("\ntabclosed:7:7:6", nvim('command_output', 'tabclose'))
eq("tabclosed:7:7:6", nvim('command_output', 'tabclose'))
-- close tab page 2, current tab is now 5
eq("\ntabclosed:2:2:5\ntabclosed:match", nvim('command_output', '2tabclose'))
eq("tabclosed:2:2:5\ntabclosed:match", nvim('command_output', '2tabclose'))
end)
end)
@ -59,7 +59,7 @@ describe('TabClosed', function()
nvim('command', 'au! TabClosed * echom "tabclosed:".expand("<afile>").":".expand("<amatch>").":".tabpagenr()')
nvim('command', 'tabedit Xtestfile')
eq({2, 2}, nvim('eval', '[tabpagenr(), tabpagenr("$")]'))
eq("\ntabclosed:2:2:1", nvim('command_output', 'close'))
eq("tabclosed:2:2:1", nvim('command_output', 'close'))
eq({1, 1}, nvim('eval', '[tabpagenr(), tabpagenr("$")]'))
end)
end)

View File

@ -7,14 +7,14 @@ describe('TabNewEntered', function()
it('matches when entering any new tab', function()
clear()
nvim('command', 'au! TabNewEntered * echom "tabnewentered:".tabpagenr().":".bufnr("")')
eq("\ntabnewentered:2:2", nvim('command_output', 'tabnew'))
eq("\n\"test.x2\" [New File]\ntabnewentered:3:3", nvim('command_output', 'tabnew test.x2'))
eq("tabnewentered:2:2", nvim('command_output', 'tabnew'))
eq("tabnewentered:3:3", nvim('command_output', 'tabnew test.x2'))
end)
end)
describe('with FILE as <afile>', function()
it('matches when opening a new tab for FILE', function()
nvim('command', 'au! TabNewEntered Xtest-tabnewentered echom "tabnewentered:match"')
eq('\n"Xtest-tabnewentered" [New File]\ntabnewentered:4:4\ntabnewentered:match',
eq('tabnewentered:4:4\ntabnewentered:match',
nvim('command_output', 'tabnew Xtest-tabnewentered'))
end)
end)
@ -24,7 +24,7 @@ describe('TabNewEntered', function()
nvim('command', 'au! TabNewEntered * echom "entered"')
nvim('command', 'tabnew test.x2')
nvim('command', 'split')
eq('\nentered', nvim('command_output', 'execute "normal \\<C-W>T"'))
eq('entered', nvim('command_output', 'execute "normal \\<C-W>T"'))
end)
end)
end)

View File

@ -16,8 +16,8 @@ describe('sign', function()
nvim('command', 'sign place 34 line=3 name=Foo buffer='..buf2)
-- now unplace without specifying a buffer
nvim('command', 'sign unplace 34')
eq("\n--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf1))
eq("\n--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf2))
eq("--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf1))
eq("--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf2))
end)
end)
end)

View File

@ -644,7 +644,7 @@ local function alter_slashes(obj)
end
local function hexdump(str)
local len = string.len( str )
local len = string.len(str)
local dump = ""
local hex = ""
local asc = ""
@ -652,22 +652,20 @@ local function hexdump(str)
for i = 1, len do
if 1 == i % 8 then
dump = dump .. hex .. asc .. "\n"
hex = string.format( "%04x: ", i - 1 )
hex = string.format("%04x: ", i - 1)
asc = ""
end
local ord = string.byte( str, i )
hex = hex .. string.format( "%02x ", ord )
local ord = string.byte(str, i)
hex = hex .. string.format("%02x ", ord)
if ord >= 32 and ord <= 126 then
asc = asc .. string.char( ord )
asc = asc .. string.char(ord)
else
asc = asc .. "."
end
end
return dump .. hex
.. string.rep( " ", 8 - len % 8 ) .. asc
return dump .. hex .. string.rep(" ", 8 - len % 8) .. asc
end
local module = {

View File

@ -849,7 +849,7 @@ describe('Ex commands coloring support', function()
{EOB:~ }|
|
]])
eq('\nError detected while processing :\nE605: Exception not caught: 42',
eq('Error detected while processing :\nE605: Exception not caught: 42',
meths.command_output('messages'))
end)
it('errors out when failing to get callback', function()