GNU Emacs で VS Code 相当のコード・デバッグ支援機能を設定する (2) – Dape

続き

GNU Emacs で VS Code 相当のコード・デバッグ支援機能を設定する (1) – Eglot からの続きです。

Dape

インストール

Emacs のパッケージ マネージャーでインストールできます。

M-x package-install RET dape RET

マウスでブレークポイントをセットできるようにしておきます。また repeat-mode も有効にします。

~/.emacs.d/init.el

(use-package dape
  :bind-keymap
  ("C-x C-a" . dape-global-map)
  
  :config
  ;; Global bindings for setting breakpoints with mouse
  (dape-breakpoint-global-mode))

;; Enable repeat mode for more ergonomic `dape' use
(use-package repeat
  :config
  (repeat-mode))

DAP アダプター

サポートされている DAP アダプターの情報は Supported debug adapters にあります。

C/C++

ここでは codelldb と lldb-dap を選びます。

codelldb

VS Code か VSCodium の Extensions から CodeLLDB をインストールしてリンクを張る方がアップデートや macOS が出してくるセキュリテイ警告への対応が容易だと思います。

% cd ~/.emacs.d/debug-adapters/codelldb
% ln -s ../../../.vscode-oss/extensions/vadimcn.vscode-lldb-1.11.0-universal
lldb-dap

macOS の the command line developer tools に付属のものを使います。ただしデフォルトで $PATH が通っていないので通っているディレクトリーからリンクを張っておきます。

% cd ~/.local/bin
% ln -s "$(xcrun --find lldb-dap)" .

Python

Homebrew には debugpy が見当たらないのとモジュールとして使用されるため pipx も使えないので pip でインストールするしかなさそうです。ただし PEP 668 and virtual environments に従い "virtual environment" を使います。 12. Virtual Environments and Packages や VS Code の Create a virtual environment も参考にしつつたとえば

% python3 -m venv ~/Developer/python/tutorial/.venv
% source ~/Developer/python/tutorial/.venv/bin/activate
(.venv) % python3 -m pip install debugpy
(.venv) % deactivate
% 
Python venv での Emacs の起動

debugpy が "system-wide" にインストールされている環境なら Emacs.app をデスクトップから起動してもよいはずですが "virtual environment" の場合は source activate した shell から emacs を 起動しないと debugpy を認識してくれません1。

% source ~/Developer/python/tutorial/.venv/bin/activate
(.venv) % emacs &

使い方の要点

M-x dape TAB TAB でコマンドの一覧が出ます。 C-h f commandname でコマンドの説明が出ます。 REPL ウインドウの開始行にコマンドの簡単な説明が表示されます。

M-x REPL C-x C-a 説明
dape-info i Dape info ウインドウを表示/非表示
dape-repl R REPL ウインドウを表示/に移動

ブレークポイント

ブレークポイントはいつでもセット/リセットできます。ソース ファイルの対象の行にカーソルをもっていき C-x C-a b とタイプします。またはマウスが有効の場合その行の左端のウインドウのへりでクリックします。するとそのヘリに ○ がつきます。なお C/C++ では -g オプションを付けてコンパイルしておかないとブレークしません。

M-x REPL C-x C-a 説明
dape-breakpoint-toggle b ブレークポイントの設定・削除
dape-breakpoint-remove-all B すべてのブレークポイントの削除
dape-breakpoint-hits h ヒット カウント ブレークポイント
dape-breakpoint-expression e 条件つきブレークポイント
dape-breakpoint-log l (中断ではなく) 該当行でメッセージ出力

デバッグ セッション

対象のソース ファイルのウィンドウで M-x dape または C-x C-a d を入力するとミニ バッファーに Run adapter: とプロンプトが出ます。 <tab>/M-C-i により変数 dape-configs で定義されているもののうちソース ファイルのモードにマッチする補完候補が表示されます。たとえば codelldb と lldb-dap が適切にインストールされていて C/C++ モードでファイルを開いているときは

Click on a completion to select it.
In this buffer, type RET to select the completion near point.

2 possible completions:
codelldb-cc 
lldb-dap 

のように表示されます。変数 dape-configs の内容と説明は C-h v dape-configs で読めます。 python が python3 へのリンクになっていない環境では補完が効きません。 debugpy command "python3" と明示的に入力する必要があります。

必要に応じオブションを入力します。たとえば C/C++ でコンパイルされたファイルをデフォルトの a.out から変更している場合は :program programname と入力します。シンボリック リンク経由で開いている場合は prefix-local/prefix-remote を調節しないとブレークポイントで停止しないことがあります。 debugpy に対しては :justMyCode t と入力した方がよいかもしれません。でないと実行がデバッグ モジュールにまで立ち入るという VS Code のデフォルトと異なる挙動に当惑するかもしれません。 C-c C-k で先頭のコンフィグ名だけを残しオプションをクリアーすることができます。 M-n/M-p で入力履歴をたどれます。

先頭に : がつくオブションは各 DAP アダプターに渡すものです。

DAP プロバーのオブションの値は json-parse-string で Elisp の表現に変換する必要があります。ただし true は t 、 false は nil です。

*scratch*

(json-parse-string "[null, \"log.txt\", null]")  ;; C-j
[:null "log.txt" :null]
M-x REPL C-x C-a 説明
dape debug d デバッグの開始 (オプション入力あり)
dape-restart restart r 前回と同じオプションで開始
dape-kill kill デバッグ セッションの強制終了
dape-disconnect-quit disconnect D デバッグ対象の動作はそのままアダプターを強制終了
dape-quit quit q Dape の終了

実行制御

M-x REPL C-x C-a 説明
dape-pause pause p 実行の中断
dape-continue continue c 次のブレークポイントまで継続
dape-step-in step s ステップ実行
dape-next next n ネクスト実行 (関数内部に立ち入らない)
dape-step-out out o 現在の関数から return するまで継続
dape-stack-select-up up < 関数呼び出しのネストの上に移動
dape-stack-select-down down > 関数呼び出しのネストの下に移動
dape-select-stack S 関数呼び出しのネストの選択移動
dape-select-thread t スレッドの選択

値の表示

M-x REPL C-x C-a 説明
dape-watch-dwim w ウォッチする変数の設定
dape-read-memory m メモリー内容の表示
dape-evaluate-expression x 式の評価

設定

Configuration を参照して便利そうなものを設定します。

アダプターの構成の規定値

M-x dape のときにミニ バッファーに入力するコンフィグのデフォルト値を設定する方法は二通りあります。

変数 dape-configs

C-h v dape-configs で説明と内容が表示されます。全体としては alist (association list) というキーと値の組みのリストになっています。キーは codelldb-cc, debugpy などのコンフィグ名です。値は plist (property list) というプロパティー名とプロパティー値の組みのリストになっています。

下記の設定では VS Code の The C/C++ extension のデフォルトに似せています。 alist-get で alist の値 (ここでは plist) を取得し plist-put でプロパティー名とプロパティー値をセットしています。すでに同名のプロパティーが存在しているときは値を更新しそうでないときは項目が追加されます。

~/.emacs.d/init.el:

(use-package dape
  ;; ...
  :config
  ;; ...
  ;; Override the default configs.  See `C-h v dape-configs`.
  (let ((program-file (lambda () (file-name-sans-extension buffer-file-name)))
        (compile-cmd (lambda () (concat "clang++ -std=c++17 -g -o " (file-name-sans-extension buffer-file-name) " " buffer-file-name)))
        (dape-config-put (lambda (config-name new-config)
                           (let ((config (alist-get config-name dape-configs)))
                             (cl-loop for (p v) on new-config by 'cddr do (plist-put config p v))))))
    (funcall dape-config-put
             'codelldb-cc (list
                           'compile compile-cmd
                           :program program-file))
    (funcall dape-config-put
             'lldb-dap (list
                        'compile compile-cmd
                        :program program-file))
    (funcall dape-config-put
             'debugpy (list
                       :justMyCode t)))

let でローカル変数に lambda を代入しているのは Emacs セッション全体に定義したくないからです。

変数 dape-command

プロジェクトのルート ディレクトリーに .dir-locals.el というファイルをおきそこで設定します。ブロジェクト配下にのみ有効です。

.dir-locals.el

((c++-mode
  . ((dape-command
      . (lldb-dap
         :program "helloworld")))))

設定値をプログラマブルに生成したいときは eval を使います。

.dir-locals.el

((c++-mode
  . ((eval
      . (setq-local dape-command
                    `(lldb-dap
                      :program ,(file-name-base buffer-file-name)))))))

ただし Emacs が安全でないかもしれない旨を問い合わせてきます。 ! で答えると次回以降の問い合わせを抑制できます。そのことは ~/.emacs.d/init.el に記録されます。

付録: 標準入出力リダイレクション

codelldb

Stdio Redirection

:stdio ["in.txt" :null :null]

lldb-dap

(lldb) settings list target.input-path
  target.input-path -- The file/path to be used by the executable program for
                       reading its standard input.
(lldb) settings list target.output-path
  target.output-path -- The file/path to be used by the executable program for
                        writing its standard output.
(lldb) settings list target.error-path
  target.error-path -- The file/path to be used by the executable program for
                       writing its standard error.

:preRunCommands ["settings set target.input-path in.txt"]

debugpy

Command line arguments passed to the program. For string type arguments, it will pass through the shell as is, and therefore all shell variable expansions will apply. But for the array type, the values will be shell-escaped.

https://github.com/microsoft/vscode-python-debugger/blob/b2439ad4fd3692c4f4070669bfd9bb28222392fc/package.json#L289

:args "arg1 arg2 arg3 <in.txt >out.txt 2>err.txt"


  1. pyvenv.el というものがあるようですが試していません。

GNU Emacs で VS Code 相当のコード・デバッグ支援機能を設定する (1) – Eglot

概要

GNU Emacs >=29.1 で VS Code 相当のコード・デバッグ支援機能を設定します。 VS Code はコード支援機能に Language Server Protocol (LSP) 、デバッグ支援機能に Debug Adapter Protocol (DAP) を使用しています。 Emacs でも LSP/DAP クライアントが開発されています。

ここでは次のパッケージを使います。筆者が初心者かつものぐさなのでなるべくインストールや設定が手軽そうなものを選びます。

今回は Eglot について書きます。

具体例の環境

他の環境の方は適宜読み替えてください。

Eglot

LSP サーバーとの連携により下記のようなコード支援機能が利用できるとのこと。

  • コード補完
  • 関数・クラスの定義箇所への移動
  • 識別子の説明表示
  • リファクタリング (ソース コード整理)
  • 即時診断 (文法上の誤りなど)

インストール

Emacs 29 に内蔵 (built-in) されていますが不具合修正を目当てに ELPA 版を使います。

M-x package-install RET eglot RET

LSP サーバー

Eglot で設定ずみの LSP サーバーの情報が Connecting to a server にあります。

C/C++ 用 LSP サーバー

Eglot 1.17 に設定ずみの C/C++ 用 LSP サーバーは次のとおりです。

  1. clangd
  2. ccls

ここでは clangd を選びます。 macOS の the command line developer tools に付属していますが Homebrew の llvm パッケージに含まれているものを使うこともできます。 ccls は Homebrew にあります。

Python 用 LSP サーバー

Eglot 1.17 に設定ずみの Python 用 LSP サーバーは次のとおりです。

  1. python-lsp-server (pylsp)
    • python-language-server からのフォーク
  2. python-language-server (pyls)
    • 現在メンテナンスされていない
  3. pyright
  4. jedi-language-server
  5. ruff-lsp

そのほか次のものがあります。

python-lsp-server, pyright, ruff-lsp, basedpyright は Homebrew にあります。ここでは python-lsp-server (pylsp) と basedpyright を選びます。

% brew install python-lsp-server basedpyright

python-lsp-server はプラグインのことを考慮すると pipx でインストールしたほうがよいかもしれません (詳細は未確認) 。

% pipx install python-lsp-server
% source ~/.local/pipx/venvs/python-lsp-server/bin/activate
(python-lsp-server) % python -m pip install <plug-in>
(python-lsp-server) % deactivate

Eglot を補強するパッケージ

  • コード補完
  • コード断片
  • ドキュメント表示

コード補完についてここでは Eglot のドキュメントに一例として挙げられている Company を選びます。

実行

LSP サーバーが用意できていれば適当なソース ファイル (ここでの例では .c, .cpp, .py) を開き M-x eglot RET とタイプすると Eglot が機能し始めるはずです。一旦 Eglot が開始すると当該プロジェクト配下の同類のファイルも管理するので都度 M-x eglot する必要はありません。

使い方の要点

コード補完

Emacs 組み込み
M-x キー
complete-symbol C-M-i
Company

M-x company-mode で有効にしてタイプされた文字列がマッチすると補完候補がポップ アップします。手動で表示させるには M-x company-complete 。

キー 動作
C-n/C-p 候補の選択移動
<return> 補完確定
<tab> 候補を可能な限り絞り込む
C-s/C-r 候補内を検索
C-o フィルター (トグル)
M-(digit) 10 番目までの候補の中から直接選択
<f1> 選択位置の候補のドキュメント表示
C-w 選択位置の候補のソース表示

当方の環境と利用状況では C-w は No location available と出て効果が見られませんでした。

識別子の説明表示

M-x キー
eldoc-doc-buffer C-h .

関数・クラスの定義箇所への移動

M-x キー
xref-find-definitions M-.
xref-go-back M-,
imenu M-g i

Eglot のコマンド

詳細は 3.3 Eglot Commands などを参照。

識別子のリネーム

言語解析によっているので置換に比べ確実です。

M-x eglot-rename

フォーマット

M-x eglot-format

リファクタリング

使い込んでいないこともあり詳細は把握していません。

M-x eglot-code-actions

inlay-hints

関数の仮引数や C++ で auto 定義した変数の型などをコード中に埋め込まれたかのように表示します。 python-lsp-server ではサポートされていないようです。

M-x eglot-inlay-hints-mode でトグルするはずですが挙動が直感的でないです。非表示にした後 on にしてもソース コードに変更がないと再表示されないです (バグ?) 。

設定

多くの場合とくに設定しなくても使い始めることができますが筆者は ~/.emacs.d/init.el に次のように書きました。 use-package を活用しています。

~/.emacs.d/init.el

(use-package eglot
  :init
  ;; for Emacs 29.4 Built-In 1.12.29
  (setq warning-minimum-level :error)
  :config
  (add-to-list 'eglot-server-programs
               `((python-mode python-ts-mode) .
                 ,(eglot-alternatives
                   '("pylsp"
                     ("basedpyright-langserver" "--stdio")))))
  :bind
  (:map eglot-mode-map
        ("C-c r" . 'eglot-rename)
        ("C-c f" . 'eglot-format)
        ("C-c a" . 'eglot-code-actions)
        ("C-c o" . 'eglot-code-action-organize-imports)
        ("C-c q" . 'eglot-code-action-quickfix)
        ("C-c e" . 'eglot-code-action-extract)
        ("C-c i" . 'eglot-code-action-inline)
        ("C-c w" . 'eglot-code-action-rewrite)
        ("C-c m" . 'eglot-inlay-hints-mode)
        ("C-c h" . 'eldoc))
  :hook
  ((c-mode c++-mode) . eglot-ensure))

(use-package company
  :bind
  ("C-c SPC" . 'company-complete)
  :hook
  (eglot-managed-mode))
  1. Emacs 29.4 Built-In 1.12.29 では Warning のウインドウがたびたび開くので warning-minimum-level を調節しています。 ELPA 最新版には不要です。
  2. .py ファイルを開き M-x eglot を実行すると pylsp と basedpyright から選べるようになります (Variable: eglot-server-programs) 。 <tab> で補完が効きます。
  3. Eglot 関連コマンドにキーをバインドします (eglot-mode-map)。
  4. .c/.cpp ファイルを開くだけで Eglot が有効になります (2.2 Starting Eglot) 。

LSP サーバーの詳細設定

詳細を確認していないので参照先を示すに留めます。

Eglot 側からの LSP サーバーの設定

LSP サーバー側での設定

設定ファイルを読み込む LSP サーバーがあります。ここで取り上げた LSP サーバーはいずれも該当するようです。当然 VS Code などの他の LSP クライアントと設定を共有することになります。

続く

次回 は DAP クライアントの Dape について書きます。