diff --git a/Sources/Neon/TextSystemInterface.swift b/Sources/Neon/TextSystemInterface.swift index 6813b47..5fb2a1b 100644 --- a/Sources/Neon/TextSystemInterface.swift +++ b/Sources/Neon/TextSystemInterface.swift @@ -9,7 +9,7 @@ public protocol TextSystemInterface { func applyStyles(for application: TokenApplication) @MainActor - var visibleRange: NSRange { get } + var visibleSet: IndexSet { get } @MainActor var content: Content { get } diff --git a/Sources/Neon/TextSystemStyler.swift b/Sources/Neon/TextSystemStyler.swift index d2cc734..a631a75 100644 --- a/Sources/Neon/TextSystemStyler.swift +++ b/Sources/Neon/TextSystemStyler.swift @@ -28,7 +28,7 @@ public final class TextSystemStyler { configuration: .init( versionedContent: textSystem.content, provider: tokenValidator.validationProvider, - priorityRangeProvider: { textSystem.visibleRange } + prioritySetProvider: { textSystem.visibleSet } ) ) } @@ -55,9 +55,9 @@ public final class TextSystemStyler { /// /// You should invoke this method when the visible text in your system changes. public func visibleContentDidChange() { - let priorityRange = textSystem.visibleRange + let prioritySet = textSystem.visibleSet - validator.validate(.range(priorityRange), prioritizing: priorityRange) + validator.validate(.set(prioritySet), prioritizing: prioritySet) } @@ -66,14 +66,14 @@ public final class TextSystemStyler { } public func validate(_ target: RangeTarget) { - let priorityRange = textSystem.visibleRange + let prioritySet = textSystem.visibleSet - validator.validate(target, prioritizing: priorityRange) + validator.validate(target, prioritizing: prioritySet) } public func validate() { - let priorityRange = textSystem.visibleRange + let prioritySet = textSystem.visibleSet - validator.validate(.range(priorityRange), prioritizing: priorityRange) + validator.validate(.set(prioritySet), prioritizing: prioritySet) } } diff --git a/Sources/Neon/TextViewSystemInterface.swift b/Sources/Neon/TextViewSystemInterface.swift index 1f12dd6..2bd458c 100644 --- a/Sources/Neon/TextViewSystemInterface.swift +++ b/Sources/Neon/TextViewSystemInterface.swift @@ -57,14 +57,14 @@ public struct TextViewSystemInterface { extension TextViewSystemInterface: TextSystemInterface { private var effectiveInterface: (any TextSystemInterface)? { - let provider = { textView.visibleTextRange } + let provider = { IndexSet(textView.visibleTextRange) } if #available(macOS 12.0, iOS 16.0, tvOS 16.0, *) { if let textLayoutManager { return TextLayoutManagerSystemInterface( textLayoutManager: textLayoutManager, attributeProvider: attributeProvider, - visibleRangeProvider: provider + visibleSetProvider: provider ) } } @@ -74,7 +74,7 @@ extension TextViewSystemInterface: TextSystemInterface { return LayoutManagerSystemInterface( layoutManager: layoutManager, attributeProvider: attributeProvider, - visibleRangeProvider: provider + visibleSetProvider: provider ) } #endif @@ -89,8 +89,8 @@ extension TextViewSystemInterface: TextSystemInterface { effectiveInterface?.applyStyles(for: application) } - public var visibleRange: NSRange { - return textView.visibleTextRange + public var visibleSet: IndexSet { + IndexSet(textView.visibleTextRange) } public var content: NSTextStorage { @@ -106,19 +106,19 @@ extension TextViewSystemInterface: TextSystemInterface { public struct LayoutManagerSystemInterface { public let layoutManager: NSLayoutManager public let attributeProvider: TokenAttributeProvider - public let visibleRangeProvider: () -> NSRange + public let visibleSetProvider: () -> IndexSet private let placeholderStorage = NSTextStorage() - public init(layoutManager: NSLayoutManager, attributeProvider: @escaping TokenAttributeProvider, visibleRangeProvider: @escaping () -> NSRange) { + public init(layoutManager: NSLayoutManager, attributeProvider: @escaping TokenAttributeProvider, visibleSetProvider: @escaping () -> IndexSet) { self.layoutManager = layoutManager self.attributeProvider = attributeProvider - self.visibleRangeProvider = visibleRangeProvider + self.visibleSetProvider = visibleSetProvider } public init?(textView: TextView, attributeProvider: @escaping TokenAttributeProvider) { guard let layoutManager = textView.layoutManager else { return nil } self.layoutManager = layoutManager - self.visibleRangeProvider = { textView.visibleTextRange } + self.visibleSetProvider = { IndexSet(textView.visibleTextRange) } self.attributeProvider = attributeProvider } } @@ -141,8 +141,8 @@ extension LayoutManagerSystemInterface: TextSystemInterface { } } - public var visibleRange: NSRange { - visibleRangeProvider() + public var visibleSet: IndexSet { + visibleSetProvider() } public var content: NSTextStorage { @@ -158,19 +158,19 @@ extension LayoutManagerSystemInterface: TextSystemInterface { public struct TextLayoutManagerSystemInterface { public let textLayoutManager: NSTextLayoutManager public let attributeProvider: TokenAttributeProvider - public let visibleRangeProvider: () -> NSRange + public let visibleSetProvider: () -> IndexSet private let placholderContent = NSTextContentManager() - public init(textLayoutManager: NSTextLayoutManager, attributeProvider: @escaping TokenAttributeProvider, visibleRangeProvider: @escaping () -> NSRange) { + public init(textLayoutManager: NSTextLayoutManager, attributeProvider: @escaping TokenAttributeProvider, visibleSetProvider: @escaping () -> IndexSet) { self.textLayoutManager = textLayoutManager self.attributeProvider = attributeProvider - self.visibleRangeProvider = visibleRangeProvider + self.visibleSetProvider = visibleSetProvider } public init?(textView: TextView, attributeProvider: @escaping TokenAttributeProvider) { guard let textLayoutManager = textView.textLayoutManager else { return nil } self.textLayoutManager = textLayoutManager - self.visibleRangeProvider = { textView.visibleTextRange } + self.visibleSetProvider = { IndexSet(textView.visibleTextRange) } self.attributeProvider = attributeProvider } } @@ -205,8 +205,8 @@ extension TextLayoutManagerSystemInterface: TextSystemInterface { } } - public var visibleRange: NSRange { - visibleRangeProvider() + public var visibleSet: IndexSet { + visibleSetProvider() } public var content: NSTextContentManager { @@ -220,17 +220,17 @@ public struct TextStorageSystemInterface { private let textStorage: NSTextStorage public let attributeProvider: TokenAttributeProvider public let defaultAttributesProvider: () -> [NSAttributedString.Key : Any] - public let visibleRangeProvider: () -> NSRange + public let visibleSetProvider: () -> IndexSet public init( textStorage: NSTextStorage, attributeProvider: @escaping TokenAttributeProvider, - visibleRangeProvider: @escaping () -> NSRange, + visibleSetProvider: @escaping () -> IndexSet, defaultAttributesProvider: @escaping () -> [NSAttributedString.Key : Any] ) { self.textStorage = textStorage self.attributeProvider = attributeProvider - self.visibleRangeProvider = visibleRangeProvider + self.visibleSetProvider = visibleSetProvider self.defaultAttributesProvider = defaultAttributesProvider } @@ -240,7 +240,7 @@ public struct TextStorageSystemInterface { #else self.textStorage = textView.textStorage #endif - self.visibleRangeProvider = { textView.visibleTextRange } + self.visibleSetProvider = { IndexSet(textView.visibleTextRange) } self.attributeProvider = attributeProvider self.defaultAttributesProvider = { textView.typingAttributes } } @@ -264,8 +264,8 @@ extension TextStorageSystemInterface: TextSystemInterface { } } - public var visibleRange: NSRange { - visibleRangeProvider() + public var visibleSet: IndexSet { + visibleSetProvider() } public var content: some VersionedContent { diff --git a/Sources/Neon/ThreePhaseTextSystemStyler.swift b/Sources/Neon/ThreePhaseTextSystemStyler.swift index 4d98356..b63ee34 100644 --- a/Sources/Neon/ThreePhaseTextSystemStyler.swift +++ b/Sources/Neon/ThreePhaseTextSystemStyler.swift @@ -30,7 +30,7 @@ public final class ThreePhaseTextSystemStyler { fallbackHandler: textSystem.validatorFallbackHandler(with: fallbackHandler), secondaryProvider: textSystem.validatorSecondaryHandler(with: secondaryValidationProvider), secondaryValidationDelay: 3.0, - priorityRangeProvider: { textSystem.visibleRange } + prioritySetProvider: { textSystem.visibleSet } ) ) } @@ -44,14 +44,14 @@ public final class ThreePhaseTextSystemStyler { } public func validate(_ target: RangeTarget) { - let priorityRange = textSystem.visibleRange + let prioritySet = textSystem.visibleSet - validator.validate(target, prioritizing: priorityRange) + validator.validate(target, prioritizing: prioritySet) } public func validate() { - let priorityRange = textSystem.visibleRange + let prioritySet = textSystem.visibleSet - validator.validate(.range(priorityRange), prioritizing: priorityRange) + validator.validate(.set(prioritySet), prioritizing: prioritySet) } } diff --git a/Sources/RangeState/RangeValidator.swift b/Sources/RangeState/RangeValidator.swift index ffcb305..a3137d3 100644 --- a/Sources/RangeState/RangeValidator.swift +++ b/Sources/RangeState/RangeValidator.swift @@ -52,10 +52,10 @@ public final class RangeValidator { /// Begin a validation pass. /// /// This must ultimately be paired with a matching call to `completeValidation(of:with:)`. - public func beginValidation(of target: RangeTarget, prioritizing range: NSRange? = nil) -> Action { + public func beginValidation(of target: RangeTarget, prioritizing set: IndexSet? = nil) -> Action { let set = target.indexSet(with: length) - guard let neededRange = nextNeededRange(in: set, prioritizing: range) else { return .none } + guard let neededRange = nextNeededRange(in: set, prioritizing: set) else { return .none } self.pendingSet.insert(range: neededRange) self.pendingRequests += 1 @@ -140,33 +140,19 @@ extension RangeValidator { extension RangeValidator { /// Computes the next contiguous invalid range - private func nextNeededRange(in set: IndexSet, prioritizing priorityRange: NSRange?) -> NSRange? { - // determine what parts of the target set are actually invalid + private func nextNeededRange(in set: IndexSet, prioritizing prioritySet: IndexSet?) -> NSRange? { + let prioritySet = prioritySet ?? fullSet + + // the candidate set is: + // - invalid + // - within our priority (or everything if we have none) + // - not already pending let workingInvalidSet = invalidSet .intersection(set) + .intersection(prioritySet) + .subtracting(pendingSet) - // here's a trick. Create a set with a single range, and then remove - // any pending ranges from it. The result can be used to determine the longest - // ranges that do not overlap pending. - let spanSet = workingInvalidSet - .limitSpanningRange - .map({ IndexSet(integersIn: $0) }) ?? IndexSet() - - let candidateSet = spanSet.subtracting(pendingSet) - - // We want to prioritize the invalid ranges that are actually in the target set - let hasInvalidRanges = set.intersection(invalidSet).isEmpty == false - let limit = priorityRange?.location ?? 0 - - // now get back the first range which is the longest continuous - // range that includes invalid regions - let range = candidateSet.nsRangeView.first { range in - guard hasInvalidRanges else { return true } - - return range.max > limit - } - - return range + return workingInvalidSet.nsRangeView.first } } diff --git a/Sources/RangeState/SinglePhaseRangeValidator.swift b/Sources/RangeState/SinglePhaseRangeValidator.swift index 22ce5ab..5a0a302 100644 --- a/Sources/RangeState/SinglePhaseRangeValidator.swift +++ b/Sources/RangeState/SinglePhaseRangeValidator.swift @@ -8,23 +8,23 @@ public final class SinglePhaseRangeValidator { public typealias ContentRange = RangeValidator.ContentRange public typealias Provider = HybridValueProvider - public typealias PriorityRangeProvider = () -> NSRange + public typealias PrioritySetProvider = () -> IndexSet private typealias Sequence = AsyncStream public struct Configuration { public let versionedContent: Content public let provider: Provider - public let priorityRangeProvider: PriorityRangeProvider? + public let prioritySetProvider: PrioritySetProvider? public init( versionedContent: Content, provider: Provider, - priorityRangeProvider: PriorityRangeProvider? = nil + prioritySetProvider: PrioritySetProvider? = nil ) { self.versionedContent = versionedContent self.provider = provider - self.priorityRangeProvider = priorityRangeProvider + self.prioritySetProvider = prioritySetProvider } } @@ -63,11 +63,11 @@ public final class SinglePhaseRangeValidator { } @discardableResult - public func validate(_ target: RangeTarget, prioritizing range: NSRange? = nil) -> RangeValidator.Action { + public func validate(_ target: RangeTarget, prioritizing set: IndexSet? = nil) -> RangeValidator.Action { // capture this first, because we're about to start one let outstanding = primaryValidator.hasOutstandingValidations - let action = primaryValidator.beginValidation(of: target, prioritizing: range) + let action = primaryValidator.beginValidation(of: target, prioritizing: set) switch action { case .none: @@ -97,9 +97,9 @@ public final class SinglePhaseRangeValidator { switch validation { case .stale: DispatchQueue.main.backport.asyncUnsafe { - let priorityRange = self.configuration.priorityRangeProvider?() ?? contentRange.value + let prioritySet = self.configuration.prioritySetProvider?() ?? IndexSet(contentRange.value) - self.validate(.range(priorityRange)) + self.validate(.set(prioritySet)) } case let .success(range): validationHandler(range) diff --git a/Sources/RangeState/ThreePhaseRangeValidator.swift b/Sources/RangeState/ThreePhaseRangeValidator.swift index e2a8826..d0c966d 100644 --- a/Sources/RangeState/ThreePhaseRangeValidator.swift +++ b/Sources/RangeState/ThreePhaseRangeValidator.swift @@ -23,7 +23,7 @@ public final class ThreePhaseRangeValidator { public let fallbackHandler: FallbackHandler? public let secondaryProvider: SecondaryValidationProvider? public let secondaryValidationDelay: TimeInterval - public let priorityRangeProvider: PrimaryValidator.PriorityRangeProvider? + public let prioritySetProvider: PrimaryValidator.PrioritySetProvider? public init( versionedContent: Content, @@ -31,14 +31,14 @@ public final class ThreePhaseRangeValidator { fallbackHandler: FallbackHandler? = nil, secondaryProvider: SecondaryValidationProvider? = nil, secondaryValidationDelay: TimeInterval = 2.0, - priorityRangeProvider: PrimaryValidator.PriorityRangeProvider? + prioritySetProvider: PrimaryValidator.PrioritySetProvider? ) { self.versionedContent = versionedContent self.provider = provider self.fallbackHandler = fallbackHandler self.secondaryProvider = secondaryProvider self.secondaryValidationDelay = secondaryValidationDelay - self.priorityRangeProvider = priorityRangeProvider + self.prioritySetProvider = prioritySetProvider } } @@ -55,7 +55,7 @@ public final class ThreePhaseRangeValidator { configuration: .init( versionedContent: configuration.versionedContent, provider: configuration.provider, - priorityRangeProvider: configuration.priorityRangeProvider + prioritySetProvider: configuration.prioritySetProvider ) ) @@ -76,21 +76,21 @@ public final class ThreePhaseRangeValidator { secondaryValidator?.invalidate(target) } - public func validate(_ target: RangeTarget, prioritizing range: NSRange? = nil) { - let action = primaryValidator.validate(target, prioritizing: range) + public func validate(_ target: RangeTarget, prioritizing set: IndexSet? = nil) { + let action = primaryValidator.validate(target, prioritizing: set) switch action { case .none: - scheduleSecondaryValidation(of: target, prioritizing: range) + scheduleSecondaryValidation(of: target, prioritizing: set) case let .needed(contentRange): - fallbackValidate(contentRange.value, prioritizing: range) + fallbackValidate(contentRange.value, prioritizing: set) } } - private func fallbackValidate(_ targetRange: NSRange, prioritizing range: NSRange?) -> Void { + private func fallbackValidate(_ targetRange: NSRange, prioritizing set: IndexSet?) -> Void { guard let provider = configuration.fallbackHandler else { return } - let action = fallbackValidator.beginValidation(of: .range(targetRange), prioritizing: range) + let action = fallbackValidator.beginValidation(of: .range(targetRange), prioritizing: set) switch action { case .none: @@ -127,15 +127,15 @@ public final class ThreePhaseRangeValidator { extension ThreePhaseRangeValidator { private func handlePrimaryValidation(of range: NSRange) { let target = RangeTarget.range(range) - let priorityRange = configuration.priorityRangeProvider?() ?? range + let prioritySet = configuration.prioritySetProvider?() ?? IndexSet(range) fallbackValidator.invalidate(target) secondaryValidator?.invalidate(target) - scheduleSecondaryValidation(of: target, prioritizing: priorityRange) + scheduleSecondaryValidation(of: target, prioritizing: prioritySet) } - private func scheduleSecondaryValidation(of target: RangeTarget, prioritizing range: NSRange?) { + private func scheduleSecondaryValidation(of target: RangeTarget, prioritizing set: IndexSet?) { if configuration.secondaryProvider == nil || secondaryValidator == nil { return } @@ -148,11 +148,11 @@ extension ThreePhaseRangeValidator { self.task = Task { try await Task.sleep(nanoseconds: delay) - await secondaryValidate(target: target, requestingVersion: requestingVersion, prioritizing: range) + await secondaryValidate(target: target, requestingVersion: requestingVersion, prioritizing: set) } } - private func secondaryValidate(target: RangeTarget, requestingVersion: Content.Version, prioritizing range: NSRange?) async { + private func secondaryValidate(target: RangeTarget, requestingVersion: Content.Version, prioritizing set: IndexSet?) async { guard requestingVersion == self.version, let validator = secondaryValidator, @@ -161,7 +161,7 @@ extension ThreePhaseRangeValidator { return } - let action = validator.beginValidation(of: target, prioritizing: range) + let action = validator.beginValidation(of: target, prioritizing: set) switch action { case .none: