Skip to content

Commit

Permalink
Removing priority set concept, new tests for bug fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
mattmassicotte committed Dec 27, 2024
1 parent 32ef005 commit d7837cd
Show file tree
Hide file tree
Showing 19 changed files with 340 additions and 244 deletions.
2 changes: 1 addition & 1 deletion Sources/Neon/TextSystemInterface+Validation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ extension TextSystemInterface {
mainActorAsyncValue: { contentRange in
await asyncValidate(
contentRange,
provider: { range in await provider.async(range) }
provider: { range in await provider.async(isolation: MainActor.shared, range) }
)
}
)
Expand Down
3 changes: 0 additions & 3 deletions Sources/Neon/TextSystemInterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ public protocol TextSystemInterface {
@MainActor
func applyStyles(for application: TokenApplication)

@MainActor
var visibleSet: IndexSet { get }

@MainActor
var content: Content { get }
}
Expand Down
30 changes: 5 additions & 25 deletions Sources/Neon/TextSystemStyler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import RangeState
///
/// This is the main component that coordinates the styling and invalidation of text. It interfaces with the text system via `TextSystemInterface`. Actual token information is provided from a `TokenProvider`.
///
/// The `TextSystemInterface` is what to update, but it is up to you to tell it when that updating is needed. This is done via the `invalidate(_:)` call, as well as `visibleContentDidChange()`. It will be also be done automatically when the content changes.
/// The `TextSystemInterface` is what to update, but it is up to you to tell it when that updating is needed. This is done via the `invalidate(_:)` call, as well as `validate(_:)`. It will be also be done automatically when the content changes.
///
/// > Note: A `TextSystemStyler` must be informed of all text content changes made using `didChangeContent(in:, delta:)`.
@MainActor
Expand All @@ -27,10 +27,8 @@ public final class TextSystemStyler<Interface: TextSystemInterface> {
self.validator = SinglePhaseRangeValidator(
configuration: .init(
versionedContent: textSystem.content,
provider: tokenValidator.validationProvider,
prioritySetProvider: { textSystem.visibleSet }
),
isolation: MainActor.shared
provider: tokenValidator.validationProvider
)
)
}

Expand All @@ -52,29 +50,11 @@ public final class TextSystemStyler<Interface: TextSystemInterface> {
validator.contentChanged(in: range, delta: delta)
}

/// Calculates any newly-visible text that is invalid
///
/// You should invoke this method when the visible text in your system changes.
public func visibleContentDidChange() {
let prioritySet = textSystem.visibleSet

validator.validate(.set(prioritySet), prioritizing: prioritySet, isolation: MainActor.shared)
}


public func invalidate(_ target: RangeTarget) {
validator.invalidate(target)
}

public func validate(_ target: RangeTarget) {
let prioritySet = textSystem.visibleSet

validator.validate(target, prioritizing: prioritySet, isolation: MainActor.shared)
}

public func validate() {
let prioritySet = textSystem.visibleSet

validator.validate(.set(prioritySet), prioritizing: prioritySet, isolation: MainActor.shared)
public func validate(_ target: RangeTarget = .all) {
validator.validate(target)
}
}
4 changes: 3 additions & 1 deletion Sources/Neon/TextViewHighlighter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,9 @@ extension TextViewHighlighter {
}

@objc private func visibleContentChanged(_ notification: NSNotification) {
styler.visibleContentDidChange()
let visibleRange = textView.visibleTextRange

styler.validate(.range(visibleRange))
}
}

Expand Down
38 changes: 4 additions & 34 deletions Sources/Neon/TextViewSystemInterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,11 @@ public struct TextViewSystemInterface {

extension TextViewSystemInterface: TextSystemInterface {
private var effectiveInterface: (any TextSystemInterface)? {
let provider = { visibleSet }

if #available(macOS 12.0, iOS 16.0, tvOS 16.0, *) {
if let textLayoutManager {
return TextLayoutManagerSystemInterface(
textLayoutManager: textLayoutManager,
attributeProvider: attributeProvider,
visibleSetProvider: provider
attributeProvider: attributeProvider
)
}
}
Expand All @@ -73,8 +70,7 @@ extension TextViewSystemInterface: TextSystemInterface {
if let layoutManager {
return LayoutManagerSystemInterface(
layoutManager: layoutManager,
attributeProvider: attributeProvider,
visibleSetProvider: provider
attributeProvider: attributeProvider
)
}
#endif
Expand All @@ -89,10 +85,6 @@ extension TextViewSystemInterface: TextSystemInterface {
effectiveInterface?.applyStyles(for: application)
}

public var visibleSet: IndexSet {
IndexSet(integersIn: textView.visibleTextRange)
}

public var content: NSTextStorage {
textStorage
}
Expand All @@ -106,19 +98,16 @@ extension TextViewSystemInterface: TextSystemInterface {
public struct LayoutManagerSystemInterface {
public let layoutManager: NSLayoutManager
public let attributeProvider: TokenAttributeProvider
public let visibleSetProvider: () -> IndexSet
private let placeholderStorage = NSTextStorage()

public init(layoutManager: NSLayoutManager, attributeProvider: @escaping TokenAttributeProvider, visibleSetProvider: @escaping () -> IndexSet) {
public init(layoutManager: NSLayoutManager, attributeProvider: @escaping TokenAttributeProvider) {
self.layoutManager = layoutManager
self.attributeProvider = attributeProvider
self.visibleSetProvider = visibleSetProvider
}

public init?(textView: TextView, attributeProvider: @escaping TokenAttributeProvider) {
guard let layoutManager = textView.layoutManager else { return nil }
self.layoutManager = layoutManager
self.visibleSetProvider = { IndexSet(integersIn: textView.visibleTextRange) }
self.attributeProvider = attributeProvider
}
}
Expand All @@ -141,10 +130,6 @@ extension LayoutManagerSystemInterface: TextSystemInterface {
}
}

public var visibleSet: IndexSet {
visibleSetProvider()
}

public var content: NSTextStorage {
layoutManager.textStorage ?? placeholderStorage
}
Expand All @@ -158,19 +143,16 @@ extension LayoutManagerSystemInterface: TextSystemInterface {
public struct TextLayoutManagerSystemInterface {
public let textLayoutManager: NSTextLayoutManager
public let attributeProvider: TokenAttributeProvider
public let visibleSetProvider: () -> IndexSet
private let placholderContent = NSTextContentManager()

public init(textLayoutManager: NSTextLayoutManager, attributeProvider: @escaping TokenAttributeProvider, visibleSetProvider: @escaping () -> IndexSet) {
public init(textLayoutManager: NSTextLayoutManager, attributeProvider: @escaping TokenAttributeProvider) {
self.textLayoutManager = textLayoutManager
self.attributeProvider = attributeProvider
self.visibleSetProvider = visibleSetProvider
}

public init?(textView: TextView, attributeProvider: @escaping TokenAttributeProvider) {
guard let textLayoutManager = textView.textLayoutManager else { return nil }
self.textLayoutManager = textLayoutManager
self.visibleSetProvider = { IndexSet(integersIn: textView.visibleTextRange) }
self.attributeProvider = attributeProvider
}
}
Expand Down Expand Up @@ -205,10 +187,6 @@ extension TextLayoutManagerSystemInterface: TextSystemInterface {
}
}

public var visibleSet: IndexSet {
visibleSetProvider()
}

public var content: NSTextContentManager {
contentManager
}
Expand All @@ -220,17 +198,14 @@ public struct TextStorageSystemInterface {
private let textStorage: NSTextStorage
public let attributeProvider: TokenAttributeProvider
public let defaultAttributesProvider: () -> [NSAttributedString.Key : Any]
public let visibleSetProvider: () -> IndexSet

public init(
textStorage: NSTextStorage,
attributeProvider: @escaping TokenAttributeProvider,
visibleSetProvider: @escaping () -> IndexSet,
defaultAttributesProvider: @escaping () -> [NSAttributedString.Key : Any]
) {
self.textStorage = textStorage
self.attributeProvider = attributeProvider
self.visibleSetProvider = visibleSetProvider
self.defaultAttributesProvider = defaultAttributesProvider
}

Expand All @@ -240,7 +215,6 @@ public struct TextStorageSystemInterface {
#else
self.textStorage = textView.textStorage
#endif
self.visibleSetProvider = { IndexSet(integersIn: textView.visibleTextRange) }
self.attributeProvider = attributeProvider
self.defaultAttributesProvider = { textView.typingAttributes }
}
Expand Down Expand Up @@ -268,10 +242,6 @@ extension TextStorageSystemInterface: TextSystemInterface {
textStorage.endEditing()
}

public var visibleSet: IndexSet {
visibleSetProvider()
}

public var content: some VersionedContent {
textStorage
}
Expand Down
16 changes: 6 additions & 10 deletions Sources/Neon/ThreePhaseTextSystemStyler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ public final class ThreePhaseTextSystemStyler<Interface: TextSystemInterface> {
provider: tokenValidator.validationProvider,
fallbackHandler: textSystem.validatorFallbackHandler(with: fallbackHandler),
secondaryProvider: textSystem.validatorSecondaryHandler(with: secondaryValidationProvider),
secondaryValidationDelay: 3.0,
prioritySetProvider: { textSystem.visibleSet }
secondaryValidationDelay: 3.0
),
isolation: MainActor.shared
)
Expand All @@ -44,15 +43,12 @@ public final class ThreePhaseTextSystemStyler<Interface: TextSystemInterface> {
validator.invalidate(target)
}

public func validate(_ target: RangeTarget) {
let prioritySet = textSystem.visibleSet

validator.validate(target, prioritizing: prioritySet, isolation: MainActor.shared)
public func validate(_ target: RangeTarget = .all) {
validator.validate(target, isolation: MainActor.shared)
}

public func validate() {
let prioritySet = textSystem.visibleSet

validator.validate(.set(prioritySet), prioritizing: prioritySet, isolation: MainActor.shared)
public var name: String? {
get { validator.name }
set { validator.name = newValue }
}
}
2 changes: 1 addition & 1 deletion Sources/Neon/TreeSitterClient+Neon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ extension TreeSitterClient {
mainActorAsyncValue: { [highlightsProvider] range in
do {
let params = TreeSitterClient.ClientQueryParams(range: range, textProvider: provider)
let namedRanges = try await highlightsProvider.async(params)
let namedRanges = try await highlightsProvider.async(isolation: MainActor.shared, params)

return TokenApplication(namedRanges: namedRanges, nameMap: nameMap, range: range)
} catch {
Expand Down
71 changes: 71 additions & 0 deletions Sources/RangeState/AwaitableQueue.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
struct AwaitableQueue<Element> {
private typealias Continuation = CheckedContinuation<Void, Never>

private enum Event {
case element(Element)
case waiter(Continuation)
}

private var pendingEvents = [Event]()

init() {

}

public var hasPendingEvents: Bool {
pendingEvents.contains { event in
switch event {
case .element:
true
case .waiter:
false
}
}
}

public mutating func processingCompleted(isolation: isolated any Actor) async {
if hasPendingEvents == false {
return
}

await withCheckedContinuation { continuation in
self.pendingEvents.append(.waiter(continuation))
}
}

public mutating func enqueue(_ element: Element) {
self.pendingEvents.append(.element(element))
}

public var pendingElements: [Element] {
pendingEvents.compactMap {
switch $0 {
case let .element(value):
value
case .waiter:
nil
}
}
}

public mutating func handlePendingWaiters() {
while let event = pendingEvents.first {
guard case let .waiter(continuation) = event else { break }

continuation.resume()
pendingEvents.removeFirst()
}
}

mutating func next() -> Element? {
handlePendingWaiters()

guard case let .element(first) = pendingEvents.first else {
return nil
}

self.pendingEvents.removeFirst()

return first
}
}
8 changes: 6 additions & 2 deletions Sources/RangeState/HybridSyncAsyncValueProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Foundation
/// A type that can perform work both synchronously and asynchronously.
public struct HybridSyncAsyncValueProvider<Input, Output, Failure: Error> {
public typealias SyncValueProvider = (Input) throws(Failure) -> Output?
public typealias AsyncValueProvider = (isolated (any Actor)?, sending Input) async throws(Failure) -> sending Output
public typealias AsyncValueProvider = (isolated (any Actor), sending Input) async throws(Failure) -> sending Output

public let syncValueProvider: SyncValueProvider
public let asyncValueProvider: AsyncValueProvider
Expand All @@ -16,10 +16,14 @@ public struct HybridSyncAsyncValueProvider<Input, Output, Failure: Error> {
self.asyncValueProvider = asyncValue
}

public func async(isolation: isolated (any Actor)? = #isolation, _ input: sending Input) async throws(Failure) -> sending Output {
public func async(isolation: isolated (any Actor), _ input: sending Input) async throws(Failure) -> sending Output {
try await asyncValueProvider(isolation, input)
}

@MainActor
public func async(_ input: sending Input) async throws(Failure) -> sending Output {
try await asyncValueProvider(MainActor.shared, input)
}

public func sync(_ input: Input) throws(Failure) -> Output? {
try syncValueProvider(input)
Expand Down
8 changes: 5 additions & 3 deletions Sources/RangeState/HybridValueProvider+RangeProcessor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Foundation
extension HybridSyncAsyncValueProvider {
/// Construct a `HybridSyncAsyncValueProvider` that will first attempt to process a location using a `RangeProcessor`.
public init(
isolation: isolated (any Actor)? = #isolation,
isolation: isolated (any Actor),
rangeProcessor: RangeProcessor,
inputTransformer: @escaping (Input) -> (Int, RangeFillMode),
syncValue: @escaping SyncValueProvider,
Expand All @@ -20,11 +20,13 @@ extension HybridSyncAsyncValueProvider {
return nil
}

func _asyncVersion(isolation: isolated(any Actor)?, input: sending Input) async throws(Failure) -> sending Output {
func _asyncVersion(isolation: isolated (any Actor), input: sending Input) async throws(Failure) -> sending Output {
let (location, fill) = inputTransformer(input)

rangeProcessor.processLocation(isolation: isolation, location, mode: fill)
await rangeProcessor.processingCompleted()
print("start", input)
await rangeProcessor.processingCompleted(isolation: isolation)
print("end", input)

return try await asyncValue(input)
}
Expand Down
Loading

0 comments on commit d7837cd

Please sign in to comment.