読者です 読者をやめる 読者になる 読者になる

cocuh's note

type(あうとぷっと) -> 駄文

いい感じに dein.vim を活用したvim confを設計した話

vim

おしごとでC++かこうと思ってごにょったときに、vim configsの設計が1.5年前ぐらいと古くてアレだったので neovimdein.vimに移行した次第です。

おしごとはほっぽりだしてvimいぢりしてました。(vimあるある)
「configの設定こだわるよりコード書け」と私も思いますが息抜きだと思いたいです。

私はvim力がひくいので何かご指摘や助言がありましたらよろしくお願いします。

github.com

おしながき

  • (旧式)私流vim confと設計思想
  • dein.vimをそれとなく読んでbetterな設計を考える
  • dein.vimを利用した私流vim conf構成紹介

(旧式)私流vim confと設計思想

よくやるようにgithub.dotfilesレポジトリ~/.dotfilesにおいて、 ~/.vim~/.vimrcシンボリックリンクをはるようにしています。

~/.vim の構成

人によっては .vimrc.dとかなる名前のやつです。

~/.vimの構成は以下のようになってます

f:id:cocu_628496:20160316051458p:plain

特徴
  • ~/.vimrc は 15行
    • 具体的な設定はすべて runtime!している。
    • git submoduleでNeoBundleを突っ込んでいる
  • ~/.vim/vimrc.d/basic
    • NeoBundleに依存していないvimの設定を記述
    • これを分離することにより、「とりあえずすぐvimが使いたい」を実現
    • NeoBundle command not found地獄の回避
  • ~/.vim/vimrc.d/plugins
    • NeoBundleコマンドが必要なplugin設定系
    • pluginごとにそれとなくファイルを分ける
      • 何のpluginが何をしているのか
      • コマンドで調べたかったら aggrepする
      • 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.vimvimfiler.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})の挙動はそんなかんじかな?

先行研究

githubvim confの先行実装をしらべて、長所と問題点を考えます

  • githubで調べると、dein.tomldein_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

長所と問題点

長所は、 tomldein#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だっけ…」がありそう
ちなみに

「Lazyしているpluginの設定ファイルのパスをtoml内に書けたらいいなぁ」 とつぶやいたら、dein.vimの作者のShougoさんが拾ってくださって検討してくださるようなのでちょっと期待。
(vim力に自信があったらPRを投げたかった)

dein.vimを利用した私流vim conf構成紹介

今回かいたvimfileはgithubにおいてあります。

github.com

方針

よって、以下に注視して設計すると良さそうに感じます
(ただし、私は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
  • {category}/{pkgname}-after.vim が存在すればhookを設定(autocmd)
    • ex)autocmd なんちゃら source ~/.config/nvim/rc.plugins/{category}/{pkgname}-after.vim

例えば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がこんな感じになる。

f:id:cocu_628496:20160316051535p:plain

この実装の問題点

  • 後の自分が、 意味不明なpluginのnameをつける可能性
    • toml書き換えとgit mvするだけで変更できるので、まぁいいかなと妥協
  • performanceがわるい…?
    • autocmdruntime!しまくってるのでちょっとこわい
    • 今のところ問題ない
  • 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の設計思想の記事も書きたいです

github.com