Quantcast
Channel: Vimタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 5608

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

$
0
0

はじめに

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

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

:normal コマンドの場合

function!s:foo() abort
  execute "normal! :echomsg 'foo'\<CR>"endfunctionnnoremap<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')endfunctionnnoremap<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')endfunctionnnoremap<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'endfunctionnnoremap<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'endfunctionnnoremap<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'endfunctionnnoremap<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
  letl:bar='bar'call feedkeys(":echomsg 'foo'\<CR>",'inx')echomsgl:barendfunctionnnoremap<nowait><Space> :<C-u>call<SID>foobar()<CR>:letbar='baz'<CR>

の出力が

foo
baz

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


Viewing all articles
Browse latest Browse all 5608

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>