From b41a66165833a2cd98829b4104721a87d9a154ef Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Wed, 6 Dec 2023 11:37:59 -0600 Subject: [PATCH 01/15] Fix selection state when template contained other formatted text --- .../ComponentsObjC/WKSourceEditorFormatterBoldItalics.m | 6 ++---- .../ComponentsObjC/WKSourceEditorFormatterTemplate.m | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m index 763e2947cd0..d89ef7e9da1 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m @@ -291,8 +291,7 @@ - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isBoldInR } else { [attributedString enumerateAttributesInRange:range options:nil usingBlock:^(NSDictionary * _Nonnull attrs, NSRange loopRange, BOOL * _Nonnull stop) { - if ((attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[WKSourceEditorCustomKeyFontBold] != nil) && - (loopRange.location == range.location && loopRange.length == range.length)) { + if (attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[WKSourceEditorCustomKeyFontBold] != nil) { isBold = YES; stop = YES; } @@ -317,8 +316,7 @@ - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isItalics } else { [attributedString enumerateAttributesInRange:range options:nil usingBlock:^(NSDictionary * _Nonnull attrs, NSRange loopRange, BOOL * _Nonnull stop) { - if ((attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[WKSourceEditorCustomKeyFontItalics] != nil) && - (loopRange.location == range.location && loopRange.length == range.length)) { + if (attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[WKSourceEditorCustomKeyFontItalics] != nil) { isItalics = YES; stop = YES; } diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m index 5d6272f4a8c..31a8080aa6a 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m @@ -150,8 +150,7 @@ - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isHorizon } else { [attributedString enumerateAttributesInRange:range options:nil usingBlock:^(NSDictionary * _Nonnull attrs, NSRange loopRange, BOOL * _Nonnull stop) { - if ((attrs[WKSourceEditorCustomKeyHorizontalTemplate] != nil) && - (loopRange.location == range.location && loopRange.length == range.length)) { + if (attrs[WKSourceEditorCustomKeyHorizontalTemplate] != nil) { isTemplate = YES; stop = YES; } From 79058826aed689dff991b0bffdfae988a2614a54 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Wed, 6 Dec 2023 11:42:25 -0600 Subject: [PATCH 02/15] Add tests --- .../WKSourceEditorTextFrameworkMediatorTests.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift index 5f01b4b4c71..f42a4d3a062 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift @@ -121,4 +121,13 @@ final class WKSourceEditorTextFrameworkMediatorTests: XCTestCase { let selectionStates1 = mediator.selectionState(selectedDocumentRange: NSRange(location: 1, length: 0)) XCTAssertFalse(selectionStates1.isHorizontalTemplate) } + + func testHorizontalTemplateButtonSelectionStateFormattedRange() throws { + let text = "Testing inner formatted {{cite web | url=https://en.wikipedia.org | title = The '''Free''' Encyclopedia}} template example." + mediator.textView.attributedText = NSAttributedString(string: text) + + // "cite web | url=https://en.wikipedia.org | title = The '''Free''' Encyclopedia" + let selectionStates = mediator.selectionState(selectedDocumentRange: NSRange(location: 26, length: 77)) + XCTAssertTrue(selectionStates.isHorizontalTemplate) + } } From b49f3a75f1971bc106b37ad3fd2bc79c242a95d5 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Wed, 6 Dec 2023 12:00:58 -0600 Subject: [PATCH 03/15] Fix vertical template syntax highlight issue --- .../WKSourceEditorFormatterTemplate.m | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m index 5d6272f4a8c..93d5e35edd2 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m @@ -34,7 +34,7 @@ - (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEdi }; _horizontalTemplateRegex = [[NSRegularExpression alloc] initWithPattern:@"\\{{2}[^\\{\\}\\n]*\\}{2}" options:0 error:nil]; - _verticalStartTemplateRegex = [[NSRegularExpression alloc] initWithPattern:@"^\\{{2}[^\\{\\}\\n]*$" options:NSRegularExpressionAnchorsMatchLines error:nil]; + _verticalStartTemplateRegex = [[NSRegularExpression alloc] initWithPattern:@"^(?:.*)(\\{{2}[^\\{\\}\n]*)$" options:NSRegularExpressionAnchorsMatchLines error:nil]; _verticalParameterTemplateRegex = [[NSRegularExpression alloc] initWithPattern:@"^\\s*\\|.*$" options:NSRegularExpressionAnchorsMatchLines error:nil]; _verticalEndTemplateRegex = [[NSRegularExpression alloc] initWithPattern:@"^([^\\{\\}\n]*\\}{2})(?:.)*$" options:NSRegularExpressionAnchorsMatchLines error:nil]; } @@ -64,11 +64,12 @@ - (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedStri options:0 range:range usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) { - NSRange matchRange = [result rangeAtIndex:0]; + NSRange fullMatch = [result rangeAtIndex:0]; + NSRange openingTemplateRange = [result rangeAtIndex:1]; - if (matchRange.location != NSNotFound) { - [attributedString addAttributes:self.verticalTemplateAttributes range:matchRange]; - } + if (fullMatch.location != NSNotFound && openingTemplateRange.location != NSNotFound) { + [attributedString addAttributes:self.verticalTemplateAttributes range:openingTemplateRange]; + } }]; [self.verticalParameterTemplateRegex enumerateMatchesInString:attributedString.string @@ -86,12 +87,14 @@ - (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedStri options:0 range:range usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) { + NSRange fullMatch = [result rangeAtIndex:0]; NSRange closingTemplateRange = [result rangeAtIndex:1]; - if (fullMatch.location != NSNotFound && closingTemplateRange.location != NSNotFound) { - [attributedString addAttributes:self.verticalTemplateAttributes range:closingTemplateRange]; - } + if (fullMatch.location != NSNotFound && closingTemplateRange.location != NSNotFound) { + [attributedString addAttributes:self.verticalTemplateAttributes range:closingTemplateRange]; + } + }]; } From 2e7cc02be407ce93598e2ad3cb80c34e7a15dafa Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Wed, 6 Dec 2023 12:06:20 -0600 Subject: [PATCH 04/15] Add tests --- .../WKSourceEditorFormatterTests.swift | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift index 6330fe0ba7c..9e8aad7edab 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift @@ -630,7 +630,7 @@ final class WKSourceEditorFormatterTests: XCTestCase { XCTAssertEqual(refClosingAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect ref formatting") } - func testVerticalStartTemplate() { + func testVerticalStartTemplate1() { let string = "{{Infobox officeholder" let mutAttributedString = NSMutableAttributedString(string: string) @@ -648,6 +648,33 @@ final class WKSourceEditorFormatterTests: XCTestCase { XCTAssertEqual(templateAttributes[.foregroundColor] as! UIColor, colors.purpleForegroundColor, "Incorrect template formatting") } + func testVerticalStartTemplate2() { + let string = "ending of previous sentence. {{cite web" + let mutAttributedString = NSMutableAttributedString(string: string) + + for formatter in formatters { + formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count)) + } + + var baseRange = NSRange(location: 0, length: 0) + let baseAttributes = mutAttributedString.attributes(at: 0, effectiveRange: &baseRange) + + var templateRange = NSRange(location: 0, length: 0) + let templateAttributes = mutAttributedString.attributes(at: 29, effectiveRange: &templateRange) + + // "ending of previous sentence. " + XCTAssertEqual(baseRange.location, 0, "Incorrect base formatting") + XCTAssertEqual(baseRange.length, 29, "Incorrect base formatting") + XCTAssertEqual(baseAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(baseAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") + + // "ending of previous sentence. " + XCTAssertEqual(templateRange.location, 29, "Incorrect template formatting") + XCTAssertEqual(templateRange.length, 10, "Incorrect template formatting") + XCTAssertEqual(templateAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect template formatting") + XCTAssertEqual(templateAttributes[.foregroundColor] as! UIColor, colors.purpleForegroundColor, "Incorrect base formatting") + } + func testVerticalParameterTemplate() { let string = "| genus = Felis" let mutAttributedString = NSMutableAttributedString(string: string) From 676f4300eeace270fbf05deb88ea2f5c16871157 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Wed, 6 Dec 2023 13:10:18 -0600 Subject: [PATCH 05/15] Bug - allow inner selected text to remove formatting by adding opposing format strings --- ...KSourceEditorFormatter+ButtonActions.swift | 68 +++++++++++++++---- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatter+ButtonActions.swift b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatter+ButtonActions.swift index e0e70e9d2f6..2095711d9fa 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatter+ButtonActions.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatter+ButtonActions.swift @@ -16,19 +16,63 @@ extension WKSourceEditorFormatter { func toggleFormatting(startingFormattingString: String, endingFormattingString: String, action: WKSourceEditorFormatterButtonAction, in textView: UITextView) { - switch action { - case .remove: - expandSelectedRangeUpToNearestFormattingStrings(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) - - if selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) { - removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) + if textView.selectedRange.length == 0 { + switch action { + case .remove: + expandSelectedRangeUpToNearestFormattingStrings(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) + + if selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) { + removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) + } + case .add: + if selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) { + removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) + } else { + addStringFormattingCharacters(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) + } } - case .add: - if textView.selectedRange.length == 0 && - selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) { - removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) - } else { - addStringFormattingCharacters(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) + } else { + + switch action { + case .remove: + + if selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) { + removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) + } else { + + // Note the flipped formatting string params. + // For example, this takes selected 'text' from: + // Testing Strikethrough text here + // To: + // Testing Strikethrough text here + // We have to add formatters in reverse order to remove formatting from 'text' + + addStringFormattingCharacters(startingFormattingString: endingFormattingString, endingFormattingString: startingFormattingString, in: textView) + } + + case .add: + + // Note: gross workaround to prevent italics misfire from continuing below + if startingFormattingString == "''" && endingFormattingString == "''" { + if selectedRangeIsSurroundedByFormattingString(formattingString: "''", in: textView) && + selectedRangeIsSurroundedByFormattingString(formattingString: "'''", in: textView) { + addStringFormattingCharacters(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) + return + } + } + + // Note the flipped formatting string params. + // For example, this takes selected 'text' from: + // Testing Strikethrough text here + // To: + // Testing Strikethrough text here + // We have to check and remove formatters in reverse order to add formatting to 'text' + + if selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: endingFormattingString, endingFormattingString: startingFormattingString, in: textView) { + removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: endingFormattingString, endingFormattingString: startingFormattingString, in: textView) + } else { + addStringFormattingCharacters(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) + } } } } From cb7b83aa2017b1f95090f0e18b92e45eee985a5b Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Wed, 6 Dec 2023 13:21:07 -0600 Subject: [PATCH 06/15] Add tests --- ...urceEditorFormatterButtonActionTests.swift | 50 +++++++++++++++++-- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift index ee99fe49cd6..8b023f7fbfb 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift @@ -42,7 +42,7 @@ final class WKSourceEditorFormatterButtonActionTests: XCTestCase { XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") } - func testSingleBoldRemove() throws { + func testCursorBoldRemove() throws { let text = "One '''Two''' Three Four" mediator.textView.attributedText = NSAttributedString(string: text) mediator.textView.selectedRange = NSRange(location: 8, length: 0) // Just a cursor inside Two @@ -50,7 +50,7 @@ final class WKSourceEditorFormatterButtonActionTests: XCTestCase { XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") } - func testSingleItalicsRemove() throws { + func testCursorItalicsRemove() throws { let text = "One Two '''Three''' Four" mediator.textView.attributedText = NSAttributedString(string: text) mediator.textView.selectedRange = NSRange(location: 14, length: 0) @@ -77,7 +77,7 @@ final class WKSourceEditorFormatterButtonActionTests: XCTestCase { XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") } - func testSingleBoldInsertAndRemove() throws { + func testCursorBoldInsertAndRemove() throws { let text = "One Two Three Four" mediator.textView.attributedText = NSAttributedString(string: text) mediator.textView.selectedRange = NSRange(location: 4, length: 0) // Just a cursor before Two @@ -87,7 +87,7 @@ final class WKSourceEditorFormatterButtonActionTests: XCTestCase { XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") } - func testSingleItalicsInsertAndRemove() throws { + func testCursorItalicsInsertAndRemove() throws { let text = "One Two Three Four" mediator.textView.attributedText = NSAttributedString(string: text) mediator.textView.selectedRange = NSRange(location: 4, length: 0) // Just a cursor before Two @@ -97,6 +97,46 @@ final class WKSourceEditorFormatterButtonActionTests: XCTestCase { XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") } + func testBoldInnerRemoveAndInsert() throws { + let text = "One '''Two Three Four''' Five" + mediator.textView.attributedText = NSAttributedString(string: text) + mediator.textView.selectedRange = NSRange(location: 11, length: 5) // Selected Three + mediator.boldItalicsFormatter?.toggleBoldFormatting(action: .remove, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One '''Two '''Three''' Four''' Five") + mediator.boldItalicsFormatter?.toggleBoldFormatting(action: .remove, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One '''Two Three Four''' Five") + } + + func testItalicsInnerRemoveAndInsert() throws { + let text = "One ''Two Three Four'' Five" + mediator.textView.attributedText = NSAttributedString(string: text) + mediator.textView.selectedRange = NSRange(location: 10, length: 5) // Selected Three + mediator.boldItalicsFormatter?.toggleItalicsFormatting(action: .remove, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One ''Two ''Three'' Four'' Five") + mediator.boldItalicsFormatter?.toggleItalicsFormatting(action: .remove, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One ''Two Three Four'' Five") + } + + func testBoldItalicsInnerRemoveBoldAndInsert() throws { + let text = "One '''''Two Three Four''''' Five" + mediator.textView.attributedText = NSAttributedString(string: text) + mediator.textView.selectedRange = NSRange(location: 13, length: 5) // Selected Three + mediator.boldItalicsFormatter?.toggleBoldFormatting(action: .remove, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One '''''Two '''Three''' Four''''' Five") + mediator.boldItalicsFormatter?.toggleBoldFormatting(action: .remove, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One '''''Two Three Four''''' Five") + } + + func testBoldItalicsInnerRemoveItalicsAndInsert() throws { + let text = "One '''''Two Three Four''''' Five" + mediator.textView.attributedText = NSAttributedString(string: text) + mediator.textView.selectedRange = NSRange(location: 13, length: 5) // Selected Three + mediator.boldItalicsFormatter?.toggleItalicsFormatting(action: .remove, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One '''''Two ''Three'' Four''''' Five") + mediator.boldItalicsFormatter?.toggleItalicsFormatting(action: .remove, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One '''''Two Three Four''''' Five") + } + func testTemplateInsert() throws { let text = "One Two Three Four" mediator.textView.attributedText = NSAttributedString(string: text) @@ -113,7 +153,7 @@ final class WKSourceEditorFormatterButtonActionTests: XCTestCase { XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") } - func testSingleTemplateInsertAndRemove() throws { + func testCursorTemplateInsertAndRemove() throws { let text = "One Two Three Four" mediator.textView.attributedText = NSAttributedString(string: text) mediator.textView.selectedRange = NSRange(location: 4, length: 0) // Just a cursor before Two From bb520c5f8877e7842db61fba49fac5f927da46f7 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Thu, 7 Dec 2023 13:51:48 -0600 Subject: [PATCH 07/15] Fix regression bug --- .../WKSourceEditorFormatterBoldItalics.m | 28 +++++++++++++++---- .../WKSourceEditorFormatterTemplate.m | 15 ++++++++-- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m index d89ef7e9da1..150257dce98 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m @@ -290,15 +290,23 @@ - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isBoldInR } } else { + __block NSRange unionRange = NSMakeRange(NSNotFound, 0); [attributedString enumerateAttributesInRange:range options:nil usingBlock:^(NSDictionary * _Nonnull attrs, NSRange loopRange, BOOL * _Nonnull stop) { - if (attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[WKSourceEditorCustomKeyFontBold] != nil) { - isBold = YES; - stop = YES; + if (attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[WKSourceEditorCustomKeyFontBold] != nil) { + if (unionRange.location == NSNotFound) { + unionRange = loopRange; + } else { + unionRange = NSUnionRange(unionRange, loopRange); } + stop = YES; + } }]; + + if (NSEqualRanges(unionRange, range)) { + isBold = YES; + } } - return isBold; } - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isItalicsInRange:(NSRange)range { @@ -315,12 +323,22 @@ - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isItalics } } else { + + __block NSRange unionRange = NSMakeRange(NSNotFound, 0); [attributedString enumerateAttributesInRange:range options:nil usingBlock:^(NSDictionary * _Nonnull attrs, NSRange loopRange, BOOL * _Nonnull stop) { if (attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[WKSourceEditorCustomKeyFontItalics] != nil) { - isItalics = YES; + if (unionRange.location == NSNotFound) { + unionRange = loopRange; + } else { + unionRange = NSUnionRange(unionRange, loopRange); + } stop = YES; } }]; + + if (NSEqualRanges(unionRange, range)) { + isItalics = YES; + } } return isItalics; diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m index 31a8080aa6a..375d432ebeb 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m @@ -149,12 +149,21 @@ - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isHorizon } } else { + __block NSRange unionRange = NSMakeRange(NSNotFound, 0); [attributedString enumerateAttributesInRange:range options:nil usingBlock:^(NSDictionary * _Nonnull attrs, NSRange loopRange, BOOL * _Nonnull stop) { - if (attrs[WKSourceEditorCustomKeyHorizontalTemplate] != nil) { - isTemplate = YES; - stop = YES; + if (attrs[WKSourceEditorCustomKeyHorizontalTemplate] != nil) { + if (unionRange.location == NSNotFound) { + unionRange = loopRange; + } else { + unionRange = NSUnionRange(unionRange, loopRange); } + stop = YES; + } }]; + + if (NSEqualRanges(unionRange, range)) { + isTemplate = YES; + } } return isTemplate; From 2e28b07fb92a94861ada8d975f95703e76b2c5da Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Thu, 7 Dec 2023 14:06:17 -0600 Subject: [PATCH 08/15] Add more tests --- ...urceEditorTextFrameworkMediatorTests.swift | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift index f42a4d3a062..b78e7b99404 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift @@ -61,6 +61,26 @@ final class WKSourceEditorTextFrameworkMediatorTests: XCTestCase { XCTAssertFalse(selectionStates9.isItalics) } + func testSelectionSpanningNonFormattedState1() throws { + let text = "Testing '''bold with {{template}}''' selection that spans nonbold." + mediator.textView.attributedText = NSAttributedString(string: text) + + // "bold with {{template}}" + let selectionStates = mediator.selectionState(selectedDocumentRange: NSRange(location: 11, length: 22)) + XCTAssertTrue(selectionStates.isBold) + XCTAssertFalse(selectionStates.isHorizontalTemplate) + } + + func testSelectionSpanningNonFormattedState2() throws { + let text = "Testing {{template | '''bold'''}} selection that spans nonbold." + mediator.textView.attributedText = NSAttributedString(string: text) + + // "template | '''bold'''" + let selectionStates1 = mediator.selectionState(selectedDocumentRange: NSRange(location: 10, length: 21)) + XCTAssertFalse(selectionStates1.isBold) + XCTAssertTrue(selectionStates1.isHorizontalTemplate) + } + func testHorizontalTemplateButtonSelectionStateCursor() throws { let text = "Testing simple {{Currentdate}} template example." mediator.textView.attributedText = NSAttributedString(string: text) From 9191d2a44b51158392163f35868bbbfc49fc1fae Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Thu, 7 Dec 2023 14:09:21 -0600 Subject: [PATCH 09/15] Cleanup - Dry WKSourceEditorFormatterBoldItalics --- .../WKSourceEditorFormatterBoldItalics.m | 49 +++++-------------- 1 file changed, 13 insertions(+), 36 deletions(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m index 150257dce98..1f89bd995fc 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m @@ -278,47 +278,24 @@ - (void)updateFonts:(WKSourceEditorFonts *)fonts inAttributedString:(NSMutableAt #pragma mark - Public - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isBoldInRange:(NSRange)range { - __block BOOL isBold = NO; - if (range.length == 0) { - - if (attributedString.length > range.location) { - NSDictionary *attrs = [attributedString attributesAtIndex:range.location effectiveRange:nil]; - - if (attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[WKSourceEditorCustomKeyFontBold] != nil) { - isBold = YES; - } - } - - } else { - __block NSRange unionRange = NSMakeRange(NSNotFound, 0); - [attributedString enumerateAttributesInRange:range options:nil usingBlock:^(NSDictionary * _Nonnull attrs, NSRange loopRange, BOOL * _Nonnull stop) { - if (attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[WKSourceEditorCustomKeyFontBold] != nil) { - if (unionRange.location == NSNotFound) { - unionRange = loopRange; - } else { - unionRange = NSUnionRange(unionRange, loopRange); - } - stop = YES; - } - }]; - - if (NSEqualRanges(unionRange, range)) { - isBold = YES; - } - } - - return isBold; + return [self attributedString:attributedString isFormattedInRange:range formattingKey:WKSourceEditorCustomKeyFontBold]; } - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isItalicsInRange:(NSRange)range { - __block BOOL isItalics = NO; + return [self attributedString:attributedString isFormattedInRange:range formattingKey:WKSourceEditorCustomKeyFontItalics]; +} + +#pragma mark - Private + +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isFormattedInRange:(NSRange)range formattingKey: (NSString *)formattingKey { + __block BOOL isFormatted = NO; if (range.length == 0) { if (attributedString.length > range.location) { NSDictionary *attrs = [attributedString attributesAtIndex:range.location effectiveRange:nil]; - if (attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[WKSourceEditorCustomKeyFontItalics] != nil) { - isItalics = YES; + if (attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[formattingKey] != nil) { + isFormatted = YES; } } @@ -326,7 +303,7 @@ - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isItalics __block NSRange unionRange = NSMakeRange(NSNotFound, 0); [attributedString enumerateAttributesInRange:range options:nil usingBlock:^(NSDictionary * _Nonnull attrs, NSRange loopRange, BOOL * _Nonnull stop) { - if (attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[WKSourceEditorCustomKeyFontItalics] != nil) { + if (attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[formattingKey] != nil) { if (unionRange.location == NSNotFound) { unionRange = loopRange; } else { @@ -337,11 +314,11 @@ - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isItalics }]; if (NSEqualRanges(unionRange, range)) { - isItalics = YES; + isFormatted = YES; } } - return isItalics; + return isFormatted; } @end From 16ee5ba2d29497e19b2a385c5fceaee19de80e44 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Thu, 7 Dec 2023 15:31:15 -0600 Subject: [PATCH 10/15] Undo line break character regression --- .../Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m index 93d5e35edd2..9083a4c4473 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m @@ -34,7 +34,7 @@ - (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEdi }; _horizontalTemplateRegex = [[NSRegularExpression alloc] initWithPattern:@"\\{{2}[^\\{\\}\\n]*\\}{2}" options:0 error:nil]; - _verticalStartTemplateRegex = [[NSRegularExpression alloc] initWithPattern:@"^(?:.*)(\\{{2}[^\\{\\}\n]*)$" options:NSRegularExpressionAnchorsMatchLines error:nil]; + _verticalStartTemplateRegex = [[NSRegularExpression alloc] initWithPattern:@"^(?:.*)(\\{{2}[^\\{\\}\\n]*)$" options:NSRegularExpressionAnchorsMatchLines error:nil]; _verticalParameterTemplateRegex = [[NSRegularExpression alloc] initWithPattern:@"^\\s*\\|.*$" options:NSRegularExpressionAnchorsMatchLines error:nil]; _verticalEndTemplateRegex = [[NSRegularExpression alloc] initWithPattern:@"^([^\\{\\}\n]*\\}{2})(?:.)*$" options:NSRegularExpressionAnchorsMatchLines error:nil]; } From 3553ec8f5f3c57d72d8549fc495406bd1d303ef0 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Thu, 7 Dec 2023 15:58:19 -0600 Subject: [PATCH 11/15] Syntax highlight nested templates --- .../Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m index 9083a4c4473..b103dcd931e 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m @@ -33,7 +33,7 @@ - (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEdi WKSourceEditorCustomKeyVerticalTemplate: [NSNumber numberWithBool:YES] }; - _horizontalTemplateRegex = [[NSRegularExpression alloc] initWithPattern:@"\\{{2}[^\\{\\}\\n]*\\}{2}" options:0 error:nil]; + _horizontalTemplateRegex = [[NSRegularExpression alloc] initWithPattern:@"\\{{2}[^\\{\\}\\n]*(?:\\{{2}[^\\{\\}\\n]*\\}{2})*[^\\{\\}\\n]*\\}{2}" options:0 error:nil]; _verticalStartTemplateRegex = [[NSRegularExpression alloc] initWithPattern:@"^(?:.*)(\\{{2}[^\\{\\}\\n]*)$" options:NSRegularExpressionAnchorsMatchLines error:nil]; _verticalParameterTemplateRegex = [[NSRegularExpression alloc] initWithPattern:@"^\\s*\\|.*$" options:NSRegularExpressionAnchorsMatchLines error:nil]; _verticalEndTemplateRegex = [[NSRegularExpression alloc] initWithPattern:@"^([^\\{\\}\n]*\\}{2})(?:.)*$" options:NSRegularExpressionAnchorsMatchLines error:nil]; From 91eac86d9179c5af42d9937e45d5a91f0713e353 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Thu, 7 Dec 2023 16:03:34 -0600 Subject: [PATCH 12/15] Add tests --- .../WKSourceEditorFormatterTests.swift | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift index 9e8aad7edab..a112c5a527c 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift @@ -630,6 +630,42 @@ final class WKSourceEditorFormatterTests: XCTestCase { XCTAssertEqual(refClosingAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect ref formatting") } + func testHorizontalNestedTemplate() { + let string = "Ford Island ({{lang-haw|Poka {{okina}}Ailana}}) is an" + let mutAttributedString = NSMutableAttributedString(string: string) + + for formatter in formatters { + formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count)) + } + + var base1Range = NSRange(location: 0, length: 0) + let base1Attributes = mutAttributedString.attributes(at: 0, effectiveRange: &base1Range) + + var templateRange = NSRange(location: 0, length: 0) + let templateAttributes = mutAttributedString.attributes(at: 13, effectiveRange: &templateRange) + + var base2Range = NSRange(location: 0, length: 0) + let base2Attributes = mutAttributedString.attributes(at: 46, effectiveRange: &base2Range) + + // "Ford Island (" + XCTAssertEqual(base1Range.location, 0, "Incorrect base formatting") + XCTAssertEqual(base1Range.length, 13, "Incorrect base formatting") + XCTAssertEqual(base1Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(base1Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") + + // "{{lang-haw|Poka {{okina}}Ailana}}" + XCTAssertEqual(templateRange.location, 13, "Incorrect template formatting") + XCTAssertEqual(templateRange.length, 33, "Incorrect template formatting") + XCTAssertEqual(templateAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect template formatting") + XCTAssertEqual(templateAttributes[.foregroundColor] as! UIColor, colors.purpleForegroundColor, "Incorrect template formatting") + + // ") is an" + XCTAssertEqual(base2Range.location, 46, "Incorrect base formatting") + XCTAssertEqual(base2Range.length, 7, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") + } + func testVerticalStartTemplate1() { let string = "{{Infobox officeholder" let mutAttributedString = NSMutableAttributedString(string: string) From f2841ee60c917db39eb8581ff7f2d2a8ea4d98c7 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Mon, 8 Jan 2024 12:57:19 -0300 Subject: [PATCH 13/15] Fix remaining conflict --- .../WKSourceEditorTextFrameworkMediatorTests.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift index 18db216f3fe..5aa38f420ed 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift @@ -61,7 +61,6 @@ final class WKSourceEditorTextFrameworkMediatorTests: XCTestCase { XCTAssertFalse(selectionStates9.isItalics) } -<<<<<<< HEAD func testSelectionSpanningNonFormattedState1() throws { let text = "Testing '''bold with {{template}}''' selection that spans nonbold." mediator.textView.attributedText = NSAttributedString(string: text) @@ -80,7 +79,7 @@ final class WKSourceEditorTextFrameworkMediatorTests: XCTestCase { let selectionStates1 = mediator.selectionState(selectedDocumentRange: NSRange(location: 10, length: 21)) XCTAssertFalse(selectionStates1.isBold) XCTAssertTrue(selectionStates1.isHorizontalTemplate) -======= + } func testClosingBoldSelectionStateCursor() throws { let text = "One '''Two''' Three" mediator.textView.attributedText = NSAttributedString(string: text) @@ -95,7 +94,6 @@ final class WKSourceEditorTextFrameworkMediatorTests: XCTestCase { let selectionStates = mediator.selectionState(selectedDocumentRange: NSRange(location: 9, length: 0)) XCTAssertTrue(selectionStates.isItalics) ->>>>>>> a08b4c44f206d70dc05551b00fbede5dc6dd8faa } func testHorizontalTemplateButtonSelectionStateCursor() throws { @@ -167,7 +165,7 @@ final class WKSourceEditorTextFrameworkMediatorTests: XCTestCase { let selectionStates = mediator.selectionState(selectedDocumentRange: NSRange(location: 26, length: 77)) XCTAssertTrue(selectionStates.isHorizontalTemplate) } - + func testStrikethroughSelectionState() throws { let text = "Testing Strikethrough Testing." mediator.textView.attributedText = NSAttributedString(string: text) From 159c284f8239a98f5fbf18ae8fc1525f13849ac9 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Mon, 8 Jan 2024 15:28:34 -0300 Subject: [PATCH 14/15] Fix failing test --- .../ComponentsObjC/WKSourceEditorFormatterBoldItalics.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m index 04b855329b6..262e928431d 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m @@ -280,6 +280,7 @@ - (void)updateFonts:(WKSourceEditorFonts *)fonts inAttributedString:(NSMutableAt - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isBoldInRange:(NSRange)range { return [self attributedString:attributedString isFormattedInRange:range formattingKey:WKSourceEditorCustomKeyFontBold]; } + - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isItalicsInRange:(NSRange)range { return [self attributedString:attributedString isFormattedInRange:range formattingKey:WKSourceEditorCustomKeyFontItalics]; } @@ -300,7 +301,7 @@ - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isFormatt // Edge case, check previous character if we are up against a closing bold or italic if (attrs[WKSourceEditorCustomKeyColorOrange]) { attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; - if (attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[WKSourceEditorCustomKeyFontItalics] != nil) { + if (attrs[WKSourceEditorCustomKeyFontBold] != nil || attrs[WKSourceEditorCustomKeyFontItalics] != nil) { isFormatted = YES; } } From d0ce9ed03e999e5239fe9e57771aec344c67fc84 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Mon, 8 Jan 2024 17:24:45 -0600 Subject: [PATCH 15/15] Fix shared method keys in bold italic formatter --- .../ComponentsObjC/WKSourceEditorFormatterBoldItalics.m | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m index 262e928431d..990d0573a3e 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m @@ -280,7 +280,6 @@ - (void)updateFonts:(WKSourceEditorFonts *)fonts inAttributedString:(NSMutableAt - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isBoldInRange:(NSRange)range { return [self attributedString:attributedString isFormattedInRange:range formattingKey:WKSourceEditorCustomKeyFontBold]; } - - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isItalicsInRange:(NSRange)range { return [self attributedString:attributedString isFormattedInRange:range formattingKey:WKSourceEditorCustomKeyFontItalics]; } @@ -294,14 +293,13 @@ - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isFormatt if (attributedString.length > range.location) { NSDictionary *attrs = [attributedString attributesAtIndex:range.location effectiveRange:nil]; - - if (attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[WKSourceEditorCustomKeyFontItalics] != nil) { + if (attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[formattingKey] != nil) { isFormatted = YES; } else { // Edge case, check previous character if we are up against a closing bold or italic if (attrs[WKSourceEditorCustomKeyColorOrange]) { attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; - if (attrs[WKSourceEditorCustomKeyFontBold] != nil || attrs[WKSourceEditorCustomKeyFontItalics] != nil) { + if (attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[formattingKey] != nil) { isFormatted = YES; } }