SLKTextInput+Implementation.m 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. //
  2. // SlackTextViewController
  3. // https://github.com/slackhq/SlackTextViewController
  4. //
  5. // Copyright 2014-2016 Slack Technologies, Inc.
  6. // Licence: MIT-Licence
  7. //
  8. #import "SLKTextInput.h"
  9. /**
  10. Implementing SLKTextInput methods in a generic NSObject helps reusing the same logic for any SLKTextInput conformant class.
  11. This is the closest and cleanest technique to extend protocol's default implementations, like you'd do in Swift.
  12. */
  13. @interface NSObject (SLKTextInput)
  14. @end
  15. @implementation NSObject (SLKTextInput)
  16. #pragma mark - Public Methods
  17. - (void)lookForPrefixes:(NSSet<NSString *> *)prefixes completion:(void (^)(NSString *prefix, NSString *word, NSRange wordRange))completion
  18. {
  19. if (![self conformsToProtocol:@protocol(SLKTextInput)]) {
  20. return;
  21. }
  22. NSAssert([prefixes isKindOfClass:[NSSet class]], @"You must provide a set containing String prefixes.");
  23. NSAssert(completion != nil, @"You must provide a non-nil completion block.");
  24. // Skip when there is no prefixes to look for.
  25. if (prefixes.count == 0) {
  26. return;
  27. }
  28. NSRange wordRange;
  29. NSString *word = [self wordAtCaretRange:&wordRange];
  30. if (word.length > 0) {
  31. for (NSString *prefix in prefixes) {
  32. if ([word hasPrefix:prefix]) {
  33. if (completion) {
  34. completion(prefix, word, wordRange);
  35. }
  36. return;
  37. }
  38. }
  39. }
  40. // Fallback to an empty callback
  41. if (completion) {
  42. completion(nil, nil, NSMakeRange(0,0));
  43. }
  44. }
  45. - (NSString *)wordAtCaretRange:(NSRangePointer)range
  46. {
  47. return [self wordAtRange:[self slk_caretRange] rangeInText:range];
  48. }
  49. - (NSString *)wordAtRange:(NSRange)range rangeInText:(NSRangePointer)rangePointer
  50. {
  51. if (![self conformsToProtocol:@protocol(SLKTextInput)]) {
  52. return nil;
  53. }
  54. NSInteger location = range.location;
  55. if (location == NSNotFound) {
  56. return nil;
  57. }
  58. NSString *text = [self slk_text];
  59. // Aborts in case minimum requieres are not fufilled
  60. if (text.length == 0 || location < 0 || (range.location+range.length) > text.length) {
  61. *rangePointer = NSMakeRange(0, 0);
  62. return nil;
  63. }
  64. NSString *leftPortion = [text substringToIndex:location];
  65. NSArray *leftComponents = [leftPortion componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  66. NSString *leftWordPart = [leftComponents lastObject];
  67. NSString *rightPortion = [text substringFromIndex:location];
  68. NSArray *rightComponents = [rightPortion componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  69. NSString *rightPart = [rightComponents firstObject];
  70. if (location > 0) {
  71. NSString *characterBeforeCursor = [text substringWithRange:NSMakeRange(location-1, 1)];
  72. NSRange whitespaceRange = [characterBeforeCursor rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]];
  73. if (whitespaceRange.length == 1) {
  74. // At the start of a word, just use the word behind the cursor for the current word
  75. *rangePointer = NSMakeRange(location, rightPart.length);
  76. return rightPart;
  77. }
  78. }
  79. // 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
  80. *rangePointer = NSMakeRange(location-leftWordPart.length, leftWordPart.length+rightPart.length);
  81. NSString *word = [leftWordPart stringByAppendingString:rightPart];
  82. NSString *linebreak = @"\n";
  83. // If a break is detected, return the last component of the string
  84. if ([word rangeOfString:linebreak].location != NSNotFound) {
  85. *rangePointer = [text rangeOfString:word];
  86. word = [[word componentsSeparatedByString:linebreak] lastObject];
  87. }
  88. return word;
  89. }
  90. #pragma mark - Private Methods
  91. - (NSString *)slk_text
  92. {
  93. if (![self conformsToProtocol:@protocol(SLKTextInput)]) {
  94. return nil;
  95. }
  96. id<SLKTextInput>input = (id<SLKTextInput>)self;
  97. UITextRange *textRange = [input textRangeFromPosition:input.beginningOfDocument toPosition:input.endOfDocument];
  98. return [input textInRange:textRange];
  99. }
  100. - (NSRange)slk_caretRange
  101. {
  102. if (![self conformsToProtocol:@protocol(SLKTextInput)]) {
  103. return NSMakeRange(0,0);
  104. }
  105. id<SLKTextInput>input = (id<SLKTextInput>)self;
  106. UITextPosition *beginning = input.beginningOfDocument;
  107. UITextRange *selectedRange = input.selectedTextRange;
  108. UITextPosition *selectionStart = selectedRange.start;
  109. UITextPosition *selectionEnd = selectedRange.end;
  110. const NSInteger location = [input offsetFromPosition:beginning toPosition:selectionStart];
  111. const NSInteger length = [input offsetFromPosition:selectionStart toPosition:selectionEnd];
  112. return NSMakeRange(location, length);
  113. }
  114. @end