VimでObjective-Cのコード補完を実行する with clang

2013年1月2日
objc / vim / ios /

概要

VimやEmacsでiOSアプリ開発をするときに「Snippetとかちゃんと設定してればXcodeほどのコード補完は必要ない」と強がりを言ってはみるものの「本当はちょっとコード補完使いたいときあるんだよね」と思ってました。

そこで、重い腰をあげてVimでもObjective-Cのコード補完ができるよう設定してみました。

具体的には、

  1. clangコマンドでのコード補完を試してみる
  2. その結果をもってVimの clang_complete プラグインを導入する

という手順で実施しました。

結果として満足いく補完環境が整いましたので紹介させていただきます。

clangコマンドでのコード補完を試してみる

clangコマンドはXcodeを使っていればはじめっから入っているコマンドです。
じつはこのclangコマンドを使うことでObjective-Cのコード補完が普通にできるとのこと。

具体的には、

// clang -cc1 -code-completion-at=ソースコード.m:行数:列数 ソースコード.m
clang -cc1 -code-completion-at=Sample.m:20:5 Sample.m

というコマンドになります。
例えば、

#import "MainViewController.h"

@implementation MainViewController

- (void)viewDidLoad
{
  [super viewDidLoad];

  NSArray* numbers = @[@1, @2, @3];
  [numbers o
}

@end

[numbers o のところで補完をしたいとして、

clang -cc1 -code-completion-at=Classes/Controllers/MainViewController.m:16:12 Classes/Controllers/MainViewController.m 

というコマンドを実行してみます。
すると COMPLETION: ではじまる行が幾つか表示され、なんらか補完結果が出ているのが分かります。

同時に、xxx warnings and xxx errors generated. とエラーが発生しているのも確認できるかと思います。

clangによる補完時のオプション

このエラーは、clangに対するオプションの不足によるものです。 そこでclangに対して以下のオプションを設定してあげます(※環境依存なので適宜読み替えてください)

この他、独自のヘッダーファイルがある場合は -I の設定を加える必要があります。

これらのオプションを指定してclangを実行してみると、

clang -cc1 -code-completion-at=Classes/Controllers/MainViewController.m:16:12 Classes/Controllers/MainViewController.m -w -fblocks -fobjc-arc -D __IPHONE_OS_VERSION_MIN_REQUIRED=40300 -include ./**/*-Prefix.pch -F /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator6.0.sdk/System/Library/Frameworks -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator6.0.sdk/usr/include 

無事にerrorなしで補完候補を引き出すことが出来ました。

clang_completeプラグインの導入

あとは clang_completeプラグイン を導入して、上記で試したオプションを設定してあげれば、無事にVim上でObjective-Cのコード補完が出来るようになります。

なお、自分の場合は neocomplcache と共用する設定をしています。

Bundle 'git://github.com/Shougo/neocomplcache.git'
Bundle 'git://github.com/Rip-Rip/clang_complete.git'
if !exists('g:neocomplcache_force_omni_patterns')
  let g:neocomplcache_force_omni_patterns = {}
endif
let g:neocomplcache_force_overwrite_completefunc = 1
let g:neocomplcache_force_omni_patterns.c =
  \ '[^.[:digit:] *\t]\%(\.\|->\)'
let g:neocomplcache_force_omni_patterns.cpp =
  \ '[^.[:digit:] *\t]\%(\.\|->\)\|\h\w*::'
let g:neocomplcache_force_omni_patterns.objc =
  \ '[^.[:digit:] *\t]\%(\.\|->\)'
let g:neocomplcache_force_omni_patterns.objcpp =
  \ '[^.[:digit:] *\t]\%(\.\|->\)\|\h\w*::'
let g:clang_complete_auto = 0
let g:clang_auto_select = 0

※2013/1/14 追記
**ここから先の話は、 次の記事 でもっと簡単な方法も紹介しています

-w
-fblocks
-fobjc-arc
-D __IPHONE_OS_VERSION_MIN_REQUIRED=40300
-include ./**/*-Prefix.pch
-F /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator6.0.sdk/System/Library/Frameworks
-I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator6.0.sdk/usr/include

特に最後の .clang_complete ファイルが重要です。
clangのオプションは .vimrc でも指定できるのですが、こいつを利用することでプロジェクトごとに違うオプションを設定できるようになります(実用時にかなり有用)。

実際にコード補完の実行

すべての設定が完了したら、実際に先ほどの

#import "MainViewController.h"

@implementation MainViewController

- (void)viewDidLoad
{
  [super viewDidLoad];

  NSArray* numbers = @[@1, @2, @3];
  [numbers o
}

@end

[numbers o の直後で補完を実行してみます。
上記と全く同じ設定をしているならInsertモード時に Ctrl+x Ctrl+o or Ctrl+x Ctrl+u で実行できるはずです。

clang_completion

ぼくの手元では、きっちり「oからはじまるNSArrayのインスタンスメソッドが一覧表示」されました!

より便利に使うためのオプションを追加

なお、実際に使ってみると.clang_completeに全てのディレクトリを追加するのが面倒臭くなってきます。
例えば、Classes/XXX/XXX/Views/というディレクトリにあるヘッダーファイルを使っている場合、

-I Classes/XXX/XXX/Views

をわざわざ.clang_completeに加えなければいけません。
1つなら良いのですがこれがどんどん増えていくようなら.clang_completeのメンテナンスのせいでコーディングリズムが崩れることになりかねません。

例えば、 カレントディレクトリ以下を再帰的に全て -I に追加してくれるオプション があればいいのに!
と思ったのですが少なくとも自分が調べて限りでは見つかりませんでした。

ということでclang_completeをForkして自分で作りました。
ひとまず今は、 tokorom/clang_complete を使っていただければこのオプションが使えます。

以下、具体的な設定値です。

#Bundle 'git://github.com/Rip-Rip/clang_complete.git'
Bundle 'git://github.com/tokorom/clang_complete.git'
let g:clang_complete_include_current_directory_recursively = 1

これでカレントディレクトリ以下のヘッダーファイルであれば買ってにインクルードされるようになります!

Related Entries
Latest Entries