Skip to content

Commit

Permalink
Added helper function to enumerate over continuous ranges of an attri…
Browse files Browse the repository at this point in the history
…bute ignoring difference in value (#329)
  • Loading branch information
rajdeep authored Jul 19, 2024
1 parent 21a098d commit e2a92b9
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 0 deletions.
34 changes: 34 additions & 0 deletions Proton/Sources/Swift/Helpers/NSAttributedString+Content.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,40 @@ public extension NSAttributedString {
}
return string.makeNSRange(from: range)
}

/// Enumerates over continuous ranges of text based on the presence or absence of a specified attribute.
/// - Parameters:
/// - attributeName: The name of the attribute to check for presence or absence.
/// - range: The range within the attributed string to enumerate. Defaults to the entire string.
/// - using: The block to apply to continuous ranges, indicating whether the attribute was present or absent for the range.
func enumerateContinuousRangesByAttribute(_ attributeName: NSAttributedString.Key, in range: NSRange? = nil, using block: (_ isPresent: Bool, _ range: NSRange) -> Void) {
let enumerationRange = range ?? NSRange(location: 0, length: self.length)
var lastRange: NSRange? = nil
var isAttributePresentInLastRange = false

self.enumerateAttributes(in: enumerationRange, options: []) { attributes, currentRange, _ in
let isAttributePresent = attributes[attributeName] != nil
if let lastRangeUnwrapped = lastRange {
if isAttributePresentInLastRange != isAttributePresent {
// Process the last range if the attribute presence state changes
block(isAttributePresentInLastRange, lastRangeUnwrapped)
lastRange = currentRange
} else {
// Extend the last range efficiently if the state hasn't changed
lastRange = NSRange(location: lastRangeUnwrapped.location, length: NSMaxRange(currentRange) - lastRangeUnwrapped.location)
}
} else {
// Initialize with the first range
lastRange = currentRange
}
isAttributePresentInLastRange = isAttributePresent
}

// Process the final range after enumeration
if let lastRangeUnwrapped = lastRange {
block(isAttributePresentInLastRange, lastRangeUnwrapped)
}
}
}

extension NSAttributedString {
Expand Down
21 changes: 21 additions & 0 deletions Proton/Tests/ExtensionTests/NSAttributedStringExtensionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,25 @@ class NSAttributedStringExtensionTests: XCTestCase {
let range = text.reverseRange(of: "isx", startingLocation: text.length - 1)
XCTAssertNil(range)
}

func testEnumeratesIgnoringValue() {
let text = NSMutableAttributedString(string: "This is a test string", attributes: [.inlineContentType: "a"])
text.append(NSAttributedString(string: " Some more text", attributes: [.inlineContentType: "b"]))
text.append(NSAttributedString(string: " And more text", attributes: [.inlineContentType: "c"]))
text.append(NSAttributedString(string: "Not with attribute", attributes: [:]))
text.append(NSAttributedString(string: "Again with attribute", attributes: [.inlineContentType: "c"]))

let expected = [
("This is a test string Some more text And more text", true),
("Not with attribute", false),
("Again with attribute", true)
]
var counter = 0
text.enumerateContinuousRangesByAttribute(.inlineContentType) { isPresent, range in
XCTAssertEqual(expected[counter].0, text.substring(from: range))
XCTAssertEqual(expected[counter].1, isPresent)
counter += 1
}
XCTAssertEqual(counter, 3)
}
}

0 comments on commit e2a92b9

Please sign in to comment.