Mercurialのコミットメッセージをvimで編集するためのあれこれ

hg commitを実行すると、コミットメッセージを編集するためのエディタが起動します。
このエディタにvimを使う場合に、いくつかの設定を行うことでプラスアルファの機能を追加しようという話です。

ありていに言うと [committia](https://github.com/rhysd/committia.vim) がすてきな感じなのでMercurialでも同じようなことができないか、という。

## コミット対象のファイルを開く

### そのまま開く
コミット時には、以下のようなテキストが一時ファイルに出力され、オープンされます。

```text
HG: コミットログを入力してください。'HG:' で始まる行は無視されます。
HG: メッセージが空のままならコミットを中止します。
HG: --
HG: ユーザ: Iwata
HG: ブランチ 'default'
HG: tortoisehg/hgqt/repotreeitem.py を変更
HG: tortoisehg/hgqt/run.py を変更
```

このように、コミット対象のファイルがテキスト上に最初から表示されているので、そこにカーソルを合わせて ``<C-w>f``、``<C-w>gf`` などとすることで簡単にそのファイルを開くことができます。

が、カレントディレクトリがリポジトリルートになっていないとこれは使えません。

Mercurial自体はリポジトリルートをカレントディレクトリに設定した状態でエディタを起動してくれるのですが、Windows版のvimだと、ファイルパスを引数に与えて起動した場合カレントディレクトリがそのファイルの親ディレクトリに変わってしまうようです。
なので、この挙動を回避するために、vim/gvimを直接起動する代わりにこんな感じのバッチを起動するようにします。

```bat:hgcommit.bat
gvim -c ":e %1"
```

### unified diffを開く
以下のようなfunctionを用意して、ファイルパス上での ``;d`` でunified形式のdiffを表示できるようにします。(同じタブ内でウインドウを分割して表示します)

```vim:hgcommit.vim
" カーソル下のファイルに対するdiffをunified diff形式で表示
nmap <buffer> ;d :call ShowHgDiffUnified(expand('<cfile>'))<CR>

function! s:find_window(pattern)
  for i in range(1, winnr('$'))
    if bufname(winbufnr(i)) =~ a:pattern
      return i
    endif
  endfor
  return 0
endfunction

function! ShowHgDiffUnified(path) abort
  let win = s:find_window('^\[hg diff\]')
  if win == 0
    botright vnew
  else
    execute win . 'wincmd w'
  endif
  setlocal filetype=diff buftype=nofile modifiable
  silent! %delete
  silent! execute 'file [hg diff] ' . fnamemodify(a:path, ':t')
  silent! execute 'r!hg diff ' . a:path
  setlocal nomodifiable
endfunction
```

* この辺のvimscriptはMercurialのコミットメッセージを編集する時だけ読み込まれればいいので、起動用バッチで ``gvim -c ":e %1" -S hgcommit.vim`` みたいに指定するか、 ``autocmd FileType hgcommit`` に引っかければいいと思います。

### side by side diffを開く
さらに以下の設定で、同じく ``;D`` で別タブにvimdiffを表示できるようにします。

```vim:hgcommit.vim

" カーソル下のファイルに対するvimdiffを新しいタブに表示
nmap <buffer> ;D :call ShowHgDiffSideBySide(expand('<cfile>'))<CR>

function! ShowHgDiffSideBySide(path) abort
  execute 'tabedit ' . a:path
  let encoding = &encoding
  vnew
  setlocal modifiable buftype=nofile
  execute 'setlocal fileencoding=' . encoding
  silent! execute 'file ' . fnamemodify(a:path, ':t')
  silent! execute 'r!hg cat ' . a:path
  silent! 1delete _
  execute 'doautocmd BufNewFile ' . a:path
  setlocal nomodifiable
  windo diffthis
endfunction
```

## コミットメッセージの挿入
GUIクライアントだと、最近入力したコミットメッセージを一覧から選択して挿入する機能があったりします。
正直あまり使いませんが、せっかくなのでこれもUnite sourceとして実装しておきましょう。

```vim:hgcommit.vim
" 最近のコミットメッセージを表示するUnite source
let s:unite_last_messages = {
            \ 'name' : 'last-messages',
            \ 'default_action' : { '*' : 'insert' },
            \ }

function! s:unite_last_messages.gather_candidates(args, context) abort
  let log = system('hg log -r "last(all(), 20)" --template "{desc}\n___SEP___\n"')
  let encoded = iconv(log, 'cp932', &encoding)
  let splitted = split(encoded, '\n___SEP___\n')
  return map(splitted, '{
            \ "word": split(v:val, "\n")[0],
            \ "source": "last-messages",
            \ "kind": "word",
            \ "action__text": v:val,
            \ }')
endfunction

call unite#define_source(s:unite_last_messages)
unlet s:unite_last_messages

nmap <buffer> ;m :Unite last-messages<CR>
```

これは単純に最新20コミットを対象にして検索する例ですが、「authorが自分」などの条件を加えてもいいかもしれません。
Windows + win32mbcs環境を前提にしているので文字コード変換(iconv)をかけていますが、Linux環境でvimのencodingもutf8ならこれは不要です。

## TODO
### commit --amendへの対応
diffの機能がcommit --amend時には無力なので、そこを何とかする。
と言ってもエディタ側からは通常のコミットなのかamendなのかは分からないので、両方のモードを用意して手動でスイッチする感じ？