Vim で自前バッファを用意するプラグインを書く

この記事は Vim Advent Calendar 2019 23 日目の記事です

qiita.com

最近メールを vim で読むためのプラグインを書いています(道半ば)
機能としては

  • メールフォルダの一覧
  • フォルダの中のメール一覧
  • メール本文の表示

があるのですが、これらを vim 上で表示するためには自分でバッファを用意してやる必要があります
Vim は通常テキストエディタとして使用され、バッファに入っているテキストは何かのファイルに紐付いていることが多いと思います
しかし、今回のメールフォルダ一覧表示のためのバッファなどは特定のファイルに紐付いていません
今回はそのようなバッファをどうすれば用意できるのかについて解説します

なお、各コマンド/関数の詳細や厳密な挙動については触れていると分量が大変なことになると思うので、適宜 help へ解説を投げます

特定のコマンドを実行したらバッファを開く

ディレクトリ構成

testplugin/
  - plugin/
    - testplugin.vim

スクリプト

  • testplugin/plugin/testplugin.vim
if exists('g:loaded_testplugin')
  finish
endif
let g:loaded_testplugin = 1

let s:save_cpo = &cpo
set cpo&vim

command! TestPlugin split hoge

let &cpo = s:save_cpo
unlet s:save_cpo

解説

command! 行以外はおまじないです
二重にプラグインを読まないことや set cpoptions の上書きなどをしています
詳細は :h use-cpo-save を参照してください

command! 行では TestPlugin コマンドを定義して、split hoge を実行するようにしています
つまり、このプラグインを読み込んだ状態で :TestPlugin を実行すると :split hoge を実行したのと同様にウィンドウが横に分割され、hoge という名前のバッファが作成されます

作成したプラグインを読みこむ

ここで作成したプラグインを読みこむ方法はいくつかありますが、今回は雑に .vimrc に以下を追記して testplugin ディレクトリ内で vim を起動する、という方法を取ります

  • .vimrc
" for develop
set runtimepath+=$PWD

これで testplugin ディレクトリにいるときに vim を起動すると :TestPlugin が実行できるようになっているかと思います

作成したバッファにテキストを追加する

スクリプト

testplugin/plugin/testplugin.vim を変更していきます

  • testplugin/plugin/testplugin.vim
if exists('g:loaded_testplugin')
  finish
endif
let g:loaded_testplugin = 1

let s:save_cpo = &cpo
set cpo&vim

command! TestPlugin split hoge | call append(0, 'hoge')

let &cpo = s:save_cpo
unlet s:save_cpo

解説

:h :bar でコマンドを連結しています
ここでは :h append()hoge という文字列をバッファに追加しています

これで :TestPlugin を実行すると hoge という名前のバッファに hoge という文字列が追加されているかと思います

バッファの設定を変更する

ここまでで気付かれている方もおられると思いますが、開いた hoge という名前のバッファを :q で閉じようとすると、保存してないぞ、と怒られるかと思います
また :wq で抜けた場合 testvim ディレクトリに hoge という名前のファイルが作成されており、その状態のまま再度 vim を起動し :TestPlugin を実行すると hoge が二行表示されるかと思います

これは hoge というバッファが ./hoge というファイルに紐付いており、そのファイルが保存/更新されているのでこのような挙動になってしまいます

ここではそれを防ぎ、特定のファイルに紐付かないようにバッファの設定を変更していきます
ついでに処理が長くなってきたので関数に分離していきます

ディレクトリ構成

testplugin/
  - plugin/
    - testplugin.vim
  - autoload/
    - testplugin.vim

スクリプト

  • testplugin/plugin/testplugin.vim
if exists('g:loaded_testplugin')
  finish
endif
let g:loaded_testplugin = 1

let s:save_cpo = &cpo
set cpo&vim

command! TestPlugin call testplugin#open_buffer()

let &cpo = s:save_cpo
unlet s:save_cpo
  • testplugin/autoload/testplugin.vim
let s:save_cpo = &cpo
set cpo&vim

function! testplugin#open_buffer() abort
  split hoge
  call append(0, 'hoge')
  call deletebufline('%', '$')
  setlocal buftype=nofile
  setlocal bufhidden=hide
  setlocal noswapfile nobuflisted
endfunction

let &cpo = s:save_cpo
unlet s:save_cpo

(Updated(2019/12/23 16:54): command 行に call がない)

解説

autoload については :h autoload/:h 41.15 を参照してください
ここでは testplugin/autoload/testplugin.vim で定義された testplugin#open_buffer() が他から参照できるという点のみが重要です

:TestPlugin を実行するとこの testplugin#open_buffer() が呼ばれます
testplugin#open_buffer() ではこれまでの処理に加えて deletebufline() で最後に表示されていた改行の削除とバッファ設定の変更をしています

  • buftypenofile にすることで :w ができないようにします
  • bufhiddenhide にして :q から抜けたときにバッファを隠しバッファとします
  • noswapfile でこのバッファについて swap ファイルを作成しないようにします
  • nobuflisted でこのバッファを :ls などで見えないようにします

詳細については :h special-buffers などを参照してください
// 正直この辺どう設定したらいいかはまだ試行錯誤中なので他の人の知見も知りたいところです
// 例えば nomodifiable も設定したいけれど、設定すると :q してからもう一度 :TestPlugin するとエラーで怒られてしまうのでどうしよう? とか

ここまででファイルに紐付かないバッファを作るプラグインが出来ました
のでこれで終わり、と言いたいところですが、最後に少しだけ変更を加えます

バッファ名を変更する

今は :TestPlugin により開かれるバッファ名は hoge になっていますが、:TestPlugin を実行して :q してから :e hoge を実行すると分かるように、:e hoge により開かれる hoge というバッファは新規ファイルではなく、既存の :TestPlugin により開かれたバッファになります
これでは hoge という名前のファイルが作成できなくて困ってしまうので、プラグイン独自のバッファ名を付けましょう

  • testplugin/autoload/testplugin.vim
let s:save_cpo = &cpo
set cpo&vim

function! testplugin#open_buffer() abort
  split testplugin://hoge
  call append(0, 'hoge')
  call deletebufline('%', '$')
  setlocal buftype=nofile
  setlocal bufhidden=hide
  setlocal noswapfile nobuflisted
endfunction

let &cpo = s:save_cpo
unlet s:save_cpo

testplugin://hoge という名前に変更しました
被りさえしなければ何でもいいのですが、:h Cmd-event を利用してバッファを作成しているプラグインなど プラグイン名://バッファ名 のようにしているプラグインがいくつか見られるのでそれに合わせてみました
// Cmd-event を利用したバッファ作成方法も解説したかったのですが、私自身がまだ理解しきれていないので今回はここまでです...

というわけで、自前でバッファを用意するプラグインを書くことができました