lxz

lxz

C++ | Typescript | 图形白痴

Neovim で dap を使用して c++ をデバッグする

以前、私はシンプルな nvim 設定を共有しました。それはプログラミングに必要なインテリジェントなヒント、構文ハイライト、コードジャンプなどの機能を実現しています。今日は nvim のデバッグフレームワーク dap を整備するつもりです。

dap はフレームワークで、クライアントは nvim 上でさまざまなデバッグ情報を表示します。たとえば、ブレークポイント、コールスタック、オブジェクトのメモリ情報などを表示します。サーバーはクライアントが必要とする機能を提供し、通常はデバッガーまたはデバッガーのラッパーです。

この記事では、Mason というプラグインを使用して dap のサーバーをインストールします。Mason については詳しく説明しませんが、将来的に機会があれば詳しくお話しします。

まずは、正常に動作しているいくつかの画像を見てみましょう:

image

実行インターフェース

image

変数情報の確認

image

ホットキー

image

関数コールスタック

dap のインストール#

Mason のインストールリストに codelldb を追加します。codelldb は vscode 用のデバッグサーバーで、vscode にデバッグ情報を提供します。このバックエンドがあれば、vscode と同じデバッグ機能を簡単に実現できます。

dap の設定#

plugins ディレクトリに新しい_dap.luaファイルを作成します。

return {
  "mfussenegger/nvim-dap",
  opt = true,
  module = { "dap" },
  requires = {
    {
      "theHamsta/nvim-dap-virtual-text",
      module = { "nvim-dap-virtual-text" },
    },
    {
      "rcarriga/nvim-dap-ui",
      module = { "dapui" },
    },
    "nvim-telescope/telescope-dap.nvim",
    {
      "jbyuki/one-small-step-for-vimkind",
      module = "osv",
    },
  },
  config = function()
    require("config.dap").setup()
  end,
  disable = false,
}

一部の人は packer で use を使ってインストールし、return を use に変更することができます。

packer のコードはすでに書かれています。次に config 関数を書きます。私の例では、ファイルをlua/config/dap/ディレクトリに置きました。異なる言語を設定する必要があるため、管理が少し楽になります。

まず、dap ディレクトリに新しい init.lua を作成します。ここがモジュールのエントリポイントで、初期化作業はここから始まります。

local M = {}

local function configure()
end

local function configure_exts()
end

local function configure_debuggers()
end

function M.setup()
	configure() -- 設定
	configure_exts() -- 拡張
	configure_debuggers() -- デバッガー
end

configure_debuggers()

return M

_dap.luarequire("config.dap").setup()を呼び出しました。この setup 関数はconfig/dap/init.luaM.setup()関数です。

現在はただの骨組みを書いただけです。さあ、正式に設定しましょう。

ホットキー#

nvim でデバッグを行うと、インターフェースは明らかにターミナル内にあるため、ホットキーを使用していくつかの操作を行う必要があります。たとえば、ブレークポイントのマーク、ステップイン、ステップアウトなどです。

config/dap/keymaps.luaでホットキーの設定を行います。

local M = {}

local whichkey = require "which-key"
-- local legendary = require "legendary"

-- local function keymap(lhs, rhs, desc)
--   vim.keymap.set("n", lhs, rhs, { silent = true, desc = desc })
-- end

function M.setup()
  local keymap = {
    d = {
      name = "DAP",
      R = { "<cmd>lua require'dap'.run_to_cursor()<cr>", "カーソルまで実行" },
      E = { "<cmd>lua require'dapui'.eval(vim.fn.input '[Expression] > ')<cr>", "入力を評価" },
      C = { "<cmd>lua require'dap'.set_breakpoint(vim.fn.input '[Condition] > ')<cr>", "条件付きブレークポイント" },
      U = { "<cmd>lua require'dapui'.toggle()<cr>", "UIを切り替え" },
      b = { "<cmd>lua require'dap'.step_back()<cr>", "ステップバック" },
      c = { "<cmd>lua require'dap'.continue()<cr>", "続行" },
      d = { "<cmd>lua require'dap'.disconnect()<cr>", "切断" },
      e = { "<cmd>lua require'dapui'.eval()<cr>", "評価" },
      g = { "<cmd>lua require'dap'.session()<cr>", "セッションを取得" },
      h = { "<cmd>lua require'dap.ui.widgets'.hover()<cr>", "変数をホバー" },
      S = { "<cmd>lua require'dap.ui.widgets'.scopes()<cr>", "スコープ" },
      i = { "<cmd>lua require'dap'.step_into()<cr>", "ステップイン" },
      o = { "<cmd>lua require'dap'.step_over()<cr>", "ステップオーバー" },
      p = { "<cmd>lua require'dap'.pause.toggle()<cr>", "一時停止" },
      q = { "<cmd>lua require'dap'.close()<cr>", "終了" },
      r = { "<cmd>lua require'dap'.repl.toggle()<cr>", "Replを切り替え" },
      s = { "<cmd>lua require'dap'.continue()<cr>", "開始" },
      t = { "<cmd>lua require'dap'.toggle_breakpoint()<cr>", "ブレークポイントを切り替え" },
      x = { "<cmd>lua require'dap'.terminate()<cr>", "終了" },
      u = { "<cmd>lua require'dap'.step_out()<cr>", "ステップアウト" },
    },
  }
  local opts = {
    mode = "n",
    prefix = "<leader>",
    buffer = nil,
    silent = true,
    noremap = true,
    nowait = false,
  }
  whichkey.register(keymap, opts)
  --- require("legendary.integrations.which-key").bind_whichkey(keymap, opts, false)

  local keymap_v = {
    d = {
      name = "デバッグ",
      e = { "<cmd>lua require'dapui'.eval()<cr>", "評価" },
    },
  }
  opts = {
    mode = "v",
    prefix = "<leader>",
    buffer = nil,
    silent = true,
    noremap = true,
    nowait = false,
  }
  whichkey.register(keymap_v, opts)
  --- require("legendary.integrations.which-key").bind_whichkey(keymap_v, opts, false)
end

return M

ここではホットキーを<leader> dにバインドしました。

次に、init.luaに戻り、setup関数で keymaps を呼び出します。

function M.setup()
	require("config.dap.keymaps").setup() -- ホットキー
end

dapui#

dapui は dap インターフェースを美化するプラグインで、通常は皆さんが設定するものです!

local function configure_exts()
	require("nvim-dap-virtual-text").setup({
		commented = true,
	})

	local dap, dapui = require("dap"), require("dapui")
	dapui.setup({
		expand_lines = true,
		icons = { expanded = "", collapsed = "", circular = "" },
		mappings = {
			-- 複数のマッピングを適用するためにテーブルを使用
			expand = { "<CR>", "<2-LeftMouse>" },
			open = "o",
			remove = "d",
			edit = "e",
			repl = "r",
			toggle = "t",
		},
		layouts = {
			{
				elements = {
					{ id = "scopes", size = 0.33 },
					{ id = "breakpoints", size = 0.17 },
					{ id = "stacks", size = 0.25 },
					{ id = "watches", size = 0.25 },
				},
				size = 0.33,
				position = "right",
			},
			{
				elements = {
					{ id = "repl", size = 0.45 },
					{ id = "console", size = 0.55 },
				},
				size = 0.27,
				position = "bottom",
			},
		},
		floating = {
			max_height = 0.9,
			max_width = 0.5, -- フローティングは画面の割合として扱われます。
			border = vim.g.border_chars, -- ボーダースタイル。'single'、'double'、または'rounded'のいずれか
			mappings = {
				close = { "q", "<Esc>" },
			},
		},
	}) -- デフォルトを使用
	dap.listeners.after.event_initialized["dapui_config"] = function()
		dapui.open({})
	end
	dap.listeners.before.event_terminated["dapui_config"] = function()
		dapui.close({})
	end
	dap.listeners.before.event_exited["dapui_config"] = function()
		dapui.close({})
	end
end

基本的な設定は皆さんあまり変わらないでしょう。おそらく誰かの設定から移植しているかもしれません。

image

アイコンの設定#

私は configure 関数の中でいくつかのデフォルトアイコンを変更しました。

local function configure()
	local dap_breakpoint = {
		breakpoint = {
			text = "",
			texthl = "LspDiagnosticsSignError",
			linehl = "",
			numhl = "",
		},
		rejected = {
			text = "",
			texthl = "LspDiagnosticsSignHint",
			linehl = "",
			numhl = "",
		},
		stopped = {
			text = "",
			texthl = "LspDiagnosticsSignInformation",
			linehl = "DiagnosticUnderlineInfo",
			numhl = "LspDiagnosticsSignInformation",
		},
	}

	vim.fn.sign_define("DapBreakpoint", dap_breakpoint.breakpoint)
	vim.fn.sign_define("DapStopped", dap_breakpoint.stopped)
	vim.fn.sign_define("DapBreakpointRejected", dap_breakpoint.rejected)
end

image

ブレークポイントマーク

image

ステップ停止

クライアントの設定#

今、クライアントの関数がまだ書かれていません。ここでは異なる言語に対して設定されたサーバーを呼び出すだけです。内容は非常にシンプルです。

新しいconfig/dap/cpp.luaを作成し、その中で C++ 関連のパラメータを設定します。注意が必要なのは、codelldb は C、C++、Rust などの言語をデバッグできるため、さらに細かいファイルには分けません。

local M = {}

function M.setup()
	-- local dap_install = require "dap-install"
	-- dap_install.config("codelldb", {})

	local dap = require("dap")
	local install_root_dir = vim.fn.stdpath("data") .. "/mason"
	local extension_path = install_root_dir .. "/packages/codelldb/extension/"
	local codelldb_path = extension_path .. "adapter/codelldb"

	dap.adapters.codelldb = {
		type = "server",
		port = "${port}",
		executable = {
			command = codelldb_path,
			args = { "--port", "${port}" },

			-- Windowsの場合はこれをコメント解除する必要があるかもしれません:
			-- detached = false,
		},
	}
	dap.configurations.cpp = {
		{
			name = "ファイルを起動",
			type = "codelldb",
			request = "launch",
			program = function()
				return vim.fn.input("実行可能ファイルのパス: ", vim.fn.getcwd() .. "/", "file")
			end,
			cwd = "${workspaceFolder}",
			stopOnEntry = true,
		},
	}

	dap.configurations.c = dap.configurations.cpp
	dap.configurations.rust = dap.configurations.cpp
end

return M

Mason がここに登場しましたが、私たちはただ Mason が codelldb をインストールするパスを見つけただけです。

設定内容は固定されており、実行ファイルのパスとパラメータを設定し、この言語をデバッグするために必要な起動パラメータを設定します。ここでは実行可能ファイルのパスを入力する簡単な方法をデフォルトで提供しています。

launch.json の設定#

上記の内容は C++ プログラムをデバッグするのに十分ですが、dap は vscode の launch.json もサポートしています。起動設定を固定テンプレートとして起動デバッグのリストに埋め込み、launch.json 内でプログラムの環境変数や起動パラメータを制御することもできますので、便利です。

dap がこれをサポートするには、setup 関数に 1 行追加するだけで十分です。

require("dap.ext.vscode").load_launchjs(nil, { codelldb = { "c", "cpp", "rust" } })

この文の意味は、launch.json のタイプが codelldb の場合、C、C++、Rust のデバッグ設定を使用するということです。上記で codelldb のパラメータと C++ のパラメータを設定し、C++ の設定を C と Rust にコピーしました。

ただし、注意が必要な点があります。launch.json では環境変数が environment フィールドに変更され、構造も変わりました。現在、dap は env フィールドのみをサポートしています。自動変換を行う PR を寄付することを考えています。

ここに launch.json の例を示します:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(codelldb) Launch",
            "type": "codelldb",
            "request": "launch",
            "program": "./build/bin/deepin-kwin_x11",
            "args": [
                "--replace"
            ],
            "stopAtEntry": true,
            "cwd": "${workspaceFolder}",
            "env": {
                "DISPLAY": ":0",
                "PATH": "${workspaceFolder}/build/bin:$PATH",
                "XDG_CURRENT_DESKTOP": "Deepin",
                "QT_PLUGIN_PATH": "${workspaceFolder}/build",
                "QT_LOGGING_RULES": "kwin_*.debug=true"
            },
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "gdbのためのプリティプリンティングを有効にする",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ]
        }
    ]
}

ここでの codelldb は実際には識別子文字列であり、vscode がデフォルトで提供するタイプは cppgdb です。私たちは同じフィールドに変更することもできます。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。