複数箇所を同時編集するieditとmultiple-cursorsの比較
Emacsでリファクタリング時に便利な複数箇所を同時編集する拡張として、ieditとmultiple-cursorsがよく紹介されている。どちらも似たようなことができそうだが、どう違うのだろう?ということで細かい使い勝手の違いと便利な設定の方法を調べてみた。
なお、この記事で使用しているのはmelpaからパッケージインストールした以下のバージョン (Emacsは24.3)。
- iedit.el: 20131021.116
- multiple-cursors.el: 20141026.503
また、基本的に自分はLinux (ubuntu) のX環境でしか使用していないので、他のOSやターミナル環境のことは考慮していない。
iedit.el
まずはiedit.elから。これは機能としてはわかりやすく、M-x iedit-mode
(デフォルトではC-;
にバインド) でカーソル位置のシンボル (またはリージョン設定しているときはそのリージョン) を対象として、バッファ内のそれにマッチする文字列を以下のスクリーンショットのようにハイライト、そのままハイライトした領域を編集するとマッチした箇所に同じ内容が適用されるというもの。再度C-;
で同時編集モードを終了。
これだけだと、常にバッファ全体が対象になってしまい融通が利かないが、その辺りを制御するコマンドはいくつか用意されている。ただ、キーバインドがちょっと押しにくい ([Meta]+[Shift]+[H]とかだったりする) ので、若干使いやすいキーバインドにしてみたのが以下の設定。
(require 'iedit)
(define-key iedit-mode-keymap (kbd "C-m") 'iedit-toggle-selection)
(define-key iedit-mode-keymap (kbd "M-p") 'iedit-expand-up-a-line)
(define-key iedit-mode-keymap (kbd "M-n") 'iedit-expand-down-a-line)
(define-key iedit-mode-keymap (kbd "M-h") 'iedit-restrict-function)
(define-key iedit-mode-keymap (kbd "M-i") 'iedit-restrict-current-line))
この設定での基本的な使い方は、以下の通り。
-
C-;
でカーソル位置の (またはリージョン指定した) シンボルを対象として同時編集モードに入る。 -
M-h
でマッチする範囲をカーソル位置の関数に限定。 -
M-i
でマッチする範囲をカーソル位置の行に限定。M-n
,M-p
で領域を上下に1行ずつ増やす。 -
TAB
,Shift-TAB
でマッチした箇所を巡回。巡回中にC-m
で現在位置のマッチを解除。 -
C-;
で同時編集モード終了。
基本的には、まずC-;
でバッファ全体にマッチさせてみてから、必要に応じて調整する形になる。
multiple-cursors.el
一方のmultiple-cursors。まず、冒頭に「似たような」と書いたが、そもそもこちらはその名の通り、**「同じ動作をするカーソルを複数箇所に生成する」**ものであり、根本的にできることが異なる。
つまり、ieditのようにマッチした領域だけでなく、その領域を超えて同時編集が可能。例えば、同時編集状態でC-eを押すと全カーソルが行末に移動して、全行末に同じ文字列を追加したりできる。しかし、この利点は個人的にはあまり用途が見当たらない。こちらの紹介ビデオのように、例えばhtmlを編集する用途には有用なのかもしれない。
もう1つのieditとの大きな違いは、同時編集のためにマーク (カーソルを生成) する箇所を手動で1つ1つ設定できること。ただ、これに関しても既に書いたように、ieditでも手動でマッチさせる領域の調整や個々のマッチ箇所の除外ができるので、multiple-cursorsでないとできないわけではない。
multiple-cursorsの面倒な点は、自分で工夫してキーバインドを設定しないとまともに使えないこと。しかも、M-x mc/mark-all-dwim
以外で起動するときは (少なくとも1箇所マークするまで) マイナーモードが生成されないので、普通だとグローバルにマップするしかなくキーバインドがやりにくい。
それを回避するために、smartrepを利用する方法とregion-bindings-modeを利用する方法 (いずれもmelpaからパッケージインストール可能) がmultiple-cursors.elのキーバインドを少しだけ改善などで紹介されていて、これに関してもどちらも試してみた結果、個人的にはregion-bindings-modeを使う方が明らかに使い勝手がよかった (region-bindings-modeは他の用途にも有用そうなので、他でも使おうとすると困るかもしれないが)。
あと、手動でマークを付けていくときの操作を標準より直感的にできるようにするため、mc-extras (これもmelpaからパッケージインストール可能) も使うと便利。ということで、これらを併用して以下のように設定してみた。ieditを意識した設定にしている。
(require 'multiple-cursors)
(require 'mc-extras)
(require 'region-bindings-mode)
(region-bindings-mode-enable)
(define-key region-bindings-mode-map (kbd "a") 'mc/mark-all-like-this-dwim)
(define-key region-bindings-mode-map (kbd "p") 'mc/mark-previous-like-this)
(define-key region-bindings-mode-map (kbd "n") 'mc/mark-next-like-this)
(define-key region-bindings-mode-map (kbd "u") 'mc/remove-current-cursor)
(define-key region-bindings-mode-map (kbd "<tab>") 'mc/cycle-forward)
(define-key region-bindings-mode-map (kbd "<backtab>") 'mc/cycle-backward)
(define-key region-bindings-mode-map (kbd "C-;") 'multiple-cursors-mode)))
(global-set-key (kbd "C-;") 'mc/mark-all-dwim)
基本的な使い方は、
- 同時編集したい文字列をリージョン選択した状態で、
a
で同じ文字列を (dwim的に) すべてマーク。何か入力すると同時編集開始。 - 同時編集したい文字列をリージョン選択した状態で、
n
で次、p
で前の同じ文字列をマークしていく。何か入力すると同時編集開始。 -
C-;
でカーソル下のシンボルを選択して (dwim的に) 同じ文字列をすべてマーク。何か入力すると同時編集開始。 - マークした状態で
TAB
,Shift-TAB
でマーク箇所を巡回。巡回中にu
で現在位置のマークを解除。 - マーク状態 or 同時編集状態のとき、
RET
で終了。(マーク状態ではC-;
でもOK)
注意点として、同時編集を開始するために何か入力するときに、region-bindings-mode-mapに割り当ててしまったキー (a
とか) は使えない。それを避けたければ、もう少し通常の編集作業では使わないキー (M-a
とか) に割り当てた方がよいかもしれない。
比べてみた結果...
自分の使い方だと、今のところieditでいいかなという感じがする。ただ、multiple-cursorsにはここに紹介した以外にも様々なコマンドがあるようだし、前述したようにカーソルの分身を作るという点でieditとは根本的に異なるので、その辺りを使いこなせる人はmultiple-cursorsを使うのがよいだろう。