This repository was archived by the owner on Oct 30, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
Copy pathSLKTextInput+Implementation.m
153 lines (114 loc) · 4.89 KB
/
SLKTextInput+Implementation.m
1
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
//
// 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