はじめに
Go言語のデバッグどのようにしていますか?
printデバッグだったり、Delveを使ってCLIでやったり、IDEのデバッグ機能だったりかと思います。
私は、nvim-dapを利用して、Neovimでデバッグしているので、その紹介となります。
準備
下記プラグインを利用していきます。
Delveが必要になるのでインストールしておきましょう。
go install github.com/go-delve/delve/cmd/dlv@latest
実際に使ってみる
サンプルコードはこちらにあります。
サンプルコードをダウンロードして、下記コマンドを実行すれば、試す環境は用意できるかと思います。
※Neovimは、0.5.0以上推奨
nvim -u vimrc
実行可能なコマンドリストは、:h dap-api
で確認できます。
ブレークポイント貼る
まずはブレークポイントを貼ってみたいと思います。
:lua require'dap'.toggle_breakpoint()
起動
nvim-dapでデバッグを実行するためには、下記を実行します。
すでに、nvim-dap-goプラグインによって設定されているリストが表示されていますので、Debug test (go.mod)
の4を押してみましょう。
:lua require'dap'.continue()
UI表示
うまく起動できれば、Bの文字が、→の文字に変わっているかと思います。(分かりやすいようにハイライト設定もできます。)
ただ、ブレークポイントに止まっただけで何もわかりませんので、
nvim-dap-uiプラグインでUIを表示してみましょう。
:lua require'dapui'.toggle()
するとっぽいUI画面が出てきます。
画面説明
レイアウトは自分で設定できます。
私の設定は、
A: スコープ内の変数
B: 監視対象の変数 (insert modeで変数名を入力すれば、その変数の値をリアルタイムに表示してくれます)
C: スタックトレース
D: ブレークポイントの場所
F: インタープリター (dlvっぽく使えます)
UI自動表示・自動非表示
毎回nvim-dap起動させて、UI起動させてーだと非常に面倒なので、
nvim-dap起動されたら、自動的にUI起動させるようにしました。
下記をvimrcに追加することで、自動で起動させるようになります。
※DAPのeventはこちらに一覧があります。
lua << EOF
require'dap'.listeners.before['event_initialized']['custom'] = function(session, body)
require'dapui'.open()
end
EOF
下記も追加することで、nvim-dap終了時にUIを消すこともできます。
使い勝手が良いようにカスタマイズできそうですね。
lua << EOF
require'dap'.listeners.before['event_terminated']['custom'] = function(session, body)
require'dapui'.close()
end
EOF
実践
:!go test
テストを実行してみて、エラーになりました。
nvim-dapを使ってデバッグしてみましょう!
さきほどと同じようにDebug test (go.mod)
の4を選択します。
するとヌルポになるので、ブレークポイントを設定していなくても途中で止まります。
UIを起動し、スタックトレースから該当箇所に移動してみます。
aNum, bNumのどっちが、nilなのか調べるために値を確認します。(プラグインによりvirtual textで値が表示されていますが、あえてwatchesで値を確認してみます。
bNumがnilになっているので、確認すると、evil関数でnilになっていることがわかりますね!
こんな感じでデバッグできます。
特定テスト関数のみ実行
毎回全体テストしていたら、無駄に時間がかかってしまいます。
特定テストのみ実行できれば良いので、カーソル近くのテスト関数のみを実行してみましょう。
まずは、依存関係であるTreesitterを入れておきます。(nvim-treesitterプラグイン)
:TSInstall go
下記を実行すると近くのテスト関数のみを実行することはできます。
:lua require'dap-go'.debug_test()
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起動
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を起動できるようになりました。
:TestNearest -strategy=dap
TestNearestの挙動しか確認していないので、他だとうまく動かないかもしれないです。
まとめ
Go言語を触りはじめのときは、printデバッグ、dlvでのCLIデバックから、nvim-dapによるデバッグというような感じでデバッグ方法は推移していきました。
デバッグ環境を快適にしておくことで、問題解決までの時間を大幅に削減できるかと思うので、
この機会に是非デバック環境を整えて、より良いVimライフを送れるようにしておきましょう。