OMQuickHelp plug-in

OMQuickHelp is really cool:

This plugin allows you to use Dash instead of Xcode's own documentation viewer when using option-click (or the equivalent keyboard shortcut) to view the documentation for the selected symbol.

This is much more convenient than the "method-aware" service I provide in AppKiDo. You don't have to select any text — just Option-click anywhere on the symbol you want to look up. To install the plug-in, just build the project and restart Xcode.

If you want the search to be done in AppKiDo instead of Dash, find these lines in OMQuickHelpPlugin.m:

BOOL opened = [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"dash://%@", searchString]]];
if (!opened) {
    return NO;

Replace the lines above with these lines:

NSPasteboard *pboard = [NSPasteboard pasteboardWithUniqueName];
[pboard declareTypes:@[NSPasteboardTypeString] owner:nil];
[pboard setString:searchString forType:NSPasteboardTypeString];
if (!NSPerformService(@"Look Up in AppKiDo", pboard)) {
    return NO;

Again, build the plug-in and restart Xcode. Replace "AppKiDo" with "AppKiDo-for-iPhone" if that's your pleasure.

Note that searching with Dash or AppKiDo is not exactly like Xcode's default Option-click behavior. Normally, Xcode shows you the documentation for whatever the compiler thinks the symbol you selected is. This is unambiguous; there is at most one relevant documentation entry. For example, if you Option-click "view" in the following code, you'll see the docs for NSViewController's view method.

// Yes, this is absurd. It's just for purposes of discussion.
[[NSViewController new] view];

Dash and AppKiDo are different in that they do purely substring-based searches, using no semantic information. Searching for "view" in either app will return a whole bunch of results containing "view". You will have to pick through the search results to find the one you want.

It may be worth occasionally picking through multiple search results if this means you can use your preferred search tool. It may even be useful to see the other search results, which may include related symbols you weren't aware of. And you can always use Xcode's Quick Help inspector (Option-Command-2) to see the same documentation that Option-click would normally have shown before you installed OMQuickHelp.


Method-aware doc searches

I finally got around to updating AppKiDo so that the various URLs it uses point to rather than the now-defunct MobileMe site. The new version requires 10.5; I got tired of not using fast enumeration.

While I was at it I added the system service I mentioned I was thinking about, and I used NSServiceCategory to put it in the "Searching" category of the Services menu. I considered putting it in the "Development" category, but I think what AppKiDo does is more akin to Dictionary than to Instruments.

Services appkido

I made the search service "method-aware", which means it tries to detect method names in your text selection. I'm hoping other doc-search apps will add this feature. They can use my simple method-parsing class, if it helps. For people who have use for the method extraction but don't want to launch AppKiDo, it might be nice to put it into a standalone Service, but I don't know if I'll get to that.

The rest of this post will be an explanation of the "method-aware" feature, copied from the release notes.

"Look Up in AppKiDo" is a service that you can invoke from any application where you have selected some text. It activates AppKiDo and performs a search for that text.

Often what we want to search for is a method name. AppKiDo tries to help by determining whether the selected text contains an Objective-C message-send or method declaration. If so, it searches for that method name. Otherwise, it searches for the literal text you have selected.

For example, in Xcode if you have [self flyToX:100 y:200 z:300], you can double-click one of the square brackets to select the whole expression, then invoke this service. AppKiDo will search for the method name flyToX:y:z:.

If you happen to be in BBEdit, where double-clicking a bracket selects the text inside the brackets, the service should still work. If there is leading whitespace or a cast, or newlines or comments anywhere, it should still work, so if you have lines like this you can select them all and then invoke the service:

(void)[self flyToX:100  // cast to void to discard the return value
                 z:900 /*300*/];

Note that "Look Up in AppKiDo" doesn't work if there is an assignment in the selected text. For example, it won't work if you select this whole line:

BOOL didFly = [self flyToX:100 y:200 z:300];

The workaround is to select just the message-send — the part after the "=".

Another intended use is when you're looking at code that declares a method and you want to search for that method name. For example, you can select these lines and it will search for browser:child:ofItem: (the "-(id)" will be ignored):

- (id)browser:(NSBrowser *)browser

This service assumes well-formed Objective-C. You might get unexpected results otherwise. If there are nested messages, it uses the top-level one. The algorithm mainly looks at punctuation — delimiters like brackets and a few other characters that need special treatment. The basic idea is that it ignores anything between delimiters, like (blah blah blah), [blah blah blah], or {blah blah blah}. For this reason it should work if your selected code contains blocks or the new object literals.

If you use the "Look Up" service, remember to assign a hotkey in System Preferences > Keyboard > Keyboard Shortcuts > Services for maximum convenience.

Docsets can have bugs

If you've grabbed the latest AppKiDo (0.988; release notes here) and it seems to be a bit slower starting up, here's why.

AppKiDo is a documentation browser for the Cocoa APIs. As such, it needs information about all sorts of API symbols: class names, protocol names, method names, function names, typedefs, and constants. This information is scraped from two places: Objective-C header files and special bundles called docsets.

A docset contains a bunch of HTML files (the documentation) along with a "docset index" — a SQLite database file named docSet.dsidx. One thing the docset index does is it maps each API symbol to two file paths:

  • The header file where the symbol is declared.
  • The HTML file where the symbol is documented.

The way AppKiDo is designed, it needs to know what framework every symbol belongs to. NSString and its methods belong to the Foundation framework, NSView and its methods belong to the AppKit framework, and so on. I've been getting this framework information by querying two tables in the docset index:

  • ZHEADER, which contains paths to header files, along with the name of the framework each header file belongs to. NSString.h belongs to the Foundation framework, NSView.h belongs to the AppKit framework, and so on.
  • ZTOKENMETAINFORMATION, which contains information about each API symbol, including a foreign key to ZHEADER.

(By the way, the docset index is actually a Core Data database. Normally one shouldn't access Core Data's underlying tables directly, but AppKiDo's access is read-only so there's no risk of corrupting the database, and SQL queries were simpler to implement than reverse-engineering the managed object model. Also, the docset index is relatively static, although that's a topic for another discussion.)

Now here's the thing. For some symbols, ZTOKENMETAINFORMATION is missing the foreign key to ZHEADER. This means you can't tell purely by querying the database which header file those symbols are declared in or which framework they belong to.

As far as I can tell, this is simply a bug in the docset. I suspect if I studied up on how docsets get created (which I should really do someday) I'd have some idea why the bug is there. In any case, it's there, and when I get a chance to write it up in more detail I'll submit a Radar.

If you have Xcode 4, and if Apple hasn't fixed the docset by the time you read this, you can see an example of this. In an iOS project, type "NSFetchedResultsControllerDelegate" and Option-click on it. You'll see that the Quick Help popup is missing the "Declared In" line that appears for most other symbols.

Missing the

Compare this to the Quick Help popup for NSString:

Showing the

Because AppKiDo (or, for iOS docs, AppKiDo-for-iPhone) can't tell what framework the affected symbols belong to, it never loads the documentation for those symbols.

My workaround was to add a second query that assumes the path to a symbol's HTML file contains the name of its framework. This is not always true, but it's true often enough for AppKiDo to figure out that NSFetchedResultsControllerDelegate belongs to Core Data.

Doing the second query means AppKiDo takes a bit longer to start up, which you may or may not be able to tell given how long startup is already. You probably wouldn't notice except that the query contains a LIKE clause, which makes it relatively slow.

For more gory details, you can see the relevant commit on GitHub.

So now you know.

How to stop AppKiDo 0.983 from crashing on launch

If AppKiDo 0.983 is crashing on launch, try going into Xcode and updating the Leopard Core Library docset. If it's the same problem a couple of people have reported, the crashing should go away. If you're not sure how to update the docset, see my next post.

(Note this is not a problem for AppKiDo-for-iPhone, so if that's crashing for you please let me know.)

Here's what was causing the crash. During startup, AppKiDo reads a property called "DocSetPlatformVersion" from a plist file in the Cocoa docset bundle. There was at least one version of the docs where that value was missing from the plist, and since I don't check for this, I ended up trying to insert nil into an array. Updating your docs gets rid of the crash by fixing the plist.

Yeah, I shouldn't be hacking around in undocumented plists (setting aside the more grievous error of not checking for nil), but the fact is that AppKiDo relies almost totally on undocumented file, directory, and database structures. There are ways I can reduce this dependency (especially when I get around to requiring Xcode 3 as a minimum), but I don't see the hackishness going completely away any time soon.

At some point I'll release an update to handle situations like this more gracefully.

Thanks to Chris Backas for helping to track this down.

Crashing bug in 0.983

I should probably have released one more sneakypeek before pushing 0.983. I've gotten a couple of reports of a crash on launch. Haven't found the problem yet, but it's totally reproducible, the stack traces look the same, and it's almost certainly a dumb error I could have caught by programming more defensively. On my list for the next minor release: more robust and informative error handling.

(The next major release, where I get rid of the long launch, is still a long ways away.)

UPDATE: Found it.