Posted at

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


つくった



画像は https://ghlinkcard.com/ から生成

参考: みんなにOSSを見てもらいたい人の為に、GitHubリポジトリのOGP的画像を自動生成してくれるサービスを作った


どういうやつ?

Vim/Neovim の terminal mode で Vim/Neovim を実行すると Vim/Neovim が入れ子に起動してしまう

asciicast

これを回避するには Neovim の場合は mhinz/neovim-remote を使うとよい

asciicast

Vim8 の場合でも clientserver 機能や terminal-api を利用すればよい

参考(clientserver): Vim の :terminal の中から外の Vim を操る方法

参考(clientserver): :h clientserver

参考(terminal-api): :terminal に関する小さい Tips

参考(terminal-api): :h terminal-api

しかしいずれにせよ、vim/nvim の中と外で実行するコマンドを切り替えねばならんというのは面倒である

なので、自身が vim/nvim の中なのか外なのかをよしなに判断して上記の回避策を実行してくれるツールを作成した

先行研究もあったが、Vim8 のみ対応のようだったので Neovim にも対応する形で実装してみた

参考: Vim in Vim しない :terminal


どうやってつかう?

こんな感じ

asciicast

あとは 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_SERVERNAMEhogehoge と入っているので 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 が非常に便利だった


-tab option

Vim の --remote オプションは親でファイルを開く際、ファイルをウィンドウ分割して開く、という挙動となっており、個人的には --remote-tab の挙動のほうが好みだったので -tab オプションで制御できるようにした

弊害として、vim 側のオプションを渡す際は -- を付ける必要があるが、まあ大抵こういうコマンドはそうなってるのでいいかな、と(適当)

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

このとき困るのは terminal-api での実装で、現状 drop コマンド(つまり挙動としては --remote と同じ)しか雑にファイルを開くときには使えないので --remote-tab を真似ることはできない

call コマンドというユーザ定義関数を呼ぶコマンドもあるにはあるが、Tapi_ で始まるユーザ定義関数しか呼べない、という制限があるため現状は未実装ということにしている

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