Skip to content

Commit 5c1a696

Browse files
committed
Finish StyledRangeContainer
1 parent f09822a commit 5c1a696

File tree

8 files changed

+185
-21
lines changed

8 files changed

+185
-21
lines changed

Sources/CodeEditSourceEditor/Enums/CaptureModifiers.swift

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,59 @@
55
// Created by Khan Winter on 10/24/24.
66
//
77

8-
enum CaptureModifiers: String, CaseIterable, Sendable {
9-
case builtin
8+
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#semanticTokenModifiers
9+
10+
enum CaptureModifiers: Int, CaseIterable, Sendable {
11+
case declaration
12+
case definition
13+
case readonly
14+
case `static`
15+
case deprecated
16+
case abstract
17+
case async
18+
case modification
19+
case documentation
20+
case defaultLibrary
21+
}
22+
23+
extension CaptureModifiers: CustomDebugStringConvertible {
24+
var debugDescription: String {
25+
switch self {
26+
case .declaration: return "declaration"
27+
case .definition: return "definition"
28+
case .readonly: return "readonly"
29+
case .static: return "static"
30+
case .deprecated: return "deprecated"
31+
case .abstract: return "abstract"
32+
case .async: return "async"
33+
case .modification: return "modification"
34+
case .documentation: return "documentation"
35+
case .defaultLibrary: return "defaultLibrary"
36+
}
37+
}
38+
}
39+
40+
struct CaptureModifierSet: OptionSet, Equatable, Hashable {
41+
let rawValue: UInt
42+
43+
static let declaration = CaptureModifierSet(rawValue: 1 << CaptureModifiers.declaration.rawValue)
44+
static let definition = CaptureModifierSet(rawValue: 1 << CaptureModifiers.definition.rawValue)
45+
static let readonly = CaptureModifierSet(rawValue: 1 << CaptureModifiers.readonly.rawValue)
46+
static let `static` = CaptureModifierSet(rawValue: 1 << CaptureModifiers.static.rawValue)
47+
static let deprecated = CaptureModifierSet(rawValue: 1 << CaptureModifiers.deprecated.rawValue)
48+
static let abstract = CaptureModifierSet(rawValue: 1 << CaptureModifiers.abstract.rawValue)
49+
static let async = CaptureModifierSet(rawValue: 1 << CaptureModifiers.async.rawValue)
50+
static let modification = CaptureModifierSet(rawValue: 1 << CaptureModifiers.modification.rawValue)
51+
static let documentation = CaptureModifierSet(rawValue: 1 << CaptureModifiers.documentation.rawValue)
52+
static let defaultLibrary = CaptureModifierSet(rawValue: 1 << CaptureModifiers.defaultLibrary.rawValue)
53+
54+
var values: [CaptureModifiers] {
55+
var rawValue = self.rawValue
56+
var values: [Int] = []
57+
while rawValue > 0 {
58+
values.append(rawValue.trailingZeroBitCount)
59+
rawValue &= ~UInt(1 << rawValue.trailingZeroBitCount)
60+
}
61+
return values.compactMap({ CaptureModifiers(rawValue: $0) })
62+
}
1063
}

Sources/CodeEditSourceEditor/Highlighting/HighlightRange.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,12 @@ import Foundation
1010
/// This struct represents a range to highlight, as well as the capture name for syntax coloring.
1111
public struct HighlightRange: Sendable {
1212
let range: NSRange
13-
let capture: CaptureName
13+
let capture: CaptureName?
14+
let modifiers: CaptureModifierSet
15+
16+
init(range: NSRange, capture: CaptureName?, modifiers: CaptureModifierSet = []) {
17+
self.range = range
18+
self.capture = capture
19+
self.modifiers = modifiers
20+
}
1421
}

Sources/CodeEditSourceEditor/Highlighting/StyledRangeContainer/StyledRangeContainer.swift

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,14 @@ class StyledRangeContainer {
2828
let minRunIdx = value.offset
2929
var minRun = value.element
3030

31-
for idx in 0..<allRuns.count where idx != minRunIdx {
31+
for idx in (0..<allRuns.count).reversed() where idx != minRunIdx {
3232
guard let last = allRuns[idx].last else { continue }
33-
minRun.combineLowerPriority(last)
33+
if idx < minRunIdx {
34+
minRun.combineHigherPriority(last)
35+
} else {
36+
minRun.combineLowerPriority(last)
37+
}
38+
3439
if last.length == minRun.length {
3540
allRuns[idx].removeLast()
3641
} else {
@@ -69,8 +74,13 @@ extension StyledRangeContainer: HighlightProviderStateDelegate {
6974
if highlight.range.lowerBound != lastIndex {
7075
runs.append(.empty(length: highlight.range.lowerBound - lastIndex))
7176
}
72-
// TODO: Modifiers
73-
runs.append(HighlightedRun(length: highlight.range.length, capture: highlight.capture, modifiers: []))
77+
runs.append(
78+
HighlightedRun(
79+
length: highlight.range.length,
80+
capture: highlight.capture,
81+
modifiers: highlight.modifiers
82+
)
83+
)
7484
lastIndex = highlight.range.max
7585
}
7686

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,39 @@
99
struct HighlightedRun: Equatable, Hashable {
1010
var length: Int
1111
var capture: CaptureName?
12-
var modifiers: Set<CaptureModifiers>
12+
var modifiers: CaptureModifierSet
1313

1414
static func empty(length: Int) -> Self {
1515
HighlightedRun(length: length, capture: nil, modifiers: [])
1616
}
1717

18+
var isEmpty: Bool {
19+
capture == nil && modifiers.isEmpty
20+
}
21+
1822
mutating func combineLowerPriority(_ other: borrowing HighlightedRun) {
1923
if self.capture == nil {
2024
self.capture = other.capture
2125
}
2226
self.modifiers.formUnion(other.modifiers)
2327
}
2428

29+
mutating func combineHigherPriority(_ other: borrowing HighlightedRun) {
30+
self.capture = other.capture ?? self.capture
31+
self.modifiers.formUnion(other.modifiers)
32+
}
33+
2534
mutating func subtractLength(_ other: borrowing HighlightedRun) {
2635
self.length -= other.length
2736
}
2837
}
38+
39+
extension HighlightedRun: CustomDebugStringConvertible {
40+
var debugDescription: String {
41+
if isEmpty {
42+
"\(length) (empty)"
43+
} else {
44+
"\(length) (\(capture?.rawValue ?? "none"), \(modifiers.values.debugDescription))"
45+
}
46+
}
47+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ extension StyledRangeStore {
1010
struct StyledRun {
1111
var length: Int
1212
let capture: CaptureName?
13-
let modifiers: Set<CaptureModifiers>
13+
let modifiers: CaptureModifierSet
1414

15-
init(length: Int, capture: CaptureName?, modifiers: Set<CaptureModifiers>) {
15+
init(length: Int, capture: CaptureName?, modifiers: CaptureModifierSet) {
1616
self.length = length
1717
self.capture = capture
1818
self.modifiers = modifiers

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ final class StyledRangeStore {
5050
return runs
5151
}
5252

53-
func set(capture: CaptureName, modifiers: Set<CaptureModifiers>, for range: Range<Int>) {
53+
func set(capture: CaptureName, modifiers: CaptureModifierSet, for range: Range<Int>) {
5454
assert(range.lowerBound >= 0, "Negative lowerBound")
5555
assert(range.upperBound <= _guts.count(in: OffsetMetric()), "upperBound outside valid range")
5656
set(runs: [Run(length: range.length, capture: capture, modifiers: modifiers)], for: range)

Tests/CodeEditSourceEditorTests/Highlighting/StyledRangeContainerTests.swift

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,81 @@ final class StyledRangeContainerTests: XCTestCase {
4040
}
4141

4242
func test_overlappingRuns() {
43-
43+
let providers = [0, 1]
44+
let store = StyledRangeContainer(documentLength: 100, providers: providers)
45+
46+
store.applyHighlightResult(
47+
provider: providers[0],
48+
highlights: [HighlightRange(range: NSRange(location: 40, length: 10), capture: .comment)],
49+
rangeToHighlight: NSRange(location: 0, length: 100)
50+
)
51+
52+
store.applyHighlightResult(
53+
provider: providers[1],
54+
highlights: [
55+
HighlightRange(range: NSRange(location: 45, length: 5), capture: nil, modifiers: [.declaration])
56+
],
57+
rangeToHighlight: NSRange(location: 0, length: 100)
58+
)
59+
60+
XCTAssertEqual(
61+
store.runsIn(range: NSRange(location: 0, length: 100)),
62+
[
63+
Run(length: 40, capture: nil, modifiers: []),
64+
Run(length: 5, capture: .comment, modifiers: []),
65+
Run(length: 5, capture: .comment, modifiers: [.declaration]),
66+
Run(length: 50, capture: nil, modifiers: [])
67+
]
68+
)
69+
}
70+
71+
func test_overlappingRunsWithMoreProviders() {
72+
let providers = [0, 1, 2]
73+
let store = StyledRangeContainer(documentLength: 200, providers: providers)
74+
75+
store.applyHighlightResult(
76+
provider: providers[0],
77+
highlights: [
78+
HighlightRange(range: NSRange(location: 30, length: 20), capture: .comment),
79+
HighlightRange(range: NSRange(location: 80, length: 30), capture: .string)
80+
],
81+
rangeToHighlight: NSRange(location: 0, length: 200)
82+
)
83+
84+
store.applyHighlightResult(
85+
provider: providers[1],
86+
highlights: [
87+
HighlightRange(range: NSRange(location: 35, length: 10), capture: nil, modifiers: [.declaration]),
88+
HighlightRange(range: NSRange(location: 90, length: 15), capture: .comment, modifiers: [.static])
89+
],
90+
rangeToHighlight: NSRange(location: 0, length: 200)
91+
)
92+
93+
store.applyHighlightResult(
94+
provider: providers[2],
95+
highlights: [
96+
HighlightRange(range: NSRange(location: 40, length: 5), capture: .function, modifiers: [.abstract]),
97+
HighlightRange(range: NSRange(location: 100, length: 10), capture: .number, modifiers: [.modification])
98+
],
99+
rangeToHighlight: NSRange(location: 0, length: 200)
100+
)
101+
102+
let runs = store.runsIn(range: NSRange(location: 0, length: 200))
103+
104+
XCTAssertEqual(runs.reduce(0, { $0 + $1.length}), 200)
105+
106+
XCTAssertEqual(runs[0], Run(length: 30, capture: nil, modifiers: []))
107+
XCTAssertEqual(runs[1], Run(length: 5, capture: .comment, modifiers: []))
108+
XCTAssertEqual(runs[2], Run(length: 5, capture: .comment, modifiers: [.declaration]))
109+
XCTAssertEqual(runs[3], Run(length: 5, capture: .comment, modifiers: [.abstract, .declaration]))
110+
XCTAssertEqual(runs[4], Run(length: 5, capture: .comment, modifiers: []))
111+
XCTAssertEqual(runs[5], Run(length: 30, capture: nil, modifiers: []))
112+
XCTAssertEqual(runs[6], Run(length: 10, capture: .string, modifiers: []))
113+
XCTAssertEqual(runs[7], Run(length: 10, capture: .string, modifiers: [.static]))
114+
XCTAssertEqual(runs[8], Run(length: 5, capture: .string, modifiers: [.static, .modification]))
115+
XCTAssertEqual(runs[9], Run(length: 5, capture: .string, modifiers: [.modification]))
116+
XCTAssertEqual(runs[10], Run(length: 90, capture: nil, modifiers: []))
117+
44118
}
119+
45120
}

Tests/CodeEditSourceEditorTests/Highlighting/StyledRangeStoreTests.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ final class StyledRangeStoreTests: XCTestCase {
110110

111111
func test_setOneRun() {
112112
let store = StyledRangeStore(documentLength: 100)
113-
store.set(capture: .comment, modifiers: [.builtin], for: 45..<50)
113+
store.set(capture: .comment, modifiers: [.static], for: 45..<50)
114114
XCTAssertEqual(store.length, 100)
115115
XCTAssertEqual(store.count, 3)
116116

@@ -125,13 +125,13 @@ final class StyledRangeStoreTests: XCTestCase {
125125
XCTAssertEqual(runs[2].capture, nil)
126126

127127
XCTAssertEqual(runs[0].modifiers, [])
128-
XCTAssertEqual(runs[1].modifiers, [.builtin])
128+
XCTAssertEqual(runs[1].modifiers, [.static])
129129
XCTAssertEqual(runs[2].modifiers, [])
130130
}
131131

132132
func test_queryOverlappingRun() {
133133
let store = StyledRangeStore(documentLength: 100)
134-
store.set(capture: .comment, modifiers: [.builtin], for: 45..<50)
134+
store.set(capture: .comment, modifiers: [.static], for: 45..<50)
135135
XCTAssertEqual(store.length, 100)
136136
XCTAssertEqual(store.count, 3)
137137

@@ -143,16 +143,16 @@ final class StyledRangeStoreTests: XCTestCase {
143143
XCTAssertEqual(runs[0].capture, .comment)
144144
XCTAssertEqual(runs[1].capture, nil)
145145

146-
XCTAssertEqual(runs[0].modifiers, [.builtin])
146+
XCTAssertEqual(runs[0].modifiers, [.static])
147147
XCTAssertEqual(runs[1].modifiers, [])
148148
}
149149

150150
func test_setMultipleRuns() {
151151
let store = StyledRangeStore(documentLength: 100)
152152

153-
store.set(capture: .comment, modifiers: [.builtin], for: 5..<15)
153+
store.set(capture: .comment, modifiers: [.static], for: 5..<15)
154154
store.set(capture: .keyword, modifiers: [], for: 20..<30)
155-
store.set(capture: .string, modifiers: [.builtin], for: 35..<40)
155+
store.set(capture: .string, modifiers: [.static], for: 35..<40)
156156
store.set(capture: .function, modifiers: [], for: 45..<50)
157157
store.set(capture: .variable, modifiers: [], for: 60..<70)
158158

@@ -164,7 +164,7 @@ final class StyledRangeStoreTests: XCTestCase {
164164

165165
let lengths = [5, 10, 5, 10, 5, 5, 5, 5, 10, 10, 30]
166166
let captures: [CaptureName?] = [nil, .comment, nil, .keyword, nil, .string, nil, .function, nil, .variable, nil]
167-
let modifiers: [Set<CaptureModifiers>] = [[], [.builtin], [], [], [], [.builtin], [], [], [], [], []]
167+
let modifiers: [CaptureModifierSet] = [[], [.static], [], [], [], [.static], [], [], [], [], []]
168168

169169
runs.enumerated().forEach {
170170
XCTAssertEqual($0.element.length, lengths[$0.offset])
@@ -178,7 +178,7 @@ final class StyledRangeStoreTests: XCTestCase {
178178

179179
var lengths = [5, 10, 5, 10, 5, 5, 5, 5, 10, 10, 30]
180180
var captures: [CaptureName?] = [nil, .comment, nil, .keyword, nil, .string, nil, .function, nil, .variable, nil]
181-
var modifiers: [Set<CaptureModifiers>] = [[], [.builtin], [], [], [], [.builtin], [], [], [], [], []]
181+
var modifiers: [CaptureModifierSet] = [[], [.static], [], [], [], [.static], [], [], [], [], []]
182182

183183
store.set(
184184
runs: zip(zip(lengths, captures), modifiers).map {
@@ -218,7 +218,7 @@ final class StyledRangeStoreTests: XCTestCase {
218218

219219
lengths = [5, 10, 5, 10, 10, 5, 10, 10, 30]
220220
captures = [nil, .comment, nil, .keyword, nil, .function, nil, .variable, nil]
221-
modifiers = [[], [.builtin], [], [], [], [], [], [], []]
221+
modifiers = [[], [.static], [], [], [], [], [], [], []]
222222

223223
runs.enumerated().forEach {
224224
XCTAssertEqual($0.element.length, lengths[$0.offset])

0 commit comments

Comments
 (0)