XcodeでもIntellijのコマンドを使いたい

この記事は [2016年 Livesense Advent Calendar](http://qiita.com/advent-calendar/2016/livesense_1) 17日目の記事です。

# まえがき

こんにちは 新卒エンジニアのroana0229 です。

みなさん、iOS開発していますか？
私は、 ~~Xcodeにもどかしい気持ちを抱きながら~~ 楽しく開発しています。

ところで、今年のWWDC2016で「Xcode Source Editor」という
Xcode8から利用できるExtensionが発表されましたね。
[Using and Extending the Xcode Source Editor](https://developer.apple.com/videos/play/wwdc2016/414/)

今までXcodeの拡張といえば、[Alcatraz](http://alcatraz.io)というXcodeのパッケージマネージャでのプラグイン管理が定番でした。
が、Xcode8になり ~~Xcode.appをごにょごにょしない限り~~ 使用できなくなりました。 

というのも、今までのプラグインは非公式で開発・公開されていたものであって
安全性が確保されている訳ではありませんでした。
Xcodeの拡張機能をApple公式にサポートしてくれたのは、とても嬉しいことです。

というわけで、Xcode Source Editorを3分クッキング形式で作ってみようと思います。
基本的な開発の仕方についてはググって頂けると、色々記事が見つかるかと思います。

# Xcode Source Editor Extensionを作ってみる

## 作るものをご紹介します

Androidも開発していて、Intellijの操作感が好きなので、その中で簡単そうなものを２つピックアップしてみました。

### 値を変数に代入する

`alt + enter`でコード補完のサジェストを出し、`Introduce Local variable`を選択すると
値を変数に代入するようコードに変更がかかります。
またその時、値の型を考慮して補完してくれます、賢いですね。

IntelliJ IDEA - Java
![demo1.gif](https://qiita-image-store.s3.amazonaws.com/0/42327/e020da8d-7aaf-58a1-0258-747168ca30a2.gif "demo1.gif")


### 選択している行を移動する

`cmd + shift`で選択している行を上下に動かすことができます。
当然複数行も対応しています。

IntelliJ IDEA - Java
![demo2.gif](https://qiita-image-store.s3.amazonaws.com/0/42327/04220133-fa3f-bc70-a127-fbb7bd484ca7.gif "demo2.gif")


## 出来上がったものがこちら

### 値を変数に代入する

`var`,`let`を切り替えられるようにしてみました。
変数名もサジェスト機能がほしいところですね。

Xcode - swift
![demo3.gif](https://qiita-image-store.s3.amazonaws.com/0/42327/dd9f1264-6d3a-3fb9-731c-7042d2e43bda.gif "demo3.gif")


```LocalVariableCommand.swift
class LocalVariableCommand: NSObject, XCSourceEditorCommand {

    func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void ) -> Void {
        guard let selection = invocation.buffer.selections.firstObject as? XCSourceTextRange else {
            completionHandler(NSError(domain: "IntellijShortcuts-Local-Variable", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid Selection"]))
            return
        }

        // ファイルの行全体を取得
        let lines = invocation.buffer.lines
        // 選択している行を取得
        if var line = lines[selection.start.line] as? String {
            // 行頭のスペースを変数へ格納
            var space = ""
            while line.characters.first == " " {
                line = String(line.characters.dropFirst())
                space += " "
            }

            if line.hasPrefix("var ") {
                // var だったら let に切り替え
                let code = line.replacingOccurrences(of: "var", with: "let")
                lines.replaceObject(at: selection.start.line, with: "\(space)\(code)")
            } else if line.hasPrefix("let ") {
                // let だったら var に切り替え
                let code = line.replacingOccurrences(of: "let", with: "var")
                lines.replaceObject(at: selection.start.line, with: "\(space)\(code)")
            } else {
                // var <##> = value のフォーマットを挿入する
                let code = "var <##> = \(line)"
                lines.replaceObject(at: selection.start.line, with: "\(space)\(code)")
            }
        }

        completionHandler(nil)
    }

}
```

### 選択している行を移動する

~~当然~~ まだ複数行選択には対応していません。
が、少しはもどかしさが解消されました。

Xcode - swift
![demo4.gif](https://qiita-image-store.s3.amazonaws.com/0/42327/02daeb1e-b2b6-4ecf-a2e0-4e7a6473735c.gif "demo4.gif")

```LineMoveNextCommand.swift
class LineMoveNextCommand: NSObject, XCSourceEditorCommand {
    
    func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void ) -> Void {
        guard let selection = invocation.buffer.selections.firstObject as? XCSourceTextRange else {
            completionHandler(NSError(domain: "IntellijShortcuts-Line-Move-Next", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid Selection"]))
            return
        }
        
        // ファイルの行全体を取得
        let lines = invocation.buffer.lines
        // 選択している次の行を取得
        if let nextLine = lines[selection.start.line+1] as? String {
            // 次の行を削除し、選択している行へ挿入することで下にずらす
            lines.removeObject(at: selection.start.line+1)
            lines.insert(nextLine, at: selection.start.line)
        }
        
        completionHandler(nil)
    }
    
}
```

```LineMovePreviousCommand.swift
class LineMovePreviousCommand: NSObject, XCSourceEditorCommand {
    
    func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void ) -> Void {
        guard let selection = invocation.buffer.selections.firstObject as? XCSourceTextRange else {
            completionHandler(NSError(domain: "IntellijShortcuts-Line-Move-Previous", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid Selection"]))
            return
        }
        
        // ファイルの行全体を取得
        let lines = invocation.buffer.lines
        // 選択している前の行を取得
        if let beforeLine = lines[selection.start.line-1] as? String {
            // 前の行を削除し、選択している次の行へ挿入することで下にずらす
            lines.removeObject(at: selection.start.line-1)
            lines.insert(beforeLine, at: selection.start.line+1)
        }
        
        completionHandler(nil)
    }
    
}
```

## できたらみんなに使ってもらおう

実際にApplicationをStoreに公開する手順と代わりありません。
今回は`.app`ファイルをexportして、githubにアップする形にしようと思います。

Xcodeメニュー > Product > Archive とするとこんな感じで出来上がります。
 
![スクリーンショット 2016-12-17 05.40.12.png](https://qiita-image-store.s3.amazonaws.com/0/42327/328c40a9-ba80-89c9-89da-e300337fe685.png "スクリーンショット 2016-12-17 05.40.12.png")

画面右の「Export」を押すと、この画面が出ますので「Export as a macOS App」を押す。

![スクリーンショット 2016-12-17 05.42.43.png](https://qiita-image-store.s3.amazonaws.com/0/42327/49e5f883-e435-21c3-bf16-1b9c00d46162.png "スクリーンショット 2016-12-17 05.42.43.png")

あっという間に、`IntellijShortcutsApp.app`ができました。
あとはそれを`/Applications`に入れて一度だけ起動するとあら簡単、Xcodeで利用できるようになりました。

※他にもいくつか公開の方法はあるようですが、簡単で手間が少ない手順にしました。


## 最後に

ね、簡単でしょう？
簡単にこういうものが作れるんだよ。と伝われば本望です。

GUI上でカラーコードをまとめられた[Crayons](https://github.com/Sephiroth87/Crayons)というプラグインが以前あり、
それを作ってみようと思って調べてみたことがこの記事のきっかけでした。

過去にもIntellijのプラグイン（[android-xml-sorter](https://github.com/roana0229/android-xml-sorter)）を作ったことがあるのですが、Xcodeも簡単に作れました。[IntellijShortcutsApp](https://github.com/roana0229/IntellijShortcutsApp)

残念ながら、Xcode Source Editor Extensionはまだできることも少ないため、開拓されている感じはありません。
（ソースコードエディタ上でしかプラグインを実行できず、GUIに手を出せないのが厳しい…）
もっと、できることが増えてIntellijで使えるショートカットを使えるようになったら嬉しいなあ。
~~欲を言えば、IntellijでiOS開発できたらもっと嬉しい。~~

まとめられているものですと、ここに良さげなものが[awesome-xcode-extensions](https://github.com/tib/awesome-xcode-extensions)に載っています。
SwiftLintなどはかなり実用的な感じですね。
このExtensionも載るといいなあ。