koturnの日記

普通の人です.ブログ上のコードはコピペ自由です.

NeoBundleLazyのautoloadのcommands指定で頑張った話

neobundle.vimには,遅延読み込み機能があり,適切に設定してやることで,プラグインの使用感はそのままで,Vimの起動時間を短縮することができる. autoloadの設定には,

  • ファイルタイプ検出時 filetypes
  • ファイル名検出時 filename_patterns
  • コマンド実行時 commands
  • 関数実行時 functions, function_prefix
  • マップ実行時 mappings
  • Uniteのソース読み込み時 unite_sources
  • インサートモードに入った時 insert

など,様々なタイミングで,プラグインのパスを runtimepath に追加し,読み込みを行うことができる. コマンドの実行時の検出は,仮のコマンドを定義しておいて,それが実行されたとき,プラグイン本体を読み込む仕組みとなっていると思われる. そして,仮のコマンドには引数の補完関数を指定することもできる. basyura/TweetVimのNeoBundleLazyの設定を例にとると,以下のような形になるだろう.

NeoBundleLazy 'basyura/TweetVim'
if neobundle#tap('TweetVim')
  call neobundle#config({
        \ 'depends': ['tyru/open-browser.vim', 'basyura/twibill.vim'],
        \ 'autoload': {
        \   'commands': [
        \     'TweetVimAccessToken',
        \     'TweetVimAddAccount',
        \     'TweetVimClearIcon',
        \     'TweetVimCommandSay',
        \     'TweetVimCurrentLineSay',
        \     'TweetVimHomeTimeline',
        \     {'name': 'TweetVimListStatuses', 'complete': 'custom,tweetvim#complete#list'},
        \     'TweetVimMentions',
        \     {'name': 'TweetVimSay', 'complete': 'custom,tweetvim#complete#account'},
        \     {'name': 'TweetVimSearch', 'complete': 'custom,tweetvim#complete#search'},
        \     {'name': 'TweetVimSwitchAccount', 'complete': 'custom,tweetvim#complete#account'},
        \     'TweetVimUserStream',
        \     {'name': 'TweetVimUserTimeline', 'complete': 'custom,tweetvim#complete#screen_name'},
        \     'TweetVimVersion'
        \   ],
        \   'unite_sources': 'tweetvim'
        \ }
        \})
  call neobundle#untap()
endif

この例のautoloadのcommands設定にあるように,コマンドの補完関数には, customcustomlist に,プラグインautoload/hoge.vim で定義されている関数を指定してもよい. その場合,コマンド引数のTab補完を行った瞬間に,プラグインのロードが行われる.

しかし,プラグインによっては, customcustomlist に, plugin/hoge.vimスクリプトローカル関数を指定している場合もある. スクリプトローカル関数はneobundle側からは見えないので,指定することはできない. プラグインのロードが行われるまで我慢してもよいが,少し気分が悪い.

そこで,.vimrcに plugin/hoge.vim で定義されている関数をコピペし,neobundleから見えるようにスクリプト番号と共に渡してやるとよい. その補完関数はプラグインのロード後には不必要になるので,プラグイン読み込み時に呼び出されるフック関数で消去する,あるいは消去するオートコマンドを定義する.

そういったプラグインの例として,mattn/gist-vimosyo-manga/vim-reanimateなどがあるので,その2つのプラグインを例にとって,設定例を紹介する.

if has('vim_starting')
  set rtp+=~/.vim/bundle/neobundle.vim
endif
call neobundle#begin()
NeoBundleFetch 'Shougo/neobundle.vim'

let g:neobundle#default_options = {'_': {'verbose': 1}}
augroup CompleteDummy
  autocmd!
augroup END

function! s:SID() abort
  return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$')
endfun
let s:sid = s:SID()
delfunction s:SID

function! s:to_global_name(scriptlocal_funcname) abort
  return '<SNR>' . s:sid . '_' . a:scriptlocal_funcname
endfunction

" 適当なイベントが発生したら,補完関数を消去
function! s:delete_function_lazy(funcname) abort
  execute 'autocmd CompleteDummy CursorHold,CursorHoldI,CursorMoved,CursorMovedI,InsertEnter *'
        \ 'delfunction' a:funcname
        \ '| autocmd! CompleteDummy CursorHold,CursorHoldI,CursorMoved,CursorMovedI,InsertEnter *'
endfunction

if neobundle#load_cache()
  NeoBundleLazy 'mattn/gist-vim'
  NeoBundleLazy 'osyo-manga/vim-reanimate'

  " キャッシュされる設定はこっちに書く
  if neobundle#tap('gist-vim')
    call neobundle#config({
          \ 'autoload': {
          \   'commands': {'name': 'Gist', 'complete': 'customlist,' . s:to_global_name('gist_CompleteArgs')}
          \ }
          \})
    call neobundle#untap()
  endif

  if neobundle#tap('vim-reanimate')
    let s:_ = 'customlist,' . s:to_global_name('reanimate_save_point_completelist')
    call neobundle#config({
          \ 'autoload': {
          \   'commands': [
          \     {'name': 'ReanimateSave', 'complete': s:_},
          \     'ReanimateSaveCursorHold',
          \     'ReanimateSaveInput',
          \     {'name': 'ReanimateLoad', 'complete': s:_},
          \     'ReanimateLoadInput',
          \     'ReanimateLoadLatest',
          \     {'name': 'ReanimateSwitch', 'complete': s:_},
          \     {'name': 'ReanimateEditVimrcLocal', 'complete': s:_},
          \     'ReanimateUnLoad'
          \   ]
          \ }
          \})
    unlet s:_
    call neobundle#untap()
  endif
  NeoBundleSaveCache
endif
call neobundle#end()

" キャッシュされないグローバル変数の設定等はこっち
if neobundle#tap('gist-vim')
  " plugin/gist.vim を見てコピペ
  function! s:gist_CompleteArgs(arg_lead,cmdline,cursor_pos) abort
    return filter(["-p", "-P", "-a", "-m", "-e", "-s", "-d", "+1", "-1", "-f", "-c", "-l", "-la", "-ls", "-b",
          \ "--listall", "--liststar", "--list", "--multibuffer", "--private", "--public", "--anonymous", "--description", "--clipboard",
          \ "--rawurl", "--delete", "--edit", "--star", "--unstar", "--fork", "--browser"
          \ ], '!stridx(v:val, a:arg_lead)')
  endfunction
  function! neobundle#tapped.hooks.on_post_source(bundle) abort
    delfunction s:gist_CompleteArgs
  endfunction
  call neobundle#untap()
endif

if neobundle#tap('vim-reanimate')
  " plugin/reanimate.vim を見てコピペ
  function! s:reanimate_save_point_completelist(arglead, ...) abort
    return filter(reanimate#save_points(), "v:val =~? '" . a:arglead . "'")
  endfunction
  " neobundle#tapped.hooks.on_source() でもいい
  function! neobundle#tapped.hooks.on_post_source(bundle) abort
    " 一時的な補完関数の中でautoload関数をコールをしているので,
    " このタイミングで補完関数は消去できない
    " なので,適当なタイミングで一時的な補完関数を消去する
    call s:delete_function_lazy('s:reanimate_save_point_completelist')
  endfunction
  let g:reanimate_save_dir = '~/.vim/save'
  let g:reanimate_default_save_name = 'reanimate'
  let g:reanimate_sessionoptions = 'curdir,folds,help,localoptions,slash,tabpages,winsize'
  call neobundle#untap()
endif

if !has('vim_starting')
  call neobundle#call_hook('on_source')
endif
filetype plugin indent on