導入
さてみなさん、突然ですが、コードを書いていて、「あっやっぱり true
じゃなくて false
だった」「 &&
じゃなくて ||
だった」などということが多々あるのではないでしょうか。そして、往々にして、入れ替えることになる組み合わせは決まっているのではないでしょうか。こういうとき、いちいちバックスペースを連打して打ち直すのは、思考のノイズになってしまってよくありません。そこで、単一のコマンドを使って、あらかじめ登録しておいた一連の thing
をじゅんぐりに出してくれる grugru.el
というパッケージを開発しました。
とりあえず使ってみたい人は、 M-x package-install RET grugru RET
してください
(インストールできない場合は、ここを参照)。その後、
(grugru-default-setup)
(global-set-key (kbd "C-:") #'grugru)
(grugru-highlight-mode)
を評価するなり init.el
に貼り付けて再起動するなりしてみましょう。すると、じゅんぐりに変えられる
thing
をハイライトしてくれるようになります。そこで C-:
を押下すると、次の thing
へと置き換えてくれます。
口で言ってもわかりにくいので、例として、C++の以下のようなソースコードを開いてみましょう。
#include <iostream>
#include <vector>
#include <array>
#include <deque>
class c{
private:
float f;
};
int main(){
bool b = false;
std::vector<int> v(1, 1);
double d;
std::cin >> d;
if (d || b) {
std::cout << d << "\n";
} else {
std::cout << b << "\n";
}
return 0;
}
ここにある シンボルはほとんどが grugru
可能です。以下にgifを置いておきます。適当なところにカーソルをもっていって、ハイライトされたら C-:
を押してみてください。連打してもよいです。実際に使ってみたgifを以下においておきます。

Figure 1: C++におけるgrugruのデモ
自分で定義する
ここでは grugru-default-setup
を使ってあらかじめ用意されたものを grugru
していますが、自分で定義することも可能です。定義するための関数は主に3つあります。
(grugru-define-global GETTER STRINGS-OR-GENERATOR)
(grugru-define-on-major-mode MAJOR GETTER STRINGS-OR-GENERATOR)
(grugru-define-local GETTER STRINGS-OR-GENERATOR)
書き方はとても簡単です。 GETTER
の部分には、 symbol
、 word
、 char
など、どの範囲をひとつの thing
としてみなすかを指定します。
STRINGS-OR-GENERATOR
には、 grugru
したい一連の文字列のリストを渡します。
grugru-define-global
はEmacs全体を通じてそれらの文字列が grugru
できるようになり、
grugru-define-on-major-mode
は MAJOR
で指定したメジャーモード(リストによる複数指定可)全体、
grugru-define-local
は現在のバッファのみで grugru
できるようになる、という違いがあります。書き方の例を示します。
(grugru-define-global 'word '("aaaa" "bbbb" "cccc"))
(grugru-define-on-major-mode '(c-mode c++-mode) 'symbol '("unsigned" "signed"))
(grugru-define-local 'char '("a" "b" "c"))
基本的には、 GETTER
には symbol
(Lispにおける識別子的な意味合い)を選んでおけば大丈夫です。もし「camelCase」の「camel」の部分だけ、といった symbol
内の単語を対象にしたい場合は
word
を、連続する記号類 (&&
や >>=
など)を対象にしたい場合は non-alphabet
を使用します。自前で定義することも可能ですが、高度な内容になるため高度な内容に預けます。
もし大文字小文字の情報を無視して定義したい場合は、 STRINGS-OR-GENERATOR
に以下のように書けばよいです。
;; 誤植があったためコードを修正しました
(grugru-define-global 'word (grugru-metagenerator-keep-case '("aaa" "bbb" "ccc")))
;; AAA -> BBB -> CCC のような `grugru' が可能!
実は STRINGS-OR-GENERATOR
には GENERATOR
と呼ばれる関数を書くことができますが、高度な内容になるため高度な内容に預けます。
また、 grugru-define-global
と grugru-define-on-major-mode
はいっぺんに定義するためのマクロが用意されています。以下の3つは全て等価になります(上2つはLispの文法上等しい)。
(grugru-define-multiple
(fundamental-mode
. ((word . ("aaa" "bbb" "ccc"))
(symbol . ("xxx" "yyy" "zzz"))
(word . ("abc" "def" "ghi"))))
(word . ("aaaa" "bbbb" "cccc"))
(symbol . ("xxxx" "yyyyy" "zzzzz"))
(word . ("abcd" "defd" "ghid")))
(grugru-define-multiple
(fundamental-mode
(word "aaa" "bbb" "ccc")
(symbol "xxx" "yyy" "zzz")
(word "abc" "def" "ghi"))
(word "aaaa" "bbbb" "cccc")
(symbol "xxxx" "yyyyy" "zzzzz")
(word "abcd" "defd" "ghid"))
(progn
(progn
(grugru-define-on-major-mode 'fundamental-mode 'word '("aaa" "bbb" "ccc"))
(grugru-define-on-major-mode 'fundamental-mode 'symbol '("xxx" "yyy" "zzz"))
(grugru-define-on-major-mode 'fundamental-mode 'word '("abc" "def" "ghi")))
(grugru-define-global 'word '("aaaa" "bbbb" "cccc"))
(grugru-define-global 'symbol '("xxxx" "yyyyy" "zzzzz"))
(grugru-define-global 'word '("abcd" "defd" "ghid")))
ちなみに、複雑な grugru
にも対応すべく、 GETTER
や STRING-OR-GENERATOR
には関数を与えることが可能になっています。詳細はここでは省きますが、
GETTER
は引数なしでカーソル位置から thing
の始点と終点のコンスセルを返し、
STRING-OR-GENERATOR
は第一引数として貰った候補が有効なら次の候補(第二引数が non-nil
なら前の候補)
を返せばよいです。
高度な内容
この項の内容はやや高度です。Emacs Lispをある程度理解している方向けになります。興味のないかたは飛ばしていただいてかまいません。
grugru-define-*
における GETTER
と STRINGS-OR-GENERATOR
は、本質的には関数です。
GETTER
で指定できる symbol
や word
は grugru-getter-alist
から alist-get
を通じて関数に置き換えられます。
STRINGS-OR-GENERATOR
で指定できる文字列のリストは、 grugru-strings-metagenerator
に格納された高階関数に通すことで関数 GENERATOR
に置き換えられます。以下はそれらを内部で行っている関数です。
(defun grugru--get-getter-function (getter)
"Get getter function from GETTER."
(setq getter (or (cdr (assq getter grugru-getter-alist)) getter))
(pcase getter
((pred functionp)
getter)
((pred integerp) ;「local な `grugru' をinteractiveに定義する」を参照
(apply-partially #'grugru--metagetter-with-integer getter))
(_ `(lambda () ,getter))))
(defun grugru--get-generator (strings-or-generator)
"Return generator from STRINGS-OR-GENERATOR."
(if (functionp strings-or-generator) strings-or-generator
(funcall grugru-strings-metagenerator strings-or-generator)))
Getter
GETTER
は、引数をとらず、2つのポイントをコンスセルにして返す関数です。返り値は bounds-of-thing-at-point
と同じで、 car
は現在のバッファにおける開始位置、 cdr
は終了位置のポイントになります。
grugru
は、この間にある部分を GENERATOR
に渡し、次の候補を得ようと試みます。
GETTER
の例として、 word
に対応する GETTER
を以下に示しておきます。
(defun grugru--getter-word ()
"Get beginning/end of word at point."
(if (or (eq (point) (point-at-eol))
(string-match "[-\\[\\]_:;&+^~|#$!?%'()<>=*{}.,/\\\\\n\t]\\| "
(buffer-substring (point) (1+ (point)))))
(save-excursion (cons (subword-left) (subword-right)))
(save-excursion
(let ((x (subword-right))
(y (subword-left)))
(cons y x)))))
Generator
GENERATOR
は、1つの引数と1つの省略可能引数をとり、 文字列 next-string
、 (valid-bounds . next-string)~、 ~nil
のどれかを返します。一つ目の引数は文字列 STRING
で、 GENERATOR
はこの文字列の次の文字列を返します。もし省略可能な第二引数 REVERSE
が
non-nil
である場合、次の文字列の代わりに 前 の文字列を返します。次の文字列として該当するものがない場合は nil
を返します。 valid-bounds
は (BEGIN . END)
のリストです。これは bounds-of-thing-at-point
の返り値とほぼ同じ意味合いですが、 STRING
の開始位置を 0
としたときの位置になります。
valid-bounds
を指定するち、指定した範囲のどこにもカーソルがない場合は該当する文字列なし(つまり返り値 nil
)として扱ってくれます。次の文字列を算出するのにより広い範囲の文字列を必要とするが、事実上のターゲット文字列はもっと狭い、もしくはカーソル位置がある位置にない場合は対象にするべきでない、というようなケースで使えます
(例: 関数の呼び出しに用いられている括弧だけを対象にしたい。/
関数名を grugru
するときに、一緒に引数の順番も入れ替えたいが、カーソルが引数上にあるときは対象にしたくない。)。
GENERATOR
の例として、 grugru-default.el
で定義されている grugru-default@emacs-lisp+nth!aref
を以下に示しておきます。この generator
は、カーソルが nth
か aref
にある場合に限り、 (nth 1 lst)
と (aref lst 1)
のような組み合わせを
grugru
します。なお、 grugru-utils-lisp-exchange-args
は関数呼び出し形をしたS式として read
できる文字列と数学的な意味での置換(permutation)を与えることで、引数部分を置換した文字列を返してくれる関数です。
(defun grugru-default@emacs-lisp+nth!aref (str &optional _)
"Return STR exchanged `nth' and `aref' with argument permutation."
(cond
((string-match "^(\\_<\\(nth\\)\\_>" str)
(cons
(cons (match-beginning 1) (match-end 1))
(grugru-utils-lisp-exchange-args
(replace-match "aref" nil nil str 1)
'(2 1))))
((string-match "^(\\_<\\(aref\\)\\_>" str)
(cons
(cons (match-beginning 1) (match-end 1))
(grugru-utils-lisp-exchange-args
(replace-match "nth" nil nil str 1)
'(2 1))))))
local な grugru
をinteractiveに定義する
実は、 grugru-define-local
は、interactiveに定義することができます。バッファ使い捨ての grugru
をぱっと定義したいときに有用です。実行すると、単に置換したい
2つの文字列を聞かれます。略語の展開のような意図で使うことを想定しているため、デフォルトの GETTER
は「カーソルから1つ目の候補の文字列の長さ分前に戻った部分まで」となっています。また、リージョンがアクティブなら中身を2つ目の文字列として自動で入力されます。もし GETTER
や置換したい文字列の数を指定したい場合は、前置引数 C-u
を付けて実行してください。

Figure 2: grugru-define-local
をinteractiveに使う
選択肢の中から grugru
を選んで適用する
さて、たくさんの grugru
を定義すると、同じ thing
に対して複数の grugru
候補がある場合が出てくるかもしれません。その場合、 grugru
コマンドは単に一番優先度の高いものを実行します。優先度は「local>major-mode>global」になっていて、同じ優先度の中では「後に定義されるほど強い」というふうになっています。
しかし、せっかく定義しても使えなければ意味がありません。そこで、 grugru-select
というものが用意されています。とても素直な関数なので使ってみればわかると思いますが、
(あれば)複数の grugru
候補の中から適用したいものを選択し、さらにどの文字列へと置換するのかを選択する、というものです。この関数は、複数の grugru
候補がある場合だけでなく、 grugru
をたくさん連打しないと目的の文字列まで到達できない時に、絞り込みによって一気に到達するのにも有用です。画像では使っていませんが、 ivy
などの候補選択ライブラリと一緒に使うとより快適だと思います。

Figure 3: grugru-select
による置換先の選択画面
grugru
を再定義する
grugru
を実行して、やっぱりこれは違うな、と思ったとき、その場でサクっと再定義できると便利ですよね。設定ファイルにこだわりがない場合は、 grugru-edit
を利用するとよいです。現在のカーソルで有効な
grugru
を再定義できます。
この関数で再定義した値のうち、グローバルなものとメジャーモードローカルのものは
grugru-edit-save-file
に保存されます。 init.el
に以下のような記述をすれば、その設定は次回起動時にも読み込まれます。
(grugru-edit-load)
保存されたファイルは単なるEmacs Lispの式ですので、気に入らないことがあれば手で編集しても構いません。
ただし、 grugru-edit
を使うと設定が分散してしまうので、 init.el
に設定を集約することにこだわりがある人にはおすすめできません。 leaf
のキーワードにも対応していますし、当然直接定義を書いても問題ないので、そういう人は素直に init.el
に書いてください。
再定義関数は grugru-redefine-*
のような名前になっていて、最後の引数として新しい STRING-OR-GENERATOR
を与えることで再定義できます。 nil
を与えれば無効にできます。
init.el
に直接書く場合、自分で定義したものは書き換えれば済むので、主にデフォルトの挙動を変えたいときに使うことになりそうです。
独立した grugru
を定義する
ここまで、なるべく負荷を減らすべく、 grugru
という単一のコマンドによって thing
の置換を行うようにしてきました。しかし、時には、もしくは人によっては、「いくつかの thing
だけを置換するようなコマンドを独立して定義したい」ということがあると思います。そういう人のために、
grugru-define-GENERATOR
というものが用意されています。基本的な引数は defun
に準じます。
body
部の文法はメジャーモード指定ができない以外は grugru-define-multiple
と同様です。たとえば以下の three-state
は、“water”、“ice”、“vapor"の3つと、“solid”、“liquid”、“gas"の3つのみを順に置換することができ、それ以外はいっさい置換できません。
(grugru-define-generator three-state ()
"Docstring. This is optional."
(symbol . ("water" "ice" "vapor"))
(symbol . ("solid" "liquid" "gas")))
結論
ここに書ききれていないことも多々ありますので、詳細はREADME.org を見てね!! 質問などあれば、コメント欄、githubのDiscussionやissue(日本語可能)、メールなどなんでもいいのでぜひご連絡ください!!
みなさんも grugru
といっしょに、ストレスのないコーディングを楽しみましょう!!
お願い
私があまり知らない言語では、どのような thing
が置換されうるのかが分からないため、デフォルトがあまり充実していないのが現状です。issueでもPRでも、SlackやTwitterのDMでも構いませんし、フォーマットの有無や日本語英語も問いませんので、どの言語(どれかひとつの言語で構いません)にどのような grugru
できそうなペアがあるか(1つだけでも構いません)、教えていただけると嬉しいです。
あと、良かったら投げ銭してってね!