Vim の jj を Mac の日本語入力中でも使えるようにする

## TL;DR

[Vim][vim] や GUI エディタの Vim 拡張機能 (e.g. [vscodevim][vscodevim]) を普段使うとき、 `jj` を `esc` にマップして insert mode を抜けるのに使っているのですが、日本語入力中に `jj` (つまり `っj`) を入力したときも機能するよう、 [Karabiner-Elements][karabiner-elements] の [complex modifications][complex-modifications] を設定する方法をまとめました。

## Requirements

これを実現するためには日本語入力で `っj` が入力されたら、

1. 現在までに入力中の日本語文章の変換を確定させ、
1. 入力を日本語から半角英数に変更し、
1. Vim insert mode から何らかの方法で抜ける

ことが必要になります。1. と 2. は `return` および `英数` キーを入力するよう設定すれば OK です。一方、3. については以下の2つの方針が考えられます。

1. `っj` → `esc` にマップする (`jj` の入力はなかったことにする)
1. `っj` → `jj` にマップする (`jj` → `esc` のマッピングは Vim や Vim 拡張機能の設定に任せる)

Karabiner-Elements を使うと macOS のグローバルなキーボード設定が変えることができるので (もちろん特定のアプリケーションに限定した設定も可能ですが)、ターミナルの Vim だろうが VS Code だろうが `jj` が使えるようになるのが大きな魅力です。そこで、様々な場面で意図しない `esc` が実行されないように、ここでは後者を選択することにします。

## Installation

ここでは @s-show さんの [Karabiner-Elementsを使ってキーの２連打に処理を割り当てる方法][qiita-article-1] を参考に、以下の `vim-in-japanese.json` を作成しました ([Gist][gist] にも置いておきました)。これを、 `~/.config/karabiner/assets/complex_modifications` に置いておけば、 Karabiner-Elements Preferences → Complex Modifications → Add rule から選択できるようになるはずです。

<img width="1000" alt="add-rule.png" src="https://qiita-image-store.s3.amazonaws.com/0/44000/708a5214-1d8f-72c6-1059-20f3d4809edd.png">

仕組みとしては、

1. 1つ目の `j` が入力されたら、変数 `j_pressed` を `1` にする。もし一定時間何も入力されなかった場合 (`to_if_invoked`) や `j` 以外の文字が入力された場合 (`to_if_canceled`) は、`j_pressed` を `0` に戻す。
1. `j_pressed` が `1` の状態で立て続けに2つ目の `j` が入力されたら`return` + `英数` + `j`に置換し、`j_pressed` を `0` に戻す。

となっています。あとは Vim の場合は `.vimrc` で、

```vimscript:.vimrc
inoremap <silent> jj <ESC>
```

としておけば OK です。上で前者の方針を選択した場合も、これを参考に簡単に設定できると思います。macOS 限定のハックですが、 Vim-like な環境で快適に日本語入力ができるようになるので便利ですね！

## Rule file

<script src="https://gist.github.com/astropenguin/f452cdecd94696b9ab0c00a9ba890d88.js"></script>

```json:vim-in-japanese.json
{
    "title": "For Vim in Japanese",
    "rules": [
        {
            "description": "日本語入力の っj を jj にマッピングする",
            "manipulators": [
                {
                    "type": "basic",
                    "from": {
                        "key_code": "j"
                    },
                    "to": [
                        {
                            "key_code": "return_or_enter"
                        },
                        {
                            "key_code": "japanese_eisuu"
                        },
                        {
                            "key_code": "j"
                        }
                    ],
                    "conditions": [
                        {
                            "type": "variable_if",
                            "name": "j_pressed",
                            "value": 1
                        },
                        {
                            "type": "input_source_if",
                            "input_sources": [
                                {
                                    "language": "^ja$"
                                }
                            ]
                        }
                    ]
                },
                {
                    "type": "basic",
                    "from": {
                        "key_code": "j"
                    },
                    "to": [
                        {
                            "set_variable": {
                                "name": "j_pressed",
                                "value": 1
                            }
                        },
                        {
                            "key_code": "j"
                        }
                    ],
                    "to_delayed_action": {
                        "to_if_invoked": [
                            {
                                "set_variable": {
                                    "name": "j_pressed",
                                    "value": 0
                                }
                            }
                        ],
                        "to_if_canceled": [
                            {
                                "set_variable": {
                                    "name": "j_pressed",
                                    "value": 0
                                }
                            }
                        ]
                    }
                }
            ]
        }
    ]
}
```

## References

+ [Manual - Karabiner - Software for macOS][complex-modifications]
+ [Karabiner-Elementsを使ってキーの２連打に処理を割り当てる方法 - Qiita][qiita-article-1]
+ [Karabiner-Elementsの設定項目をまとめました - Qiita][qiita-article-2]
+ [astropenguin/vim-in-japanese.json][gist]

<!-- markdown links -->

[vim]: https://www.vim.org
[vscodevim]: http://aka.ms/vscodevim
[vscode]: https://code.visualstudio.com
[karabiner-elements]: https://pqrs.org/osx/karabiner
[complex-modifications]: https://pqrs.org/osx/karabiner/document.html#configuration-complex-modifications
[qiita-article-1]: https://qiita.com/s-show/items/c991327a5317c3e0cf4b
[qiita-article-2]: https://qiita.com/s-show/items/a1fd228b04801477729c
[gist]: https://gist.github.com/astropenguin/f452cdecd94696b9ab0c00a9ba890d88