👨‍🔧

Neovimの'g@'でLuaの関数を呼び出す

2022/12/21に公開

この記事は、Vim Advent Calendar 2022に投稿したものとなります。
前日までの記事は、リンクを参照してください。

g@オペレーター

vimにはg@オペレーターという、少し特殊なオペレーターがあります。
このオペレーターは、実行されると入力を待機する状態となり、w4$のようなモーションが入力されると、'operatorfunc'に設定されているVimScriptの関数が発火されます。

使用例

詳しい使い方は、Vim上で:h map-operatorと調べて出てくるサンプルがわかりやすいです。

nnoremap <expr> <F4> CountSpaces()
xnoremap <expr> <F4> CountSpaces()
" doubling <F4> works on a line
nnoremap <expr> <F4><F4> CountSpaces() .. '_'

function CountSpaces(type = '') abort
  if a:type == ''
    set opfunc=CountSpaces
    return 'g@'
  endif

  let sel_save = &selection
  let reg_save = getreginfo('"')
  let cb_save = &clipboard
  let visual_marks_save = [getpos("'<"), getpos("'>")]

  try
    set clipboard= selection=inclusive
    let commands = #{line: "'[V']y", char: "`[v`]y", block: "`[\<c-v>`]y"}
    silent exe 'noautocmd keepjumps normal! ' .. get(commands, a:type, '')
    echom count(getreg('"'), ' ')
  finally
    call setreg('"', reg_save)
    call setpos("'<", visual_marks_save[0])
    call setpos("'>", visual_marks_save[1])
    let &clipboard = cb_save
    let &selection = sel_save
  endtry
endfunction

F4を押すと、CountSpaces関数が引数なしでコールされ、'opfunc(operatorfunc)'の設定をした後、g@という文字列がreturnされます。
<expr>で関数からreturnされた文字列が評価されるため、g@オペレーターが発動し、入力待機状態となります。
この状態でモーションを入力すると、'operatorfunc'に設定したCountSpaces関数が今度は引数ありでコールされます。
この時の引数は、

  • 行単位のline
  • 文字単位のchar
  • 矩形のblock

のいずれかの文字列になります。それぞれの説明は難しくなるので、:h g@を参考していただきたいです。

Luaの関数を'operatorfunc'に設定する

'operatorfunc'はVim scriptの関数を設定するためのオプションのため、Luaの関数はそのままでは発火させることが出来ません。
そこで、v:luaというvim変数を利用します。
詳しくは、:h v:lua-callで確認できますが、このvim変数を利用することで、Lua側で定義されたグローバル関数をv:lua.Somefunc()のような記述で呼び出すことが出来ます。

lua << EOF
function SomeFunc()
  print("こんばんは")
end
EOF

" :call v:lua.SomeFunc() -> こんばんは

これを利用し、'operatorfunc'にLuaの関数を設定します。
set operatorfunc=v:lua.Luaのグローバル関数 と設定します。

lua << EOF
function OpeFunc(type)
  print(string.format("OpeFunc:%s",type))
end
EOF

set operatorfunc=v:lua.OpeFunc

この状態で、g@とモーションを入力することで、Lua側で定義されているOpeFuncが引数付きで呼ばれることを確認できます。

また、'tagfunc''omnifunc'といった他の関数が設定されるオプションも同様に、v:luaを利用してLuaの関数を設定することが出来るようです。
こちらも、詳しくは:h v:lua-callで確認できます。

まとめ

g@は一見クセが強い機能ですが、モーションと組み合わせることによって簡易的な括弧補完を作ったりすることが出来ます。そういった機能をLua側で実装したい際には、v:luaを使うことで多少なりとも書きやすくなるかもしれません。

Discussion