高速でお手軽なソースコード検索スクリプト

巨大なライブラリを利用したプロジェクトで、問題に遭遇したり、ライブラリを把握したいと思った際に、高速なソースコード検索ツールが欲しくなります。

Visual Studio の検索は遅すぎるし、gtags なども使ったみたが、エラー文字列の一部から検索したかったりする場合もあり、なかなか満足するものに出会えなかった。

そんな時に [この記事](http://blog.kazuhooku.com/2017/03/peco.html) に出会った。とてもありがたい。

とても良かったが、ファイルを開く前に周辺コードが読みたかったのと、そこから Visual Studio で開いてブレークポイントを張ったりなど、複数のキーバインドを設定したかった。

このアイデアを元に fzf のプレビューとキーバインドを利用して以下のものが実現できた。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ソースコード検索に使っている 100行程度の１ソーススクリプト。 fzf, ripgrep, sourcehighlighter を zsh, python で繋げたもの。テキストファイルなら何でも。VS や VS Code で該当行を表示することもできる。 <a href="https://t.co/qwX81K5PQl">pic.twitter.com/qwX81K5PQl</a></p>&mdash; takezoh (@takezoh_) <a href="https://twitter.com/takezoh_/status/1220232537760382976?ref_src=twsrc%5Etfw">January 23, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## 利用ツールのインストール

ご利用のパッケージマネージャーなどで、 `fzf` `ripgrep` をインストールします。

保守的なディストリビューションでインストールされる fzf だとバージョンが古く一部の機能が使えなかったりします。

## スクリプト

スクリプトは全て zsh で書いています。


### メイン部分

```zsh:g
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:該当行の文字列` に展開される。

### プレビュー部分

```zsh:g
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
}
```
1. ソースハイライトにかける
2. 該当行の周辺コードを取得
3. 該当行のスタイルを変更

プレビュー表示を作る部分は高級言語のほうがやりやすいので python を呼び出している。
`src-highlight.sh` はソースハイライトのスクリプト。
`source-highligher` や `pygments` などお好みのものをご利用ください。
プレビューウィンドウを上下に分割しているので、ターミナルの行数を２で割ったものを元に周辺コードを拾っている。

### ファイル開く部分

```zsh:g
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](https://qiita.com/Takezoh/items/a1a960696a692a54fb0f) は Visual Studio でファイルを開くスクリプト。

### スクリプト全体

https://github.com/takezoh/dotfiles/blob/master/scripts/g

```zsh:g
#!/usr/bin/env zsh

main() {
	...
}

preview() {
	...
}

open() {
	...
}

case "$1" in
	--preview)
		shift
		preview "$@"
		;;
	--open)
		shift
		open "$@"
		;;
	*)
		main "$@"
		;;
esac
```

## .ignore

ripgrep は `.ignore` を読んでくれるので、書いておくと無駄な探索が省けて高速化できる。

## Happy

ソースコードだけでなく、設定ファイル群など、テキストベースの様々なものが高速に検索できるようになって、大幅に生産性が向上し、いっぱいサボれるようになった。