Skip to content

Commit 80a7d81

Browse files
authored
Unify the behavior around backspacing iOS lists and quotes (#194)
Previous PRs brought a bit of mayhem to lists and quotes on iOS in terms of their behavior consistency; - the added utils that removed styles when all characters of a line were removed messed with the fact that lists and quotes shouldn't do that. A zero width space should appear there. Added a check in these utils for these styles. - after fixing the above, zero width spaces still didn't appear in the first line because empty input "reset attributes" check was running first. Changed their order. - cleaned up and fixed the logic for handling backspaces within lists. Now backspace that is visibly done on the list/quote marker only removes style, keeping the whole line intact - also fixed one edge case to the above behavior, which resulted in the style being removed because the attributes magically weren't preserved
1 parent 1a29e50 commit 80a7d81

6 files changed

Lines changed: 68 additions & 77 deletions

File tree

ios/EnrichedTextInputView.mm

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -980,8 +980,8 @@ - (void)manageSelectionBasedChanges {
980980
}
981981

982982
// typing attributes for empty lines selection reset
983-
NSString *currentString = [[textView.textStorage.string copy] stringByReplacingOccurrencesOfString:@"\u200B" withString:@""];
984-
if(textView.selectedRange.length == 0 && [_recentlyEmittedString isEqualToString: currentString] ) {
983+
NSString *currentString = [textView.textStorage.string copy];
984+
if(textView.selectedRange.length == 0 && [_recentlyEmittedString isEqualToString:currentString]) {
985985
// no string change means only a selection changed with no character changes
986986
NSRange paragraphRange = [textView.textStorage.string paragraphRangeForRange:textView.selectedRange];
987987
if(
@@ -1012,15 +1012,15 @@ - (void)anyTextMayHaveBeenModified {
10121012
return;
10131013
}
10141014

1015+
// zero width space adding or removal
1016+
[ZeroWidthSpaceUtils handleZeroWidthSpacesInInput:self];
1017+
10151018
// emptying input typing attributes management
10161019
if(textView.textStorage.string.length == 0 && _recentlyEmittedString.length > 0) {
10171020
// reset typing attribtues
10181021
textView.typingAttributes = defaultTypingAttributes;
10191022
}
10201023

1021-
// zero width space removal
1022-
[ZeroWidthSpaceUtils handleZeroWidthSpacesInInput:self];
1023-
10241024
// inline code on newlines fix
10251025
InlineCodeStyle *codeStyle = stylesDict[@([InlineCodeStyle getStyleType])];
10261026
if(codeStyle != nullptr) {

ios/styles/BlockQuoteStyle.mm

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -119,30 +119,19 @@ - (void)removeTypingAttributes {
119119
}
120120

121121
- (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text {
122-
if(
123-
[self detectStyle:_input->textView.selectedRange] &&
124-
NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0)) &&
125-
[text isEqualToString:@""]
126-
) {
127-
// removing first quote line by backspacing doesn't remove typing attributes because it doesn't run textViewDidChange
128-
// so we try guessing that a line should be deleted here
122+
if([self detectStyle:_input->textView.selectedRange] && text.length == 0) {
123+
// backspace while the style is active
124+
129125
NSRange paragraphRange = [_input->textView.textStorage.string paragraphRangeForRange:_input->textView.selectedRange];
130-
[self removeAttributes:paragraphRange];
131-
return YES;
132-
} else if(
133-
[self detectStyle:_input->textView.selectedRange] &&
134-
[text isEqualToString:@""]
135-
) {
136-
// other case; make sure removing all the (non newline) text from a quto line also removes the line itself
137-
NSRange paragraphRange = [_input->textView.textStorage.string paragraphRangeForRange:range];
138-
NSValue *nonNewlineVal = [ParagraphsUtils getNonNewlineRangesIn:_input->textView range:paragraphRange].firstObject;
139-
if(nonNewlineVal == nullptr) {
140-
return NO;
141-
}
142-
NSRange nonNewlineRange = [nonNewlineVal rangeValue];
143-
if(NSEqualRanges(range, nonNewlineRange)) {
144-
[TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:_input withSelection:YES];
145-
[self removeAttributes:NSMakeRange(range.location, 0)];
126+
127+
if(NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0))) {
128+
// a backspace on the very first input's line quote
129+
// it doesn't run textVieDidChange so we need to manually remove attributes
130+
[self removeAttributes:paragraphRange];
131+
return YES;
132+
} else if(range.location == paragraphRange.location - 1) {
133+
// same case in other lines; here, the removed range location will be exactly 1 less than paragraph range location
134+
[self removeAttributes:paragraphRange];
146135
return YES;
147136
}
148137
}

ios/styles/OrderedListStyle.mm

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -131,30 +131,19 @@ - (void)removeTypingAttributes {
131131
}
132132

133133
- (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text {
134-
if(
135-
[self detectStyle:_input->textView.selectedRange] &&
136-
NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0)) &&
137-
[text isEqualToString:@""]
138-
) {
139-
// removing first list point by backspacing doesn't remove typing attributes because it doesn't run textViewDidChange
140-
// so we try guessing that a point should be deleted here
134+
if([self detectStyle:_input->textView.selectedRange] && text.length == 0) {
135+
// backspace while the style is active
136+
141137
NSRange paragraphRange = [_input->textView.textStorage.string paragraphRangeForRange:_input->textView.selectedRange];
142-
[self removeAttributes:paragraphRange];
143-
return YES;
144-
} else if(
145-
[self detectStyle:_input->textView.selectedRange] &&
146-
[text isEqualToString:@""]
147-
) {
148-
// other case; make sure removing all the (non newline) text from a list item also removes the item itself
149-
NSRange paragraphRange = [_input->textView.textStorage.string paragraphRangeForRange:range];
150-
NSValue *nonNewlineVal = [ParagraphsUtils getNonNewlineRangesIn:_input->textView range:paragraphRange].firstObject;
151-
if(nonNewlineVal == nullptr) {
152-
return NO;
153-
}
154-
NSRange nonNewlineRange = [nonNewlineVal rangeValue];
155-
if(NSEqualRanges(range, nonNewlineRange)) {
156-
[TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:_input withSelection:YES];
157-
[self removeAttributes:NSMakeRange(range.location, 0)];
138+
139+
if(NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0))) {
140+
// a backspace on the very first input's line list point
141+
// it doesn't run textVieDidChange so we need to manually remove attributes
142+
[self removeAttributes:paragraphRange];
143+
return YES;
144+
} else if(range.location == paragraphRange.location - 1) {
145+
// same case in other lines; here, the removed range location will be exactly 1 less than paragraph range location
146+
[self removeAttributes:paragraphRange];
158147
return YES;
159148
}
160149
}

ios/styles/UnorderedListStyle.mm

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -131,30 +131,19 @@ - (void)removeTypingAttributes {
131131
}
132132

133133
- (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text {
134-
if(
135-
[self detectStyle:_input->textView.selectedRange] &&
136-
NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0)) &&
137-
[text isEqualToString:@""]
138-
) {
139-
// removing first list point by backspacing doesn't remove typing attributes because it doesn't run textViewDidChange
140-
// so we try guessing that a point should be deleted here
134+
if([self detectStyle:_input->textView.selectedRange] && text.length == 0) {
135+
// backspace while the style is active
136+
141137
NSRange paragraphRange = [_input->textView.textStorage.string paragraphRangeForRange:_input->textView.selectedRange];
142-
[self removeAttributes:paragraphRange];
143-
return YES;
144-
} else if(
145-
[self detectStyle:_input->textView.selectedRange] &&
146-
[text isEqualToString:@""]
147-
) {
148-
// other case; make sure removing all the (non newline) text from a list item also removes the item itself
149-
NSRange paragraphRange = [_input->textView.textStorage.string paragraphRangeForRange:range];
150-
NSValue *nonNewlineVal = [ParagraphsUtils getNonNewlineRangesIn:_input->textView range:paragraphRange].firstObject;
151-
if(nonNewlineVal == nullptr) {
152-
return NO;
153-
}
154-
NSRange nonNewlineRange = [nonNewlineVal rangeValue];
155-
if(NSEqualRanges(range, nonNewlineRange)) {
156-
[TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:_input withSelection:YES];
157-
[self removeAttributes:NSMakeRange(range.location, 0)];
138+
139+
if(NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0))) {
140+
// a backspace on the very first input's line list point
141+
// it doesn't run textVieDidChange so we need to manually remove attributes
142+
[self removeAttributes:paragraphRange];
143+
return YES;
144+
} else if(range.location == paragraphRange.location - 1) {
145+
// same case in other lines; here, the removed range location will be exactly 1 less than paragraph range location
146+
[self removeAttributes:paragraphRange];
158147
return YES;
159148
}
160149
}

ios/utils/OccurenceUtils.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ + (BOOL)detect
4747
NSRange attrRange = NSMakeRange(0, 0);
4848
attrValue = [input->textView.textStorage attribute:key atIndex:index effectiveRange:&attrRange];
4949
}
50-
return condition(attrValue, NSMakeRange(index, 0));
50+
return condition(attrValue, detectionRange);
5151
}
5252

5353
+ (BOOL)detectMultiple

ios/utils/ParagraphAttributesUtils.mm

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#import "ParagraphAttributesUtils.h"
22
#import "EnrichedTextInputView.h"
3+
#import "StyleHeaders.h"
34
#import "ParagraphsUtils.h"
45
#import "TextInsertionUtils.h"
56

@@ -10,6 +11,10 @@ @implementation ParagraphAttributesUtils
1011
// hence the solution - reset typing attributes
1112
+ (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text input:(id)input {
1213
EnrichedTextInputView *typedInput = (EnrichedTextInputView *)input;
14+
UnorderedListStyle *ulStyle = typedInput->stylesDict[@([UnorderedListStyle getStyleType])];
15+
OrderedListStyle *olStyle = typedInput->stylesDict[@([OrderedListStyle getStyleType])];
16+
BlockQuoteStyle *bqStyle = typedInput->stylesDict[@([BlockQuoteStyle getStyleType])];
17+
1318
if(typedInput == nullptr) {
1419
return NO;
1520
}
@@ -21,15 +26,34 @@ + (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text i
2126

2227
// find a non-newline range of the paragraph
2328
NSRange paragraphRange = [typedInput->textView.textStorage.string paragraphRangeForRange:range];
29+
2430
NSArray *paragraphs = [ParagraphsUtils getNonNewlineRangesIn:typedInput->textView range:paragraphRange];
2531
if(paragraphs.count == 0) {
2632
return NO;
2733
}
2834

2935
NSRange nonNewlineRange = [(NSValue *)paragraphs.firstObject rangeValue];
3036

31-
// if the backspace removes the whole content of a paragraph - do the thing
32-
if(NSEqualRanges(nonNewlineRange, range)) {
37+
// if the backspace removes the whole content of a paragraph (possibly more but has to start where the paragraph starts), we remove the typing attributes
38+
if(range.location == nonNewlineRange.location && range.length >= nonNewlineRange.length) {
39+
// for lists and quotes we want to remove the characters but keep attribtues so that a zero width space appears here
40+
// so we do the removing manually and reapply attributes
41+
if([ulStyle detectStyle:nonNewlineRange]) {
42+
[TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:typedInput withSelection:YES];
43+
[ulStyle addAttributes:NSMakeRange(range.location, 0)];
44+
return YES;
45+
}
46+
if([olStyle detectStyle:nonNewlineRange]) {
47+
[TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:typedInput withSelection:YES];
48+
[olStyle addAttributes:NSMakeRange(range.location, 0)];
49+
return YES;
50+
}
51+
if([bqStyle detectStyle:nonNewlineRange]) {
52+
[TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:typedInput withSelection:YES];
53+
[bqStyle addAttributes:NSMakeRange(range.location, 0)];
54+
return YES;
55+
}
56+
3357
// do the replacement manually
3458
[TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:typedInput withSelection:YES];
3559
// reset typing attribtues

0 commit comments

Comments
 (0)