Skip to content

Commit

Permalink
Merge pull request #3 from kaunteya/char-modification
Browse files Browse the repository at this point in the history
Added insertString and deleteString
  • Loading branch information
mattmassicotte authored Mar 25, 2024
2 parents 1b578fc + 7682819 commit 43f7923
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 0 deletions.
49 changes: 49 additions & 0 deletions Sources/TextViewPlus/NSTextView+AttributedString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public extension NSTextView {
return attributedSubstring(forProposedRange: range, actualRange: actualRange)?.copy() as? NSAttributedString
}

/// Undoable replacement of attributed string in specified range
func replaceString(in range: NSRange, with attributedString: NSAttributedString) {
if let manager = undoManager {
let originalString = safeAttributedSubstring(forProposedRange: range, actualRange: nil)
Expand All @@ -50,4 +51,52 @@ public extension NSTextView {

replaceCharacters(in: range, with: attributedString)
}

func insertCharacters(_ attributedString: NSAttributedString, at location: Int) {
guard let storage = textStorage else {
fatalError("Unable to replace characters in a textview without a backing NSTextStorage")
}

storage.insert(attributedString, at: location)

didChangeText()
}

/// Undoable insertion of attributed string at specified location
func insertString(_ attributedString: NSAttributedString, at location: Int) {
if let manager = undoManager {
let inverseLength = attributedString.length
let inverseRange = NSRange(location: location, length: inverseLength)

manager.registerUndo(withTarget: self, handler: { view in
view.deleteString(in: inverseRange)
})
}

insertCharacters(attributedString, at: location)
}

func deleteCharacters(in range: NSRange) {
guard let storage = textStorage else {
fatalError("Unable to replace characters in a textview without a backing NSTextStorage")
}

storage.deleteCharacters(in: range)

didChangeText()
}

/// Undoable deletion of string in specified range
func deleteString(in range: NSRange) {
if let manager = undoManager {
let originalString = safeAttributedSubstring(forProposedRange: range, actualRange: nil)
let usableReplacementString = originalString ?? NSAttributedString()

manager.registerUndo(withTarget: self, handler: { view in
view.insertString(usableReplacementString, at: range.location)
})
}

deleteCharacters(in: range)
}
}
38 changes: 38 additions & 0 deletions Tests/TextViewPlusTests/TextViewPlusTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,44 @@ final class TextViewPlusTests: XCTestCase {

XCTAssertEqual(textView.string, "abc")
}

func testProgrammaticInsertionOfAttributedString() {
let textView = TestableTextView(string: "abc")

let attrString = NSAttributedString(string: "z")
textView.insertString(attrString, at: 1)

XCTAssertEqual(textView.string, "azbc")

textView.undoManager!.undo()

XCTAssertEqual(textView.string, "abc")
}

func testProgrammaticDeletionOfAttributedString() {
let textView = TestableTextView(string: "abc")

textView.deleteString(in: NSRange(location: 1, length: 1))

XCTAssertEqual(textView.string, "ac")

textView.undoManager!.undo()

XCTAssertEqual(textView.string, "abc")
}

func testProgrammaticDeletionOfAttributedStringWithFullRange() {
let textView = TestableTextView(string: "abc")

textView.deleteString(in: NSRange(location: 0, length: 3))

XCTAssert(textView.string.isEmpty)

textView.undoManager!.undo()

XCTAssertEqual(textView.string, "abc")
}

}

extension TextViewPlusTests {
Expand Down

0 comments on commit 43f7923

Please sign in to comment.