VimとテンキーとVT100


<strong>2018/08/17 追記:</strong> <a href="https://github.com/vim/vim/commit/e6882bdc44efbcea0af647f5759a8f48cf29e27c">Vim 8.1.0142</a> で、<a href="#vimとテンキー">Vimとテンキー</a>で紹介した設定相当の変更が入りましたので、TERM環境変数をxtermやxterm-256color等で使用している時は特になにも設定しなくてもテンキーが使えるようになりました。

端末上で Vim を使っていて、テンキーが使えないと悩んだ事は無いでしょうか?
今回は端末上の Vim で標準ではテンキーが使えない理由の説明と、使えるようにする設定について書いてみます。

# VT100のキーパッドモード
現在の端末エミュレータは、DEC の VT100 という端末の動作を標準としています。この VT100 のキーパッド (テンキー) には、**数値キーパッドモ－ド**と**アプリケーションキーパッドモード**の二つのモードがあります。

数値キーパッドモードは、キーパッドのキーに書かれている数字や記号をそのまま送信するモードです。このモードでは、例えばキーパッドの 0 キーを押した時に 0 が送信されますが、メインキー側の 0 を押した時にも 0 が送信される為、ホスト側で動いているアプリケーションにはどちらの 0 を押したのかは区別が付きません。

アプリケーションキーパッドモードは、キーパッドの各キーで決められた文字列、たとえば 0 キーは &lt;ESC&gt;Op という文字列(キーシーケンス)を送信するモードです。このモードではホスト側のアプリケーションがメインキーとキーパッドのどちらのキーが押されたかを判別できるようになりますが、対応していないアプリケーションではキーパッドのキーを正しく認識出来なくなります。

Vim は起動時にキーパッドをアプリケーションキーパッドモードに切り替えます。しかし、アプリケーションキーパッドモードで端末がどのようなキーシーケンスを送ってくるか認識出来ない為、キーパッドのキーが使えない状態になります。

# Vimでの特殊キーの認識方法
Vim 自体は内部的にはメインキーの数字キーとキーパッドの数字キーを区別出来るようになっています。しかし、端末が送ってくるキーシーケンスをキーパッドのキーだと紐付けられない為、キーパッドのキーが使えなくなっています。

Unix 系 OS には termcap や terminfo という、端末の情報を集めたデータベースが有ります。termcap, terminfo は端末の情報を集めたデータベースで、端末の機能の使い方(例えばカーソルを動かすにはどうすればいいかとか)や、特殊キーの情報(例えば F1 キーを押した時にどのようなキーシーケンスが送られてくるか)などが記録されています。

Vim はこの termcap や terminfo の情報を参照する事によって F1 等の特殊キーを認識しています。しかし、termcap や terminfo には、キーパッドの数字キーや記号キーの情報が有りません。その為、Vim はキーパッドのキーを正しく認識出来なくなります。

# アプリケーションキーパッドモードの無効化
キーパッドのキーが使えない原因は判りましたが、使えるようにするにはどうすればよいでしょうか。アプリケーションキーパッドに切り替わるのが問題ならば、切り替わらないようにするというのが一つの方法として考えられます。これには端末エミュレータで設定する方法と、Vim で設定する方法の二種類があります。

## 端末エミュレータでの設定
端末エミュレータによっては、アプリケーションキーパッドモードを無効化する設定を持っている物があります。例えば Tera Term や PuTTY にはこの「アプリケーションキーパッドモードを無効化する」設定があります。端末が対応している場合、この機能を利用するのが楽でしょう。

Tera Term では [設定] - [キーボード] にある "無効化するモード" の "アプリケーションキーパッド" にチェックを入れます。
![Tera Term キーボード設定](https://qiita-image-store.s3.amazonaws.com/0/7627/ece29a06-6681-713d-6211-e36391cc5db3.png)

PuTTY では [Terminal] - [Features] にある "Disable application keypad mode" にチェックを入れます。
![PuTTY Features設定](https://qiita-image-store.s3.amazonaws.com/0/7627/8995b78c-d447-5fda-8206-c905ea72ab0c.png)

端末エミュレータ側での対処は、以下のような長所が有ります。

 * 複数のサーバを利用している場合、サーバ毎に個別に設定する必要が無い
 * Vim だけではなく、同様の問題が出る他のアプリケーションでも有効

一方、以下のようなデメリットも有ります。

 * 「アプリケーションキーパッドモードの無効化機能」が有る端末でしか使えない
 * 問題が出ていないアプリケーションでは逆に正しく動かなくなる可能性が有る

## Vimでの設定
端末側ではなく、Vim 側でアプリケーションキーパッドモードを使わないように設定する事も可能です。Vim がアプリケーションキーパッドに切り替えないようにするには、.vimrc に以下の設定を追加します。

```vim:.vimrc
if &term =~ "xterm" || &term =~ "screen" || &term =~ "vt100"
    " Disable appkeypad
    let &t_ks = "\e[?1h"
endif
```

この設定は、端末が VT100 や xterm と互換性のある場合のみに使えます。現在の一般的な端末エミュレータならば問題なく使えるでしょう。

Vim 側での対処には、以下のような長所が有ります。

 * 他のアプリケーションに影響を与えない
 * 利用する端末エミュレータを選ばない

デメリットとしては以下が考えられます。

 * 複数のサーバが有る場合、それぞれで設定する必要が有る

# Vimとテンキー
別の方法としては、Vim がキーパッドのキーを正しく認識できるようにするというのが有ります。
以下の設定を .vimrc に追加して、アプリケーションキーパッドモードでキーパッドのキーがそれぞれどのようなキーシーケンスを送ってくるかを設定すると、キーパッドのキーが使えるようになります。

```vim:.vimrc
if &term =~ "xterm" || &term =~ "screen" || &term =~ "vt100"
    " keypad + <kPlus>
    exec "set t_K6=\eOk"
    " keypad - <kMinus>
    exec "set t_K7=\eOm"
    " keypad / <kDivide>
    exec "set t_K8=\eOo"
    " keypad * <kMultiply>
    exec "set t_K9=\eOj"
    " keypad Enter <kEnter>
    exec "set t_KA=\eOM"
    " keypad . <kPoint>
    exec "set t_KB=\eOn"
    " keypad 0 <k0>
    exec "set t_KC=\eOp"
    " keypad 1 <k1>
    exec "set t_KD=\eOq"
    " keypad 2 <k2>
    exec "set t_KE=\eOr"
    " keypad 3 <k3>
    exec "set t_KF=\eOs"
    " keypad 4 <k4>
    exec "set t_KG=\eOt"
    " keypad 5 <k5>
    exec "set t_KH=\eOu"
    " keypad 6 <k6>
    exec "set t_KI=\eOv"
    " keypad 7 <k7>
    exec "set t_KJ=\eOw"
    " keypad 8 <k8>
    exec "set t_KK=\eOx"
    " keypad 9 <k9>
    exec "set t_KL=\eOy"
endif
```

この設定は、端末が VT100 や xterm と互換性のある場合のみに使えます。現在の一般的な端末エミュレータならば問題なく使えるでしょう。

# 最後に
Vim でテンキーを使えるようにする設定を紹介しましたが、本当はこんな設定をしなくても使えるのがいいんですけれどね。