Open23

[emacs+evil]最高のオレオレキーバインドを究める

biofermin2biofermin2

emacs+evilを利用する人向けの最高のキーバインドを模索する場所。
emacsを快適に使いたい人も該当するかな?
evilを導入したらviの編集パワーをemacsに取り込めた。
viはもう実体ではなくキーバインドとしてしかその文化を残せないんじゃないか?と思うくらい。
早速alias設定もvi=emacsclientなんかにしてみたけど、
うーん、完全にうまくいくかというとそうでもないな。
serverが立ち上がっていないともちろんemacsclientは使えない訳だし。

とりあえず、使っててキー操作に不満のある点やカスタマイズについて思いついたら随時あげていく事にする。

evilを導入したら程よくemacsキーバインドも使えるので、然程不自由はしてないけど、
たまに不自由に感じたポイントもある。
これまで感じたポイントは

1,バッファ切り替え
バッファ選択の際、enterが効かなかったがoでなんとかなっているから
特にカスタマイズしなかった。
2,elscreenのタブ切り替え
これについてはいろいろカスタマイズした。
いじる箇所は.emacs.d/init.elとevilのフォルダ内のevil-maps.el
新規カスタマイズを追加する際は、init.elに書き込んで、
不要な設定を消す時は、evil-maps.el側をコメント化するようにした。

そうすれば何を追加して何を消したかがわかる。

タブの操作については自分はviではあまりタブを使わなかった。
viはターミナル側でサクッと編集してサクッと閉じるという使い方くらい。
がっつりカスタマイズしたければ、それはemacsに任せて、
vi側はほぼ基本的な使い方だけにしていた。
(昔はガッツリvimもカスタマイズしたけど、
vimをカスタマイズしているvimscriptなる言語とemacs lispでは
emacs lispの方が表現の幅とか柔軟性があるかなと。あくまで個人的な感覚だけど。
あと結局似た様な設定をするなら二度手間かなと。)

という事もあり、viだとタブはgt,gTで切り替えとの事だが、個人的にあまりその操作に馴染みがない。
どちらかというとemacs側でC-t(C-)cでタブを作成
C-t(C-)p,C-t(C-)nで前後にタブ移動、
C-t(C-)kでタブを削除
この4操作くらい。
という事でこれに関しては①tc,tp,tn,tkというタイプと
②gj,gh,gl,gkというタイプ、(後に廃止。理由については下の方に記す)
③gt,gTも一応用意した。

どちらかというと感覚的に馴染んでいるのは①。
けど、指の動きが一番少ないのは②なので、
今は②に矯正中。③はほぼほぼ使ってない。

全てnormalステートで追加してある。

3,行頭、行末移動
これに関してはemacsにmwinパッケージを追加して
便利に使ってきた。
viキーバインドだと0,$。
まぁ、0はいいとして、$は正直ダサいし、使いづらい。
という事もあって、C-a,C-eにそれぞれmwinパッケージの
操作を結びつけて設定した。
あと、せっかくviキーバインド使えるようにしたんだから
もう少しviっぽい操作もという事で、H,Lも
mwinの操作に結びつけて設定しておいた。
本来のviキーバインドだとHは画面先頭、Lは画面最終行なのだが、
正直あまり使わないし、evil側でもHとLには空きがあったので、
敢えて自分は違う動きを設定した。使い勝手としてはかなり気持ちいい。
(但し、通常のvimでは当然カーソルが違う所に飛んで行くのでこれに慣れるとビックリするけど)
C-a,C-eはnormal,insertの両ステートに、
H,Lに関してはnormalステートだけに設定している。そうしないとHとLが書けない笑

4,その他
あとはなぜかC-dがparedit-forward-deleteに結びつけてinsertステートに設定されている。
自分でもなんでそうしたのか忘れた笑
多分、自然と使っている操作で、そう設定した方が違和感を感じないからなんだろうと思う。

5,削除したもの
邪魔で消したものは次の2行
;;(define-key evil-normal-state-map "\C-t" 'pop-tag-mark)
;;(define-key evil-normal-state-map (kbd "M-.") 'evil-repeat-pop-next)
上の方はscreenでのタブの切り替えで個人的に使用してるため。
下の方は、slimeで関数定義を参照する際に使ってるので。

全然チラ裏じゃないな笑

biofermin2biofermin2

いつもlispを書いているので、次の2つのパッケージはちょっと気になっている。
evil-lisp-state
https://github.com/syl20bnr/evil-lisp-state
evil-lispy
https://github.com/sp3ctum/evil-lispy
昨日簡単に眺めた感じだと、とりあえず、lisp-stateの方はステート管理出来るので、
入れてもそんな邪魔にはならなそうな印象。
ただ、キーバインドとしてそんな欲しいかというと、
うーん、新たにvi系での括弧の編集方法を覚えないといけないのが、
面倒臭いと感じた。pareditのemacsキーバインドはそのままevil上でも
使えるし。

evil-lispyの方はキーバイドがなかなかいいんじゃないってのが
結構見られた。ただ、ステート管理と違って、モード管理だから
ちょっと入れるとややこしくなりそうな印象がした。

それだったら、evil-lispyのキーバインドをそのまま
パクってinit.elに個人設定してあげた方がいいかなと。

biofermin2biofermin2

不満点を書くのを忘れてたが、
C-f
の扱いについては非常に難しいと感じている。
viだと次ページへ飛ぶ
emacsだと次の文字に移動
emacsだからついついemacsキーバインドとして使ってしまうんだけど
いきなりカーソルが飛ぶからびっくりするんだよね笑
かといって、これをemacs側にカスタマイズしてしまうと、
vi操作している時にページめくりができなくなるからそれもまた不便で。
という事で、これは慣れるしかないのかと思っている。
evilは微妙にemacsキーバインドも混じっていたりするから、
結構便利な時は多い反面時々、そうやって混乱する事がある。
(これに関しては下方に書いた通り、ページ遷移については
Space,shift+spaceで次ページ前ページに移動する事にして、
文字の移動についてはlキーで移動する事に。
よってC-fは使わなくなった)

あと、uキー。
evil的にはundoだし、自分でもundoのつもりで使うんだけど、
戻りすぎてやってきた作業が吹っ飛ぶ笑
1uとすれば1つ手前の作業に戻るって話だけど、
それでもちょっとしっくりこない。
で、C-rでredoをやろうとするんだけど効かない。
で、emacsキーバインドのC-/を使ったり、
何回も押していると突如ある程度の所までは戻ったり。
なんか混乱が起きる。でも、ちょっと調べた方が良さげだな、この辺り。

biofermin2biofermin2

今ネット見たらやっぱevilのredo not workingって記事がポツポツ上がってるな。

biofermin2biofermin2

u ;; evil-undo
C-r ;; evil-redo
C-/ ;; undo-tree-undo
C-? ;; undo-tree-redo
こんな感じになってるのか?
とりあえず、一番下のやつだけ知らなかったし、
C-/がtree-undoだという事も知らなかった。
普通にemacsキーバインドのundoだと思ってた。
要はshift押すか押さないかの違いだな。

biofermin2biofermin2

自分がevil-lispyから欲しいのは
( jump to the previous parenthesis and enter evil-lispy-mode
) same, but jump to the next parenthesis instead
[ and ] Jump out of the current sexp (to the left / right), enter lispy-mode
これだけだな笑
C-SPC select current symbol/expression and enter evil-lispy-mode
これはわざわざ設定しなくてもそうなっているし、
<i insert at the start, inside, like so: ($ foo)
'>A insert at the end, inside, like so: (foo $)
ここらも上の(aと)iで対応出来るから不要。

括弧のジャンプは%がvi系だと対応する括弧に飛んだり戻ったり出来たと思うけど、
対応というよりも実際使う場面では次の括弧に飛んでほしいとかの方があったら
いいと思うわけで、それについてはviだとf(した後;でジャンプすればいい訳だけど、
上の方が直感的かなと。
あとS式のジャンプなら自分はC-M-fを使っている。
C-M-bはなぜかvirtual keyboadというものにbindingされているので、
C-S-bに設定している。(後日virtual keyboardの設定をいじって解決済)
でもこの辺りも上のやり方の方がスマートにかつ直感的に操作出来る気がする。

biofermin2biofermin2

lispyの()本来思ってたのと違う挙動かも?
自分で作るか?笑

biofermin2biofermin2

とりあえず、[]はつなげるだけだから簡単に設定作れた。
()の方がちょっとelisp調べないとだめだな。

biofermin2biofermin2

とりあえず、viの場合、f(だとかf)で常に括弧の向きを気にしないと
いけないから、括弧の開閉関係なく右側にある括弧にジャンプが)
左側にある括弧にジャンプが(ってな感じのものを作れれば良さげだな。

biofermin2biofermin2

evilに似てるものは発見。
(define-key evil-motion-state-map "[(" 'evil-previous-open-paren)
(define-key evil-motion-state-map "])" 'evil-next-close-paren)

biofermin2biofermin2

C-M-a,C-M-e 意外とこれ知らなかった。毎回C-M-f,M-Bを使ってた。
C-M-eはS式の括弧の末端ではなく、一行下の先頭にカーソルが移るけど、
評価は出来るな。
自分の設定を見直したら
C-M-a runs the command beginning-of-defun (found in global-map)
C-M-e runs the command end-of-defun (found in global-map)
C-M-f runs the command paredit-forward (found in paredit-mode-map)
M-B runs the command backward-sexp (found in global-map)
paredit-backward is an interactive Lisp function in 'paredit.el'.
It is bound to C-M-b.
となっていた。pareditのチートシートみて覚えてたから抜けてたようだ。
しかし、M-Bにはbackward-sexpが設定してあるので、これはparedit-backwardに切り替えといた方が良さげ。

biofermin2biofermin2

[と]にそれぞれparedit-backwardとparedit-forwardをバインドしたけど、
動作が綺麗じゃない。要は途中で止まる。
それを考えたら,標準のC-M-a,C-M-eの方が気持ちいい動きをするので、
バインド先を後者に切り替える事にする。
()に関してはre-search-forward,re-search-backwardを使って
関数を作ってみたが動きがうまく行きそうで、行かなくて調べたら
やはり同様の事が書かれていた。
https://emacs.stackexchange.com/questions/58840/how-to-search-with-a-regexp-before-point
という事で、looking-atとlooking-backを使って試して見てるが、
やり方がまずいのかまだうまく行ってない。

biofermin2biofermin2

とりあえず後退しながら括弧の位置を探す関数はうまくいった。
前進しながらの関数は途中まで行くがend-of-lineで止まってしまい、
bufferの最後まで進まない。

biofermin2biofermin2

よし、完成としよう。
とりあえず、
()[]のevilキーバインド出来た。

biofermin2biofermin2

emacsのview-mode最高!みたいなツイートを見かけたから
どれどれとview-mode見てみたが、w3mに似た感じか。
要はvi系。
ただSpaceで次ページに移ったり、
Shift+Spaceで前ページに戻ったりは確かに便利なので、
evilのnormalステートのキーバインドに早速追加した。
という事で個人的にはview-modeは要らなくなった笑
w3mもそうだけど、スペースキーでページ移動は気持ちいい。

biofermin2biofermin2

あれからまた自分のカスタマイズしたキーバインドを使ってみているが、
正直()の出番は少ないというか忘れている事が多い笑

[]に関しては戻る方は特に問題ないけど、
やはり]で進む方が問題がある。
特に自分の場合、lispコードで
((())))| ;=> result
みたいに|の位置にカーソルがある時に評価している。
]を使うと
((()))
| ;=>result
というようになっている。現状。
評価出来なくはないが、
例えば
((()))
((()))
というように2段繋がっている場合に
具合がよろしくない。要は次のS式を評価してしまう。
連続移動が出来ないという問題はあるが、それは
+キーで回避も出来るからやはりparedit-forwardだったかに
直した方がいい気がしてきた。そうじゃないと実践に活かせない気がする。
という事でそこだけ戻す。
要はC-M-eではなくC-M-fにバインドされている関数を
]にもバインドし直す。

biofermin2biofermin2

evil-previous-open-paren
evil-next-close-paren
というコマンドも見つけたけど、
使ってみるとちょっと挙動がしっくりこない。

biofermin2biofermin2

]の挙動は独自関数にバインドしてあげたらうまく行くようになったが、
S式内部からだと次のS式の末端まで飛んでしまうバグと
バッファの末端でエラーが出る。

バッファ末端はとりあえずいいとして、
S式内と外で挙動が違うのは困る。
S式内部にカーソルが位置しているという事は
どうやったらわかるんだろう?

また)の挙動もイマイチなんだよな。
まだしっくりしない。

(と[はうまく行っているけど。

あと、次のS式の頭にカーソル移動させる
関数も作った。

C-M-aだとカーソル前のS式の先頭に移動
させるので、似ているけど、移動方向が逆。

しかし、この関数をバインドするキーをどれにしたらいいのか思いつかない。。。
[はC-M-aと同じだったはずだし。
うーん、せっかくC-M-aというバインドがあるんだから
同じものをバインドするんじゃなくて
[に新しい関数をバインドすればいいけど、
そうなると[も]も下方向に移動という話になり、
実際使い勝手としてはどうなんだろう?

とりあえず、試してみるか。

biofermin2biofermin2

うーん、既に試した後があった笑
コメントにしてあった。
まぁ、うーん、悪くはないけど、
()の関係と[]の関係が若干違うのが、どうかなと思う気もする。
なんていうんか美しくないというか。
自分はいいかもしれないけど、
他者がこれを使うと違和感がありそうな気もする。
次のS式の先頭に行くのは
}にバインドするか?

biofermin2biofermin2

やってみたら evil-forward-paragraph というコマンドが既にバインドされていたが、
これはC-M-eに非常に似ているが、若干違う。
あと{の方もC-M-aに似ているけど、ちょっと挙動が違った。
S式の前の空白行の先頭まで移動している。
わかりやすく書くと*に最初カーソルがあったとして

|<-ここに移動 ; {
((((*S式))))
|<-ここに移動 ; }
(|(((S式))))
↑ 自分の作った関数はここに移動

S式の前後の空白に更に何かを書こうとする場合に便利な気もする。
悩ましい。頭がぐちゃぐちゃになってきた。

biofermin2biofermin2

なんか括弧の細かい挙動1つ1つ考えると多岐にわたってややこしいんだけど、
結局自分が何をしたいか?どういうキーバインドがあれば、
便利にコードを書く事が出来るか?
フォーカスするのはそこだな。
正直言うと、()についてはあまり便利とは感じてない笑
すごく面白いけどね。
既存のもので言うとC-M-uとかC-M-dあたりがそれに近いんだけど、
あれは前も書いた通り、括弧の方向をいちいち気にしないといけないから
それに比べればマシって話でしかない。
こういった話はgifアニメにでもした方がわかりやすいかもなぁ。

S式でよく使うキーバインドというものを1度ピックアップしたらいいかもしれない。

パッと思いつくのがC-M-kだなぁ。これは便利に使ってる。
これと似てる削除系C-kも一気に括弧内を消したい時には便利。
ポンポンって飛ぶ時にやっぱり[]あたりはありがたいな。
前はよくC-M-fは使ってた。S式を評価する際に便利だし。
これとマクロ合わせればそんなにこのキーバインドも苦にならないというか。
あとはparedit関連のC-→,C-←
M-sもよく使う。あとはよく分かってないけどC-uとかC-qとかたまに括弧が崩れた時に。
あまり崩れないからいまいちどっちがどっちかあやふやに使ってたりするけど。

これくらいで結構満足してるんだよね。S式に関しては。

ただEvilに関しては良くも悪くも括弧のバランスは無視したりする。
Pareditは逆に結構ガチガチにバランス保とうとしてくれるので、
時折柔軟性に欠くなと感じる時もあるけど、
Evilの方がその点気楽。その代わりバランスが崩れる事もあるので、
個人的にはrainbow-delimitersは入れといた方がいいかなと思う。

biofermin2biofermin2

一番最初に書いたgh,gj,gk,glでのタブの操作
だが、よくよく考えたらgjとgkは改行なしの
1行での上下移動に使う可能性がある。
たまに使う。
調べるとevil-map.el内で
(define-key evil-motion-state-map "gj" 'evil-next-visual-line)
(define-key evil-motion-state-map "gk" 'evil-previous-visual-line)
というように設定されているが、motion-stateという事で
自分はnormal-stateに設定しているから微妙に被ってない。
ただ、viのキーバインドを完全に無視している。
タブの新規作成をcreateでgc
タブの削除をdeleteでgdなんて代替案も考えたが、
そこまでprefixをgに拘るべきか?とも思う。
そうなるとtc,tk,tn,tpの方が従来の操作に似ているし、
screenなどとの操作とも似ているからそこに落ち着くべき
なのかと最近考えている。