rsms

Extending Xcode

A lot of software enables the user to extend it in order to enhance productivity and customize her work environment. An excellent example of this kind of software is TextMate. But I’m not going to talk about TextMate today, but instead another very popular development tool for the Mac OS – Apple Xcode.

Xcode have had plug-in support during most of it’s life time so far, but the means for creating new plugins has been (and is still in many ways) something only Apple has. Documentation is extremely sparse and most of that which is available is outdated. If you bring up the “New project…” (CMD+SHIFT+N) you will find “AppleScript Xcode Plug-in” inside the “Standard Apple Plug-ins” category. However great this might sound, it restricts you to only fiddle with AppleScript, which in my point of view is just a pain in the ass. (AppleScript, that is. One might call it a necessary evil)

Writing a “real” plugin (using Cocoa, AppKit aso.) should be as easy as writing a normal Application, since a xcplugin is just a simple bundle like any other OS X bundle. What is different though, is that you probably want to fiddle with Xcode itself. (i.e. change the color of some text based on some rules, which for instance a language extension would do) Doing so requires knowledge (i.e. headers) about how to poke around. Unfortunately Apple does not expose these interfaces. Damien Bobillot maintains a (somewhat outdated) document called Xcode’s Plugin Interface in which he provides a package of private headers one must have, in order to use the DevToolCore framework.

A template to the rescue

I have created a Xcode 3 template for creating new Xcode plugins in Objective-C. There are currently two ways of getting a copy of the template:

Install the template by copying or moving the “Xcode Plugin for Objective-C” directory into /Developer/Library/Xcode/Project Templates/Standard Apple Plug-ins/ (i.e. not in your home directory).

After the template has been installed, just (re)start Xcode, activate “File” → “New project…” (CMD+SHIFT+N) and you will find “Xcode Plugin for Objective-C” in the “Standard Apple Plug-ins” category. Select it and create a new project with a name of your choice. Do not include the suffix Plugin, as this is automatically added. (Note: do not use spaces in the name. This is a bug.)

Hit “Build”, wait a few seconds for it to create you new plugin and then copy your newly built xcplugin into /Developer/Library/Xcode/Plug-ins (You can simply drag the NameOfYourPlugin.xcplugin from “Products” and drop it in the Plug-ins directory)

Restart Xcode and you should see a new menu item at the rightmost position in the main menu. This is just something that comes with the template to get you started. Open NameOfYourPlugin.m (in the “Classes” group) and you should be on the right track. (I assume you’re familiar with Objective-C and Cocoa)

Here is a minimal version of the principal class: (If you’re like me, you probably want a clean slate)

#!m
#import "NameOfYourPlugin.h"
@implementation NameOfYourPlugin
- (void)awakeFromNib {
  // Setup plugin. If it takes time, use a separate thread.
}
@end

Interfacing with DevToolCore

Linking against and using the DevToolCore framework is not something I’m going to talk about here. I suggest you read Xcode’s Plugin Interface.

Other alternatives

Xcode sports a somewhat obscure user scripting menu. (has the looks of a small scroll) You can add your own helpers or modify any of the scripts already included by Apple.

Cocoa-wiz-and-best-friend Mattias Arrelid and I sat up real late this morning trying to create a Mercurial commit script with a graphical interface. We tried everything. And I mean everything – PyObjC, AppleScript, RubyCocoa… But none enabled us to manage a simple window with text input. We ended up using CommitWindow (a tool included with TextMate, which we both use in parallel to Xcode)

Here is the script, which brings up a commit window that can display diffs:

#!/bin/sh
path="%%%{PBXFilePath}%%%"
dir="$(dirname "$path")"
filename="$(basename "$path")"
TM_SUPPORT_PATH="$(echo 'POSIX path of (path to application "TextMate")'|osascript)Contents/SharedSupport/Support"

cd "$dir"

r=$(TM_SUPPORT_PATH="$TM_SUPPORT_PATH" \
    "$TM_SUPPORT_PATH/bin/CommitWindow.app/Contents/MacOS/CommitWindow" \
    --diff-cmd "/usr/local/bin/hg,diff" "$filename")

if (echo "$r" | grep 'commit window: cancel' >/dev/null); then
  exit 0
else
  sh -c "hg --verbose commit $r"
  exit $?
fi

To install it, copy the source from above, select [script icon] → “Edit User Scripts…” in Xcode. Create a new group if you want (the bottom-left “+” button) and and a new “Shell Script” by clicking the same “+” button. Paste the code. Rename it and assign a keyboard shortcut by double-clicking the appropriate fields in the command table view.

To round things up, you might be able to Create a SIMBL Plugin Bundle if that’s more like your cup of tea.