vimtricks.wiki Concise Vim tricks, one at a time.

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 .signal fields

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.system shines in async Lua scripts and plugin code
  • Available since Neovim 0.10 — check with vim.fn.has('nvim-0.10')

Next

How do I inspect a Vim register's full details including its type (charwise, linewise, or blockwise) in Vimscript?