mirror of
https://github.com/neovim/neovim.git
synced 2026-07-13 12:50:31 +00:00
fix(lua): vim.wait(0) does not call loop_poll #39679
Problem:
Regression from c822a2657c: `vim.wait(0)` does not call `loop_poll`,
so `vim.wait(1)` is needed to "yield" from Lua.
Solution:
- Ensure that `vim._core.loop_poll()` is always called, even when `time=0`.
- Document how to interrupt Lua code (ctrl-c).
ref https://github.com/neovim/neovim/issues/6800
This commit is contained in:
@@ -459,8 +459,8 @@ typed. The `vgetc()` function is used for this. It also handles mapping.
|
||||
|
||||
What we consider the "Nvim event loop" is actually a wrapper around `uv_run` to
|
||||
handle both the `fast_events` queue and possibly (a suitable subset of) deferred
|
||||
events. Therefore "raw" `vim.uv.run()` is often not enough to "yield" from Lua
|
||||
plugins; instead they can call `vim.wait(0)`.
|
||||
events. Therefore "raw" `vim.uv.run()` is often not enough to yield from Lua;
|
||||
instead call `vim.wait(0)` and check its result.
|
||||
|
||||
Updating the screen is mostly postponed until a command or a sequence of
|
||||
commands has finished. The work is done by `update_screen()`, which calls
|
||||
|
||||
@@ -87,6 +87,22 @@ Finally, you can include Lua code in a Vimscript file by putting it inside a
|
||||
end
|
||||
EOF
|
||||
<
|
||||
*lua-guide-interrupt*
|
||||
Lua cannot normally be interrupted by |CTRL-C| (Lua does not implicitly
|
||||
"yield" to Nvim). To allow the user to interrupt potentially slow loops,
|
||||
periodically call |vim.wait()|. Passing `interval=0` skips the dummy timer
|
||||
setup, minimizing per-call overhead in a tight loop: >lua
|
||||
|
||||
while true do
|
||||
-- ...work...
|
||||
local _, code = vim.wait(0, nil, 0)
|
||||
if code == -2 then -- CTRL-C.
|
||||
break
|
||||
end
|
||||
end
|
||||
<
|
||||
See also: https://github.com/neovim/neovim/issues/6800
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
Using Lua files on startup *lua-guide-config*
|
||||
|
||||
|
||||
@@ -1499,6 +1499,15 @@ vim.wait({time}, {callback}, {interval}, {fast_only}) *vim.wait()*
|
||||
if vim.wait(10000, function() return vim.g.timer_result end) then
|
||||
print('Only waiting a little bit of time!')
|
||||
end
|
||||
|
||||
-- Yield via vim.wait() to allow CTRL-C to interrupt Lua code.
|
||||
while true do
|
||||
-- ...work...
|
||||
local _, code = vim.wait(0, nil, 0)
|
||||
if code == -2 then -- CTRL-C.
|
||||
break
|
||||
end
|
||||
end
|
||||
<
|
||||
|
||||
Parameters: ~
|
||||
|
||||
@@ -128,10 +128,6 @@ gD Goto global Declaration. When the cursor is on a
|
||||
1gD Like "gD", but ignore matches inside a {} block that
|
||||
ends before the cursor position.
|
||||
|
||||
*CTRL-C*
|
||||
CTRL-C Interrupt current (search) command.
|
||||
In Normal mode, any pending command is aborted.
|
||||
|
||||
*:noh* *:nohlsearch*
|
||||
:noh[lsearch] Stop the highlighting for the 'hlsearch' option. It
|
||||
is automatically turned back on when using a search
|
||||
|
||||
@@ -11,6 +11,16 @@ Various commands *various*
|
||||
==============================================================================
|
||||
1. Various commands *various-cmds*
|
||||
|
||||
*CTRL-C*
|
||||
CTRL-C Interrupt the current command, script, mapping,
|
||||
prompt, or search.
|
||||
|
||||
In Normal mode, any pending command is aborted. During
|
||||
a search, interrupts the search.
|
||||
|
||||
Lua code cannot be interrupted unless it "yields" via
|
||||
|vim.wait()|.
|
||||
|
||||
*CTRL-L*
|
||||
CTRL-L Clears and redraws the screen. The redraw may happen
|
||||
later, after processing typeahead.
|
||||
|
||||
@@ -69,6 +69,15 @@ vim._extra = {
|
||||
--- if vim.wait(10000, function() return vim.g.timer_result end) then
|
||||
--- print('Only waiting a little bit of time!')
|
||||
--- end
|
||||
---
|
||||
--- -- Yield via vim.wait() to allow CTRL-C to interrupt Lua code.
|
||||
--- while true do
|
||||
--- -- ...work...
|
||||
--- local _, code = vim.wait(0, nil, 0)
|
||||
--- if code == -2 then -- CTRL-C.
|
||||
--- break
|
||||
--- end
|
||||
--- end
|
||||
--- ```
|
||||
---
|
||||
--- @param time number Number of milliseconds to wait. Must be non-negative number, any fractional
|
||||
@@ -156,19 +165,26 @@ function vim.wait(time, callback, interval, fast_only)
|
||||
local poll_timeout = -1
|
||||
if has_deadline then
|
||||
local remaining_ms = time - (vim.uv.hrtime() - start) / 1e6
|
||||
if remaining_ms <= 0 then
|
||||
cleanup()
|
||||
return false, -1
|
||||
end
|
||||
|
||||
-- loop_poll() takes an integer timeout, so cap each individual poll
|
||||
-- while still honoring larger overall waits.
|
||||
poll_timeout = math.min(math.ceil(remaining_ms), vim._maxint)
|
||||
-- loop_poll() takes an integer timeout, so cap each individual poll while still honoring
|
||||
-- larger overall waits. Always poll at least once, so vim.wait(0) still pumps the event loop
|
||||
-- (lets user code "yield" to CTRL-C).
|
||||
poll_timeout = math.max(0, math.min(math.ceil(remaining_ms), vim._maxint))
|
||||
end
|
||||
|
||||
-- The dummy timer wakes `vim._core.loop_poll()` on interval boundaries. Without it,
|
||||
-- polling would only resume for unrelated events or the final timeout.
|
||||
vim._core.loop_poll(poll_timeout, fast_only)
|
||||
|
||||
-- Check deadline AFTER polling (mirrors LOOP_PROCESS_EVENTS_UNTIL behavior).
|
||||
-- `got_int` may have been set during poll; let check_interrupt() catch it.
|
||||
if has_deadline and time - (vim.uv.hrtime() - start) / 1e6 <= 0 then
|
||||
if vim._core.check_interrupt() then
|
||||
cleanup()
|
||||
return false, -2
|
||||
end
|
||||
cleanup()
|
||||
return false, -1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -2567,6 +2567,26 @@ describe('lua stdlib', function()
|
||||
end)
|
||||
end)
|
||||
|
||||
it('lets CTRL-C interrupt a Lua loop', function()
|
||||
api.nvim_set_var('channel', api.nvim_get_chan_info(0).id)
|
||||
exec_lua([[
|
||||
function _G.Loop()
|
||||
vim.rpcnotify(vim.g.channel, 'ready')
|
||||
while true do
|
||||
local _, code = vim.wait(0, nil, 0)
|
||||
if code == -2 then
|
||||
vim.rpcnotify(vim.g.channel, 'wait', code)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
]])
|
||||
feed(':lua _G.Loop()<CR>')
|
||||
eq({ 'notification', 'ready', {} }, next_msg(500))
|
||||
feed('<C-C>')
|
||||
eq({ 'notification', 'wait', { -2 } }, next_msg(500))
|
||||
end)
|
||||
|
||||
it('fails in fast callbacks #26122', function()
|
||||
local screen = Screen.new(80, 10)
|
||||
exec_lua([[
|
||||
|
||||
Reference in New Issue
Block a user