はじめに
Vim の組み込み関数に feedkeys() というものがあります。 Vim にキー入力を送る関数で、例えば :call feedkeys("1G")<CR> と入力するとカーソルが一行目へ移動します。
乱暴に言えば :normal コマンドに似ているのですが、 Vim script を書く上では違いがいくつかあります。一つの大きな違いは :normal コマンドが即時に実行されるのに対し、 :call feedkeys() を使う場合はキー入力を処理キューの最後尾に追加しそれが消費されるまで遅延することです。実際に、キーマッピングを定義して比べてみましょう。
:normal コマンドの場合
function! s:foo() abort
execute "normal! :echomsg 'foo'\<CR>"
endfunction
nnoremap <nowait> <Space> :<C-u>call <SID>foo()<CR>:echomsg 'bar'<CR>
<Space> に定義されたマッピングは関数 s:foo() を実行した後、 :echomsg 'bar'<CR> を実行します。 :normal コマンドはその場で実行されるので出力されるのは
foo
bar
の順です。 :messages コマンドで確認できます。 :execute コマンドを使っているのは <CR> のような特殊なキーを :normal コマンドに渡すためで、そうでなければ必要ありません。例で実行しているのは Ex コマンドなので、実のところ、 execute "normal! :echomsg 'foo'\<CR>" は echomsg 'foo' で置き換えられますね。
feedkeys() 関数を使った場合
さて、次は feedkeys() を使った場合です。
function! s:foo() abort
call feedkeys(":echomsg 'foo'\<CR>", 'n')
endfunction
nnoremap <nowait> <Space> :<C-u>call <SID>foo()<CR>:echomsg 'bar'<CR>
出力は
bar
foo
の順になります。これは feedkeys() 関数がキー入力 :echomsg 'foo'<CR> を処理キューの最後尾に追加した結果です。この場合の <Space> の処理キューをすべて展開すると、
:<C-u>call <SID>foo()<CR>:echomsg 'bar'<CR>:echomsg 'foo'<CR>
と、このようになり出力の順番が説明できます。
'i' フラグを使った場合
feedkeys() の挙動はその第二引数に与えれらる文字列で制御することができ、 'i' が含まれる場合は最後尾ではなく現在位置に挿入します。つまり、
function! s:foo() abort
call feedkeys(":echomsg 'foo'\<CR>", 'in')
endfunction
nnoremap <nowait> <Space> :<C-u>call <SID>foo()<CR>:echomsg 'bar'<CR>
の出力は
foo
bar
となります。キー入力を s:foo() 実行の直後へ挿入した結果ですね。
:<C-u>call <SID>foo()<CR>:echomsg 'foo'<CR>:echomsg 'bar'<CR>
feedkeys() の 'x' フラグ
Vim 7.4 のいつだったか忘れましたが、 feedkeys() 関数に 'x' フラグが追加されました。
'x' 先行入力が空になるまでコマンドを実行する。 これは ":normal!" を使うのと似ている。 'x' なしで数回feedkeys()を呼んだ後、'x' ありで1回 ({string}が空でも可能) feedkeys()を呼ぶことで先行入力 をすべて実行できる。Note Vimが挿入モードを終了したとき は、スクリプト続行前の文字入力待ちによる立ち往生を避け るために、<Esc>が入力されたかのように振る舞う。 Vim 日本語ヘルプ (https://github.com/vim-jp/vimdoc-ja/) より
正直なところどういう働きなのか今までよくわかってなかったのですが、やっと 'x' フラグが違いを生む場合を見つけたのでまとめます。
'x' フラグの無い場合
次のようなキーマッピングを <Space> に定義します。上の例とは違い、 feedkeys() のあとに echomsg 'bar' が実行され、さらに関数の s:foo() の外に :echomsg 'baz'<CR> というキー入力が控えています。
function! s:foobar() abort
call feedkeys(":echomsg 'foo'\<CR>", 'n')
echomsg 'bar'
endfunction
nnoremap <nowait> <Space> :<C-u>call <SID>foobar()<CR>:echomsg 'baz'<CR>
まず、 'x' フラグなしの場合です。出力は
bar
baz
foo
となります。上の 'i' フラグなしで feedkeys() を用いた場合と同じように処理キューの末尾に追加したのでこうなります。 echomsg 'bar' は関数 s:foo() 内で実行されるので最初に来ます。
'x' フラグを使った場合
function! s:foobar() abort
call feedkeys(":echomsg 'foo'\<CR>", 'nx')
echomsg 'bar'
endfunction
nnoremap <nowait> <Space> :<C-u>call <SID>foobar()<CR>:echomsg 'baz'<CR>
出力は
baz
foo
bar
となります。 'x' フラグ付きだと :echomsg 'foo'<CR> を最後尾に加えた処理キュー
:echomsg 'baz'<CR>:echomsg 'foo'<CR>
をすべて実行してからつぎの echomsg 'bar' へ移るようです。私にはこれが最初理解できず悩みました。特に後方の echomsg 'baz' まで実行されるのが意外でした。
'i' および 'x' フラグを使った場合
さらに 'i' と 'x' の両方を使うこともできます。
function! s:foobar() abort
call feedkeys(":echomsg 'foo'\<CR>", 'inx')
echomsg 'bar'
endfunction
nnoremap <nowait> <Space> :<C-u>call <SID>foobar()<CR>:echomsg 'baz'<CR>
出力は
foo
baz
bar
となります。 :echomsg 'foo'<CR> を挿入した処理キュー
:echomsg 'foo'<CR>:echomsg 'baz'<CR>
をすべて実行してからつぎの echomsg 'bar' へ移ったみたいですね。
まとめ
feedkeys() は結果を予期しにくいので難しい印象があります。特に 'x' フラグはプラグインでの使用はかなり注意が必要でしょう。例えばあるプラグインが定義するマッピング <Plug>(foo) の中で 'x' フラグ付きで feedkeys() を使っている場合、ユーザーが
nmap <Space> <Plug>(foo):echomsg 'bar'<CR>
のようなマッピングを作れば :echomsg 'bar'<CR> の部分はプラグインの関数内で実行される可能性があります。:echomsg コマンドなら特に害はないかもしれませんが、
function! s:foobar() abort
let l:bar = 'bar'
call feedkeys(":echomsg 'foo'\<CR>", 'inx')
echomsg l:bar
endfunction
nnoremap <nowait> <Space> :<C-u>call <SID>foobar()<CR>:let bar = 'baz'<CR>
の出力が
foo
baz
になるのはまずいような…?