Surround the world #2

surround.vim の正確な動作仕様が知りたいのだけれど、それっぽい文書が見つからないのでソースと動作を見ながら目コピーせざるを得ない。

先立って用語がある。

  • 包囲領域: カーソル位置を含む選択範囲。前方に開始文字列、後方に終端文字列がある
  • 開始文字列: 包囲領域の先頭にある文字列。”(” “‘” のようにたいていは 1 文字だがタグの場合は複数の文字になりうる
  • 終端文字列: 包囲領域の末尾にある文字列。”)” “‘” のようにたいていは 1 文字だがタグの場合は複数の文字になりうる
  • 包囲識別子: 開始文字列、終端文字列へマッピングされる 1 文字の識別子:
    識別子 開始文字列 終端文字列 備考
    / /* */ C コメントを対象にする
    次のうちのいずれか: !#$%&*+,\-.:;=?@^_|~ 識別子そのもの
    次のうちのいずれか以外のテキストオブジェクトの末尾の1文字: pswW それぞれのテキストオブジェクトの先頭文字及び末尾文字。さらに、識別子が以下のいずれかである場合は開始文字列の末尾および終端文字列の先頭に U+0020 が付加される: ([{
    a < >
    r [ ]
  • 包囲識別文字列: 開始文字列、終端文字列へマッピングされる 1 文字以上の文字列。以下の例外を除き、包囲識別子と同じく 1 文字の入力をユーザーに求める。以下の場合はモードは line_input へ遷移する:
    • 最初の文字が [cci]tT^T<,[/cci] のいずれかであった場合、[cci]>[/cci] が入力されるまでは通常の line_input と同じ動作。初期値は [cci]<[/cci]。[cci]>[/cci] が入力されたら自動的に line_input モードを抜ける(これは一時的な cnomap により実現されている)
    • 最初の文字が [cci]l[/cci] または [cci]\[/cci] であった場合、…
    • 最初の文字が [cci]f[/cci] または [cci]F[/cci] であった場合、…
    • 最初の文字が [cci]^F[/cci] であった場合、…
    • 最初の文字が上記以外であった場合、その文字が入力されたら自動的に line_input モードを抜ける。
      以下の文字に関しては特別なマッピングが施される:

      文字 開始文字列 終端文字列
      b ( )
      B { および U+0020 U+0020 および }
      r [ ]
      a < >
      p \n \n\n
      s U+0020 (空文字)
      : : (空文字)
      上記以外の英字 (空文字) (空文字)

      またこのとき、U+0020 を前置することができる。U+0020 を前置した場合、包囲を新しく行う場合に開始文字列の直後、終端文字列の直前に U+0020 が追加される

* * *

surround.vimは大きく分けて normal モード、input モード、visual モードのそれぞれに特定のマップを定義する。まず normal モードから見ていく。normal モードに定義されるマップは

  • [cci]ds[/cci]: ds + 包囲識別子 を入力する。包囲領域から開始文字列・終端文字列を削除する
  • [cci]cs[/cci]: cs + 包囲識別子(from) + 包囲識別文字列(to) を入力する。領域を囲んでいる from を to に変更する
  • [cci]cS[/cci]: cs と似ているが、開始文字列の直後、終端文字列の直前に改行が挿入され、また包囲領域の内容は適切にインデントされる
  • [cci]ys[/cci]: ys + (モーション | テキストオブジェクト) + 包囲識別文字列を入力する。モーションによって生成された範囲の先頭に開始文字列、末尾に終端文字列を追加する
  • [cci]yS[/cci]: ys と似ているが、開始文字列の直後、終端文字列の直前に改行が挿入され、また包囲領域の内容は適切にインデントされる
  • [cci]yss[/cci]: これは ys の Operation Alias に相当する。つまり暗黙的に [cci]_[/cci] モーションが指定されたことになる。従ってカーソル行全体が対象になるのだが、開始文字列が追加されるのは対象文字列の最初の非空白文字の直前、終端文字列が追加されるのは対象文字列の最後の非空白文字の直後である。なお、この機能はカウント n を前置することができ、その場合はカーソル行から n 行分が操作対象になる
  • [cci]ySs[/cci]: yss と似ているが、開始文字列の直後、終端文字列の直前に改行が挿入され、また包囲領域の内容は適切にインデントされる
  • [cci]ySS[/cci]: ySs と同じ

visual/select モードに定義されるマップは

  • [cci]S[/cci]: (v|V|^V) + S + 包囲識別文字列を入力する。normal モードの ys に相当
  • [cci]gS[/cci]: (v|V|^V) + gS + 包囲識別文字列を入力する。normal モードの yS に相当

いくつか気になる点がある。

  • d/c オペレーションが認識する包囲領域は複数行に対応していないようだ。いいのかな?
  • [cci]ys[/cci] はちょっと動作とコマンドが乖離しすぎて違和感がある。なぜ y に割り当てたんだろう
  • [cci]ys[/cci] が包囲識別文字列を最後に入力させるのは実装もめんどうそう。ys + 包囲識別文字列 + (モーション | テキストオブジェクト) ならちょっとだけ楽だったんだけど
  • 言うまでもなく、vi コマンドのオペレーションはオペレータとモーションの組み合わせで構成され、そこには直交性がある。が、surround.vim の場合決め打ちで最低限のマップ定義しかしてないので直交性が崩れている。いいのかな? ただし、たとえば [cci]>s”[/cci] とかできてもあんまり意味はないのは確かではある

しかし surround.vim 自体がすでにほぼ10年の歴史のあるプラグインであるので、世の中のユーザーはこの仕様にすっかり馴染んでいると思われる。なのでできるだけ尊重してそのまんま移植するのが正解であろう。

Leave a Reply

Your email address will not be published. Required fields are marked *