swap.vim を書きました

思い返してみると、実際たいしたことはないうえに頻繁にあるわけでもないんですけど、関数の引数だったり配列の要素だったりの順番を入れ替えたくなることがあります。

call func(arg1, arg2, arg3)

これが例えば arg2arg3 を入れ替えるとかだったら問題ないんです。 , arg2 を delete して、

call func(arg1, arg3)

arg3 の後ろへ paste 、

call func(arg1, arg3, arg2)

できましたね。世の中には vim-textobj-parameter あるいは fork版 という便利なものがあるので、これらを使えばいっそう心の平穏が保たれます。ところが arg1arg3 を入れ替えるとなると、うってかわって心穏やかではいられません。 arg1 を切り取ったり、コンマを移動させたり面倒なわけです。この辺 Vim がうまいことやってくれるといいなー、とは思っていました。

ところが、上の例くらい簡単ならいいんですけど、関数が引数に関数を取ったり、

call func(arg1, func(arg1, arg2, arg3), arg3)

任意の文字列を含んだり、

call func(arg1, 'foo, bar')

してくると、なかなか複雑です。しかも、言語によって違う部分もあるので難しい。そんなわけでしばらく放置していたんですけど、まあそこそこいい感じに動く、ぐらいなら可能かもしれないと思いたって swap.vim を書きました。

このプラグインは三つのキーマッピング g<, g>, gs を定義します。g< はカーソル下のアイテムを直前のアイテムと入れ替えます。 g> はカーソル下のアイテムを直後のアイテムと入れ替えます。最初の例

call func(arg1, arg2, arg3)

でいえば、カーソルを arg2 に置いて g< を入力すると arg2arg1 が入れ替わり、

call func(arg2, arg1, arg3)

一方、 g> を入力すると arg2arg3 が入れ替わります、

call func(arg1, arg3, arg2)

gs はちょっと変わり種で、 swap mode と呼んでいるモードのようなものを開始して、インタラクティブに要素入れ替えを行います。 swap mode では h, l によって隣り合う要素の入れ替え、 j, k によるカーソルの移動、 u, <C-r> による簡単なアンドゥ・リドゥ、数字 1 から 9 による入れ替えアイテムの直接指定ができます。納得のいく順番になったらご存知のように <Esc> でノーマルモードに戻ります。

これらはドットリピートで繰り返すことができますが、注意する点が一つあります。 g<, g> は常にカーソル下のアイテムとの相対位置で働く一方で、 gs が繰り返される場合は何番目のアイテムか、という絶対位置で働きます。この違いを覚えておかないと予期しない結果を得るでしょう。

一応、非明示的な行継続がある場合でもいい感じにできるようにインデントをスキップするようにしています。また、設定次第では行継続文字を無視もできます。例えば Vim script であればデフォルトで設定されているので行頭の \ をスキップします。

demo

大きなブロック (移動しているもの) があるのは {} によるグループ化を考慮しているためです。