🐞

単一ウィンドウで扱うNeovim用デバッグUI (nvim-dap-view)

に公開

nvim-dap-viewとは

nvim-dap-viewは、NeovimにおいてDAP[1]ベースのデバッグのUIを提供するプラグインです。
nvim-dapを基盤とし、デバッグ情報を複数の分割ウィンドウに常駐させるのではなく、単一のウィンドウ上で切り替えて表示するUIを提供します。


nvim-dap-view

既存のデバッグUIが抱える課題

Neovimにはデバッグ情報を可視化するためのプラグインがいくつか存在しますが、中でも2021年のリリース以来最も広く使われてきたのがnvim-dap-uiです。

nvim-dap-uiは、nvim-dapの機能に対して視認性の高いUIを追加することを目的としたプラグインです。
変数のスコープ、ブレークポイント、REPL等のデバッグに必要な情報を複数の分割ウィンドウで表示することで、IDEに近い体験を提供します。

一方で、いくつかの課題も指摘されています。
複数のデバッグ情報をウィンドウ毎に表示するUIは、マウス操作を前提としたIDEでは扱いやすい一方、キーボード操作を主とするNeovimでは情報を切り替えるたびにウィンドウ間のキー操作が必要になり、操作が煩雑になりやすい側面があります。
加えて、ウィンドウ数が増えることで編集領域が圧迫される点を負担に感じるという意見もあります。

nvim-dap-viewのアプローチ

nvim-dap-viewは、nvim-dap-uiが抱えがちな上記の課題に対して、UIそのものを見直すことでアプローチしています。
最大の特徴は、複数のデバッグ情報を一つのウィンドウ上で切り替えるという設計です。それぞれの情報は独立したウィンドウとして常駐するのではなく、必要なときに切り替えることで表示されます。
この設計により、UIの存在を必要以上に意識せず、通常の編集作業と近い感覚でデバッグを行うことができます。

デバッグ画面の比較

nvim-dap-uiとnvim-dap-viewで、それぞれのデバッグ画面を比較します。


nvim-dap-ui


nvim-dap-view

nvim-dap-uiにおいて左上から左下へ縦に連なるウィンドウは、nvim-dap-viewにおいてそれぞれScopes, Breakpoints, Threads, Watchesに対応しています。
中央下のウィンドウはREPLに対応しており、ターミナルはいずれもnvim-dap由来のものを活用しています[2]

導入方法とカスタマイズの例

ここからは、lazy.nvimを用いた nvim-dap-viewの導入方法と、主要なカスタマイズの例を紹介します。
比較のため、同じ設定をnvim-dap-uiで行う場合についても、必要に応じて補足します。

前提条件

  • Neovim v0.11+
  • 言語毎の設定は省略

プラグインの導入

{
  "mfussenegger/nvim-dap",
  dependencies = "igorlfs/nvim-dap-view",
  keys = {
    { "<F5>", "<cmd>DapContinue<CR>" },
    { "<F9>", "<cmd>DapToggleBreakpoint<CR>" },
    { "<F11>", "<cmd>DapStepInto<CR>" },
    -- (以下略)
  },
},
{
  "igorlfs/nvim-dap-view",
  keys = {
    { "<F7>", "<cmd>DapViewToggle!<CR>" },
  },
  opts = {},
},
nvim-dap-uiの場合
{
  "mfussenegger/nvim-dap",
  dependencies = "rcarriga/nvim-dap-ui",
  keys = {
    { "<F5>", "<cmd>DapContinue<CR>" },
    { "<F9>", "<cmd>DapToggleBreakpoint<CR>" },
    { "<F11>", "<cmd>DapStepInto<CR>" },
    -- (以下略)
  },
},
{
  "rcarriga/nvim-dap-ui",
  dependencies = "nvim-neotest/nvim-nio",
  keys = {
    {
      "<F7>",
      function()
        require("dapui").toggle()
      end,
    },
  },
  opts = {},
},

UIの連動

デフォルトの設定では、デバッグの開始とUIの表示が連動しておらず、表示・非表示の切替を手動で行う必要があります。
オプションauto_toggleを使用することで、UIの表示・非表示をデバッグと連動させることができます。

{
  "igorlfs/nvim-dap-view",
  opts = {
    auto_toggle = true,
  },
},
nvim-dap-uiの場合

nvim-dap-uiは、デバッグとUIを連動させるオプションを提供しません。
nvim-dapには、デバッグの開始・終了といったイベントにフックして任意の処理を実行できる仕組みが用意されており、これを利用することで、デバッグの開始・終了に合わせてUIの表示・非表示を自動化できます[3]

{
  "mfussenegger/nvim-dap",
  config = function()
    local dap = require("dap")
    dap.listeners.after.event_initialized["dapui_config"] = function()
      require("dapui").open()
    end
    dap.listeners.before.event_terminated["dapui_config"] = function()
      require("dapui").close()
    end
    dap.listeners.before.event_exited["dapui_config"] = function()
      require("dapui").close()
    end
  end,
},

ターミナルの統合


before


after

デフォルトの設定では、nvim-dap-viewはデバッグ開始時に統合されたウィンドウとターミナルウィンドウの計2つを生成します。
表示するセクションにconsoleを含めることで、ターミナルを含む単一のウィンドウとして統合することができます。

{
  "igorlfs/nvim-dap-view",
  opts = {
    winbar = {
      sections = { "console", "watches", "scopes", "exceptions", "breakpoints", "threads", "repl" },
    },
  },
},

GUIボタンの表示


before


after

デフォルトの設定では、▶(Continue)や↻(Restart)といったボタンは表示されません。
以下のように設定することで、ビューの隣にボタンを配置することができます。

{
  "igorlfs/nvim-dap-view",
  opts = {
    winbar = {
      controls = {
        enabled = true,
        position = "left", -- ボタンの配置位置 ("left"|"right")
      },
    },
  },
},

その他の設定

その他の設定項目については、公式ドキュメントをご参照ください。

おわりに

本記事では、nvim-dap-viewがどのような課題から生まれ、どのような設計を採っているのかを、nvim-dap-uiとの比較を通して紹介しました。
複数ウィンドウを前提としたUIとは異なり、情報を一つのウィンドウで切り替えて扱うという設計は、操作の流れと編集領域をシンプルに保ちたいユーザにとって有力な選択肢となるでしょう。

脚注
  1. Debug Adapter Protocol ↩︎

  2. 後述の設定により統合可能 ↩︎

  3. nvim-dap-viewにおいても、内部で同じ仕様を利用している ↩︎

Discussion