debugging go in neovim

dlv

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

neovim >= 0.5

~/.config/nvim/init.vim

Plug 'mfussenegger/nvim-dap'
Plug 'leoluz/nvim-dap-go'
Plug 'rcarriga/nvim-dap-ui'

lua << EOF
require('dap-go').setup()
local dap = require "dap"
  dap.adapters.go = function(callback, config)
    local stdout = vim.loop.new_pipe(false)
    local handle
    local pid_or_err
    local port = 38697
    local opts = {
      stdio = {nil, stdout},
      args = {"dap", "-l", "127.0.0.1:" .. port},
      detached = true
    }
    handle, pid_or_err = vim.loop.spawn("dlv", opts, function(code)
      stdout:close()
      handle:close()
      if code ~= 0 then
        print('dlv exited with code', code)
      end
    end)
    assert(handle, 'Error running dlv: ' .. tostring(pid_or_err))
    stdout:read_start(function(err, chunk)
      assert(not err, err)
      if chunk then
        vim.schedule(function()
          require('dap.repl').append(chunk)
        end)
      end
    end)
    -- Wait for delve to start
    vim.defer_fn(
      function()
        callback({type = "server", host = "127.0.0.1", port = port})
      end,
      100)
  end
  -- https://github.com/go-delve/delve/blob/master/Documentation/usage/dlv_dap.md
  dap.configurations.go = {
    {
      type = "go",
      name = "Debug",
      request = "launch",
      program = "${file}"
    },
    {
      type = "go",
      name = "Debug test", -- configuration for debugging test files
      request = "launch",
      mode = "test",
      program = "${file}"
    },
    -- works with go.mod packages and sub packages 
    {
      type = "go",
      name = "Debug test (go.mod)",
      request = "launch",
      mode = "test",
      program = "./${relativeFileDirname}"
    } 
}

require("dapui").setup({
  icons = { expanded = "▾", collapsed = "▸" },
  mappings = {
    -- Use a table to apply multiple mappings
    expand = { "<CR>", "<2-LeftMouse>" },
    open = "o",
    remove = "d",
    edit = "e",
    repl = "r",
  },
  sidebar = {
    -- You can change the order of elements in the sidebar
    elements = {
      -- Provide as ID strings or tables with "id" and "size" keys
      {
        id = "scopes",
        size = 0.25, -- Can be float or integer > 1
      },
      { id = "breakpoints", size = 0.25 },
      { id = "stacks", size = 0.25 },
      { id = "watches", size = 00.25 },
    },
    size = 40,
    position = "left", -- Can be "left", "right", "top", "bottom"
  },
  tray = {
    elements = { "repl" },
    size = 10,
    position = "bottom", -- Can be "left", "right", "top", "bottom"
  },
  floating = {
    max_height = nil, -- These can be integers or a float between 0 and 1.
    max_width = nil, -- Floats will be treated as percentage of your screen.
    border = "single", -- Border style. Can be "single", "double" or "rounded"
    mappings = {
      close = { "q", "<Esc>" },
    },
  },
  windows = { indent = 1 },
})
EOF

keys mapping

map({ "n", "<F4>", ":lua require('dapui').toggle()<CR>" })
map({ "n", "<F5>", ":lua require('dap').toggle_breakpoint()<CR>" })
map({ "n", "<F9>", ":lua require('dap').continue()<CR>" })

map({ "n", "<F1>", ":lua require('dap').step_over()<CR>" })
map({ "n", "<F2>", ":lua require('dap').step_into()<CR>" })
map({ "n", "<F3>", ":lua require('dap').step_out()<CR>" })

map({ "n", "<Leader>dsc", ":lua require('dap').continue()<CR>" })
map({ "n", "<Leader>dsv", ":lua require('dap').step_over()<CR>" })
map({ "n", "<Leader>dsi", ":lua require('dap').step_into()<CR>" })
map({ "n", "<Leader>dso", ":lua require('dap').step_out()<CR>" })

map({ "n", "<Leader>dhh", ":lua require('dap.ui.variables').hover()<CR>" })
map({ "v", "<Leader>dhv", ":lua require('dap.ui.variables').visual_hover()<CR>" })

map({ "n", "<Leader>duh", ":lua require('dap.ui.widgets').hover()<CR>" })
map({ "n", "<Leader>duf", ":lua local widgets=require('dap.ui.widgets');widgets.centered_float(widgets.scopes)<CR>" })

map({ "n", "<Leader>dro", ":lua require('dap').repl.open()<CR>" })
map({ "n", "<Leader>drl", ":lua require('dap').repl.run_last()<CR>" })

map({ "n", "<Leader>dbc", ":lua require('dap').set_breakpoint(vim.fn.input('Breakpoint condition: '))<CR>" })
map({ "n", "<Leader>dbm", ":lua require('dap').set_breakpoint({ nil, nil, vim.fn.input('Log point message: ') })<CR>" })
map({ "n", "<Leader>dbt", ":lua require('dap').toggle_breakpoint()<CR>" })

map({ "n", "<Leader>dc", ":lua require('dap.ui.variables').scopes()<CR>" })
map({ "n", "<Leader>di", ":lua require('dapui').toggle()<CR>" })

docs

nvim-dap

Debug Adapter installation

nvim-dap-ui