connect.tech- enhancing your workflow with xcode source editor extensions

Post on 12-Jan-2017

128 Views

Category:

Software

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Enhancing Your Workflow with Xcode Source Editor Extensions

By: Jesse Black Software Engineer stable|kerneljesse.black@stablekernel.com

@stablekernel

Hi, I’m Jesse Black.

• Programming for over eight years • Created a Mac App for my family business • Worked for 3 years with Gramercy Consultants developing iOS and Android apps • Working for stable|kernel for the past 3 years developing iOS apps, Android apps

and their supporting APIs

We’re stable|kernel.

stable|kernel is an Atlanta-based mobile development company to craft smartly-designed mobile applications that connect brands directly with their users.

Enhancing Your Workflow with Xcode Source Editor Extensions

@stablekernel

Overview

Mac App Extensions Xcode Source Editor Extensions Demo: Create Source Editor Extension and debugging it XC Classes Example: JSON formatter Demo: JSON formatter in action

Mac App Extensions

@stablekernel

• Mac App Extensions

• Affect the user experience of other apps

• Need to be lightweight and run fast

• Require user interaction in order to be run

• Distribute via host app

Why use Xcode Source Editor Extensions

@stablekernel

• Less context switching

• It is too fun to customize your workflow

Xcode Source Editor Extensions

@stablekernel

• Differences with general Mac App Extensions

• Extensions are installed by just putting the host application in your applications folder

• Only works with one type of target application, which is Xcode

• Doesn’t have UI

Xcode Source Editor Extensions

@stablekernel

• Allows you to add custom commands under Xcode’s Editor menu

• Commands can manipulate text and text selections

• Users can put bind keyboard shortcuts to invoke your commands

Limitations to Xcode Source Editor Extensions

@stablekernel

• Can only start with user interaction

• Lack of ability to integrate into the build process and archive process. It is for modifying source code while it is being edited.

Expectations of Xcode Source Editor Extensions

@stablekernel

• Fast start up

• Fast execution

• Security and Stability

Demo

@stablekernel

Overview

Create Source Editor Extension How to install How to debug How to uninstall

Defining Commands

@stablekernel

• Command Identifier

• Class Name

• Command Name

Commands can be defined in the Extension’s plist or by supplying a dictionary at runtime. Commands provided at runtime override commands defined in the plist.

XC Source Editor Extension

@stablekernel

func extensionDidFinishLaunching() {}

var commandDefinitions: [[XCSourceEditorCommandDefinitionKey: Any]] {}

XC Source Editor Command

@stablekernel

func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void ) -> Void {

XC Source Editor Command

@stablekernel

• Modify text, text selection

• Exit early with an error message that is displayed in Xcode

XC Source Editor Command Invocation

@stablekernel

• Encapsulates all details needed to fulfill the user’s request • contentUTI • usesTabsForIndentation • indentationWidth • tabWidth • buffer

XC Source Text Buffer

@stablekernel

• completeBuffer • lines • selections

• defined in terms of lines and columns

Example: JSON formatter

@stablekernel

• Define minify and prettify commands • Implement XCSourceEditorCommand’s perform method

• Validate requirements for command • Get first text selection • Return error if selected text isn’t valid JSON • Use JSONSerialization class to format text • Replace selected text

XCSourceEditorExtension

@stablekernel

var commandDefinitions: [[XCSourceEditorCommandDefinitionKey: Any]] { return [ [ .classNameKey: "JeSON.SourceEditorCommand", .identifierKey: JeSONInvocation.Minify.rawValue, .nameKey: "Minify" ], [ .classNameKey: "JeSON.SourceEditorCommand", .identifierKey: JeSONInvocation.Prettify.rawValue, .nameKey: "Prettify" ], ] }

XCSourceEditorExtension

@stablekernel

enum JeSONInvocation: String { case Prettify = "com.jesseblack.HelloWorldProject.JeSON.Prettify" case Minify = "com.jesseblack.HelloWorldProject.JeSON.Minify" }

@stablekernel

func textAtSelectionIndex(_ at: Int, buffer: XCSourceTextBuffer) -> String { let textRange = buffer.selections[at] as! XCSourceTextRange let selectionLines = buffer.lines.enumerated().filter { (offset, element) -> Bool in return offset >= textRange.start.line && offset <= textRange.end.line }.map { return $1 } return selectionLines.enumerated().reduce("") { (result, enumeration) -> String in let line = enumeration.element as! String if enumeration.offset == 0 && enumeration.offset == selectionLines.count - 1 { let startIndex = line.index(line.startIndex, offsetBy: textRange.start.column) let endIndex = line.index(line.startIndex, offsetBy: textRange.end.column + 1)

return result + line.substring(with: startIndex..<endIndex) } else if enumeration.offset == 0 { let startIndex = line.index(line.startIndex, offsetBy: textRange.start.column) return result + line.substring(from: startIndex) } else if enumeration.offset == selectionLines.count - 1 { let endIndex = line.index(line.startIndex, offsetBy: textRange.end.column + 1) return result + line.substring(to: endIndex) } return result + line } }

@stablekernel

func replaceTextAtSelectionIndex(_ at: Int, replacementText: String, buffer: XCSourceTextBuffer) { let textRange = buffer.selections[at] as! XCSourceTextRange

let lastLine = buffer.lines[textRange.end.line] as! String let endIndex = lastLine.index(lastLine.startIndex, offsetBy: textRange.end.column+1) let suffix = lastLine.substring(from: endIndex) let firstLine = buffer.lines[textRange.start.line] as! String let startIndex = firstLine.index(firstLine.startIndex, offsetBy: textRange.start.column) let prefix = firstLine.substring(to: startIndex) let range = NSMakeRange(textRange.start.line, textRange.end.line - textRange.start.line + 1) buffer.lines.removeObjects(in: range) buffer.lines.insert(prefix+replacementText+suffix, at: textRange.start.line) let newRange = XCSourceTextRange(start: textRange.start, end: textRange.start) buffer.selections.setArray([newRange]) }

Performing the command

@stablekernel

func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void ) -> Void { guard let jeSONInvocation = JeSONInvocation(rawValue: invocation.commandIdentifier) else { let errorInfo = [NSLocalizedDescriptionKey: "Command not recognized"] completionHandler(NSError(domain: "", code: 0, userInfo: errorInfo)) return } let buffer = invocation.buffer guard buffer.selections.count == 1 else { let errorInfo = [NSLocalizedDescriptionKey: "Command only handles 1 selection at a time"] completionHandler(NSError(domain: "", code: 0, userInfo: errorInfo)) return }

Performing the command

@stablekernel

// perform continued

let selectedText = textAtSelectionIndex(0, buffer: buffer) let data = selectedText.data(using: .utf8) do { let jsonObject = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions()) switch jeSONInvocation { case .Minify: let miniData = try! JSONSerialization.data(withJSONObject: jsonObject, options: JSONSerialization.WritingOptions()) let string = String.init(data: miniData, encoding: .utf8)! replaceTextAtSelectionIndex(0, replacementText: string, buffer: buffer) case .Prettify: let miniData = try! JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted) let string = String.init(data: miniData, encoding: .utf8)! replaceTextAtSelectionIndex(0, replacementText: string, buffer: buffer) } } catch { completionHandler(error) }

completionHandler(nil) }

JSON Formatter Demo

@stablekernel

Overview

See all the hard work pay off

Questions?

Business Inquiries:Sarah WoodwardDirector of Business Developmentsarah.woodward@stablekernel.com

Jesse BlackSoftware Engineerjesse.black@stablekernel.comblog.stablekernel.com@JesseBlack82

http://www.slideshare.net/stablekernel https://github.com/JesseBlack82/HelloExtensions

top related