nvim-dapを利用したGo言語デバッグ

# はじめに
Go言語のデバッグどのようにしていますか？
printデバッグだったり、Delveを使ってCLIでやったり、IDEのデバッグ機能だったりかと思います。
私は、nvim-dapを利用して、Neovimでデバッグしているので、その紹介となります。

![intro.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218282/c75c5e82-1ed1-b1f2-9ee8-f3f737d71846.gif)


# 準備
下記プラグインを利用していきます。
- [nvim-dap](https://github.com/mfussenegger/nvim-dap)
- [nvim-dap-ui](https://github.com/rcarriga/nvim-dap-ui)
- [nvim-dap-go](https://github.com/leoluz/nvim-dap-go)
- [nvim-dap-virtual-text](https://github.com/theHamsta/nvim-dap-virtual-text)

Delveが必要になるのでインストールしておきましょう。

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

# 実際に使ってみる

サンプルコードは[こちら](https://github.com/deresmos/nvim-dap-go-sample)にあります。
サンプルコードをダウンロードして、下記コマンドを実行すれば、試す環境は用意できるかと思います。
※Neovimは、0.5.0以上推奨

```bash
nvim -u vimrc
```

実行可能なコマンドリストは、`:h dap-api`で確認できます。

## ブレークポイント貼る
まずはブレークポイントを貼ってみたいと思います。

```vim
:lua require'dap'.toggle_breakpoint()
```

![dap-breakpoint.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218282/214db626-c3bc-6583-0002-c7d477b941c5.png)



## 起動
nvim-dapでデバッグを実行するためには、下記を実行します。
すでに、nvim-dap-goプラグインによって設定されているリストが表示されていますので、`Debug test (go.mod)`の4を押してみましょう。

```vim
:lua require'dap'.continue()
```

![dap-continue.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218282/5c489a0a-b38c-ee42-5f4c-54c7b7791bf9.png)


## UI表示
うまく起動できれば、Bの文字が、→の文字に変わっているかと思います。（分かりやすいようにハイライト設定もできます。）
ただ、ブレークポイントに止まっただけで何もわかりませんので、
nvim-dap-uiプラグインでUIを表示してみましょう。


```vim
:lua require'dapui'.toggle()
```

![dap-ui.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218282/33b360f9-82ee-e411-9ac8-0acc039f05bc.png)


するとっぽいUI画面が出てきます。

### 画面説明

レイアウトは自分で設定できます。
私の設定は、

A: スコープ内の変数
B: 監視対象の変数 (insert modeで変数名を入力すれば、その変数の値をリアルタイムに表示してくれます)
C: スタックトレース
D: ブレークポイントの場所
F: インタープリター (dlvっぽく使えます)

![dap-ui-layout.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218282/780171c1-12ac-b461-8ad0-7105773588c5.png)


## UI自動表示・自動非表示

毎回nvim-dap起動させて、UI起動させてーだと非常に面倒なので、
nvim-dap起動されたら、自動的にUI起動させるようにしました。
下記をvimrcに追加することで、自動で起動させるようになります。
※DAPのeventは[こちら](https://microsoft.github.io/debug-adapter-protocol/specification#Events)に一覧があります。

```lua
lua << EOF
  require'dap'.listeners.before['event_initialized']['custom'] = function(session, body)
    require'dapui'.open()
  end
EOF
```

下記も追加することで、nvim-dap終了時にUIを消すこともできます。
使い勝手が良いようにカスタマイズできそうですね。

```lua
lua << EOF
  require'dap'.listeners.before['event_terminated']['custom'] = function(session, body)
    require'dapui'.close()
  end
EOF
```


# 実践

```vim
:!go test
```

テストを実行してみて、エラーになりました。
nvim-dapを使ってデバッグしてみましょう！

さきほどと同じように`Debug test (go.mod)`の4を選択します。
するとヌルポになるので、ブレークポイントを設定していなくても途中で止まります。
UIを起動し、スタックトレースから該当箇所に移動してみます。
aNum, bNumのどっちが、nilなのか調べるために値を確認します。（プラグインによりvirtual textで値が表示されていますが、あえてwatchesで値を確認してみます。
bNumがnilになっているので、確認すると、evil関数でnilになっていることがわかりますね！
こんな感じでデバッグできます。

![dap-intro.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218282/0dd18ade-2a18-c691-ea73-46ef4a124c91.gif)


## 特定テスト関数のみ実行
毎回全体テストしていたら、無駄に時間がかかってしまいます。
特定テストのみ実行できれば良いので、カーソル近くのテスト関数のみを実行してみましょう。

まずは、依存関係であるTreesitterを入れておきます。([nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter)プラグイン)

```vim
:TSInstall go
```

下記を実行すると近くのテスト関数のみを実行することはできます。

```vim
:lua require'dap-go'.debug_test()
```

## vim-testのストラテジーをカスタムしてみる
テスト実行には、[vim-test](https://github.com/vim-test/vim-test)を利用することが多いかと思いますが、
カーソル近くのテスト関数のみ実行するTestNearestコマンドがありますね。
vim-testは、strategyをカスタマイズできますので、vim-test → nvim-dap実行するstrategyを作ってみます。（VimScript → Lua関数呼び出しをスマートにしたい。

### 大まかな処理の流れ
- `go test -run 'TestAdd$' ./.`のような文字列がくるので、args部分の`-run 'TestAdd$'`を`-test.run 'TestAdd$'`に変換
- program部分の`./.`を抽出 (pathはいい感じに調整)
- dap設定に渡してあげて、nvim-dap起動


```vim
lua << EOF
  Dap = {}
  Dap.vim_test_strategy = {
    go = function(cmd)
      local test_func = string.match(cmd, "-run '([^ ]+)'")
      local path = string.match(cmd, "[^ ]+$")
      path = string.gsub(path, "/%.%.%.", "")
      configuration = {
        type = "go",
        name = "nvim-dap strategy",
        request = "launch",
        mode = "test",
        program = path,
        args = {},
      }
      if test_func then
        table.insert(configuration.args, "-test.run")
        table.insert(configuration.args, test_func)
      end
      if path == nil or path == "." then
        configuration.program = "./"
      end
      return configuration
    end,
  }
  function Dap.strategy()
    local cmd = vim.g.vim_test_last_command
    local filetype = vim.bo.filetype
    local f = Dap.vim_test_strategy[filetype]
    if not f then
      print("This filetype is not supported.")
      return
    end
    configuration = f(cmd)
    require'dap'.run(configuration)
  end
EOF
function! DapStrategy(cmd)
  echom 'It works! Command for running tests: ' . a:cmd
  let g:vim_test_last_command = a:cmd
  lua Dap.strategy()
endfunction
let g:test#custom_strategies = {'dap': function('DapStrategy')}
```

ストラテジーをdapに設定することで、nvim-dapを起動できるようになりました。

```vim
:TestNearest -strategy=dap
```

TestNearestの挙動しか確認していないので、他だとうまく動かないかもしれないです。

# まとめ
Go言語を触りはじめのときは、printデバッグ、dlvでのCLIデバックから、nvim-dapによるデバッグというような感じでデバッグ方法は推移していきました。
デバッグ環境を快適にしておくことで、問題解決までの時間を大幅に削減できるかと思うので、
この機会に是非デバック環境を整えて、より良いVimライフを送れるようにしておきましょう。