Neovim Core ↔ Lua Runtime: Synchronization of Terminal Output
0 reputation · 23 Mar 2023, 23:39 UTC
0 reputation · 23 Mar 2023, 23:39 UTC
The goal is to understand how the core C implementation of Neovim’s terminal emulator coordinates with the Lua runtime when a terminal process emits rapid output. In practice, high‑frequency writes can block the main event loop, causing the editor to freeze or lose keystrokes. The current design routes all terminal output through the core’s I/O loop, but Lua cannot directly influence the buffering strategy. This separation raises an unresolved decision: should the core expose a non‑blocking, event‑driven terminal API to Lua?
Key constraints include maintaining backward compatibility with existing plugins that use :term and ensuring that any new API does not introduce race conditions between the core and Lua. The uncertainty lies in whether a unified async model can be implemented without breaking the current line‑buffered interface.
Specific questions for investigation:
32775 reputation · 24 Mar 2023, 08:05 UTC
At present Neovim’s core already offers the minimal non‑blocking path you need: vim.api.nvim_out_write() writes directly into the UI buffer and is flushed by the core’s rendering engine as soon as the event loop processes it. Exposing a new “async terminal API” would add a parallel I/O layer, increase the surface for race conditions, and break backward compatibility with existing plugins that rely on the traditional line‑buffered :term interface. Therefore, the recommended approach is to continue using the existing API and to trigger an explicit UI refresh when instant visibility is required.
out_write + redraw is Sufficientvim.api.nvim_out_write('your text\n'). The trailing \n is required to move to the next line.vim.api.nvim_command('redraw') (or redraw! to ignore lazyredraw).redraw only after a few hundred characters to keep performance acceptable.Lua can register callbacks for job output, which is often what you need when dealing with external processes in a terminal. Use vim.fn.jobstart() with the on_stdout and on_stderr options:
local job = vim.fn.jobstart('bash', {
on_stdout = function(_, data)
for _, line in ipairs(data) do
vim.api.nvim_out_write(line .. '\n')
end
end,
on_stderr = function(_, data)
for _, line in ipairs(data) do
vim.api.nvim_err_write('ERR: ' .. line .. '\n')
end
end,
})
The callbacks run in the same event loop, so they cannot interfere with the core’s processing. If you need to perform more expensive work, wrap the callback body in vim.schedule() to defer execution until after the current UI refresh.
out_write once per batch.redraw only when a new UI line is required (e.g., after a prompt).lazyredraw enabled for normal editing; use redraw! only in REPL‑like scenarios.Could you confirm whether you have lazyredraw set or if you run Neovim inside a terminal multiplexer (tmux, screen)? These settings affect when the UI actually flushes output and may change whether redraw! is necessary.
Use comments to ask for clarification. Post a solution as an answer.
32,775 reputation · 24 Mar 2023, 09:57 UTC
It's worth separating two things the discussion conflates: reading output from the child process and rendering it. The libuv pipe read itself is already asynchronous and chunked on the main loop; on_stdout callbacks fire from the event loop, not from a blocking poll. When keystrokes get lost during a flood (e.g. yes in a :term), the bottleneck is almost always per-chunk Lua work plus screen redraw, not the I/O path. So a new core "async terminal API" would not fix the common case — the loop is already event-driven.
Rather than writing or redrawing inside on_stdout, keep the callback minimal: append the chunk to a Lua table, then drain the queue on a vim.schedule or a vim.uv timer tick. This decouples arrival rate from processing cost and lets Neovim's event coalescing do its job. In practice this keeps input responsive under identical load where a naive per-chunk handler does not.
vim.schedule.:help channel-callbacks and :help jobstart for your installed version, and measure before redesigning.