How do I run a shell command asynchronously in Neovim 0.10+ without blocking the editor?
Answer
vim.system()
Explanation
vim.system(cmd, opts, on_exit) is Neovim 0.10's built-in API for running external commands asynchronously. Unlike the older vim.fn.jobstart(), it returns a SystemObj with a clean interface, handles stdout/stderr capture, and supports both callback and synchronous modes.
How it works
cmdis a list of strings:{'git', 'status', '--short'}optscan includetext = true(decode as UTF-8 string),cwd,env,stdinon_exitis a callback called with a result table containingcode,stdout,stderr- Without
on_exit, the call is synchronous and blocks until completion - Call
:wait()on the returned object to block and get the result from an async call
Example
Async: display git branch in a notification:
vim.system({'git', 'branch', '--show-current'}, {text = true}, function(result)
if result.code == 0 then
vim.notify('Branch: ' .. result.stdout:gsub('%s+$', ''), vim.log.levels.INFO)
end
end)
Sync (blocks): capture command output inline:
local result = vim.system({'date'}, {text = true}):wait()
vim.print(result.stdout)
Tips
- Prefer async (with callback) for long-running commands to keep the editor responsive
result.codeis the exit code; non-zero means failuretext = truein opts ensures stdout/stderr are decoded as strings, not byte arrays- For Neovim < 0.10, use
vim.fn.jobstart()orvim.fn.system()instead