Vimをターミナルマルチプレクサとして使うためにやったこと

# Vim 8 terminal job
Vim 8 からは terminal jobというVimのバッファ上でシェルを操作できる機能があります。

こちらなんですが、実はシェルを操作できるだけじゃなく、sshやNode.jsやClojureのreplなどttyを必要とするアプリケーションを指定するとバッファ上でそのままジョブが動いたりして便利です。

この記事ではこのterminal jobを利用した環境を~~魔改造~~カスタマイズしてより便利にしていく方法を紹介します。

この記事はbashを前提としています。

# Vim上のbashからVimにコマンドを送れるようにする
## Vimのclientserver機能
Vimにはclientserver機能があり、こちらを使うと他プロセスから対応するサーバ名のVimに対してコマンドを送信できます。

こちらを利用するためにはclientserver機能を有効にしたVimが必要です。

まずVim上で以下を実行します。

```vimscript
:call remote_startserver('mainecoon')
```

そして、ターミナル上(Vim上のものでも、他のものでも構いません)で、以下を実行します。

```bash
$ vim --servername mainecoon --remote-send '<C-w>:echo "hoge"<CR>'
```

すると、Vimには `--remote-send` で指定した `<C-w>:echo "hoge"<CR>` というキー入力が送信され、これにより `:echo "hoge"` が実行されます。

更に、clientserver機能を実行した状態で起動したアプリケーションの環境変数には `VIM_SERVERNAME` という値が追加されます。
よって、Vim上のbashでVimにキー入力を送信したい場合bashで以下のようなコマンドを実行すれば良いことがわかります。

```bash
$ vim --servername $VIM_SERVERNAME --remote-send '<C-w>:echo "hoge"<CR>'
```

ここまでのことを踏まえると以下のように `.bashrc` に追記することで、簡単なコマンドでVimにキー入力ができることが分かります。

```bash
if [ -n $VIM_SERVERNAME ]; then
    function v() {
        vim --servername $VIM_SERVERNAME --remote-send "<C-W>$*<CR>"
    }
fi
```

追記し、新たにVim上でbashを開くと

```bash
$ v :new hoge.md
```

などとすることでVim上のbashから新たなバッファを開き、そこで指定したファイルの編集を開始できます。

しかし

```bash
$ cd ./hoge ; v :new fuga.md
```

のような相対パスでのファイル指定は上手く実行できません。これはterminal jobのバッファのカレントディレクトリが起動時のカレントディレクトリのままになっているからです。

というわけで、指定コマンドの実行前に `:lcd $PWD` してあげることで解決します。
最終的に `v` という関数は以下のようになります。

```bash
if [ -n $VIM_SERVERNAME ]; then
    function v() {
        vim --servername $VIM_SERVERNAME --remote-send "<C-W>:lcd ${PWD}<CR>"
        vim --servername $VIM_SERVERNAME --remote-send "<C-W>$*<CR>"
    }
fi
```

毎起動時にclientserverを起動する場合は以下を `.vimrc` に記述してください(サーバ名は自由にしてください)

```vimscript
if has('clientserver') && len(v:servername) <= 0
    let server_list=split(serverlist(), '\n')
    let server_count=len(server_list)
     
    if match(server_list, v:servername) <= 0
        call remote_startserver('siberian' . server_count)
    endif
endif
```

## 注意事項
terminal jobが生きている状態では `:e hoge.md` はできないので、 `:e! hoge.md` しないといけません。我慢してください。

# 独自のエイリアスを定義し、Vimで対応するコマンドを実行する
人の欲は止まるところを知らず、Vim上で開いたbashから任意のVimコマンドにエイリアスをつけたくなります。

そこで、.bashrcに最高の関数を定義することで、無理矢理これを実現します。

## エイリアス設定の形式
今回は

```
alies commands args...
```

のような形式でエイリアス設定を記述することにしました。

よって、Vim上のbashで`vn` というエイリアスで `:new` したい場合は

```
vn :new
```

という形になります。今回はこの設定ファイルを `~/.vimfuncrc` に配置することにします(rcではないが書いた当初の気持ちでこのような命名になっています)

## エイリアス設定からVimコマンド実行用のbash関数を生成する関数を `.bashrc` に書く

解説が大変なので先にスクリプトを貼ります。

```bash
if [ -n $VIM_SERVERNAME ]; then
    function v() {
        vim --servername $VIM_SERVERNAME --remote-send "<C-W>:lcd ${PWD}<CR>"
        vim --servername $VIM_SERVERNAME --remote-send "<C-W>$*<CR>"
    }
 
    function vFuncDeclare() {
        eval "function $1() {
            v `echo "$*" | cut -d' ' -f2-` "\$*"
        }"
    }
 
    VFUNCPROP=`sed -e 's/^/vFuncDeclare /g' ~/.vimfuncrc`
    VFUNCCOUNT=`wc -l <<EOF
$VFUNCPROP
EOF`
 
    for i in `seq 1 $VFUNCCOUNT`; do
    `head -$i <<EOF | tail -1
$VFUNCPROP
EOF`
    done
 
fi
```


重要なのは `vFuncDeclare` という関数で、これは

```
funcname :vim-func arg1 arg2 ...
```

の形式から

```bash
function funcname() {
  v :vim-func arg1 arg2 ... "$*"
}
```

という関数を定義する関数です。さらに、ここで定義された関数には追加の引数を与えることができます。

たとえば

```
vn :new
```

という定義をこの関数に渡すと

```bash
function vn() {
  v :new "$*"
}
```

という関数が定義されるので、Vim内のbashから

```bash
$ vn ./filename
```

とすると新しいバッファで `./filename` を編集できます。

この `vFuncDeclare` の後のあれこれは `.vimfuncrc` の中身をなんとかして `vFuncDeclare` に渡すための記述です。

## 実際の設定例
```
vn :new
vt :tabnew
gt :tabnext
gT :tabprevious
wt T'<C-W>':Nop 
vterm :term 
Vterm :term $* '<CR><C-W>T<C-W>':Nop
vssh :term ssh
Vssh :term ssh $* '<CR><C-W>T<C-W>':Nop
Vemacs :term emacs -nw $* '<CR><C-W>T<C-W>':Nop
vquit :qa!
```

`Vterm`や`Vssh`などがなにやら激しいことになっています。

`Vterm`を例に見るとこれは実際に定義される関数が

```bash
function Vterm() {
  v :term $* '<CR><C-W>T<C-W>':Nop
}
```

となり、実際に実行される際は`v`関数内で

```bash
vim --servername $VIM_SERVERNAME \
    --remote-send "<C-W>:term {{Vtermの引数全て}}<CR><C-W>T<CR><C-W>:Nop{{Vtermの引数全て}}<CR>"
```

のように実行されます。これによって
1. `:term {{引数}}` を実行
2.  `<C-W>T` で開いたバッファを新たなタブへ移動
3.   `:Nop {{引数}}` を実行

の順で処理を実行させることが可能です。
`：Nop`は余計なキー入力を抑制するためのVimコマンドで.vimrcなどで

```vimscript
function! Noperation()
endfunction
 
command! -nargs=* Nop call Noperation()
```

を定義しておいてください。

# 最後に
無理くり改造するのが楽しかっただけで他にもっと良いカスタマイズやプラグインなどがあるかもしれません。
みなさまも是非環境を魔改造し快適なCUIライフを