Neovim Debugger
Setting up Neovim debugger
In this article you will learn how to setup your Neovim to debug pretty much every programming language out there!
Overview
In this article we will learn how to configure and use nvim-dap
which is a Debug Adapter Protocol client implementation for Neovim. I
will choose C/C++/Rust
languages as a demonstration but this will also
apply to any other language.
What’s different between this article and others?
Most articles regarding this matter is that most of them descriping how to
configure nvim-dap
but not dealing with issues such as:
- What if you want to give the executable name before start debugging?
- What if you want to give arguments to the program?
- What if you don’t want to use
launch.json
to configure every single debugging session? - What if you want to compile before run?
All these issues don’t get attention alot and I think it’s the reason why using a debugger in a text editor is so hard because you have to deal with these issues and more yourself.
My goal here is to simplify these issues and give you the way on how to extend them.
Getting Started
First of you will need to install some plugins using the plugin manager you
prefer, if you are using vim-plug
copy this to your init.vim
, save the file,
source it, and execute :PlugInstall
Plug 'nvim-treesitter/nvim-treesitter', {'do': ':TSUpdate'}
Plug 'mfussenegger/nvim-dap'
Plug 'rcarriga/nvim-dap-ui'
Plug 'theHamsta/nvim-dap-virtual-text'
Plug 'nvim-telescope/telescope-dap.nvim'
A brief description about these plugins:
nvim-treesitter
: An incremental parsing system for programming tools.nvim-dap
: Debug Adapter Protocol client implementation for Neovim.nvim-dap-ui
: A UI for nvim-dap.nvim-dap-virtual-text
: Virtual text support tonvim-dap
usingtreesitter
to find variable definitions.nvim-telescope/telescope-dap.nvim
: Telescope support fornvim-dap
.
Now as you successfully installed these plugins, now let’s configure our
debugger to work with C/C++/Rust
code.
Setting Up
To keep things organized and easy to maintain, I recommend using this
structure, for more information about this structure see :help rtp
, but
basically any .vim
or .lua
file inside after/plugin
gets sourced after
init.vim
.
.
├── after
│ └── plugin
│ └── debugger
│ ├── cpp.lua
│ └── init.lua
├── lua
│ └── keymap.lua
└── init.vim
And here is a note about thes files
lua/keymap.lua
: A simple class to simplify remaps in lua.after/plugin/debugger/init.lua
: Here we keep the general configurations and remaps.after/plugin/debugger/cpp.lua
: here we keep a specific configurations forC/C++/Rust
debugger. We keep this convention for any new language (e.g., go.lua). —
DAP Configuration
Let’s now start configuring our debugger!
Copy this to your lua/keymap.lua
local M = {}
local function bind(op, outer_opts)
outer_opts = outer_opts or {noremap = true}
return function(lhs, rhs, opts)
opts = vim.tbl_extend("force",
outer_opts,
opts or {}
)
vim.keymap.set(op, lhs, rhs, opts)
end
end
M.nmap = bind("n", {noremap = false})
M.nnoremap = bind("n")
M.vnoremap = bind("v")
M.xnoremap = bind("x")
M.inoremap = bind("i")
return M
Now, here is the content of after/plugin/debugger/init.lua
First we include all modules
local Remap = require("keymap")
local dap = require("dap")
local dapui = require("dapui")
local daptext = require("nvim-dap-virtual-text")
local telescope = require("telescope")
local nnoremap = Remap.nnoremap
Then we setup dap-ui
, you can customize it to your taste
vim.fn.sign_define(
"DapBreakpoint",
{ text = "●", texthl = "", linehl = "debugBreakpoint", numhl = "debugBreakpoint" }
)
vim.fn.sign_define(
"DapBreakpointCondition",
{ text = "◆", texthl = "", linehl = "debugBreakpoint", numhl = "debugBreakpoint" }
)
vim.fn.sign_define("DapStopped", { text = "▶", texthl = "", linehl = "debugPC", numhl = "debugPC" })
dap.defaults.fallback.force_external_terminal = true
daptext.setup()
dapui.setup({
layouts = {
{
elements = {
"watches",
{ id = "scopes", size = 0.5 },
{ id = "repl", size = 0.15 },
},
size = 79,
position = "left",
},
{
elements = {
"console",
},
size = 0.25,
position = "bottom",
},
},
controls = {
-- Requires Neovim nightly (or 0.8 when released)
enabled = true,
-- Display controls in this element
element = "repl",
icons = {
pause = "",
play = "",
step_into = "",
step_over = "",
step_out = "",
step_back = "",
run_last = "↻",
terminate = "□",
},
},
})
telescope.load_extension("dap")
And finally your remaps, again customize it to your needs
-- Start
nnoremap("<F9>", function()
dap.continue()
dapui.open()
end)
-- Exit
nnoremap("<F7>", function()
dap.terminate()
dapui.close()
vim.cmd("sleep 50ms")
dap.repl.close()
end)
-- Restart
nnoremap("<F21>", function()
dap.terminate()
vim.cmd("sleep 50ms")
dap.repl.close()
dap.continue()
end) -- Shift F9
nnoremap("<leader>B", function()
dap.set_breakpoint(vim.fn.input("Breakpoint condition: "))
end)
nnoremap("<F8>", function()
dap.toggle_breakpoint()
end)
nnoremap("<F20>", function()
dap.clear_breakpoints()
end) -- SHIFT+F8
nnoremap("<F10>", function()
dap.step_over()
end)
nnoremap("<leader>rc", function()
dap.run_to_cursor()
end)
nnoremap("<F11>", function()
dap.step_into()
end)
nnoremap("<F12>", function()
dap.step_out()
end)
nnoremap("<leader>dp", function()
dap.pause()
end)
nnoremap("<leader>dtc", function()
telescope.extensions.dap.commands({})
end)
C/C++/Rust Debugger
Now, let’s add support to C/C++/Rust languages.
In this step you have basically two options
-
Using
codelldb
: (recommended) Go to releases and download the latest version (codelldb-x86_64-linux.vsix). Unpack it by changing its extension to.zip
and make asimlink
forcodelldb-x86_64-linux/extension/adapter/codelldb
to/usr/bin/codelldb
to be in yourPATH
. -
Using
vscode-cpptools
: do the same as previous but create asimlink
forcpptools-linux/extension/debugAdapters/bin/OpenDebugAD7
to/usr/bin/OpenDebugAD
.
Now, here is the content of after/plugin/debugger/cpp.lua
Firstly, configure the debug adapter
local dap = require("dap")
-- vscode-cpptools
dap.adapters.cppdbg = {
id = "cppdbg",
type = "executable",
command = "/usr/bin/OpenDebugAD",
}
-- codelldb
dap.adapters.codelldb = {
type = "server",
port = "${port}",
executable = {
command = "/usr/bin/codelldb",
args = { "--port", "${port}" },
},
}
Secondly, configure for each language. This is for C/C++
dap.configurations.cpp = {
{
-- Change it to "cppdbg" if you have vscode-cpptools
type = "codelldb",
request = "launch",
program = function ()
-- Compile and return exec name
local filetype = vim.bo.filetype
local filename = vim.fn.expand("%")
local basename = vim.fn.expand('%:t:r')
local makefile = os.execute("(ls | grep -i makefile)")
if makefile == "makefile" or makefile == "Makefile" then
os.execute("make debug")
else
if filetype == "c" then
os.execute(string.format("gcc -g -o %s %s", basename, filename))
else
os.execute(string.format("g++ -g -o %s %s", basename, filename))
end
end
return basename
end,
args = function ()
local argv = {}
arg = vim.fn.input(string.format("argv: "))
for a in string.gmatch(arg, "%S+") do
table.insert(argv, a)
end
vim.cmd('echo ""')
return argv
end,
cwd = "${workspaceFolder}",
-- Uncomment if you want to stop at main
-- stopAtEntry = true,
MIMode = "gdb",
miDebuggerPath = "/usr/bin/gdb",
setupCommands = {
{
text = "-enable-pretty-printing",
description = "enable pretty printing",
ignoreFailures = false,
},
},
},
}
-- You can even copy configurations
dap.configurations.c = dap.configurations.cpp
And this is for Rust
dap.configurations.rust = {
{
type = "codelldb",
request = "launch",
-- This is where cargo outputs the executable
program = function ()
os.execute("cargo build &> /dev/null")
return "target/debug/${workspaceFolderBasename}"
end,
args = function ()
local argv = {}
arg = vim.fn.input(string.format("argv: "))
for a in string.gmatch(arg, "%S+") do
table.insert(argv, a)
end
return argv
end,
cwd = "${workspaceFolder}",
-- Uncomment if you want to stop at main
-- stopOnEntry = true,
MIMode = "gdb",
miDebuggerPath = "/usr/bin/gdb",
setupCommands = {
{
text = "-enable-pretty-printing",
description = "enable pretty printing",
ignoreFailures = false,
},
},
},
}
At this point you have a very powerful debugger in your neovim
,
you can add support to other languages by creating a file under
after/plugin/debugger/language.lua
and refer to nvim-dap
wiki for
all the information you need to do so.