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

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

  • cmd is a list of strings: {'git', 'status', '--short'}
  • opts can include text = true (decode as UTF-8 string), cwd, env, stdin
  • on_exit is a callback called with a result table containing code, 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.code is the exit code; non-zero means failure
  • text = true in opts ensures stdout/stderr are decoded as strings, not byte arrays
  • For Neovim < 0.10, use vim.fn.jobstart() or vim.fn.system() instead

Next

How do I enable matchit so % jumps between if/else/end style pairs?