SlideShare a Scribd company logo
Enhancing Your Workflow with Xcode Source Editor Extensions
By: Jesse Black
Software Engineer
stable|kernel

jesse.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 Woodward
Director of Business Development
sarah.woodward@stablekernel.com
Jesse Black
Software Engineer
jesse.black@stablekernel.com
blog.stablekernel.com
@JesseBlack82
http://www.slideshare.net/stablekernel
https://github.com/JesseBlack82/HelloExtensions

More Related Content

PDF
Connect.Tech- Swift Memory Management
PDF
Connect.Tech- Level Up Your Game With TravisCI
PDF
Reactive Programming with JavaScript
PDF
Memory Management on iOS
PDF
JavaScript in 2016
ODP
From object oriented to functional domain modeling
PDF
Distributing information on iOS
PPTX
Advance JS and oop
Connect.Tech- Swift Memory Management
Connect.Tech- Level Up Your Game With TravisCI
Reactive Programming with JavaScript
Memory Management on iOS
JavaScript in 2016
From object oriented to functional domain modeling
Distributing information on iOS
Advance JS and oop

What's hot (20)

PDF
Practical tips for building apps with kotlin
PDF
Save time with kotlin in android development
PDF
Android Architecture Components with Kotlin
PPT
Memory management in Objective C
PPTX
Demystifying Oak Search
PDF
How AngularJS Embraced Traditional Design Patterns
PDF
Scala @ TechMeetup Edinburgh
PPT
Objective C Memory Management
PPTX
PDF
Workshop 26: React Native - The Native Side
DOC
EMF Tips n Tricks
PPTX
Кирилл Безпалый, .NET Developer, Ciklum
PDF
Kotlin for Android devs
PDF
Workshop 23: ReactJS, React & Redux testing
PPT
PDF
JavaCro 2014 Scala and Java EE 7 Development Experiences
PPT
Ios - Introduction to memory management
PPT
55 New Features in Java 7
PDF
Workshop 17: EmberJS parte II
Practical tips for building apps with kotlin
Save time with kotlin in android development
Android Architecture Components with Kotlin
Memory management in Objective C
Demystifying Oak Search
How AngularJS Embraced Traditional Design Patterns
Scala @ TechMeetup Edinburgh
Objective C Memory Management
Workshop 26: React Native - The Native Side
EMF Tips n Tricks
Кирилл Безпалый, .NET Developer, Ciklum
Kotlin for Android devs
Workshop 23: ReactJS, React & Redux testing
JavaCro 2014 Scala and Java EE 7 Development Experiences
Ios - Introduction to memory management
55 New Features in Java 7
Workshop 17: EmberJS parte II
Ad

Viewers also liked (20)

PPTX
How to create a jQuery Modal Window
PDF
Design patterns
DOC
Lampiran unit root test
PDF
Purchasing power parity a unit root, cointegration and var analysis in emergi...
PDF
A Hypervisor IPS based on Hardware Assisted Virtualization Technology
DOCX
EQUITY MARKET INTEGRATION IN SELECTED MARKETS: EVIDENCE FROM UNIT ROOT AND CO...
PDF
Representational State Transfer (REST)
PDF
1.Buffer Overflows
PPTX
Creational pattern
PPTX
Intrinsic Encounter - Cognitive Load Theory
PDF
Web design trends for 2010
PDF
Minimize Cognitive Load + Maximize Conversions : 10 Tips
PDF
Buffer overflow and other software vulnerabilities: theory and practice of pr...
PDF
Representational State Transfer (REST)
PPT
Lecture 13
PPTX
Buffer overflow
PPTX
Hardware support for efficient virtualization
PDF
Xcode, Basics and Beyond
PPTX
Load-testing 101 for Startups with Artillery.io
PDF
How to Run a 1,000,000 VU Load Test using Apache JMeter and BlazeMeter
How to create a jQuery Modal Window
Design patterns
Lampiran unit root test
Purchasing power parity a unit root, cointegration and var analysis in emergi...
A Hypervisor IPS based on Hardware Assisted Virtualization Technology
EQUITY MARKET INTEGRATION IN SELECTED MARKETS: EVIDENCE FROM UNIT ROOT AND CO...
Representational State Transfer (REST)
1.Buffer Overflows
Creational pattern
Intrinsic Encounter - Cognitive Load Theory
Web design trends for 2010
Minimize Cognitive Load + Maximize Conversions : 10 Tips
Buffer overflow and other software vulnerabilities: theory and practice of pr...
Representational State Transfer (REST)
Lecture 13
Buffer overflow
Hardware support for efficient virtualization
Xcode, Basics and Beyond
Load-testing 101 for Startups with Artillery.io
How to Run a 1,000,000 VU Load Test using Apache JMeter and BlazeMeter
Ad

Recently uploaded (20)

PDF
AI in Product Development-omnex systems
PDF
Digital Strategies for Manufacturing Companies
PPTX
ISO 45001 Occupational Health and Safety Management System
PPT
Introduction Database Management System for Course Database
PPTX
CHAPTER 12 - CYBER SECURITY AND FUTURE SKILLS (1) (1).pptx
PDF
How Creative Agencies Leverage Project Management Software.pdf
PPTX
CRUISE TICKETING SYSTEM | CRUISE RESERVATION SOFTWARE
PDF
A REACT POMODORO TIMER WEB APPLICATION.pdf
PDF
How to Choose the Right IT Partner for Your Business in Malaysia
PPT
JAVA ppt tutorial basics to learn java programming
PPTX
Safe Confined Space Entry Monitoring_ Singapore Experts.pptx
PDF
Softaken Excel to vCard Converter Software.pdf
PPTX
FLIGHT TICKET RESERVATION SYSTEM | FLIGHT BOOKING ENGINE API
PDF
How to Migrate SBCGlobal Email to Yahoo Easily
PDF
top salesforce developer skills in 2025.pdf
PPTX
Materi_Pemrograman_Komputer-Looping.pptx
PPTX
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
PPTX
Transform Your Business with a Software ERP System
PPTX
Materi-Enum-and-Record-Data-Type (1).pptx
PDF
Upgrade and Innovation Strategies for SAP ERP Customers
AI in Product Development-omnex systems
Digital Strategies for Manufacturing Companies
ISO 45001 Occupational Health and Safety Management System
Introduction Database Management System for Course Database
CHAPTER 12 - CYBER SECURITY AND FUTURE SKILLS (1) (1).pptx
How Creative Agencies Leverage Project Management Software.pdf
CRUISE TICKETING SYSTEM | CRUISE RESERVATION SOFTWARE
A REACT POMODORO TIMER WEB APPLICATION.pdf
How to Choose the Right IT Partner for Your Business in Malaysia
JAVA ppt tutorial basics to learn java programming
Safe Confined Space Entry Monitoring_ Singapore Experts.pptx
Softaken Excel to vCard Converter Software.pdf
FLIGHT TICKET RESERVATION SYSTEM | FLIGHT BOOKING ENGINE API
How to Migrate SBCGlobal Email to Yahoo Easily
top salesforce developer skills in 2025.pdf
Materi_Pemrograman_Komputer-Looping.pptx
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
Transform Your Business with a Software ERP System
Materi-Enum-and-Record-Data-Type (1).pptx
Upgrade and Innovation Strategies for SAP ERP Customers

Connect.Tech- Enhancing Your Workflow With Xcode Source Editor Extensions

  • 1. Enhancing Your Workflow with Xcode Source Editor Extensions By: Jesse Black Software Engineer stable|kernel
 [email protected]
  • 2. @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
  • 3. 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. 
 

  • 4. 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
  • 5. 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
  • 6. Why use Xcode Source Editor Extensions @stablekernel • Less context switching • It is too fun to customize your workflow
  • 7. 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
  • 8. 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
  • 9. 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.
  • 10. Expectations of Xcode Source Editor Extensions @stablekernel • Fast start up • Fast execution • Security and Stability
  • 11. Demo @stablekernel Overview Create Source Editor Extension How to install How to debug How to uninstall
  • 12. 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.
  • 13. XC Source Editor Extension @stablekernel func extensionDidFinishLaunching() {} var commandDefinitions: [[XCSourceEditorCommandDefinitionKey: Any]] {}
  • 14. XC Source Editor Command @stablekernel func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void ) -> Void {
  • 15. XC Source Editor Command @stablekernel • Modify text, text selection • Exit early with an error message that is displayed in Xcode
  • 16. XC Source Editor Command Invocation @stablekernel • Encapsulates all details needed to fulfill the user’s request • contentUTI • usesTabsForIndentation • indentationWidth • tabWidth • buffer
  • 17. XC Source Text Buffer @stablekernel • completeBuffer • lines • selections • defined in terms of lines and columns
  • 18. 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
  • 19. 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" ], ] }
  • 20. XCSourceEditorExtension @stablekernel enum JeSONInvocation: String { case Prettify = "com.jesseblack.HelloWorldProject.JeSON.Prettify" case Minify = "com.jesseblack.HelloWorldProject.JeSON.Minify" }
  • 21. @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 } }
  • 22. @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]) }
  • 23. 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 }
  • 24. 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) }
  • 26. Questions? Business Inquiries: Sarah Woodward Director of Business Development [email protected] Jesse Black Software Engineer [email protected] blog.stablekernel.com @JesseBlack82 http://www.slideshare.net/stablekernel https://github.com/JesseBlack82/HelloExtensions