Vim/Neovim の terminal mode の中で Vim/Neovim を実行するときに便利そうなやつを作った

# つくった
<a href="https://github.com/tennashi/vimalter"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110762/1086f53e-b5c0-863b-cb94-85440d79a07a.png" width="460px"></a>
画像は https://ghlinkcard.com/ から生成
参考: [みんなにOSSを見てもらいたい人の為に、GitHubリポジトリのOGP的画像を自動生成してくれるサービスを作った](https://qiita.com/po3rin/items/eac851304cf058c532af)

# どういうやつ?
Vim/Neovim の terminal mode で Vim/Neovim を実行すると Vim/Neovim が入れ子に起動してしまう
[![asciicast](https://asciinema.org/a/YT7OLA31NCoEEtZILhuIgCTC1.svg)](https://asciinema.org/a/YT7OLA31NCoEEtZILhuIgCTC1)

これを回避するには Neovim の場合は [mhinz/neovim-remote](https://github.com/mhinz/neovim-remote) を使うとよい
[![asciicast](https://asciinema.org/a/e4jEutC0LeWVMEuaxgohK8yUX.svg)](https://asciinema.org/a/e4jEutC0LeWVMEuaxgohK8yUX)

Vim8 の場合でも clientserver 機能や terminal-api を利用すればよい
参考(clientserver): [Vim の :terminal の中から外の Vim を操る方法](https://mattn.kaoriya.net/software/vim/20171219121032.htm)
参考(clientserver): [:h clientserver](https://vim-jp.org/vimdoc-ja/remote.html#clientserver)
参考(terminal-api): [:terminal に関する小さい Tips](https://qiita.com/mattn/items/e99e5dc7c4054ba25e7d)
参考(terminal-api): [:h terminal-api](https://vim-jp.org/vimdoc-ja/terminal.html#terminal-api)

しかしいずれにせよ、vim/nvim の中と外で実行するコマンドを切り替えねばならんというのは面倒である
なので、自身が vim/nvim の中なのか外なのかをよしなに判断して上記の回避策を実行してくれるツールを作成した
先行研究もあったが、Vim8 のみ対応のようだったので Neovim にも対応する形で実装してみた
参考: [Vim in Vim しない :terminal](https://pocke.hatenablog.com/entry/2019/03/23/001206)

# どうやってつかう?
こんな感じ
[![asciicast](https://asciinema.org/a/qmUVLV7e93kLWgBcI3ARt64vt.svg)](https://asciinema.org/a/qmUVLV7e93kLWgBcI3ARt64vt)

あとは `alias vim=vimalter` みたいな感じで shell の alias 登録すれば、どこでもとりあえず `vim` って実行すればいい感じに vim/nvim が開く

# なにをしている?
## `vimalter` コマンドを実行しているのは vim なのか nvim なのか shell なのか
vim/nvim は terminal mode のとき、いくつか環境変数を追加してくれているので、それを利用した
環境変数 `VIMRUNTIME` に runtimepath を入れてくれているようなので(ex. `VIMRUNTIME=/usr/share/nvim/runtime`)一つ上のディレクトリ(ex. `/usr/share/nvim`) の部分が `nvim` なのか `vim` なのかで判定した

**もしかすると自前で vim/nvim を build してたりするとここに `vim` or `nvim` が入っておらず、上手く動かないかもしれない**

## neovim の場合
単純に引数を `nvr` コマンドに食わせて実行すればよい

## vim の場合
vim は少し複雑で `:term` する前に `call remote_startserver('hogehoge')` を叩いているかどうかで挙動を変える必要がある
`remote_startserver()` は `+clientserver` オプションを付けて build された vim でしか実行できない関数で、引数に指定された名前(例では `hogehoge`)で vim サーバを起動するコマンドである

これが実行されていれば terminal mode では 環境変数 `VIM_SERVERNAME` に `hogehoge` と入っているので `vim --servername $VIM_SERVERNAME --remote` file_name というコマンドを実行すれば親の vim で `file_name` を開いてくれる

`call remote_startserver('hogehoge')` されていない場合は環境変数 `VIM_SERVERNAME` は空なので他の方法を考える必要がある
今回は terminal-api を利用した

terminal-api の詳細は先に挙げた参考資料に任せるとして、とにかく `echo -e \x1b]51;[\"drop\",\"file_name\"]\x07` と terminal mode で実行すれば親の vim で `file_name` に指定したファイルを開いてくれる

### vim の実行ファイルを探索する
`/usr/bin/vim` が vim だといつから錯覚していた?

はい、debian 系(というか `update-alternatives` を使用している場合などは大抵) `/usr/bin/vim` とはただのシンボリックリンクである
実態はどこにいるかは辿ってみないと分からないので雑に `vim` に引数を食わせるわけにはいかない
ちゃんと現在 terminal mode で実行中の vim の実 path を辿ってやる必要がある
方法は愚直に PPID を辿って `vim` または `vim.basic`(debian で `apt` で入れた vim の実行ファイル名はこれになる) という文字列が実行ファイル名になっているものを探す
`apt` でインストールされた vim の実行ファイル名は他にも `vim.tiny` とかもあるが、`vim.tiny` は terminal mode が使用できないので除外した
**他にも実行ファイル名のパターンがあれば Issue か PR をください...!**
この実装をする際 [shirou/gopsutil](https://github.com/shirou/gopsutil) が非常に便利だった

## `-tab` option
Vim の `--remote` オプションは親でファイルを開く際、ファイルをウィンドウ分割して開く、という挙動となっており、個人的には `--remote-tab` の挙動のほうが好みだったので `-tab` オプションで制御できるようにした
弊害として、vim 側のオプションを渡す際は `--` を付ける必要があるが、まあ大抵こういうコマンドはそうなってるのでいいかな、と(適当)

`vim --remote-tab` だと、既存に空ファイルのウィンドウがあればそのウィンドウでファイルを開き、それ以外の場合は新規に tab を作成してそこでファイルを開く、という挙動をする

このとき困るのは `terminal-api` での実装で、現状 `drop` コマンド(つまり挙動としては `--remote` と同じ)しか雑にファイルを開くときには使えないので `--remote-tab` を真似ることはできない
`call` コマンドというユーザ定義関数を呼ぶコマンドもあるにはあるが、`Tapi_` で始まるユーザ定義関数しか呼べない、という制限があるため現状は未実装ということにしている

今後の予定として `Tapi_` 関数群を提供する Vim plugin を書いて、この挙動を実装する方針である