vi というエディタに神秘的な力を与えている機能の一つ、それが [cci]:global[/cci] コマンド。
もちろん wasavi もこれを実装している。が、実装したのが結構前でそれほどコードをブラッシュアップしているわけでもなく、また今回ちょっとなかなか致命的なバグを見つけたので、おさらいしたい。下手をすると、このバグに対する修正は ex コマンドを実行する機構そのものに手を入れないといけないかもしれない。
まず global コマンドは 2 つのパスで構成される。第一に、与えられた正規表現にマッチする行を抜き出して、一時的なバッファにそれを覚えておくループを行う。第二に、バッファから 1 行取り出し、その行が存在するならば、与えられた ex コマンドを実行するというループを行う。この 2 パス動作は POSIX で記述された仕様でもある(ただし logically two-pass となっているので必ずしも実装において 2 パスにすることが求められているわけではない、と思う)。
このとき、入れ子で実行される ex コマンドには若干の制限がかけられる。
- global、v、undo コマンドは入れ子にできない
- edit コマンドなどでバッファの内容全体が置き換えられる ex コマンドを実行した場合は、動作を終了し、その旨を表示する
- その他 open、visual コマンドの動作について特別扱いがあるが、wasavi はこれらを実装してないので省略
で。
問題はそのへんではなく、global コマンドの入れ子になっている、[cci]c[/cci] オプションを付加した [cci]:s[/cci] コマンドが正しく動かないのだ。これはむずかしい。c オプションを付加した :s コマンドは処理が 1 関数の中で完結しない。一旦マッチ候補を洗い出して substituteWorker インスタンスへ突っ込んだらそこで :s コマンドの処理を抜ける。このとき、モードを ex_s_prompt 状態へ移行する。キー入力がなされたら substituteWorker インスタンス内の置換処理を呼び出す……というように、処理を分割しないといけないのだ。
入れ子のコマンドの実行が分割されていると、global コマンドの動作が正しくならない。これはすなわち、入れ子の ex コマンド群が分割されていたら、global コマンドの実行も分割しないといけないということだ。現在はそうなっていないのである。