Skip to content

Commit

Permalink
visibleSet concept
Browse files Browse the repository at this point in the history
  • Loading branch information
mattmassicotte committed Feb 20, 2024
1 parent 40c3726 commit 7b6651b
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 86 deletions.
2 changes: 1 addition & 1 deletion Sources/Neon/TextSystemInterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
14 changes: 7 additions & 7 deletions Sources/Neon/TextSystemStyler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public final class TextSystemStyler<Interface: TextSystemInterface> {
configuration: .init(
versionedContent: textSystem.content,
provider: tokenValidator.validationProvider,
priorityRangeProvider: { textSystem.visibleRange }
prioritySetProvider: { textSystem.visibleSet }
)
)
}
Expand All @@ -55,9 +55,9 @@ public final class TextSystemStyler<Interface: TextSystemInterface> {
///
/// 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)
}


Expand All @@ -66,14 +66,14 @@ public final class TextSystemStyler<Interface: TextSystemInterface> {
}

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)
}
}
46 changes: 23 additions & 23 deletions Sources/Neon/TextViewSystemInterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
}
}
Expand All @@ -74,7 +74,7 @@ extension TextViewSystemInterface: TextSystemInterface {
return LayoutManagerSystemInterface(
layoutManager: layoutManager,
attributeProvider: attributeProvider,
visibleRangeProvider: provider
visibleSetProvider: provider
)
}
#endif
Expand All @@ -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 {
Expand All @@ -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
}
}
Expand All @@ -141,8 +141,8 @@ extension LayoutManagerSystemInterface: TextSystemInterface {
}
}

public var visibleRange: NSRange {
visibleRangeProvider()
public var visibleSet: IndexSet {
visibleSetProvider()
}

public var content: NSTextStorage {
Expand All @@ -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
}
}
Expand Down Expand Up @@ -205,8 +205,8 @@ extension TextLayoutManagerSystemInterface: TextSystemInterface {
}
}

public var visibleRange: NSRange {
visibleRangeProvider()
public var visibleSet: IndexSet {
visibleSetProvider()
}

public var content: NSTextContentManager {
Expand All @@ -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
}

Expand All @@ -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 }
}
Expand All @@ -264,8 +264,8 @@ extension TextStorageSystemInterface: TextSystemInterface {
}
}

public var visibleRange: NSRange {
visibleRangeProvider()
public var visibleSet: IndexSet {
visibleSetProvider()
}

public var content: some VersionedContent {
Expand Down
10 changes: 5 additions & 5 deletions Sources/Neon/ThreePhaseTextSystemStyler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public final class ThreePhaseTextSystemStyler<Interface: TextSystemInterface> {
fallbackHandler: textSystem.validatorFallbackHandler(with: fallbackHandler),
secondaryProvider: textSystem.validatorSecondaryHandler(with: secondaryValidationProvider),
secondaryValidationDelay: 3.0,
priorityRangeProvider: { textSystem.visibleRange }
prioritySetProvider: { textSystem.visibleSet }
)
)
}
Expand All @@ -44,14 +44,14 @@ public final class ThreePhaseTextSystemStyler<Interface: TextSystemInterface> {
}

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)
}
}
38 changes: 12 additions & 26 deletions Sources/RangeState/RangeValidator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ public final class RangeValidator<Content: VersionedContent> {
/// 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
Expand Down Expand Up @@ -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
}
}

Expand Down
16 changes: 8 additions & 8 deletions Sources/RangeState/SinglePhaseRangeValidator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,23 @@ public final class SinglePhaseRangeValidator<Content: VersionedContent> {

public typealias ContentRange = RangeValidator<Content>.ContentRange
public typealias Provider = HybridValueProvider<ContentRange, Validation>
public typealias PriorityRangeProvider = () -> NSRange
public typealias PrioritySetProvider = () -> IndexSet

private typealias Sequence = AsyncStream<ContentRange>

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
}
}

Expand Down Expand Up @@ -63,11 +63,11 @@ public final class SinglePhaseRangeValidator<Content: VersionedContent> {
}

@discardableResult
public func validate(_ target: RangeTarget, prioritizing range: NSRange? = nil) -> RangeValidator<Content>.Action {
public func validate(_ target: RangeTarget, prioritizing set: IndexSet? = nil) -> RangeValidator<Content>.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:
Expand Down Expand Up @@ -97,9 +97,9 @@ public final class SinglePhaseRangeValidator<Content: VersionedContent> {
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)
Expand Down
Loading

0 comments on commit 7b6651b

Please sign in to comment.