Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2763,7 +2763,7 @@ SPEC CHECKSUMS:
FBLazyVector: a293a88992c4c33f0aee184acab0b64a08ff9458
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
glog: 5683914934d5b6e4240e497e0f4a3b42d1854183
hermes-engine: 0552e8a1c46c773b6888e9fe198c2272cc097193
hermes-engine: b45e3b4b9d7f8227a6c11c8342f81742829b8af8
RCT-Folly: 59ec0ac1f2f39672a0c6e6cecdd39383b764646f
RCTDeprecation: 2b70c6e3abe00396cefd8913efbf6a2db01a2b36
RCTRequired: f3540eee8094231581d40c5c6d41b0f170237a81
Expand Down
7 changes: 6 additions & 1 deletion ios/EnrichedTextInputView.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#pragma once
#import "AttributesManager.h"
#import "BaseStyleProtocol.h"
#import "InputConfig.h"
#import "InputParser.h"
Expand All @@ -22,14 +23,18 @@ NS_ASSUME_NONNULL_BEGIN
InputConfig *config;
@public
InputParser *parser;
@public
AttributesManager *attributesManager;
@public
NSMutableDictionary<NSAttributedStringKey, id> *defaultTypingAttributes;
@public
NSDictionary<NSNumber *, id<BaseStyleProtocol>> *stylesDict;
NSDictionary<NSNumber *, id> *stylesDict;
NSDictionary<NSNumber *, NSArray<NSNumber *> *> *conflictingStyles;
NSMutableDictionary<NSNumber *, NSArray<NSNumber *> *> *blockingStyles;
@public
BOOL blockEmitting;
@public
NSValue *dotReplacementRange;
}
- (void)emitOnLinkDetectedEvent:(NSString *)text
url:(NSString *)url
Expand Down
1,463 changes: 762 additions & 701 deletions ios/EnrichedTextInputView.mm

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions ios/attributesManager/AttributesManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#pragma once
#import <UIKit/UIKit.h>

@class EnrichedTextInputView;

@interface AttributesManager : NSObject
@property(nonatomic, weak) EnrichedTextInputView *input;
- (instancetype)initWithInput:(EnrichedTextInputView *)input;
- (void)addDirtyRange:(NSRange)range;
- (void)shiftDirtyRangesWithEditedRange:(NSRange)editedRange
changeInLength:(NSInteger)delta;
- (void)didRemoveTypingAttribute:(NSString *)key;
- (void)clearRemovedTypingAttributes;
- (void)manageTypingAttributesWithOnlySelection:(BOOL)onlySelectionChanged;
- (void)handleDirtyRangesStyling;
@end
174 changes: 174 additions & 0 deletions ios/attributesManager/AttributesManager.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
#import "AttributesManager.h"
#import "AttributeEntry.h"
#import "EnrichedTextInputView.h"
#import "RangeUtils.h"
#import "StyleHeaders.h"

@implementation AttributesManager {
NSMutableArray<NSValue *> *_dirtyRanges;
NSSet *_customAttributesKeys;
NSMutableSet *_removedTypingAttributes;
}

- (instancetype)initWithInput:(EnrichedTextInputView *)input {
self = [super init];
_input = input;
_dirtyRanges = [[NSMutableArray alloc] init];
_removedTypingAttributes = [[NSMutableSet alloc] init];

// setup customAttributes
NSMutableSet *_customAttrsSet = [[NSMutableSet alloc] init];
for (StyleBase *style in _input->stylesDict.allValues) {
[_customAttrsSet addObject:[style getKey]];
}
_customAttributesKeys = _customAttrsSet;

return self;
}

- (void)addDirtyRange:(NSRange)range {
if (range.length == 0) {
return;
}
[_dirtyRanges addObject:[NSValue valueWithRange:range]];
_dirtyRanges = [[RangeUtils connectAndDedupeRanges:_dirtyRanges] mutableCopy];
}

- (void)shiftDirtyRangesWithEditedRange:(NSRange)editedRange
changeInLength:(NSInteger)delta {
if (delta == 0) {
return;
}
NSArray *shiftedRanges = [RangeUtils shiftRanges:_dirtyRanges
withEditedRange:editedRange
changeInLength:delta];
_dirtyRanges =
[[RangeUtils connectAndDedupeRanges:shiftedRanges] mutableCopy];
}

- (void)didRemoveTypingAttribute:(NSString *)key {
[_removedTypingAttributes addObject:key];
}

- (void)clearRemovedTypingAttributes {
[_removedTypingAttributes removeAllObjects];
}

- (void)handleDirtyRangesStyling {
for (NSValue *rangeObj in _dirtyRanges) {
NSRange dirtyRange = [rangeObj rangeValue];

// dirty range can sometimes be wrong because of apple doing some changes
// behind the scenes
if (dirtyRange.location + dirtyRange.length >
_input->textView.textStorage.string.length)
continue;

// firstly, get all styles' occurences in that dirty range
NSMutableDictionary *presentStyles = [[NSMutableDictionary alloc] init];
for (StyleBase *style in _input->stylesDict.allValues) {
// the dict has keys of StyleType NSNumber and values of an array of all
// occurences
presentStyles[@([[style class] getType])] = [style all:dirtyRange];
}

// now reset the attributes to default ones
[_input->textView.textStorage setAttributes:_input->defaultTypingAttributes
range:dirtyRange];

// then apply styling and re-apply meta-attribtues following the saved
// occurences
for (NSNumber *styleType in presentStyles) {
StyleBase *style = _input->stylesDict[styleType];
if (style == nullptr)
continue;

for (StylePair *stylePair in presentStyles[styleType]) {
NSRange occurenceRange = [stylePair.rangeValue rangeValue];
[style applyStyling:occurenceRange];
[style add:occurenceRange withTyping:NO withDirtyRange:NO];
}
}
}
// do the typing attributes management, with no selection
[self manageTypingAttributesWithOnlySelection:NO];

[_dirtyRanges removeAllObjects];
}

- (void)manageTypingAttributesWithOnlySelection:(BOOL)onlySelectionChanged {
InputTextView *textView = _input->textView;
NSRange selectedRange = textView.selectedRange;

// Typing attributes get reset when only selection changed to an empty line
// (or empty line with newline).
if (onlySelectionChanged) {
NSRange paragraphRange =
[textView.textStorage.string paragraphRangeForRange:selectedRange];
// User changed selection to an empty line (or empty line with a newline).
if (paragraphRange.length == 0 ||
(paragraphRange.length == 1 &&
[[NSCharacterSet newlineCharacterSet]
characterIsMember:[textView.textStorage.string
characterAtIndex:paragraphRange
.location]])) {
textView.typingAttributes = _input->defaultTypingAttributes;
return;
}
}

// General typing attributes management.

// Firstly, we make sure only default + custom + paragraph typing attribtues
// are left.
NSMutableDictionary *newAttrs = [_input->defaultTypingAttributes mutableCopy];

for (NSString *key in _input->textView.typingAttributes.allKeys) {
if ([_customAttributesKeys containsObject:key]) {
if ([key isEqualToString:NSParagraphStyleAttributeName]) {
// NSParagraphStyle for paragraph styles -> only keep the textLists
// property
NSParagraphStyle *pStyle =
(NSParagraphStyle *)_input->textView
.typingAttributes[NSParagraphStyleAttributeName];
if (pStyle != nullptr && pStyle.textLists.count == 1) {
NSMutableParagraphStyle *newPStyle =
[[NSMutableParagraphStyle alloc] init];
newPStyle.textLists = pStyle.textLists;
newAttrs[NSParagraphStyleAttributeName] = newPStyle;
}
} else {
// Inline styles -> keep the key/value as a whole
newAttrs[key] = _input->textView.typingAttributes[key];
}
}
}

// Then, we add typingAttributes from present inline styles.
// We check for the previous character to naturally extend typing attributes.
// getEntryIfPresent properly returns nullptr for styles that we don't want to
// extend this way. Attributes from _removedTypingAttributes aren't added
// because they were just removed.
for (StyleBase *style in _input->stylesDict.allValues) {
if ([style isParagraph])
continue;
if ([_removedTypingAttributes containsObject:[style getKey]])
continue;

AttributeEntry *entry = nullptr;

if (selectedRange.location > 0) {
entry =
[style getEntryIfPresent:NSMakeRange(selectedRange.location - 1, 1)];
}

if (entry == nullptr)
continue;

newAttrs[entry.key] = entry.value;
}

textView.typingAttributes = newAttrs;
}

@end
59 changes: 33 additions & 26 deletions ios/extensions/LayoutManagerExtension.mm
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#import "LayoutManagerExtension.h"
#import "ColorExtension.h"
#import "EnrichedTextInputView.h"
#import "ParagraphsUtils.h"
#import "RangeUtils.h"
#import "StyleHeaders.h"
#import <objc/runtime.h>

Expand Down Expand Up @@ -66,13 +66,12 @@ - (void)drawCodeBlocks:(EnrichedTextInputView *)typedInput
origin:(CGPoint)origin
visibleCharRange:(NSRange)visibleCharRange {
CodeBlockStyle *codeBlockStyle =
typedInput->stylesDict[@([CodeBlockStyle getStyleType])];
typedInput->stylesDict[@([CodeBlockStyle getType])];
if (codeBlockStyle == nullptr) {
return;
}

NSArray<StylePair *> *allCodeBlocks =
[codeBlockStyle findAllOccurences:visibleCharRange];
NSArray<StylePair *> *allCodeBlocks = [codeBlockStyle all:visibleCharRange];
NSArray<StylePair *> *mergedCodeBlocks =
[self mergeContiguousStylePairs:allCodeBlocks];
UIColor *bgColor = [[typedInput->config codeBlockBgColor]
Expand All @@ -86,8 +85,8 @@ - (void)drawCodeBlocks:(EnrichedTextInputView *)typedInput
continue;

NSArray *paragraphs =
[ParagraphsUtils getSeparateParagraphsRangesIn:typedInput->textView
range:blockCharacterRange];
[RangeUtils getSeparateParagraphsRangesIn:typedInput->textView
range:blockCharacterRange];
if (paragraphs.count == 0)
continue;

Expand Down Expand Up @@ -205,12 +204,12 @@ - (void)drawBlockQuotes:(EnrichedTextInputView *)typedInput
origin:(CGPoint)origin
visibleCharRange:(NSRange)visibleCharRange {
BlockQuoteStyle *bqStyle =
typedInput->stylesDict[@([BlockQuoteStyle getStyleType])];
typedInput->stylesDict[@([BlockQuoteStyle getType])];
if (bqStyle == nullptr) {
return;
}

NSArray *allBlockquotes = [bqStyle findAllOccurences:visibleCharRange];
NSArray *allBlockquotes = [bqStyle all:visibleCharRange];

for (StylePair *pair in allBlockquotes) {
NSRange paragraphRange = [typedInput->textView.textStorage.string
Expand Down Expand Up @@ -246,19 +245,24 @@ - (void)drawLists:(EnrichedTextInputView *)typedInput
origin:(CGPoint)origin
visibleCharRange:(NSRange)visibleCharRange {
UnorderedListStyle *ulStyle =
typedInput->stylesDict[@([UnorderedListStyle getStyleType])];
typedInput->stylesDict[@([UnorderedListStyle getType])];
OrderedListStyle *olStyle =
typedInput->stylesDict[@([OrderedListStyle getStyleType])];
CheckboxListStyle *cbStyle =
typedInput->stylesDict[@([CheckboxListStyle getStyleType])];
if (ulStyle == nullptr || olStyle == nullptr || cbStyle == nullptr) {
typedInput->stylesDict[@([OrderedListStyle getType])];
// CheckboxListStyle *cbStyle =
// typedInput->stylesDict[@([CheckboxListStyle getStyleType])];
if (ulStyle == nullptr && olStyle == nullptr) {
return;
}

NSMutableArray *allLists = [[NSMutableArray alloc] init];
[allLists addObjectsFromArray:[ulStyle findAllOccurences:visibleCharRange]];
[allLists addObjectsFromArray:[olStyle findAllOccurences:visibleCharRange]];
[allLists addObjectsFromArray:[cbStyle findAllOccurences:visibleCharRange]];
if (ulStyle != nullptr) {
[allLists addObjectsFromArray:[ulStyle all:visibleCharRange]];
}
if (olStyle != nullptr) {
[allLists addObjectsFromArray:[olStyle all:visibleCharRange]];
}
// [allLists addObjectsFromArray:[cbStyle
// findAllOccurences:visibleCharRange]];

for (StylePair *pair in allLists) {
NSParagraphStyle *pStyle = (NSParagraphStyle *)pair.styleValue;
Expand All @@ -268,9 +272,9 @@ - (void)drawLists:(EnrichedTextInputView *)typedInput
[typedInput->config orderedListMarkerColor]
};

NSArray *paragraphs = [ParagraphsUtils
getSeparateParagraphsRangesIn:typedInput->textView
range:[pair.rangeValue rangeValue]];
NSArray *paragraphs =
[RangeUtils getSeparateParagraphsRangesIn:typedInput->textView
range:[pair.rangeValue rangeValue]];

for (NSValue *paragraph in paragraphs) {
NSRange paragraphGlyphRange =
Expand All @@ -287,8 +291,9 @@ - (void)drawLists:(EnrichedTextInputView *)typedInput
pStyle.textLists.firstObject
.markerFormat;

if (markerFormat ==
NSTextListMarkerDecimal) {
if ([markerFormat
isEqualToString:
@"EnrichedOrderedList"]) {
NSString *marker = [self
getDecimalMarkerForList:typedInput
charIndex:
Expand All @@ -301,8 +306,10 @@ - (void)drawLists:(EnrichedTextInputView *)typedInput
markerAttributes:markerAttributes
origin:origin
usedRect:usedRect];
} else if (markerFormat ==
NSTextListMarkerDisc) {
} else if ([markerFormat
isEqualToString:
@"EnrichedUnorderedLis"
@"t"]) {
[self drawBullet:typedInput
origin:origin
usedRect:usedRect];
Expand All @@ -329,7 +336,7 @@ - (NSString *)getDecimalMarkerForList:(EnrichedTextInputView *)input
[fullText paragraphRangeForRange:NSMakeRange(index, 0)];
if (currentParagraph.location > 0) {
OrderedListStyle *olStyle =
input->stylesDict[@([OrderedListStyle getStyleType])];
input->stylesDict[@([OrderedListStyle getType])];

NSInteger prevParagraphsCount = 0;
NSInteger recentParagraphLocation =
Expand All @@ -339,7 +346,7 @@ - (NSString *)getDecimalMarkerForList:(EnrichedTextInputView *)input

// seek for previous lists
while (true) {
if ([olStyle detectStyle:NSMakeRange(recentParagraphLocation, 0)]) {
if ([olStyle detect:NSMakeRange(recentParagraphLocation, 0)]) {
prevParagraphsCount += 1;

if (recentParagraphLocation > 0) {
Expand Down Expand Up @@ -406,7 +413,7 @@ - (void)drawDecimal:(EnrichedTextInputView *)typedInput
usedRect:(CGRect)usedRect {
CGFloat gapWidth = [typedInput->config orderedListGapWidth];
CGFloat markerWidth = [marker sizeWithAttributes:markerAttributes].width;
CGFloat markerX = usedRect.origin.x - gapWidth - markerWidth / 2;
CGFloat markerX = origin.x + usedRect.origin.x - gapWidth - markerWidth / 2;

[marker drawAtPoint:CGPointMake(markerX, usedRect.origin.y + origin.y)
withAttributes:markerAttributes];
Expand Down
Loading