Xcode 関数定義からヘッダファイルの関数宣言を生成するプラグインを作ってみた

作成したプラグインの紹介と、プラグイン作成にあたり調べた事についてです。

# 作成したプラグインについて
## 概要
ソースコード中の関数定義からヘッダーファイルに関数宣言を追加します。

![rakufun.gif](https://qiita-image-store.s3.amazonaws.com/0/37443/cde3821c-a518-e759-0484-d7cd2623f236.gif)

## インストール

[github](https://github.com/hmuronaka/Rakufun)からソース一式をダウンロードして、コンパイル->xcode再起動でインストール完了です。

ショートカットキーがCTRL+mに割りついているので、RakufunPlugin.mのDEFAULT_KEYを必要に応じて変更しください。

## 使い方

```objc
// A.m
-(void)foo { // <- ここでCTRL+mを実行
...          // <- ここでも実行してもOK
}

// A.h
@interface A

-(void)foo; // <- これを自動生成!
            // 既に存在する場合は生成しません。（パラメータ名が不一致する場合などは生成します。）
@end
```

※現状では関数外から実行すると、カーソル位置から探索して、一番最初にマッチしたシグネチャに対して自動生成してしまいます。

## 課題

- 構文解析が貧弱なのでできれば強化したい。
- 複数の関数を処理できるようにしたい。
- ヘッダーファイルから定義を生成したい。
- 関数外から実行した場合は処理をしないようにする。
 
# 実装編

プラグイン作成にあたって調べた事などです。

## プラグインを作る準備

ざっとこのあたりには目を通します。

- [Xcode Plugin が盛り上がっているらしい](http://qiita.com/griffin_stewie/items/ffb53d0a5d03fe2d91a4)
- [初めてのXcode 5 プラグイン開発](http://dev.classmethod.jp/smartphone/xcode5-plugin-1/)
- [Xcode 5 プラグイン 開発のテクニック](http://dev.classmethod.jp/smartphone/xcode5-plugin-2/)

注意する点としては、
Targets->Build Settings->Deployment->Skip InstallをNoにする必要があります。（そうしないとDeploy先にプラグインがインストールされない）

正直なところ実際に作るには情報が足らず大変でしたが、この情報がなければスタートもできませんでした。記事を書いてくださった方々に感謝です。

## 調べ方

基本的にやりたい事に近いプラグインを見つけて、コードを調べる、という作業になりました。

今回の場合、参考となったプラグインは

- [XVim](https://github.com/XVimProject/XVim)
- [VVDocumenter](https://github.com/onevcat/VVDocumenter-Xcode)
- [BBUncrustifyPlugin](https://github.com/benoitsan/BBUncrustifyPlugin-Xcode)

です。

## Xcode.app内のFrameworkについて

Xcode上のエディタの情報を参照するために、Xcode.app内のライブラリをリンクする必要があります。

ソースコードから情報を取得するにあたり

- IDEKit
- DVTKit
- IDEFoundation
- IDESourceEditor(これはリンクできないっぽいので不要かも）

をリンクしています。

### Frameworkのヘッダーファイルについて

参考にしたプラグインを見ると、上記のFramework用に独自ヘッダーが定義されてたりして「なんだろうな〜」と思っていたのですが、[class-dump](http://stevenygard.com/projects/class-dump/)というツールでFrameworkからヘッダーファイルを生成しているようです。

※但し、このファイルはとんでもない行数があり、ファイルを開くとXcodeが非常に重くなるので、実際にはプラグインで利用するクラス/メソッド/プロパティのみを取り出して、独自にヘッダーファイルを定義しています。

例

```objc
// Rakufun/Xcode/XcodeComponents.hの抜粋
// Rakufunプラグインで必要な定義のみ行っている。
@interface IDEEditor : NSObject

@property(readonly) IDESourceCodeDocument *sourceCodeDocument;

@end

@class IDESourceCodeEditor;

@interface IDESourceCodeEditor : IDEEditor
@property (retain) NSTextView *textView;
- (IDESourceCodeDocument *)sourceCodeDocument;
@end
```
## 〜の仕方

ここからはHow toです。

### プラグインの初期化の仕方

いくつかのプラグインを参考にすると、だいたい次のように初期化されているようです。(VVDocumenterを参考）

```objc
+(void)pluginDidLoad:(NSBundle*)plugin {
    DbgLog(@"load Rakufun!");
    [self sharedInstance];
}

+(instancetype)sharedInstance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedInstance = [[self alloc] init];
    });
    return _sharedInstance;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(applicationDidFinishLaunching:)
                                                     name:NSApplicationDidFinishLaunchingNotification
                                                   object:nil];
    }
    return self;
}

-(void)applicationDidFinishLaunching:(NSNotification*)noti {
    // ここで初期化!!
    [self initMenu];
}
```

### 現在のエディタの取得の仕方

BBUncrustifyPluginを参考

```objc
// 現在のソースコードエディターを返す
+(IDESourceCodeEditor*)currentEditor {
    NSWindowController *currentWindowController = [[NSApp keyWindow] windowController];
    if ([currentWindowController isKindOfClass:NSClassFromString(@"IDEWorkspaceWindowController")]) {
        IDEWorkspaceWindowController *workspaceController = (IDEWorkspaceWindowController *)currentWindowController;
        IDEEditorArea *editorArea = [workspaceController editorArea];
        IDEEditorContext *editorContext = [editorArea lastActiveEditorContext];
        IDEEditor* editor = (IDEEditor*)[editorContext editor];
        
        if( [editor isKindOfClass:NSClassFromString(@"IDESourceCodeEditor")] ) {
            DbgLog(@"(get source code editor");
            return (IDESourceCodeEditor*)editor;
        }
    }
    return nil;
}

+(IDESourceCodeDocument*)currentDocument {
    IDESourceCodeEditor* editor = [self currentEditor];
    if( editor ) {
        return editor.sourceCodeDocument;
    }
    return nil;
}

+ (NSTextView *)currentSourceCodeView {
    IDESourceCodeEditor* editor = [self currentEditor];
    if( editor ) {
        return editor.textView;
    }
    return nil;
}
```

当初、IDESourceCodeEditorのカテゴリで実現しようとしたのですが、リンクエラーになって実現できませんでした。カテゴリを作る場合は、”コンパイル時に”シンボルが解決できる必要があるようです。

### 現在のソースコード（文字列）の取得の仕方

```objc
NSTextView* headerView = [XcodeHelper currentSourceCodeView];
NSString* headerText = headerView.textStorage.string;
```

### ヘッダーファイルとソースファイルを切り替えるには

xvimを参考

```objc
[NSApp sendAction:@selector(jumpToPreviousCounterpart:) to:nil from:self];
```

### Xcodeへ送信できるactionは？

IDEKit.h内のIDEEditorContextクラスやIDESourceCodeEditor.h内のIDESourceCodeEditorクラスなどを参考にする。

----

以上です。プラグインを作る際の参考になれば幸いです。