Vimの隠しコマンド

1.Vimを立ち上げる

```
$ vim
```

2.コマンドモードに入る

```
:
```

3.`Ni!`とタイプしエンター

```
:Ni!
```

4.結果は......

```
Do you demand a shrubbery?
```


# なにこれ

モンティ・パイソン関連のジョークらしいです（参考：[Wikipedia](https://en.wikipedia.org/wiki/Knights_who_say_Ni)）。とは言っても私も元ネタを全く知らないのでよくわかりません。

検索してみるといくつかこのイースターエッグに言及した記事が見つかりました（他のイースターエッグも紹介されていて面白いです）。

 * [Vim に隠されたイースターエッグ](http://qiita.com/Linda_pp/items/53dd8467534bc657a232)
 * [Vim のイースターエッグを見る](http://vim-jp.org/vim-users-jp/2011/04/01/Hack-210.html)



# 詳細

[この記事](http://qiita.com/wholekeik/items/e9b528c240d15c714aa4)でやっていたVim scriptの高速化にあたってコードを眺めていると以下のような行を発見しました。

```c:src/ex_docmd.c
/* Check for wrong commands. */
if (*p == '!' && ea.cmd[1] == 0151 && ea.cmd[0] == 78)
{
    errormsg = uc_fun_cmd();
    goto doend;
}
```

`ea.cmd`は実行するExコマンドを表す文字列（`q`、`w`など）なのですが、なぜか文字リテラルではなく数値リテラルと比較しています。しかもその一方は8進数で書かれています。コメントには`Check for wrong commands`とそれっぽいことが書かれていますが、なんだか妙な感じです。そこで、エラーメッセージを返しているらしい`uc_fun_cmd()`を見てみます。

## `uc_fun_cmd()`

```c:src/ex_docmd.c
    static char_u *
uc_fun_cmd(void)
{
    static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
                            0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
                            0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
                            0xb9, 0x7f, 0};
    int		i;

    for (i = 0; fcmd[i]; ++i)
        IObuff[i] = fcmd[i] - 0x40;
    IObuff[i] = 0;
    return IObuff;
}
```

!?
文字列をわざわざ`""`を使わずに、しかも16進数で書き、それからさらに各文字から`0x40`を引くというなんとも回りくどいことをしています。一体この文字列はなんなのでしょうか。せっかくなのでVimを使って確かめてみましょう。

まず`fcmd`の右辺の`{`と`}`を`[`と`]`に置き換えます。surround系のプラグインを入れていれば一発です。
次に`[]`内の改行を取り除きます。`J`コマンドを使うといいでしょう。
さらに`ya[`とし、配列全体を無名レジスタにいれておきます。
最後にコマンドモードで`:echo join(map(eval(@")[:-2], 'nr2char(v:val - 0x40)'), '')`を実行します。

```
Do you demand a shrubbery?
```

冒頭のメッセージが復元できました。

## メッセージの発生条件

さて、このエラーメッセージが表示されるのは、

```c:src/ex_docmd.c
/* Check for wrong commands. */
if (*p == '!' && ea.cmd[1] == 0151 && ea.cmd[0] == 78)
{
    errormsg = uc_fun_cmd();
    goto doend;
}
```

このif文の条件式、`*p == '!' && ea.cmd[1] == 0151 && ea.cmd[0] == 78`が満たされた時です。ここで`ea.cmd`はユーザーが入力したコマンドを表す文字列で、`p`はそのコマンドの次の文字を指しています。それでは`0151`と`78`を文字に直しましょう。

```vim
:echo nr2char(0151) " => i
:echo nr2char(78) " => N
```

最初の文字が`N`、次が`i`、コマンドの次に来るのが`!`ですから、これはつまり`:Ni!`です。これで最初の現象が解明できました。~~なお、`:Ni!`以外にも`:Niiiiiiiiiii!`や`:Nippon!`、`:'<,'>Ni! foobar`などにも反応します。コマンドの名前が`Ni`で始まり、`!`が指定されていれば引数があろうと範囲指定があろうとこのメッセージが表示されます。~~（**追記: 最新のバージョンでは修正されています。下記参照**）


# ~~コマンド名に関する注意事項~~

**追記: patch 8.0.0656によりこの問題は修正されました。**

> patch 8.0.0656: cannot use ! after some user commands
> Problem:    Cannot use ! after some user commands.
> Solution:   Properly check for existing command. (Higashi Higashi)

内容が内容だけにProblem:やSolution:のところでジョークをかましてくるかと期待してたんですがいつもと同じ文章でちょっと残念......（言われてもわからないけど）。

**注意: ここから先はversion 8.0.0655以前の内容です。**

~~ここで少し気になるのが、ユーザーが`Ni`というコマンドを定義した場合です。~~

```vim
:command! -bang Ni echo 'User-defined Ni'
:Ni " => User-defined Ni
:Ni! " => Do you demand a shrubbery?
```

~~あれ。定義されているかどうかは関係なくこうなるんですね。イースターエッグをhelpに書くわけにもいかないので（わざわざ難読化していることですし）全く知らない人が突然ハマりそうで怖いです。~~



## ~~引っかかりそうな単語~~

* ~~Niceなんとか~~
* ~~Nightなんとか~~
* ~~9, 19, 90, ...~~
* ~~Nim language（奇しくもPython風の言語）~~
* ~~Nitroなんとか/Nickelなんとか（物質名はソフトウェアに結構ある気がする）~~
* ~~Ninja（ビルドツールのNinjaは大丈夫なんでしょうか）~~
* ~~その他多数（[List all words starting with ni](https://www.morewords.com/starts-with/ni/)）~~

~~Niceから始まるコマンドなんてもうすでにありそうで怖いですね。皆さんもコマンドを定義するときには`Ni`から始まっていないかどうかを確認しましょう。~~