Vundle 何時才會集滿 pull request

以 plugin 管理工具來說,Vundle 概念清楚、使用穩定、開發品質高(不是一個 toy project),名字也漂亮(重要); 比較不滿的是開發進度,已確認的 pull request 等了很久也沒 merge 回去,享受不到,著急。

這次整理 plugin 覺得卡在 Vundle 蠻悶的,就變節 NeoBundle 去了。

NeoBundle 是什麼

NeoBundle 的開發者是 neocomplcacheunite.vim 的作者 Shougo,他的 Vundle fork 改了太多東西,就獨立出來了。
使用上除了 :BundleInstall 的花俏畫面不見外,其他沒什麼損失; 新增功能(部份是實驗性的)則包括:

  • 鎖定 plugin 版本,有新版也不更新
    不想一直踩地雷的話,這個會很實用:
    NeoBundle 'Shougo/vimshell', '3787e5'
  • 支援 svn 和 Mercurial (hg) repository
    使用 type 參數指定 svnhg,也能從 URL 自動判斷:
    NeoBundle 'some.hg.repo.url', {'type': 'hg'}
    其實 Vundle 也有實作,但目前未採用,理由參考 Pull Request #134: Adding more DVCS support by ralesi · gmarik/vundle, 比較傾向將來以 Vundle plugin 的方式實現。
  • 自動使用 vimproc
    如果有裝 vimproc,就會用 vimproc 執行外部指令。(這個我沒用過)
  • unite.vim 介面
    用途類似 Vundle 的 interactive mode,另開 window 瀏覽 plugin,可以各別按 i 進行 Install 等操作。
    我認為 unite.vim 介面比較親切,因為不必學特殊 key mapping。
    註::Unite neobundle 基本上需要裝 vimproc,否則只有 neobundle/log、neobundle/lazy 和 neobundle/search 可用。
  • base 和 nosync 選項
    base 可以把特定 plugin 安裝在不一樣的目錄,nosync 可以讓 plugin 不自動更新,變成很例外的處理。
    NeoBundle 'muttator', {'type': 'nosync', 'base': '~/.vim/bundle'}
    我發現測試 local plugin 的時候很好用,後述。
  • build 選項
    安裝時可以自動執行指定的 script,如果 plugin 需要編譯就會有用:
    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',
          \    },
          \ }
    Vundle 作法是提供 callback 介面,不過目前還放在 events branch。
  • Lazy Load
    少用的 plugin,可以先不加進 runtimepath 以縮短啟動時間。
    NeoBundleLazy 'klen/python-mode'
    需要的時候再用 :NeoBundleSource 讀進來:
    autocmd FileType python NeoBundleSource python-mode
    或許可以用 autocommand FuncUndefined 達成自動 source 的功能。
  • 其他看不見的變更
    相信 NeoBundle 還重寫了很多東西,比如「改善 :NeoBundle 的效率」。 不過這些東西在 doc 甚至 commit log 都不很清楚,其實也是個問題。

忘掉安裝程序

從 Vundle 轉換到 NeoBundle,大致上把一些目錄、指令改名即可,安裝過程幾乎沒變。

不過整個流程還是太特殊/脫序了,我不想每次查文件,所以乾脆把檢查寫進 .vimrc,若有問題,直接提示我該怎麼做。

  1. 設定一個目錄放所有 plugin,通常是 ~/.vim(我會視環境調整)下的 bundle 目錄。
    (之後我會把預設的 runtimepath (~/.vim) 砍掉,讓 Vim 只吃 bundle 目錄裡的 script)
    然後把 neobundle.vim 加進 runtimepath,才能繼續安裝。
    if has("gui_win32")
      " g:gvim_rtp should be set by gvimrc
      if exists('g:gvim_rtp') && isdirectory(fnamemodify(g:gvim_rtp, ':p'))
        let s:rtp = g:gvim_rtp
      else
        echoerr "You must set runtimepath (for plugin bundling) manually."
        finish
      endif
    else
      let s:rtp = "~/.vim"
    endif
    
    if !isdirectory(fnamemodify(s:rtp, ':p'))
      try
        call mkdir(fnamemodify(s:rtp, ':p'), "p")
      catch /^Vim\%((\a\+)\)\=:E739/
        echoerr "Error detected while processing: " . v:throwpoint . ":\n  " . v:exception .
              \  "\nCan't make runtime directory. Skipped sourcing vimrc.\n"
        finish
      endtry
    endif
    
    execute "set runtimepath+=" . fnamemodify(s:rtp, ':p') . "bundle/neobundle.vim"
  2. 呼叫 neobundle#rc
    失敗的話就是沒裝 NeoBundle,請提示我 git clone 到剛設定的目錄。
    try
      call neobundle#rc(fnamemodify(s:rtp, ':p') . "bundle")
    catch /^Vim\%((\a\+)\)\=:E117/
      echoerr "Error detected while processing: " . v:throwpoint . ":\n  " . v:exception .
            \ "\n\nNo 'Bundle plugin' installed for this vimrc. Skipped sourcing plugins." .
            \ "\n\nTo install one:\n  " .
            \ "git clone http://github.com/Shougo/neobundle.vim.git " . fnamemodify(s:rtp, ":p") . "bundle/neobundle.vim\n"
      finish
    endtry
    註:步驟 1、2 要包進 if has('vim_starting') ... endif 才不會每次讀 .vimrc 時都做。
  3. filetype off。 這個步驟我沒研究為什麼 doc 說必須做(而且要在一開始做?),拿掉好像也沒事。
    接著移除預設 runtimepath。
    filetype off
    set runtimepath-=~/.vim
  4. 列出要裝的 plugin,最後再把 filetype 開回來。
    NeoBundle 'Shougo/neobundle.vim'
    NeoBundle 'nginx.vim'
    NeoBundle '有幾個 plugin,這裡就加幾行'
    filetype plugin indent on
    註:實際上我不在這做 :NeoBundle,而是用後述 s:bundles 處理。
  5. 這樣就完成 NeoBundle 設定了。
    進入 Vim 執行 :NeoBundleInstall 把 plugin 都裝起來。
    安裝後 :NeoBundleLog 可以看 log,個人感覺比 Vundle 的輸出有用。

自己控制 bundle 列表

把舊的 :Bundle 全部換成 :NeoBundle 也是很空虛的動作,所以我把 plugin 列表寫成陣列,這樣即使回鍋 Vundle 也比較好改。

  1. 建立 s:bundles 變數,就可以自訂選項,減少對 bundle plugin 的依賴。
    let s:bundles = [
          \   ['Shougo/neobundle.vim'],
          \   ['L9'],
          \ ]
    let s:bundles += [
          \   ['bootleq/vim-gitdiffall', {":prefer_local": 1}],
          \   ['nginx.vim'],
          \ ]
  2. 此處的自訂選項有 :skip:prefer_local
    :skip 就是不要裝,因為寫成陣列就不能直接註解掉一行了……
    :prefer_local 則是為了測試 plugin 方便,如果這個 plugin 有一份 repository 裝在 ~/repository,那就是我自己亂改的版本,請 NeoBundle 還是要讀它,但更新的時候要跳過。
    for bundle in s:bundles
      let s:tmp_options = get(bundle, 1, {})
      if get(s:tmp_options, ":skip")
        continue
      elseif get(s:tmp_options, ":prefer_local")
        if isdirectory(fnamemodify('~/repository/' . split(bundle[0], '/')[-1], ':p'))
          let s:tmp_options = {"base": '~/repository', "type": "nosync"}
        endif
      endif
    
      call filter(s:tmp_options, "v:key[0] != ':'")
      execute "NeoBundle " . string(bundle[0]) . (empty(s:tmp_options) ? '' : ', ' . string(s:tmp_options))
    endfor
    unlet! s:bundles s:tmp_options

Windows 沒有 git 怎麼辦

其實這次整理 plugin 最想做的是更新 Windows 上的 bundle,結果沒什麼進展。

最後做法是:

  1. 從有 git 的環境複製 .vimrc 和整個 bundle 目錄。
  2. gVim 的 vimrc 去 :source 真正使用的 .vimrc。