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 8c464dea60c..daf676df290 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 @@ -15,35 +15,71 @@ extension WKSourceEditorFormatter { } func toggleFormatting(startingFormattingString: String, wildcardStartingFormattingString: String? = nil, endingFormattingString: String, action: WKSourceEditorFormatterButtonAction, in textView: UITextView) { - - switch action { - case .remove: - - var resolvedStartingFormattingString = startingFormattingString - if let wildcardStartingFormattingString { - resolvedStartingFormattingString = getModifiedStartingFormattingStringForSingleWildcard(startingFormattingString: wildcardStartingFormattingString, textView: textView) - } - - expandSelectedRangeUpToNearestFormattingStrings(startingFormattingString: resolvedStartingFormattingString, endingFormattingString: endingFormattingString, in: textView) + + var resolvedStartingFormattingString = startingFormattingString + if let wildcardStartingFormattingString { + resolvedStartingFormattingString = getModifiedStartingFormattingStringForSingleWildcard(startingFormattingString: wildcardStartingFormattingString, textView: textView) + } + + if textView.selectedRange.length == 0 { - if selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: resolvedStartingFormattingString, endingFormattingString: endingFormattingString, in: textView) { - removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: resolvedStartingFormattingString, endingFormattingString: endingFormattingString, in: textView) - } - case .add: - if textView.selectedRange.length == 0 { + switch action { + case .remove: + + expandSelectedRangeUpToNearestFormattingStrings(startingFormattingString: resolvedStartingFormattingString, endingFormattingString: endingFormattingString, in: textView) - var resolvedStartingFormattingString = startingFormattingString - if let wildcardStartingFormattingString { - resolvedStartingFormattingString = getModifiedStartingFormattingStringForSingleWildcard(startingFormattingString: wildcardStartingFormattingString, textView: textView) + if selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: resolvedStartingFormattingString, endingFormattingString: endingFormattingString, in: textView) { + removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: resolvedStartingFormattingString, endingFormattingString: endingFormattingString, in: textView) } + case .add: + if selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: resolvedStartingFormattingString, endingFormattingString: endingFormattingString, in: textView) { + removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: resolvedStartingFormattingString, endingFormattingString: endingFormattingString, in: textView) + } else { + addStringFormattingCharacters(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) + } + } + } else { + + switch action { + case .remove: if selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: resolvedStartingFormattingString, endingFormattingString: endingFormattingString, in: textView) { removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: resolvedStartingFormattingString, 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) } - } else { - addStringFormattingCharacters(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) } } } diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift index a82b76b6e04..a8343cc0e16 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