emacsのmultiple-cursorsでカーソルを増殖して矩形処理

2023年4月15日
thumbnail
multiple-cursors.el は、複数のカーソルによる編集機能を提供するEmacs パッケージです。これにより、単語、フレーズに複数のカーソルを出現させ、それら全てに対して同時にアクションを実行することができます。一度に数行のテキストを編集したり、コード内の変数、関数名を一度に書き換えたりするために使用できます。
こちらが完成済みのコードです。
(use-package multiple-cursors
  :ensure t
  :bind (
         ;; カーソルを当てているキーワードと同じキーワードにカーソルを複製する
         ;; リージョンが選ばれている場合は、選ばれたキーワードと同じ場所にカーソルを複製する
         ("C-." . mc/mark-next-like-this-symbol)
         ("C-," . mc/mark-previous-like-this-symbol)

         ;; カーソルを1行下または上に複製する
         ;; リージョンが選ばれている場合は、厳密にそのキーワードとマッチする箇所にカーソルを複製する
         ("C-c C-." . mc/mark-next-symbol-like-this)
         ("C-c C-," . mc/mark-previous-symbol-like-this)

         ;; カーソルを当てているキーワードで、同一ファイル上全ての場所にカーソルを複製する
         ;; リージョンが選ばれている場合は、選ばれたキーワードと同じ箇所全てにカーソルを複製する
         ("C-c C-;" . mc/mark-all-like-this-dwim)
         ))

使い方

このサンプルコードを編集するという体で説明します。
def replace_file_string(filename, oldstring, newstring):
    with open(filename, 'r') as f:
        contents = f.read()
        contents = contents.replace(oldstring, newstring)
    with open(filename, 'w') as f:
        f.write(contents)

replace_file_string('file.txt', 'old', 'new')
コードのcontents変数を別のものに変更してみましょう。一番最初のcontents変数にカーソルを合わせた後、("C-." mc/mark-next-like-this-symbol)で同じキーワードをにカーソルを置いてゆきます。
article image
この状態では全てのカーソルでリージョン選択状態になっています。
普通のカーソルとして、操作をしたい場合は1度C-gで選択モードを解除します。
contentsを適当にabcみたいな変数に変更してみます。バックスペースで文字を消して、最後にabcとタイプすれば全ての変数が変換されます。
article image
マルチカーソルを解除したい場合はC-gでできます。

注意キャメルケースをうまく認知できない

このカーソル複製方法は、キャメルケースによる変数の違いをうまく認知できません。
コードをこのように変更してみます。
def replace_file_string(filename, oldstring, newstring):
    with open(filename, 'r') as f:
        contents = f.read()
        contentsEdited = contents.replace(oldstring, newstring)
    with open(filename, 'w') as f:
        f.write(contentsEdited)

replace_file_string('file.txt', 'old', 'new')
下の画像のように、contentscontentsEditedは別の変数であるにも関わらず、どちらもcontentsが含まれているため、カーソルが複製されてしまいます。
article image

より厳密にカーソルを複製

上で利用したmark-next-like-this-symbolはざっくりとしたキーワードの範囲でカーソルを複製します。
そうではなく、リージョンで選んだ範囲でマッチするものにカーソルを複製する方法もあります。contentsに正確にマッチする場所にカーソルを複製してみましょう。一番上のcontents変数をC-SPCで選択して、("C-c C-." . mc/mark-next-symbol-like-this)でカーソルを複製します。
article image

一気にカーソルを複製する

1つ1つC-.でカーソルを増やすのが面倒な場合は、カーソルを複製したいキーワードにカーソルを当てて、"C-c C-." mc/mark-all-like-this-dwim一括選択できます。注意としましてはバッファ内全てのキーワードにカーソルを複製するので、もしかすると意図しない場所にもカーソルが生まれているかもしれません。あとは同じように一度にカーソルを操作できます。

メインカーソルを移動する

カーソルが複製されている場所が、必ずしも、現在見えているバッファの中だけに収まる訳ではありません。正しい場所にカーソルが複製できるかチェックするためにも移動はできると良いしょう。メインのカーソルは複製を始めたところにあります。私のテーマでは灰色になっているカーソルがメインで、黒色が複製されたカーソルです。メインのカーソルを次の候補に移動させるにはC-v前に戻るにはM-vで移動できます。
article image

マルチカーソルでよく使うパターン

例えば、マークダウンを書いていて、このようなリストがあるとします。
- 項目1
	- サブ項目1
	- サブ項目2
	- サブ項目3
	- サブ項目4
一連のサブ項目をトップの階層に移動させたい場合、普通に操作すると、各行のインデントを削除していく必要があります。multiple-cursorsはこれをうまい具合にやってくれます。
カーソルを- サブ項目1-に合わせて、("C-c C-." mc/mark-next-symbol-like-this)を実行します。このコマンドははリージョンを選択していない場合は、現在のカーソルの1つ下にカーソルを複製してくれます。ババっとこんな感じにカーソルを複製します。
article image
そのまま、バックスペースをタイプすると、良い感じに階層を上に移動できます。
article image

まとめ

カーソル複製の操作は正直複雑です。このブログを書いているときも思ったような複製が出来なくて色々試していました。もしも操作に迷った時は上のサンプルコードのコメントを参考にしてください。
profile image
Ted
大学でコンセンサスアルゴリズムを研究。卒業後ベンチャー企業に入社してフルスタックでWebサービスを開発。現在は大手IT企業に転職し、プログラミングを行っている。AIにプログラマーの仕事を奪って欲しいと願っている。