Setting up the Debug Adapter Protocol (DAP) in Neovim transforms C and C++ workflows by integrating full-featured debugging—breakpoints, call stacks, REPL execution, and variable evaluation—directly into your modal editing experience.
Rather than maintaining static code snippets in this post, you can inspect my active setup directly on GitHub:
👉 VincentTam/nvim-config: lua/plugins/dap.lua
Core Architecture
The setup relies on three primary components managed through
lazy.nvim:
mfussenegger/nvim-dap: The foundational DAP client implementing protocol communication with native debuggers.rcarriga/nvim-dap-ui: Provides layout management, split frames, and floating windows for scopes, stacks, and watches.williamboman/mason.nvim: Handles system binaries and language tool installation.
Ergonomic Navigation: Shift + Arrow Shortcuts
While traditional DAP keymaps often use multi-key leader sequences (e.g.,
<Leader>dn), navigating execution during a active step-by-step session is much
faster with single-key modifications.
Inspired by a tip in nvim-dap README’s, you can map stepping operations
directly to directional Shift + Arrow combinations:
<S-Up>: Continue execution to next breakpoint (dap.continue())<S-Down>: Step Over statement (dap.step_over())<S-Right>: Step Into function (dap.step_into())<S-Left>: Step Out / Up stack frame (dap.step_out())
These bindings allow rapid execution stepping without leaving home-row focus or typing multi-key combinations repeatedly while inspecting loop iterations.
Direct GDB Integration & DAP Quirks
Connecting GDB through native nvim-dap requires specific configurations that
are not fully detailed in the default documentation:
1. Modern GDB DAP Flag Modern GDB (v14+) ships with native DAP support.
Instead of relying on intermediate wrapper scripts, configure the GDB adapter
directly with the --interpreter=dap flag:
dap.adapters.gdb = {
type = "executable",
command = "gdb",
args = { "--interpreter=dap", "--eval-command", "set print pretty on" }
}
Then configure the C/C++ file debugging.
local c_cpp_config = {
{
name = "Launch file",
type = "gdb",
request = "launch",
program = function()
return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/', 'file')
end,
cwd = '${workspaceFolder}',
stopAtBeginningOfMainSubprogram = false,
-- Add this line to map current workspace to root relative paths:
sourceFileMap = {
["${workspaceFolder}"] = "${workspaceFolder}"
},
},
}
dap.configurations.c = c_cpp_config
dap.configurations.cpp = c_cpp_config
I used Gemini to generate this file under ~/.config/nvim. It skipped some
lines in the official config wiki. It has suggested the sourceFileMap
for suppressing some errors that appeared when I was using this plugin.
However, after doing so, the same red cross was still there.
2. REPL Function Calls & Stdout Flushing
When evaluating expressions inside the [dap-repl] console, calling
non-returning void C functions or functions containing printf statements
requires explicit GDB execution commands:
- Prefix Calls: Execute functions with explicit
callsyntax (e.g.,call displayMaze(maze)) so DAP routes the request directly to GDB rather than treating it as a standard variable evaluation. - Stream Flushing: C output buffers often delay rendering to standard
output. Issuing
call fflush(0)directly inside the REPL forces pending stdout buffers to flush immediately into the debug output window.