The Glaring Hole in the NSTextInputClient Protocol

The fine folks at Apple generally do a pretty good job of designing the programmatic interfaces, "protocols" in Objective-C speak, that the system and applications use to communicate with each other. I mean that most sincerely. Designing programmatic interfaces is hard, because you have to anticipate the needs of different system components. Sometimes, you have to do that before relevant components have been written, so you'll not always anticipate those needs in a way that leads to a suitable user experience.

The NSTextInputClient protocol has a classic example of this problem. Take some time to read the reference, and see if you can spot the hole. Go ahead. Take your time. I'll wait.

Did you spot it? No? OK. Try this:

  1. Make sure you have the Kotoeri input source enabled (System Preferences/Language & Text/Input Sources; check Kotoeri and at least the Hirigana sub-item). You'll also want to check the "Show Input menu on menu bar" check box.
  2. Launch TextEdit
  3. Go to the Input menu (a US flag if you're a US user), and select "Hiragana"
  4. Type "sa"<return><space>"sa"<return>"
  5. Back up a character, without selecting it, by pressing the left arrow key
  6. On the Input menu, under Kotoeri, select "Reverse Conversion"

At this point, you'll get a popup menu that provides a number of alternative characters to be inserted instead of the default "さ" that was inserted in place of the second "sa" that you typed in step 4.

What just happened? Well, you asked the Kotoeri IME (Input Method Editor) to try to reconvert some text in the document. At that point, Kotoeri needs to figure out what text you want to reconvert. Now, put yourself in the place of someone who's implementing this feature in Kotoeri. Go back to the reference for the NSTextInputClient protocol, and ask yourself, how do I implement this feature?

Well, the first thing you'll do is, see if the user has any text selected, because that's a pretty clear indication of exactly which text the user wants to reconvert. So, you'll call the selectedRange method. If the selected range isn't empty, then you'll just call attributedSubstringForProposedRange:actualRange: passing the selected range you just got from calling the selectedRange method.

In the steps above, however, the user selection is an insertion point. The selectedRange method returns an empty range. Now what? You'll need to probe the document content to figure out what text to reconvert. But, the insertion point can be at the end of the document. As a Kotoeri implementer, the first question you're likely to ask is, how long is the document? Unfortunately, there's no method in the NSTextInputClient protocol that provides a direct answer to this question. Thus, the glaring hole in the protocol.

At this point, as someone who is implementing an input method editor, you have two options. You can create a reasonable range of text to probe based on the location of the insertion point, and call attributedSubstringForProposedRange:actualRange: paying attention to the actualRange value that you get back. Or, you can simply call the attributedString method, and get the length of the result.

It's possible that the Kotoeri IME has been revised for Lion, but, as of Snow Leopard, Kotoeri actually uses the second option. And, note that the attributedString method is optional. If you've written an application that implements the NSTextInputClient protocol but have omitted an implementation for the attributedString method, then nothing will happen if the user selects "Reverse Conversion" in the above scenario.

OK, so what's the big deal? The protocol does provide a solution to the problem from the IME's perspective, so everything's fine, right? Well, not quite. Let's put our end-user hats back on. Some users have documents that are thousands of pages long. You do not want to implement the attributedString method in a way that simply returns a standard NSAttributedString, or even just a regular NSString. Both the performance and the memory usage is not likely to lead to a satisfying end-user experience for people who spend their days editing long documents.

Careful readers of the NSString class reference will figure out a reasonable solution to this problem, but a more robust design for the NSTextInputClient protocol might have obviated the need for applications developers to pay such close attention. Having said that, it's probably not fair to either Apple systems engineers or the folks who implemented the Kotoeri IME to argue that they should have thought of this. Anticipating this kind of corner case is something even very smart people will miss.



Currently playing in iTunes: Days Is Almost Gone by The Derek Trucks Band