有馬総一郎のブログ

(彼氏の事情)

2018年11月02日 23:20:13 JST - 5 minute read - Vim

Windowsでfzf.vimを使う(そして、正しく動かないRg検索ができるようにする)

今更だけど、巷では曖昧な検索(fuzzy find)ができるのがシャレてるらしい。

そんなこと気にもとめたことなかったけど、 unite.vimdenite.nvimと使ってきたけど、両方、曖昧な検索できるね。

しかし、一覧→normarlモード/insertモードの切替、アクションの多さ、insertモードのおける上下が<c-t>、<c-g>というのがデフォルトマップだったり、とトゥーマッチに感じる部分が多い。Python依存プラグインというのもあって、挙動が安定しないように思える(自分の使い方か悪いかも知れません…)。

次に、 ctrlp.vimを使おうとしたのだけど、 simplenote.vimで作成されるバッファが表示/検索できない。また、基本、ファイル、バッファ、MRU以外の検索の仕方がよく分からなかったので、止め。

それでもって、 fzfなるツールの存在を知る。コマンド結果をfuzzy検索できて、選択できちゃう、みたいな使い方ができる。作成者がvimmerとあって、vim用のプラグインが用意されている。

Ubuntuで使ってみると、パフォーマンスみたいのは良く分からないし、バッファー検索がたまに引っ掛かるような時があるけど、まあ良いじゃないかなと。deniteで表示できていたものとか大体表示される。

さて、Windowsでも使ってみようとするも…

function fzf#vim#files[15]..<SNR>84_fzf[18]..<SNR>84_wrap の処理中にエラーが検出されました:
行   12:
E117: 未知の関数です: fzf#wrap
E15: 無効な式です: fzf#wrap(a:name, opts, a:bang)
行   14:
E121: 未定義の変数です: wrapped
E15: 無効な式です: wrapped
function fzf#vim#files[15]..<SNR>84_fzf の処理中にエラーが検出されました:
行   18:
E117: 未知の関数です: fzf#run
E15: 無効な式です: fzf#run(s:wrap(a:name, merged, bang))

なエラーが出る…どういうことだ。もしや、Mac/Linux/Unix限定?そんなことは無かった。fzfには二つのレポジトリがあってメインである junegunn/fzfをインストールせず、 junegunn/fzf.vimだけをインストールしているためだった。

Ubuntuではfzfをインストールするために

git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install

したときに、‘junegunn/fzf’のvimプラグインも一緒に落としてきていて、それをPlug '~/.fzf'で指定することでUbuntuでは自然に使用可能な状態になっていた。

なので、Windowsも

git clone --depth 1 https://github.com/junegunn/fzf.git %USERPROFILE%/.fzf

と落してきて、

%USERPROFILE%\.fzf\bin直下にバイナリーファイル fzf.exe を配置1すれば同様に使用できるようになる。

一応、確認として、一通りの動きを確かめる…よし問題ない…かと思われたが。Rgコマンドがおかしい。ヒットしない…0件で表示される。どうしてだ。

Rg [PATTERN] rg search result (ALT-A to select all, ALT-D to deselect all)

とあるのでもしやgrep結果をかますのか?と勘違いして

autocmd QuickFixCmdPost *grep* FzfRg!

とやるも意味なし…というか

fzf on Windows

パターン未入力だと''が検索されてる?

fzf.vim/plugin/fzf.vim 52行目を見ると

    \'command!      -bang -nargs=* Rg                        call fzf#vim#grep("rg --column --line-number --no-heading --color=always --smart-case ".shellescape(<q-args>), 1, <bang>0)',

となっている、そこから fzf#vim#grep辿ってechoを仕掛けRg nameを実行すると

rg --column --line-number --no-heading --color=always --smart-case  'name'

と表示される。コマンドプロンプト(cmd.exe)からシングルコーテーションで括って検索させると確かに検索されない、というか'name'が検索される。

There may be something wrong on windows.を見ると、あまり英語は得意ではないので、勘違いしてるかも知れないが、Widowsのコマンドプロンプトの仕様なんてしったこっちゃねー、という回答。

ならshellescape(<q-args>)を直せばいいと調べる。

正直、<q-args>を更にshellescapeする意味が分からないが、どうも<q-args>のままでは括ってくれなくて、shellescapeでシングルコーテーションで括られてしまうようだ。

shellescape({string} [, {special}]) shellescape() シェルコマンドの引数として利用できるように{string}をエスケープ する。 MS-WindowsとMS-DOSでは、‘shellslash’ が設定されていない場合、 {string}をダブルクォートで囲み、{string}の中のダブルクォートを 全て二重にする。

Windowsでもset shellslashは普通するだろ。これを変えるのはありえない。なので普通にダブルコーテーションで括るようにした。幸いにして、fzf.vimではコマンドをvimrc(init.vim)で上書き設定することできる。

if has('win32') || has('win64')
  command! -bang -nargs=* FzfRg
    \ call fzf#vim#grep(
    \   'rg --column --line-number --no-heading --color=always --smart-case "'.<q-args>.'"', 1,
    \   <bang>0)
endif

ちなみにFzfRgとしてるのはlet g:fzf_command_prefix = 'Fzf'fzf系のコマンドの接頭辞に Fzf を付けているため。

'"'.fnameescape(<q-args>).'"'とかした方がいいのかなぁとか思ったけど、結構そのまま渡すのがいいみたい。エスケープが不十分らしくて fnameescape()は使うな - 永遠に未完成みたいに書かれたけど、まあ厳密さはそこまで求めてないので。

欲を言えばDenite grepみたいに実行したらパターンの入力を促されるタイプだったら良かったのだけど、Rgはそうなってないので、

nnoremap <silent> [fzf]r :<C-u>call <SID>SearchRipgrep()<CR>
function! s:SearchRipgrep()
  let l:word = input("Search Word? ")
  if l:word =~ "\s*"
    execute ":FzfRg " . l:word
  endif
endfunction

とテキトーな感じで入力を促すようにしてマッピングする。これならイけそうだ。

おまけだが、 mattn/vim-fzなるものを見付けた…いやぁ vim の進化ってのも激しいね…


  1. パス通せばどこでもいいけど ↩︎