この記事はEmacs Advent Calendar 2019 21日目の記事です。
20日目@tachiyamaさんの記事
最強エディタEmacsならマトリックス(っぽいこと)もできる
から引き継いで記事を書いていきます。
始めに
Emacsは万を超える組み込みコマンドと、多くのパッケージを利用することができるエディタです。我々Emacsユーザは、他のプログラマに馬鹿にされても、自分のEmacsを自分好みに改良し続けて行かなければならないのです。
冗談はここまでにしておいて(冗談じゃない?)
皆さんは超有能Emacs LispパッケージEXWM(Emacs X Window Manager) を知っているでしょうか。このパッケージはEmacsをタイル型ウィンドウマネージャとして動作されるためのパッケージです。
個人の意見ですが、EXWMには大きく2つのメリットがあると思っています。
まず、X Windowアプリケーションをバッファとして扱える機能。そして、ワークスペースの概念です。
EXWMのメリット
X WindowアプリケーションがEmacsのバッファとして扱えるため、C-x b
でバッファリスト表示・切り替え・キルなどが瞬時にできます。EXWMはEmacsを使うメリットの一つである、統一されたインターフェイスでの操作を体現するパッケージなのです。
次にワークスペースの概念ですが、s-n
(nには0 - 9の数字)のキーバインドでワークスペースを切り替えることができます。これが非常に便利で、ワークスペース1はプログラム用、ワークスペース2はシェル、ワークスペース3はウェブブラウザというように、脳内で整理ができて、スッキリします。
EXWMの問題点
もちろん、メリットばかりではなく、不便なところもあります。
そもそも、EXWMタイル型ウィンドウマネージャなので、フローティングウィンドウの扱いに関しては、普通に使いにくいです。(フローティングしたい場面はあまりない?)
加えて、EXWMはまだ不安定な部分があります。環境によっては起動することができなかったり、キー入力が行えない場合があります。
筆者は1年程、Debian testing上で利用していましたが、ある日突然キー入力を受け付けなくなってしまいました。
暗いGNOME生活
後で直そうと思いながらGNOMEに避難していたんですが、いつの間にか半年以上もGNOMEを使っていました。ですが常に、GNOME上のEmacs使いづらいなあと思っていました。特に、バッファリストが1つのみなので、大量にバッファを開く私にとっては、その切り替えが面倒で仕方がありませんでした。また、ウィンドウが一つなので、大量のバッファを一度に開くこともできず、本当に使いづらいなあと。(Vから始まるエディタよりかは、はるかに便利ですが)
perspective.elを使い始めた
そこで、バッファをグループ化し、その切り替えができるperspective.elを使い始めました。これは、ワークスペースを作り出すことができ、自由に名前をつけることができます。そして、その行き来もC-c p s <名前>
で切り替えることができます。しかし、このパッケージはあまり定着しませんでした。まず、キーバインドが長いですし、名前を打つのも面倒で、結局一つのワークスペースでことを済ましてしまいがちでした。そこで、次のように考えました。
切り替えのキーバインドが単純で、名前を打つ必要がなく、perspective.elのワークスペースを移動できないものか。
そこで、ひらめいたわけです。EXWMのようにs-n
で簡単にperspective.elのワークスペースを切り替えることができるようにすれば良いやと。
ようやく本題
まず、やりたいことを決めます。
s-n
(nには0 - 9の数字)でperspective.elのワークスペースを切り替えられるようにしたい。
方法
これを実現するための方法を適当に考えます。
- 起動時にperspective.elのワークスペースを0 - 9の名前で作る。
-
s-n
のキーバインドを(persp-switch (int-to-string n))
に割り当てる。
こんな感じでできそうです。簡単です。ちなみに、筆者のEmacs Lispスキルは、読めば分かる程度で、ほとんど書けません。なので、今回書いてみたコードはEmacs Lisp流の書き方には程遠いかと思われます・・・。アドバイスがあれば是非コメント欄にお願いします。
書いてみる
完成状態
一応、考えた通りの動作をするEmacs Lispのコードを最初に書いておきます。いいから早く見せろっていう人は、このコードをinit.elに書いてみてください。ちなみに、キーバインドはs-n
ではなく、M-n
で登録しているので、注意してください。
※追記※
先日リリースされた、Emacs 27.0.91からclライブラリが標準で廃止になり、lexical-letが使えなくなってしまったので、
それに対応するように以下のコードを変更しました。
(require 'perspective)
(persp-mode 1)
;; ワークスペース生成
(mapc (lambda (i)
(persp-switch (int-to-string i)))
(number-sequence 0 9))
;;;; キーに登録する関数を返す関数
;;(defun local-switch-workspace (i)
;; (lexical-let ((index i))
;; (lambda ()
;; (interactive)
;; (persp-switch (int-to-string index)))))
;;
;; 更新 2020/04/28
;;
(defun local-switch-workspace (index)
`(lambda ()
(interactive)
(persp-switch (int-to-string ,index))))
;; キーバインドの登録を行う
(mapc (lambda (i)
(global-set-key (kbd (format "M-%d" i)) (local-switch-workspace i)));;
(number-sequence 0 9))
; 最初のワークスペースは"1"に設定
(persp-switch "1")
ワークスペースの作成
起動時にワークスペースを生成したいので、ワークスペースを作成する処理をinit.elに直接書きます。
;; ワークスペース生成
(mapc (lambda (i)
(persp-switch (int-to-string i)))
(number-sequence 0 9))
mapcにワークスペースを生成する関数をラムダ式と、number-sequenceで生成した0 ~ 9のリストを渡して、ワークスペースの生成を行っています。mapcは、第2引数のリストから値を一つづつ取り出し、第1引数に渡された関数に渡して実行するという関数です。この関数は非常に便利で結構多用しています。
;; キーに登録する関数を返す関数
;;(defun local-switch-workspace (i)
;; (lexical-let ((index i))
;; (lambda ()
;; (interactive)
;; (persp-switch (int-to-string index)))))
;;
;; 更新 2020/04/28
;;
(defun local-switch-workspace (index)
`(lambda ()
(interactive)
(persp-switch (int-to-string ,index))))
この関数はワークスペースを切り替える関数を返します。この関数の返り値はglobal-set-key
に渡されることを前提にしています。例えば、この関数に1を渡して実行すると、(persp-switch "1")
を実行する関数が帰ってくることになります。その関数をglobal-set-key
に渡すわけです。
最初は、global-set-key
にlambda式を直接渡そうとしていたんですが、どうもうまく行かなくて、このような形に落ち着きました。Google先生もとい、歴戦のEmacs Lispプログラマたちの知恵の結晶を拝見した結果、lexical-let
を使う関数になりました。このlexical-letはクロージャを作るために必要になっていますが、この機能はEmacs 24.1以降で利用可能なため、それ以前のEmacsを利用している方は注意が必要です。まあ、2019年の今、Emacs 24.1より前のバージョンを使っている人は珍しいでしょうが。(筆者はEmacs 26.3を使っています)
;; キーバインドの登録を行う
(mapc (lambda (i)
(global-set-key (kbd (format "M-%d" i)) (local-switch-workspace i)))
(number-sequence 0 9))
ここで、実際にM-n
(nは0 ~ 9の数字)にキーバインドを登録しています。内容は簡単で、mapcを使って0 ~ 9のキーにlocal-switch-workspace
の返り値の関数を登録しています。
あれ、おかしいですね
s-n
に登録するはずがM-n
に登録しています。すみません。Super-Number
のキーバインドはGNOMEに登録されていて、Emacsに渡すことができませんでした。なので、Altキー(Metaキー)で妥協しました。
; 最初のワークスペースは"1"に設定
(persp-switch "1")
最後にデフォルトのワークスペースを1にして終わりです。
使ってみて
大体1週間ほど使っていますが、めちゃくちゃ捗ってます。EXWMのワークスペース切り替えに関してはほぼ完全に再現できていると思います。また、裏ではperspective.elが動いているので、perspective.elが提供する機能をそのまま利用できるという利点もあります。
あと、ちょうどアドベントカレンダーのネタにできてよかったなと。
構成の紹介
最後に筆者に定着した使い方を紹介したいと思います。
WS1 ~ 4 | WS5 ~ 7 | WS8 ~ 9 |
---|---|---|
プログラム + シェル | 文章や簡単なスクリプト | 空き |
おわりに
ワークスペース切り替えをもっとスムーズに行いたい人は是非、init.elに書いてみてください。もしかしたらハマるかもしれませんよ(`ェ´)ピャー