*ฅ^•ﻌ•^ฅ* ✨✨  HWisnu's blog  ✨✨ о ฅ^•ﻌ•^ฅ

Debugger in Neovim

Introduction

There has been some discussion on using debugger more actively as one of the primary tools when programming. Debugger is particularly useful to help us navigate the complexity of the program, especially if the program we are building is a complex one.

Debugger in Neovim

One of the key inspiration for me to write this is due to the image of complexity associated with establishing config in Neovim. I can assure you if you are not interested in doing custom Neovim, you can set up your own debugging IDE in 10-15 minutes.

Install Neovim

lazyvim-logo I'm using LazyVim. Just follow the installation process which is fairly easy:

install-lazyvim

Install DAP (Debugger Adapter Protocol)

You install DAP via Mason:

mason

I mainly writes C, Zig and Rust so codelldb here is perfect for my use case. You can choose whatever DAP you need: elixir, python, go, haskell, etc.

Config - Plugins

Now we are going to do some neovim configuration that is famous to be hard to do..well it is hard if you want to custom everything, but relatively easy if you are okay with the defaults.

Go to your neovim config, mine is at ~/.config/nvim.
Below is the tree view of my neovim config directory:

nv-config-tree Next let's install the two plugins (mason-nvim-dap and nvim-dap-ui).

mason-nvim-dap.lua

return {
  "jay-babu/mason-nvim-dap.nvim",
  event = "VeryLazy",
  dependencies = {
    "williamboman/mason.nvim",
    "mfussenegger/nvim-dap",
  },
  opts = {
    handlers = {},
    ensure_installed = {
      "codelldb",
    },
  },
}

nvim-dap-ui.lua

return {
  {
    "rcarriga/nvim-dap-ui",
    dependencies = {
      "mfussenegger/nvim-dap",
      "nvim-neotest/nvim-nio",
    },
    event = "VeryLazy",

    config = function()
      local dap, dapui = require("dap"), require("dapui")
      dapui.setup()
      dap.listeners.before.attach.dapui_config = function()
        dapui.open()
      end
      dap.listeners.before.launch.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

      vim.keymap.set("n", "<F1>", dap.close, { desc = "DAP: Close" })
      vim.keymap.set("n", "<F4>", dap.continue, { desc = "DAP: Continue" })
      vim.keymap.set("n", "<F5>", dap.clear_breakpoints, { desc = "DAP: Clear Breakpoint" })
      vim.keymap.set("n", "<F8>", dap.toggle_breakpoint, { desc = "DAP: Toggle Breakpoint" })
      vim.keymap.set("n", "<F10>", dap.step_over, { desc = "DAP: Step Over" })
      vim.keymap.set("n", "<F11>", dap.step_into, { desc = "DAP: Step Into" })
      vim.keymap.set("n", "<F12>", dap.step_out, { desc = "DAP: Step Out" })

      vim.keymap.set("n", "<Leader>dt", dapui.toggle, { desc = "Toggle DAP-UI" })
      vim.keymap.set(
        "n",
        "<Leader>dr",
        ":lua require('dapui').open({reset = true})<CR>",
        { noremap = true, desc = "Reset DAP-UI layout" }
      )
      vim.fn.sign_define(
        "DapBreakpoint",
        { text = "🔴", texthl = "DapBreakpoint", linehl = "DapBreakpoint", numhl = "DapBreakpoint" }
      )
      vim.fn.sign_define(
        "DapStopped",
        -- the text symbol below looks empty but it's actually there (orange play button)
        { text = "▶️", texthl = "DapStopped", linehl = "DapStopped", numhl = "DapStopped" }
      )
    end,
  },
}

And that's it...you've successfully set up a debugger in your Neovim! How long did it took? 20-30 mins at most assuming you encountered roadblocks?

What it looks like

Mine looks like this: view-nvim-dap

Additional references

Check these youtube vids:

Enjoy your new Neovim debugger, cheers!