mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
Merge #11159 'API: nvim_exec'
API: nvim_exec: function to source multiline Vimscript ("anonymous :source")
This commit is contained in:
commit
735d89d09e
@ -25,6 +25,7 @@
|
|||||||
#include "nvim/highlight.h"
|
#include "nvim/highlight.h"
|
||||||
#include "nvim/window.h"
|
#include "nvim/window.h"
|
||||||
#include "nvim/types.h"
|
#include "nvim/types.h"
|
||||||
|
#include "nvim/ex_cmds2.h"
|
||||||
#include "nvim/ex_docmd.h"
|
#include "nvim/ex_docmd.h"
|
||||||
#include "nvim/screen.h"
|
#include "nvim/screen.h"
|
||||||
#include "nvim/memline.h"
|
#include "nvim/memline.h"
|
||||||
@ -72,10 +73,70 @@ void api_vim_free_all_mem(void)
|
|||||||
map_free(String, handle_T)(namespace_ids);
|
map_free(String, handle_T)(namespace_ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes Vimscript (multiline block of Ex-commands), like anonymous
|
||||||
|
/// |:source|.
|
||||||
|
///
|
||||||
|
/// Optionally returns (non-error, non-shell |:!|) output.
|
||||||
|
///
|
||||||
|
/// On execution error: fails with VimL error, does not update v:errmsg.
|
||||||
|
///
|
||||||
|
/// @see |execute()|
|
||||||
|
/// @see |nvim_command()|
|
||||||
|
///
|
||||||
|
/// @param src Vimscript code
|
||||||
|
/// @param output Capture and return all (non-error, non-shell |:!|) output
|
||||||
|
/// @param[out] err Error details (Vim error), if any
|
||||||
|
String nvim_exec(String src, Boolean output, Error *err)
|
||||||
|
FUNC_API_SINCE(7)
|
||||||
|
{
|
||||||
|
if (!output) {
|
||||||
|
try_start();
|
||||||
|
do_source_str(src.data, "nvim_exec()");
|
||||||
|
try_end(err);
|
||||||
|
return (String)STRING_INIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int save_msg_silent = msg_silent;
|
||||||
|
garray_T *const save_capture_ga = capture_ga;
|
||||||
|
garray_T capture_local;
|
||||||
|
ga_init(&capture_local, 1, 80);
|
||||||
|
|
||||||
|
try_start();
|
||||||
|
msg_silent++;
|
||||||
|
capture_ga = &capture_local;
|
||||||
|
do_source_str(src.data, "nvim_exec()");
|
||||||
|
capture_ga = save_capture_ga;
|
||||||
|
msg_silent = save_msg_silent;
|
||||||
|
try_end(err);
|
||||||
|
|
||||||
|
if (ERROR_SET(err)) {
|
||||||
|
goto theend;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (capture_local.ga_len > 1) {
|
||||||
|
String s = (String){
|
||||||
|
.data = capture_local.ga_data,
|
||||||
|
.size = (size_t)capture_local.ga_len,
|
||||||
|
};
|
||||||
|
// redir usually (except :echon) prepends a newline.
|
||||||
|
if (s.data[0] == '\n') {
|
||||||
|
memmove(s.data, s.data + 1, s.size - 1);
|
||||||
|
s.data[s.size - 1] = '\0';
|
||||||
|
s.size = s.size - 1;
|
||||||
|
}
|
||||||
|
return s; // Caller will free the memory.
|
||||||
|
}
|
||||||
|
theend:
|
||||||
|
ga_clear(&capture_local);
|
||||||
|
return (String)STRING_INIT;
|
||||||
|
}
|
||||||
|
|
||||||
/// Executes an ex-command.
|
/// Executes an ex-command.
|
||||||
///
|
///
|
||||||
/// On execution error: fails with VimL error, does not update v:errmsg.
|
/// On execution error: fails with VimL error, does not update v:errmsg.
|
||||||
///
|
///
|
||||||
|
/// @see |nvim_exec()|
|
||||||
|
///
|
||||||
/// @param command Ex-command string
|
/// @param command Ex-command string
|
||||||
/// @param[out] err Error details (Vim error), if any
|
/// @param[out] err Error details (Vim error), if any
|
||||||
void nvim_command(String command, Error *err)
|
void nvim_command(String command, Error *err)
|
||||||
@ -378,7 +439,7 @@ theend:
|
|||||||
return (String)STRING_INIT;
|
return (String)STRING_INIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluates a VimL expression (:help expression).
|
/// Evaluates a VimL |expression|.
|
||||||
/// Dictionaries and Lists are recursively expanded.
|
/// Dictionaries and Lists are recursively expanded.
|
||||||
///
|
///
|
||||||
/// On execution error: fails with VimL error, does not update v:errmsg.
|
/// On execution error: fails with VimL error, does not update v:errmsg.
|
||||||
|
@ -3015,11 +3015,78 @@ static FILE *fopen_noinh_readbin(char *filename)
|
|||||||
return fdopen(fd_tmp, READBIN);
|
return fdopen(fd_tmp, READBIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char_u *buf;
|
||||||
|
size_t offset;
|
||||||
|
} GetStrLineCookie;
|
||||||
|
|
||||||
/// Read the file "fname" and execute its lines as EX commands.
|
/// Get one full line from a sourced string (in-memory, no file).
|
||||||
|
/// Called by do_cmdline() when it's called from do_source_str().
|
||||||
|
///
|
||||||
|
/// @return pointer to allocated line, or NULL for end-of-file or
|
||||||
|
/// some error.
|
||||||
|
static char_u *get_str_line(int c, void *cookie, int indent, bool do_concat)
|
||||||
|
{
|
||||||
|
GetStrLineCookie *p = cookie;
|
||||||
|
size_t i = p->offset;
|
||||||
|
if (strlen((char *)p->buf) <= p->offset) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
while (!(p->buf[i] == '\n' || p->buf[i] == '\0')) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
char buf[2046];
|
||||||
|
char *dst;
|
||||||
|
dst = xstpncpy(buf, (char *)p->buf + p->offset, i - p->offset);
|
||||||
|
if ((uint32_t)(dst - buf) != i - p->offset) {
|
||||||
|
smsg(_(":source error parsing command %s"), p->buf);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
buf[i - p->offset] = '\0';
|
||||||
|
p->offset = i + 1;
|
||||||
|
return (char_u *)xstrdup(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes lines in `src` as Ex commands.
|
||||||
|
///
|
||||||
|
/// @see do_source()
|
||||||
|
int do_source_str(const char *cmd, const char *traceback_name)
|
||||||
|
{
|
||||||
|
char_u *save_sourcing_name = sourcing_name;
|
||||||
|
linenr_T save_sourcing_lnum = sourcing_lnum;
|
||||||
|
char_u sourcing_name_buf[256];
|
||||||
|
if (save_sourcing_name == NULL) {
|
||||||
|
sourcing_name = (char_u *)traceback_name;
|
||||||
|
} else {
|
||||||
|
snprintf((char *)sourcing_name_buf, sizeof(sourcing_name_buf),
|
||||||
|
"%s called at %s:%"PRIdLINENR, traceback_name, save_sourcing_name,
|
||||||
|
save_sourcing_lnum);
|
||||||
|
sourcing_name = sourcing_name_buf;
|
||||||
|
}
|
||||||
|
sourcing_lnum = 0;
|
||||||
|
|
||||||
|
GetStrLineCookie cookie = {
|
||||||
|
.buf = (char_u *)cmd,
|
||||||
|
.offset = 0,
|
||||||
|
};
|
||||||
|
const sctx_T save_current_sctx = current_sctx;
|
||||||
|
current_sctx.sc_sid = SID_STR;
|
||||||
|
current_sctx.sc_seq = 0;
|
||||||
|
current_sctx.sc_lnum = save_sourcing_lnum;
|
||||||
|
int retval = do_cmdline(NULL, get_str_line, (void *)&cookie,
|
||||||
|
DOCMD_VERBOSE | DOCMD_NOWAIT | DOCMD_REPEAT);
|
||||||
|
current_sctx = save_current_sctx;
|
||||||
|
sourcing_lnum = save_sourcing_lnum;
|
||||||
|
sourcing_name = save_sourcing_name;
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the file `fname` and executes its lines as Ex commands.
|
||||||
///
|
///
|
||||||
/// This function may be called recursively!
|
/// This function may be called recursively!
|
||||||
///
|
///
|
||||||
|
/// @see do_source_str
|
||||||
|
///
|
||||||
/// @param fname
|
/// @param fname
|
||||||
/// @param check_other check for .vimrc and _vimrc
|
/// @param check_other check for .vimrc and _vimrc
|
||||||
/// @param is_vimrc DOSO_ value
|
/// @param is_vimrc DOSO_ value
|
||||||
@ -3360,6 +3427,8 @@ char_u *get_scriptname(LastSet last_set, bool *should_free)
|
|||||||
_("API client (channel id %" PRIu64 ")"),
|
_("API client (channel id %" PRIu64 ")"),
|
||||||
last_set.channel_id);
|
last_set.channel_id);
|
||||||
return IObuff;
|
return IObuff;
|
||||||
|
case SID_STR:
|
||||||
|
return (char_u *)_(":source (no file)");
|
||||||
default:
|
default:
|
||||||
*should_free = true;
|
*should_free = true;
|
||||||
return home_replace_save(NULL,
|
return home_replace_save(NULL,
|
||||||
|
@ -421,13 +421,12 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
|
|||||||
// If force_abort is set, we cancel everything.
|
// If force_abort is set, we cancel everything.
|
||||||
did_emsg = false;
|
did_emsg = false;
|
||||||
|
|
||||||
/*
|
// KeyTyped is only set when calling vgetc(). Reset it here when not
|
||||||
* KeyTyped is only set when calling vgetc(). Reset it here when not
|
// calling vgetc() (sourced command lines).
|
||||||
* calling vgetc() (sourced command lines).
|
|
||||||
*/
|
|
||||||
if (!(flags & DOCMD_KEYTYPED)
|
if (!(flags & DOCMD_KEYTYPED)
|
||||||
&& !getline_equal(fgetline, cookie, getexline))
|
&& !getline_equal(fgetline, cookie, getexline)) {
|
||||||
KeyTyped = false;
|
KeyTyped = false;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Continue executing command lines:
|
* Continue executing command lines:
|
||||||
|
@ -331,6 +331,7 @@ EXTERN int garbage_collect_at_exit INIT(= false);
|
|||||||
#define SID_NONE -6 // don't set scriptID
|
#define SID_NONE -6 // don't set scriptID
|
||||||
#define SID_LUA -7 // for Lua scripts/chunks
|
#define SID_LUA -7 // for Lua scripts/chunks
|
||||||
#define SID_API_CLIENT -8 // for API clients
|
#define SID_API_CLIENT -8 // for API clients
|
||||||
|
#define SID_STR -9 // for sourcing a string
|
||||||
|
|
||||||
// Script CTX being sourced or was sourced to define the current function.
|
// Script CTX being sourced or was sourced to define the current function.
|
||||||
EXTERN sctx_T current_sctx INIT(= { 0 COMMA 0 COMMA 0 });
|
EXTERN sctx_T current_sctx INIT(= { 0 COMMA 0 COMMA 0 });
|
||||||
|
@ -16,6 +16,8 @@ local parse_context = helpers.parse_context
|
|||||||
local request = helpers.request
|
local request = helpers.request
|
||||||
local source = helpers.source
|
local source = helpers.source
|
||||||
local next_msg = helpers.next_msg
|
local next_msg = helpers.next_msg
|
||||||
|
local tmpname = helpers.tmpname
|
||||||
|
local write_file = helpers.write_file
|
||||||
|
|
||||||
local pcall_err = helpers.pcall_err
|
local pcall_err = helpers.pcall_err
|
||||||
local format_string = helpers.format_string
|
local format_string = helpers.format_string
|
||||||
@ -74,9 +76,127 @@ describe('API', function()
|
|||||||
eq({mode='i', blocking=false}, nvim("get_mode"))
|
eq({mode='i', blocking=false}, nvim("get_mode"))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
describe('nvim_exec', function()
|
||||||
|
it('one-line input', function()
|
||||||
|
nvim('exec', "let x1 = 'a'", false)
|
||||||
|
eq('a', nvim('get_var', 'x1'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it(':verbose set {option}?', function()
|
||||||
|
nvim('exec', 'set nowrap', false)
|
||||||
|
eq('nowrap\n\tLast set from :source (no file)',
|
||||||
|
nvim('exec', 'verbose set wrap?', true))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('multiline input', function()
|
||||||
|
-- Heredoc + empty lines.
|
||||||
|
nvim('exec', "let x2 = 'a'\n", false)
|
||||||
|
eq('a', nvim('get_var', 'x2'))
|
||||||
|
nvim('exec','lua <<EOF\n\n\n\ny=3\n\n\nEOF', false)
|
||||||
|
eq(3, nvim('eval', "luaeval('y')"))
|
||||||
|
|
||||||
|
eq('', nvim('exec', 'lua <<EOF\ny=3\nEOF', false))
|
||||||
|
eq(3, nvim('eval', "luaeval('y')"))
|
||||||
|
|
||||||
|
-- Multiple statements
|
||||||
|
nvim('exec', 'let x1=1\nlet x2=2\nlet x3=3\n', false)
|
||||||
|
eq(1, nvim('eval', 'x1'))
|
||||||
|
eq(2, nvim('eval', 'x2'))
|
||||||
|
eq(3, nvim('eval', 'x3'))
|
||||||
|
|
||||||
|
-- Functions
|
||||||
|
nvim('exec', 'function Foo()\ncall setline(1,["xxx"])\nendfunction', false)
|
||||||
|
eq(nvim('get_current_line'), '')
|
||||||
|
nvim('exec', 'call Foo()', false)
|
||||||
|
eq(nvim('get_current_line'), 'xxx')
|
||||||
|
|
||||||
|
-- Autocmds
|
||||||
|
nvim('exec','autocmd BufAdd * :let x1 = "Hello"', false)
|
||||||
|
nvim('command', 'new foo')
|
||||||
|
eq('Hello', request('nvim_eval', 'g:x1'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('non-ASCII input', function()
|
||||||
|
nvim('exec', [=[
|
||||||
|
new
|
||||||
|
exe "normal! i ax \n Ax "
|
||||||
|
:%s/ax/--a1234--/g | :%s/Ax/--A1234--/g
|
||||||
|
]=], false)
|
||||||
|
nvim('command', '1')
|
||||||
|
eq(' --a1234-- ', nvim('get_current_line'))
|
||||||
|
nvim('command', '2')
|
||||||
|
eq(' --A1234-- ', nvim('get_current_line'))
|
||||||
|
|
||||||
|
nvim('exec', [[
|
||||||
|
new
|
||||||
|
call setline(1,['xxx'])
|
||||||
|
call feedkeys('r')
|
||||||
|
call feedkeys('ñ', 'xt')
|
||||||
|
]], false)
|
||||||
|
eq('ñxx', nvim('get_current_line'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('execution error', function()
|
||||||
|
eq('Vim:E492: Not an editor command: bogus_command',
|
||||||
|
pcall_err(request, 'nvim_exec', 'bogus_command', false))
|
||||||
|
eq('', nvim('eval', 'v:errmsg')) -- v:errmsg was not updated.
|
||||||
|
eq('', eval('v:exception'))
|
||||||
|
|
||||||
|
eq('Vim(buffer):E86: Buffer 23487 does not exist',
|
||||||
|
pcall_err(request, 'nvim_exec', 'buffer 23487', false))
|
||||||
|
eq('', eval('v:errmsg')) -- v:errmsg was not updated.
|
||||||
|
eq('', eval('v:exception'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('recursion', function()
|
||||||
|
local fname = tmpname()
|
||||||
|
write_file(fname, 'let x1 = "set from :source file"\n')
|
||||||
|
-- nvim_exec
|
||||||
|
-- :source
|
||||||
|
-- nvim_exec
|
||||||
|
request('nvim_exec', [[
|
||||||
|
let x2 = substitute('foo','o','X','g')
|
||||||
|
let x4 = 'should be overwritten'
|
||||||
|
call nvim_exec("source ]]..fname..[[\nlet x3 = substitute('foo','foo','set by recursive nvim_exec','g')\nlet x5='overwritten'\nlet x4=x5\n", v:false)
|
||||||
|
]], false)
|
||||||
|
eq('set from :source file', request('nvim_get_var', 'x1'))
|
||||||
|
eq('fXX', request('nvim_get_var', 'x2'))
|
||||||
|
eq('set by recursive nvim_exec', request('nvim_get_var', 'x3'))
|
||||||
|
eq('overwritten', request('nvim_get_var', 'x4'))
|
||||||
|
eq('overwritten', request('nvim_get_var', 'x5'))
|
||||||
|
os.remove(fname)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('traceback', function()
|
||||||
|
local fname = tmpname()
|
||||||
|
write_file(fname, 'echo "hello"\n')
|
||||||
|
local sourcing_fname = tmpname()
|
||||||
|
write_file(sourcing_fname, 'call nvim_exec("source '..fname..'", v:false)\n')
|
||||||
|
meths.exec('set verbose=2', false)
|
||||||
|
local traceback_output = 'line 0: sourcing "'..sourcing_fname..'"\n'..
|
||||||
|
'line 0: sourcing "'..fname..'"\n'..
|
||||||
|
'hello\n'..
|
||||||
|
'finished sourcing '..fname..'\n'..
|
||||||
|
'continuing in nvim_exec() called at '..sourcing_fname..':1\n'..
|
||||||
|
'finished sourcing '..sourcing_fname..'\n'..
|
||||||
|
'continuing in nvim_exec() called at nvim_exec():0'
|
||||||
|
eq(traceback_output,
|
||||||
|
meths.exec('call nvim_exec("source '..sourcing_fname..'", v:false)', true))
|
||||||
|
os.remove(fname)
|
||||||
|
os.remove(sourcing_fname)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('returns output', function()
|
||||||
|
eq('this is spinal tap',
|
||||||
|
nvim('exec', 'lua <<EOF\n\n\nprint("this is spinal tap")\n\n\nEOF', true))
|
||||||
|
eq('', nvim('exec', 'echo', true))
|
||||||
|
eq('foo 42', nvim('exec', 'echo "foo" 42', true))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
describe('nvim_command', function()
|
describe('nvim_command', function()
|
||||||
it('works', function()
|
it('works', function()
|
||||||
local fname = helpers.tmpname()
|
local fname = tmpname()
|
||||||
nvim('command', 'new')
|
nvim('command', 'new')
|
||||||
nvim('command', 'edit '..fname)
|
nvim('command', 'edit '..fname)
|
||||||
nvim('command', 'normal itesting\napi')
|
nvim('command', 'normal itesting\napi')
|
||||||
|
Loading…
Reference in New Issue
Block a user