🐙

Vimの手動補完を自動でトリガーすれば自動補完になります

2024/07/23に公開

ということに気づき勢いで作りました。


自動で補完ウィンドウが開く

思ったより行数少なくできました。

auto_cmp_start.vim
" キーワード補完の検索元の設定 お好みで
" set complete=.,w,k,b,u

" 補完動作の設定
" auto_cmp_startが何度も呼ばれないようにmenuoneでpumを表示
" 自動で選択までされないようnoselect
set completeopt=menuone,noselect

" 補完の最低文字数
" 1にしたら流石に候補が多すぎてちょっと動作が重かったので3くらいが妥当かと思われる
let s:MINIMUM_COMPLETE_LENGTH = 3

" 補完関数
function! s:auto_cmp_start() abort
  " 既に補完ウィンドウが表示されている場合は何もせず終了
  if pumvisible()
    return
  endif

  " カーソルより左側の範囲を取得し、[:keyword:]を使って補完に使えない記号などを除去
  let prev_str = (slice(getline('.'), 0, charcol('.')-1) .. v:char)
        \ ->substitute('.*[^[:keyword:]]', '', '')

  " カーソル直前の部分(補完元文字列)が最低文字数に満たなければ終了
  if strchars(prev_str) < s:MINIMUM_COMPLETE_LENGTH
    return
  endif

  " <c-n>を実行して補完スタート
  call feedkeys("\<c-n>", 'ni')
endfunction

" 入力時に上記の関数を自動で実行
augroup auto_cmp_start
  autocmd!
  autocmd InsertCharPre * call s:auto_cmp_start()
augroup END

補足

カーソル位置と桁番号・バッファ文字列がどのように対応するかは(特にインサートモードでは)直感的ではありません。以下のようなマッピングを一時的に定義して、適当な文字列にカーソルを置いて+を入力して調べました。string()を使うと空文字やスペースだけの文字列も可視化できるので調査に便利です。

inoremap + <cmd>echomsg charcol('.') string(slice(getline('.'), 0, charcol('.')-1))<cr>

また、InsertCharPreは、その名の通り文字が挿入される直前に発火します。この段階では入力した文字はバッファに反映されていません。
したがって、単純にgetline()を呼んだだけでは、そのとき入力した文字を読み込めません。この文字はv:charに保存されているので、バッファから読み込んだ文字列に連結しています。

https://vim-jp.org/vimdoc-ja/autocmd.html#InsertCharPre

ちなみに、s:auto_cmp_start()関数は条件を見てearly returnしているだけなので、条件を逆転させれば一つのifにまとめることも可能です。

let s:MINIMUM_COMPLETE_LENGTH = 3
autocmd InsertCharPre * if !pumvisible() && (slice(getline('.'), 0, charcol('.')-1) .. v:char)->substitute('.*[^[:keyword:]]', '', '')->len() >= s:MINIMUM_COMPLETE_LENGTH | call feedkeys("\<c-n>", 'ni') | endif

記事冒頭のdemoで表示される辞書はソートしたものを使っています。

https://zenn.dev/vim_jp/articles/09ab965ebb0f6e

追記

この設定をしばらく使っていて、インサートモードで<c-r>系のマッピングを使ってペーストすると暴発することがわかりました。autocmdが連続でトリガーされ、望まない位置で補完が発動してしまいました。
ということで入力中は待機し、vimがフリーになってから実行するようにしたほうが良いです。これは以前書いたdebounceによって実現可能です。

https://zenn.dev/vim_jp/articles/9b1db46217a27d

早すぎる実行を待てばよいだけなので、待機時間は0でOKです。また、この場合はバッファの更新後に関数が実行されるため、v:charを連結するテクニックは不要となります。

- autocmd InsertCharPre * call s:auto_cmp_start()
+ autocmd InsertCharPre * call Debounce($'{expand("\<SID>")}auto_cmp_start', 0)

補完ウィンドウを自動で閉じる設定も併せてご覧ください。

https://zenn.dev/kawarimidoll/articles/251289f6a638a7

Discussion