Zsh で実行に失敗したコマンドを履歴に残さない テイク2
あらすじ:zsh で(例えば、間違ってタイプして)実行に失敗したコマンドを履歴に残さないように 以前の記事 で設定しました。ところが使っているうちに一つ気に入らない振る舞いをしている点が見つかります。この度、久しぶりに設定ファイルに手を入れて改良してみることにしました。
さて何が問題だったかというと、例えば
echo $fpath | tr ' ' '\n'
というコマンドが、履歴ファイルには
echo $fpath | tr ' ' ' '
という風に保存されてしまう点です……(\nが評価されてしまっている)。これはひどい。
以前の実装では、実行するコマンド(${1%%$'\n'})を一時変数(_LASTCMD)に保存し、実行直後に _LASTCMD にいくつかの処理を施し、終了ステータスが 0 ならば履歴に書き込んでいました。この『いくつかの処理』で echo ${_LASTCMD} としている箇所があったのが問題です。これは具体的には zsh のオプションで設定できる hist_reduce_blanks という機能*1を自前で実装しているところに登場します。Shell expansion に精通していたら回避できるような気もするのですが、諦めて別の手段で解決します。そもそも zsh に元来備わっている機能を自前で実装する必要があるアプローチは筋が悪い。
ということで今までは
『実行前にコマンドを一時保存 → コマンドの文字列にいくつかの処理 → コマンドを履歴ファイルに保存』
としていたところを
『実行コマンドを履歴ファイルに保存(いくつかの処理は zsh におまかせ)→ 終了ステータスを見て不要なら履歴ファイルから消去』
とします。
こちら を参考にしました。下記のような設定を .zshrc などに記入すれば良いはずです。*2
__update_history() {
local last_status="$?"
local HISTFILE=~/.zsh_history
fc -W
if [[ ${last_status} -ne 0 ]]; then
ed -s ${HISTFILE} <<EOF >/dev/null
d
w
q
EOF
fi
}
precmd_functions+=(__update_history)
実に 2 年越しの改良ですが(今のところ)概ね満足しています。*3
YouCompleteMe (YCM) を用いた快適な C++ のコードリーディング
はじめに
それなりに大きなコードベースをエディタで読むときには、カーソルの下の変数・関数や、クラスやメンバ関数の宣言・定義などを確認したくなるものです。以前 書いたように rg などを用いて(find と grep の代替だと思ってもらって OK です)読んでも良いのですが、何も考えずに一発で正しい場所に飛べると思考が途切れないので助かります。
比較的よくあるニーズだからか、Vim では(例えば)関数の定義されているファイル名と位置を記録した tags と呼ばれるファイルを用意すると、CTRL-] ショートカットでカーソルの下の関数名の定義に飛ぶことができます(CTRL-T で飛び元に戻る)。
tags ファイルの生成には Exuberant Ctags(または後継の Universal Ctags)、Cscope または GNU GLOBAL などを用いれば良く、「C 言語のプロジェクトならば」基本的にストレスなく目的が達成できます。*1 ところがタイトルにあるように問題は C++ 言語で、上記のアプローチはの複雑さに対してやや力が足りません(理由については man ctags の BUGS や man cscope の Notices セクションをどうぞ)。
そこで、今のところ最も良い解決策だと思っているのは 悪名高い(最も嫌われているって…笑 *2)YouCompleteMe(略して 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-completer が C言語ファミリーに対応させるためのオプションになります。*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.txtにset(CMAKE_EXPORT_COMPILE_COMMANDS ON)を追加する)と良いです。Bearというプログラムを用いて、ビルドコマンドを wrap する。makeやsconsだったら$ 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:カーソル下の単語の定義に飛ぶ。
宣言と定義では定義が優先されます。
ちなみにこの GoTo は Vim のタグスタックに登録「されない」ため CTRL-T で飛び元に戻ることはできません。ジャンプリストには登録されるため、CTRL-O と CTRL-I で行き来することになります。
おわりに
C++ のプロジェクトにおけるコードリーディングを強力にサポートしてくれる YCM という Vim のプラグインを紹介しました。コード補完と合わせて、個人的にはかかせないプラグインになっています。最後に少し触れましたがタグスタックに登録されないのが気にいらないので、アドホックな手段で解決しています。こちらについてはまたの機会に紹介したいと思います。