Emacsのperspective.elワークスペースをもっと使いやすく

この記事はEmacs Advent Calendar 2019 21日目の記事です。

20日目@tachiyamaさんの記事
[最強エディタEmacsならマトリックス(っぽいこと)もできる](https://qiita.com/tachiyama/items/24093c5ab1d537d97858)
から引き継いで記事を書いていきます。

# 始めに

Emacsは万を超える組み込みコマンドと、多くのパッケージを利用することができるエディタです。我々Emacsユーザは、他のプログラマに馬鹿にされても、自分のEmacsを自分好みに改良し続けて行かなければならないのです。

冗談はここまでにしておいて（冗談じゃない？）

皆さんは超有能Emacs Lispパッケージ[EXWM（Emacs X Window Manager）](https://github.com/ch11ng/exwm) を知っているでしょうか。このパッケージはEmacsをタイル型ウィンドウマネージャとして動作されるためのパッケージです。

個人の意見ですが、EXWMには大きく２つのメリットがあると思っています。
まず、**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](https://github.com/nex3/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が使えなくなってしまったので、
それに対応するように以下のコードを変更しました。***

```emacs-lisp
(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に直接書きます。

```emacs-lisp
;; ワークスペース生成
(mapc (lambda (i)
        (persp-switch (int-to-string i)))
      (number-sequence 0 9))
```

mapcにワークスペースを生成する関数をラムダ式と、number-sequenceで生成した0 \~ 9のリストを渡して、ワークスペースの生成を行っています。mapcは、第2引数のリストから値を一つづつ取り出し、第1引数に渡された関数に渡して実行するという関数です。この関数は非常に便利で結構多用しています。

```emacs-lisp
;; キーに登録する関数を返す関数
;;(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を使っています）

```emacs-lisp
;; キーバインドの登録を行う
(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キー）で妥協しました。

```emacs-lisp
; 最初のワークスペースは"1"に設定
(persp-switch "1")
```

最後にデフォルトのワークスペースを1にして終わりです。

###

## 使ってみて

大体1週間ほど使っていますが、めちゃくちゃ捗ってます。EXWMのワークスペース切り替えに関してはほぼ完全に再現できていると思います。また、裏ではperspective.elが動いているので、perspective.elが提供する機能をそのまま利用できるという利点もあります。
あと、ちょうどアドベントカレンダーのネタにできてよかったなと。

### 構成の紹介

最後に筆者に定着した使い方を紹介したいと思います。

| WS1 ~ 4 | WS5 ~ 7 | WS8 ~ 9 |
|:----------:|:-----------:|:------------:|
| プログラム + シェル| 文章や簡単なスクリプト | 空き |

# おわりに

ワークスペース切り替えをもっとスムーズに行いたい人は是非、init.elに書いてみてください。もしかしたらハマるかもしれませんよ（｀ェ´）ﾋﾟｬｰ