There, I fixed Xcode—You’re Welcome.
Update: Xcode 4 ships with a brand new completion engine and UI that makes my hackjob plugin look like a toy. Go use that instead!
In my last installment, I discussed Xcode’s frustrating implementation of code completion.
I’ve now taken matters into my own hands.
It turns out Xcode has an (undocumented) plugin system. And it also turns out that this guy created a really fantastic plugin for Xcode that automatically inserts a matching opening bracket ‘
[’ when typing the closing bracket ‘
Now, it turns out Apple added this bracket-insertion feature on their own, possibly being inspired by this, so the plugin is now just reference material.
Really useful reference material for doing what I want to do.
I’ll save the nitty-gritty details of how the plugin works for a later post (or just browse the source). Let’s just say that we have access to keystrokes as you type them in Xcode. How would we use that to achieve automatic completion list display?
Take 1: Popup all the time!
The most useful behavior, it seems, is for the popup list to appear as soon as you type a reasonable character that can be completed, such as an alphabetical character. Here we’ll type ‘
u’ because we want
So that’s the first thing I implemented. Listen for alphabetical character, popup the list.
The problem turns out to be that the list takes “a while” to appear, because we’re in Objective-C which is still C and the number of legal symbols you can type in a particular location is immense. I mean look at that tiny scrollbar! There’s all manner of detritus in there like
You might think, no problem, I’ll just ignore the popup and keep typing because the list will narrow itself down as I go. But, in practice, it turns out to be extremely annoying because you can’t type small words like “
if” and “
for” without the popup list slowing you down.
Examine our options
I imagine that Apple encountered this same problem, and it seems the way they addressed it was by analyzing the number of items that would match and only giving you a suggested completion if the number of possible matches is small.
This is why, with Apple’s completion, you must type “
nss” before it will automatically suggest something like “
NSString”. If you just type “
ns” you can sit there forever and it’ll never suggest anything. But if you type “
cl” it might suggest “
CLLocation” because there aren’t a lot of other things starting with “
My beef with this approach is that it’s unpredictable. How do I know how many letters I’m supposed to type before I can press
enter and move on?
Take 2: Smart delay
The solution turned out to be surprisingly simple. Display the list after typing any alphanumeric character, but with a delay proportional to the number of characters in the word entered thus far. On the 4th character and beyond, the list always appears immediately.
This way you can type “
uivi[enter]” as fast as possible and you get
UIViewController (depending on your typing history, but this is intuitive).
By the time you get to the 4th character, the list is almost always reasonably small and therefore much more helpful.
If you just type “
u” and forget what you were doing, we’ll wait about 0.4 seconds before presenting the list to help you.
More importantly, you can type “
for (int i = 0…” at normal typing speed without the popup appearing at all, and once you get stuck on something you’ll pause naturally and only then will the list appear.
Of course, you don’t want the list appearing on just any alphanumeric character: If you’re writing a string or a comment, the list isn’t going to help you. So the plugin does some extra work to detect those situations. It’s not a full-on lexer but it seems to work 99% of the time.
Additionally, if you type a period ‘
.’ to access a struct member or ObjC property, the list always appears immediately.
One last neat feature: When you type a semicolon, it will be automatically moved to the end of the line as necessary. I don’t know about you, but I often find myself in this kind of situation:
…having just typed a method name and needing to cap off the line with a semicolon. Normally I’d have to go find the right arrow key first, but with the plugin you just type ‘
;’ and it puts it in the right place.