Control more directly the completion range for group chats. Fixes #13237.
We now go back to the nearest whitespace and see if it would autocomplete any nicks at that point. If it would, we perform the autocompletion starting at that point. If it doesn't, we let the OS do its normal thing.
This fixes autocompleting things like "[mynick]" or the channel name (such as "#adium") since they start with non-word characters, but are perfectly valid.
1.1 --- a/Frameworks/Adium Framework/Source/AIMessageEntryTextView.h Sat Oct 24 18:29:23 2009 -0400
1.2 +++ b/Frameworks/Adium Framework/Source/AIMessageEntryTextView.h Sun Oct 25 21:21:42 2009 -0400
1.3 @@ -26,6 +26,7 @@
1.4 * @brief Should the tab key trigger an autocomplete?
1.5 */
1.6 - (BOOL)textViewShouldTabComplete:(NSTextView *)inTextView;
1.7 +- (NSRange)textView:(NSTextView *)inTextView rangeForCompletion:(NSRange)completionRange;
1.8 - (void)textViewDidCancel:(NSTextView *)inTextView;
1.9 @end
1.10
2.1 --- a/Frameworks/Adium Framework/Source/AIMessageEntryTextView.m Sat Oct 24 18:29:23 2009 -0400
2.2 +++ b/Frameworks/Adium Framework/Source/AIMessageEntryTextView.m Sun Oct 25 21:21:42 2009 -0400
2.3 @@ -1504,6 +1504,18 @@
2.4 group:PREF_GROUP_DUAL_WINDOW_INTERFACE];
2.5 }
2.6
2.7 +#pragma mark Autocompleting
2.8 +- (NSRange)rangeForUserCompletion
2.9 +{
2.10 + NSRange completionRange = [super rangeForUserCompletion];
2.11 +
2.12 + if ([self.delegate respondsToSelector:@selector(textView:rangeForCompletion:)]) {
2.13 + completionRange = [self.delegate textView:self rangeForCompletion:completionRange];
2.14 + }
2.15 +
2.16 + return completionRange;
2.17 +}
2.18 +
2.19 #pragma mark Writing Direction
2.20 - (void)toggleBaseWritingDirection:(id)sender
2.21 {
3.1 --- a/Plugins/Dual Window Interface/AIMessageViewController.m Sat Oct 24 18:29:23 2009 -0400
3.2 +++ b/Plugins/Dual Window Interface/AIMessageViewController.m Sun Oct 25 21:21:42 2009 -0400
3.3 @@ -90,6 +90,8 @@
3.4 - (void)setUserListVisible:(BOOL)inVisible;
3.5 - (void)setupShelfView;
3.6 - (void)updateUserCount;
3.7 +
3.8 +- (NSArray *)contactsMatchingBeginningString:(NSString *)partialWord;
3.9 @end
3.10
3.11 @implementation AIMessageViewController
3.12 @@ -974,6 +976,12 @@
3.13 }
3.14
3.15 #pragma mark Autocompletion
3.16 +- (BOOL)canTabCompleteForPartialWord:(NSString *)partialWord
3.17 +{
3.18 + return ([self contactsMatchingBeginningString:partialWord].count > 0 ||
3.19 + [self.chat.displayName rangeOfString:partialWord options:(NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound);
3.20 +}
3.21 +
3.22 /*!
3.23 * @brief Should the tab key cause an autocompletion if possible?
3.24 *
3.25 @@ -981,55 +989,90 @@
3.26 */
3.27 - (BOOL)textViewShouldTabComplete:(NSTextView *)inTextView
3.28 {
3.29 - return self.chat.isGroupChat;
3.30 + if (self.chat.isGroupChat) {
3.31 + NSRange completionRange = inTextView.rangeForUserCompletion;
3.32 + NSString *partialWord = [inTextView.textStorage.string substringWithRange:completionRange];
3.33 + return [self canTabCompleteForPartialWord:partialWord];
3.34 + }
3.35 +
3.36 + return NO;
3.37 +}
3.38 +
3.39 +- (NSRange)textView:(NSTextView *)inTextView rangeForCompletion:(NSRange)charRange
3.40 +{
3.41 + if (self.chat.isGroupChat && charRange.location > 0) {
3.42 + NSString *partialWord = nil;
3.43 + NSString *allText = [inTextView.textStorage.string substringWithRange:NSMakeRange(0, NSMaxRange(charRange))];
3.44 + NSRange whitespacePosition = [allText rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet] options:NSBackwardsSearch];
3.45 +
3.46 + if (whitespacePosition.location == NSNotFound) {
3.47 + // We went back to the beginning of the string and still didn't find a whitespace; use the whole thing.
3.48 + partialWord = allText;
3.49 + whitespacePosition = NSMakeRange(0, 0);
3.50 + } else {
3.51 + // We found a whitespace, use from it until our current position.
3.52 + partialWord = [allText substringWithRange:NSMakeRange(NSMaxRange(whitespacePosition), allText.length - NSMaxRange(whitespacePosition))];
3.53 + }
3.54 +
3.55 + // If this matches any contacts or the room name, use this new range for autocompletion.
3.56 + if ([self canTabCompleteForPartialWord:partialWord]) {
3.57 + charRange = NSMakeRange(NSMaxRange(whitespacePosition), allText.length - NSMaxRange(whitespacePosition));
3.58 + }
3.59 + }
3.60 +
3.61 + return charRange;
3.62 +}
3.63 +
3.64 +- (NSArray *)contactsMatchingBeginningString:(NSString *)partialWord
3.65 +{
3.66 + NSMutableArray *contacts = [NSMutableArray array];
3.67 +
3.68 + for (AIListContact *listContact in self.chat) {
3.69 + if ([listContact.UID rangeOfString:partialWord
3.70 + options:(NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound ||
3.71 + [listContact.formattedUID rangeOfString:partialWord
3.72 + options:(NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound ||
3.73 + [listContact.displayName rangeOfString:partialWord
3.74 + options:(NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound) {
3.75 + [contacts addObject:listContact];
3.76 + }
3.77 + }
3.78 +
3.79 + return contacts;
3.80 }
3.81
3.82 - (NSArray *)textView:(NSTextView *)textView completions:(NSArray *)words forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index
3.83 {
3.84 - NSMutableArray *completions;
3.85 + NSMutableArray *completions = nil;
3.86
3.87 if (self.chat.isGroupChat) {
3.88 - NSString *partialWord = [[textView.textStorage attributedSubstringFromRange:charRange] string];
3.89 -
3.90 + NSString *suffix = nil;
3.91 + NSString *partialWord = [textView.textStorage.string substringWithRange:charRange];
3.92 BOOL autoCompleteUID = [self.chat.account chatShouldAutocompleteUID:self.chat];
3.93
3.94 - NSString *suffix;
3.95 + //At the start of a line, append ": "
3.96 if (charRange.location == 0) {
3.97 - //At the start of a line, append ": "
3.98 suffix = @": ";
3.99 - } else {
3.100 - suffix = nil;
3.101 }
3.102
3.103 completions = [NSMutableArray array];
3.104 - for(AIListContact *listContact in self.chat) {
3.105 - if ([listContact.displayName rangeOfString:partialWord
3.106 - options:(NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound ||
3.107 - [listContact.formattedUID rangeOfString:partialWord
3.108 - options:(NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound ||
3.109 - [listContact.UID rangeOfString:partialWord
3.110 - options:(NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound) {
3.111 -
3.112 - NSString *displayName = [self.chat aliasForContact:listContact];
3.113 -
3.114 - if (!displayName) {
3.115 - displayName = autoCompleteUID ? listContact.formattedUID : listContact.displayName;
3.116 - }
3.117 -
3.118 - [completions addObject:(suffix ? [displayName stringByAppendingString:suffix] : displayName)];
3.119 - }
3.120 +
3.121 + for (AIListContact *listContact in [self contactsMatchingBeginningString:partialWord]) {
3.122 + NSString *displayName = [self.chat aliasForContact:listContact];
3.123 +
3.124 + if (!displayName)
3.125 + displayName = autoCompleteUID ? listContact.formattedUID : listContact.displayName;
3.126 +
3.127 + [completions addObject:(suffix ? [displayName stringByAppendingString:suffix] : displayName)];
3.128 }
3.129
3.130 - if ([self.chat.name rangeOfString:partialWord options:(NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound) {
3.131 - [completions addObject:self.chat.name];
3.132 + if ([self.chat.displayName rangeOfString:partialWord options:(NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound) {
3.133 + [completions addObject:self.chat.displayName];
3.134 }
3.135
3.136 if ([completions count]) {
3.137 *index = 0;
3.138 }
3.139 -
3.140 - } else {
3.141 - completions = nil;
3.142 }
3.143
3.144 return [completions count] ? completions : words;