C や C++ のコードを自動で整形する clang-format を Vim で

この記事は Vim Advent Calendar 269 日目の記事になります.

きつねさんとおぼえる!Clang という本を読んでいて,clang 周りで色々役立ちそうなツールがあることを知ったので,その中でも C や C++ソースコードを整形する clang-format というツールの Vim への応用について書きます.

clang-format とは

clang-format とは,入力した C や C++Objective-C は未確認)のソースコードを一定のルールに従って自動整形して出力してくれるツールです.clang の静的解析機能を使っています.インストールは LLVM project の clang のサイトを参照してください.

例えば,次のようなソースコードがあったとします.

#include <iostream>

#define TEST_CPP_FOR_OPERATOR_CLANG_FORMAT_LONG_MACRO " CPP for vim-operator-clang-format"

void f(){ std::cout << "hello\n"; }

int main()
{
    int * hoge = {1,3,5,7};
    for(int i=0;i<4;++i){
        if(i%2==0) std::cout << hoge[i] << std::endl;
    }

    std::cout << "this is very very long one-line string. so this line go over 80 column .\n";

    std::cout << "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:";
    return 0;
}

これを test.cpp として保存し,clang-format test.cpp とすると次のような出力を得られます.

#include <iostream>

#define TEST_CPP_FOR_OPERATOR_CLANG_FORMAT_LONG_MACRO                          \
  " CPP for vim-operator-clang-format"

void f() { std::cout << "hello\n"; }

int main() {
  int *hoge = { 1, 3, 5, 7 };
  for (int i = 0; i < 4; ++i) {
    if (i % 2 == 0)
      std::cout << hoge[i] << std::endl;
  }

  std::cout << "this is very very long one-line string. so this line go over "
               "80 column .\n";

  std::cout
      << "Permission is hereby granted, free of charge, to any person "
         "obtaining a copy of this software and associated documentation files "
         "(the \"Software\"), to deal in the Software without restriction, "
         "including without limitation the rights to use, copy, modify, merge, "
         "publish, distribute, sublicense, and/or sell copies of the Software, "
         "and to permit persons to whom the Software is furnished to do so, "
         "subject to the following conditions:";
  return 0;
}

ポインタの*の配置の調整や , 間および for 文への半角スペースの追加,長すぎる行の自動分割(デフォルトで1行80文字以内)といった整形が行われています.これ以外にも様々な箇所で整形を行ってくれます.文字列中の単語まで考慮して整形してくれるのが良い感じです. また,-i オプションをつけるとソースコードを直接書き換えることもできます. コマンドの詳細は $ clang-format -help で確認できます

clang-format のコーディングスタイル

clang-format は一定のコーディングスタイルに従ってコードを整形します.-style オプションで指定することが出来,LLVM, Google, Chromium, Mozilla が指定可能です.

$ clang-format -style=Google hoge.cpp # Google coding style でフォーマット

さらに細かくコーディングルールをカスタマイズする

実際にコーディングしていると,自分のコーディングスタイルが上記のコーディングスタイルにうまく当てはまらないことがあります.その場合は,-style オプションに辞書を渡すことでより細かいスタイルの設定が可能です. 例えば,タブ幅は空白4文字分が良く,C++11 の機能を使っている場合は次のようにします.

$ clang-format -style="{BasedOnStyle: Google, IndentWidth: 4, Standard: C++11}" hoge.cpp

デフォルトの設定値がどうなっているかは $ clang-format -dump-config で確認することができます. どういう設定項目と設定値があるかについてのドキュメントはちょっと面倒ですが現状だとソースコードのドキュメントを見に行くのが良さそうです.

clang::format::FormatStyle Struct Reference

clang-format の自動整形機能を Vim のオペレータとして使う

前節で書いたように,clang-format は基本的にファイル単位での整形を行います.(一応 -line オプションで整形する行を制御できますが,いちいち指定するのは面倒です) しかし,実際はソースコードに合わせて部分的に手動で整形したかったり,気に入らない箇所だけ自動で整形したい場合も多いです. 僕の場合は,基本的に整形は手で,その補助的なツールとして clang-format を使いたいというモチベーションがありました. そこで,Vim で編集中の C や C++ソースコードをインタラクティブに整形できる Vim プラグインがあると良いと考えました.

Vim には オペレータとテキストオブジェクトという便利な機能があり,テキストオブジェクトで指定した範囲にオペレータで指定した処理を行うことが出来ます.詳しくは :help operator:help text-object で確認することが出来ますが,日頃 diw などのキー入力で使っているものです(d は削除用オペレータ,iw は inner word を指定するテキストオブジェクト). 今回はテキストオブジェクトで指定された範囲を clang-format で整形するオペレータ,vim-clang-format をつくってみました.

vim-clang-format - Github

kana さんの vim-operator-user を利用しているので,こちらも導入しておく必要があります. インストール後は <Plug>(operator-clang-format) を好きなキーにマッピングします.

map ,x <Plug>(operator-clang-format)

,x に割り当ててみると,例えば次のように編集中の C や C++ ソースを整形できます.

  • ビジュアルモードで対象範囲を選択し,,x で整形
  • ,xi{ で括弧内を整形
  • vim-textobj-entire を使っている場合,,xieソースコード全体を整形
  • vim-textobj-indent を使っている場合,,xiiで同じインデントレベルのブロックを整形

整形はすべて行指向(line-wise),つまり行単位で行われるので注意してください.

スクリーンショット

下記の設定済みの Vim での整形の様子です.どの範囲を整形しているのかわかりやすくするためにビジュアルモードで予め選択範囲を示しました. screenshot

vim-clang-format のカスタマイズ

vim-clang-format はタブ幅やタブ文字使用の有無を Vim の設定から自動で取得するようになっています. しかし,前節でも書いたように,コーディングスタイルはさらに細かく設定することが出来ます. 一応READMEでも触れていますが,help ドキュメントはまだ書けていないのでここでも紹介しておきます.

  • g:clang_format#command

g:clang_format#command には clang-format の実行ファイルの場所を設定します.デフォルト値は "clang-format" です.

  • g:clang_format#code_style

g:clang_format#code_style には上記の4種類のコーディングスタイルのうち1つを指定します.デフォルト値は "Google" です.

  • g:clang_format#style_options

g:clang_format#style_options にはコーディングスタイルのより細かい設定を書きます.上記では -style オプションに辞書で設定したものを Vim script の辞書型の値で指定します.デフォルトは {} です.

  • g:clang_format#extra_args

上記以外の設定が必要な場合は g:clang_format#extra_args に文字列を設定します.この文字列は clang-format の引数に直接渡されます.デフォルトは "" です.

設定例

僕の設定例です.

map <silent><Leader>x <Plug>(operator-evalruby)

" アクセス指定子は1インデント分下げる
" 短い if 文は1行にまとめる
" テンプレートの宣言(template<class ...>)後は必ず改行する
" C++11 の機能を使う
" {} の改行は Stroustrup スタイル(関数宣言時の { のみ括弧前で改行を入れる)
let g:clang_format#style_options = {
            \ "AccessModifierOffset" : -4,
            \ "AllowShortIfStatementsOnASingleLine" : "true",
            \ "AlwaysBreakTemplateDeclarations" : "true",
            \ "Standard" : "C++11",
            \ "BreakBeforeBraces" : "Stroustrup",
            \ }

ちなみに,この設定で先ほどの test.cpp 全体をフォーマットすると次のようになります.

#include <iostream>

#define TEST_CPP_FOR_OPERATOR_CLANG_FORMAT_LONG_MACRO \
    " CPP for vim-operator-clang-complete"

void f()
{
    std::cout << "hello\n";
}

int main()
{
    int* hoge = {1, 3, 5, 7};
    for (int i = 0; i < 4; ++i) {
        if (i % 2 == 0) std::cout << hoge[i] << std::endl;
    }

    std::cout << "this is very very long one-line string. so this line go over "
                 "80 column .\n";

    std::cout
        << "Permission is hereby granted, free of charge, to any person "
           "obtaining a copy of this software and associated documentation "
           "files (the \"Software\"), to deal in the Software without "
           "restriction, including without limitation the rights to use, copy, "
           "modify, merge, publish, distribute, sublicense, and/or sell copies "
           "of the Software, and to permit persons to whom the Software is "
           "furnished to do so, subject to the following conditions:";
    return 0;
}

まとめ

  • clang-format は自動で C や C++ のコードを整形してくれるツールで,コーディングスタイルはそれなりに細かくカスタマイズできます
  • Vim で対象範囲を整形してくれるプラグイン vim-clang-format を作成しました
  • 細々とした機械的なコード整形を手で行うこと無くこなせます

for 文や , のスペースについて,以前はスニペットや vim-smartinput を使っていましたが,適当に書いて後から整形するという手段が増えました. clang には他にも色々な静的解析ツールやライブラリ,プラグイン機構があるので,それらを使ってもっと C や C++ でのコーディングを楽にしていけたら良いなぁと考えています.