Xcode Source Editor Extension を使った Xcode プラグインの作り方
Xcode Source Editor Extension
「Xcode Source Editor Extension」は、Xcode 8 から作れるようになった Xcode の Source Editor の機能拡張のことです。Xcode の Source Editor を拡張するような機能を、誰でも実装し提供することができます。
本記事では、プロジェクトを作成し簡単なコマンドを実装するところまでを取り上げます。
プロジェクトの作成
「Create a New Xcode project」を選びます。
Xcode Source Editor Extension は、Mac アプリの Extension として作ります。ということで「macOS」の「Cocoa Application」を選びます。
プロジェクト名を適当に決めます。これは Mac アプリのプロジェクトの名前になります。ここでは「SampleApp」とします。
プロジェクトの保存場所を任意のディレクトリにします。
プロジェクトができたら、ターゲットを追加します。
「macOS」の「Xcode Source Editor Extension」を選びます。ここでは「SampleExtension」とします。
「Active "SampleExtension" scheme?」と聞かれます。これは、ビルドやデバッグのときに Extension を有効化するかどうかを聞かれています。「Activate」を選びます。
これで Source Editor Extension を開発する準備が整いました。
ファイル構成
生成された Source Editor Extension のファイル構成を見てみましょう。
ファイル名 | 説明 |
---|---|
SourceEditorExtension.swift | Extension のコアとなる XCSourceEditorExtension のサブクラス |
SourceEditorCommand.swift | Extension が提供するコマンドの実装を行う XCSourceEditorCommand のサブクラス |
Info.plist | Extension の設定を定義する Plist ファイル |
SourceEditorExtension
は、いわば AppDelegate
のようなものです。Extension が起動された時をハンドリングできたり、コマンドを定義できたりします(コマンドの定義は Info.plist
でも定義可能)。
Extension で提供したいコマンドの数だけ XCSourceEditorCommand
のサブクラスを作ります。自動生成されたままの状態では、SourceEditorCommand
というコマンド定義用のクラスが1つだけ定義されています。
コマンドを実装してみる
いきなり実用的な Extension を作るのはハードルが高いので、まずは簡単なコマンドから実装してみましょう。
選択した行に、以下のコードを追加するコマンドを実装してみたいと思います。
let message = "Hello!"
SourceEditorCommand
クラスに perform:with:completionHandler:
メソッドを定義し、次のように実装します。
import Foundation import XcodeKit class SourceEditorCommand: NSObject, XCSourceEditorCommand { func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void ) -> Void { let textBuffer = invocation.buffer let lines = textBuffer.lines let selections = textBuffer.selections guard let selection = selections.firstObject as? XCSourceTextRange else { completionHandler(NSError(domain: "SampleExtension", code: 401, userInfo: ["reason": "text not selected"])) return } let line = selection.end.line let end = selection.end.column let indentSpace = String(repeating: " ", count: end) lines.insert("\(indentSpace)let message = \"Hello!\"", at: line) completionHandler(nil) } }
選択中の内容は、引数で渡されている XCSourceEditorCommandInvocation
の buffer
から取得できます。buffer
は XCSourceTextBuffer
クラスで、selections
には選択中の内容が配列で格納されています。firstObject
を取得すると、1つ目の選択中の内容が取得できます。また、lines
には行ごとの情報が格納されています。selections
も lines
も、NSMutableArray
です。
selection
は XCSourceTextRange
クラスです。選択の範囲が表現されており、開始の行・列と終了の行・列が取得できます(行・列は XCSourceTextPosition
クラス)。
選択が終わっている行を対象に、挿入したいコードを追加しています。最後には引数で渡されている completionHandler()
を呼び出して終わりです。
デバッグ
デバッグするには、プロジェクト設定の「Signing」で「Enable Development Signing」をクリックする必要があります。Map アプリの Target と Extension の Target、両方で有効にします。
デバッグしようとすると、Extension の対象とするアプリを選択できます。もちろん Xcode ですね。
おぉ…なんか黒い…。
iOS アプリの Xcode プロジェクトを開いてみます。本物の Xcode と、デバッグ中の Xcode が明確に見分けがつくよう、アプリアイコンとステータスウインドウが黒くなっています。
実装した Source Editor Extension のコマンドは、メニューの「Editor」の一番下に表示されます。「SampleExtension」という名前にしたので「SampleExtension」が表示されます。その中にある「Source Editor Command」を選びましょう。
選択している行・列の後に、コードが追加されました!
まとめ
Mac アプリや iOS アプリの Extension を作ったことがある人は、非常に覚えやすい作りだと思います。うまく活用して Xcode をより多機能にしていきたいですね!