巨大なライブラリを利用したプロジェクトで、問題に遭遇したり、ライブラリを把握したいと思った際に、高速なソースコード検索ツールが欲しくなります。
Visual Studio の検索は遅すぎるし、gtags なども使ったみたが、エラー文字列の一部から検索したかったりする場合もあり、なかなか満足するものに出会えなかった。
そんな時に この記事 に出会った。とてもありがたい。
とても良かったが、ファイルを開く前に周辺コードが読みたかったのと、そこから Visual Studio で開いてブレークポイントを張ったりなど、複数のキーバインドを設定したかった。
このアイデアを元に fzf のプレビューとキーバインドを利用して以下のものが実現できた。
ソースコード検索に使っている 100行程度の1ソーススクリプト。 fzf, ripgrep, sourcehighlighter を zsh, python で繋げたもの。テキストファイルなら何でも。VS や VS Code で該当行を表示することもできる。 pic.twitter.com/qwX81K5PQl
— takezoh (@takezoh_) January 23, 2020
利用ツールのインストール
ご利用のパッケージマネージャーなどで、 fzf
ripgrep
をインストールします。
保守的なディストリビューションでインストールされる fzf だとバージョンが古く一部の機能が使えなかったりします。
スクリプト
スクリプトは全て zsh で書いています。
メイン部分
main() {
exec rg --line-number --ignore-case "$@" . 2>/dev/null | fzf -e --multi --no-sort --exit-0 --reverse --prompt="$* > " \
--preview 'exec g --preview {}' \
--preview-window up \
--bind 'enter:execute(g --open less {})' \
--bind 'ctrl-o:execute(g --open nvim {})' \
--bind 'ctrl-v:execute-silent(g --open code {})' \
--bind 'ctrl-t:execute-silent(g --open vs {})' \
--bind 'ctrl-y:execute-silent(echo {} | clipboard -i)'
}
ripgrep でファイルを探索・展開し fzf に食わせる。
スクリプトの引数を ripgrep に与えることで対象ファイルのフィルタ等を行う。
e.g.) g -tcpp
g -g '*scl.csv'
--preview
プレビュー機能
--bind
キーバインドを設定
execute-silient
は比較的新しいバージョンでないと使えない模様。
clipboard
はプラットフォーム毎のクリップボードの扱いを吸収する自前スクリプト。
pbcopy
xclip
などに置き換えてください。
{}
は FILEPATH:LINENUMBER:該当行の文字列
に展開される。
プレビュー部分
preview() {
local lines=$(( `tput lines` / 2 - 2 ))
exec cat <<EOF | python3
import subprocess
window_lines = ${lines}
filename, linenumber, vars = '''$* '''.split(':', 2)
linenumber = int(linenumber) - 1
p = subprocess.run(['src-highlight.sh', filename], stdout=subprocess.PIPE, shell=False)
p.check_returncode()
context = p.stdout.decode('utf-8').replace('\r\n', '\n').split('\n')
contextlines = len(context)
headline = max(0, linenumber - (window_lines - 1) // 2)
tailline = min(contextlines, window_lines - (linenumber - headline) + linenumber)
headline = headline - max(0, min(window_lines, contextlines) - (tailline - headline))
out = context[headline:linenumber]
out.append(u'\u001b[38;5;253;48;5;163;01m' + vars.rstrip())
out[linenumber + 1:] = context[linenumber + 1:tailline]
print ('\n'.join(out).replace('\t', ' '))
EOF
}
- ソースハイライトにかける
- 該当行の周辺コードを取得
- 該当行のスタイルを変更
プレビュー表示を作る部分は高級言語のほうがやりやすいので python を呼び出している。
src-highlight.sh
はソースハイライトのスクリプト。
source-highligher
や pygments
などお好みのものをご利用ください。
プレビューウィンドウを上下に分割しているので、ターミナルの行数を2で割ったものを元に周辺コードを拾っている。
ファイル開く部分
open() {
local cmd=$1
shift
local args=(`echo $@ | awk -F : '{ print $1 " " $2 }'`)
local filename=${args[1]}
local linenumber=${args[2]}
case "$cmd" in
vs)
if [ -d /mnt/c/Windows ]; then
(cmd.exe /c `wslpath -aw /path/to/openfile.vbs` `wslpath -aw $filename` $linenumber) &
fi
;;
code)
if [ -d /mnt/c/Windows ]; then
code --reuse-window --goto "`wslpath -aw $filename`:$linenumber"
else
code --reuse-window --goto "$filename:$linenumber"
fi
;;
*)
exec $cmd "+${linenumber}" $filename
;;
esac
}
WSL から Windows プログラムを開く場合、ファイルパスは wslpath
コマンドを利用して Windowsファイルシステムのパスに変換して渡す必要がある。
WSL の判定は uname -a | grep Microsoft
でもいいかもしれない。
openfile.vbs は Visual Studio でファイルを開くスクリプト。
スクリプト全体
# !/usr/bin/env zsh
main() {
...
}
preview() {
...
}
open() {
...
}
case "$1" in
--preview)
shift
preview "$@"
;;
--open)
shift
open "$@"
;;
*)
main "$@"
;;
esac
.ignore
ripgrep は .ignore
を読んでくれるので、書いておくと無駄な探索が省けて高速化できる。
Happy
ソースコードだけでなく、設定ファイル群など、テキストベースの様々なものが高速に検索できるようになって、大幅に生産性が向上し、いっぱいサボれるようになった。