無数のパッチを組み合わせて君だけの MacVim を作ろう!

この記事は Vim Advent Calendar 2013 の 9 日目の記事です. 昨日は id:daisuzu さんの Fabricで最新のVimをバラまく でした.僕はサーバでの Vim で自前の環境使うのは諦めているので,すごいなぁと感心するばかりです.

先日開催された VimConf2013 にて,パッチ職人である @kaoriya さんや @k_takata さんの発表があり,Vim にはマージされていない多くの便利パッチがあることが分かりました. そこで,この記事ではそれらの公開されているパッチを MacVim に自前で取り込み,ビルド,メンテしていく方法を考えていきたいと思います.

ほしいパッチを探す

まずは適用したいパッチが無ければ話にならないので,公開されているパッチを漁ります. 例えば,以下のリンクが参考になります.

今回は takata さんの breakindent パッチ と kaoriya さんの autoload キャッシュパッチ,Shougo さんの 補完候補を挿入しないようにするパッチ が気になるので導入してみようと思います.

Homebrew の Formula を書く

自分でパッチを当てた MacVim の管理をするために,Mac のパッケージマネージャである Homebrew を使います. Homebrew では,Formula と呼ばれるビルドスクリプトを Ruby で書くことでパッケージを追加できます. Formula の書き方は Formula Cookbook などに載っていますが,ここではパッチを追加するだけなので Ruby の知識すらほぼ必要ありません. Homebrew に元々ある /usr/local/Library/Formula/macvim.rb をコピーしてきてそれにパッチを追加します.

ファイル名がそのままパッケージ名になるため,まずはファイル名を変更します.俺専用なので,ここでは macvim-rhysd.rb とします. さらに,ファイルを開き Formula 名(Formula クラスを継承しているクラスの名前)を変更します. これはファイル名と対応していて,ここでは MacVimMacVimRhysd に変更します.

既に Homebrew のデフォルトの MacVim をインストールしている場合,実行ファイル名が被っていてエラーになってしまうので,既に MacVim をインストール済みでないか確認するためのコードを追加します.

conflicts_with 'macvim',
  :because => 'This formula installs my patched version of MacVim.'

既に MacVim をインストールしている場合は brew unlinkbrew uninstall します.

最後にパッチを定義します. 適用するパッチを指定するには Formula クラスの patches メソッドをオーバーライドします.

  def patches
    {
      :p1 => [
              'https://bitbucket.org/k_takata/vim-ktakata-mq/raw/98482edd59b30091f30371dcadad4e3ffcc132be/vim-7.4.035-breakindent.patch',
              'https://bitbucket.org/koron/vim-kaoriya-patches/raw/6658116d59073a4471a83fea41a0791718773a96/X010-autoload_cache.diff',
              'https://gist.github.com/Shougo/5654189/raw'
             ]
    }
  end

patches メソッドはパッチ一覧の Hash を返します.キーはパッチを適用する階層(patch コマンドのオプション)を指定し,その階層で適用するパッチを Array で指定します.パッチは文字列でそのまま与えても良いですし,パッチが公開されている URL を指定しても良いです.文字列でそのまま与える場合は,DATA__END__ を使ってファイル末尾にパッチを書くのが一般的です.

Formula に書くべきはこれだけで,あとは好きなオプションでインストールします.

brew install path/to/macvim-rhysd.vim --HEAD --with-scope --with-luajit --override-system-vim

brew install はパッチの適用に失敗してもそのまま続行されてしまうので,パッチが正しく当たったかどうかをログで確認しましょう. これだけでお気に入りのパッチを適用した MacVim が出来上がりました.

パッチ適用済み Vim 用の設定を書く

パッチを適用した Vim を手に入れたら,新しく有効になった機能用の設定を(必要であれば)書きます. ここで大切なのは,パッチを適用していない Vim であっても問題ないように書くことです. 特定のオプションや変数,関数などを追加するパッチの場合は exists() を使って判別できます.

if exists('+breakindent')
    set breakindent
endif

さらに,特定の feature を追加するパッチなら has() で判定できます.最近だと timers を追加するパッチが熱いようですね.

判別する方法が無い場合は catch を使って例外を捕まえることでエラーが出るのを回避します.

try
    set completeopt-=noselect
    set completeopt+=noinsert
    let g:neocomplete#enable_auto_select = 1
    let g:neocomplete#enable_complete_select = 1
catch /^Vim\%((\a\+)\)\=:E474/
    " when the patch isn't applied
endtry

この例では,パッチを当てていない Vim ではパッチで追加される noselectnoinsert が存在しないため,E474 が発生するのでそれを catch しています.

書いた Formula のメンテナンス

まだ書いた Formula は Homebrew で管理されていません.自分で書いた Formula を Homebrew で管理するには,任意のリポジトリを Homebrew の管理対象に追加する tap というサブコマンドを追加します. ここでは macvim-rhysd.rb を追加した https://github.com/rhysd/homebrew-rhysd というリポジトリを tap します.

brew tap rhysd/homebrew-rhysd

さらに,参考にした本家の MacVim の Formula(macvim.rb)が更新された場合は自分の手元のものも更新したいです.その場合は macvim.rb の変更を git diff で取り,それを手元の macvim-rhysd.rb に適用します.自動化したい場合は,git の hook を使って brew update したときに macvim.rb の変更をチェックするようにすれば良いと思います.

まとめ

これであんなパッチやこんなパッチが適用された俺専用 MacVim が簡単に導入できる環境が整いました. 今回書いた Formula はこちらで公開しています.

なお,俺専用 MacVim は他の誰も使ったことの無い MacVim です.パッチはすべて experimental でそれ自身がバグを含んでいる可能性がある上,他のパッチと競合する可能性もあるので注意が必要です.

今回は Mac 環境についてでしたが,Arch Linux の abs 用の PKGBUILD 版も気が向いたら書きます.

明日は @alpaca_taichou さんです.よろしくお願いします.