どうしてもemacsのキーバインドが使いたい（特にCtrl-Wが使いたい）！

#1. どうしてもemacsのキーバインドが使いたい！
元emacs派としては、キーバインドは何としてもemacsのものを使いたいという気持ちがとても強いです。
ホームポジションから手を離さないでよいという効果は生産性向上のためにとても重要であり、個人的には、以下のような頻出するキーバインドの利用は必須であると思っています。

- カーソルの移動等、頻出であるCtrl-F, B, N, P, A, E, D, Hあたりは必須。
- Ctrl-S, R, K, W, G, Alt-W (Ctrl-[ W), Ctrl-X Uあたりは、まあ最悪妥協はできるができたら対応しておきたい。

最近はいろいろなアプリが出てきており、残念ながら、emacsのキーバインドは世間のデファクトスタンダードにはなっていないように思いますので、何かしら対応を考える必要があります。対応案としては、

1. emacsを使い続ける
2. 使いたいアプリのキーバインドをできる限りemacsに合わせてカスタマイズする

のどちらかだと思います。1で対応している方もたまにお見かけしますが、さすがに今のご時世便利なアプリがたくさん出ているのでemacsだけで全ての作業を対応し続けるのは無理があるように思います。（強力なemacs派の皆様から反論があるかもしれませんが。）

ということで、個人的な備忘もかねて、私なりの対応策を投稿しておきたいと思います。

**<Font color=red>特に、後述の理由から、JupyterLab（※）でCtrl-Wは使うことができず、これまで我慢していましたが、ちょっと無理やり工夫をして使えるようにしたので、同様のことをやりたいと思っている方がいるかもしれないと思い投稿をしてみることにしました。**</Font>具体的には、Ctrl-WがChromeに渡るときは一旦Ctrl-Lにして渡すようにし、ChromeにブロックされずにJupyterLabに渡るようにするというのがアイデアの概要になります。

（※）ブラウザ上で動くアプリ全般同様ですが。

#2. emacsのキーバインドを使えるようにする方法
個人的には、これまで、以下のような対策を行っています。
1. OS全体のキーバインドを[AutoHotKey](https://www.autohotkey.com/)を使ってカスタマイズする
2. ただし、1の対応では、かゆいところに手が届かない場合もあるため、アプリ独自にキーバインドのカスタマイズができるもの（例えばVS CodeやJupyter等）については、アプリ独自の機能を用いてカスタマイズを行う。

つまり、2段階でカスタマイズを行うことで、より忠実に再現できるアプリは忠実に再現を行う。それ以外のアプリも、AutoHotKeyを使って、まあまあ使えるレベルまでカスタマイズを行うイメージです。

ちなみに、この記事はWindowsを前提にしていますが、他のOSでも同じようなことはできると思います。

#3. AutoHotKeyの設定

ということで、まずはAutoHotKeyにより、OS全体のキーバインドを変更します。（実際にはOSの設定を変えているわけではないと思いますが。）

- AutoHotKeyをインストールし、[こちら](https://gist.github.com/mxl00474/5289c8ed9e05ba6b535307ee8207042d)のahkファイルを起動すると、（ほぼ）emacsのキーバインドが設定されます。
- （お恥ずかしながら忘れてしまったのですが）、もともとはどこかから拾ってきたものを自分で少し修正したものです。
- 特に、以下の箇所を修正することで、開いているWindowやアプリ毎に、各キーの設定変更を無効にできたりできるので便利です。

```
; Applications you want to disable emacs-like keybindings
; (Please comment out applications you don't use)
is_target()
{
  IfWinActive,ahk_class ConsoleWindowClass ; Cygwin
    Return 1 
  IfWinActive,ahk_class MEADOW ; Meadow
    Return 1 
  IfWinActive,ahk_class cygwin/x X rl-xterm-XTerm-0
    Return 1
  IfWinActive,ahk_class MozillaUIWindowClass ; keysnail on Firefox
    Return 1
  ; Avoid VMwareUnity with AutoHotkey
  IfWinActive,ahk_class VMwareUnityHostWndClass
    Return 1
  IfWinActive,ahk_class Vim ; GVIM
    Return 1
  IfWinActive,ahk_exe Code.exe ; VS Code
    Return 1
  IfWinActive,ahk_class CASCADIA_HOSTING_WINDOW_CLASS ; Windows Terminal
    Return 1
  IfWinActive,ahk_class CabinetWClass ; Explorer
    Return 1
  If is_jupyter() ; Jupyter tab on Chrome
    Return 1

; Debug
;  WinGetTitle, CurrentWindowTitle, ahk_class Chrome_WidgetWin_1
;  MsgBox, % CurrentWindowTitle

  Return 0
}
```
ClassやExe名は、AutoHotKeyに付属している、Window Spyというツールで調べることができます。
Window Spyは、AutoHotKeyを起動している状態で、画面右下のAutHotKeyのアイコンを右クリックすることで起動することができます。

![ahk1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50769/68c7dbb4-bc08-99ff-bc8c-d030c06b48e4.png)

Ahk_classやahk_exeのところにclass名やexe名が表示されるので、上の設定ファイルに設定することで、アプリを指定して設定を無効にすることができます。

![ahk2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50769/26be749f-832a-d316-6f4e-4c124d4e43cd.png)

実際には、同じファイルの下のほうに以下のような箇所があり、ここで先ほどの関数を呼び出して制御をしています。

```
^k::
  If is_target()
    Send %A_ThisHotkey%
  Else
    kill_line()
  Return
```

特に、私は、通常はChromeを使っているときは、AutoHotKeyによる設定を有効にしておきたいのですが、JupyterLabを使っているときだけはJupyterLabの設定のほうで制御したいので、以下のような設定を入れています。

```
is_jupyter()
{
  IfWinActive,ahk_exe chrome.exe ; chrome
  {
    WinGetTitle, CurrentWindowTitle, ahk_class Chrome_WidgetWin_1
    If (CurrentWindowTitle == "JupyterLab - Google Chrome") ; Only deactivate when Jupyter lab tab is open
      Return 1
  }
  Return 0
}
```

また、詳細は後述しますが、ChromeがCtrl-Wに勝手に（私からすると）変なショートカットキーを割り当ててしまっているため、Chrome上でJupyterLabを使っているときには、Ctrl-Wは、Chromeに渡るときには、あまり使わない他ののショートカット（私の場合はCtrl-L）に変更するようにしておきます。

```
^w::
  If is_jupyter()
  {
    ;MsgBox, % CurrentWindowTitle
    Send ^l
  }
  Else If is_target()
    Send %A_ThisHotkey%
  Else
    kill_region()
  Return
```

#4. Chromeのショートカットキーについて

- Chromeはいまいちで、一部のキーについては、あらかじめ勝手に変なショートカットキーが割り当てられてしまっており、しかも、変更することができません。。[こちら](https://superuser.com/questions/569248/disable-hardwired-chrome-hot-key-ctrlw#:~:text=On%20the%20your%20system%20on,It%20disables%20it.)等で、いろいろと対策を考えている方がいらっしゃいますので、とりあえず、提案されているとおり、[ctrlw](https://chrome.google.com/webstore/detail/ctrlw/goejokenmdamcapadhgghgpeeaeaaedc/)というChromeの拡張機能を導入し、変なショートカットキーは無効化します。
- ただ、上の方法により無効化すると、アプリのレイヤーまでCtrl-Wのシグナルが渡らなくなってしまうみたいで、例えば、後述のJupyterLabの設定を行ってもCtrl-Wは使えません。（なので、上記の通り、AutoHotKeyで、Ctrl-Wを無理やり一旦Ctrl-Lに変更することでシグナルをアプリのレイヤーまで飛ぶようにしています。）
- ちなみに、最近は、下の図のように、Chrome自身でもショートカットキーの設定を無効にできるみたいですが、上記と同様、シグナルはアプリのレイヤーまでは飛んでこないようでした。（以下はChromeのExtensionのショートカットキーの設定画面を開いたところです。）

![chrome1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50769/3ed36e1a-9b6d-53a1-d343-7aa937e124cd.png)

#5. JupyterのCell内のキーバインドをemacsにする方法
次に、JupyterLabの設定でemacs風のキーバインドを設定する方法についてご説明します。

##5.1. JupyterLabをemacs風キーバインドにする

まず、JupyterLabのキーバインドをemacs風にすること自体は、[jupyterlab-emacskeys](https://github.com/kpe/jupyterlab-emacskeys)というJupyterLabの拡張をインストールすることで簡単にできます。実際には、この拡張機能は簡単なことしかしておらず、

- 実は、JupyterLab自体が、emacs風のキーバインドをサポートしており、この拡張機能は、JupyterLabの設定を、emacs風のキーバインドを使うように変更しているだけ。
- JupyterLabの中では「[codemirror](https://codemirror.net/index.html)」というWeb上で動くEditorのライブラリを使っており、このライブラリが、emacs, vi, sublime textのキーバインドをサポートしているようです。
- ただ、codemirrorのマニュアルにも、Chromeにブロックされてしまうため、Ctrl-W等はちゃんと使えないよ・・・と書いてあります。（[こちら](https://codemirror.net/demo/emacs.html)）

##5.2. Ctrl-Wを無理やり使えるようにする

ということで考えた解決策として、

- 先ほどのAutoHotKeyのところでも少しご説明したとおり、まずはOSからChromeに渡されるときに、Ctrl-WはCtrl-Lに変更されて渡されるようにします。
- Chromeでは、Ctrl-Lには変なショートカットキーは割り当てられていないのでブロックされず、JupyterLabに渡されます。
- JupyterLab側で、codemirrorの設定をいじる（後述）ことで、Ctrl-Lに選択箇所のカットを割り当てます。

##5.3. emacs.jsの変更

上記の変更は、JupyterLabのインストールフォルダ内にある、emacs.jsというファイルをいじることで行うことができます。このファイルは、私の場合は以下にありました。

```
/usr/local/share/jupyter/lab/staging/node_modules/codemirror/keymap
```

※ちなみに、JupyterLabのサーバーは別のマシン（Linux）で動いていることを前提としています。

emacs.jsを[こちら](https://gist.github.com/mxl00474/6ed88c19e12755f7d58f6efbaaab6746)のように変更することで、Ctrl-Lに選択箇所のカットが割り当てられます。

※具体的には、元のemacs.jsに以下の行を追加しています。

```JavaScript
    "Ctrl-L": function(cm) {kill(cm, cm.getCursor("start"), cm.getCursor("end"), true);},
```

また、ついでに、Ctrl-[ WでCopyもできるよう、以下も追加しています。

```JavaScript
    "Ctrl-[ W": function(cm) {
      addToRing(cm.getSelection());
      clearMark(cm);
    },
```

以上、殴り書きで分かりにくかったかもしれませんが、同じような悩みを持った方の解決策になればうれしいかなと思います。
せっかくアドベントのシーズンなのと、今日の枠が空いていたので、アドベントとして投稿してみました。