ryota2357

Neovimでcodelldbを使ってC++をデバッグする(nvim-dap, Mason.nvim)

投稿日:

更新日:

Neovim に C++のデバッグ環境を作った。
こんな感じのできた。

デバッグの様子

フローティングなウィンドウも出せた。

ホバー

使ったもの

次の 3 つのプラグインを使った。

nvim-dap は Neovim とデバッガをいい感じに繋いでくれるやつ。nvim-dap-ui は nvim-dap にいい感じの UI を作ってくれるやつ。Mason.nvim は LSP, DAP とかのインストーラ。

設定

プラグインマネージャに dein.vim を使っている。(dein.vim の lua ラッパー dein-snip.lua 使ってる)

[[plugins]]
repo = 'mfussenegger/nvim-dap'
depends = 'nvim-dap-ui'
hook_source = '''
  (後述)
'''
hook_add = '''
  (後述)
'''

codelldb のインストール

Mason.nvim で入れる。

Mason

nvim-dap の Wiki には「VSCode 拡張ダウンロードして解凍」って書いてあるけど Mason 使えばその必要はない。
快適だね。

nvim-dap の の設定

hook_source はこんな感じ。
説明はコメントで。

" アイコンの色定義
hi DapBreakpointTextHl guifg=#AA0000
hi DapStoppedTextHl guifg=#00c853

lua << EOF
  local dap = require('dap')

  -- アイコン設定
  vim.fn.sign_define('DapBreakpoint', { text='', texthl='DapBreakpointTextHl' })
  vim.fn.sign_define('DapStopped', { text='', texthl='DapStoppedTextHl' })

  -- ここにデバッガの設定
  dap.adapters = {
    codelldb = {
      type = 'server',
      port = '${port}',
      executable = {

        -- Masonはここにデバッガを入れてくれる
        command = vim.fn.stdpath('data') .. '/mason/packages/codelldb/extension/adapter/codelldb',

        -- ポートを自動的に割り振ってくれる
        args = {'--port', '${port}'}
      }
    }
  }

  -- ここにファイルタイプ別の設定
  dap.configurations = {
    cpp = {
      -- 複数指定することもできる
      -- 複数あるとデバッグ開始時にどの設定使うか聞かれる
      {
        -- なくてもいい。複数の設定があるとき、それらを識別するための名前
        name = 'Launch file',

        -- dap.adapters にあるデバッガから、どれを使うか
        type = 'codelldb',

        -- デバッガ起動する
        request = 'launch',

        -- コンパイル時に -g オプションをつけてビルドした実行ファイルを指定する
        -- こんな感じでinputで指定できるようにしておく
        program = function()
          return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/a.out', 'file')
        end,

        -- よく分からないけど、nvim-dapのWikiに書いてあったので
        cwd = '${workspaceFolder}',

        -- trueだとバイナリのデバッグになっちゃう(なんで?)
        stopOnEntry = false
      }
    }
  }
EOF

nvim-dap の hook_add は次のようにした。

DapBeginDapEnd ってコマンドを定義してる。
DapBegin でデバッグ開始、DapEnd で終了。キーマッピングとオプションを DapBegin ~ DapEnd の間だけに適用したかったのでこんな感じになった。

command! DapBegin call <SID>DapBegin()
command! DapEnd call <SID>DapEnd()

function! s:DapBegin() abort
  call dein#source('nvim-dap') " 複数回sourceされることはない
  let b:save_laststatus = &laststatus
  setlocal laststatus=2
  nnoremap <silent><F4>  <Cmd>lua require'dap'.toggle_breakpoint()<CR>
  nnoremap <silent><F5>  <Cmd>lua require'dap'.continue()<CR>
  nnoremap <silent><F10> <Cmd>lua require'dap'.step_over()<CR>
  nnoremap <silent><F11> <Cmd>lua require'dap'.step_into()<CR>
  nnoremap <silent><F12> <Cmd>lua require'dap'.step_out()<CR>
  lua require'dapui'.open()
  nnoremap <buffer><buffer><2-LeftMouse> <Cmd>lua require'dapui'.eval()<CR>
endfunction

function! s:DapEnd() abort
  silent! nunmap <F4>
  silent! nunmap <F5>
  silent! nunmap <F10>
  silent! nunmap <F11>
  silent! nunmap <F12>
  silent! nunmap <2-LeftMouse>
  if exists('b:save_laststatus')
    let &laststatus = b:save_laststatus
    unlet b:save_laststatus
  endif
  lua require'dapui'.close()
  lua require'dap'.clear_breakpoints()
  lua require'dap'.disconnect()
endfunction

UI (nvim-dap-ui)

on_source = 'nvim-dap' を指定しておくと nvim-dap をソースした時(DapBegin コマンドを入力した時)に自動的に nvim-dap-ui をソースしてくれるようになる。

[[plugins]]
repo = 'rcarriga/nvim-dap-ui'
on_source = 'nvim-dap'
hook_source = '''
  (後述)
'''

hook_source でデフォルト設定を少し変更する。
説明はコメントで。

lua << EOF
  require('dapui').setup {
    -- アイコン (nard font)
    icons = { expanded = '', collapsed = '' },

    -- UIのレイアウトを変更
    layouts = {
      {
        -- 表示するUI要素
        elements = {
          -- それぞれの要素を id で、size に大きさを指定
          { id = 'repl', size = 0.15 },
          { id = 'stacks', size = 0.2 },
          { id = 'watches', size = 0.2 },
          { id = 'scopes', size = 0.35 },
          { id = 'breakpoints', size = 0.1 }
        },

        -- 横幅の40%の大きさ
        size = 0.4,

        -- 画面左側に表示
        position = 'left'
      },

      {
        -- 表示するUI要素、sizeを指定しない場合は文字列だけでok
        elements = { 'console' },

        -- 縦幅の25%の大きさ
        size = 0.25,

        -- 画面下側に表示
        position = 'bottom'
      }
    }
  }
EOF

全体

上で説明した C++のデバッグ設定に加えて Python の設定(debugpy)もしている。

dap.toml
[[plugins]]
repo = 'mfussenegger/nvim-dap'
depends = 'nvim-dap-ui'
hook_source = '''
hi DapBreakpointTextHl guifg=#AA0000
hi DapStoppedTextHl guifg=#00c853
lua << EOF
  local dap = require('dap')
  vim.fn.sign_define('DapBreakpoint', { text='', texthl='DapBreakpointTextHl' })
  vim.fn.sign_define('DapStopped', { text='', texthl='DapStoppedTextHl' })
  dap.adapters = {
    codelldb = {
      type = 'server',
      port = '${port}',
      executable = {
        command = vim.fn.stdpath('data') .. '/mason/packages/codelldb/extension/adapter/codelldb',
        args = {'--port', '${port}'}
      }
    },
    debugpy = {
      type = 'executable',
      command = vim.fn.stdpath('data') .. '/mason/packages/debugpy/venv/bin/python',
      args = { '-m', 'debugpy.adapter' }
    }
  }
  dap.configurations = {
    cpp = {
      {
        name = 'Launch file',
        type = 'codelldb',
        request = 'launch',
        program = function()
          return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/a.out', 'file')
        end,
        cwd = '${workspaceFolder}',
        stopOnEntry = false -- trueだとバイナリのデバッグになっちゃう(なんで?)
      }
    },
    python = {
      {
        name = 'Launch file',
        type = 'debugpy',
        request = 'launch',
        program = '${file}',
        pythonPath = vim.fn.fnamemodify('~/.pyenv/shims/python', ':p')
      }
    }
  }
EOF
'''
hook_add = '''
  command! DapBegin call <SID>DapBegin()
  command! DapEnd call <SID>DapEnd()

  function! s:DapBegin() abort
    call dein#source('nvim-dap') " 複数回sourceされることはない
    let b:save_laststatus = &laststatus
    setlocal laststatus=2
    nnoremap <silent><F4>  <Cmd>lua require'dap'.toggle_breakpoint()<CR>
    nnoremap <silent><F5>  <Cmd>lua require'dap'.continue()<CR>
    nnoremap <silent><F10> <Cmd>lua require'dap'.step_over()<CR>
    nnoremap <silent><F11> <Cmd>lua require'dap'.step_into()<CR>
    nnoremap <silent><F12> <Cmd>lua require'dap'.step_out()<CR>
    lua require'dapui'.open()
    nnoremap <buffer><buffer><2-LeftMouse> <Cmd>lua require'dapui'.eval()<CR>
  endfunction

  function! s:DapEnd() abort
    silent! nunmap <F4>
    silent! nunmap <F5>
    silent! nunmap <F10>
    silent! nunmap <F11>
    silent! nunmap <F12>
    silent! nunmap <2-LeftMouse>
    if exists('b:save_laststatus')
      let &laststatus = b:save_laststatus
      unlet b:save_laststatus
    endif
    lua require'dapui'.close()
    lua require'dap'.clear_breakpoints()
    lua require'dap'.disconnect()
  endfunction
'''

[[plugins]]
repo = 'rcarriga/nvim-dap-ui'
on_source = 'nvim-dap'
hook_source = '''
lua << EOF
  require('dapui').setup {
    icons = { expanded = '', collapsed = '' },
    layouts = {
      {
        elements = {
          { id = 'repl', size = 0.15 },
          { id = 'stacks', size = 0.2 },
          { id = 'watches', size = 0.2 },
          { id = 'scopes', size = 0.35 },
          { id = 'breakpoints', size = 0.1 }
        },
        size = 0.4,
        position = 'left'
      },
      {
        elements = { 'console' },
        size = 0.25,
        position = 'bottom'
      }
    }
  }
EOF
'''