Vimの undojoin で :%!autopep8 や :%!clang-format を快適に

#前置 (plugin の話はありません)

<font color="blue">mattn さんのコメントを受けて記事を修正しました。</font>

　Vim でプログラムを編集していると、バッファ全体に外部コマンドを適用したい時があります。たとえば、python の autopep8 や c/c++ の clang-format など。
　これらのフォーマッタには整形範囲を限定するオプションがあり、ここは手動で整形したい、ここは機械的な整形で、と使い分けができます。

```csh:autopep8
autopep8 --line-range 100 110
```
```csh:clang-format
clang-format -lines=100:110
```
　ところで、これらのコマンドは部分的な整形であっても、入力にはファイル全体を渡す必要があります。そうすると Vim からは ``:%!`` を使って、

```vim:
:%!autopep8 --line-range 100 110 -
```

とすれば、一応目的を達します。
問題はここから・・・

#問題1. 範囲指定が面倒だ。
　これは、``a:firstline`` ``a:lastline`` (``:h func-range``) を使った関数を定義し、visual モードの選択範囲を渡せばよいので、割合簡単に解決しました。

```vim:.vimrc
" autopep8
function! s:FormatAutopep8() range
    let cmd = ':silent %! autopep8 '
        \ .'--line-range '.a:firstline.' '.a:lastline.' '
        \ .'-'
    execute cmd
endfunction

augroup Formater
    au!
    au FileType python
        \ :xnoremap <buffer> <silent> f :call <SID>FormatAutopep8()<cr>
augroup END
```
こうして、visual モードで整形したい部分を選択し、``f`` を押すとその部分だけ整形されるようになりました。が、

#問題2. 実行時や undo/redo 時にカーソルが飛んでいく。
　実行すると、カーソルが1行目へ飛んでしまいます。これは使いづらい。
　また、結果が期待と違っていたからと ``u`` すれば、カーソルが最終行に飛び、さらに ``<c-r>`` してもカーソルが最終行に。この辺りの挙動が何だか良くわからないですが、全然嬉しくない。

　今回の場合、整形前後を目diff (という言葉はあるのかな)するのが小さな楽しみでもあるので、実行時も undo/redo 時もカーソル位置は動いて欲しくないのです。

　実行後のカーソル位置の保存は、`winsaveview()` と `winrestview()` を使えば実現できますが、undo/redo 時にカーソル位置を留めるのが難しい。
　いろいろ調べると `undojoin` (`:h undojoin` 「以降の変更を直前の undo ブロックにつなげる。」) が正解に近そうな感じだったので、試行錯誤の結果、このようになりました。

```vim:.vimrc
function! s:KeepPosExec(cmd)
    let save_view = winsaveview()

    " autopep8 で先頭のスペースが欠ける?
    "silent! execute "normal! a \<bs>\<esc>" | undojoin | execute a:cmd
    " これだとOK
    silent! execute "normal! i \<esc>x" | undojoin | execute a:cmd

    call winrestview(save_view)
endfunction

" autopep8
function! s:FormatAutopep8() range
    let cmd = ':silent %! autopep8 '
        \ .'--line-range '.a:firstline.' '.a:lastline.' '
        \ .'-'
    call s:KeepPosExec(cmd)
endfunction

augroup Formater
    au!
    au FileType python
        \ :xnoremap <buffer> <silent> f :call <SID>FormatAutopep8()<cr>
augroup END
```

　``execute "normal! a \<bs>\<esc>"`` は何もしない、一見意味のないコマンドですが、現在のカーソル位置をundoブロックに記憶させる意味があります。ここに `undojoin` で `execute` を繋げた形、
```silent! execute "normal! a \<bs>\<esc>" | undojoin | execute a:cmd``` では、全体がひとつの undo ブロックとして記憶されます。そしてその起点は `normal!〜`ですから、`u` したとき戻ってくるのは現在位置、という仕組みです。

#結果
こうなりました。同種のコマンドなら同様に対応できそうです。
~~ただ、``"normal! a \<bs>\<esc>"`` という力技、なんとかならないかな・・・~~
<font color="blue">力技でもなんでもなく、マニュアル記載のやり方でした。mattn さんありがとうございます。</font>

```vim:.vimrc
function! s:KeepPosExec(cmd)
    let save_view = winsaveview()

    " autopep8 で先頭のスペースが欠ける?
    "silent! execute "normal! a \<bs>\<esc>" | undojoin | execute a:cmd
    " これだとOK
    silent! execute "normal! i \<esc>x" | undojoin | execute a:cmd

    call winrestview(save_view)
endfunction

" clang-format
function! s:FormatClang() range
    let cmd = ':silent %! clang-format '
        \ .'-lines='.a:firstline.':'.a:lastline.' '
        \ .'-style="{
        \   AccessModifierOffset                : -4     ,
        \   AlwaysBreakTemplateDeclarations     : true   ,
        \   Standard                            : C++11  ,
        \   ColumnLimit                         : 100    ,
        \   BreakBeforeBraces                   : Attach ,
        \   IndentWidth                         : 4      ,
        \   UseTab                              : Never  ,
        \   AllowShortIfStatementsOnASingleLine : false  ,
        \   AlignConsecutiveAssignments         : true   ,
        \   AlignConsecutiveDeclarations        : true   ,
        \   AlignEscapedNewlinesLeft            : true   ,
        \   IndentCaseLabels                    : true   ,
        \ }"'
    call s:KeepPosExec(cmd)
endfunction

" autopep8
function! s:FormatAutopep8() range
    let cmd = ':silent %! autopep8 '
        \ .'--line-range '.a:firstline.' '.a:lastline.' '
        \ .'-'
    call s:KeepPosExec(cmd)
endfunction

augroup Formater
    au!
    au FileType c,cpp,objc,objcpp
        \ :xnoremap <buffer> <silent> f :call <SID>FormatClang()<cr>
    au FileType python
        \ :xnoremap <buffer> <silent> f :call <SID>FormatAutopep8()<cr>
augroup END
```