複数箇所を同時に編集可能にする iedit の紹介

はじめに

複数箇所を同時に編集するツールEmacsに揃ってきた. 本稿ではその中の一つである,ieditについて紹介する.

参考記事

先人の書いた記事は参考になるだろう.

インストール

MELPAに登録されているので,M-x list-packages からieditを選択し,インストールする.

ソースコード

https://github.com/victorhge/iedit

設定

(require 'iedit)

使用方法

以下,使用方法について述べる.

M-x iedit-mode (基本)

M-x iedit-mode,もしくはC-;でカーソル下のシンボルをカレントバッファ全体に渡ってハイライトする. マッチ箇所を1箇所編集するだけでその編集が全体に反映されるので, リファクタリングに大変重宝するというわけである. 終了するときは再度M-x iedit-mode(or C-;)とすればよい.

ただし,シンボルは部分マッチしないので, 部分マッチさせたいときはリージョンで明示的に囲ってからiedit-modeを発動する.

たとえば,

foo
foo-bar
foo-bar-baz

という3行が記述されたバッファにおいて,カーソルが1行目の行末にある状況を考える.

このときiedit-modeを発動すると1行目のfooのみがハイライトされ,2行目と3行目のfooにはマッチしない. 2行目,3行目にあるfooにもマッチするようにしたいならば, 事前にfooをリージョンで囲ってからiedit-modeを発動せよ,ということである.

なお上記の挙動はあくまで「デフォルト」である. 後述するカスタマイズ変数の設定により,挙動を多少変更できる.

バリエーション

  • C-u 0 M-x iedit-mode or C-u 0 C-;

現在の関数内にマッチ箇所を限定させることが可能.

  • C-u M-x iedit-mode or C-u C-;

カレントバッファ内で直前のiedit-modeを実行.要はresume機能である.

  • C-u C-u M-x iedit-mode or C-u C-u C-;

バッファグローバルレベルで直前のiedit-modeを実行. あるバッファで発動したiedit-modeのマッチ内容を別のバッファでも発動したいときに用いる.

  • activeなリージョンが存在する時の振る舞い

一度iedit-modeを発動してマッチさせた後, それらマッチ箇所を含む形である大きさのリージョンを作り(activeなリージョンで囲む), 再度iedit-modeを発動させるとマッチ箇所がそのリージョン内に限定され,リージョン外のマッチ箇所は解除される. C-u M-x iedit-modeとすると逆にリージョン内のマッチを解除し,リージョン外のマッチ箇所はそのまま残る. リージョンがactivateでなくなればこの振る舞いは終了する.

  • isearch中に発動

isearch中にC-;を押すことでiedit-modeに移行できる.

キーバインド

以下はiedit-mode発動中に有効なキーバインドである.

キーバインド 説明
C-; iedit-mode終了
C-' マッチ箇所を含む行のみを抽出して表示
C-? ミニバッファにヘルプを表示
TAB 次のマッチ箇所に移動
Shift-TAB 前のマッチ箇所に移動
M-< カレントバッファ内の最初のマッチ箇所に移動
M-> カレントバッファ内の最後のマッチ箇所に移動
M-I (M-Shift-i) マッチ箇所を現在行に限定
M-H (M-Shift-h) マッチ箇所を関数内に限定
M-{ マッチ範囲を上に1行増やす
M-} マッチ範囲を下に1行増やす
M-G (M-Shift-g) 直前のiedit-modeを適用(探索範囲はバッファグローバル)
M-C (M-Shift-c) 大文字・小文字の区別をトグル
M-D (M-Shift-d) マッチ箇所を一斉に削除
M-U (M-Shift-u) マッチ箇所を大文字に
M-L (M-Shift-l) マッチ箇所を小文字に
M-N (M-Shift-n) マッチ箇所を数字を順番に挿入
M-SPC マッチ箇所を一斉にスペースに置換
M-; カーソル位置にあるマッチ箇所をトグル
M-B iedit-buffering発動

上記キーバインドはひと通り試してみて,ぜひ使用感を掴んでほしい.

使用例

  • TAB, Shift-TAB, M-<, M->

マッチ箇所がウィンドウの表示範囲外になるときに,TABやShift-TABを押していくことでどんどんジャンプしていけるのでチラ見に便利.

  • C-'

iedit-mode発動後,どれくらいハイライトがヒットしたかを視覚的に確認できる. C-u N(数字)C-'とすれば周辺N行を含めて抽出可能.デフォルトは周辺1行.

  • M-H

とりあえずiedit-modeを発動してみて,思ったより範囲が広かったらM-Hで範囲を関数内に狭める. ナローイング(例えばnarrow-to-defun)を発動する手間が省けて便利. はじめから関数内に限定したいならC-u 0 C-;

  • M-B

カーソル下のマッチ箇所のみを対象に編集可能になる(ieditのバッファリング). 編集が完了したら再度M-B or C-;で編集を全体に反映させる. 状況によっては便利.

M-x iedit-rectangle-mode(発展)

rectangle領域を対象にしてiedit-modeを発動する. するとrectangle領域の全てが編集対象になり,ある行の編集内容がすべての行に反映される. 複数行編集の拡張の一つと捉えるべき. なおキーバインドC-x r RETである.

カスタマイズ

iedit-current-symbol-default

カーソル下にあるシンボルをieditのデフォルト入力にするかどうか.初期値はt(デフォルト入力にする). nilのときはリージョンで明示的に囲ってから発動させる必要がある.

iedit-only-at-symbol-boundaries

シンボルの部分マッチを許容するかどうか.初期値はt(許容しない). nilならば部分マッチを許容する. またnilにするとマッチングが過剰になる傾向になることには注意.

iedit-toggle-key-default

iedit-modeを発動するキー.デフォルトではC-;に設定されている.

iedit-occurrence

ハイライト時のフェイス.デフォルトはhighlightを継承している.

iedit-read-only-occurrence

よく分かっていない.

iedit-case-sensitive-default

iedit-modeの編集時に大文字と小文字を区別するかどうか.初期値はt(区別しない).

iedit-unmatched-lines-invisible-default

マッチしない行を自動的に隠すかどうか.初期値はnil(隠さない).

iedit-transient-mark-sensitive

transient-mark-modeのON/OFFを気にするかどうか.初期値はt(ON/OFFを気にする). すなわち,tの場合,transient-mark-modeがOFFならば, たとえリージョンがactiveであってもieditは何もしない.

iedit-overlay-priority

The priority of the overlay used to indicate matches. 初期値は200. まだよく分かっていない.

yasnippetを用いている人は以下の設定もしておくと,TAB移動時に意図しないスニペットの展開が抑制できて幸せになれる.

(add-hook 'iedit-mode-hook
          #'(lambda ()
              (yas-minor-mode -1)))

(add-hook 'iedit-mode-end-hook
          #'(lambda ()
              (yas-minor-mode 1)))

欠点

デフォルトでC-;キーバインドとして奪ってしまうので, それらのキーを用いる設定をしている方々は注意が必要である. また,C-;C-'はterminal emulatorでは「基本的に」用いることができないので, 個人で押しやすいキーに設定したりなどカスタマイズの必要性が発生するだろう.

おわりに

本稿ではieditを紹介した. 同時編集系ではmultiple-cursorsなどもあるが,ieditでも十分実用に供するレベルという感想である.