Quantcast
Channel: Vimタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 5608

Vim Terminal機能を使いたい

$
0
0

はじめに

neovim、version8.0以降のvimに実装されているterminal機能を使いたかった。
なお、今回は8.1以降のvimを想定している(8.0→8.1で一部オプションや挙動が変わってたりする)
最新のvimを入れたかったらUbuntuなら

sudo apt remove vim

sudo add-apt-repository ppa:jonathonf/vim
sudo apt update
sudo apt install vim

とかでいけると思う。ほかの環境はちょっとわかんない。

画面下部に表示するように

terminalはデフォルトの設定だと画面上部に出てくる。なぜだ。
とりあえず下記の設定で画面下部に出るようになる。

ウィンドウの下にterminalを開く
set splitbelow

厳密にいうとこれは画面の水平分割時にいまのウィンドウの下に新しいウィンドウを生成するようになる設定だけど細かいことは気にしない。

デフォルトのターミナル、存在感大きくない?

ターミナルはデフォルトの設定だと画面を二分割して表示される。でかい。
ということで出てくるターミナルのサイズを指定。

terminalのサイズ指定
set termwinsize=7x0

7が高さ指定、0が幅指定。1以上だと指定行(列)数サイズになる。0だと最大。間の文字はエックス。
ちなみにvimのドキュメントだとなぜか間の文字を*(アスタリスク)にするほうが強調されてたりするけどこれは最低サイズを指定するというあまりいらない子だった。

terminalがいっぱい出てきて邪魔

:terminalは実行すればしただけ新たにウィンドウを生成する。そんなにいらない。
下記の関数ですでに存在すればterminalを生成しないようにしている。

ターミナルが開かれていないなら開く
function! TermOpen()if empty(term_list())
        execute "terminal"endifendfunction

term_list()はvimの組み込み関数で、現在開いているterminalのバッファのリストが取得できる。
これが空の時だけターミナルを開く。これを適当なキーで呼び出せるようにする。

キーマップ
noremap <silent><space>t:call TermOpen()<CR>

とりあえずspace + tで実行できるように。この書き方だとわざわざキー入力を再現してしまうのでもっと賢い書き方があったら教えてほしい。

二個目以降は既存のターミナルへフォーカス!!

さっきの関数だと二個目以降は無視される。これはあまりよろしくないので下記のように拡張する。

terminalを開く関数の最終形
function! TermOpen()if empty(term_list())
        execute "terminal"elsecall win_gotoid(win_findbuf(term_list()[0])[0])endifendfunction

term_listでバッファ番号を取得、win_findbuf()でウィンドウのIDへ変換。win_gotoid()でそのIDのウィンドウへと飛んでいる。
bufid → winidの変換にはbufwinidもあるが、こちらは現在のタブしか探せないっぽかったのでこっちにした。
これで二個目以降を開こうとしたら一番初めに開いたターミナルにフォーカスするようになる。よって配列要素アクセスはハードコーディングしていても問題はない。え? 直接:terminalを入力? 知らない子ですねぇ。

vimを閉じようとしたらターミナルだけ残って嫌な気持ちに

これを解決するためにかなり無理やりな実装をしている。あまり参考にしないでほしい。
(しかも完全には解決できていない)

アルゴリズムとしては、何かしらのウィンドウを閉じたときにterminalが開かれているか確認、そのウィンドウが開かれているタブのウィンドウの数を確認、残っているウィンドウの数が一つだけならterminalを終了、といった感じ。

まずはterminalが開かれているか確認

これは簡単。さっきもやってる。

ターミナルが開かれているか確認
if!empty(term_list())

これでいける。

terminalが開かれているタブで開かれているタブのウィンドウの数を確認

これがちょっとめんどくさい。まずterm_list()で帰ってくるのはバッファ番号だ。そしてvimにはバッファ番号からそのバッファが開かれているタブを取得する関数は用意されていない(たぶん。あったらごめん)。
しかし、タブ番号から開かれているウィンドウ数を取得する関数は用意されている。

そのタブ内で開かれているウィンドウ数を取得
tabpagewinnr(tabnr,'$')

'$' を指定しないとそのタブのカレントウィンドウ番号を返す。

つまり、バッファ番号からそのバッファが開かれているタブを見つけることさえできれば良い。
調べたら先人がいた。コピペOKとのことなのでありがたくコピペすることにする。

バッファ番号からタブ番号へ
function! CreateBufnr2tabnrDict() abort
  let bufnr2tabnr_dict ={}for tnr in range(1, tabpagenr('$'))for bnr in tabpagebuflist(tnr)let bufnr2tabnr_dict[bnr]= has_key(bufnr2tabnr_dict, bnr) ? add(bufnr2tabnr_dict[bnr], tnr):[tnr]endforendforfor val in values(bufnr2tabnr_dict)call uniq(sort(val))endforreturn bufnr2tabnr_dict
endfunctionfunction! Bufnr2tabnr(bnr) abort
  return CreateBufnr2tabnrDict()[a:bnr]endfunction

解説はkoturn様にお任せする。わかりやすい説明をありがとうございます。

準備は整った!!

いまこそterminalをぶっ〇す時。

terminalを終了する関数
function! ExitTerm()if!empty(term_list())let term_tabnr = Bufnr2tabnr(term_list()[0])let num_win_in_tabnr = tabpagewinnr(term_tabnr[0],'$')if num_win_in_tabnr ==1call term_sendkeys(term_list()[0],"exit\<CR>")endifendifendfunction

term_list()でバッファ番号を取得、koturn様がタブ番号に変換、そのタブ内のウィンドウ数を取得、ひとつだけならぶっ〇す。
ちなみに〇す方法もうまい方法が見つからなかったのでterminal内でexitって打ってる。よろしくなさそう。

あとはこれを呼び出すだけ。

autocmdで呼び出し
augroup term-exit
  autocmd!
  autocmd BufEnter * call ExitTerm()
augroup END

使うイベントはBufLeaveとかでも良さそう。

残った課題

上記までの方法だと、

  • 現在のタブには作業用ウィンドウがひとつ、terminalウィンドウがひとつの合計二つ。別のタブが開かれている

みたいなときには現在の作業用ウィンドウを閉じるとterminalも終了して次のタブに移動する。すばらしい。
しかし、

  • 現在のタブには作業用ウィンドウがひとつ、terminalウィンドウがひとつの合計二つ。別のタブは開かれていない

みたいなときだと、現在の作業用ウィンドウを閉じようとしたらterminalだけがサヨナラして現在のウィンドウは生存する。身代わりになるとは...やるなterminal...

仕方がないのでそういう時は魔法のコマンド。

ザラキ
qa!

万事解決。(作業用のウィンドウの保存し忘れとかはauto-saveが解決する)。
まぁタブが残ってるときの挙動だけでもうまくいったので満足。眠くなってきたのでおわりにした。もうちょっとましな方法があったら共有する。


Viewing all articles
Browse latest Browse all 5608

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>