Async code runner for Neovim. Powered by Zig for near-zero overhead execution with non-blocking output streaming.
Zignite.nvim is a code runner for Neovim focused on low-latency execution and interactive output. It uses terminal buffers in floats, splits, vsplits, and tabs, so programs keep stdin, ANSI colors, and real-time streaming. A Zig backend handles command execution, filetype normalization, build/run resolution, project parsing, quickfix processing, and command detection.
- Features
- Requirements
- Architecture
- Installation
- Configuration
- Usage
- Troubleshooting
- Development
- License
- Interactive Terminal Output: Runner windows are real terminals, so stdin-driven programs continue to work.
- Full ANSI Colors: Compiler errors and logs retain their rich coloring.
- Zig Backend: Build resolution, runner resolution, command detection, project parsing, quickfix processing, and execution support run through a native backend.
- Safety Timeouts: Commands that exceed the configured timeout are terminated by the Zig backend.
- Quickfix Integration: Non-zero exits can populate the quickfix list so errors are easy to jump through.
- Unified Zig Daemon: Reuses one backend daemon for config sync, build/run resolve, detection, project parsing, and quickfix processing to reduce repeat-run latency.
- Build System Support: Supports
cargo,zig build,npm,make, CMake, Meson, Bazel, Maven, Gradle, Go modules/workspaces, and more. - Interactive Command Picker: Choose between
run,test,build,clean, and detected project commands. - Project Detection: Detects project roots so project-aware commands run from the correct working directory.
- Smart Language Detection: Uses Neovim filetype first, then falls back to file extension/shebang for mixed-language folders.
- Platform: Linux and macOS only.
- Neovim >= 0.10
- Zig
0.16.0
The backend targets Zig 0.16.0. Earlier versions will not compile.
The current architecture is intentionally split:
- Lua owns the Neovim frontend layer: setup, config, RPC transport, picker/window UI, and thin controller flow
- Zig owns the backend layer: config interpretation, filetype normalization, build/run resolution, project parsing, system queries, detection, quickfix processing, and execution support
For contributors: CONTRIBUTING.md
The build step is required to compile the Zig backend.
Example files in this repo:
example_config.lua: full reference config with more options than most users need
Lazy.nvim
{
"valonmulolli/zignite.nvim",
build = "cd zig && zig build -Doptimize=ReleaseFast",
config = function()
require("zignite").setup({})
end,
}If you manage keymaps through Lazy's keys field, use Lazy's key format:
{
"valonmulolli/zignite.nvim",
keys = {
{ "<leader>r", "<cmd>RunFile<cr>", mode = "n", desc = "Run file" },
{ "<leader>rb", "<cmd>RunBuildSelect<cr>", mode = "n", desc = "Build picker" },
{ "<leader>rl", "<cmd>RunLive<cr>", mode = "n", desc = "Run live/watch command" },
},
config = function()
require("zignite").setup({
keymaps = {}, -- avoid duplicate mappings when Lazy keys are used
})
end,
}vim.pack (Neovim >= 0.10)
-- Register the build hook before vim.pack.add() to catch install events
vim.api.nvim_create_autocmd('PackChanged', {
group = vim.api.nvim_create_augroup('zignite-build', { clear = true }),
callback = function(ev)
if ev.data.spec.name == 'zignite.nvim' then
-- :wait() ensures the build completes before the next statement
vim.system({ 'zig', 'build', '-Doptimize=ReleaseFast' }, { cwd = ev.data.path .. '/zig' }):wait()
end
end,
})
vim.pack.add({
{ src = 'https://github.com/valonmulolli/zignite.nvim', version = 'master' },
})Nix / NixOS (flake)
This repo now exposes a flake package that builds the Zig backend during packaging.
flake.nix input:
zignite = {
url = "github:valonmulolli/zignite.nvim";
inputs.nixpkgs.follows = "nixpkgs";
};Home Manager Neovim plugins:
programs.neovim = {
enable = true;
plugins = [
inputs.zignite.packages.${pkgs.system}.default
];
};Zignite works out of the box for 20+ languages. See example_config.lua for all available options.
:RunFile: Run the current file using the filetype runner (single-file flow).:RunCode: Run the current visual selection, or the current file when used without a visual range.:RunBuild <name> <mode>: Run a specific build command by name (e.g.,:RunBuild test vsplit). Supports tab completion for command names.:RunBuildSelect: Open an interactive picker to choose a command (build, test, run, etc.).:RunBuildLast: Repeat the most recent:RunBuild/picker command for the current filetype.:RunLive: Run the best live/watch command for current filetype (live,dev,watch,serve,start,preview).:RunClose: Close runner output (close_behavior="stop"stops jobs,"hide"only closes output).:StopCode: Terminate the currently running process.
For compiled languages such as Rust, Zig, C++, and Go, you often want more than a single-file runner. Use <leader>rb (default) to open the build command picker:
build → cargo build
check → cargo check
▶ run → cargo run
test → cargo test
clean → cargo clean
For Zig projects, the default commands also include a project-level check path:
build → zig build
check → zig build check
run → zig build run
test → zig build test
Use:
j/k(or arrow keys) to navigateEnterto select/to start inline filter in the same picker popup (type to filter,Enterapply,Esccancel)cto clear filterrto run the previous build command for current filetype
On narrower screens the picker automatically switches to a smaller compact layout while staying in a single window. The selected command still appears on the bottom command line.
To use external prompt modes instead of inline filtering, set:
picker = {
filter_input = "ui", -- use vim.ui.input popup
-- or
filter_input = "cmdline",
layout = "compact", -- optional: force compact picker layout
}Picker commands are built from your configured build_commands.<filetype> plus
auto-detected commands when available. Detection currently covers:
- tool commands for
zig,go,rust,c,cpp,python,odin, andfortran - project commands for
Makefile,package.json, Maven, Gradle, CMake, Meson, Bazel,Cargo.toml,go.mod,go.work, andpyproject.toml - Python project workflows for
uv,requirements.txt/pip, and conda (environment.yml/environment.yaml)
Configured commands always take priority when names overlap.
Auto-detection is enabled by default. You can disable specific detectors:
detect = {
go = false,
rust = false,
c_cpp_make = false,
bazel_project = false,
}Detection, build resolution, run resolution, config sync, and quickfix reuse a
persistent Zig backend daemon (--daemon) for lower overhead. The plugin
builds this backend during installation, and the resolver path expects it to be
available.
RunBuild, RunLive, and :RunBuild completion use configured commands plus
cached detected commands first, then refresh detection in the background. That
keeps command dispatch responsive even when tool help output still has to be
parsed.
Picker detection runtime defaults:
detect_runtime = {
async_picker = true,
cache_ttl_ms = 15000,
live_merge = true,
}async_picker = true::RunBuildSelectopens immediately from configured/cached commands.cache_ttl_ms: stale threshold used before triggering refresh.live_merge = true: refreshed detected commands are merged into the open picker without closing it.
Python project support is intentionally limited to:
uvrequirements.txt/pip- conda (
environment.yml/environment.yaml)
Any build command can request runtime arguments by using $zignite_args in the
command template. When selected, the picker prompts for input and runs the
expanded command.
build_commands = {
python = {
pip = "pip install $zignite_args",
},
}zig fetch integration: The zig fetch command is detected automatically and
prompts for URL/path input. Paste a plain GitHub repo URL or <owner>/<repo>,
and Zignite expands it to the saved Zig form:
https://github.com/<owner>/<repo>
↓
zig fetch --save git+https://github.com/<owner>/<repo>
- Use
:RunFilefor fast single-file feedback. - Use
:RunBuild runwhen you explicitly want project-wide startup/build behavior. - For Zig,
:RunFileprefers project build execution (zig build ...) only when the source imports build-defined modules that requirebuild.zig.
On non-zero exit, terminal output feeds into the quickfix list so you can jump through errors. The pipeline uses Zig processing when available, with an automatic Lua fallback.
quickfix = {
enabled = true,
max_lines = 1000, -- Keep only last N lines from terminal output
max_bytes = 262144, -- Byte cap for quickfix processing
}You can use these variables in your custom runner commands:
| Variable | Description | Example |
|---|---|---|
$file |
Full file path | /home/user/main.py |
$fileName |
Filename with extension | main.py |
$fileNameWithoutExt |
Filename without extension | main |
$dir |
Directory path | /home/user/project |
$projectName |
Project root folder name | my-project-cli |
$projectNameShort |
Project name without suffix | my-project |
Run zig build -Doptimize=ReleaseFast inside the plugin's zig/ directory manually.
The Zig backend auto-detects the correct build/run command for supported filetypes. If nothing appears:
- Check that your
build.zig,Cargo.toml,Makefile,CMakeLists.txt,package.json, etc. are in the project root. - The filetype must be one of the supported languages (zig, rust, go, c, cpp, python, odin, fortran, java, kotlin, javascript, typescript).
- If your project uses an unsupported build system, add a custom command in your
setup via
build_commandsorrunners:runners = { my_lang = "my-compiler $file" }
The Zig backend picks go run . by default (whole-module execution). For
single-file feedback, use :RunFile which uses the configured runner
(go run $file). Switch to :RunBuild run when you want full module execution.
If you define mappings via Lazy.nvim keys, use { "<lhs>", "<rhs>", mode = "n", ... } format.
The { "n", "<lhs>", "<rhs>", ... } format is for require("zignite").setup({ keymaps = { ... } }).
lua zig/test/runner.luacd zig
zig build bench # defaults to 3000 iterations
zig build bench-fast # defaults to 1000 iterations
zig build bench -- 10000See CONTRIBUTING.md for benchmark details.
MIT License - see LICENSE file for details.