深酒とお昼寝で忘れる

深酒とお昼寝で忘れる

素面でも意図したことを忘れがちなしらふいとさんは、忘れる前に何かしら書き残せたらとても満足のようです

YouCompleteMe (YCM) を用いた快適な C++ のコードリーディング

はじめに

それなりに大きなコードベースをエディタで読むときには、カーソルの下の変数・関数や、クラスやメンバ関数の宣言・定義などを確認したくなるものです。以前 書いたように rg などを用いて(findgrep の代替だと思ってもらって OK です)読んでも良いのですが、何も考えずに一発で正しい場所に飛べると思考が途切れないので助かります。

比較的よくあるニーズだからか、Vim では(例えば)関数の定義されているファイル名と位置を記録した tags と呼ばれるファイルを用意すると、CTRL-] ショートカットでカーソルの下の関数名の定義に飛ぶことができます(CTRL-T で飛び元に戻る)。 tags ファイルの生成には Exuberant Ctags(または後継の Universal Ctags)、Cscope または GNU GLOBAL などを用いれば良く、「C 言語のプロジェクトならば」基本的にストレスなく目的が達成できます。*1 ところがタイトルにあるように問題は C++ 言語で、上記のアプローチはの複雑さに対してやや力が足りません(理由については man ctagsBUGSman cscopeNotices セクションをどうぞ)。

そこで、今のところ最も良い解決策だと思っているのは 悪名高い(最も嫌われているって…笑 *2YouCompleteMe(略して YCM) という Vimプラグインを使うことです。 YCM は Vim 用のコード補完エンジンで、コードリーディングに役立つ機能も備えています。C 言語ファミリー(C、C++、Objective C、Objective C++)に関してはソースコードの解析を libclang に頼っており*3(意味解析を行ない生成した AST をメモリに置くため)高速かつ 100% 正確に宣言・定義に飛ぶことができるのです。

インストール(とアップデート)

YCM はクライアントサーバ型のプラグインでサーバが C++ で書かれており(クライアントは Python)、また最新の libclang を使うことが推奨されているため、これらのコンパイルが必要になります。個人的にはコンパイルが必要なプラグインは自前で管理したいので(この記事dein_local.toml で読み込みます)プラグインを使わずにインストールします。プラグインPLUGIN_DIR ディレクトリに保存するとしたら下記のような手順を踏みます。*4

$ cd ${PLUGIN_DIR}
$ git clone https://github.com/Valloric/YouCompleteMe
$ cd YouCompleteMe
$ git submodule update --init --recursive
$ python2 ./install.py --clang-completer

--clang-completerC言語ファミリーに対応させるためのオプションになります。*5 ちなみにアップデートする場合は、下記の様に git pull して(submodule も忘れずに)から再度コンパイルします。

$ cd ${PLUGIN_DIR}/YouCompleteMe
$ git pull origin master
$ git submodule foreach git pull origin master
$ python2 ./install.py --clang-completer

準備

libclang を使って意味解析をするにはソースコードを parse する必要があるため、コンパイルする際のフラグ情報が必要になります。最も簡単な手段は compilation database を用意することです。下記の様な選択肢があります。

  • CMake をビルドに使用する場合は configure の際に -DCMAKE_EXPORT_COMPILE_COMMANDS=ON というオプションを渡す(または CMakeLists.txtset(CMAKE_EXPORT_COMPILE_COMMANDS ON) を追加する)と良いです。
  • Bear というプログラムを用いて、ビルドコマンドを wrap する。makescons だったら $ bear make$ bear scons と実行します。

これらでフラグ情報を備えた compile_commands.json というファイルが生成されるので、プロジェクトのルートディレクトリに置けば YCM が Vim の起動時に読み込んでくれます。これで準備が整いました。

本題

ここまで来たらあとはコマンドを実行するだけです。YCM のマニュアルにあるとおり、

nnoremap <Leader>jd :YcmCompleter GoTo<CR>

とでもショートカットを設定しておけば、例えばメソッド名の上で <Leader>jd とタイプすればその定義に飛ぶことができるはずです。ちなみに GoTo は下記の候補から該当「してそうな」ものに飛んでくれます。

  • GoToInclude: #include <stdio.h> などヘッダが記述されている行で実行されたら該当するファイルに飛ぶ。
  • GoToDeclaration:カーソル下の単語の宣言に飛ぶ。
  • GoToDefinition:カーソル下の単語の定義に飛ぶ。

宣言と定義では定義が優先されます。

ちなみにこの GoToVim のタグスタックに登録「されない」ため CTRL-T で飛び元に戻ることはできません。ジャンプリストには登録されるため、CTRL-OCTRL-I で行き来することになります。

おわりに

C++ のプロジェクトにおけるコードリーディングを強力にサポートしてくれる YCM という Vimプラグインを紹介しました。コード補完と合わせて、個人的にはかかせないプラグインになっています。最後に少し触れましたがタグスタックに登録されないのが気にいらないので、アドホックな手段で解決しています。こちらについてはまたの機会に紹介したいと思います。

*1:ソースコードに変更を加えると対応する tags ファイルの情報と実際の位置がずれるため、何らかの方法で tags ファイルを更新する必要があります。

*2:セットアップに骨が折れる、100MB 超のサイズ、などなど。

*3:例えば Python では jedi を使います。

*4: Linux だったら build-essential、cmake、python-dev、python3-dev などのパッケージをインストールしておく必要があります。

*5:他にも C#、Go、TypeScript、JavaScript、Rust、Java に対応させるオプションがあるようなので、使ってみたい場合は YCM のマニュアルを見てください。

echo でタブを表示する

メモが続きます。

bashzshecho コマンドでタブを表示するには \ によるエスケープを解釈してもらう必要があり、-e オプションを渡すことで可能となります。

% echo -e "Hello\tworld!"
Hello   world!

ここ で知りました。

他の端末から接続している tmux セッションを切り離す

いつも tmux という端末マルチプレクサを使って作業をしています(tmux の説明は省きます)。tmux では複数の端末から同じセッションに接続している場合、最も表示範囲の小さい端末に合わせて表示範囲が決まります(大きな端末は余白に . が表示されます)。そのため、うっかりノート PC などから接続したままになっていると、他の場所で大きい画面から接続したときに悲しい気持ちになります。

そんなときは、こちらのコマンドが役に立ちます!

tmux attach-session -d

tmux のセッションに接続する際に、既存のセッションを全て切り離してくれます。

ここ で知りました。