100行でfzf

## 簡素にfzfしたい
[対話的にfind](https://qiita.com/neras_1215/items/ff32d8e50874213930cc)の記事も書きましたが、動的に検索結果が出るのはやはり魅力的

## 難点
・nmap問題
ノーマルモードでのpopupを使用しているため、n系のmap全てを独自関数（以下ソースでは`s:my_key_map`）にまとめておかないといけない
まぁvimrcにまとめられるようにしてんだから一応問題はない

→  exe "mapclear <buffer>"
で解決.
cal win_execute(winid, "mapclear <buffer>")を追加しました

・findキャッシュ問題 →job_startによる非同期処理で対応
結局はfindコマンド結果が必要なため、全件findを別で行う。
検索対象外のオプション変更や、別ディレクトリを検索したい場合、一度キャッシュしたfind結果を更新しなくてはいけない。
これはfindが重いことを考慮して別関数にしているので、わざわざ呼び出さないといけない。
fzfコマンドに劣るが、まぁ流石にしょうがない。ちゃんと.gitフォルダとかを除外してれば、だいたいの場合かかっても3秒で、以降キャッシュ見るからノータイム。



## 簡単な仕組み解説
ノーマルモードで行う
n系のmapをクリア
popupを作成し、「検索ワード入力」
検索結果が動的に表示される
検索対象は、MRUとファイルを切り替えられる

★popupを2個作成しており
・文字入力用ポップアップ：入力キーを、文字入力とみなし、表示する
・検索結果表示用ポップアップ：入力された時に、検索結果を表示する

エンターキーで入力を終了し、検索結果をjkで選択する

## デモ
![fzf_demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/453310/7fdf424e-03f7-6617-cb37-d9084a48773b.gif)


## ソース置いておきます

```.vim
nnoremap <leader>f :call FzfStart()<CR>

let g:fzf_find_cmd = 'find . -type f -name "*" -not -path "*.git/*" -not -path "*.class"'
let g:fzf_searched_dir = execute('pwd')[1:] " first char is ^@, so trim
let g:fzf_find_result_tmp = []

fu! FzfStart()
  " clear map to escape
  nmapclear
  if stridx(execute('pwd')[1:], g:fzf_searched_dir) == -1 || len(g:fzf_find_result_tmp) == 0
    let g:fzf_find_result_tmp = []
    let g:fzf_searched_dir = execute('pwd')[1:]
    echo 'find files in ['.g:fzf_searched_dir.'] and chache ...'
    cal job_start(g:fzf_find_cmd, {'out_cb': function('s:fzf_find_start'), 'close_cb': function('s:fzf_find_end')})
  endif
  let g:fzf_mode = 'his'
  let g:fzf_searching_zone = '(*^-^) BUF & MRU'
  let g:fzf_pwd_prefix = 'pwd:[' . execute('pwd')[1:] . ']>>'
  let g:fzf_enter_keyword = []
  let g:fzf_his_result = map(split(execute('ls'), '\n'), { i,v -> split(filter(split(v, ' '), { i,v -> v != '' })[2], '"')[0] }) + map(split(execute('oldfiles'), '\n'), { i,v -> split(v, ': ')[1] })
  let g:fzf_find_result = g:fzf_his_result
  let g:fzf_enter_win = popup_create(g:fzf_pwd_prefix, #{ title: 'Type or <BS> / past:<Space> / MRU<>FZF:<Tab> / choose:<Enter> / end:<Esc> / chache refresh:<C-f>',  border: [], zindex: 99, minwidth: &columns/2, maxwidth: &columns/2, maxheight: 1, line: &columns/4-&columns/24, filter: function('s:fzf_refresh_result') })
  cal win_execute(g:fzf_enter_win, "mapclear <buffer>")
  cal s:fzf_create_choose_win()
endf

fu! s:fzf_create_choose_win()
  let g:fzf_c_idx = 0
  let g:fzf_choose_win = popup_menu(g:fzf_find_result, #{ title: g:fzf_searching_zone, border: [], zindex: 98, minwidth: &columns/2, maxwidth: &columns/2, minheight: 2, maxheight: &lines/2, filter: function('s:fzf_choose') })
  cal win_execute(g:fzf_enter_win, "mapclear <buffer>")
endf

fu! s:fzf_find_start(ch, msg) abort
  let g:fzf_find_result_tmp = add(g:fzf_find_result_tmp, a:msg)
endf

fu! s:fzf_find_end(ch) abort
  echo 'find files in ['.g:fzf_searched_dir.'] and chache is complete!!'
endf

fu! s:fzf_refresh_result(winid, key) abort
  if a:key is# "\<Esc>"
    call popup_close(g:fzf_enter_win)
    call popup_close(g:fzf_choose_win)
    return 1
  elseif a:key is# "\<CR>"
    call popup_close(g:fzf_enter_win)
    return 1
  elseif a:key is# "\<C-f>"
    let g:fzf_searched_dir = execute('pwd')[1:]
    echo 'find files in ['.g:fzf_searched_dir.'] and chache ...'
    cal job_start(g:fzf_find_cmd, {'out_cb': function('s:fzf_find_start'), 'close_cb': function('s:fzf_find_end')})
  elseif a:key is# "\<Space>"
    for i in range(0,strlen(@")-1)
      let g:fzf_enter_keyword = add(g:fzf_enter_keyword, strpart(@",i,1))
    endfor
  elseif a:key is# "\<Tab>"
    let g:fzf_mode = g:fzf_mode == 'his' ? 'fzf' : 'his'
    let g:fzf_searching_zone = g:fzf_mode == 'his' ? '(*^-^) BUF & MRU' : '(*^-^) FZF [' . g:fzf_searched_dir . ']'
    cal popup_close(g:fzf_choose_win)
    cal timer_start(0, { -> s:fzf_create_choose_win() })
  elseif a:key is# "\<BS>" && len(g:fzf_enter_keyword) > 0
    unlet g:fzf_enter_keyword[len(g:fzf_enter_keyword)-1]
  elseif a:key is# "\<BS>" && len(g:fzf_enter_keyword) == 0
    " noop
  else
    let g:fzf_enter_keyword = add(g:fzf_enter_keyword, a:key)
  endif

  if g:fzf_mode == 'his'
    let g:fzf_find_result = len(g:fzf_enter_keyword) != 0 ? matchfuzzy(g:fzf_his_result, join(g:fzf_enter_keyword, '')) : g:fzf_his_result
  else
    let g:fzf_find_result = len(g:fzf_enter_keyword) != 0 ? matchfuzzy(g:fzf_find_result_tmp, join(g:fzf_enter_keyword, '')) : g:fzf_find_result_tmp
  endif

  cal setbufline(winbufnr(g:fzf_enter_win), 1, g:fzf_pwd_prefix . join(g:fzf_enter_keyword, ''))
  cal setbufline(winbufnr(g:fzf_choose_win), 1, map(range(1,30), { i,v -> '' }))
  cal setbufline(winbufnr(g:fzf_choose_win), 1, g:fzf_find_result[0:29]) " re view only first 30 files
  return a:key is# "x" || a:key is# "\<Space>" ? 1 : popup_filter_menu(a:winid, a:key)
endf

fu! s:fzf_choose(winid, key) abort
  if a:key is# 'j'
    let g:fzf_c_idx = g:fzf_c_idx == len(g:fzf_find_result)-1 ? len(g:fzf_find_result)-1 : g:fzf_c_idx + 1
  elseif a:key is# 'k'
    let g:fzf_c_idx = g:fzf_c_idx == 0 ? 0 : g:fzf_c_idx - 1
  elseif a:key is# "\<CR>"
    return s:fzf_open(a:winid, 'e', g:fzf_find_result[g:fzf_c_idx])
  elseif a:key is# "\<C-v>"
    return s:fzf_open(a:winid, 'vnew', g:fzf_find_result[g:fzf_c_idx])
  elseif a:key is# "\<C-t>"
    return s:fzf_open(a:winid, 'tabnew', g:fzf_find_result[g:fzf_c_idx])
  endif
  return popup_filter_menu(a:winid, a:key)
endf

fu! s:fzf_open(winid, op, f) abort
  cal popup_close(a:winid)
  exe a:op a:f
  return 1
endf

```