How do I run an external command asynchronously from Neovim Lua and capture its output?
Answer
vim.system({cmd}, {opts}, callback)
Explanation
vim.system() is Neovim's (0.10+) built-in Lua API for running external processes with clean stdout/stderr capture and optional async callbacks. It replaces older patterns like vim.fn.system() (blocking) and vim.fn.jobstart() (more verbose async).
How it works
- Synchronous: call without a callback and it blocks, returning a result object
- Asynchronous: pass a callback as the third argument; it receives the result when the process exits
- The result object has
.stdout,.stderr,.code(exit code), and.signalfields
Example
" Synchronous: block until command finishes
:lua local out = vim.system({'git', 'log', '--oneline', '-5'}, {text=true}):wait()
:lua print(out.stdout)
" Asynchronous: continue editing, print when done
:lua vim.system({'git', 'log', '--oneline', '-5'}, {text=true}, function(obj)
print(obj.stdout)
end)
Tips
- Pass
{text = true}to automatically decode stdout/stderr as UTF-8 strings instead of raw bytes - Use
:wait()on the returned object for synchronous execution without a callback argument - For simple one-shot shell output,
vim.fn.system('cmd')is still fine in Vimscript;vim.systemshines in async Lua scripts and plugin code - Available since Neovim 0.10 — check with
vim.fn.has('nvim-0.10')