A better way to run system commands in Neovim
Published: 03-07-25
Running commands in NVIM
‘system’ and ‘schedule’ functions
- from the docs
function vim.system(cmd: string[], opts?: vim.SystemOpts, on_exit?: fun(out: vim.SystemCompleted))
-> Object: vim.SystemObj
Runs a system command or throws an error if
cmd
cannot be run.
function vim.schedule(fn: fun())
Schedules
fn
to be invoked soon by the main event-loop. Useful to avoid |textlock| or other temporary restrictions.
Chaining them together
vim.system({ "some command" }, { text = true }, function(obj)
vim.schedule(function()
if #obj.stderr > 0 then
vim.api.nvim_notify("Something failed...", vim.log.levels.ERROR, {})
end
if #obj.stdout > 0 then
vim.api.nvim_notify("Yay it worked...", vim.log.levels.INFO, {})
end
end)
end)
Example with Dune commands
local function get_project_name(dune_root)
local handle = vim.loop.fs_scandir(dune_root)
if handle then
while true do
local name, type = vim.loop.fs_scandir_next(handle)
if not name then break end
if type == "file" and name:match("(.+)%.opam$") then
return name:match("(.+)%.opam$") -- Extracts <proj_name>
end
end
end
return nil
end
local function duneExec()
local cwd = vim.fn.getcwd()
local dune_root = vim.lsp.buf.list_workspace_folders()[1]
vim.cmd("cd " .. dune_root)
local project_name = get_project_name(dune_root)
if project_name then
vim.api.nvim_notify("Executing dune project: " .. project_name, vim.log.levels.INFO, {})
else
vim.api.nvim_notify("No .opam file found in " .. dune_root, vim.log.levels.WARN, {})
return nil
end
vim.system({ "dune", "exec", project_name }, { text = true }, function(obj)
vim.schedule(function()
if #obj.stderr > 0 then
vim.api.nvim_notify("dune exec failed...", vim.log.levels.ERROR, {})
vim.api.nvim_notify(obj.stderr, vim.log.levels.WARN, {})
end
if #obj.stdout > 0 then
vim.api.nvim_notify(obj.stdout, vim.log.levels.INFO, {})
end
end)
end)
vim.cmd("cd " .. cwd)
end