Vim 組み込み関数の feedkeys() と 'x' フラグ

# はじめに

Vim の組み込み関数に `feedkeys()` というものがあります。 Vim にキー入力を送る関数で、例えば `:call feedkeys("1G")<CR>` と入力するとカーソルが一行目へ移動します。

乱暴に言えば `:normal` コマンドに似ているのですが、 Vim script を書く上では違いがいくつかあります。一つの大きな違いは `:normal` コマンドが即時に実行されるのに対し、 `:call feedkeys()` を使う場合はキー入力を処理キューの最後尾に追加しそれが消費されるまで遅延することです。実際に、キーマッピングを定義して比べてみましょう。



# :normal コマンドの場合

```vim
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()` を使った場合です。

```vim
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'` が含まれる場合は最後尾ではなく現在位置に挿入します。つまり、

```vim
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>` というキー入力が控えています。

```vim
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' フラグを使った場合

```vim
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' の両方を使うこともできます。

```vim
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` コマンドなら特に害はないかもしれませんが、

```vim
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
```

になるのはまずいような…？