1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

100行でfzf

Last updated at Posted at 2022-11-20

簡素にfzfしたい

対話的にfindの記事も書きましたが、動的に検索結果が出るのはやはり魅力的

難点

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

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

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

簡単な仕組み解説

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

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

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

デモ

fzf_demo.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

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?