読者です 読者をやめる 読者になる 読者になる

Vimのニッチな正規表現で遊ぼう

Vim

さあみなさんVim Advent Calendarも14日目ということで佳境にさしかかってきましたね!
とか他のAdvent Calendarに参加してたなら言ってたかもしれないですが
Vim Advent Calendarの場合1月5日ぐらいまで登録されてる状態とかどういうことですか...

まぁ案の定Advent Calendar締切ギリギリになって急いで記事書いてますこんばんわ。*1
寝ずに記事を書いてるので勢いがついて変なこと口走ってないか心配ですが、
乗るしかない、このビッグウェーブに!ということで終始勢いだけですがそれなりに有用なことも含めたつもりです。たぶん。


いろいろネタがあって迷いましたが、Vim正規表現について
他の正規表現エンジンにはない(と思われる)ニッチな機能を紹介していきたいと思います。
ちなみにVimの正規表現エンジンをマルチバイト文字に対応させたのはKoRoNさんです。

  • 項目名はすべて:helpで引けるようにしてあります。
  • 以後正規表現でマッチさせる文字列を「対象文字列」、正規表現のパターンを「パターン」と呼びます。

:help /\v と :help /\V

Vimを使うみなさんがまず戸惑うのはふだん使うPerlなどとの正規表現との違いです。
*はVimでもそのまま*ですが、+は\+ですし、?は\?です。
しかし実はVimでもPerlのような正規表現を使えるのです。
(ここでは便宜的に「バールPerlのような正規表現」を「拡張正規表現」と呼びます)


しかし実はVimでもこの拡張正規表現*2を使えるのです。大事なことなので2回言いました。
それは

/\vfoo(bar)baz

のようにして「\v」を含めると使うことができます。

nnoremap / /\v

として常に拡張正規表現を使うようにするのもいいでしょう。
人によってはPerl正規表現Vimで使えるプラグインe_regex.vimがいらなくなるかもしれません。


なんでこれがデフォルトじゃないの?と言われると
「歴史的事情」
「viの呪縛」
後方互換性」
「$ file `which vi`
/usr/bin/vi: symbolic link to `/usr/bin/vim'」
とかまぁいろいろ解答はありますが
まぁ正規表現にはよく使われてるものはあれど標準はないし
方言があるだけなので歴史的事情とか言うほどでもない気がします。
それはまぁともかく僕はもうVim正規表現飼いならされてしまった慣れてしまったので特に不満はないです。

ちなみにVim正規表現拡張正規表現にどのように対応しているかは
:help /magicすればだいたい雰囲気は掴めるかと思います。

:help /\c と :help /\C

これは先程の\vと\Vと似たような正規表現クラスで、
これがパターンに含まれていると
\cは'ignorecase'がオンであるかのように、
\Cは'ignorecase'がオフであるかのように振舞います。
ignorecaseについては.vimrcによくset ignorecaseとかset icみたいに書いてる人もいるかと思います。
検索の際に大文字小文字を考慮しないマッチをしてくれるオプションです。


ちなみにVimスクリプトではユーザのignorecaseの値に影響されないように
\cと\Cや=~#演算子と=~?演算子を使います。
=~#演算子は大文字小文字を考慮するマッチ、
=~?演算子は大文字小文字を考慮しないマッチをします。
この演算子があれば\cと\Cいらないんじゃ?と思うかもしれませんが

:echo matchstr('foo', 'Foo\c')

のように関数でマッチするか判定する場合は演算子が使えないので必要です。
上のように正規表現を扱う関数に\cや\Cを与えなかったり、
=~演算子を使ってしまうとユーザの'ignorecase'に影響されてしまいます。
ポータブルなVimプラグインを書く上で考慮すべき重要な点と言えるでしょう。

:help /\%[]

たとえば「\%[vim]」というパターンは

''
'v'
'vi'
'vim'

にマッチします。空文字列にもマッチする点に注意。

:help /\zs と :help /\ze

実例を出す前に、すでに先程ちらっと登場したmatchstr()関数の解説をしておきます。
matchstr()関数の使い方は:help参照

" 「bar」と表示
:echo matchstr('foobarbaz', 'bar')

のように、matchstr(対象文字列, パターン)といった風に使います。*3
ようはパターンにマッチした対象文字列の部分文字列を返す関数です。

matchstr()関数の解説をしたので本題に入ると
たとえば

:echo matchstr('foobarbaz', 'foo\zsbar\zebaz')

は「bar」と表示します。しかし

:echo matchstr('barbaz', 'foo\zsbar\zebaz')

はマッチしないので空文字を返します。
これはどういうことか。
つまり、'foo\zsbar\zebaz'というパターンが意味するのは、
「前にfoo、後ろにbazが続くbar」という意味なのです。
これを拡張正規表現(Perl)で書くと肯定先読みなどを駆使して

(?<=foo)bar(?=baz)

と書く必要があります。
正規表現エンジンを実装したことのある正規表現マスターであれば
こんな正規表現HEでもないでしょうが、僕ら一般人にとってはこんなの読めたもんじゃありません。
それに比べて\zsと\zeを使えば...なんということでしょう。
前後の文字列を直感的に表現できています。
これこそ求めていたものじゃないでしょうか。


しかし以下のPerlのコードのようにグルーピングでマッチした文字列に参照するコードを書けるなら何も複雑なことはありません。
肯定先読みだか後読みだかどっちが前だか後ろだか思い出せなくなるような代物を使うことはないのです。

print "match:$1" if "foobarbaz" =~ /foo(bar)baz/;


しかし思い出してください。Vimはエディタです。
エディタで正規表現を使う主なケースとはなんでしょうか?


そう、検索です。
このようにグルーピングした部分のみ検索のようなことは大抵どのエディタでもできません。
というより、する必要がありません。肯定先読みなどを使うからです。
しかし、Vimなら\zsと\zeがあるのです。


あと、さっきはまぁ読めたもんじゃないとか正直言い過ぎましたが、
後読みとか先読みとか名前がわかりにくいことと、
さらに検索で()とか押しにくい記号とかいろいろ押したくない、さっと検索したいというニーズがあるのです。


Vimmerに訊いてみれば分かるはずです。彼等はみな\zsと\zeが好きなはずです。
\zsと\zeが好きなVimmerがいなければ僕に訊いてください。僕は好きです。

:help /\%l と :help /\%c と /\%v

最後に最近発見して今自分の中で最も熱い正規表現クラスを紹介しましょう。
それは\%l、\%c、\%vです。
まだるっこしいので先に例を示します。
次のコマンドを打ってみてください。

:help help.txt@en
/\%5l\%21c\|\%6l\%20c\|\%6l\%24c\|\%6l\%28c

すると...


ほら!!隠されたキーワード「Unko」が見えてきました!!!いつも見るhelpファイルにこんなキーワードが!!!*4






(ツッコミを放棄しつつ)つまり、これらの正規表現クラスを使えばバッファの任意の場所をハイライトできるのです。
\%vは\%cと同じで桁を縦にハイライトしますが、文字を含む場合に結果が変わってきます。
これらはゼロ幅なので、「\%1l\%1c...」を検索するとバッファ1行目の1桁目から3文字分ハイライトします。
先程は\%lと\%cしか使っていませんでしたが、ゼロ幅といえど指定した位置1文字だけハイライトされるので無事「Unko」が浮かび上がったということです。


これを使えばいかにも楽しそうなものが作れると思いませんか?
この方法を使えばアニメーションができるのではないか、と思ったのですが
そういえばKoRoNさんが作ったnyancat.vimも普通にsyntax使ってやってました。
まぁどうみてもsyntax以外にVimであんなことする方法はないのですが。
いやしかしnyancat.vimは\%lや%\vは使わずバッファを書き換えているようで、
ということはこの方法はそれなりにありなんじゃ?(ネタ的に
というわけでVimでアニメーションする熱が自分の中で高まってきたのでやってみます。
せっかくなのでクリスマスな色で染め上げて雰囲気を出しましょう。


実装としてはmatchadd()と前述の%\vを使い、
奇数桁と偶数桁をそれぞれ別のハイライトグループで指定して、
それぞれ赤と緑のハイライトグループにリンクし、
ループの中で適当にsleepしながら赤と緑を交互にリンクし直してるだけです。
時間の都合で非常に単純なサンプルですね。


実装は抜きにしてスクリーンショットを見てください。



なんだか笑点っぽい色になって非常に残念です。
今度はDarkGreenでやってみます。



より笑点っぽい色になってしまいました...


ここで背景はそのままで文字だけその色にすれば以外とクリスマスっぽく見えることに気付きました。
まるでイルミネーションのようです。











ほら






















ね?













というわけで

Advent Calendarの締切的に妥協して非常に少ない試行錯誤の上
出来上がったものがこちらになります。
tyru/merryxmas.vim · GitHub

  • gVimでしか動きません。
  • :MerryXmasや:MerryXmas!してみてください。
  • 何かキーを押せば止まります。


あなたもVimでアニメーション、やってみませんか?

misc.

と、こんな感じで正規表現からなぜかアニメーションしたい熱に発展していったのですが、
よく考えてみればVimのsyntaxにはまだconcealという7.3の新機能があります。
これを使えばsyntaxで動的にバッファの内容を書き換えたりもできます。
応用すればアニメーションの自由度もより高いものになるでしょう。
応用例?それはみなさんが考えるものです(モゴモゴ


あと\kは'iskeyword'オプションに含まれてる文字であればマッチするとか、
あと\iは'isident'、\fは'isfname'、\pは'isprint'だとか、
Vimにしかない正規表現、というよりVimでしか
意味を為さない正規表現クラスはいくつかありますが、
それは読者が:help patternしていろいろ読み進めていってください。
読者が:helpを1つずつ読み進めていけば、
来年のAdvent Calendarまでずっと
幸せなクリスマス気分でいられるだろうとの粋な計らいな訳はまったくなくて
膨大すぎてまとめる気力がありませんでした。


それにVimのhelpはまだ自分も未開拓の部分がたくさんあり、再発見がたくさんあります。
読者の方々は来年のAdvent Calendarまで毎日:helpの項目を1個ずつ読んでいく
一人Advent Calendarとかどうでしょうか?あ、はい、嫌ですね。わかります。


...まぁ一人は嫌でも、多人数でやる:help読書会とかあってもよくないでしょうか?
最近のVimのイベントはかなり強烈で、一部ではスピリチュアルな替え歌で
善良なVim初心者を洗脳しunite教に引きずり込み人柱を育成するとの報告もあります。
これは善良なVimmerに対する注意喚起ですが、こういった注意喚起も事実上この勢力には無力であることが実情です。
つまりみんなunite教に入ればええんやこういった勢力、および隠れ勢力に対する密かなアンチテーゼ、かどうかはわかりませんが、
集まって:help読むだけのゆるいイベントがあってもいいんじゃないかと思います。


そんなこと言ってる自分は極度の人見知りなので参加するかはわかりません(キリッ
なのでまぁ自分のイベント経験も数少ないので
何故か毎回イベント会場で替え歌が披露されるイベントしか知らないため、
国内のVimイベントに対する偏見もあるかもしれませんが、
いつか:help読書会は一人だけでもいいからやってみたいなぁと思ってたりします。*5

*1:13:30から外出予定で1頃返ってきて午前7:13現在まで寝ずに記事を書いている状況です。なお今日も1時ごろに帰る予定なのでちょっと記事の公開遅れてしまいます。申し訳ありません。

*2:もちろん完全ではないと思いますが、+とか?とか|とか()とかよく使うものが動くので不満は少なくなるかと

*3:前述したように\cか\Cをパターン文字列に含めた方がいいですが、説明のためにあえて省いてます。

*4:なお集中線はここを参考にしました。ありがとうございます。

*5:...いやほんと普段ネットで会話してる人達と会いたいってのもすごくあるんですけどね、やっぱ新しい場所で人と会うのって緊張しちゃうんですよね...orz