William Bowling is sharing code with you
Bitbucket is a code hosting site. Unlimited public and private repositories. Free for small teams.
Don't show this again1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | //
// AIIRCChannelLinker.m
// Adium
//
// Created by Zachary West on 2009-04-02.
// Copyright 2009 __MyCompanyName__. All rights reserved.
//
#import "AIIRCChannelLinker.h"
#import <Adium/AIContentControllerProtocol.h>
#import <Adium/AIContentObject.h>
#import <Adium/AIChat.h>
#import <Adium/AIAccount.h>
#import <Adium/AIService.h>
#import <Adium/AIListContact.h>
#import <AIUtilities/AIAttributedStringAdditions.h>
@implementation AIIRCChannelLinker
- (void)installPlugin
{
//Register us as a filter
[adium.contentController registerContentFilter:self ofType:AIFilterMessageDisplay direction:AIFilterIncoming];
[adium.contentController registerContentFilter:self ofType:AIFilterMessageDisplay direction:AIFilterOutgoing];
[adium.contentController registerContentFilter:self ofType:AIFilterDisplay direction:AIFilterIncoming];
[adium.contentController registerContentFilter:self ofType:AIFilterDisplay direction:AIFilterOutgoing];
}
- (void)uninstallPlugin
{
[adium.contentController unregisterContentFilter:self];
}
/*!
* @brief Parse the channel names into links.
*
* RFC 1459 parsing:
* <channel> ::= ('#' | '&') <chstring>
* <chstring> ::= <any 8bit code except SPACE, BELL, NUL, CR, LF and comma (',')>
*
* We ignore &channels (local channels) since & is commonly used in text, and we do not
* consider channels with quotes in their name since it will mess up linking.
*
* Valid channel names are converted into irc://(account host)/(channel name)
*/
- (NSAttributedString *)filterAttributedString:(NSAttributedString *)inAttributedString context:(id)context
{
AIAccount *account = nil;
if ([context isKindOfClass:[AIContentObject class]]) {
account = ((AIContentObject *)context).chat.account;
} else if ([context isKindOfClass:[AIListContact class]]) {
account = ((AIListContact *)context).account;
}
if (!account || ![account.service.serviceClass isEqualToString:@"IRC"]) {
return inAttributedString;
}
NSScanner *scanner = [NSScanner scannerWithString:[inAttributedString string]];
NSMutableAttributedString *newString = [inAttributedString mutableCopy];
// Set our character sets static, since we do this _a lot_.
static NSMutableCharacterSet *validPrefix = nil;
static NSCharacterSet *channelStart = nil;
static NSMutableCharacterSet *validValues = nil;
if (!validPrefix) {
// Characters valid before a channel's start character are all of the mode flags.
validPrefix = [[NSCharacterSet characterSetWithCharactersInString:@"@+%."] mutableCopy];
[validPrefix formUnionWithCharacterSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
if (!channelStart) {
channelStart = [[NSCharacterSet characterSetWithCharactersInString:@"#"] retain];
}
if (!validValues) {
// Start out with newline and whitespace characters.
validValues = [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy];
// Add in comma, space and quotation marks (technically valid but messes up linking).
[validValues addCharactersInString:@", \""];
// Add in all control characters.
[validValues formUnionWithCharacterSet:[NSCharacterSet controlCharacterSet]];
// Now invert.
[validValues invert];
}
[scanner setCharactersToBeSkipped:nil];
[newString beginEditing];
while(!scanner.isAtEnd) {
[scanner scanUpToCharactersFromSet:channelStart intoString:NULL];
if(scanner.isAtEnd) {
break;
}
NSUInteger startLocation = scanner.scanLocation;
NSString *linkText = nil;
// Advance to the start of the channel name.
// Check to make sure we aren't exceeding the string bounds.
if(startLocation < scanner.string.length) {
scanner.scanLocation++;
} else {
break;
}
// Grab any valid characters we can - # is a valid channel names.
BOOL anyCharacters = [scanner scanCharactersFromSet:validValues intoString:&linkText];
// If we're at the start *or* the channel name begins after a space or newline, this is a valid link.
if((scanner.scanLocation - linkText.length) == 1 ||
[validPrefix characterIsMember:[scanner.string characterAtIndex:(scanner.scanLocation - linkText.length - 2)]]) {
if (![inAttributedString attribute:NSLinkAttributeName atIndex:startLocation effectiveRange:NULL]) {
NSString *channelName = [scanner.string substringWithRange:NSMakeRange(startLocation, 1)];
// Only append if we have text.
if (linkText.length) {
channelName = [channelName stringByAppendingString:linkText];
// If the last character is a punctuation character, drop it.
if ([[NSCharacterSet punctuationCharacterSet] characterIsMember:[channelName characterAtIndex:channelName.length-1]]) {
channelName = [channelName substringToIndex:channelName.length-1];
}
}
NSString *linkURL = [NSString stringWithFormat:@"irc://%@/%@",
account.host,
channelName];
[newString addAttribute:NSLinkAttributeName
value:linkURL
range:NSMakeRange(startLocation, channelName.length)];
}
}
// If we didn't read any characters in following the channel start, advance.
if (!anyCharacters && scanner.scanLocation + 1 < scanner.string.length) {
scanner.scanLocation++;
}
}
[newString endEditing];
return [newString autorelease];
}
/*!
* @brief When should this filter run?
*/
- (CGFloat)filterPriority
{
return DEFAULT_FILTER_PRIORITY;
}
@end
|