Merge pull request #10690 from bfredl/lua_print

lua: laundry list (crashes and additions)
This commit is contained in:
Björn Linse 2019-08-05 13:59:40 +02:00 committed by GitHub
commit f5d1e0e7b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 176 additions and 43 deletions

View File

@ -400,7 +400,8 @@ is safe to execute API methods. >
end))
A subset of the API is available in direct luv callbacks ("fast" callbacks),
most notably |nvim_get_mode()| and |nvim_input()|.
most notably |nvim_get_mode()| and |nvim_input()|. It is possible to
check whether code is running in this context using |vim.in_fast_event()|.
Example: repeating timer
@ -462,6 +463,14 @@ vim.schedule({callback}) *vim.schedule()*
Schedules {callback} to be invoked soon by the main event-loop. Useful
to avoid |textlock| or other temporary restrictions.
vim.in_fast_event() *vim.in_fast_event()*
Returns true if the code is executing as part of a "fast" event
handler, where most of the API is disabled. These are low-level event
such as luv callbacks |lua-loop-callbacks|, which can be invoked at
any time nvim polls for input. When this returns `false` most API
functions are callable, but can be subjected to other restrictions,
such as |textlock|.
vim.type_idx *vim.type_idx*
Type index for use in |lua-special-tbl|. Specifying one of the
values from |vim.types| allows typing the empty table (it is

View File

@ -223,6 +223,9 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
// schedule
lua_pushcfunction(lstate, &nlua_schedule);
lua_setfield(lstate, -2, "schedule");
// in_fast_event
lua_pushcfunction(lstate, &nlua_in_fast_event);
lua_setfield(lstate, -2, "in_fast_event");
// vim.loop
luv_set_loop(lstate, &main_loop.uv);
@ -323,6 +326,42 @@ void executor_exec_lua(const String str, typval_T *const ret_tv)
nlua_pop_typval(lstate, ret_tv);
}
static void nlua_print_event(void **argv)
{
char *str = argv[0];
const size_t len = (size_t)(intptr_t)argv[1]-1; // exclude final NUL
for (size_t i = 0; i < len;) {
const size_t start = i;
while (i < len) {
switch (str[i]) {
case NUL: {
str[i] = NL;
i++;
continue;
}
case NL: {
// TODO(bfredl): use proper multiline msg? Probably should implement
// print() in lua in terms of nvim_message(), when it is available.
str[i] = NUL;
i++;
break;
}
default: {
i++;
continue;
}
}
break;
}
msg((char_u *)str + start);
}
if (len && str[len - 1] == NUL) { // Last was newline
msg((char_u *)"");
}
xfree(str);
}
/// Print as a Vim message
///
/// @param lstate Lua interpreter state.
@ -362,47 +401,24 @@ static int nlua_print(lua_State *const lstate)
lua_pop(lstate, 1);
}
#undef PRINT_ERROR
lua_pop(lstate, nargs + 1);
ga_append(&msg_ga, NUL);
{
const size_t len = (size_t)msg_ga.ga_len - 1;
char *const str = (char *)msg_ga.ga_data;
for (size_t i = 0; i < len;) {
const size_t start = i;
while (i < len) {
switch (str[i]) {
case NUL: {
str[i] = NL;
i++;
continue;
if (in_fast_callback) {
multiqueue_put(main_loop.events, nlua_print_event,
2, msg_ga.ga_data, msg_ga.ga_len);
} else {
nlua_print_event((void *[]){ msg_ga.ga_data,
(void *)(intptr_t)msg_ga.ga_len });
}
case NL: {
str[i] = NUL;
i++;
break;
}
default: {
i++;
continue;
}
}
break;
}
msg((char_u *)str + start);
}
if (len && str[len - 1] == NUL) { // Last was newline
msg((char_u *)"");
}
}
ga_clear(&msg_ga);
return 0;
nlua_print_error:
emsgf(_("E5114: Error while converting print argument #%i: %.*s"),
curargidx, (int)errmsg_len, errmsg);
ga_clear(&msg_ga);
lua_pop(lstate, lua_gettop(lstate));
return 0;
const char *fmt = _("E5114: Error while converting print argument #%i: %.*s");
size_t len = (size_t)vim_snprintf((char *)IObuff, IOSIZE, fmt, curargidx,
(int)errmsg_len, errmsg);
lua_pushlstring(lstate, (char *)IObuff, len);
return lua_error(lstate);
}
/// debug.debug: interaction with user while debugging.
@ -436,15 +452,20 @@ int nlua_debug(lua_State *lstate)
if (luaL_loadbuffer(lstate, (const char *)input.vval.v_string,
STRLEN(input.vval.v_string), "=(debug command)")) {
nlua_error(lstate, _("E5115: Error while loading debug string: %.*s"));
}
tv_clear(&input);
if (lua_pcall(lstate, 0, 0, 0)) {
} else if (lua_pcall(lstate, 0, 0, 0)) {
nlua_error(lstate, _("E5116: Error while calling debug string: %.*s"));
}
tv_clear(&input);
}
return 0;
}
int nlua_in_fast_event(lua_State *lstate)
{
lua_pushboolean(lstate, in_fast_callback > 0);
return 1;
}
#ifdef WIN32
/// os.getenv: override os.getenv to maintain coherency. #9681
///

View File

@ -75,6 +75,7 @@ describe('vim.loop', function()
exec_lua([[
local timer = vim.loop.new_timer()
timer:start(20, 0, function ()
_G.is_fast = vim.in_fast_event()
timer:close()
vim.api.nvim_set_var("valid", true)
vim.api.nvim_command("echomsg 'howdy'")
@ -89,18 +90,20 @@ describe('vim.loop', function()
{1:~ }|
{2: }|
{3:Error executing luv callback:} |
{3:[string "<nvim>"]:4: E5560: nvim_set_var must not }|
{3:[string "<nvim>"]:5: E5560: nvim_set_var must not }|
{3:be called in a lua loop callback} |
{4:Press ENTER or type command to continue}^ |
]])
feed('<cr>')
eq(false, eval("get(g:, 'valid', v:false)"))
eq(true, exec_lua("return _G.is_fast"))
-- callbacks can be scheduled to be executed in the main event loop
-- where the entire API is available
exec_lua([[
local timer = vim.loop.new_timer()
timer:start(20, 0, vim.schedule_wrap(function ()
_G.is_fast = vim.in_fast_event()
timer:close()
vim.api.nvim_set_var("valid", true)
vim.api.nvim_command("echomsg 'howdy'")
@ -120,6 +123,7 @@ describe('vim.loop', function()
howdy |
]])
eq(true, eval("get(g:, 'valid', v:false)"))
eq(false, exec_lua("return _G.is_fast"))
-- fast (not deferred) API functions are allowed to be called directly
exec_lua([[

View File

@ -14,6 +14,7 @@ local command = helpers.command
local write_file = helpers.write_file
local redir_exec = helpers.redir_exec
local alter_slashes = helpers.alter_slashes
local exec_lua = helpers.exec_lua
local screen
@ -53,11 +54,11 @@ describe('print', function()
v_tblout = setmetatable({}, meta_tblout)
]])
eq('', redir_exec('luafile ' .. fname))
eq('\nE5114: Error while converting print argument #2: [NULL]',
eq('\nE5105: Error while calling lua chunk: E5114: Error while converting print argument #2: [NULL]',
redir_exec('lua print("foo", v_nilerr, "bar")'))
eq('\nE5114: Error while converting print argument #2: Xtest-functional-lua-overrides-luafile:2: abc',
eq('\nE5105: Error while calling lua chunk: E5114: Error while converting print argument #2: Xtest-functional-lua-overrides-luafile:2: abc',
redir_exec('lua print("foo", v_abcerr, "bar")'))
eq('\nE5114: Error while converting print argument #2: <Unknown error: lua_tolstring returned NULL for tostring result>',
eq('\nE5105: Error while calling lua chunk: E5114: Error while converting print argument #2: <Unknown error: lua_tolstring returned NULL for tostring result>',
redir_exec('lua print("foo", v_tblout, "bar")'))
end)
it('prints strings with NULs and NLs correctly', function()
@ -76,6 +77,29 @@ describe('print', function()
eq('\nabc ', redir_exec('lua print("abc", "")'))
eq('\nabc def', redir_exec('lua print("abc", "", "def")'))
end)
it('defers printing in luv event handlers', function()
exec_lua([[
local cmd = ...
function test()
local timer = vim.loop.new_timer()
local done = false
timer:start(10, 0, function()
print("very fast")
timer:close()
done = true
end)
-- be kind to slow travis OS X jobs:
-- loop until we know for sure the callback has been executed
while not done do
os.execute(cmd)
vim.loop.run("nowait") -- fake os_breakcheck()
end
print("very slow")
vim.api.nvim_command("sleep 1m") -- force deferred event processing
end
]], (iswin() and "timeout 1") or "sleep 0.1")
eq('\nvery slow\nvery fast',redir_exec('lua test()'))
end)
end)
describe('debug.debug', function()
@ -183,6 +207,81 @@ describe('debug.debug', function()
{cr:Press ENTER or type command to continue}^ |
]])
end)
it("can be safely exited with 'cont'", function()
feed('<cr>')
feed(':lua debug.debug() print("x")<cr>')
screen:expect{grid=[[
|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
lua_debug> ^ |
]]}
feed("conttt<cr>") -- misspelled cont; invalid syntax
screen:expect{grid=[[
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
lua_debug> conttt |
{E:E5115: Error while loading debug string: (debug comma}|
{E:nd):1: '=' expected near '<eof>'} |
lua_debug> ^ |
]]}
feed("cont<cr>") -- exactly "cont", exit now
screen:expect{grid=[[
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
lua_debug> conttt |
{E:E5115: Error while loading debug string: (debug comma}|
{E:nd):1: '=' expected near '<eof>'} |
lua_debug> cont |
x |
{cr:Press ENTER or type command to continue}^ |
]]}
feed('<cr>')
screen:expect{grid=[[
^ |
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
|
]]}
end)
end)
describe('package.path/package.cpath', function()