Vim で :terminal の使い勝手をよくした
重い腰を上げてやっと Vim の :terminal
を使ってみたんですが、いろいろと使い勝手が悪かってので魔改造しました。
いろいろと便利な設定を書こうと思ったら完全に煽り記事みたいになってしまい大変申し訳無くry。
:terminal
を真面目に触ってからまだ半日ぐらいしか経ってないので間違っている部分があればゆるしててへぺろ。
ちなみに Vim 8.0.1295 時点での話になります。
<C-w>
prefix を変更する
さて、:terminal
はなぜか Vim の思想に反して insert mode 主体で操作することになります。
なので :terminal
を実行すると insert から開始されてまず面食らいます。
それはいいんですが、何故か insert から抜ける際に <Esc>
ではなくて <C-w><S-n>
を使用するわけのわからない仕様となっています。
と、いうのも :terminal
内ではなぜか insert 時の操作として <C-w>
が prefix をして使用されており、これを基準として操作するからです。
insert で <C-w>
ってめっちゃ使うのになんでこのキーを prefix にするのか小一時間(ry。
なのでまず <C-w>
をどうにかします。
幸いにもこの prefix は 'termkey'
で設定することができるので好きなキーに割り当てておきましょう。
" とりあえず Alt + w にでも set termkey=<A-w>
これで <A-w><S-n>
で insert から抜けることができます。
が、流石にこれも Vim らしくないので <Esc>
で insert を抜けたいです。
幸いにも :tmap
という :terminal
の insert 時専用のキーマッピングを行う機能があるのでそれを使用します(なんで imap
じゃないんだろうか???
" <Esc> で :terminal の insert を抜ける " <A-w> の部分は 'termkey' によって変わる tnoremap <Esc> <A-w><S-n>
これで :terminal
を使用する上でのストレスはだいぶ軽減されたと思います。
filetype
を設定する
次は 'filetype'
まわりの設定を行います。
Vim といえば autocmd FileType
等でその filetype
固有の設定を行うのが当たり前ですよね。
しかし、なぜか :terminal
では 'filetype'
が設定されていないので自前で 'filetype'
を設定することにします。
:terminal
を実装した人って本当に Vim を作ったことがあるのか本気で気になる
function! s:bufnew() " 幸いにも 'buftype' は設定されているのでそれを基準とする if &buftype == "terminal" && &filetype == "" set filetype=terminal endif endfunction function! s:filetype() " ここに :terminal のバッファ固有の設定を記述する endfunction augroup my-terminal autocmd! " BufNew の時点では 'buftype' が設定されていないので timer イベントでごまかすなど… autocmd BufNew * call timer_start(0, { -> s:bufnew() }) autocmd FileType terminal call s:filetype() augroup END
ポイントとしては autocmd BufNew
の時点では 'buftype'
が設定されていないようなので timer_start()
を使用して関数の呼び出しを遅延しています。
割と無理やり実装したのでこの辺りのタイミングはもっといい手段があるかもしれません…。
とにかくこれで filetype=terminal
を設定できたのであとは autocmd FileType terminal
等でやりたい放題になります。
:terminal
の起動直後に insert から抜ける
先程も書きましたが :terminal
は insert から開始されます。
ですが、Vim を使っているのであればやはり normal 主体で使いたいですよね。
と、いうことで『:terminal
を起動したあとに insert から抜ける』という設定を行います。
" 先ほど作った関数に追記する function! s:filetype() " これも <A-w> の部分は自分の環境に合わせて直してね call timer_start(0, { -> feedkeys("\<A-w>\<S-n>") }) endfunction
これも filetype=terminal
したタイミングだと inser から抜ける事ができなかったので timer_start()
でごまかしています。
起動した :terminal
を使い回す
最後に起動している :terminal
を使い回すようにします。
どういうことかというと :terminal
は :terminal
コマンドを実行する度に新しいバッファが生成されます。
この :terminal
のバッファは :q!
をしない限りは生き続けるので :terminal
をする度にどんどん :terminal
のバッファが増え続けます。
なので、すでに起動している :terminal
があればその :terminal
のバッファを開くようにしてみます。
function! s:open(args) abort if empty(term_list()) execute "terminal" a:args else let bufnr = term_list()[0] execute term_getsize(bufnr)[0] . "new" execute "buffer + " bufnr endif endfunction " すでに :terminal が存在していればその :terminal を使用する command! -nargs=* \ Terminal call s:open(<q-args>)
:terminal
の起動には :Terminal
コマンドを使用します。
term_list()
で『起動している :terminal
のバッファリスト』を取得できるので、それを使用して既存の :terminal
を開きます。
これは完全に簡易実装なのですが、バッファの開き方などを指定出来るようにはしたいですね…。
所感
と、いうことでざっと :terminal
を使ってみたんですが、それ自体がつらいというか全体的に Vim のセオリーから外れている点が一番つらいですね…。
未だに <C-w>
を prefix に割り当ててる理由がわかりません…。
よくよく考えたら『:terminal
を使ってみた』ではなくて『:terminal
を起動させてみた』ですねこれ。
まだ :terminal
自体全然使ってないな…これからがんばろう…。
あと
* Vim 上で端末を使える利点ってなんですか?
っていう質問をされた時の答えを教えてください。
補足
一応 <Esc>
を使わないのは以下のような理由があるみたい。
:terminal の中で ESC を使うアプリに ESC を(シームレスに)送れるべきだという物だと思います。
— mattnっぽい人 (@mattn_jp) 2017年11月14日
設定まとめ
" vital-palette-keymapping " <A-w> みたいなのを \<A-w> にして feedkeys() で呼び出せるようにするためのエスケープ関数 function! s:escape_special_key(key) " Workaround : <C-?> https://github.com/osyo-manga/vital-palette/issues/5 if a:key ==# "<^?>" return "\<C-?>" endif execute 'let result = "' . substitute(escape(a:key, '\"'), '\(<.\{-}>\)', '\\\1', 'g') . '"' return result endfunction " キーマッピングの設定 set termkey=<A-w> if exists(":tmap") tnoremap <Esc> <A-w><S-n> endif function! s:bufnew() if &buftype == "terminal" && &filetype == "" set filetype=terminal endif endfunction function! s:filetype() " set filetype=terminal のタイミングでは動作しなかったので " timer_start() で遅延して設定する call timer_start(0, { -> feedkeys(s:escape_special_key(&termkey) . "\<S-n>") }) endfunction augroup my-terminal autocmd! autocmd BufNew * call timer_start(0, { -> s:bufnew() }) autocmd FileType terminal call s:filetype() augroup END function! s:open(args) abort if empty(term_list()) execute "terminal" a:args else let bufnr = term_list()[0] execute term_getsize(bufnr)[0] . "new" execute "buffer + " bufnr endif endfunction " すでに :terminal が存在していればその :terminal を使用する command! -nargs=* \ Terminal call s:open(<q-args>)