adium 2610:d6bb9d4249fc: Use NSPredicate for mention detection. ...
commits at adium.im
commits at adium.im
Fri Aug 21 19:03:22 UTC 2009
details: http://hg.adium.im/adium/rev/d6bb9d4249fc
revision: 2610:d6bb9d4249fc
author: Stephen Holt <sholt at adium.im>
date: Fri Aug 21 15:08:10 2009 -0400
Use NSPredicate for mention detection. Fixes #12831.
Refactor our mention filter to search for strings based on NSPredicate matches. Aside from simplifying the main matching loop, this also lets users seach using ICU regexps like so: ```/[mM]atchng\s[sS]tring/```.
All matches are case and diacritic insensitive.
See http://userguide.icu-project.org/strings/regexp for details on expression syntax.
diffs (269 lines):
diff -r d063b5446352 -r d6bb9d4249fc Frameworks/AIUtilities Framework/Source/AIStringAdditions.h
--- a/Frameworks/AIUtilities Framework/Source/AIStringAdditions.h Thu Aug 20 11:02:19 2009 -0400
+++ b/Frameworks/AIUtilities Framework/Source/AIStringAdditions.h Fri Aug 21 15:08:10 2009 -0400
@@ -53,6 +53,8 @@
- (NSString *)stringByUnescapingFromXMLWithEntities:(NSDictionary *)entities;
- (NSString *)stringByEscapingForShell;
+- (NSString *)stringByEscapingForRegexp;
+
//- (BOOL)isURLEncoded;
- (NSString *)stringByAddingPercentEscapesForAllCharacters;
diff -r d063b5446352 -r d6bb9d4249fc Frameworks/AIUtilities Framework/Source/AIStringAdditions.m
--- a/Frameworks/AIUtilities Framework/Source/AIStringAdditions.m Thu Aug 20 11:02:19 2009 -0400
+++ b/Frameworks/AIUtilities Framework/Source/AIStringAdditions.m Fri Aug 21 15:08:10 2009 -0400
@@ -527,6 +527,7 @@
enum characterNatureMask {
whitespaceNature = 0x1, //space + \t\n\r\f\a
shellUnsafeNature, //backslash + !$`"'
+ regexpUnsafeNature, //blakslash + |.*+?{}()$^
};
static enum characterNatureMask characterNature[USHRT_MAX+1] = {
//this array is initialised such that the space character (0x20)
@@ -650,6 +651,96 @@
return result;
}
+- (NSString *)stringByEscapingForRegexp
+{
+ if (!(characterNature[' '] & whitespaceNature)) {
+ //if space doesn't have the whitespace nature, clearly we need to build the nature array.
+
+ //first, set all characters to zero.
+ bzero(&characterNature, sizeof(characterNature));
+
+ //then memorise which characters have the whitespace nature.
+ characterNature[' '] = whitespaceNature;
+ //NOTE: if you give more characters the whitespace nature, be sure to
+ // update escapeNames below.
+
+ //finally, memorise which characters have the unsafe (for regexp) nature.
+ characterNature['\\'] = regexpUnsafeNature;
+ characterNature['/'] = regexpUnsafeNature;
+ characterNature['|'] = regexpUnsafeNature;
+ characterNature['.'] = regexpUnsafeNature;
+ characterNature['*'] = regexpUnsafeNature;
+ characterNature['+'] = regexpUnsafeNature;
+ characterNature['?'] = regexpUnsafeNature;
+ characterNature['{'] = regexpUnsafeNature;
+ characterNature['}'] = regexpUnsafeNature;
+ characterNature['('] = regexpUnsafeNature;
+ characterNature[')'] = regexpUnsafeNature;
+ characterNature['['] = regexpUnsafeNature;
+ characterNature['$'] = regexpUnsafeNature;
+ characterNature['^'] = regexpUnsafeNature;
+ }
+
+ unsigned myLength = [self length];
+ unichar *myBuf = malloc(sizeof(unichar) * myLength);
+ if (!myBuf) return nil;
+ [self getCharacters:myBuf];
+ const unichar *myBufPtr = myBuf;
+
+ size_t buflen = 0;
+ unichar *buf = NULL;
+
+ const size_t buflenIncrement = getpagesize() / sizeof(unichar);
+
+ /*the boundary guard happens everywhere that i increases, and MUST happen
+ * at the beginning of the loop.
+ *
+ *initialising buflen to 0 and buf to NULL as we have done above means that
+ * realloc will act as malloc:
+ * - i is 0 at the beginning of the loop
+ * - so is buflen
+ * - and buf is NULL
+ * - realloc(NULL, ...) == malloc(...)
+ *
+ *oh, and 'SBEFR' stands for String By Escaping For Regexp
+ * (the name of this method).
+ */
+#define SBEFR_BOUNDARY_GUARD \
+do { \
+if (i == buflen) { \
+buf = realloc(buf, sizeof(unichar) * (buflen += buflenIncrement)); \
+if (!buf) { \
+NSLog(@"in stringByEscapingForRegexp: could not allocate %lu bytes", (unsigned long)(sizeof(unichar) * buflen)); \
+free(myBuf); \
+return nil; \
+} \
+} \
+} while (0)
+
+ unsigned i = 0;
+ for (; myLength--; ++i) {
+ SBEFR_BOUNDARY_GUARD;
+
+ if (characterNature[*myBufPtr] & regexpUnsafeNature) {
+ //escape this character
+ buf[i++] = '\\';
+ SBEFR_BOUNDARY_GUARD;
+ }
+
+ buf[i] = *myBufPtr;
+ ++myBufPtr;
+ }
+
+#undef SBEFR_BOUNDARY_GUARD
+
+ free(myBuf);
+
+ NSString *result = [NSString stringWithCharacters:buf length:i];
+ free(buf);
+
+ return result;
+}
+
- (NSString *)volumePath
{
NSEnumerator *pathEnum = [[[NSWorkspace sharedWorkspace] mountedLocalVolumePaths] objectEnumerator];
diff -r d063b5446352 -r d6bb9d4249fc Source/AIMentionAdvancedPreferences.h
--- a/Source/AIMentionAdvancedPreferences.h Thu Aug 20 11:02:19 2009 -0400
+++ b/Source/AIMentionAdvancedPreferences.h Fri Aug 21 15:08:10 2009 -0400
@@ -7,6 +7,8 @@
#import <Adium/AIAdvancedPreferencePane.h>
+#define PREF_KEY_MENTIONS @"Saved Mentions"
+
@interface AIMentionAdvancedPreferences : AIAdvancedPreferencePane {
IBOutlet NSTextField *label_explanation;
diff -r d063b5446352 -r d6bb9d4249fc Source/AIMentionAdvancedPreferences.m
--- a/Source/AIMentionAdvancedPreferences.m Thu Aug 20 11:02:19 2009 -0400
+++ b/Source/AIMentionAdvancedPreferences.m Fri Aug 21 15:08:10 2009 -0400
@@ -11,8 +11,6 @@
#import <AIUtilities/AIImageAdditions.h>
#import <AIUtilities/AIArrayAdditions.h>
-#define PREF_KEY_MENTIONS @"Saved Mentions"
-
@interface AIMentionAdvancedPreferences()
- (void)saveTerms;
@end
diff -r d063b5446352 -r d6bb9d4249fc Source/AIMentionEventPlugin.h
--- a/Source/AIMentionEventPlugin.h Thu Aug 20 11:02:19 2009 -0400
+++ b/Source/AIMentionEventPlugin.h Fri Aug 21 15:08:10 2009 -0400
@@ -14,12 +14,14 @@
* write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
+#import <AIUtilities/AIStringAdditions.h>
#import <Adium/AIContentControllerProtocol.h>
-
+#import <Adium/AIPreferenceControllerProtocol.h>
#import "AIMentionAdvancedPreferences.h"
@interface AIMentionEventPlugin : AIPlugin <AIContentFilter> {
- AIMentionAdvancedPreferences *advancedPreferences;
+ AIMentionAdvancedPreferences *advancedPreferences;
+ NSArray *mentionPredicates;
}
-
+ at property(copy, nonatomic) NSArray *mentionPredicates;
@end
diff -r d063b5446352 -r d6bb9d4249fc Source/AIMentionEventPlugin.m
--- a/Source/AIMentionEventPlugin.m Thu Aug 20 11:02:19 2009 -0400
+++ b/Source/AIMentionEventPlugin.m Fri Aug 21 15:08:10 2009 -0400
@@ -25,7 +25,6 @@
#import <Adium/AIChat.h>
#import <Adium/AIContactAlertsControllerProtocol.h>
-#define PREF_KEY_MENTIONS @"Saved Mentions"
/*!
* @class AIMentionEventPlugin
@@ -33,6 +32,8 @@
*/
@implementation AIMentionEventPlugin
+ at synthesize mentionPredicates;
+
/*!
* @brief Install
*/
@@ -41,6 +42,9 @@
[adium.contentController registerContentFilter:self
ofType:AIFilterContent
direction:AIFilterIncoming];
+
+ [adium.preferenceController registerPreferenceObserver:self
+ forGroup:PREF_GROUP_GENERAL];
advancedPreferences = [[AIMentionAdvancedPreferences preferencePaneForPlugin:self] retain];
}
@@ -68,33 +72,28 @@
NSString *messageString = [inAttributedString string];
AIAccount *account = (AIAccount *)message.destination;
+ NSString *contactAlias = [chat aliasForContact:[account contactWithUID:account.UID]];
// XXX When we fix user lists to contain accounts, fix this too.
- NSArray *myNames = [NSArray arrayWithObjects:
- account.UID,
- account.displayName,
- /* can be nil */ [chat aliasForContact:[account contactWithUID:account.UID]],
- nil];
+ NSArray *myPredicates = [NSArray arrayWithObjects:
+ [NSPredicate predicateWithFormat:@"SELF MATCHES[cd] %@", [NSString stringWithFormat:@".*\\b%@\\b.*", [account.UID stringByEscapingForRegexp]]],
+ [NSPredicate predicateWithFormat:@"SELF MATCHES[cd] %@", [NSString stringWithFormat:@".*\\b%@\\b.*", [account.displayName stringByEscapingForRegexp]]],
+ /* can be nil */ contactAlias? [NSPredicate predicateWithFormat:@"SELF MATCHES[cd] %@", [NSString stringWithFormat:@".*\\b%@\\b.*", [contactAlias stringByEscapingForRegexp]]] : nil,
+ nil];
- myNames = [myNames arrayByAddingObjectsFromArray:[adium.preferenceController preferenceForKey:PREF_KEY_MENTIONS group:PREF_GROUP_GENERAL]];
-
- for(NSString *checkString in myNames) {
- NSRange range = [messageString rangeOfString:checkString options:NSCaseInsensitiveSearch];
+ myPredicates = [myPredicates arrayByAddingObjectsFromArray:self.mentionPredicates];
- if(range.location != NSNotFound &&
- (range.location == 0 || ![[NSCharacterSet alphanumericCharacterSet] characterIsMember:[messageString characterAtIndex:range.location-1]]) &&
- (range.location + range.length >= [messageString length] || ![[NSCharacterSet alphanumericCharacterSet] characterIsMember:[messageString characterAtIndex:range.location+range.length]]))
- {
+ for(NSPredicate *predicate in myPredicates) {
+ if([predicate evaluateWithObject:messageString]) {
if(message.trackContent && adium.interfaceController.activeChat != chat) {
[chat incrementUnviewedMentionCount];
}
-
[message addDisplayClass:@"mention"];
-
+ AILog(@"MENTIONED!");
break;
}
}
-
+
return inAttributedString;
}
@@ -106,4 +105,28 @@
return LOWEST_FILTER_PRIORITY;
}
+/*!
+ * @brief Rebuild predicates on preference saves.
+ */
+#pragma mark Preference Observing
+- (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
+{
+ if(firstTime || [key isEqualToString:PREF_KEY_MENTIONS]) {
+ AILog(@"building mentions...");
+ NSArray *allMentions = [adium.preferenceController preferenceForKey:PREF_KEY_MENTIONS group:PREF_GROUP_GENERAL];
+ NSMutableArray *predicates = [NSMutableArray arrayWithCapacity:[allMentions count]];
+ NSPredicate *regexPredicate = [NSPredicate predicateWithFormat:@"SELF MATCHES '/.*/'"];
+
+ for (NSString *mention in allMentions) {
+ if([regexPredicate evaluateWithObject:mention]) {
+ [predicates addObject:[NSPredicate predicateWithFormat:@"SELF MATCHES[cd] %@", [NSString stringWithFormat:@".*%@.*", [mention substringWithRange:NSMakeRange(1, [mention length]-2)]]]];
+ } else {
+ AILog(@"%@ -> %@", mention, [mention stringByEscapingForRegexp]);
+ [predicates addObject:[NSPredicate predicateWithFormat:@"SELF MATCHES[cd] %@", [NSString stringWithFormat:@".*\\b%@\\b.*", [mention stringByEscapingForRegexp]]]];
+ }
+ }
+ self.mentionPredicates = predicates;
+ }
+}
+
@end
More information about the commits
mailing list