vimのcompletefuncの仕様についてのメモ
タグ: vim / 初版公開: 2014-05-04

ユーザ補完とcompletfunc

vimでは__<C-x><C-u>__でユーザ補完を呼び出すことができる。 ユーザ補完は変数completefuncで指定された関数を使って行われる。 あるきまりに従って補完用の関数を作ってcompletefuncに指定してやることで、vimで独自の補完を定義することができる。

completefuncの関数はユーザ補完が開始されたタイミングで__2回__呼ばれる。 この呼び出しはそれぞれ異なった役割を持ち、関数は別の値を返すことが期待される。 これがポイントであり、補完の関数の理解を難しくしている原因である。

2回の呼び出しとは以下である。

  1. 補完するテキストの始点の桁番号を返すことを期待した呼び出し
  2. 補完するテキストの候補を返すことを期待した呼び出し

completefuncの関数は2つの引数findstartbaseを取り、どちらの呼び出しかで渡される引数が異なる。 1.の呼び出しでは、findstartが1で、baseがemptyである。 2.の呼び出しでは、findstartが0で、baseには補完するテキストの始点からカーソル位置までの文字列が入る。

completefuncの例

これを踏まえて、vimのドキュメントから月の名前を補完する補完関数の例を見てみよう。

fun! CompleteMonths(findstart, base)
  if a:findstart
    " 単語の始点を検索する
    let line = getline('.')
    let start = col('.') - 1
    while start > 0 && line[start - 1] =~ '\a'
      let start -= 1
    endwhile
    return start
  else
    " "a:base" にマッチする月を探す
    let res = []
    for m in split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec")
      if m =~ '^' . a:base
        call add(res, m)
      endif
    endfor
    return res
  endif
endfun
set completefunc=CompleteMonths

補完するテキストの始点の桁番号を期待した呼び出し

まずこの関数は、a:firstlineを見て、1回目の呼び出しか2回目の呼び出しかを判定して、別の処理を行う。 if a:firstlineが真になる1回目の呼び出しでは、単語の始点を検索して桁番号を返す。 具体的にははじめにgetline(.)でカーソルのある行の文字列を取得する。 続いてcol('.') - 1で、カーソル位置の直前を単語のはじまりだと仮定する。 次のwhileループでは、単語のはじまりの文字がパターン\aにマッチしている間、単語のはじまりを前にずらしてゆくことで、本当の単語のはじまりを特定する。 なお\aisfile変数で指定されたファイル名の構成文字であり、一般的にはスペースは含まれない。

このため[]をカーソル位置として以下の行でCompleteMonthsが呼び出されると、barのはじまりの桁番号である4がこの関数の返り値となる。

foo bar[] baz

補完するテキストの候補を返すことを期待した呼び出し

続いて2回目の呼び出しでは、if a:firstlineは偽となる。 こちらは前述の呼び出しよりいくらか見通しが良い。 a:baseには補完を開始する単語のはじまりからカーソル位置までの文字列が格納されている。 この値を使って候補の絞り込みを行う。 今回は月の名前を補完する例のためfor m in split("Jan..."で月の名前の配列を用意する。 この要素をa:baseとマッチさせて、マッチする要素だけを残した配列を作り、これを返す。 これがそのまま補完の候補である。

例えばa:baseMなら、MarMayのみを要素として持つ配列が返り値となる。 なお返すのは文字列の配列ではなく、ハッシュの配列でも構わない。 有効なハッシュの形式はvimで独自の補完を設定する(complete関数の使い方)で紹介したcomplete関数の第2引数と同様である。

参考