7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-09-17

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

mattn さんのコメントを受けて記事を修正しました。

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

autopep8
autopep8 --line-range 100 110
clang-format
clang-format -lines=100:110

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

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

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

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

.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 ブロックにつなげる。」) が正解に近そうな感じだったので、試行錯誤の結果、このようになりました。

.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ブロックに記憶させる意味があります。ここに undojoinexecute を繋げた形、
silent! execute "normal! a \<bs>\<esc>" | undojoin | execute a:cmd では、全体がひとつの undo ブロックとして記憶されます。そしてその起点は normal!〜ですから、u したとき戻ってくるのは現在位置、という仕組みです。

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

.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
7
2
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?