Skip to content

Commit d3c03cf

Browse files
committed
Make StyledRangeStore Generalized and Sendable
1 parent 30eb8a8 commit d3c03cf

File tree

11 files changed

+190
-152
lines changed

11 files changed

+190
-152
lines changed

Sources/CodeEditSourceEditor/Highlighting/Highlighter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ extension Highlighter: StyledRangeContainerDelegate {
276276
guard let range = NSRange(location: offset, length: run.length).intersection(range) else {
277277
continue
278278
}
279-
storage?.setAttributes(attributeProvider.attributesFor(run.capture), range: range)
279+
storage?.setAttributes(attributeProvider.attributesFor(run.value?.capture), range: range)
280280
offset += range.length
281281
}
282282

Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeContainer.swift

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,34 @@ protocol StyledRangeContainerDelegate: AnyObject {
1818
/// See ``runsIn(range:)`` for more details on how conflicting highlights are handled.
1919
@MainActor
2020
class StyledRangeContainer {
21-
var _storage: [ProviderID: StyledRangeStore] = [:]
21+
struct StyleElement: StyledRangeStoreElement, CustomDebugStringConvertible {
22+
var capture: CaptureName?
23+
var modifiers: CaptureModifierSet
24+
25+
var isEmpty: Bool {
26+
capture == nil && modifiers.isEmpty
27+
}
28+
29+
func combineLowerPriority(_ other: StyleElement?) -> StyleElement {
30+
StyleElement(
31+
capture: self.capture ?? other?.capture,
32+
modifiers: modifiers.union(other?.modifiers ?? [])
33+
)
34+
}
35+
36+
func combineHigherPriority(_ other: StyleElement?) -> StyleElement {
37+
StyleElement(
38+
capture: other?.capture ?? self.capture,
39+
modifiers: modifiers.union(other?.modifiers ?? [])
40+
)
41+
}
42+
43+
var debugDescription: String {
44+
"\(capture?.stringValue ?? "(empty)"), \(modifiers)"
45+
}
46+
}
47+
48+
var _storage: [ProviderID: StyledRangeStore<StyleElement>] = [:]
2249
weak var delegate: StyledRangeContainerDelegate?
2350

2451
/// Initialize the container with a list of provider identifiers. Each provider is given an id, they should be
@@ -28,13 +55,13 @@ class StyledRangeContainer {
2855
/// - providers: An array of identifiers given to providers.
2956
init(documentLength: Int, providers: [ProviderID]) {
3057
for provider in providers {
31-
_storage[provider] = StyledRangeStore(documentLength: documentLength)
58+
_storage[provider] = StyledRangeStore<StyleElement>(documentLength: documentLength)
3259
}
3360
}
3461

3562
func addProvider(_ id: ProviderID, documentLength: Int) {
3663
assert(!_storage.keys.contains(id), "Provider already exists")
37-
_storage[id] = StyledRangeStore(documentLength: documentLength)
64+
_storage[id] = StyledRangeStore<StyleElement>(documentLength: documentLength)
3865
}
3966

4067
func removeProvider(_ id: ProviderID) {
@@ -55,10 +82,10 @@ class StyledRangeContainer {
5582
///
5683
/// - Parameter range: The range to query.
5784
/// - Returns: An array of continuous styled runs.
58-
func runsIn(range: NSRange) -> [StyledRangeStoreRun] {
85+
func runsIn(range: NSRange) -> [StyledRangeStoreRun<StyleElement>] {
5986
// Ordered by priority, lower = higher priority.
6087
var allRuns = _storage.sorted(by: { $0.key < $1.key }).map { $0.value.runs(in: range.intRange) }
61-
var runs: [StyledRangeStoreRun] = []
88+
var runs: [StyledRangeStoreRun<StyleElement>] = []
6289

6390
var minValue = allRuns.compactMap { $0.last }.enumerated().min(by: { $0.1.length < $1.1.length })
6491

@@ -93,8 +120,8 @@ class StyledRangeContainer {
93120
}
94121

95122
func storageUpdated(replacedContentIn range: Range<Int>, withCount newLength: Int) {
96-
_storage.values.forEach {
97-
$0.storageUpdated(replacedCharactersIn: range, withCount: newLength)
123+
for (key, value) in _storage {
124+
_storage[key]?.storageUpdated(replacedCharactersIn: range, withCount: newLength)
98125
}
99126
}
100127
}
@@ -109,11 +136,11 @@ extension StyledRangeContainer: HighlightProviderStateDelegate {
109136
/// - rangeToHighlight: The range to apply the highlights to.
110137
func applyHighlightResult(provider: ProviderID, highlights: [HighlightRange], rangeToHighlight: NSRange) {
111138
assert(rangeToHighlight != .notFound, "NSNotFound is an invalid highlight range")
112-
guard let storage = _storage[provider] else {
139+
guard var storage = _storage[provider] else {
113140
assertionFailure("No storage found for the given provider: \(provider)")
114141
return
115142
}
116-
var runs: [StyledRangeStoreRun] = []
143+
var runs: [StyledRangeStoreRun<StyleElement>] = []
117144
var lastIndex = rangeToHighlight.lowerBound
118145

119146
for highlight in highlights {
@@ -123,10 +150,9 @@ extension StyledRangeContainer: HighlightProviderStateDelegate {
123150
continue // Skip! Overlapping
124151
}
125152
runs.append(
126-
StyledRangeStoreRun(
153+
StyledRangeStoreRun<StyleElement>(
127154
length: highlight.range.length,
128-
capture: highlight.capture,
129-
modifiers: highlight.modifiers
155+
value: StyleElement(capture: highlight.capture, modifiers: highlight.modifiers)
130156
)
131157
)
132158
lastIndex = highlight.range.max
@@ -137,6 +163,7 @@ extension StyledRangeContainer: HighlightProviderStateDelegate {
137163
}
138164

139165
storage.set(runs: runs, for: rangeToHighlight.intRange)
166+
_storage[provider] = storage
140167
delegate?.styleContainerDidUpdate(in: rangeToHighlight)
141168
}
142169
}

Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStoreRun.swift

Lines changed: 0 additions & 47 deletions
This file was deleted.

Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStore+Coalesce.swift renamed to Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+Coalesce.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ extension StyledRangeStore {
1616
/// rather than the queried one.
1717
///
1818
/// - Parameter range: The range of the item to coalesce around.
19-
func coalesceNearby(range: Range<Int>) {
19+
mutating func coalesceNearby(range: Range<Int>) {
2020
var index = findIndex(at: range.lastIndex).index
2121
if index < _guts.endIndex && _guts.index(after: index) != _guts.endIndex {
2222
coalesceRunAfter(index: &index)
@@ -30,11 +30,11 @@ extension StyledRangeStore {
3030
}
3131

3232
/// Check if the run and the run after it are equal, and if so remove the next one and concatenate the two.
33-
private func coalesceRunAfter(index: inout Index) {
33+
private mutating func coalesceRunAfter(index: inout Index) {
3434
let thisRun = _guts[index]
3535
let nextRun = _guts[_guts.index(after: index)]
3636

37-
if thisRun.styleCompare(nextRun) {
37+
if thisRun.compareValue(nextRun) {
3838
_guts.update(at: &index, by: { $0.length += nextRun.length })
3939

4040
var nextIndex = index

Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStore+FindIndex.swift renamed to Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+FindIndex.swift

File renamed without changes.

Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStore+OffsetMetric.swift renamed to Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+OffsetMetric.swift

File renamed without changes.

Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStore+StyledRun.swift renamed to Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore+StyledRun.swift

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,25 @@ import _RopeModule
99
extension StyledRangeStore {
1010
struct StyledRun {
1111
var length: Int
12-
let capture: CaptureName?
13-
let modifiers: CaptureModifierSet
12+
let value: Element?
1413

1514
static func empty(length: Int) -> Self {
16-
StyledRun(length: length, capture: nil, modifiers: [])
15+
StyledRun(length: length, value: nil)
1716
}
1817

1918
/// Compare two styled ranges by their stored styles.
2019
/// - Parameter other: The range to compare to.
2120
/// - Returns: The result of the comparison.
22-
func styleCompare(_ other: Self) -> Bool {
23-
capture == other.capture && modifiers == other.modifiers
21+
func compareValue(_ other: Self) -> Bool {
22+
return if let lhs = value, let rhs = other.value {
23+
lhs == rhs
24+
} else if let lhs = value {
25+
lhs.isEmpty
26+
} else if let rhs = other.value {
27+
rhs.isEmpty
28+
} else {
29+
true
30+
}
2431
}
2532
}
2633
}
@@ -50,7 +57,7 @@ extension StyledRangeStore.StyledRun: RopeElement {
5057

5158
mutating func split(at index: Self.Index) -> Self {
5259
assert(index >= 0 && index <= length)
53-
let tail = Self(length: length - index, capture: capture, modifiers: modifiers)
60+
let tail = Self(length: length - index, value: value)
5461
length = index
5562
return tail
5663
}

Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeStore/StyledRangeStore.swift renamed to Sources/CodeEditSourceEditor/StyledRangeStore/StyledRangeStore.swift

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ import _RopeModule
1212
///
1313
/// Internally this class uses a `Rope` from the swift-collections package, allowing for efficient updates and
1414
/// retrievals.
15-
final class StyledRangeStore {
16-
typealias Run = StyledRangeStoreRun
17-
typealias Index = Rope<StyledRun>.Index
18-
var _guts = Rope<StyledRun>()
15+
struct StyledRangeStore<Element: StyledRangeStoreElement>: Sendable {
16+
typealias Run = StyledRangeStoreRun<Element>
17+
typealias RopeType = Rope<StyledRun>
18+
typealias Index = RopeType.Index
19+
var _guts = RopeType()
1920

2021
var length: Int {
2122
_guts.count(in: OffsetMetric())
@@ -26,7 +27,7 @@ final class StyledRangeStore {
2627
private var cache: (range: Range<Int>, runs: [Run])?
2728

2829
init(documentLength: Int) {
29-
self._guts = Rope([StyledRun(length: documentLength, capture: nil, modifiers: [])])
30+
self._guts = RopeType([StyledRun(length: documentLength, value: nil)])
3031
}
3132

3233
// MARK: - Core
@@ -48,7 +49,7 @@ final class StyledRangeStore {
4849

4950
while index < _guts.endIndex {
5051
let run = _guts[index]
51-
runs.append(Run(length: run.length - (offset ?? 0), capture: run.capture, modifiers: run.modifiers))
52+
runs.append(Run(length: run.length - (offset ?? 0), value: run.value))
5253

5354
index = _guts.index(after: index)
5455
offset = nil
@@ -57,22 +58,21 @@ final class StyledRangeStore {
5758
return runs
5859
}
5960

60-
/// Sets a capture and modifiers for a range.
61+
/// Sets a value for a range.
6162
/// - Parameters:
62-
/// - capture: The capture to set.
63-
/// - modifiers: The modifiers to set.
63+
/// - value: The value to set for the given range.
6464
/// - range: The range to write to.
65-
func set(capture: CaptureName, modifiers: CaptureModifierSet, for range: Range<Int>) {
65+
mutating func set(value: Element, for range: Range<Int>) {
6666
assert(range.lowerBound >= 0, "Negative lowerBound")
6767
assert(range.upperBound <= _guts.count(in: OffsetMetric()), "upperBound outside valid range")
68-
set(runs: [Run(length: range.length, capture: capture, modifiers: modifiers)], for: range)
68+
set(runs: [Run(length: range.length, value: value)], for: range)
6969
}
7070

7171
/// Replaces a range in the document with an array of runs.
7272
/// - Parameters:
7373
/// - runs: The runs to insert.
7474
/// - range: The range to replace.
75-
func set(runs: [Run], for range: Range<Int>) {
75+
mutating func set(runs: [Run], for range: Range<Int>) {
7676
let gutsRange = 0..<_guts.count(in: OffsetMetric())
7777
if range.clamped(to: gutsRange) != range {
7878
let upperBound = range.clamped(to: gutsRange).upperBound
@@ -83,7 +83,7 @@ final class StyledRangeStore {
8383
_guts.replaceSubrange(
8484
range,
8585
in: OffsetMetric(),
86-
with: runs.map { StyledRun(length: $0.length, capture: $0.capture, modifiers: $0.modifiers) }
86+
with: runs.map { StyledRun(length: $0.length, value: $0.value) }
8787
)
8888

8989
coalesceNearby(range: range)
@@ -95,7 +95,7 @@ final class StyledRangeStore {
9595

9696
extension StyledRangeStore {
9797
/// Handles keeping the internal storage in sync with the document.
98-
func storageUpdated(replacedCharactersIn range: Range<Int>, withCount newLength: Int) {
98+
mutating func storageUpdated(replacedCharactersIn range: Range<Int>, withCount newLength: Int) {
9999
assert(range.lowerBound >= 0, "Negative lowerBound")
100100
assert(range.upperBound <= _guts.count(in: OffsetMetric()), "upperBound outside valid range")
101101

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//
2+
// StyledRangeStoreRun.swift
3+
// CodeEditSourceEditor
4+
//
5+
// Created by Khan Winter on 11/4/24.
6+
//
7+
8+
protocol StyledRangeStoreElement: Equatable, Hashable {
9+
var isEmpty: Bool { get }
10+
func combineLowerPriority(_ other: Self?) -> Self
11+
func combineHigherPriority(_ other: Self?) -> Self
12+
}
13+
14+
/// Consumer-facing value type for the stored values in this container.
15+
struct StyledRangeStoreRun<Element: StyledRangeStoreElement>: Equatable, Hashable {
16+
var length: Int
17+
var value: Element?
18+
19+
static func empty(length: Int) -> Self {
20+
StyledRangeStoreRun(length: length, value: nil)
21+
}
22+
23+
var isEmpty: Bool {
24+
value?.isEmpty ?? true
25+
}
26+
27+
mutating func combineLowerPriority(_ other: StyledRangeStoreRun) {
28+
value = value?.combineLowerPriority(other.value) ?? other.value
29+
}
30+
31+
mutating func combineHigherPriority(_ other: StyledRangeStoreRun) {
32+
value = value?.combineHigherPriority(other.value) ?? other.value
33+
}
34+
35+
mutating func subtractLength(_ other: borrowing StyledRangeStoreRun) {
36+
self.length -= other.length
37+
}
38+
}
39+
40+
extension StyledRangeStoreRun: CustomDebugStringConvertible {
41+
var debugDescription: String {
42+
if let value = value as? CustomDebugStringConvertible {
43+
"\(length) (\(value.debugDescription))"
44+
} else {
45+
"\(length) (empty)"
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)