導入
さてみなさん、突然ですが、コードを書いていて、「あっやっぱり 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つだけでも構いません)、教えていただけると嬉しいです。
あと、良かったら投げ銭してってね!