forked from CodeEditApp/CodeEditSourceEditor
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathStyledRangeStore.swift
More file actions
115 lines (94 loc) · 4.07 KB
/
StyledRangeStore.swift
File metadata and controls
115 lines (94 loc) · 4.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//
// StyledRangeStore.swift
// CodeEditSourceEditor
//
// Created by Khan Winter on 10/24/24
//
import _RopeModule
/// StyledRangeStore 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.
final class StyledRangeStore {
typealias Run = StyledRangeStoreRun
typealias Index = Rope<StyledRun>.Index
var _guts = Rope<StyledRun>()
var length: Int {
_guts.count(in: OffsetMetric())
}
/// A small performance improvement for multiple identical queries, as often happens when used
/// in ``StyledRangeContainer``
private var cache: (range: Range<Int>, runs: [Run])?
init(documentLength: Int) {
self._guts = Rope([StyledRun(length: documentLength, capture: nil, modifiers: [])])
}
// MARK: - Core
/// Find all runs in a range.
/// - Parameter range: The range to query.
/// - Returns: A continuous array of runs representing the queried range.
func runs(in range: Range<Int>) -> [Run] {
assert(range.lowerBound >= 0, "Negative lowerBound")
assert(range.upperBound <= _guts.count(in: OffsetMetric()), "upperBound outside valid range")
if let cache, cache.range == range {
return cache.runs
}
var runs = [Run]()
var index = findIndex(at: range.lowerBound).index
var offset: Int? = range.lowerBound - _guts.offset(of: index, in: OffsetMetric())
while index < _guts.endIndex {
let run = _guts[index]
runs.append(Run(length: run.length - (offset ?? 0), capture: run.capture, modifiers: run.modifiers))
index = _guts.index(after: index)
offset = nil
}
return runs
}
/// Sets a capture and modifiers for a range.
/// - Parameters:
/// - capture: The capture to set.
/// - modifiers: The modifiers to set.
/// - range: The range to write to.
func set(capture: CaptureName, modifiers: CaptureModifierSet, for range: Range<Int>) {
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)
}
/// 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<Int>) {
let gutsRange = 0..<_guts.count(in: OffsetMetric())
if range.clamped(to: gutsRange) != range {
let upperBound = range.clamped(to: gutsRange).upperBound
let missingCharacters = range.upperBound - upperBound
storageUpdated(replacedCharactersIn: upperBound..<upperBound, withCount: missingCharacters)
}
_guts.replaceSubrange(
range,
in: OffsetMetric(),
with: runs.map { StyledRun(length: $0.length, capture: $0.capture, modifiers: $0.modifiers) }
)
coalesceNearby(range: range)
cache = nil
}
}
// MARK: - Storage Sync
extension StyledRangeStore {
/// Handles keeping the internal storage in sync with the document.
func storageUpdated(replacedCharactersIn range: Range<Int>, withCount newLength: Int) {
assert(range.lowerBound >= 0, "Negative lowerBound")
assert(range.upperBound <= _guts.count(in: OffsetMetric()), "upperBound outside valid range")
if newLength != 0 {
_guts.replaceSubrange(range, in: OffsetMetric(), with: [.empty(length: newLength)])
} else {
_guts.removeSubrange(range, in: OffsetMetric())
}
if _guts.count > 0 {
// Coalesce nearby items if necessary.
coalesceNearby(range: Range(lowerBound: range.lowerBound, length: newLength))
}
cache = nil
}
}