Obsidianのvimモードで、ctrl+bfpnを使えるようにする

## 背景

話題のメモツール／ナレッジマネジメントツール[Obsidian][01]では、`Vimのキー設定`が選べます。インサートモードで`ctrl+bfpn`などemacsライク（macOS標準ライク）に動いてほしいのですけれども、そうなりません。

<img src="https://i.gyazo.com/b21cb9dc4439a10efda1e1535e948609.png" width=40%>

ちなみに、以前の[Obsidian][01]は「ノーマルモードはvimライク、インサートモードはemacs(macOS)ライクな挙動」でしたが、[v0.11.10(2021-03-27)][03]で変更になりました。[Vimrc Supportプラグイン][04]を用いても、うまく設定できません。

そこで、Karabiner-Elements（以下KE）を使って、「全体としてはVimキー設定で、インサートモードのカーソル移動はemacs(macOS)ライクに」なるようにしました。

※後日記※
Karabiner-Elementsアプリから、ダウンロードしてインストールできるように、登録しました。(2021-09-24)

### 環境

ここで書いていることは、下記のバージョンで実施しました。

- [Karabiner-Elements][02] 13.5.0
- [Obsidian][01] 0.12.12
- macOS BigSur 11.5.2


## 概要

```plantuml
!includeurl https://raw.githubusercontent.com/matthewjosephtaylor/plantuml-style/master/style.pu

top to bottom direction

rectangle "Obsidian Vimキー設定" as OV
rectangle "KEインストール"        as KE
rectangle "jsonファイル作成"      as MJ
rectangle "jsonファイル読込み"    as RJ

OV -> KE
KE -> MJ
MJ -> RJ

```

## 手順詳細

### Obsidian Vimキー設定

設定 > エディタ > Vimのキー設定をONにします。
<img src="https://i.gyazo.com/b21cb9dc4439a10efda1e1535e948609.png" width=50%>

### Karabiner-Element（KE）のインストール

[Karabiner-Elementsの公式ページ][02]からダウンロードしてインストールします。

### jsonファイル作成

下記のjsonファイルを`~/.config/karabiner/assets/complex_modifications`に保存します。ファイル名は適当で良いです（たとえば`obsidian-vim-emacs.json`）。

```json:obsidian-vim-emacs.json
{
  "title": "emacs like key for Obsidian.app",
  "rules": [
    {
      "description": "ctrl + bfpnae (only for Obsidian.app)",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "b",
            "modifiers": {
              "mandatory": [
                "left_control"
              ],
              "optional": [
                "any"
              ]
            }
          },
          "to": [
            {
              "key_code": "left_arrow"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_if",
              "bundle_identifiers": [
                "^md\\.obsidian$"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "f",
            "modifiers": {
              "mandatory": [
                "left_control"
              ],
              "optional": [
                "any"
              ]
            }
          },
          "to": [
            {
              "key_code": "right_arrow"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_if",
              "bundle_identifiers": [
                "^md\\.obsidian$"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "p",
            "modifiers": {
              "mandatory": [
                "left_control"
              ],
              "optional": [
                "any"
              ]
            }
          },
          "to": [
            {
              "key_code": "up_arrow"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_if",
              "bundle_identifiers": [
                "^md\\.obsidian$"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "n",
            "modifiers": {
              "mandatory": [
                "left_control"
              ],
              "optional": [
                "any"
              ]
            }
          },
          "to": [
            {
              "key_code": "down_arrow"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_if",
              "bundle_identifiers": [
                "^md\\.obsidian$"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "a",
            "modifiers": {
              "mandatory": [
                "left_control"
              ],
              "optional": [
                "any"
              ]
            }
          },
          "to": [
            {
              "key_code": "home"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_if",
              "bundle_identifiers": [
                "^md\\.obsidian$"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "e",
            "modifiers": {
              "mandatory": [
                "left_control"
              ],
              "optional": [
                "any"
              ]
            }
          },
          "to": [
            {
              "key_code": "end"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_if",
              "bundle_identifiers": [
                "^md\\.obsidian$"
              ]
            }
          ]
        }
      ]
    },
    {
      "description": "ctrl + hdk (only for Obsidian.app)",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "h",
            "modifiers": {
              "mandatory": [
                "left_control"
              ],
              "optional": [
                "any"
              ]
            }
          },
          "to": [
            {
              "key_code": "delete_or_backspace"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_if",
              "bundle_identifiers": [
                "^md\\.obsidian$"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "d",
            "modifiers": {
              "mandatory": [
                "left_control"
              ],
              "optional": [
                "any"
              ]
            }
          },
          "to": [
            {
              "key_code": "delete_forward"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_if",
              "bundle_identifiers": [
                "^md\\.obsidian$"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "k",
            "modifiers": {
              "mandatory": [
                "left_control"
              ],
              "optional": [
                "any"
              ]
            }
          },
          "to": [
            {
              "key_code": "end",
              "modifiers": "left_shift"
            },
            {
              "key_code": "x",
              "modifiers": "left_command"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_if",
              "bundle_identifiers": [
                "^md\\.obsidian$"
              ]
            }
          ]
        }
      ]
    }
  ]
}
```

### jsonファイル読込み

Complex modificationsタブ > Add rule > emacs like key for Obsidian.app > Enable Allボタンを押します。

<img src="https://i.gyazo.com/b340c10f5f1f453458dff7e75db72d59.png" width=70%>

## おわりに


### 設定に関する補足

使えるようにしたキーは、bfpnae（移動）およびhdk（削除）です。

上記の設定は、Obsidianアプリ上だけで有効です。他のアプリでの挙動は変わりません。

bundle_identifiersは、`terminal`で取得することができます（[How to find the Bundle ID of an application on Mac](https://www.hexnode.com/mobile-device-management/help/how-to-find-the-bundle-id-of-an-application-on-mac/)を参考にしました）。

```bash
$ osascript -e 'id of app "Obsidian"'

md.obsidian
```

### 制限
`C-k`(kill-line)は、emacs(macOS)の動作がスマート（賢く）て、KEで再現するのはむずかしいです。上記jsonでは、`shift+end`したのちに`cmd+x(cut)`で代用しました。このため「行末で`C-k`すると行削除になってしまう」「行中で`C-k C-k`すると、行が削除され、クリップボードには行頭からカーソルまでのテキストが入っている」など、emacsの人の期待動作とは異なりますのでご注意ください。

`shift+end`したのちに`backspace_or_delete`という手もあるのですが、さらに期待動作と離れていきます（行末で`C-k`すると`backspase`になる）。

また、`shift+end`だと論理行末まで行ってしまうので、表示行末までにしたいかたは、`shift+command+右矢印`に変えたほうがいいかもしれません。

### ちなみに

Obsidianの、Vimモードのインサートモードでの挙動が、emacs(macOS）ライクでなくなった（v0.11.10(2021-03-27)以降）ことが、仕様なのかバグなのかいまいちわからないところがあるのですが、とりあえずの応急処置をしました。

`C-k`の挙動をKEで再現することはむずかしいので、せめてオプションで「インサートモードでemacs(macOS)ライクな挙動」を選べるようにならないか、開発者に問い合わせしてみようと思っています。

> 追記：公式フォーラムで聞いてみましたが、バグではないようです...

### 後日記

Karabiner-Elementsのアプリからダウンロードできるように、サイトに登録しました。

[Karabiner-Elements complex_modifications rules - Application Specific](https://ke-complex-modifications.pqrs.org/#application-specific)

にある`Obsidian.app, emacs like key at vim-insert mode_rev02`です。

### リソース

KEのルールのインポートについて、公式ドキュメントはこちら：
[Use more complex rules](https://karabiner-elements.pqrs.org/docs/manual/configuration/configure-complex-modifications/)

KEの設定jsonの書き方について、公式ドキュメントのサンプルはこちら：
[Typical complex_modifications examples
](https://karabiner-elements.pqrs.org/docs/json/typical-complex-modifications-examples/)

KEの設定jsonのコレクションはこちら：
[Karabiner-Elements complex_modifications rules
](https://ke-complex-modifications.pqrs.org)

KEの設定の具体的ステップはこの記事を参考にさせていただきました：
[USキーボードの左右Commandキーで仮想化Windowsの英数/かな入力を切り替える](https://qiita.com/yukiri/items/6560e572dbe5eae4042f)

[Obsidian｜べんりな使いかたとコツ（目次） - Qiita][05]

[01]: https://obsidian.md
[02]: https://karabiner-elements.pqrs.org
[03]: https://forum.obsidian.md/t/obsidian-release-v0-11-10/15351
[04]: https://github.com/esm7/obsidian-vimrc-support
[05]: https://qiita.com/hann-solo/items/ac8fa75394813e8adcf4