You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

153 lines
4.9 KiB

//
// SlackTextViewController
// https://github.com/slackhq/SlackTextViewController
//
// Copyright 2014-2016 Slack Technologies, Inc.
// Licence: MIT-Licence
//
#import "SLKTextInput.h"
/**
Implementing SLKTextInput methods in a generic NSObject helps reusing the same logic for any SLKTextInput conformant class.
This is the closest and cleanest technique to extend protocol's default implementations, like you'd do in Swift.
*/
@interface NSObject (SLKTextInput)
@end
@implementation NSObject (SLKTextInput)
#pragma mark - Public Methods
- (void)lookForPrefixes:(NSSet<NSString *> *)prefixes completion:(void (^)(NSString *prefix, NSString *word, NSRange wordRange))completion
{
if (![self conformsToProtocol:@protocol(SLKTextInput)]) {
return;
}
NSAssert([prefixes isKindOfClass:[NSSet class]], @"You must provide a set containing String prefixes.");
NSAssert(completion != nil, @"You must provide a non-nil completion block.");
// Skip when there is no prefixes to look for.
if (prefixes.count == 0) {
return;
}
NSRange wordRange;
NSString *word = [self wordAtCaretRange:&wordRange];
if (word.length > 0) {
for (NSString *prefix in prefixes) {
if ([word hasPrefix:prefix]) {
if (completion) {
completion(prefix, word, wordRange);
}
return;
}
}
}
// Fallback to an empty callback
if (completion) {
completion(nil, nil, NSMakeRange(0,0));
}
}
- (NSString *)wordAtCaretRange:(NSRangePointer)range
{
return [self wordAtRange:[self slk_caretRange] rangeInText:range];
}
- (NSString *)wordAtRange:(NSRange)range rangeInText:(NSRangePointer)rangePointer
{
if (![self conformsToProtocol:@protocol(SLKTextInput)]) {
return nil;
}
NSInteger location = range.location;
if (location == NSNotFound) {
return nil;
}
NSString *text = [self slk_text];
// Aborts in case minimum requieres are not fufilled
if (text.length == 0 || location < 0 || (range.location+range.length) > text.length) {
*rangePointer = NSMakeRange(0, 0);
return nil;
}
NSString *leftPortion = [text substringToIndex:location];
NSArray *leftComponents = [leftPortion componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *leftWordPart = [leftComponents lastObject];
NSString *rightPortion = [text substringFromIndex:location];
NSArray *rightComponents = [rightPortion componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *rightPart = [rightComponents firstObject];
if (location > 0) {
NSString *characterBeforeCursor = [text substringWithRange:NSMakeRange(location-1, 1)];
NSRange whitespaceRange = [characterBeforeCursor rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]];
if (whitespaceRange.length == 1) {
// At the start of a word, just use the word behind the cursor for the current word
*rangePointer = NSMakeRange(location, rightPart.length);
return rightPart;
}
}
// In the middle of a word, so combine the part of the word before the cursor, and after the cursor to get the current word
*rangePointer = NSMakeRange(location-leftWordPart.length, leftWordPart.length+rightPart.length);
NSString *word = [leftWordPart stringByAppendingString:rightPart];
NSString *linebreak = @"\n";
// If a break is detected, return the last component of the string
if ([word rangeOfString:linebreak].location != NSNotFound) {
*rangePointer = [text rangeOfString:word];
word = [[word componentsSeparatedByString:linebreak] lastObject];
}
return word;
}
#pragma mark - Private Methods
- (NSString *)slk_text
{
if (![self conformsToProtocol:@protocol(SLKTextInput)]) {
return nil;
}
id<SLKTextInput>input = (id<SLKTextInput>)self;
UITextRange *textRange = [input textRangeFromPosition:input.beginningOfDocument toPosition:input.endOfDocument];
return [input textInRange:textRange];
}
- (NSRange)slk_caretRange
{
if (![self conformsToProtocol:@protocol(SLKTextInput)]) {
return NSMakeRange(0,0);
}
id<SLKTextInput>input = (id<SLKTextInput>)self;
UITextPosition *beginning = input.beginningOfDocument;
UITextRange *selectedRange = input.selectedTextRange;
UITextPosition *selectionStart = selectedRange.start;
UITextPosition *selectionEnd = selectedRange.end;
const NSInteger location = [input offsetFromPosition:beginning toPosition:selectionStart];
const NSInteger length = [input offsetFromPosition:selectionStart toPosition:selectionEnd];
return NSMakeRange(location, length);
}
@end