#前置 (plugin の話はありません)
mattn さんのコメントを受けて記事を修正しました。
Vim でプログラムを編集していると、バッファ全体に外部コマンドを適用したい時があります。たとえば、python の autopep8 や c/c++ の clang-format など。
これらのフォーマッタには整形範囲を限定するオプションがあり、ここは手動で整形したい、ここは機械的な整形で、と使い分けができます。
autopep8 --line-range 100 110
clang-format -lines=100:110
ところで、これらのコマンドは部分的な整形であっても、入力にはファイル全体を渡す必要があります。そうすると Vim からは :%!
を使って、
:%!autopep8 --line-range 100 110 -
とすれば、一応目的を達します。
問題はここから・・・
#問題1. 範囲指定が面倒だ。
これは、a:firstline
a:lastline
(:h func-range
) を使った関数を定義し、visual モードの選択範囲を渡せばよいので、割合簡単に解決しました。
" 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 ブロックにつなげる。」) が正解に近そうな感じだったので、試行錯誤の結果、このようになりました。
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>"
という力技、なんとかならないかな・・・
力技でもなんでもなく、マニュアル記載のやり方でした。mattn さんありがとうございます。
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