Karabiner ElementsでEmacs Mode（mark setとかC-x C-s）風なルールを作ってみた


# 追記 (2021/06/21）
現在では Karabiner Elements の公式で Emacs Mode が用意されているのでこの記事の存在意義は薄くなっています。ただ公式のルールよりも少し設定が多い（昔の Karabiner 風の作りになっている）ので、昔からの設定に馴染んでいる方はお試し頂いても良いかもしれません。

# 概要

旧KarabinerにあったEmacs Mode（mark setやC-xとか）風のものをKarabiner Elementsで再現するルールを作成してみました。自分で使う分には困らない程度には動くものが作成できたので公開してみます。

Elementsの標準ではEmacsの単純なキーマッピングしかなく、Emacs Modeは実現できないと勝手に思い込んでいたのですが、ルール中に変数が使えるという記事を見て、それなら実現できるんじゃないの？ と思って作ってみたものです。

# 使い方
とりあえず試してみたい方は、ここに記載された手順で今回作成したルールを導入することができます。

最初に下記のURLをブラウザで開きます。Karabiner Elements の Import 画面が表示されるので、そのままルールを Import します。（ここで Import する JSON に作成した Emacs Mode についてのルールが記載されています）

```
karabiner://karabiner/assets/complex_modifications/import?url=https%3A%2F%2Fraw.githubusercontent.com%2Fmadogiwa%2FKE-complex_modifications%2Fmaster%2Fpublic%2Fjson%2Femacs_mode.json
```

インポートに成功すると下記のような画面が表示されます。この画面では、インポートしたルールから、有効にしたいルールを選択することができます。後で無効にできるので、ここではとりあえずEnable Allで全て有効にします。

<img width="640" alt="enable_all.png" src="https://qiita-image-store.s3.amazonaws.com/0/3740/2c8025a8-e5d8-8806-5f8a-a7c0b6a3a9c2.png">

下記のような画面が表示されれば設定完了です。不要なルールがある場合は、該当するルールのRemoveボタンを押すことで削除が可能です。また一度削除（無効化）した場合でも、左下にあるAdd ruleボタンを押すと、一覧が出てきますので、そこから再度追加することも可能です。

<img width="640" alt="karabiner_preferences.png" src="https://qiita-image-store.s3.amazonaws.com/0/3740/e8c9139f-ec63-6884-ce2c-b78bb554cc73.png">


# 設定ファイルの説明など
今回作成したルールは、下記リポジトリのsrc/json/emacs_mode.json.erbに元ソースがあります。

https://github.com/madogiwa/KE-complex_modifications

またKarabiner Elementsのルールを作成する方法については、下記のページを参考にさせて頂きました。

https://rcmdnk.com/blog/2017/08/30/computer-mac-karabiner/

設定ファイルの意味などはこちらのページで詳細に説明されていますので、今回のルールに関係した部分のみをメモ的に記載しておきます。

## C-x C-s
Karabiner Elementsの各ルールは、基本的にfromに入力キー、toに出力キーという形でマッピングを記述するのですが、toでset_variableを指定すると変数に値を入れることができます。

下記はC-x部分のルールで、Control-Xが押された状態であることを記録するため emacs_mode_cx という変数に1を入れています。

            "description": "[Emacs Mode] Enable Control+X as prefix key",
            "manipulators": [

                {
                    "type": "basic",
                    "from": <%= from("x", ["control"], ["caps_lock"]) %>,
                    "to": [
                        { "set_variable": { "name": "emacs_mode_cx", "value": 1 } }
                    ],

C-sなどの各コマンドに対応する記述は下記のようになります。conditionsには前提条件を設定でき、先ほど設定したemacs_mode_cxが1（C-xが押された後）である場合のみキーの置き換えを行うようにしています。また実行した後は、to_after_key_up（たぶんキーを離した時に実行される）で変数を0にすることで、C-xを押す前の状態に戻しています。

```erb:src/json/emacs_mode.json.erb
        {
            "description": "[Emacs Mode|C-x] C-s to Save (Command+S)",
            "manipulators": [
                {
                    "type": "basic",
                    "from": <%= from("s", ["control"], ["caps_lock"]) %>,
                    "to": <%= to([["s", ["left_command"]]]) %>,
                    "to_after_key_up": [
                        { "set_variable": { "name": "emacs_mode_cx", "value": 0 } }
                    ],
                    "conditions": [
                        <%= frontmost_application_unless("emacs_mode_key_bindings_exception") %>,
                        { "type": "variable_if", "name": "emacs_mode_cx", "value": 1 }
                    ]
```

## mark set
mark setについてもC-xと同様に、emacs_mode_marksetという変数で状態を表しています。mark setについては、Control+Spaceを押すたびに、変数を0/1でトグルさせる必要があるため、下記のようにルールを２つ記載しています。

ちなみにconditionsにemacs_mode_cxも含めているのは、mark setとC-xを同時には使えないようにすることで、ルールを単純にするためです。

```erb:src/json/emacs_mode.json.erb
                {
                    "type": "basic",
                    "from": <%= from("spacebar", ["control"], ["caps_lock"]) %>,
                    "to": [
                        { "set_variable": { "name": "emacs_mode_markset", "value": 1 } }
                    ],
                    "conditions": [
                        <%= frontmost_application_unless("emacs_mode_key_bindings_exception") %>,
                        { "type": "variable_if", "name": "emacs_mode_cx", "value": 0 },
                        { "type": "variable_if", "name": "emacs_mode_markset", "value": 0 }
                    ]
                },
                {
                    "type": "basic",
                    "from": <%= from("spacebar", ["control"], ["caps_lock"]) %>,
                    "to": [
                        { "set_variable": { "name": "emacs_mode_cx", "value": 0 } },
                        { "set_variable": { "name": "emacs_mode_markset", "value": 0 } }
                    ],
                    "conditions": [
                        <%= frontmost_application_unless("emacs_mode_key_bindings_exception") %>,
                        { "type": "variable_if", "name": "emacs_mode_markset", "value": 1 }
                    ]
                },
```

Control+Bなどのカーソル移動については、emacs_mode_markset変数が1の場合と0の場合、それぞれのルールを記載しています。1の場合は、下記のようにtoのModifierキーにShiftを追加しています。

```erb:src/json/emacs_mode.json.erb
                {
                    "type": "basic",
                    "from": <%= from("b", ["control"], ["caps_lock"]) %>,
                    "to": <%= to([["left_arrow", ["left_shift"]]]) %>,
                    "conditions": [
                        <%= frontmost_application_unless("emacs_mode_key_bindings_exception") %>,
                        { "type": "variable_if", "name": "emacs_mode_cx", "value": 0 },
                        { "type": "variable_if", "name": "emacs_mode_markset", "value": 1 }
                    ]
                },
```

またmark setした状態で、普通にカーソルキーを使って移動をする場合も考えられるため、1の場合は通常のカーソルキーなどについてもShiftキーを付与しています。下記は←を押した時にshiftを付け加えるものです。

```erb:src/json/emacs_mode.json.erb
                {
                    "type": "basic",
                    "from": <%= from("left_arrow", [], ["caps_lock"]) %>,
                    "to": <%= to([["left_arrow", ["left_shift"]]]) %>,
                    "conditions": [
                        <%= frontmost_application_unless("emacs_mode_key_bindings_exception") %>,
                        { "type": "variable_if", "name": "emacs_mode_markset", "value": 1 }
                    ]
                },
```

# 補足
今回作成したルールには、今のところ下記のような問題があります。

 * 他にカーソル移動を伴うルールがあるとmark setがうまく動かない
 * <del>C-xの後に定義していないキーを押した場合はC-xを押したままの状態になる</del> (2017/11/15 コメントで頂いた情報を元に問題を解消しました)

これらはルール設定で対応すると面倒そうなので、代わりに旧Karabinerにあった状態表示（Stickyな通知）ができれば良いのですが、今のところElementsのルールにはなさそうです。