いい感じに dein.vim を活用したvim confを設計した話
おしごとでC++かこうと思ってごにょったときに、vim configsの設計が1.5年前ぐらいと古くてアレだったので neovimとdein.vimに移行した次第です。
おしごとはほっぽりだしてvimいぢりしてました。(vimあるある)
「configの設定こだわるよりコード書け」と私も思いますが息抜きだと思いたいです。
私はvim力がひくいので何かご指摘や助言がありましたらよろしくお願いします。
おしながき
(旧式)私流vim confと設計思想
よくやるようにgithubの.dotfilesレポジトリを~/.dotfilesにおいて、
~/.vimと~/.vimrcにシンボリックリンクをはるようにしています。
~/.vim の構成
人によっては .vimrc.dとかなる名前のやつです。
~/.vimの構成は以下のようになってます

特徴
~/.vimrcは 15行- 具体的な設定はすべて
runtime!している。 - git submoduleでNeoBundleを突っ込んでいる
- 具体的な設定はすべて
~/.vim/vimrc.d/basic~/.vim/vimrc.d/plugins- NeoBundleコマンドが必要なplugin設定系
- pluginごとにそれとなくファイルを分ける
- 何のpluginが何をしているのか
- コマンドで調べたかったら
agかgrepする - pluginのdisableを簡単に(
.bakとかつけるとdisableされる)
~/.vimrcこと~/.vim/vimrc
runtime! vimrc.d/basic/*.vim if empty(glob("~/.vim/bundle/neobundle.vim/README.md")) echo 'neobundle not installed!!(run :DotfilesSubmoduleUpdate)' function! DotfilesSubmoduleUpdate() cd ~/.dotfiles !git submodule init !git submodule update q endfunction command DotfilesSubmoduleUpdate :call DotfilesSubmoduleUpdate() else runtime! vimrc.d/plugin/*.vim endif
何を意図してこの設計であるか
- 人間はわすれる。初心貫徹は無理
- pluginを入れた意図、設定、コード規約は忘れる
- dotfilesは反復型開発するべきという思想
- 反復型開発のときの変更容易性を重視
- 新しい環境で「とりあえずすぐvim使いたい」の実現
- 鯖に凸するときとか 「とりあえずすぐvim使いたい」 ときがあるので、pluginがいらないときがある
- ex)新しい鯖でさくっと使いたいときは、補完とかいらない
- git clone待ちとか
NeoBundleInstall待ちで「fuuuuuuuuuuuck」ってならないように
- pluginのdisableを簡単に
- 環境によってはpluginがシステムのライブラリに依存していて死ぬことがある
vimfiler.vimをvimfiler.vim.backにするとすぐdisableされる
旧式vim confの問題点
- NeoBundleを使っている
dein.vimがいいかんじなので移行したい - NeoBundleLazyを使ってない
- (設計当時の私はNeoBundleLazyがよくわかってなかった)
dein.vimを読んでbetterな設計を考える
dein.vimの要約
toml形式とdein#add({pkgname})の関数呼び出し形式のパッケージ追加に対応- (じつはNeoBundleもtoml対応してたようだけど知らなかった)
- pluginのディレクトリを統合して1つにし、最小のruntimepathで読み出している
- 私の設定では
~/.cache/dein/.deinあたりにmerge済みのplugin集合体がある - たぶん、これが
dein.vimの速さの秘訣の1つ
- 私の設定では
- vimrcやtomlの更新日時を見てcacheやstateを更新するか決定している
dein#load_state({path})やdein#load_cache({vimrc})の挙動はそんなかんじかな?
先行研究
github でvim confの先行実装をしらべて、長所と問題点を考えます
- githubで調べると、
dein.tomlとdein_lazy.tomlにpluginを列挙する init.vim(or.vimrc)でtomlを読み込むif dein#tap({pkgname})してif文内に設定を記述- Lazyをしているpluginは、
dein#source#{pkgname}をautocmdしている
if dein#tap('Shougo/deoplete') execute 'autocmd plugin_hook User' 'dein#source#'.dein#name \ 'source ~/.vim/hook/deoplete.vim' endif
長所と問題点
長所は、 tomlと dein#tapを書いた2つのファイルだけ読むので非常に速そう。
ただし、初心貫徹できない私はその設定ファイルのメンテナンスは無理そう
初心貫徹できない私にとっての問題点は以下
if dein#tap({pkgname})の記述が単調autocmd plugin_hook User' 'dein#source#{pkgname}の記述が単調- →関数化をすべき
- 未来の私がif文使わず書いてごちゃごちゃになるのを防止したい
- Lazyしているpluginの設定ファイルのパスを自分で指定しなければならない
- →自動的に決定するようにすべき
- 後の自分が意味不明なfilenameをつける可能性をなくす
- filenameが直書きなので変更容易性が低そう
if dein#tap({pkgname})が大量に書いてあるファイルは読みにくい- vimのfold(
"{{{)を使うのも良いですが、わたしはあんまり使わない人なので… - 「このplugin、なんのpluginだっけ…」がありそう
- vimのfold(
ちなみに
「Lazyしているpluginの設定ファイルのパスをtoml内に書けたらいいなぁ」 とつぶやいたら、dein.vimの作者のShougoさんが拾ってくださって検討してくださるようなのでちょっと期待。
(vim力に自信があったらPRを投げたかった)
@cocu_tan 確かにそれがあると記述がすっきりとしますね。機能追加を検討します
— 暗黒美夢王(deoplete dev) (@ShougoMatsu) 2016年3月14日
dein.vimを利用した私流vim conf構成紹介
今回かいたvimfileはgithubにおいてあります。
方針
よって、以下に注視して設計すると良さそうに感じます
(ただし、私はvim力が低いのでこの対策が本当に効果的かそうでないかは自信がないです)
- watchしているfileを最小にすべき
- tomlやvimrcあたり?(なにをwatchしてるのかはあんまり読んでないです)
- 更新したかったら自分で
dein#update()などを叩けば良いかなと判断
- pluginの意図がわかるようにする
- 何をしているpluginなのか、どうして入れたのか推測できるように
- pluginの設定をファイルで分離する
- これまで同様のやりかたが使える
実装
- 一つのtomlファイルにpluginを列挙書く
dein.toml- 1つだけにして早くなるかなとちょっと期待
- toml内のpluginをコメントアウトするとdisableするように
- pluginに名前をつけれるようなのでそれを利用
{category}/{pkgname}のように名前をつける- この
/をpathのように使う
{category}/{pkgname}.vimを読み込む(runtime!)- ex)
runtime! ~/.config/nvim/rc.plugins/{category}/{pkgname}.vim
- ex)
{category}/{pkgname}-after.vimが存在すればhookを設定(autocmd)- ex)
autocmd なんちゃら source ~/.config/nvim/rc.plugins/{category}/{pkgname}-after.vim
- ex)
例えばvimfiler(pkgname:basic/vimfiler)の設定は、
- Lazyする必要がないものは
~/.config/nvim/rc.plugins/basic/vimfiler.vim - Lazyする必要あるものは
~/.config/nvim/rc.plugins/basic/vimfiler-after.vim
にかくようになる
実際の実装はこのような感じ。
let s:dein_plugin_path = 'rc.plugins' function! s:_hook_path(plugin) return expand('~/.config/nvim/').s:dein_plugin_path.'/'.a:plugin.'-after.vim' endfunction function! s:_conf_path(plugin) return s:dein_plugin_path.'/'.a:plugin.'.vim' endfunction function! s:apply_config(plugin) let l:name = a:plugin['name'] execute "runtime! ".s:_conf_path(l:name) let l:path = s:_hook_path(l:name) if filereadable(l:path) execute 'autocmd plugin_hook User' 'dein#source#'.l:name \ 'source '.l:path endif endfunction call map(deepcopy(dein#get()), 's:apply_config(v:val)')
tomlはこんな感じに。
#===========
# basic
[[plugins]]
name = 'basic/vimfiler'
repo = 'Shougo/vimfiler'
[[plugins]]
name = 'basic/vimref'
repo = 'thinca/vim-ref'
#===========
# lang
[[plugins]]
name = 'lang/python/compl-jedi'
repo = 'davidhalter/jedi-vim'
if = "!has('neovim')"
on_ft = 'python'
[[plugins]]
name = 'lang/rust/ft'
repo = 'rust-lang/rust.vim'
on_ft = 'rust'
[[plugins]]
name = 'lang/rust/compl-racer'
repo = 'phildawes/racer'
build = 'cargo build --release'
on_ft = 'rust'
すると設定ファイルtreeがこんな感じになる。

この実装の問題点
- 後の自分が、 意味不明なpluginのnameをつける可能性
toml書き換えとgit mvするだけで変更できるので、まぁいいかなと妥協
- performanceがわるい…?
autocmdとruntime!しまくってるのでちょっとこわい- 今のところ問題ない
- deinが一部設定ファイルをwatchしていない
dein#save_state()のタイミングの問題?- 前述のように妥協した点なので…
dein#update()などを叩けば良いのでまぁいいや…あとで引っかかりそう…
- 作者が意図した設計でなさそうなので、仕様変更で死ぬ可能性がある
- そのときはそのとき…
- この設計したのが私
- vimが強い人間が設計したわけではない
このような問題点があるので参考にする際は自己責任でおねがいです。
設計の議論はぜひしたいのでコメントおねがいします。
わからないこと
- vimをどうゆうふうにすると遅くなるのか
runtime!連発とautocmd連発とruntimepath連発では、どれがどの程度遅くなるのか、という勘- Lazyすべきpluginとすべきでないpluginの違い・注目すべきところ
- (
dein#check_lazy_plugin()便利!)
- (
dein.vimのdein-hooksの文面- 曰く
Note: You must set the autocmd before |dein#end()|. - https://github.com/Shougo/dein.vim/blob/master/doc/dein.txt#L609
- この実装では
dein#end()後にautocmdしている。 - タイミングの問題が起こるのでこのように記述しているのだろうが、問題が起こっていないし作者の
Shougoさんのvimrcもdein#end()後にautocmdしているのでいいのかな…?よくわからない - 余裕があればあとでissueたてます
- 曰く
最後に
ひさびさにvimいぢりたのしかったです。
なんだかんだvim力がないことを実感しました
話はかわりますが、dotfilesってすぐごちゃごちゃになる(切羽詰まった自分がごちゃごちゃにする)ので、他の方の設計とか考え方とか気になったりします。
私のdotfilesはによかったらstarください(乞食やめよう)。
需要があればdotfilesの設計思想の記事も書きたいです