Skip to content

Commit

Permalink
RangeProcessor API change, tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mattmassicotte committed Jan 18, 2024
1 parent 1e3c7d3 commit e2a38d8
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 29 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/ChimeHQ/SwiftTreeSitter", revision: "87ed52a71d4ad6b5e6a11185b42f6f74eb5b47da"),
.package(url: "https://github.com/ChimeHQ/Rearrange", revision: "3b154b59176ce40839071e7830c83410a0139d86"),
.package(url: "https://github.com/ChimeHQ/Rearrange", from: "1.8.0"),
],
targets: [
.target(name: "ConcurrencyCompatibility"),
Expand Down
46 changes: 28 additions & 18 deletions Sources/RangeState/RangeProcessor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public enum RangeFillMode: Sendable, Hashable {
}

/// A type that can perform on-demand processing of range-based data.
@MainActor
public final class RangeProcessor {
private typealias Continuation = CheckedContinuation<(), Never>
private typealias VersionedMutation = Versioned<Int, RangeMutation>
Expand All @@ -31,7 +32,7 @@ public final class RangeProcessor {
/// Function to apply changes.
///
/// These mutations can come from the content being modified or from operations that require lazy processing. The parameter's postApplyLimit property defines the maximum read location and needs to be respected to preserve the lazy access semantics.
public typealias ChangeHandler = (RangeMutation) -> Void
public typealias ChangeHandler = (RangeMutation, @MainActor @escaping () -> Void) -> Void
public typealias LengthProvider = () -> Int

public struct Configuration {
Expand All @@ -58,7 +59,7 @@ public final class RangeProcessor {
public let configuration: Configuration

// when starting, we have not even processed zero yet
public private(set) var maximumProcessedLocation: Int = -1
public private(set) var maximumProcessedLocation: Int?
private var targetProcessingLocation: Int = -1
private var pendingProcessedLocation: Int = -1
private var version = 0
Expand Down Expand Up @@ -88,7 +89,7 @@ extension RangeProcessor {

precondition(location <= length)

let start = max(maximumProcessedLocation, 0)
let start = max(maximumProcessedLocation ?? 0, 0)
let realDelta = location - start

if realDelta <= 0 {
Expand Down Expand Up @@ -118,7 +119,7 @@ extension RangeProcessor {
// update our target
self.targetProcessingLocation = max(location, targetProcessingLocation)

continueFillingIfNeeded()
scheduleFilling()
case .required:
processMutation(mutation)
}
Expand All @@ -128,7 +129,11 @@ extension RangeProcessor {
}

public func processed(_ location: Int) -> Bool {
maximumProcessedLocation >= location
precondition(location >= 0)

guard let maximumProcessedLocation else { return false }

return maximumProcessedLocation >= location
}

public func processed(_ range: NSRange) -> Bool {
Expand Down Expand Up @@ -191,10 +196,12 @@ extension RangeProcessor {

// at this point, it is possible that the target location is no longer meaningful. But that's ok, because it will be clamped and possibly overwritten anyways

configuration.changeHandler(mutation)
configuration.changeHandler(mutation, {
self.completeContentChanged(mutation)
})
}

public func completeContentChanged(_ mutation: RangeMutation) {
private func completeContentChanged(_ mutation: RangeMutation) {
self.processedVersion += 1

resumeLeadingContinuations()
Expand All @@ -213,15 +220,7 @@ extension RangeProcessor {

updateProcessedLocation(by: mutation.delta)

DispatchQueue.main.asyncUnsafe {
self.continueFillingIfNeeded()
}
}

public func completeContentChanged(in range: NSRange, delta: Int) {
let mutation = RangeMutation(range: range, delta: delta)

completeContentChanged(mutation)
scheduleFilling()
}

public func continueFillingIfNeeded() {
Expand All @@ -236,12 +235,23 @@ extension RangeProcessor {

processMutation(mutation)
}

private func scheduleFilling() {
DispatchQueue.main.async {
self.continueFillingIfNeeded()
}
}
}

extension RangeProcessor {
private func updateProcessedLocation(by delta: Int) {
self.maximumProcessedLocation += delta
precondition(maximumProcessedLocation >= 0)
var newMax = maximumProcessedLocation ?? 0

newMax += delta

precondition(newMax >= 0)

self.maximumProcessedLocation = newMax
}

private func resumeLeadingContinuations() {
Expand Down
15 changes: 5 additions & 10 deletions Sources/TreeSitterClient/TreeSitterClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public final class TreeSitterClient {
configuration: .init(
deltaRange: Self.deltaRange,
lengthProvider: configuration.lengthProvider,
changeHandler: { [unowned self] in self.didChange($0) }
changeHandler: { [unowned self] in self.didChange($0, completion: $1) }
)
)
private lazy var sublayerValidator = SublayerValidator(
Expand Down Expand Up @@ -116,7 +116,7 @@ public final class TreeSitterClient {
}

private var maximumProcessedContent: LanguageLayer.Content {
configuration.contentProvider(rangeProcessor.maximumProcessedLocation)
configuration.contentProvider(rangeProcessor.maximumProcessedLocation ?? 0)
}
}

Expand All @@ -125,22 +125,17 @@ extension TreeSitterClient {
rangeProcessor.hasPendingChanges
}

private func didChange(_ mutation: RangeMutation) {
private func didChange(_ mutation: RangeMutation, completion: @MainActor @escaping () -> Void) {
let limit = mutation.postApplyLimit

let content = configuration.contentProvider(limit)

layerTree.didChangeContent(content, in: mutation.range, delta: mutation.delta, completion: { invalidated in
self.completeChange(mutation, invalidating: invalidated)
completion()
self.handleInvalidation(invalidated, sublayers: false)
})
}

private func completeChange(_ mutation: RangeMutation, invalidating invalidated: IndexSet) {
rangeProcessor.completeContentChanged(mutation)

handleInvalidation(invalidated, sublayers: false)
}

private func handleInvalidation(_ set: IndexSet, sublayers: Bool) {
let transformedSet = set.apply(rangeProcessor.pendingMutations)

Expand Down
54 changes: 54 additions & 0 deletions Tests/RangeStateTests/RangeProcessorTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import XCTest

import RangeState
import Rearrange

final class RangeProcessorTests: XCTestCase {
@MainActor
func testSynchronousFill() {
let exp = expectation(description: "mutation")

let changeHandler: RangeProcessor.ChangeHandler = { mutation, completion in
XCTAssertEqual(mutation, RangeMutation(range: NSRange(0..<0), delta: 10))

exp.fulfill()
completion()
}

let processor = RangeProcessor(
configuration: .init(
lengthProvider: { 100 },
changeHandler: changeHandler
)
)

XCTAssertTrue(processor.processLocation(10, mode: .required))

wait(for: [exp], enforceOrder: true)
}

@MainActor
func testOptionalFill() {
let exp = expectation(description: "mutation")

let changeHandler: RangeProcessor.ChangeHandler = { mutation, completion in
XCTAssertEqual(mutation, RangeMutation(range: NSRange(0..<0), delta: 10))

exp.fulfill()
completion()
}

let processor = RangeProcessor(
configuration: .init(
lengthProvider: { 100 },
changeHandler: changeHandler
)
)

XCTAssertFalse(processor.processLocation(10, mode: .optional))

wait(for: [exp], enforceOrder: true)

XCTAssert(processor.processed(10))
}
}

0 comments on commit e2a38d8

Please sign in to comment.