EmacsでのC/C++開発
Posted on April 7, 2019
by nobiruwa
tl;dr
lsp-modeとcclsを用いることで、十分に快適な環境を得ることができました。
切っ掛け
OpenGLのチュートリアルを使って学習しているところ、GNU Globalでは一部のAPIの補完がイマイチだったので、環境を模索することにしました。
例えば、以下のAPIはGNU Globalが/usr/include
配下を入力して作るタグGTAGSには登録されません。
目標はOpenGLの開発をベンチマークとして、以下の事が実現できるようになることです。
- 関数名の補完ができる
- 関数の引数の型・戻り値(つまり関数の型)が分かる
- 貧弱なPCでもストレスなく使用できる
変遷
- ggtags + GNU Global
- glewのAPIが補完できたりできなかったりしました。
- 関数を参照するヘッダー内行を検索することで関数の型が分かるのですが、カレントバッファーがいちいちヘッダーファイルに変化するのが億劫に感じました。
- ggtags + Exuberant Ctags
- GNU Globalと大差ありませんでした。
- irony-mode
glBindVertexArray
を補完できるようになりましたが、関数の型が分からず惜しいです。
- lsp-mode + clangd
- 補完と関数の型は申し分ありませんでした。
- ただ、clangdがlsp-modeから送信されるリクエストを処理しきれず、CPU使用率が100%を超えしばしば通信が切断されます。
- eglot + clangd
- 補完と関数の型はlsp-modeほどの情報量はありませんでしたが、申し分ありませんでした。
- eglotはEmacs側でリクエストをdebounceしますが、clangdはそれでも負荷が高いです。
- emacs-ycmd + ycmd
- 期待したような情報が表示されず、カスタマイズによる改善を断念しました。
- ggtags + universal-ctags
- universal-ctagsのコマンドを読み解くのが面倒になり使用を断念しました。
- lsp-mode + ccls
- 補完と関数の型は申し分ありませんでした。
- cclsがdeounce(キューに溜まったリクエストを間引く)するようで、CPU使用率は許容範囲でした。
lsp-mode + ccls の構築手順
開発のモードとしてlsp-modeを用い、バックエンドにはCCLSを用います。
この組み合わせが気に入った理由は以下の通りです。
- lsp-mode + company-lsp + lsp-ui により、様々な情報が出力されます。
- cclsは他のバックエンド(clangd、cquery)に比べて動作が軽量です。
- LSP (Language Protocol Server) に基づいているため、フロントエンドとバックエンドは乗り換えが比較的容易です。
cclsはクライアントからのリクエストを適度にdebounce(間引く)するため、比較的CPU使用率が跳ね上がることを防いでいるようです。
詳細はさておき、Clangと組み合わせて開発環境を揃えるための手順と設定を記録しておきます。
Clangのインストール + cclsのビルド
公式の説明からほぼそのままです。
ただ、好みでClangはDebianの公式リポジトリからインストールし、release
ディレクトリにビルドされるように変えています。
$ sudo apt-get install cmake clang clang-format clang-tools clangd libclang-dev llvm
$ cd ~/repo
$ git clone --depth=1 --recursive https://github.com/MaskRay/ccls.git ccls.git
$ cd ccls.git
$ cmake -H. -Brelease -DCMAKE_BUILD_TYPE=Release
$ cmake --build release
Emacsパッケージのインストール
M-x package-install
ccls
clang-format
lsp-mode
lsp-ui
Emacsパッケージの設定
;;;;;;;;
;; ccls
;;;;;;;;
require 'ccls)
(setq ccls-executable (expand-file-name "~/repo/ccls.git/Release/ccls")) (
;;;;;;;;
;; clang-format
;;;;;;;;
require 'clang-format) (
;;;
;; company-mode
;; company-*
;;;
require 'company)
(
;; company-backends
require 'company-clang)
(require 'company-dict)
(require 'company-lsp)
(
setq company-dict-dir "~/repo/nobiruwa.github/dot-emacs.d.git/company-dict")
(
"company"
(with-eval-after-load
(global-company-mode +1);; C-[ C-i
"C-M-i") 'company-complete)
(global-set-key (kbd "C-M-i") 'company-complete)
(define-key emacs-lisp-mode-map (kbd "C-M-i") 'company-complete)
(define-key lisp-interaction-mode-map (kbd
setq company-backends
(
'(company-bbdb
company-nxml
company-css
company-eclim
company-semantic
company-lsp
company-clang
company-xcode
company-cmake
company-capf
company-files
(company-dabbrev-code company-etags company-keywords company-dict)
company-oddmuse company-dabbrev)))
;;;;;;;;
;; lsp-mode
;;;;;;;;
require 'lsp-mode)
(setq lsp-prefer-flymake nil)
('c-mode-hook #'lsp)
(add-hook 'c++-mode-hook #'lsp) (add-hook
;;;;;;;;
;; lsp-ui
;;;;;;;;
require 'lsp-ui) (