From d3c03cfe16b29b13f246dd08ab926296014a677f Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Wed, 28 May 2025 16:12:23 -0500 Subject: [PATCH 1/6] Make StyledRangeStore Generalized and `Sendable` --- .../Highlighting/Highlighter.swift | 2 +- .../StyledRangeContainer.swift | 51 +++++++--- .../StyledRangeStoreRun.swift | 47 --------- .../StyledRangeStore+Coalesce.swift | 6 +- .../StyledRangeStore+FindIndex.swift | 0 .../StyledRangeStore+OffsetMetric.swift | 0 .../StyledRangeStore+StyledRun.swift | 19 ++-- .../StyledRangeStore/StyledRangeStore.swift | 28 +++--- .../StyledRangeStoreRun.swift | 48 +++++++++ .../StyledRangeContainerTests.swift | 44 ++++----- .../Highlighting/StyledRangeStoreTests.swift | 97 ++++++++++--------- 11 files changed, 190 insertions(+), 152 deletions(-) delete mode 100644 Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStoreRun.swift rename Sources/CodeEditSourceEditor/{Highlighting/StyledRangeContainer => }/StyledRangeStore/StyledRangeStore+Coalesce.swift (89%) rename Sources/CodeEditSourceEditor/{Highlighting/StyledRangeContainer => }/StyledRangeStore/StyledRangeStore+FindIndex.swift (100%) rename Sources/CodeEditSourceEditor/{Highlighting/StyledRangeContainer => }/StyledRangeStore/StyledRangeStore+OffsetMetric.swift (100%) rename Sources/CodeEditSourceEditor/{Highlighting/StyledRangeContainer => }/StyledRangeStore/StyledRangeStore+StyledRun.swift (79%) rename Sources/CodeEditSourceEditor/{Highlighting/StyledRangeContainer => }/StyledRangeStore/StyledRangeStore.swift (79%) create mode 100644 Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStoreRun.swift diff --git a/Sources/CodeEditSourceEditor/Highlighting/Highlighter.swift b/Sources/CodeEditSourceEditor/Highlighting/Highlighter.swift index 884415bf0..4a9b0c9e4 100644 --- a/Sources/CodeEditSourceEditor/Highlighting/Highlighter.swift +++ b/Sources/CodeEditSourceEditor/Highlighting/Highlighter.swift @@ -276,7 +276,7 @@ extension Highlighter: StyledRangeContainerDelegate { guard let range = NSRange(location: offset, length: run.length).intersection(range) else { continue } - storage?.setAttributes(attributeProvider.attributesFor(run.capture), range: range) + storage?.setAttributes(attributeProvider.attributesFor(run.value?.capture), range: range) offset += range.length } diff --git a/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeContainer.swift b/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeContainer.swift index 6ac69bcc7..f159c25f7 100644 --- a/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeContainer.swift +++ b/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeContainer.swift @@ -18,7 +18,34 @@ protocol StyledRangeContainerDelegate: AnyObject { /// See ``runsIn(range:)`` for more details on how conflicting highlights are handled. @MainActor class StyledRangeContainer { - var _storage: [ProviderID: StyledRangeStore] = [:] + struct StyleElement: StyledRangeStoreElement, CustomDebugStringConvertible { + var capture: CaptureName? + var modifiers: CaptureModifierSet + + var isEmpty: Bool { + capture == nil && modifiers.isEmpty + } + + func combineLowerPriority(_ other: StyleElement?) -> StyleElement { + StyleElement( + capture: self.capture ?? other?.capture, + modifiers: modifiers.union(other?.modifiers ?? []) + ) + } + + func combineHigherPriority(_ other: StyleElement?) -> StyleElement { + StyleElement( + capture: other?.capture ?? self.capture, + modifiers: modifiers.union(other?.modifiers ?? []) + ) + } + + var debugDescription: String { + "\(capture?.stringValue ?? "(empty)"), \(modifiers)" + } + } + + var _storage: [ProviderID: StyledRangeStore] = [:] weak var delegate: StyledRangeContainerDelegate? /// Initialize the container with a list of provider identifiers. Each provider is given an id, they should be @@ -28,13 +55,13 @@ class StyledRangeContainer { /// - providers: An array of identifiers given to providers. init(documentLength: Int, providers: [ProviderID]) { for provider in providers { - _storage[provider] = StyledRangeStore(documentLength: documentLength) + _storage[provider] = StyledRangeStore(documentLength: documentLength) } } func addProvider(_ id: ProviderID, documentLength: Int) { assert(!_storage.keys.contains(id), "Provider already exists") - _storage[id] = StyledRangeStore(documentLength: documentLength) + _storage[id] = StyledRangeStore(documentLength: documentLength) } func removeProvider(_ id: ProviderID) { @@ -55,10 +82,10 @@ class StyledRangeContainer { /// /// - Parameter range: The range to query. /// - Returns: An array of continuous styled runs. - func runsIn(range: NSRange) -> [StyledRangeStoreRun] { + func runsIn(range: NSRange) -> [StyledRangeStoreRun] { // Ordered by priority, lower = higher priority. var allRuns = _storage.sorted(by: { $0.key < $1.key }).map { $0.value.runs(in: range.intRange) } - var runs: [StyledRangeStoreRun] = [] + var runs: [StyledRangeStoreRun] = [] var minValue = allRuns.compactMap { $0.last }.enumerated().min(by: { $0.1.length < $1.1.length }) @@ -93,8 +120,8 @@ class StyledRangeContainer { } func storageUpdated(replacedContentIn range: Range, withCount newLength: Int) { - _storage.values.forEach { - $0.storageUpdated(replacedCharactersIn: range, withCount: newLength) + for (key, value) in _storage { + _storage[key]?.storageUpdated(replacedCharactersIn: range, withCount: newLength) } } } @@ -109,11 +136,11 @@ extension StyledRangeContainer: HighlightProviderStateDelegate { /// - rangeToHighlight: The range to apply the highlights to. func applyHighlightResult(provider: ProviderID, highlights: [HighlightRange], rangeToHighlight: NSRange) { assert(rangeToHighlight != .notFound, "NSNotFound is an invalid highlight range") - guard let storage = _storage[provider] else { + guard var storage = _storage[provider] else { assertionFailure("No storage found for the given provider: \(provider)") return } - var runs: [StyledRangeStoreRun] = [] + var runs: [StyledRangeStoreRun] = [] var lastIndex = rangeToHighlight.lowerBound for highlight in highlights { @@ -123,10 +150,9 @@ extension StyledRangeContainer: HighlightProviderStateDelegate { continue // Skip! Overlapping } runs.append( - StyledRangeStoreRun( + StyledRangeStoreRun( length: highlight.range.length, - capture: highlight.capture, - modifiers: highlight.modifiers + value: StyleElement(capture: highlight.capture, modifiers: highlight.modifiers) ) ) lastIndex = highlight.range.max @@ -137,6 +163,7 @@ extension StyledRangeContainer: HighlightProviderStateDelegate { } storage.set(runs: runs, for: rangeToHighlight.intRange) + _storage[provider] = storage delegate?.styleContainerDidUpdate(in: rangeToHighlight) } } diff --git a/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStoreRun.swift b/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStoreRun.swift deleted file mode 100644 index 06335edba..000000000 --- a/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStoreRun.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// StyledRangeStoreRun.swift -// CodeEditSourceEditor -// -// Created by Khan Winter on 11/4/24. -// - -/// Consumer-facing value type for the stored values in this container. -struct StyledRangeStoreRun: Equatable, Hashable { - var length: Int - var capture: CaptureName? - var modifiers: CaptureModifierSet - - static func empty(length: Int) -> Self { - StyledRangeStoreRun(length: length, capture: nil, modifiers: []) - } - - var isEmpty: Bool { - capture == nil && modifiers.isEmpty - } - - mutating package func combineLowerPriority(_ other: borrowing StyledRangeStoreRun) { - if self.capture == nil { - self.capture = other.capture - } - self.modifiers.formUnion(other.modifiers) - } - - mutating package func combineHigherPriority(_ other: borrowing StyledRangeStoreRun) { - self.capture = other.capture ?? self.capture - self.modifiers.formUnion(other.modifiers) - } - - mutating package func subtractLength(_ other: borrowing StyledRangeStoreRun) { - self.length -= other.length - } -} - -extension StyledRangeStoreRun: CustomDebugStringConvertible { - var debugDescription: String { - if isEmpty { - "\(length) (empty)" - } else { - "\(length) (\(capture.debugDescription), \(modifiers.values.debugDescription))" - } - } -} diff --git a/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStore+Coalesce.swift b/Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+Coalesce.swift similarity index 89% rename from Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStore+Coalesce.swift rename to Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+Coalesce.swift index 542661b5a..39f26612e 100644 --- a/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStore+Coalesce.swift +++ b/Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+Coalesce.swift @@ -16,7 +16,7 @@ extension StyledRangeStore { /// rather than the queried one. /// /// - Parameter range: The range of the item to coalesce around. - func coalesceNearby(range: Range) { + mutating func coalesceNearby(range: Range) { var index = findIndex(at: range.lastIndex).index if index < _guts.endIndex && _guts.index(after: index) != _guts.endIndex { coalesceRunAfter(index: &index) @@ -30,11 +30,11 @@ extension StyledRangeStore { } /// Check if the run and the run after it are equal, and if so remove the next one and concatenate the two. - private func coalesceRunAfter(index: inout Index) { + private mutating func coalesceRunAfter(index: inout Index) { let thisRun = _guts[index] let nextRun = _guts[_guts.index(after: index)] - if thisRun.styleCompare(nextRun) { + if thisRun.compareValue(nextRun) { _guts.update(at: &index, by: { $0.length += nextRun.length }) var nextIndex = index diff --git a/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStore+FindIndex.swift b/Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+FindIndex.swift similarity index 100% rename from Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStore+FindIndex.swift rename to Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+FindIndex.swift diff --git a/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStore+OffsetMetric.swift b/Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+OffsetMetric.swift similarity index 100% rename from Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStore+OffsetMetric.swift rename to Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+OffsetMetric.swift diff --git a/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStore+StyledRun.swift b/Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+StyledRun.swift similarity index 79% rename from Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStore+StyledRun.swift rename to Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+StyledRun.swift index 3fe15a150..ea45402d9 100644 --- a/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStore+StyledRun.swift +++ b/Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+StyledRun.swift @@ -9,18 +9,25 @@ import _RopeModule extension StyledRangeStore { struct StyledRun { var length: Int - let capture: CaptureName? - let modifiers: CaptureModifierSet + let value: Element? static func empty(length: Int) -> Self { - StyledRun(length: length, capture: nil, modifiers: []) + StyledRun(length: length, value: nil) } /// Compare two styled ranges by their stored styles. /// - Parameter other: The range to compare to. /// - Returns: The result of the comparison. - func styleCompare(_ other: Self) -> Bool { - capture == other.capture && modifiers == other.modifiers + func compareValue(_ other: Self) -> Bool { + return if let lhs = value, let rhs = other.value { + lhs == rhs + } else if let lhs = value { + lhs.isEmpty + } else if let rhs = other.value { + rhs.isEmpty + } else { + true + } } } } @@ -50,7 +57,7 @@ extension StyledRangeStore.StyledRun: RopeElement { mutating func split(at index: Self.Index) -> Self { assert(index >= 0 && index <= length) - let tail = Self(length: length - index, capture: capture, modifiers: modifiers) + let tail = Self(length: length - index, value: value) length = index return tail } diff --git a/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStore.swift b/Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore.swift similarity index 79% rename from Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStore.swift rename to Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore.swift index 68d4056da..407f4fbf0 100644 --- a/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStore.swift +++ b/Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore.swift @@ -12,10 +12,11 @@ import _RopeModule /// /// Internally this class uses a `Rope` from the swift-collections package, allowing for efficient updates and /// retrievals. -final class StyledRangeStore { - typealias Run = StyledRangeStoreRun - typealias Index = Rope.Index - var _guts = Rope() +struct StyledRangeStore: Sendable { + typealias Run = StyledRangeStoreRun + typealias RopeType = Rope + typealias Index = RopeType.Index + var _guts = RopeType() var length: Int { _guts.count(in: OffsetMetric()) @@ -26,7 +27,7 @@ final class StyledRangeStore { private var cache: (range: Range, runs: [Run])? init(documentLength: Int) { - self._guts = Rope([StyledRun(length: documentLength, capture: nil, modifiers: [])]) + self._guts = RopeType([StyledRun(length: documentLength, value: nil)]) } // MARK: - Core @@ -48,7 +49,7 @@ final class StyledRangeStore { while index < _guts.endIndex { let run = _guts[index] - runs.append(Run(length: run.length - (offset ?? 0), capture: run.capture, modifiers: run.modifiers)) + runs.append(Run(length: run.length - (offset ?? 0), value: run.value)) index = _guts.index(after: index) offset = nil @@ -57,22 +58,21 @@ final class StyledRangeStore { return runs } - /// Sets a capture and modifiers for a range. + /// Sets a value for a range. /// - Parameters: - /// - capture: The capture to set. - /// - modifiers: The modifiers to set. + /// - value: The value to set for the given range. /// - range: The range to write to. - func set(capture: CaptureName, modifiers: CaptureModifierSet, for range: Range) { + mutating func set(value: Element, for range: Range) { assert(range.lowerBound >= 0, "Negative lowerBound") assert(range.upperBound <= _guts.count(in: OffsetMetric()), "upperBound outside valid range") - set(runs: [Run(length: range.length, capture: capture, modifiers: modifiers)], for: range) + set(runs: [Run(length: range.length, value: value)], for: range) } /// Replaces a range in the document with an array of runs. /// - Parameters: /// - runs: The runs to insert. /// - range: The range to replace. - func set(runs: [Run], for range: Range) { + mutating func set(runs: [Run], for range: Range) { let gutsRange = 0..<_guts.count(in: OffsetMetric()) if range.clamped(to: gutsRange) != range { let upperBound = range.clamped(to: gutsRange).upperBound @@ -83,7 +83,7 @@ final class StyledRangeStore { _guts.replaceSubrange( range, in: OffsetMetric(), - with: runs.map { StyledRun(length: $0.length, capture: $0.capture, modifiers: $0.modifiers) } + with: runs.map { StyledRun(length: $0.length, value: $0.value) } ) coalesceNearby(range: range) @@ -95,7 +95,7 @@ final class StyledRangeStore { extension StyledRangeStore { /// Handles keeping the internal storage in sync with the document. - func storageUpdated(replacedCharactersIn range: Range, withCount newLength: Int) { + mutating func storageUpdated(replacedCharactersIn range: Range, withCount newLength: Int) { assert(range.lowerBound >= 0, "Negative lowerBound") assert(range.upperBound <= _guts.count(in: OffsetMetric()), "upperBound outside valid range") diff --git a/Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStoreRun.swift b/Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStoreRun.swift new file mode 100644 index 000000000..6e3b0deb9 --- /dev/null +++ b/Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStoreRun.swift @@ -0,0 +1,48 @@ +// +// StyledRangeStoreRun.swift +// CodeEditSourceEditor +// +// Created by Khan Winter on 11/4/24. +// + +protocol StyledRangeStoreElement: Equatable, Hashable { + var isEmpty: Bool { get } + func combineLowerPriority(_ other: Self?) -> Self + func combineHigherPriority(_ other: Self?) -> Self +} + +/// Consumer-facing value type for the stored values in this container. +struct StyledRangeStoreRun: Equatable, Hashable { + var length: Int + var value: Element? + + static func empty(length: Int) -> Self { + StyledRangeStoreRun(length: length, value: nil) + } + + var isEmpty: Bool { + value?.isEmpty ?? true + } + + mutating func combineLowerPriority(_ other: StyledRangeStoreRun) { + value = value?.combineLowerPriority(other.value) ?? other.value + } + + mutating func combineHigherPriority(_ other: StyledRangeStoreRun) { + value = value?.combineHigherPriority(other.value) ?? other.value + } + + mutating func subtractLength(_ other: borrowing StyledRangeStoreRun) { + self.length -= other.length + } +} + +extension StyledRangeStoreRun: CustomDebugStringConvertible { + var debugDescription: String { + if let value = value as? CustomDebugStringConvertible { + "\(length) (\(value.debugDescription))" + } else { + "\(length) (empty)" + } + } +} diff --git a/Tests/CodeEditSourceEditorTests/Highlighting/StyledRangeContainerTests.swift b/Tests/CodeEditSourceEditorTests/Highlighting/StyledRangeContainerTests.swift index 1ea05fc20..6506f2eb0 100644 --- a/Tests/CodeEditSourceEditorTests/Highlighting/StyledRangeContainerTests.swift +++ b/Tests/CodeEditSourceEditorTests/Highlighting/StyledRangeContainerTests.swift @@ -2,7 +2,7 @@ import XCTest @testable import CodeEditSourceEditor final class StyledRangeContainerTests: XCTestCase { - typealias Run = StyledRangeStoreRun + typealias Run = StyledRangeStoreRun @MainActor func test_init() { @@ -27,16 +27,16 @@ final class StyledRangeContainerTests: XCTestCase { XCTAssertNotNil(store._storage[providers[0]]) XCTAssertEqual(store._storage[providers[0]]!.count, 3) - XCTAssertNil(store._storage[providers[0]]!.runs(in: 0..<100)[0].capture) - XCTAssertEqual(store._storage[providers[0]]!.runs(in: 0..<100)[1].capture, .comment) - XCTAssertNil(store._storage[providers[0]]!.runs(in: 0..<100)[2].capture) + XCTAssertNil(store._storage[providers[0]]!.runs(in: 0..<100)[0].value?.capture) + XCTAssertEqual(store._storage[providers[0]]!.runs(in: 0..<100)[1].value?.capture, .comment) + XCTAssertNil(store._storage[providers[0]]!.runs(in: 0..<100)[2].value?.capture) XCTAssertEqual( store.runsIn(range: NSRange(location: 0, length: 100)), [ - Run(length: 40, capture: nil, modifiers: []), - Run(length: 10, capture: .comment, modifiers: []), - Run(length: 50, capture: nil, modifiers: []) + Run(length: 40, value: nil), + Run(length: 10, value: .init(capture: .comment, modifiers: [])), + Run(length: 50, value: nil) ] ) } @@ -63,10 +63,10 @@ final class StyledRangeContainerTests: XCTestCase { XCTAssertEqual( store.runsIn(range: NSRange(location: 0, length: 100)), [ - Run(length: 40, capture: nil, modifiers: []), - Run(length: 5, capture: .comment, modifiers: []), - Run(length: 5, capture: .comment, modifiers: [.declaration]), - Run(length: 50, capture: nil, modifiers: []) + Run(length: 40, value: nil), + Run(length: 5, value: .init(capture: .comment, modifiers: [])), + Run(length: 5, value: .init(capture: .comment, modifiers: [.declaration])), + Run(length: 50, value: nil) ] ) } @@ -107,16 +107,16 @@ final class StyledRangeContainerTests: XCTestCase { XCTAssertEqual(runs.reduce(0, { $0 + $1.length}), 200) - XCTAssertEqual(runs[0], Run(length: 30, capture: nil, modifiers: [])) - XCTAssertEqual(runs[1], Run(length: 5, capture: .comment, modifiers: [])) - XCTAssertEqual(runs[2], Run(length: 5, capture: .comment, modifiers: [.declaration])) - XCTAssertEqual(runs[3], Run(length: 5, capture: .comment, modifiers: [.abstract, .declaration])) - XCTAssertEqual(runs[4], Run(length: 5, capture: .comment, modifiers: [])) - XCTAssertEqual(runs[5], Run(length: 30, capture: nil, modifiers: [])) - XCTAssertEqual(runs[6], Run(length: 10, capture: .string, modifiers: [])) - XCTAssertEqual(runs[7], Run(length: 10, capture: .string, modifiers: [.static])) - XCTAssertEqual(runs[8], Run(length: 5, capture: .string, modifiers: [.static, .modification])) - XCTAssertEqual(runs[9], Run(length: 5, capture: .string, modifiers: [.modification])) - XCTAssertEqual(runs[10], Run(length: 90, capture: nil, modifiers: [])) + XCTAssertEqual(runs[0], Run(length: 30, value: nil)) + XCTAssertEqual(runs[1], Run(length: 5, value: .init(capture: .comment, modifiers: []))) + XCTAssertEqual(runs[2], Run(length: 5, value: .init(capture: .comment, modifiers: [.declaration]))) + XCTAssertEqual(runs[3], Run(length: 5, value: .init(capture: .comment, modifiers: [.abstract, .declaration]))) + XCTAssertEqual(runs[4], Run(length: 5, value: .init(capture: .comment, modifiers: []))) + XCTAssertEqual(runs[5], Run(length: 30, value: nil)) + XCTAssertEqual(runs[6], Run(length: 10, value: .init(capture: .string, modifiers: []))) + XCTAssertEqual(runs[7], Run(length: 10, value: .init(capture: .string, modifiers: [.static]))) + XCTAssertEqual(runs[8], Run(length: 5, value: .init(capture: .string, modifiers: [.static, .modification]))) + XCTAssertEqual(runs[9], Run(length: 5, value: .init(capture: .string, modifiers: [.modification]))) + XCTAssertEqual(runs[10], Run(length: 90, value: nil)) } } diff --git a/Tests/CodeEditSourceEditorTests/Highlighting/StyledRangeStoreTests.swift b/Tests/CodeEditSourceEditorTests/Highlighting/StyledRangeStoreTests.swift index 5b43d5d2e..7a50e185d 100644 --- a/Tests/CodeEditSourceEditorTests/Highlighting/StyledRangeStoreTests.swift +++ b/Tests/CodeEditSourceEditorTests/Highlighting/StyledRangeStoreTests.swift @@ -7,6 +7,8 @@ extension StyledRangeStore { } final class StyledRangeStoreTests: XCTestCase { + typealias Store = StyledRangeStore + override var continueAfterFailure: Bool { get { false } set { } @@ -15,7 +17,7 @@ final class StyledRangeStoreTests: XCTestCase { func test_initWithLength() { for _ in 0..<100 { let length = Int.random(in: 0..<1000) - let store = StyledRangeStore(documentLength: length) + var store = Store(documentLength: length) XCTAssertEqual(store.length, length) } } @@ -23,26 +25,27 @@ final class StyledRangeStoreTests: XCTestCase { // MARK: - Storage func test_storageRemoveCharacters() { - let store = StyledRangeStore(documentLength: 100) + var store = Store(documentLength: 100) store.storageUpdated(replacedCharactersIn: 10..<12, withCount: 0) XCTAssertEqual(store.length, 98, "Failed to remove correct range") XCTAssertEqual(store.count, 1, "Failed to coalesce") } func test_storageRemoveFromEnd() { - let store = StyledRangeStore(documentLength: 100) + var store = Store(documentLength: 100) store.storageUpdated(replacedCharactersIn: 95..<100, withCount: 0) XCTAssertEqual(store.length, 95, "Failed to remove correct range") XCTAssertEqual(store.count, 1, "Failed to coalesce") } func test_storageRemoveSingleCharacterFromEnd() { - let store = StyledRangeStore(documentLength: 10) + var store = Store(documentLength: 10) store.set( // Test that we can delete a character associated with a single syntax run too runs: [ .empty(length: 8), - .init(length: 1, modifiers: [.abstract]), - .init(length: 1, modifiers: [.declaration])], + .init(length: 1, value: .init(modifiers: [.abstract])), + .init(length: 1, value: .init(modifiers: [.declaration])) + ], for: 0..<10 ) store.storageUpdated(replacedCharactersIn: 9..<10, withCount: 0) @@ -51,70 +54,70 @@ final class StyledRangeStoreTests: XCTestCase { } func test_storageRemoveFromBeginning() { - let store = StyledRangeStore(documentLength: 100) + var store = Store(documentLength: 100) store.storageUpdated(replacedCharactersIn: 0..<15, withCount: 0) XCTAssertEqual(store.length, 85, "Failed to remove correct range") XCTAssertEqual(store.count, 1, "Failed to coalesce") } func test_storageRemoveAll() { - let store = StyledRangeStore(documentLength: 100) + var store = Store(documentLength: 100) store.storageUpdated(replacedCharactersIn: 0..<100, withCount: 0) XCTAssertEqual(store.length, 0, "Failed to remove correct range") XCTAssertEqual(store.count, 0, "Failed to remove all runs") } func test_storageInsert() { - let store = StyledRangeStore(documentLength: 100) + var store = Store(documentLength: 100) store.storageUpdated(replacedCharactersIn: 45..<45, withCount: 10) XCTAssertEqual(store.length, 110) XCTAssertEqual(store.count, 1, "Failed to coalesce") } func test_storageInsertAtEnd() { - let store = StyledRangeStore(documentLength: 100) + var store = Store(documentLength: 100) store.storageUpdated(replacedCharactersIn: 100..<100, withCount: 10) XCTAssertEqual(store.length, 110) XCTAssertEqual(store.count, 1, "Failed to coalesce") } func test_storageInsertAtBeginning() { - let store = StyledRangeStore(documentLength: 100) + var store = Store(documentLength: 100) store.storageUpdated(replacedCharactersIn: 0..<0, withCount: 10) XCTAssertEqual(store.length, 110) XCTAssertEqual(store.count, 1, "Failed to coalesce") } func test_storageInsertFromEmpty() { - let store = StyledRangeStore(documentLength: 0) + var store = Store(documentLength: 0) store.storageUpdated(replacedCharactersIn: 0..<0, withCount: 10) XCTAssertEqual(store.length, 10) XCTAssertEqual(store.count, 1, "Failed to coalesce") } func test_storageEdit() { - let store = StyledRangeStore(documentLength: 100) + var store = Store(documentLength: 100) store.storageUpdated(replacedCharactersIn: 45..<50, withCount: 10) XCTAssertEqual(store.length, 105) XCTAssertEqual(store.count, 1, "Failed to coalesce") } func test_storageEditAtEnd() { - let store = StyledRangeStore(documentLength: 100) + var store = Store(documentLength: 100) store.storageUpdated(replacedCharactersIn: 95..<100, withCount: 10) XCTAssertEqual(store.length, 105) XCTAssertEqual(store.count, 1, "Failed to coalesce") } func test_storageEditAtBeginning() { - let store = StyledRangeStore(documentLength: 100) + var store = Store(documentLength: 100) store.storageUpdated(replacedCharactersIn: 0..<5, withCount: 10) XCTAssertEqual(store.length, 105) XCTAssertEqual(store.count, 1, "Failed to coalesce") } func test_storageEditAll() { - let store = StyledRangeStore(documentLength: 100) + var store = Store(documentLength: 100) store.storageUpdated(replacedCharactersIn: 0..<100, withCount: 10) XCTAssertEqual(store.length, 10) XCTAssertEqual(store.count, 1, "Failed to coalesce") @@ -123,8 +126,8 @@ final class StyledRangeStoreTests: XCTestCase { // MARK: - Styles func test_setOneRun() { - let store = StyledRangeStore(documentLength: 100) - store.set(capture: .comment, modifiers: [.static], for: 45..<50) + var store = Store(documentLength: 100) + store.set(value: .init(capture: .comment, modifiers: [.static]), for: 45..<50) XCTAssertEqual(store.length, 100) XCTAssertEqual(store.count, 3) @@ -134,18 +137,18 @@ final class StyledRangeStoreTests: XCTestCase { XCTAssertEqual(runs[1].length, 5) XCTAssertEqual(runs[2].length, 50) - XCTAssertNil(runs[0].capture) - XCTAssertEqual(runs[1].capture, .comment) - XCTAssertNil(runs[2].capture) + XCTAssertNil(runs[0].value?.capture) + XCTAssertEqual(runs[1].value?.capture, .comment) + XCTAssertNil(runs[2].value?.capture) - XCTAssertEqual(runs[0].modifiers, []) - XCTAssertEqual(runs[1].modifiers, [.static]) - XCTAssertEqual(runs[2].modifiers, []) + XCTAssertEqual(runs[0].value?.modifiers, nil) + XCTAssertEqual(runs[1].value?.modifiers, [.static]) + XCTAssertEqual(runs[2].value?.modifiers, nil) } func test_queryOverlappingRun() { - let store = StyledRangeStore(documentLength: 100) - store.set(capture: .comment, modifiers: [.static], for: 45..<50) + var store = Store(documentLength: 100) + store.set(value: .init(capture: .comment, modifiers: [.static]), for: 45..<50) XCTAssertEqual(store.length, 100) XCTAssertEqual(store.count, 3) @@ -154,21 +157,21 @@ final class StyledRangeStoreTests: XCTestCase { XCTAssertEqual(runs[0].length, 3) XCTAssertEqual(runs[1].length, 50) - XCTAssertEqual(runs[0].capture, .comment) - XCTAssertNil(runs[1].capture) + XCTAssertEqual(runs[0].value?.capture, .comment) + XCTAssertNil(runs[1].value?.capture) - XCTAssertEqual(runs[0].modifiers, [.static]) - XCTAssertEqual(runs[1].modifiers, []) + XCTAssertEqual(runs[0].value?.modifiers, [.static]) + XCTAssertEqual(runs[1].value?.modifiers, nil) } func test_setMultipleRuns() { - let store = StyledRangeStore(documentLength: 100) + var store = Store(documentLength: 100) - store.set(capture: .comment, modifiers: [.static], for: 5..<15) - store.set(capture: .keyword, modifiers: [], for: 20..<30) - store.set(capture: .string, modifiers: [.static], for: 35..<40) - store.set(capture: .function, modifiers: [], for: 45..<50) - store.set(capture: .variable, modifiers: [], for: 60..<70) + store.set(value: .init(capture: .comment, modifiers: [.static]), for: 5..<15) + store.set(value: .init(capture: .keyword, modifiers: []), for: 20..<30) + store.set(value: .init(capture: .string, modifiers: [.static]), for: 35..<40) + store.set(value: .init(capture: .function, modifiers: []), for: 45..<50) + store.set(value: .init(capture: .variable, modifiers: []), for: 60..<70) XCTAssertEqual(store.length, 100) @@ -182,13 +185,13 @@ final class StyledRangeStoreTests: XCTestCase { runs.enumerated().forEach { XCTAssertEqual($0.element.length, lengths[$0.offset]) - XCTAssertEqual($0.element.capture, captures[$0.offset]) - XCTAssertEqual($0.element.modifiers, modifiers[$0.offset]) + XCTAssertEqual($0.element.value?.capture, captures[$0.offset]) + XCTAssertEqual($0.element.value?.modifiers ?? [], modifiers[$0.offset]) } } func test_setMultipleRunsAndStorageUpdate() { - let store = StyledRangeStore(documentLength: 100) + var store = Store(documentLength: 100) var lengths = [5, 10, 5, 10, 5, 5, 5, 5, 10, 10, 30] var captures: [CaptureName?] = [nil, .comment, nil, .keyword, nil, .string, nil, .function, nil, .variable, nil] @@ -196,7 +199,7 @@ final class StyledRangeStoreTests: XCTestCase { store.set( runs: zip(zip(lengths, captures), modifiers).map { - StyledRangeStore.Run(length: $0.0, capture: $0.1, modifiers: $1) + Store.Run(length: $0.0, value: .init(capture: $0.1, modifiers: $1)) }, for: 0..<100 ) @@ -214,14 +217,14 @@ final class StyledRangeStoreTests: XCTestCase { "Run \($0.offset) has incorrect length: \($0.element.length). Expected \(lengths[$0.offset])" ) XCTAssertEqual( - $0.element.capture, + $0.element.value?.capture, captures[$0.offset], // swiftlint:disable:next line_length - "Run \($0.offset) has incorrect capture: \(String(describing: $0.element.capture)). Expected \(String(describing: captures[$0.offset]))" + "Run \($0.offset) has incorrect capture: \(String(describing: $0.element.value?.capture)). Expected \(String(describing: captures[$0.offset]))" ) XCTAssertEqual( - $0.element.modifiers, - modifiers[$0.offset], - "Run \($0.offset) has incorrect modifiers: \($0.element.modifiers). Expected \(modifiers[$0.offset])" + $0.element.value?.modifiers, + modifiers[$0.offset], // swiftlint:disable:next line_length + "Run \($0.offset) has incorrect modifiers: \(String(describing: $0.element.value?.modifiers)). Expected \(modifiers[$0.offset])" ) } @@ -236,8 +239,8 @@ final class StyledRangeStoreTests: XCTestCase { runs.enumerated().forEach { XCTAssertEqual($0.element.length, lengths[$0.offset]) - XCTAssertEqual($0.element.capture, captures[$0.offset]) - XCTAssertEqual($0.element.modifiers, modifiers[$0.offset]) + XCTAssertEqual($0.element.value?.capture, captures[$0.offset]) + XCTAssertEqual($0.element.value?.modifiers ?? [], modifiers[$0.offset]) } } } From c7d78235dfc90c462ad46636f1a7d3c990182e6a Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Wed, 28 May 2025 16:19:32 -0500 Subject: [PATCH 2/6] Rename to `RangeStore` --- .../Highlighting/Highlighter.swift | 4 ++-- .../StyledRangeContainer.swift | 6 +++--- .../RangeStore+Coalesce.swift} | 4 ++-- .../RangeStore+FindIndex.swift} | 4 ++-- .../RangeStore+OffsetMetric.swift} | 8 ++++---- .../RangeStore+StyledRun.swift} | 16 ++++++++-------- .../RangeStore.swift} | 8 ++++---- .../RangeStoreRun.swift} | 0 .../Highlighting/StyledRangeStoreTests.swift | 4 ++-- 9 files changed, 27 insertions(+), 27 deletions(-) rename Sources/CodeEditSourceEditor/{StyledRangeStore/StyledRangeStore+Coalesce.swift => RangeStore/RangeStore+Coalesce.swift} (95%) rename Sources/CodeEditSourceEditor/{StyledRangeStore/StyledRangeStore+FindIndex.swift => RangeStore/RangeStore+FindIndex.swift} (85%) rename Sources/CodeEditSourceEditor/{StyledRangeStore/StyledRangeStore+OffsetMetric.swift => RangeStore/RangeStore+OffsetMetric.swift} (52%) rename Sources/CodeEditSourceEditor/{StyledRangeStore/StyledRangeStore+StyledRun.swift => RangeStore/RangeStore+StyledRun.swift} (81%) rename Sources/CodeEditSourceEditor/{StyledRangeStore/StyledRangeStore.swift => RangeStore/RangeStore.swift} (94%) rename Sources/CodeEditSourceEditor/{StyledRangeStore/StyledRangeStoreRun.swift => RangeStore/RangeStoreRun.swift} (100%) diff --git a/Sources/CodeEditSourceEditor/Highlighting/Highlighter.swift b/Sources/CodeEditSourceEditor/Highlighting/Highlighter.swift index 4a9b0c9e4..31c1a2a1d 100644 --- a/Sources/CodeEditSourceEditor/Highlighting/Highlighter.swift +++ b/Sources/CodeEditSourceEditor/Highlighting/Highlighter.swift @@ -17,7 +17,7 @@ import OSLog /// /// This class manages multiple objects that help perform this task: /// - ``StyledRangeContainer`` -/// - ``StyledRangeStore`` +/// - ``RangeStore`` /// - ``VisibleRangeProvider`` /// - ``HighlightProviderState`` /// @@ -35,7 +35,7 @@ import OSLog /// | Queries coalesced styles /// v /// +-------------------------------+ +-----------------------------+ -/// | StyledRangeContainer | ------> | StyledRangeStore[] | +/// | StyledRangeContainer | ------> | RangeStore[] | /// | | | | Stores styles for one provider /// | - manages combined ranges | | - stores raw ranges & | /// | - layers highlight styles | | captures | diff --git a/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeContainer.swift b/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeContainer.swift index f159c25f7..c971376d2 100644 --- a/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeContainer.swift +++ b/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeContainer.swift @@ -45,7 +45,7 @@ class StyledRangeContainer { } } - var _storage: [ProviderID: StyledRangeStore] = [:] + var _storage: [ProviderID: RangeStore] = [:] weak var delegate: StyledRangeContainerDelegate? /// Initialize the container with a list of provider identifiers. Each provider is given an id, they should be @@ -55,13 +55,13 @@ class StyledRangeContainer { /// - providers: An array of identifiers given to providers. init(documentLength: Int, providers: [ProviderID]) { for provider in providers { - _storage[provider] = StyledRangeStore(documentLength: documentLength) + _storage[provider] = RangeStore(documentLength: documentLength) } } func addProvider(_ id: ProviderID, documentLength: Int) { assert(!_storage.keys.contains(id), "Provider already exists") - _storage[id] = StyledRangeStore(documentLength: documentLength) + _storage[id] = RangeStore(documentLength: documentLength) } func removeProvider(_ id: ProviderID) { diff --git a/Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+Coalesce.swift b/Sources/CodeEditSourceEditor/RangeStore/RangeStore+Coalesce.swift similarity index 95% rename from Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+Coalesce.swift rename to Sources/CodeEditSourceEditor/RangeStore/RangeStore+Coalesce.swift index 39f26612e..d6fb93259 100644 --- a/Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+Coalesce.swift +++ b/Sources/CodeEditSourceEditor/RangeStore/RangeStore+Coalesce.swift @@ -1,5 +1,5 @@ // -// StyledRangeStore+Internals.swift +// RangeStore+Internals.swift // CodeEditSourceEditor // // Created by Khan Winter on 10/25/24 @@ -7,7 +7,7 @@ import _RopeModule -extension StyledRangeStore { +extension RangeStore { /// Coalesce items before and after the given range. /// /// Compares the next run with the run at the given range. If they're the same, removes the next run and grows the diff --git a/Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+FindIndex.swift b/Sources/CodeEditSourceEditor/RangeStore/RangeStore+FindIndex.swift similarity index 85% rename from Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+FindIndex.swift rename to Sources/CodeEditSourceEditor/RangeStore/RangeStore+FindIndex.swift index a07076b58..363768d2e 100644 --- a/Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+FindIndex.swift +++ b/Sources/CodeEditSourceEditor/RangeStore/RangeStore+FindIndex.swift @@ -1,11 +1,11 @@ // -// StyledRangeStore+FindIndex.swift +// RangeStore+FindIndex.swift // CodeEditSourceEditor // // Created by Khan Winter on 1/6/25. // -extension StyledRangeStore { +extension RangeStore { /// Finds a Rope index, given a string offset. /// - Parameter offset: The offset to query for. /// - Returns: The index of the containing element in the rope. diff --git a/Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+OffsetMetric.swift b/Sources/CodeEditSourceEditor/RangeStore/RangeStore+OffsetMetric.swift similarity index 52% rename from Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+OffsetMetric.swift rename to Sources/CodeEditSourceEditor/RangeStore/RangeStore+OffsetMetric.swift index a05b68f68..2a74bf166 100644 --- a/Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+OffsetMetric.swift +++ b/Sources/CodeEditSourceEditor/RangeStore/RangeStore+OffsetMetric.swift @@ -1,5 +1,5 @@ // -// StyledRangeStore+OffsetMetric.swift +// RangeStore+OffsetMetric.swift // CodeEditSourceEditor // // Created by Khan Winter on 10/25/24 @@ -7,15 +7,15 @@ import _RopeModule -extension StyledRangeStore { +extension RangeStore { struct OffsetMetric: RopeMetric { typealias Element = StyledRun - func size(of summary: StyledRangeStore.StyledRun.Summary) -> Int { + func size(of summary: RangeStore.StyledRun.Summary) -> Int { summary.length } - func index(at offset: Int, in element: StyledRangeStore.StyledRun) -> Int { + func index(at offset: Int, in element: RangeStore.StyledRun) -> Int { return offset } } diff --git a/Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+StyledRun.swift b/Sources/CodeEditSourceEditor/RangeStore/RangeStore+StyledRun.swift similarity index 81% rename from Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+StyledRun.swift rename to Sources/CodeEditSourceEditor/RangeStore/RangeStore+StyledRun.swift index ea45402d9..823eb54d0 100644 --- a/Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+StyledRun.swift +++ b/Sources/CodeEditSourceEditor/RangeStore/RangeStore+StyledRun.swift @@ -1,12 +1,12 @@ // -// StyledRangeStore+StyledRun.swift +// RangeStore+StyledRun.swift // CodeEditSourceEditor // // Created by Khan Winter on 10/25/24 import _RopeModule -extension StyledRangeStore { +extension RangeStore { struct StyledRun { var length: Int let value: Element? @@ -32,7 +32,7 @@ extension StyledRangeStore { } } -extension StyledRangeStore.StyledRun: RopeElement { +extension RangeStore.StyledRun: RopeElement { typealias Index = Int var summary: Summary { Summary(length: length) } @@ -63,28 +63,28 @@ extension StyledRangeStore.StyledRun: RopeElement { } } -extension StyledRangeStore.StyledRun { +extension RangeStore.StyledRun { struct Summary { var length: Int } } -extension StyledRangeStore.StyledRun.Summary: RopeSummary { +extension RangeStore.StyledRun.Summary: RopeSummary { // FIXME: This is entirely arbitrary. Benchmark this. @inline(__always) static var maxNodeSize: Int { 10 } @inline(__always) - static var zero: StyledRangeStore.StyledRun.Summary { Self(length: 0) } + static var zero: RangeStore.StyledRun.Summary { Self(length: 0) } @inline(__always) var isZero: Bool { length == 0 } - mutating func add(_ other: StyledRangeStore.StyledRun.Summary) { + mutating func add(_ other: RangeStore.StyledRun.Summary) { length += other.length } - mutating func subtract(_ other: StyledRangeStore.StyledRun.Summary) { + mutating func subtract(_ other: RangeStore.StyledRun.Summary) { length -= other.length } } diff --git a/Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore.swift b/Sources/CodeEditSourceEditor/RangeStore/RangeStore.swift similarity index 94% rename from Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore.swift rename to Sources/CodeEditSourceEditor/RangeStore/RangeStore.swift index 407f4fbf0..2ebd7d488 100644 --- a/Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore.swift +++ b/Sources/CodeEditSourceEditor/RangeStore/RangeStore.swift @@ -1,5 +1,5 @@ // -// StyledRangeStore.swift +// RangeStore.swift // CodeEditSourceEditor // // Created by Khan Winter on 10/24/24 @@ -7,12 +7,12 @@ import _RopeModule -/// StyledRangeStore is a container type that allows for setting and querying captures and modifiers for syntax +/// RangeStore is a container type that allows for setting and querying captures and modifiers for syntax /// highlighting. The container reflects a text document in that its length needs to be kept up-to-date. /// /// Internally this class uses a `Rope` from the swift-collections package, allowing for efficient updates and /// retrievals. -struct StyledRangeStore: Sendable { +struct RangeStore: Sendable { typealias Run = StyledRangeStoreRun typealias RopeType = Rope typealias Index = RopeType.Index @@ -93,7 +93,7 @@ struct StyledRangeStore: Sendable { // MARK: - Storage Sync -extension StyledRangeStore { +extension RangeStore { /// Handles keeping the internal storage in sync with the document. mutating func storageUpdated(replacedCharactersIn range: Range, withCount newLength: Int) { assert(range.lowerBound >= 0, "Negative lowerBound") diff --git a/Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStoreRun.swift b/Sources/CodeEditSourceEditor/RangeStore/RangeStoreRun.swift similarity index 100% rename from Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStoreRun.swift rename to Sources/CodeEditSourceEditor/RangeStore/RangeStoreRun.swift diff --git a/Tests/CodeEditSourceEditorTests/Highlighting/StyledRangeStoreTests.swift b/Tests/CodeEditSourceEditorTests/Highlighting/StyledRangeStoreTests.swift index 7a50e185d..502be9c31 100644 --- a/Tests/CodeEditSourceEditorTests/Highlighting/StyledRangeStoreTests.swift +++ b/Tests/CodeEditSourceEditorTests/Highlighting/StyledRangeStoreTests.swift @@ -1,13 +1,13 @@ import XCTest @testable import CodeEditSourceEditor -extension StyledRangeStore { +extension RangeStore { var length: Int { _guts.summary.length } var count: Int { _guts.count } } final class StyledRangeStoreTests: XCTestCase { - typealias Store = StyledRangeStore + typealias Store = RangeStore override var continueAfterFailure: Bool { get { false } From 26d2b66da0faf41f442bfde4e5d523a583d5b49e Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Wed, 28 May 2025 16:25:07 -0500 Subject: [PATCH 3/6] Finish Rename --- .../StyledRangeContainer.swift | 10 +++++----- .../RangeStore/RangeStore+OffsetMetric.swift | 6 +++--- ...edRun.swift => RangeStore+StoredRun.swift} | 18 ++++++++--------- .../RangeStore/RangeStore.swift | 16 ++++++++------- .../RangeStore/RangeStoreElement.swift | 12 +++++++++++ .../RangeStore/RangeStoreRun.swift | 20 +++++++------------ .../StyledRangeContainerTests.swift | 2 +- 7 files changed, 46 insertions(+), 38 deletions(-) rename Sources/CodeEditSourceEditor/RangeStore/{RangeStore+StyledRun.swift => RangeStore+StoredRun.swift} (81%) create mode 100644 Sources/CodeEditSourceEditor/RangeStore/RangeStoreElement.swift diff --git a/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeContainer.swift b/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeContainer.swift index c971376d2..b1971b7d0 100644 --- a/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeContainer.swift +++ b/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeContainer.swift @@ -18,7 +18,7 @@ protocol StyledRangeContainerDelegate: AnyObject { /// See ``runsIn(range:)`` for more details on how conflicting highlights are handled. @MainActor class StyledRangeContainer { - struct StyleElement: StyledRangeStoreElement, CustomDebugStringConvertible { + struct StyleElement: RangeStoreElement, CustomDebugStringConvertible { var capture: CaptureName? var modifiers: CaptureModifierSet @@ -82,10 +82,10 @@ class StyledRangeContainer { /// /// - Parameter range: The range to query. /// - Returns: An array of continuous styled runs. - func runsIn(range: NSRange) -> [StyledRangeStoreRun] { + func runsIn(range: NSRange) -> [RangeStoreRun] { // Ordered by priority, lower = higher priority. var allRuns = _storage.sorted(by: { $0.key < $1.key }).map { $0.value.runs(in: range.intRange) } - var runs: [StyledRangeStoreRun] = [] + var runs: [RangeStoreRun] = [] var minValue = allRuns.compactMap { $0.last }.enumerated().min(by: { $0.1.length < $1.1.length }) @@ -140,7 +140,7 @@ extension StyledRangeContainer: HighlightProviderStateDelegate { assertionFailure("No storage found for the given provider: \(provider)") return } - var runs: [StyledRangeStoreRun] = [] + var runs: [RangeStoreRun] = [] var lastIndex = rangeToHighlight.lowerBound for highlight in highlights { @@ -150,7 +150,7 @@ extension StyledRangeContainer: HighlightProviderStateDelegate { continue // Skip! Overlapping } runs.append( - StyledRangeStoreRun( + RangeStoreRun( length: highlight.range.length, value: StyleElement(capture: highlight.capture, modifiers: highlight.modifiers) ) diff --git a/Sources/CodeEditSourceEditor/RangeStore/RangeStore+OffsetMetric.swift b/Sources/CodeEditSourceEditor/RangeStore/RangeStore+OffsetMetric.swift index 2a74bf166..efbfb4a12 100644 --- a/Sources/CodeEditSourceEditor/RangeStore/RangeStore+OffsetMetric.swift +++ b/Sources/CodeEditSourceEditor/RangeStore/RangeStore+OffsetMetric.swift @@ -9,13 +9,13 @@ import _RopeModule extension RangeStore { struct OffsetMetric: RopeMetric { - typealias Element = StyledRun + typealias Element = StoredRun - func size(of summary: RangeStore.StyledRun.Summary) -> Int { + func size(of summary: RangeStore.StoredRun.Summary) -> Int { summary.length } - func index(at offset: Int, in element: RangeStore.StyledRun) -> Int { + func index(at offset: Int, in element: RangeStore.StoredRun) -> Int { return offset } } diff --git a/Sources/CodeEditSourceEditor/RangeStore/RangeStore+StyledRun.swift b/Sources/CodeEditSourceEditor/RangeStore/RangeStore+StoredRun.swift similarity index 81% rename from Sources/CodeEditSourceEditor/RangeStore/RangeStore+StyledRun.swift rename to Sources/CodeEditSourceEditor/RangeStore/RangeStore+StoredRun.swift index 823eb54d0..b7a7afdf1 100644 --- a/Sources/CodeEditSourceEditor/RangeStore/RangeStore+StyledRun.swift +++ b/Sources/CodeEditSourceEditor/RangeStore/RangeStore+StoredRun.swift @@ -1,5 +1,5 @@ // -// RangeStore+StyledRun.swift +// RangeStore+StoredRun.swift // CodeEditSourceEditor // // Created by Khan Winter on 10/25/24 @@ -7,12 +7,12 @@ import _RopeModule extension RangeStore { - struct StyledRun { + struct StoredRun { var length: Int let value: Element? static func empty(length: Int) -> Self { - StyledRun(length: length, value: nil) + StoredRun(length: length, value: nil) } /// Compare two styled ranges by their stored styles. @@ -32,7 +32,7 @@ extension RangeStore { } } -extension RangeStore.StyledRun: RopeElement { +extension RangeStore.StoredRun: RopeElement { typealias Index = Int var summary: Summary { Summary(length: length) } @@ -63,28 +63,28 @@ extension RangeStore.StyledRun: RopeElement { } } -extension RangeStore.StyledRun { +extension RangeStore.StoredRun { struct Summary { var length: Int } } -extension RangeStore.StyledRun.Summary: RopeSummary { +extension RangeStore.StoredRun.Summary: RopeSummary { // FIXME: This is entirely arbitrary. Benchmark this. @inline(__always) static var maxNodeSize: Int { 10 } @inline(__always) - static var zero: RangeStore.StyledRun.Summary { Self(length: 0) } + static var zero: RangeStore.StoredRun.Summary { Self(length: 0) } @inline(__always) var isZero: Bool { length == 0 } - mutating func add(_ other: RangeStore.StyledRun.Summary) { + mutating func add(_ other: RangeStore.StoredRun.Summary) { length += other.length } - mutating func subtract(_ other: RangeStore.StyledRun.Summary) { + mutating func subtract(_ other: RangeStore.StoredRun.Summary) { length -= other.length } } diff --git a/Sources/CodeEditSourceEditor/RangeStore/RangeStore.swift b/Sources/CodeEditSourceEditor/RangeStore/RangeStore.swift index 2ebd7d488..5e22fc5f8 100644 --- a/Sources/CodeEditSourceEditor/RangeStore/RangeStore.swift +++ b/Sources/CodeEditSourceEditor/RangeStore/RangeStore.swift @@ -7,14 +7,16 @@ import _RopeModule -/// RangeStore is a container type that allows for setting and querying captures and modifiers for syntax -/// highlighting. The container reflects a text document in that its length needs to be kept up-to-date. +/// RangeStore is a container type that allows for setting and querying values for relative ranges in text. The +/// container reflects a text document in that its length needs to be kept up-to-date. It can efficiently remove and +/// replace subranges even for large documents. Provides helper methods for keeping some state in-sync with a text +/// document's content. /// /// Internally this class uses a `Rope` from the swift-collections package, allowing for efficient updates and /// retrievals. -struct RangeStore: Sendable { - typealias Run = StyledRangeStoreRun - typealias RopeType = Rope +struct RangeStore: Sendable { + typealias Run = RangeStoreRun + typealias RopeType = Rope typealias Index = RopeType.Index var _guts = RopeType() @@ -27,7 +29,7 @@ struct RangeStore: Sendable { private var cache: (range: Range, runs: [Run])? init(documentLength: Int) { - self._guts = RopeType([StyledRun(length: documentLength, value: nil)]) + self._guts = RopeType([StoredRun(length: documentLength, value: nil)]) } // MARK: - Core @@ -83,7 +85,7 @@ struct RangeStore: Sendable { _guts.replaceSubrange( range, in: OffsetMetric(), - with: runs.map { StyledRun(length: $0.length, value: $0.value) } + with: runs.map { StoredRun(length: $0.length, value: $0.value) } ) coalesceNearby(range: range) diff --git a/Sources/CodeEditSourceEditor/RangeStore/RangeStoreElement.swift b/Sources/CodeEditSourceEditor/RangeStore/RangeStoreElement.swift new file mode 100644 index 000000000..6b0a23b95 --- /dev/null +++ b/Sources/CodeEditSourceEditor/RangeStore/RangeStoreElement.swift @@ -0,0 +1,12 @@ +// +// RangeStoreElement.swift +// CodeEditSourceEditor +// +// Created by Khan Winter on 5/28/25. +// + +protocol RangeStoreElement: Equatable, Hashable { + var isEmpty: Bool { get } + func combineLowerPriority(_ other: Self?) -> Self + func combineHigherPriority(_ other: Self?) -> Self +} diff --git a/Sources/CodeEditSourceEditor/RangeStore/RangeStoreRun.swift b/Sources/CodeEditSourceEditor/RangeStore/RangeStoreRun.swift index 6e3b0deb9..48c68cc2c 100644 --- a/Sources/CodeEditSourceEditor/RangeStore/RangeStoreRun.swift +++ b/Sources/CodeEditSourceEditor/RangeStore/RangeStoreRun.swift @@ -1,43 +1,37 @@ // -// StyledRangeStoreRun.swift +// RangeStoreRun.swift // CodeEditSourceEditor // // Created by Khan Winter on 11/4/24. // -protocol StyledRangeStoreElement: Equatable, Hashable { - var isEmpty: Bool { get } - func combineLowerPriority(_ other: Self?) -> Self - func combineHigherPriority(_ other: Self?) -> Self -} - /// Consumer-facing value type for the stored values in this container. -struct StyledRangeStoreRun: Equatable, Hashable { +struct RangeStoreRun: Equatable, Hashable { var length: Int var value: Element? static func empty(length: Int) -> Self { - StyledRangeStoreRun(length: length, value: nil) + RangeStoreRun(length: length, value: nil) } var isEmpty: Bool { value?.isEmpty ?? true } - mutating func combineLowerPriority(_ other: StyledRangeStoreRun) { + mutating func combineLowerPriority(_ other: RangeStoreRun) { value = value?.combineLowerPriority(other.value) ?? other.value } - mutating func combineHigherPriority(_ other: StyledRangeStoreRun) { + mutating func combineHigherPriority(_ other: RangeStoreRun) { value = value?.combineHigherPriority(other.value) ?? other.value } - mutating func subtractLength(_ other: borrowing StyledRangeStoreRun) { + mutating func subtractLength(_ other: borrowing RangeStoreRun) { self.length -= other.length } } -extension StyledRangeStoreRun: CustomDebugStringConvertible { +extension RangeStoreRun: CustomDebugStringConvertible { var debugDescription: String { if let value = value as? CustomDebugStringConvertible { "\(length) (\(value.debugDescription))" diff --git a/Tests/CodeEditSourceEditorTests/Highlighting/StyledRangeContainerTests.swift b/Tests/CodeEditSourceEditorTests/Highlighting/StyledRangeContainerTests.swift index 6506f2eb0..c366ad46c 100644 --- a/Tests/CodeEditSourceEditorTests/Highlighting/StyledRangeContainerTests.swift +++ b/Tests/CodeEditSourceEditorTests/Highlighting/StyledRangeContainerTests.swift @@ -2,7 +2,7 @@ import XCTest @testable import CodeEditSourceEditor final class StyledRangeContainerTests: XCTestCase { - typealias Run = StyledRangeStoreRun + typealias Run = RangeStoreRun @MainActor func test_init() { From 7e7172cb80520d8e6d33d6f4d543cea80acfb2ff Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Wed, 28 May 2025 16:30:31 -0500 Subject: [PATCH 4/6] Fix Doc Comment Drawing --- .../Highlighting/Highlighter.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/CodeEditSourceEditor/Highlighting/Highlighter.swift b/Sources/CodeEditSourceEditor/Highlighting/Highlighter.swift index 31c1a2a1d..678e2761f 100644 --- a/Sources/CodeEditSourceEditor/Highlighting/Highlighter.swift +++ b/Sources/CodeEditSourceEditor/Highlighting/Highlighter.swift @@ -34,12 +34,12 @@ import OSLog /// | /// | Queries coalesced styles /// v -/// +-------------------------------+ +-----------------------------+ -/// | StyledRangeContainer | ------> | RangeStore[] | -/// | | | | Stores styles for one provider -/// | - manages combined ranges | | - stores raw ranges & | -/// | - layers highlight styles | | captures | -/// | + getAttributesForRange() | +-----------------------------+ +/// +-------------------------------+ +-------------------------+ +/// | StyledRangeContainer | ------> | RangeStore[] | +/// | | | | Stores styles for one provider +/// | - manages combined ranges | | - stores raw ranges & | +/// | - layers highlight styles | | captures | +/// | + getAttributesForRange() | +-------------------------+ /// +-------------------------------+ /// ^ /// | Sends highlighted runs From 168725835499e6bd6aceda339721452ef2927991 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Wed, 28 May 2025 16:31:19 -0500 Subject: [PATCH 5/6] Update Test Names --- .../StyledRangeStoreTests.swift => RangeStoreTests.swift} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename Tests/CodeEditSourceEditorTests/{Highlighting/StyledRangeStoreTests.swift => RangeStoreTests.swift} (99%) diff --git a/Tests/CodeEditSourceEditorTests/Highlighting/StyledRangeStoreTests.swift b/Tests/CodeEditSourceEditorTests/RangeStoreTests.swift similarity index 99% rename from Tests/CodeEditSourceEditorTests/Highlighting/StyledRangeStoreTests.swift rename to Tests/CodeEditSourceEditorTests/RangeStoreTests.swift index 502be9c31..6fa78447e 100644 --- a/Tests/CodeEditSourceEditorTests/Highlighting/StyledRangeStoreTests.swift +++ b/Tests/CodeEditSourceEditorTests/RangeStoreTests.swift @@ -6,7 +6,7 @@ extension RangeStore { var count: Int { _guts.count } } -final class StyledRangeStoreTests: XCTestCase { +final class RangeStoreTests: XCTestCase { typealias Store = RangeStore override var continueAfterFailure: Bool { From 0acd4585cecce74782bfa4d1d75cd59dacb4e63d Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Wed, 28 May 2025 16:35:30 -0500 Subject: [PATCH 6/6] Loosen `RangeStoreElement` Requirements --- .../StyledRangeContainer.swift | 14 +++++++++++--- .../RangeStore/RangeStoreElement.swift | 2 -- .../RangeStore/RangeStoreRun.swift | 8 -------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeContainer.swift b/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeContainer.swift index b1971b7d0..d61bfb009 100644 --- a/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeContainer.swift +++ b/Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeContainer.swift @@ -83,6 +83,14 @@ class StyledRangeContainer { /// - Parameter range: The range to query. /// - Returns: An array of continuous styled runs. func runsIn(range: NSRange) -> [RangeStoreRun] { + func combineLowerPriority(_ lhs: inout RangeStoreRun, _ rhs: RangeStoreRun) { + lhs.value = lhs.value?.combineLowerPriority(rhs.value) ?? rhs.value + } + + func combineHigherPriority(_ lhs: inout RangeStoreRun, _ rhs: RangeStoreRun) { + lhs.value = lhs.value?.combineHigherPriority(rhs.value) ?? rhs.value + } + // Ordered by priority, lower = higher priority. var allRuns = _storage.sorted(by: { $0.key < $1.key }).map { $0.value.runs(in: range.intRange) } var runs: [RangeStoreRun] = [] @@ -97,9 +105,9 @@ class StyledRangeContainer { for idx in (0.., withCount newLength: Int) { - for (key, value) in _storage { + for key in _storage.keys { _storage[key]?.storageUpdated(replacedCharactersIn: range, withCount: newLength) } } diff --git a/Sources/CodeEditSourceEditor/RangeStore/RangeStoreElement.swift b/Sources/CodeEditSourceEditor/RangeStore/RangeStoreElement.swift index 6b0a23b95..9db2d325e 100644 --- a/Sources/CodeEditSourceEditor/RangeStore/RangeStoreElement.swift +++ b/Sources/CodeEditSourceEditor/RangeStore/RangeStoreElement.swift @@ -7,6 +7,4 @@ protocol RangeStoreElement: Equatable, Hashable { var isEmpty: Bool { get } - func combineLowerPriority(_ other: Self?) -> Self - func combineHigherPriority(_ other: Self?) -> Self } diff --git a/Sources/CodeEditSourceEditor/RangeStore/RangeStoreRun.swift b/Sources/CodeEditSourceEditor/RangeStore/RangeStoreRun.swift index 48c68cc2c..116764eec 100644 --- a/Sources/CodeEditSourceEditor/RangeStore/RangeStoreRun.swift +++ b/Sources/CodeEditSourceEditor/RangeStore/RangeStoreRun.swift @@ -18,14 +18,6 @@ struct RangeStoreRun: Equatable, Hashable { value?.isEmpty ?? true } - mutating func combineLowerPriority(_ other: RangeStoreRun) { - value = value?.combineLowerPriority(other.value) ?? other.value - } - - mutating func combineHigherPriority(_ other: RangeStoreRun) { - value = value?.combineHigherPriority(other.value) ?? other.value - } - mutating func subtractLength(_ other: borrowing RangeStoreRun) { self.length -= other.length }