Skip to content

Commit f09822a

Browse files
committed
First Attempt At Overlapping Ranges
1 parent 0713d2c commit f09822a

File tree

6 files changed

+77
-27
lines changed

6 files changed

+77
-27
lines changed

Sources/CodeEditSourceEditor/Highlighting/HighlighProviding/HighlightProviderState.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import CodeEditTextView
1111
import OSLog
1212

1313
protocol HighlightProviderStateDelegate: AnyObject {
14-
func applyHighlightResult(provider: UUID, highlights: [HighlightRange], rangeToHighlight: NSRange)
14+
typealias ProviderID = Int
15+
func applyHighlightResult(provider: ProviderID, highlights: [HighlightRange], rangeToHighlight: NSRange)
1516
}
1617

1718
@MainActor
@@ -24,7 +25,7 @@ class HighlightProviderState {
2425
// MARK: - State
2526

2627
/// A unique identifier for this provider. Used by the delegate to determine the source of results.
27-
let id: UUID
28+
let id: Int
2829

2930
/// Any indexes that highlights have been requested for, but haven't been applied.
3031
/// Indexes/ranges are added to this when highlights are requested and removed
@@ -65,7 +66,7 @@ class HighlightProviderState {
6566
/// - visibleRangeProvider: A visible range provider for determining which ranges to query.
6667
/// - language: The language to set up the provider with.
6768
init(
68-
id: UUID = UUID(),
69+
id: Int,
6970
delegate: HighlightProviderStateDelegate,
7071
highlightProvider: HighlightProviding,
7172
textView: TextView,

Sources/CodeEditSourceEditor/Highlighting/Highlighter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class Highlighter: NSObject {
8888
self.attributeProvider = attributeProvider
8989
self.visibleRangeProvider = VisibleRangeProvider(textView: textView)
9090

91-
let providerIds = providers.indices.map({ _ in UUID() })
91+
let providerIds = providers.indices.map({ $0 })
9292
self.rangeContainer = StyledRangeContainer(documentLength: textView.length, providers: providerIds)
9393

9494
super.init()

Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeContainer.swift

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,44 @@
88
import Foundation
99

1010
class StyledRangeContainer {
11-
typealias Run = StyledRangeStore.Run
12-
var _storage: [UUID: StyledRangeStore] = [:]
11+
var _storage: [ProviderID: StyledRangeStore] = [:]
1312

14-
init(documentLength: Int, providers: [UUID]) {
13+
init(documentLength: Int, providers: [ProviderID]) {
1514
for provider in providers {
1615
_storage[provider] = StyledRangeStore(documentLength: documentLength)
1716
}
1817
}
1918

20-
func runsIn(range: NSRange) -> [Run] {
21-
19+
func runsIn(range: NSRange) -> [HighlightedRun] {
20+
// Ordered by priority, lower = higher priority.
21+
var allRuns = _storage.sorted(by: { $0.key < $1.key }).map { $0.value.runs(in: range.intRange) }
22+
var runs: [HighlightedRun] = []
23+
24+
var minValue = allRuns.compactMap { $0.last }.enumerated().min(by: { $0.1.length < $1.1.length })
25+
26+
while let value = minValue {
27+
// Get minimum length off the end of each array
28+
let minRunIdx = value.offset
29+
var minRun = value.element
30+
31+
for idx in 0..<allRuns.count where idx != minRunIdx {
32+
guard let last = allRuns[idx].last else { continue }
33+
minRun.combineLowerPriority(last)
34+
if last.length == minRun.length {
35+
allRuns[idx].removeLast()
36+
} else {
37+
// safe due to guard a few lines above.
38+
allRuns[idx][allRuns[idx].count - 1].subtractLength(minRun)
39+
}
40+
}
41+
42+
allRuns[minRunIdx].removeLast()
43+
44+
runs.append(minRun)
45+
minValue = allRuns.compactMap { $0.last }.enumerated().min(by: { $0.1.length < $1.1.length })
46+
}
47+
48+
return runs.reversed()
2249
}
2350

2451
func storageUpdated(replacedContentIn range: Range<Int>, withCount newLength: Int) {
@@ -29,21 +56,21 @@ class StyledRangeContainer {
2956
}
3057

3158
extension StyledRangeContainer: HighlightProviderStateDelegate {
32-
func applyHighlightResult(provider: UUID, highlights: [HighlightRange], rangeToHighlight: NSRange) {
59+
func applyHighlightResult(provider: ProviderID, highlights: [HighlightRange], rangeToHighlight: NSRange) {
3360
assert(rangeToHighlight != .notFound, "NSNotFound is an invalid highlight range")
3461
guard let storage = _storage[provider] else {
3562
assertionFailure("No storage found for the given provider: \(provider)")
3663
return
3764
}
38-
var runs: [Run] = []
65+
var runs: [HighlightedRun] = []
3966
var lastIndex = rangeToHighlight.lowerBound
4067

4168
for highlight in highlights {
4269
if highlight.range.lowerBound != lastIndex {
4370
runs.append(.empty(length: highlight.range.lowerBound - lastIndex))
4471
}
4572
// TODO: Modifiers
46-
runs.append(Run(length: highlight.range.length, capture: highlight.capture, modifiers: []))
73+
runs.append(HighlightedRun(length: highlight.range.length, capture: highlight.capture, modifiers: []))
4774
lastIndex = highlight.range.max
4875
}
4976

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// HighlightedRun.swift
3+
// CodeEditSourceEditor
4+
//
5+
// Created by Khan Winter on 11/4/24.
6+
//
7+
8+
/// Consumer-facing value type for the stored values in this container.
9+
struct HighlightedRun: Equatable, Hashable {
10+
var length: Int
11+
var capture: CaptureName?
12+
var modifiers: Set<CaptureModifiers>
13+
14+
static func empty(length: Int) -> Self {
15+
HighlightedRun(length: length, capture: nil, modifiers: [])
16+
}
17+
18+
mutating func combineLowerPriority(_ other: borrowing HighlightedRun) {
19+
if self.capture == nil {
20+
self.capture = other.capture
21+
}
22+
self.modifiers.formUnion(other.modifiers)
23+
}
24+
25+
mutating func subtractLength(_ other: borrowing HighlightedRun) {
26+
self.length -= other.length
27+
}
28+
}

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

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import _RopeModule
1313
/// Internally this class uses a `Rope` from the swift-collections package, allowing for efficient updates and
1414
/// retrievals.
1515
final class StyledRangeStore {
16+
typealias Run = HighlightedRun
1617
typealias Index = Rope<StyledRun>.Index
1718
var _guts = Rope<StyledRun>()
1819

@@ -24,17 +25,6 @@ final class StyledRangeStore {
2425
self._guts = Rope([StyledRun(length: documentLength, capture: nil, modifiers: [])])
2526
}
2627

27-
/// Consumer-facing value type for the stored values in this container.
28-
struct Run: Equatable, Hashable, Sendable {
29-
let length: Int
30-
let capture: CaptureName?
31-
let modifiers: Set<CaptureModifiers>
32-
33-
static func empty(length: Int) -> Self {
34-
Run(length: length, capture: nil, modifiers: [])
35-
}
36-
}
37-
3828
// MARK: - Core
3929

4030
func runs(in range: Range<Int>) -> [Run] {

Tests/CodeEditSourceEditorTests/Highlighting/StyledRangeContainerTests.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@ import XCTest
22
@testable import CodeEditSourceEditor
33

44
final class StyledRangeContainerTests: XCTestCase {
5-
typealias Run = StyledRangeContainer.Run
5+
typealias Run = HighlightedRun
66

77
func test_init() {
8-
let providers = [UUID(), UUID()]
8+
let providers = [0, 1]
99
let store = StyledRangeContainer(documentLength: 100, providers: providers)
1010

1111
// Have to do string conversion due to missing Comparable conformance pre-macOS 14
12-
XCTAssertEqual(store._storage.keys.map(\.uuidString).sorted(), providers.map(\.uuidString).sorted())
12+
XCTAssertEqual(store._storage.keys.sorted(), providers)
1313
XCTAssert(store._storage.values.allSatisfy({ $0.length == 100 }), "One or more providers have incorrect length")
1414
}
1515

1616
func test_setHighlights() {
17-
let providers = [UUID(), UUID()]
17+
let providers = [0, 1]
1818
let store = StyledRangeContainer(documentLength: 100, providers: providers)
1919

2020
store.applyHighlightResult(
@@ -38,4 +38,8 @@ final class StyledRangeContainerTests: XCTestCase {
3838
]
3939
)
4040
}
41+
42+
func test_overlappingRuns() {
43+
44+
}
4145
}

0 commit comments

Comments
 (0)