最初に結論
Vim で C# の開発をやっているマイノリティの皆さまの役に立つかもしれない記事です。
下記の設定を行うことで上の図のように Omnisharp-Vim の機能である omnisharp_find_usages (使っている場所を調べるコマンド) の結果を fzf の画面でプレビュー付きで確認できるようになります!(画像のソースコードは aspnetcore-app-workshop のコードです)
下記の設定の場合に必要なもの
init.vim (vimrc) の設定
let g:OmniSharp_selector_ui = 'fzf'
let g:OmniSharp_selector_findusages = 'fzf'
let g:OmniSharp_fzf_options = {
\'options': [
\'--delimiter', ':| col ',
\'--preview', 'bat --color=always {1} --highlight-line {2}',
\'--preview-window', '+{2}+3/2'
\]
\}
解説
ここからは、上記の設定に行きつくまでの流れにそって設定の説明をします。
きっかけ
10月から担当している案件が変わりました。案件が変わるのをきっかけに Omnisharp-Vim の使い方をおさらいしておこうと公式の内容をざっと見ると fzf と連携できることが分かりました。
fzf を使わない場合、 omnisharp_find_usages は vim 上で別画面で表示されます。
この場合、検索結果の画面を消す作業が発生します。(場合によっては便利なんですが、大体は検索してみたいファイルが決まったら消えて欲しい場面のほうが私は多かったです。)
また、デフォルトの検索結果の画面は該当箇所の前後のコードがプレビューで見れないので、本当に自分が見たいコードかどうかはファイルを開かないと分からないのが不便です。
これを fzf にすれば、フローティングウィンドウに出来て、さらにプレビュー機能も使えるのでは?と思い、Omnisharp-Vim と fzf を連携させてみることにしました。
fzf と連携してみる
公式の通り下記の設定を行ってみました。
let g:OmniSharp_selector_ui = 'fzf'
let g:OmniSharp_selector_findusages = 'fzf'
が、エラーを吐きました。ガーン。どうやら vim のプラグインとして fzf が認識できていないようです。
公式の手順 を確認してみたところ、init.vim に set rtp+= で fzf へのパスを指定すれば良いようでしたが、これが上手くいきませんでした。fzf は scoop 経由でインストールしていましたが、通すべきパスが正直分かりませんでしたので諦めました。
公式の手順 にはvim プラグインとしてインストールする手順も書かれていたので、そちらで対応します。
私はプラグインマネージャーとして dein.vim を使っていますので、こちらを参考にしつつ、下記設定で fzf を vim プラグインとして認識できるようにインストールしました。
[[plugins]] # fzf
repo = 'junegunn/fzf'
build = './install --all'
merged = 0
今一度、 omnisharp_find_usages を実行すると下記画面が出ました!

(行末が若干青くなっているのは、私が行末に空文字がある場合に色付けをするようにvimを設定しているためです。後述するフローティングウィンドウにすると、この手のvimの設定はフローティングウィンドウには引き継がれませんでした。)
とりあえず、fzf を使うことができました!
これによって、毎回検索結果の画面を手動で消す必要がなくなりました。
また、予期せぬナイスな機能として、検索結果に対して更なる絞り込みができるようになりました。(よく考えれば fzf ってそういうものなんですが...)
フローティングウィンドウにしたい
さて、fzf は使えるようになりましたが、まだフローティングウィンドウにしてプレビュー画面を出せていません。
ここからは fzf の設定が必要そうです。
OmniSharp-Vim のヘルプ によると fzfへのオプションは let g:OmniSharp_fzf_options = { 'right': '50%' } のような感じで渡せるようです。
では、オプションはどのように書くのでしょうか?
OmniSharp-Vim の fzf オプションは fzf#run を実行しているだけっぽいです。
なので fzf#run の内容を見ていると、こんな感じでリストで渡せば良さそうなことが分かりました。

試しに --preview のオプションを渡してみます。
let g:OmniSharp_fzf_options = {'options': ['--preview']}
するとプレビュー画面はエラーを吐いていますが、フローティングウィンドウとプレビュー画面自体は出現するようになりました。

fzf のオプション指定を詰める
さて、ここまでくれば、あとは fzf の設定を頑張れば良さそうです。
fzf の Preview Window の内容を見ます。
どうやらプレビューで見たいものを cat や bat に渡すことで、プレビューが見れるようです。
見たいものは {} を使って渡すようです。{} の説明には {} is replaced with the single-quoted string of the focused line とありますが、今一つピンと来ていません。
bat の使い方としては bat 見たいファイルのパス なので {} の部分が、おそらくファイルのパスになればうまく行く気がします。
一旦、vim ではなくPowerShell上で確認してみます。
echo "test.txt" | fzf --preview 'bat --color=always {}'
上記を実行すると、test.txt の内容が bat で確認できました。

どうやら {} は fzf で選択している行、この場合だと test.txt が文字列として展開されるようです。
{} をもう少し調べてみると下記の記事が大変分かりやすかったです。
となると、今回の omnisharp_find_usages を実行したときに fzf 上で選択している行がどのような形式の文字列になっているかを確認する必要がありそうです。
ちなみにプレビュー画面で確認するには bat に以下の情報を渡す必要があります。
- ファイルのパス
- ハイライトしたい行の行番号
選択されている行は src\ConferencePlanner\BackEnd\Data\ApplicationDbContext.cs: 27 col 18 public DbSet<Session> Sessions => Set<Sess.. となっています。 (.. の部分は表示上省略されているだけで、画面を大きくすると最後まで表示されます。)
まとめると
vimを開いた場所からのファイルの相対パス: 行数 col 列数 検索したシンボルを使っている行の内容
となっていました。
ここから、ファイルの相対パスと行数がとれると素敵なプレビュー画面になりそうです。
delimiter を指定してファイルの相対パスと行数を取得する
先の記事によると、区切り文字を指定したい場合は --delimiter で指定するとありました。
fzf のヘルプ を見ると --delimiter は正規表現で指定できるようです。

今回の場合だと、欲しいのはファイルの相対パスと行数だけです。なので正規表現でファイルの相対パスと行数だけ、取りだせれば良さそうです。
欲張らずにとりあえず、まずは delimiter に : を指定して、ファイルの相対パスだけ取り出せるか確認してみます。確認は vim ではなく PowerShell 上で確認していきます。
echo "src\ConferencePlanner\BackEnd\Data\ApplicationDbContext.cs: 27 col 18 public DbSet<Session> Sessions => Set<Session>();" | fzf --delimiter : --preview 'bat --color=always {1}'
上記の echo の内容は omnisharp_find_usages を実行したときに fzf 上で選択している行の内容にしています。重要なのは fzf --delimiter : --preview 'bat --color=always {1} の部分です。区切り文字に : を指定し、{1} で区切った文字列の1番目を取りだしています。
キタ━━━━(゚∀゚)━━━━!!
ついでに二番目以降の文字列はどうなっているか見ましょう。{1} の部分を {2} に変えてみます。するとエラーになりました。

bat の引数に {2} の内容がファイルのパスとして渡されてエラーとなっています。
ファイルのパスとして 27 col 18 public DbSet<S2ssion> Sessions => Set<Session>(); が渡されたようです。つまり:の区切られた文字列の塊の2番目が渡されたようです。
前述のように区切り文字は正規表現で指定できるので or 指定も可能です。今回の場合だと行数にあたる 27 という数値をとりたいので :| col (colの後に半角スペース)を指定すればうまくいく気がします。
同様に PowerShell 上で動作確認をします。
まずはファイルの相対パスが取得できるかを確認します。
echo "src\ConferencePlanner\BackEnd\Data\ApplicationDbContext.cs: 27 col 18 public DbSet<S2ssion> Sessions => Set<Session>();" | fzf --delimiter ":| col " --preview 'bat --color=always {1}'
上記を実行し先ほどと同様に bat でファイルの中身を見ることができたので {1} の部分でファイルの相対パスを取得できていそうです。
次は {1} の部分を {2}に変更して実行します。
echo "src\ConferencePlanner\BackEnd\Data\ApplicationDbContext.cs: 27 col 18 public DbSet<S2ssion> Sessions => Set<Session>();" | fzf --delimiter ":| col " --preview 'bat --color=always {2}'
すると行数にあたる 27 が bat の引数として渡され、エラーを吐いています。よって、delimiter の指定は :| col でいけそうです。

ファイルの相対パスと行数を使って検索対象の行をハイライト表示にする
さて、ファイルの相対パスと行数をとる手段は分かりました。次は、検索対象の行をハイライト表示にするようにします。ハイライト表示は簡単で --highlight-line {2} で指定すればいいようです。
PowerShell で確認します。
echo "src\ConferencePlanner\BackEnd\Data\ApplicationDbContext.cs: 27 col 18 public DbSet<S2ssion> Sessions => Set<Session>();" | fzf --delimiter ":| col " --preview 'bat --color=always {1} --highlight-line {2}'
fzf 以降のコマンドを抜き出すと fzf --delimiter ":| col " --preview 'bat --color=always {1} --highlight-line {2} となっています。( --highlight-line {2} が追加)
あれ?ハイライトされてない?と思いスクロールしてみると、ちゃんと 27 行目がハイライトされていました。

なるほどです。ハイライトだけでなく、表示を開始する行の指定も必要のようです。
先の記事によると --preview-window オプションでスクロールのオフセット位置を指定できるとありました。
今回はハイライト行がプレビュー画面の真ん中あたりに来て欲しいので、記事を参考にして、(27行目の位置 + 3行(ヘッダー分))/2(真ん中を指定)をオフセット量にします。
またまた、PowerShell上で確認しましょう。
echo "src\ConferencePlanner\BackEnd\Data\ApplicationDbContext.cs: 27 col 18 public DbSet<S2ssion> Sessions => Set<Session>();" | fzf --delimiter ":| col " --preview 'bat --color=always {1} --highlight-line {2}' --preview-window '+{2}+3/2'
先ほどのコマンドに --preview-window '+{2}+3/2' の内容が増えています。
やりました!これこそが私が求めていたプレビュー画面です!
最後はinit.vim (vimrc) で指定
さぁ、PowerShell上では動作を確認できましたので、あとは vim 上で実現するだけです。
vim 側では OmniSharp_fzf_options で指定すれば良いことは分かっています。
なのでこう、書けば良いわけです。(一番最初に示した内容と同じです。)
let g:OmniSharp_fzf_options = {
\'options': [
\'--delimiter', ':| col ',
\'--preview', 'bat --color=always {1} --highlight-line {2}',
\'--preview-window', '+{2}+3/2'
\]
\}
他の設定と合わせて書くと、こうなります。
let g:OmniSharp_selector_ui = 'fzf'
let g:OmniSharp_selector_findusages = 'fzf'
let g:OmniSharp_fzf_options = {
\'options': [
\'--delimiter', ':| col ',
\'--preview', 'bat --color=always {1} --highlight-line {2}',
\'--preview-window', '+{2}+3/2'
\]
\}
結果は、こうなります!見事に Omnisharp-Vim で検索されたものが fzf に渡されてプレビュー画面で内容確認ができるようになっています。イエーイ。

あとはお好みで bat のカラーテーマを変更すると良いでしょう。
以下の記事が役に立ちました。
あとがき
この設定のおかげで開発速度爆上がりです(自分比)。ちゃんと、fzf のオプションを確認したことがなく、今まではコピペばっかりだったので大変勉強になりました。
時々開発環境を見直してみると、当時は気づけなかったことにも気づけて良いなと感じました。











