Edited at
LivesenseDay 17

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

More than 1 year has passed since last update.

この記事は 2016年 Livesense Advent Calendar 17日目の記事です。


まえがき

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

みなさん、iOS開発していますか?

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

ところで、今年のWWDC2016で「Xcode Source Editor」という

Xcode8から利用できるExtensionが発表されましたね。

Using and Extending the Xcode Source Editor

今までXcodeの拡張といえば、AlcatrazというXcodeのパッケージマネージャでのプラグイン管理が定番でした。

が、Xcode8になり Xcode.appをごにょごにょしない限り 使用できなくなりました。

というのも、今までのプラグインは非公式で開発・公開されていたものであって

安全性が確保されている訳ではありませんでした。

Xcodeの拡張機能をApple公式にサポートしてくれたのは、とても嬉しいことです。

というわけで、Xcode Source Editorを3分クッキング形式で作ってみようと思います。

基本的な開発の仕方についてはググって頂けると、色々記事が見つかるかと思います。


Xcode Source Editor Extensionを作ってみる


作るものをご紹介します

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


値を変数に代入する

alt + enterでコード補完のサジェストを出し、Introduce Local variableを選択すると

値を変数に代入するようコードに変更がかかります。

またその時、値の型を考慮して補完してくれます、賢いですね。

IntelliJ IDEA - Java

demo1.gif


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

cmd + shiftで選択している行を上下に動かすことができます。

当然複数行も対応しています。

IntelliJ IDEA - Java

demo2.gif


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


値を変数に代入する

var,letを切り替えられるようにしてみました。

変数名もサジェスト機能がほしいところですね。

Xcode - swift

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


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

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

スクリーンショット 2016-12-17 05.42.43.png

あっという間に、IntellijShortcutsApp.appができました。

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

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


最後に

ね、簡単でしょう?

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

GUI上でカラーコードをまとめられたCrayonsというプラグインが以前あり、

それを作ってみようと思って調べてみたことがこの記事のきっかけでした。

過去にもIntellijのプラグイン(android-xml-sorter)を作ったことがあるのですが、Xcodeも簡単に作れました。IntellijShortcutsApp

残念ながら、Xcode Source Editor Extensionはまだできることも少ないため、開拓されている感じはありません。

(ソースコードエディタ上でしかプラグインを実行できず、GUIに手を出せないのが厳しい…)

もっと、できることが増えてIntellijで使えるショートカットを使えるようになったら嬉しいなあ。

欲を言えば、IntellijでiOS開発できたらもっと嬉しい。

まとめられているものですと、ここに良さげなものがawesome-xcode-extensionsに載っています。

SwiftLintなどはかなり実用的な感じですね。

このExtensionも載るといいなあ。