More Fun with Autocomplete
Today I did some more stuff with autocomplete in NSTextView
. What I wanted to achieve is have a list of suggestions for the autocomplete, but have the text that will actually be inserted in the view differ from the suggestions. Have a look at the picture below to see what I mean. You can see that each entry in the suggestions list starts out with "iCal Event: ...". The one selected at the moment is "iCal Event: Meeting with Rory O'Connor" - however, the text that gets inserted in the view is just "Meeting with Rory O'Connor".
Now, why is that exciting? Because the way autocomplete works is that NSTextView
's completionsForPartialWordRange:indexOfSelectedItem:
(or the corresponding delegate method) just returns an array of strings, and those strings will both be shown in the suggestion dropdown box and be string that will actually inserted. So, the first task is to have the insertion be different from the suggestion. This can be achieved by overriding NSTextView
's insertCompletion:forPartialWordRange:movement:isFinal:
, which is responsible for the actual insertion. You could do something like this:
- (void)insertCompletion: (NSString *)word forPartialWordRange: (NSRange)charRange movement: (int)movement isFinal: (BOOL)flag { [super insertCompletion: @"not the same" forPartialWordRange: charRange movement: movement isFinal: flag]; }
This would always insert "not the same", regardless of what the user had chosen from the list. Amazing, but not very useful yet. To make it a little more useful, I guess there are a number of options. One could have a lookup function (e.g. via an NSDictionary
) to get from word
to whatever we want. Or a simple transformation, to get rid of the leading "iCal Event: ".
However, I chose to go a different route - extending NSString
so that I could ask word
for the string that should be inserted, along the lines of [word completionString]
. Categories will not do that for me, because they don't allow me to define additional instance variables (i.e. the completionString), so I had to subclass NSString
. There is a little complication here, because NSString
is the abstract superclass of a class cluster, so subclassing isn't as straight-forward as in other cases. I need to override the primitive methods length
and characterAtIndex:
, and I need to store the actual string. The interface for my NSString
subclass looks like this:
@interface MultiString : NSString { NSString *completionString; NSString *string; } - (id)initWithString: (NSString*)string1 andCompletionString: (NSString*)string2; - (NSString *)string; - (NSString *)completionString; // primitive methods I need to implement: - (unsigned int)length; - (unichar)characterAtIndex: (unsigned)index; @end
The implementation looks like this:
@implementation MultiString - (id)initWithString: (NSString*)string1 andCompletionString: (NSString*)string2 { self = [super init]; if (self) { string = string1; completionString = string2; } return self; } - (NSString *)string { return string; } - (NSString *)completionString { return completionString; } - (unsigned int)length { return [string length]; } - (unichar)characterAtIndex: (unsigned)index { return [string characterAtIndex: index]; } @end
Having this, I can use MultiString
to generate strings with two representations, add them to the autocompletion array, and let insertCompletion:forPartialWordRange:movement:isFinal:
return the completionString
instead of the "normal" string. This is what it looks like:
- (void)insertCompletion: (NSString *)word forPartialWordRange: (NSRange)charRange movement: (int)movement isFinal: (BOOL)flag { if ([word isKindOfClass: [MultiString class]]) { [super insertCompletion: [word completionString] forPartialWordRange: charRange movement: movement isFinal: flag]; } else { [super insertCompletion: word forPartialWordRange: charRange movement: movement isFinal: flag]; } }
You might wonder why I test for the class of word
. The reason is that, if the user aborts the autocomplete process (by hitting escape again), none of the elements of the completion arrays will be inserted, but instead the original word from the text view. Now, this word is not of class MultiString
, so calling completionString
on it would result in an error, and insertCompletion:forPartialWordRange:movement:isFinal:
would not successfully return anything.
That's all!
0 Comments:
Post a Comment
<< Home