Bazel integration for Neovim. Select targets, build, run, and debug from within the editor using overseer.nvim and toggleterm.nvim.
Designed to mirror the workflow of cmake-tools.nvim so both build systems can share keybindings with project-type detection.
- Target selection via
bazel querywith telescope picker (async, non-blocking) - Build and run targets through overseer tasks with quickfix error parsing
- Debug with
nvim-dap(builds with--config=dbg, resolves binary viabazel cquery) - Refresh
compile_commands.json(hedron/bazel-compile-commands-extractor) - Lualine statusline components
- Persistent state across Neovim restarts (targets, config)
- Auto-discover
--configvalues from.bazelrc - Configurable query scope and DAP adapter
- Neovim >= 0.10
- overseer.nvim
- toggleterm.nvim
- nvim-dap (for debugging)
- telescope.nvim (optional, falls back to
vim.ui.select) - codicons (for lualine components)
- Bazel installed and on
$PATH
{
"david_wright/bazel-tools.nvim",
dependencies = { "stevearc/overseer.nvim", "akinsho/toggleterm.nvim" },
config = function()
require("bazel-tools").setup()
end,
}For local development:
{
dir = "~/projects/bazel-tools.nvim",
name = "bazel-tools.nvim",
dependencies = { "stevearc/overseer.nvim", "akinsho/toggleterm.nvim" },
config = function()
require("bazel-tools").setup({
query_scope = "//src/...",
})
end,
}All options and their defaults:
require("bazel-tools").setup({
bazel_command = "bazel", -- bazel executable
query_scope = "//...", -- scope passed to `bazel query`
build_kind_filter = "rule", -- kind filter for build target selection
run_kind_filter = "cc_binary", -- kind filter for run target selection
test_kind_filter = ".*_test", -- kind filter for test target selection
overseer = {
direction = "right", -- overseer window direction
},
dap = {
adapter = "cppdbg", -- nvim-dap adapter name
build_config = "dbg", -- --config used for debug builds
},
refresh_compdb_target = "//:refresh_compile_commands",
auto_refresh_compdb = {
enabled = false, -- auto-refresh on BUILD save
debounce_ms = 2000, -- debounce interval
},
})| Command | Description |
|---|---|
BazelSelectConfig |
Pick a --config value (default, dbg, ...) |
BazelSelectBuildTarget |
Pick a build target via bazel query |
BazelSelectRunTarget |
Pick a run target (cc_binary rules) |
BazelSelectTestTarget |
Pick a test target (test rules) |
BazelBuild |
Build the selected target |
BazelRun |
Run the selected target |
BazelTest |
Run the selected test target |
BazelSetArgs |
Set per-target run arguments |
BazelDebug |
Build with debug config, resolve binary, launch DAP |
BazelBuildCurrentFile |
Build the target owning the current file |
BazelRefreshCompdb |
Run refresh_compile_commands for clangd |
BazelStopExecutor |
Stop running build tasks |
BazelStopRunner |
Stop running run tasks |
BazelStopTester |
Stop running test tasks |
BazelGotoBuildFile |
Jump to the nearest BUILD file |
BazelGotoLabel |
Navigate to the label under the cursor |
bazel-tools.nvim does not set keybindings. Here is a suggested which-key setup that shares the <leader>c prefix with cmake-tools based on project detection:
local bt = require("bazel-tools")
if bt.is_bazel_project() then
require("which-key").add({
{ "<leader>c", group = "Bazel" },
{ "<leader>cc", "<cmd>BazelSelectConfig<cr>", desc = "Select config" },
{ "<leader>cg", "<cmd>BazelRefreshCompdb<cr>", desc = "Refresh compile_commands" },
{ "<leader>ct", "<cmd>BazelSelectBuildTarget<cr>", desc = "Select build target" },
{ "<leader>cb", "<cmd>wa<cr><cmd>BazelBuild<cr>", desc = "Build" },
{ "<leader>cT", "<cmd>BazelSelectRunTarget<cr>", desc = "Select run target" },
{ "<leader>cr", "<cmd>wa<cr><cmd>BazelRun<cr>", desc = "Run" },
{ "<leader>cd", "<cmd>wa<cr><cmd>BazelDebug<cr>", desc = "Debug" },
{ "<leader>ca", "<cmd>BazelSetArgs<cr>", desc = "Set run args" },
{ "<leader>cf", "<cmd>wa<cr><cmd>BazelBuildCurrentFile<cr>", desc = "Build current file" },
{ "<leader>cx", "<cmd>BazelSelectTestTarget<cr>", desc = "Select test target" },
{ "<leader>cX", "<cmd>wa<cr><cmd>BazelTest<cr>", desc = "Run test" },
{ "<leader>cB", "<cmd>BazelGotoBuildFile<cr>", desc = "Go to BUILD file" },
{ "<leader>cL", "<cmd>BazelGotoLabel<cr>", desc = "Go to label" },
{ "<leader>cs", "<cmd>BazelStopExecutor<cr>", desc = "Stop build" },
{ "<leader>cS", "<cmd>BazelStopRunner<cr>", desc = "Stop run" },
})
elseif bt.is_cmake_project() then
-- cmake-tools keybindings here
endAdd Bazel status to your statusline:
local bt_lualine = require("bazel-tools.lualine").components()
-- Insert into your lualine config
ins_left(bt_lualine.config) -- "Bazel: [dbg]" — click to change
ins_left(bt_lualine.build_button) -- gear icon — click to build
ins_left(bt_lualine.build_target) -- "[//src/app:main]" — click to change
ins_left(bt_lualine.debug_button) -- debug icon — click to debug
ins_left(bt_lualine.run_button) -- run icon — click to run
ins_left(bt_lualine.run_target) -- "[//src/app:main]" — click to change
ins_left(bt_lualine.test_button) -- "Test" — click to test
ins_left(bt_lualine.test_target) -- "[//src/test:unit]" — click to changeAll components are guarded by is_bazel_project() and only display in Bazel workspaces.
local bt = require("bazel-tools")
bt.is_bazel_project() -- true if MODULE.bazel, WORKSPACE, or WORKSPACE.bazel exists
bt.is_cmake_project() -- true if CMakeLists.txt exists
bt.get_config() -- current config name ("default" or "dbg")
bt.get_build_target() -- current build target label or "[none]"
bt.get_run_target() -- current run target label or "[none]"
bt.get_test_target() -- current test target label or "[none]"
bt.get_run_args() -- arguments for current run targetBazelDebug performs three steps:
- Builds the selected run target with
--config=<dap.build_config>(default:dbg) - Resolves the output binary path via
bazel cquery --output=files - Launches nvim-dap with the resolved binary
This requires a .bazelrc debug config that produces debuggable binaries:
build:dbg -c dbg
build:dbg --copt=-O0
build:dbg --copt=-g
build:dbg --strip=never
MIT