Vim の単語を対象とするテキストオブジェクトを使い分ける

さて、Vim で単語を選択するテキストオブジェクトといえば iwaw ですが、わたしは他にもいくつかの『単語を選択する』テキストオブジェクトを使い分けています。

そもそも iw とは

iwaw は『カーソル下の単語』を対象とするテキストオブジェクトになる。
では、この『単語』とは具体的にはどういう定義なのか。
:help word をみてみると

                                                        *word*
word はアルファベット、数字、アンダースコア、もしくは他の非空白文字の連続で構
成され、ホワイトスペース(スペース、タブ、<EOL>)で区切られます。これらは
'iskeyword' オプションで変更することができます。空行もwordとみなされます。

と記述されている。
つまり正規表現\wのような『アルファベット、数字、アンダースコア』だけではなくて 'iskeyword' にも依存する事がわかる。
Vim における正規表現 \w[0-9A-Za-z_] にマッチする文字になる。

'iskeyword' とは

:help 'iskeyword' を見ると

'iskeyword' 'isk'        文字列 (MS-DOSとWin32でのVimの既定値:
                                            "@,48-57,_,128-167,224-235"
                                   それ以外のシステムでのVimの既定値:
                                            "@,48-57,_,192-255"
                                Viの既定値: "@,48-57,_")
                        バッファについてローカル
                        {Vi にはない}
        Keyword は、"w", "*", "[i" 等の多くのコマンドで検索と認識に使われる。
        またパターン |pattern| 内の "\k" にも使われる。このオプションの値の書
        式の説明については、オプション 'isfname' を参考にすること。C言語プログ
        ラムには "a-z,A-Z,48-57,_,.,-,>" が使えるだろう。
        ヘルプファイルでは、このオプションは '*', '"', '|' と空白の仲間を除い
        た全ての printable な文字 {訳注: 文書先頭を参照} に設定される (コマン
        ド上で CTRL-] を入力したときにそのコマンドについてのヘルプにジャンプで
        きるようにするため)。
        'lisp' がオンのときは、文字 '-' は常に含まれる。
        NOTE: このオプションは、オプション 'compatible' がオンのときはViの既定
        値に、オフのときはVimの既定値になるので注意すること。

と記述されています。
噛み砕いていうと『バッファごとに単語の定義を拡張するため』のオプションになります。
例えば、filetype=vim の場合には @,48-57,_,128-167,224-235,:,# という風に #: などを含むキーワードが設定されている。
これにより : を含む g:global_value みたいな変数名や # を含む hoge#bar_foo() みたいな autoload 関数名を1つの単語として扱う事ができるようになる。
これを使用して各言語によって対象とする『単語』を変更する事ができる。

また、この 'iskeyword' の設定は正規表現 \k にマッチする値としても参照される。
したがって、Vim プラグインをつくるときに『単語』を対象とするような処理を行う場合は \w\k を使い分ける必要がある。
他にも 'iskeyword'*expand("<cword>") などでも使用されるので覚えておくとよい。

'iskeyword' 以外の範囲を選択したい

ここまで読んで iwaw の単語の定義がわかったと思う。
では、冒頭の話に戻る。
iw'iskeyword' の値に依存するので g:global_valuehoge#foo_bar みたいな :# を含む名前を対象とすることはわかった。
では逆に :# などを含まない正規表現 \w にマッチする global_valuehoge だけを選択したい場合はどうすればよいのか?
'iskeyword' から :# を取り除けばよい?しかし、『何を単語としたい』のかはその時によって違うはずだ。
その度に 'iskeyword' を変更するなどバカらしい。

textobj-from_regexp を使う

そんな時に利用できるのが textobj-from_regexp プラグインになる。
neobundle.vim を使っていれば以下のように設定を追加すればインストールすることができる。

NeoBundle "osyo-manga/vim-textobj-from_regexp"

このプラグインを使用することで正規表現を使用して『任意の範囲』のテキストオブジェクトを簡単に定義する事ができる。

textobj-from_regexp を使用して対象とするテキストオブジェクトを使い分ける

実際に使ってみる。
先ほど言っていた正規表現 \w にマッチする単語を選択するような設定は以下のようにして記述する事ができる。

" i<C-w> で \w の範囲を選択する
omap <expr> i<C-w> textobj#from_regexp#mapexpr('\w\+')
xmap <expr> i<C-w> textobj#from_regexp#mapexpr('\w\+')

上記のコードを見てもわかるとおり、設定方法は至って単純だ。
textobj#from_regexp#mapexpr() の引数に対して『対象となる』正規表現を渡し、それを <expr> としてキーマッピングするだけである。
これでテキストオブジェクト i<C-w> を呼び出すことで \w にマッチするような範囲が対象となる。
これにより 'iskeyword' に依存する g:global_value を選択したいのかそれとも global_value だけを選択したいのかがキーマッピングにより柔軟に切り替える事ができる。 また、更に細かく globalvalue だけといった『_ を含まない英数字のみの範囲』も対象としたい場合は以下のように設定することもできる。

omap <expr> i<A-w> textobj#from_regexp#mapexpr('[A-Za-z0-9]\+')
xmap <expr> i<A-w> textobj#from_regexp#mapexpr('[A-Za-z0-9]\+')

これにより

  • 従来の 'iskeyword' に依存する iw
  • \w だけにマッチする i<C-w>
  • 英数字のみにマッチする i<A-w>

という3種類の選択方法を使い分ける事ができるようになる。

まとめ

  • テキストオブジェクト iw, aw の範囲は 'iskeyword' に依存する
  • Vim おける正規表現 \w\k は意味が少し違うので使い分ける必要がある
  • textobj-from_regexp を使用して複数の単語の範囲を使い分けると捗る