f

アーカイブ

2015-11-01

NeoBundleによるVimプラグイン管理の作法

テキストエディタVimのプラグインマネージャーであるNeoBundle(neobundle.vim)を使い始めて2年ほど経過した。NeoBundleの設定方法についての考え方や作法がわかってきたのでまとめる。 NeoBundleを導入したのは2013年の冬頃であり,Gitで管理始めたのが2014-05-06(https://github.com/lamsh/dotfile/commit/879786443d29974173138da221cf7409bbd4ddc2)となっている。

何ヶ月か前に以下の記事を見て感銘を受けた。この内容を参考にNeoBundleでの管理方法を見なおした。

最強の dotfiles 駆動開発と GitHub で管理する運用方法 - Qiita http://qiita.com/b4b4r07/items/b70178e021bef12cd4a2

NeoBundleでプラグインを管理するときは以下の観点が重要だ。

  1. NeoBundleがなくても最低限の動作の確保
  2. インストール部と設定部の分離
  3. OSやGUI,コマンドの有無などの状況に応じたNeoBundleの実行
  4. プラグインの遅延ロード

僕の.vimrcは以下で公開している。

lamsh/dotfile https://github.com/lamsh/dotfile

NeoBundleがなくても最低限の動作の確保

NeoBundleの配布元でNeoBundleの設定方法が書いてある。

Shougo/neobundle.vim https://github.com/Shougo/neobundle.vim

NeoBundleを使い始めた人はおそらくこの設定をそのまま使うだろう。しかし,この設定だと問題がある。それは,「NeoBundleがインストールされていないとVimの起動時にエラーが起こる」というものだ。

Vimを起動してすぐにNeoBundleをインストールすれば解決することだ。しかし,他人のPC上で迅速に作業を行わないといけないときに,NeoBundleや各種のプラグインのインストールを待っていられない。プラグインの数が多くなってくると初回のダウンロード時間を無視できない。したがって,NeoBundleや各種プラグインがなくてもVimの最低限の動作を確保が必要だ。ここでいう「最低限の動作」とは,NeoBundleやプラグインがインストールされていないことで生じるエラーをなくすという意味だ。

以下ではNeoBundleがなくても最低限の動作を確保する方法を説明する。

NeoBundleがインストールされていない状態でVimを起動した時に最初に起こるエラーは以下だ。

senooken% 0-0: vim .vimrc
Error detected while processing /home/senooken/.vimrc:
line   45:
E117: Unknown function: neobundle#begin

これは,NeoBundleにプラグインのインストール基準となるパスを指定する以下の関数で発生している。

  call neobundle#begin(expand('~/.vim/bundle/'))

これは,NeoBundleがインストールされていないために未定義の関数neobundle#begin()を呼び出しているために発生している。幸いなことにVimにはtry-catchによるエラー処理の構文が存在しているので,このエラーを捕捉して判定する。以下のように記述する。

"" const variable
let s:FALSE = 0
let s:TRUE = !s:FALSE

if has('vim_starting') "" runtimepathにneobundle.vimをインストールしたディレクトリを指定 set runtimepath+=~/.vim/bundle/neobundle.vim/ endif
let s:is_neobundle_installed = s:TRUE
try "" プラグインをインストールする基準となるパスを指定 call neobundle#begin(expand('~/.vim/bundle/')) catch /^Vim\%((\a\+)\)\=:E117/› " catch error E117: Unkown function let s:is_neobundle_installed = s:FALSE set title titlestring=NeoBundle\ is\ not\ installed!
endtry if s:is_neobundle_installed "" Let NeoBundle manage NeoBundle NeoBundleFetch 'Shougo/neobundle.vim' filetype plugin indent on " valid vim plugin

NeoBundle 'Shougo/neocomplete'
"" ... " ここにNeoBundleで管理したいプラグインを好きなだけ列挙する。
NeoBundle 'tyru/caw.vim'

call neobundle#end()
filetype plugin indent on
" NeoBundleCheck " I'm not prefered checking.
endif

以下では上記の重要な部分について解説していく。

まず,最初にs:FALSE, s:TRUEのようにbooleanのスクリプトローカル変数として定義した。こうすることで,その後の条件判定がわかりやすくなる。

"" const variable
let s:FALSE = 0
let s:TRUE = !s:FALSE

以下では最初にs:is_neobundle_installedというNeoBundleがインストールされているかどうかを示す変数を用意した。そして,try-catch文によりneobundle#begin()関数の未定義エラーE117を補足して,s:is_neobundle_installeds:FALSEを設定する。同時に,Vimのタイトルに"NeoBundle is not installed!というメッセージを表示させてユーザーに知らせる。

let s:is_neobundle_installed = s:TRUE
try "" プラグインをインストールする基準となるパスを指定 call neobundle#begin(expand('~/.vim/bundle/')) catch /^Vim\%((\a\+)\)\=:E117/› " catch error E117: Unkown function let s:is_neobundle_installed = s:FALSE set title titlestring=NeoBundle\ is\ not\ installed!
endtry

NeoBundleでプラグインを管理する部分をif s:is_neobundle_installed-endifで囲ってNeoBundleがインストールされていないときは実行されないようにする。NeoBundleCheckコマンドを使うと,インストールされていないプラグインがあるとVimの起動時に確認してきて邪魔なので設定しない。

if s:is_neobundle_installed
  "" Let NeoBundle manage NeoBundle
  NeoBundleFetch 'Shougo/neobundle.vim'

NeoBundle 'Shougo/neocomplete'
"" ... " ここにNeoBundleで管理したいプラグインを好きなだけ列挙する。
NeoBundle 'tyru/caw.vim'

call neobundle#end()
filetype plugin indent on
" NeoBundleCheck " I'm not prefered checking.
endif

インストール部と設定部の分離

前節でNeoBundleのインストール部分について設定をした。今度はインストールした各プラグインの設定を記述していく。プラグインの設定についても,インストールされていなくてもエラーが出ないように注意する必要がある。このことについては以下の記事とそこでリンクされている「 ももんが流NeoBundle管理術 - かなりすごいブログ」で解説されているneobundle#tapneobundle#untap関数が参考になる。

新・ももんが流NeoBundle管理術(あたらしい) - かなりすごいブログ http://blog.supermomonga.com/articles/vim/neobundle-sugoi-setting.html

プラグインの設定のセクション化の考えはよいが,neobundle#tap-untapはあまりよくない。理由は以下3点だ。

  • 既にneobundle#is_installed()neobundle#is_sourced()という同等の関数がある。
  • tapを使うとuntapしないと無駄にメモリを使う。コードが長くなる。
  • 開発者自体も気に入っていない。以下の引用参照。

Shougo
> tap 使うなら neobundle#is_installed() 使うかなぁ…。まぁ hook じゃなくて neobundle#is_installed() だけでよい気もするけど 02/23 20:49
はい。それがあるからいらない

vim-jp – Lingr http://lingr.com/room/vim/archives/2014/02/23

これらのことから,neobundle#tap()ではなくneobundle#is_installd()を使う。また,neobundle#is_installed()はNeoBundleの関数であり,NeoBundleがインストールされていなければエラーとなる。そこで,冒頭の記事の作者 @b4b4r07と同じように関数でラップして使う。これを考慮すると以下のような記述となる。

function! s:neobundled(bundle)
  return s:is_neobundle_installed && neobundle#is_installed(a:bundle)
endfunction

if s:neobundled('neocomplete')
  let g:neocomplete#enable_at_startup = 1
  let g:neocomplete#enable_ignore_case = 1
  let g:neocomplete#enable_smart_case = 1
  if !exists('g:neocomplete#keyword_patterns')
    let g:neocomplete#keyword_patterns = {}
  endif
  let g:neocomplete#keyword_patterns._ = '\h\w*'
  "" C++
  if !exists('g:neocomplete#force_omni_input_patterns')
    let g:neocomplete#force_omni_input_patterns = {}
  endif
  let g:neocomplete#force_omni_input_patterns.cpp =
        \ '[^.[:digit:] *\t]\%(\.\|->\)\w*\|\h\w*::\w*'
  inoremap <expr><TAB> pumvisible() ? "\<C-n>" : "\<TAB>"
  inoremap <expr><S-TAB> pumvisible() ? "\<C-p>" : "\<S-TAB>"
endif

"" 好きな数だけif s:neobundled('プラグイン名') - endifを繰り返す。

最初にs:neobundled()関数を定義して,以下2点の条件判定を行う。

  • NeoBundleがインストールされていること。
  • 指定したプラグインがインストールされていること。

Vimでは&&演算子は前の評価結果が偽だと後ろは評価されないようなので,指定したプラグインが未インストールでも問題なく動作する。function!のように!をつけることで.vimrcを複数回読み込んだ時でも既に定義されていることによるエラーを抑制している。

function! s:neobundled(bundle)
  return s:is_neobundle_installed && neobundle#is_installed(a:bundle)
endfunction

以下のようにif s:neobundled('プラグイン名') - endifで設定を行うプラグインをセクション化して,インストールされていなければ実行されないようにする。こうすることで,プラグインの設定がインストールされているかどうかに依存しなくなる。

if s:neobundled('プラグイン名')
設定
endif

ここまででNeoBundleやプラグインに依存しないNeoBundleの設定手順は完了した。以下ではNeoBundleでプラグインを管理するときのコツを記す。

OSやGUI,コマンドの有無などの状況に応じたNeoBundleの実行

NeoBundleで管理するときに,GUI(gvim)動作時や特定のOS,コマンドの有無,依存プラグインにうまく対応したいことがある。

以下のような関数などで条件判定できるのでこれを利用する。

has('gui_running')
executable('コマンド')

"" 各種OS
let s:is_windows = has('win16') || has('win32') || has('win64') let s:is_cygwin = has('win32unix') let s:is_msys = has('win32unix') let s:is_mac = !s:is_windows && !s:is_cygwin \ && (has('mac') || has('macunix') || has('gui_macvim') || \ (!executable('xdg-open') && \ system('uname') =~? '^darwin')) let s:is_linux = !s:is_mac && has('unix')

GUI起動時の例は以下となる。

  if has('gui_running')
    NeoBundle 'istepura/vim-toolbar-icons-silk' " cool gvim toolbar icon
  endif

makeコマンド存在時の例は以下となる。

  if executable('make')
    NeoBundle 'Shougo/vimproc', {
      \ 'build' : {
      \    'windows': 'echo "Sorry, cannot update vimproc binary file in Windows."',
      \    'cygwin' : 'make -f make_cygwin.mak',
      \    'mac' : 'make -f make_mac.mak',
      \    'unix' : 'make -f make_unix.mak',
      \   },
      \ }
  endif

プラグインが別のプラグインに依存しているときはNeoBundleのオプションであるdependsで指定できる。

参考:http://www.slideshare.net/Shougo/neobundlevim#18

依存関係の定義例。

  NeoBundle 'garbas/vim-snipmate' , {'depends' :
        \ [ 'MarcWeber/vim-addon-mw-utils',
        \   'tomtom/tlib_vim']
        \ }

プラグインの遅延ロード

言語設定など特定の状況でだけ読み込みたいプラグインがある。そういう場合にはNeoBundleLazyコマンドによるプラグインの遅延ロードが有効となる。以下のようにオプションのautoloadで読み込みたいファイルタイプや状況を指定することで実現できる。

  NeoBundleLazy 'vim-jp/cpp-vim', { 'autoload': {'filetypes' : 'cpp'}}
  NeoBundleLazy 'asciidoc.vim', {"autoload" : {"filetypes" : "asciidoc"}}

なお,遅延ロードについては自分がまだよくわかっていない部分が多いので詳細はかけない。

0 件のコメント:

コメントを投稿