Personal Blog

How to debug like a PRO using Neovim 🔥

10/5/2022, 6:00 PM

I've been using Neovim to write code since a couple of years, but I must admit that every time I wanted to debug something I would switch to VS Code or Firefox debugging tools (For Node and Javascript), but recently I decided to research how to set up Neovim to do it as well, I started digging into some documentations and found it so interesting that I ended up writing this article 😅.

Few editors are as extensible and powerful as Neovim, I believe everything you can think of can be implemented on it, including debugging (like a PRO).

In this article, we will learn how to prepare our Neovim to debug any kind of language using DAP (Debug Adapter Protocol) and in the process understand a bit better this technology. We will focus mainly on Golang, but the same knowledge can be applied to any other language with DAP support.

What is DAP

The Debug Adapter Protocol (DAP) is a standardized protocol introduced by Microsoft and used in Visual Studio Code that allows editors and IDEs (Tools) to outsource much of the logic that before had to be implemented in every editor to an intermediary called a Debug Adapter that can be reused across multiple tools, resulting in faster development times for editors’ developers and better user experiences for normal developers as they can get similar top code assistance across multiple editors.

Before DAP

Before DAP, every time you developed a new editor, and you wanted to support code debugging inside of it, you had to take care of all the implementation details of features like:

  • breakpoints

  • watch expressions

  • step in, step out

  • etc

And besides this, you also had to take care of all the UI implementation, all of these of course was a lot of effort for tool’s developers that some tools simple could not afford.

Before DAP

After DAP

Now with DAP editors’ developers only have to focus on:

  • Integrating a DAP Client that will handle all the communication with the Debug Adapter

  • Focus on the UI development to send or show the data from the Debug Adapter

After DAP

Installing a DAP Client

Currently, Neovim doesn’t have a built-in DAP Client, as it does for the Language Server Protocol (LSP). So we need to manually install nvim-dap which is a normal Neovim plugin that will act as the DAP Client.

If you’re using Packer, simply add to your plugin list:

use 'mfussenegger/nvim-dap'

Installing a debugger

In order to start debugging, you first need to install a debugger for the language you’re using, for Go:

Language

Debugger

Golang

delve

Installing delve for Golang

go install github.com/go-delve/delve/cmd/dlv@latest

Configuring the Debug Adapter

The next thing is to tell Neovim how to communicate with delve, you don’t really need to install another plugin to do this, but there are multiple plugins that basically do most of the configuration of the adapter for us.

For Go, we can use nvim-dap-go which only require the following lines:

use 'leoluz/nvim-dap-go' -- Install the plugin with Packer
require('dap-go').setup()

What does nvim-dap-go actually do?

You can skip this section unless you want to understand what nvim-dap-go actually does.

This plugin basically tells how Neovim should launch the debugger (in our case delve) by using the API provided by the plugin nvim-dap

  • First it checks if delve is installed in the system, otherwise it returns an error

  • Then it starts delve and delve runs as a server

  • Then it tells nvim-dap that it can connect to delve by attaching to the server that was started in the previous step

A very simplistic version of this extension's code would be:

  • Tell nvim-dap how to connect to the running delve server

dap.adapters.go = function(callback, config)
  -- Wait for delve to start
    vim.defer_fn(function()
        callback({type = "server", host = "127.0.0.1", port = "port"})
      end,
    100)
end

  • Configure how the debuggee (our application) should be launched, in this case we’re creating a configuration name Debug and telling nvim-dap to start debugging the current opened file

dap.configurations.go = {
    {
      type = "go",
      name = "Debug",
      request = "launch",
      program = "${file}",
    },
...

nvim-dap-go complete code

Defining configuration using launch.json file

If you're familiar with how VS Code works, you might know that you can provide the above configuration using a file called launch.json that is located in your project folder, if you prefer this method, this is also possible using nvim-dap!

Simply add the following line to your configuration:

require('dap.ext.vscode').load_launchjs(nil, {})

  • The first parameter is the path where you store the file, by default is .vscode/launch.json, but you can change it to whatever you want

Making the debugging interface amazing 💅!

Installing nvim-dap-ui

nvim dap ui

This is a nice package that will make the debugging much nicer, it basically puts all the DAP information into Neovim buffers.

To install it:

use { "rcarriga/nvim-dap-ui", requires = {"mfussenegger/nvim-dap"} }

Open automatically when a new debug session is created

local dap, dapui =require("dap"),require("dapui")
dap.listeners.after.event_initialized["dapui_config"]=function()
  dapui.open()
end
dap.listeners.before.event_terminated["dapui_config"]=function()
  dapui.close()
end
dap.listeners.before.event_exited["dapui_config"]=function()
  dapui.close()
end

Make the breakpoints look nicer

In your configuration, put this to change the ugly B text for these icons

vim.fn.sign_define('DapBreakpoint',{ text ='🟥', texthl ='', linehl ='', numhl =''})
vim.fn.sign_define('DapStopped',{ text ='▶️', texthl ='', linehl ='', numhl =''})

Set some keymaps

vim.keymap.set('n', '<F5>', require 'dap'.continue)
vim.keymap.set('n', '<F10>', require 'dap'.step_over)
vim.keymap.set('n', '<F11>', require 'dap'.step_into)
vim.keymap.set('n', '<F12>', require 'dap'.step_out)
vim.keymap.set('n', '<leader>b', require 'dap'.toggle_breakpoint)

What do you think?

What do you think about this article? Leave your comments 💬

Profile Picture

Miguel

I'm a Senior Software Engineer working in Berlin, primary focused on Frontend Technologies but also interested in Backend with Go, Terminal Apps and Neovim.

More posts

How to automate software installation and configuration on macOS using Homebrew and Stow

Debugging Javascript applications with Neovim

My notes of how to compile a typescript library

How to create a basic implementation of React Hook Form

Neovim: Using Telescope to find text inside specifics paths

© 2023 • Built with Gatsby