いい感じに 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の設計思想の記事も書きたいです