Last night, I wrote my first ever Xcode plug-in. It’s a very odd place to explore and one I wasn’t planning for when I woke up yesterday.
I’m on overdrive this week, preparing for next. Among other things, I’m organizing and documenting libraries. I’m setting up outlines of outlines (based on my best guesses of what will be announced at WWDC). I’m “pre-writing” coverage to be filled in later and otherwise getting my tools in place for the onslaught of information that is WWDC. It’s exhausting.
Preparing for writing doesn’t just involve cleanup. I’m trying to establish my toolset as well. Take manual code wrapping, for example. It continues to be one of my most hated tasks. Each book project requires a different number of maximum characters, with the layout adjusting to fonts and physical pages as well as ebook design.
Yesterday, I decided to tackle the problem head on. I first wrote an app that would fold text for me. Although it respected code boundaries, I felt it did a pretty poor job of deciding where to wrap semantically.
After testing it on a bunch of code, primarily Swift but also Objective-C, I decided I really wasn’t happy with my algorithm. Swift in particular lends itself to very long lines and lots-of-dots that make folding a hard problem.
Xcode does a better job. Not perfect, otherwise there wouldn’t be all those Objective-C line wrapping repos on github but better. How could I leverage that expertise for my own projects?
For obvious reasons, copy/paste doesn’t work in retaining wrapping information. Xcode doesn’t expose the text editor windows to Applescript, let alone how those windows layout their text.
So I bit the bullet and downloaded Delisa “Kattrali” Mason’s Xcode Plugin Template from github. I was nervous. I’ve never worked directly with the Xcode plugin architecture. But if I wanted to scrape Xcode, I couldn’t think of a better choice.
The templates in this repo install to your ~/Library/Developer/Xcode/Templates/Project Templates folder. Once added, you quit and restart Xcode then select File > New Project > OS X > Application Plug-in > Xcode Plugin to create a new project.
The skeleton code is simple. It consists of a small NSObject extension that implements pluginDidLoad: and a new class that creates the plugin. It didn’t seem as if there was enough there there to create a working plug-in. But when I compiled, installed, and restarted Xcode, a custom Edit menu option appeared. Selected, it popped up an alert, exactly as advertised.
This skeleton got me to a critical starting point. When you run as a plug-in, you run as part of Xcode. The NSApp in your code refers to Xcode itself. This enables you to retrieve the application’s mainWindow and start exploring.
What I discovered were two classes of source text: DVTSourceTextView (in normal editors) and IDEPlaygroundTextView (in playgrounds). Each descends from NSTextView and offers standard access to a layout manager and a backing text storage. A little work with CoreText decomposing the layout into line-by-line fragments gave me the Xcode-powered wrapping I was looking for.
My new plugin adds an option to the bottom of the Edit menu. It copies out the Xcode-wrapped line fragments to the system pasteboard with hard carriage returns added between lines.
Now I can establish a practical size for Xcode editor panes that coordinates with my required column count for any project. I enable Xcode > Preferences > Text Editing > Page Guide at Column and adjust the column count to match the maximum width for any book page layout.