Skip to content

Commit aba0a0b

Browse files
committed
Finish Deltas Tests, Rename hasTokens
1 parent baafd2e commit aba0a0b

File tree

4 files changed

+136
-31
lines changed

4 files changed

+136
-31
lines changed

CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenHighlightProvider.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ final class SemanticTokenHighlightProvider<
6666
return
6767
}
6868

69-
guard storage.hasTokens else {
69+
guard storage.hasReceivedData else {
7070
// We have no semantic token info, request it!
7171
try await requestTokens(languageServer: languageServer, textView: textView)
7272
await MainActor.run {
@@ -154,7 +154,7 @@ final class SemanticTokenHighlightProvider<
154154
}
155155

156156
func queryHighlightsFor(textView: TextView, range: NSRange, completion: @escaping HighlightCallback) {
157-
guard storage.hasTokens else {
157+
guard storage.hasReceivedData else {
158158
pendingHighlightCallbacks.append(completion)
159159
return
160160
}

CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenStorage/GenericSemanticTokenStorage.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import CodeEditSourceEditor
1515
/// See ``SemanticTokenStorage``.
1616
protocol GenericSemanticTokenStorage: AnyObject {
1717
var lastResultId: String? { get }
18-
var hasTokens: Bool { get }
18+
var hasReceivedData: Bool { get }
1919

2020
init()
2121

CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenStorage/SemanticTokenStorage.swift

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,15 @@ final class SemanticTokenStorage: GenericSemanticTokenStorage {
2424
let tokens: [SemanticToken]
2525
}
2626

27+
/// The last received result identifier.
2728
var lastResultId: String? {
2829
state?.resultId
2930
}
3031

31-
var hasTokens: Bool {
32+
/// Indicates if the storage object has received any data.
33+
/// Once `setData` has been called, this returns `true`.
34+
/// Other operations will fail without any data in the storage object.
35+
var hasReceivedData: Bool {
3236
state != nil
3337
}
3438

@@ -108,19 +112,9 @@ final class SemanticTokenStorage: GenericSemanticTokenStorage {
108112
}
109113
}
110114

111-
// Set the current state and decode the new token data
112-
var decodedTokens: [SemanticToken] = []
113-
for idx in stride(from: 0, to: tokenData.count, by: 5) {
114-
decodedTokens.append(SemanticToken(
115-
line: tokenData[idx],
116-
char: tokenData[idx + 1],
117-
length: tokenData[idx + 2],
118-
type: tokenData[idx + 3],
119-
modifiers: tokenData[idx + 4]
120-
))
121-
}
115+
// Re-decode the updated token data and set the updated state
116+
let decodedTokens = SemanticTokens(data: tokenData).decode()
122117
state = CurrentState(resultId: deltas.resultId, tokenData: tokenData, tokens: decodedTokens)
123-
124118
return invalidatedSet
125119
}
126120

CodeEditTests/Features/LSP/SemanticTokens/SemanticTokenStorageTests.swift

Lines changed: 126 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ struct SemanticTokenStorageTests {
3535
@Test
3636
func initialState() async throws {
3737
#expect(storage.state == nil)
38-
#expect(storage.hasTokens == false)
38+
#expect(storage.hasReceivedData == false)
3939
#expect(storage.lastResultId == nil)
4040
}
4141

@@ -53,36 +53,147 @@ struct SemanticTokenStorageTests {
5353
#expect(state.resultId == "1234")
5454

5555
#expect(storage.lastResultId == "1234")
56-
#expect(storage.hasTokens == true)
56+
#expect(storage.hasReceivedData == true)
57+
}
58+
59+
@Test
60+
func overwriteDataRepeatedly() async throws {
61+
let dataToApply: [(String?, [SemanticToken])] = [
62+
(nil, semanticTokens),
63+
("1", []),
64+
("2", semanticTokens.dropLast()),
65+
("3", semanticTokens)
66+
]
67+
for (resultId, tokens) in dataToApply {
68+
storage.setData(SemanticTokens(resultId: resultId, tokens: tokens))
69+
let state = try #require(storage.state)
70+
#expect(state.tokens == tokens)
71+
#expect(state.resultId == resultId)
72+
#expect(storage.lastResultId == resultId)
73+
#expect(storage.hasReceivedData == true)
74+
}
5775
}
5876

5977
@Suite("ApplyDeltas")
6078
struct TokensDeltasTests {
61-
let storage = SemanticTokenStorage()
79+
struct DeltaEdit {
80+
let start: Int
81+
let deleteCount: Int
82+
let data: [Int]
83+
84+
func makeString() -> String {
85+
let dataString = data.map { String($0) }.joined(separator: ",")
86+
return "{\"start\": \(start), \"deleteCount\": \(deleteCount), \"data\": [\(dataString)] }"
87+
}
88+
}
89+
90+
func makeDelta(resultId: String, edits: [DeltaEdit]) throws -> SemanticTokensDelta {
91+
// This is unfortunate, but there's no public initializer for these structs.
92+
// So we have to decode them from JSON strings
93+
let editsString = edits.map { $0.makeString() }.joined(separator: ",")
94+
let deltasJSON = "{ \"resultId\": \"\(resultId)\", \"edits\": [\(editsString)] }"
95+
let decoder = JSONDecoder()
96+
let deltas = try decoder.decode(SemanticTokensDelta.self, from: Data(deltasJSON.utf8))
97+
return deltas
98+
}
99+
100+
let storage: SemanticTokenStorage
62101

63102
let semanticTokens = [
64103
SemanticToken(line: 0, char: 0, length: 10, type: 0, modifiers: 0),
65104
SemanticToken(line: 1, char: 2, length: 5, type: 2, modifiers: 3),
66105
SemanticToken(line: 3, char: 8, length: 10, type: 1, modifiers: 0)
67106
]
68107

69-
@Test(
70-
arguments: [
71-
#"{ "resultId": "1", "edits": [{"start": 0, "deleteCount": 0, "data": [0, 2, 3, 0, 1] }] }"#
72-
]
73-
)
74-
func applyDeltas(deltasJSON: String) async throws {
75-
// This is unfortunate, but there's no public initializer for these structs.
76-
// So we have to decode them from JSON strings
77-
let decoder = JSONDecoder()
78-
let deltas = try decoder.decode(SemanticTokensDelta.self, from: Data(deltasJSON.utf8))
108+
init() {
109+
storage = SemanticTokenStorage()
110+
storage.setData(SemanticTokens(tokens: semanticTokens))
111+
#expect(storage.state?.tokens == semanticTokens)
112+
}
113+
114+
@Test
115+
func applyEmptyDeltasNoChange() throws {
116+
let deltas = try makeDelta(resultId: "1", edits: [])
117+
118+
_ = storage.applyDelta(deltas)
119+
120+
let state = try #require(storage.state)
121+
#expect(state.tokens.count == 3)
122+
#expect(state.resultId == "1")
123+
#expect(state.tokens == semanticTokens)
124+
}
125+
126+
@Test
127+
func applyInsertDeltas() throws {
128+
let deltas = try makeDelta(resultId: "1", edits: [.init(start: 0, deleteCount: 0, data: [0, 2, 3, 0, 1])])
129+
130+
_ = storage.applyDelta(deltas)
131+
132+
let state = try #require(storage.state)
133+
#expect(state.tokens.count == 4)
134+
#expect(storage.lastResultId == "1")
135+
136+
// Should have inserted one at the beginning
137+
#expect(state.tokens[0].line == 0)
138+
#expect(state.tokens[0].char == 2)
139+
#expect(state.tokens[0].length == 3)
140+
#expect(state.tokens[0].modifiers == 1)
141+
142+
// We inserted a delta into the space before this one (at char 2) so this one starts at the same spot
143+
#expect(state.tokens[1] == SemanticToken(line: 0, char: 2, length: 10, type: 0, modifiers: 0))
144+
#expect(state.tokens[2] == semanticTokens[1])
145+
#expect(state.tokens[3] == semanticTokens[2])
146+
}
147+
148+
@Test
149+
func applyDeleteOneDeltas() throws {
150+
// Delete the second token (semanticTokens[1]) from the initial state.
151+
// Each token is represented by 5 numbers, so token[1] starts at raw data index 5.
152+
let deltas = try makeDelta(resultId: "2", edits: [.init(start: 5, deleteCount: 5, data: [])])
153+
_ = storage.applyDelta(deltas)
154+
155+
let state = try #require(storage.state)
156+
#expect(state.tokens.count == 2)
157+
#expect(state.resultId == "2")
158+
// The remaining tokens should be the first and third tokens, except we deleted one line between them
159+
// so the third token's line is less one
160+
#expect(state.tokens[0] == semanticTokens[0])
161+
#expect(state.tokens[1] == SemanticToken(line: 2, char: 8, length: 10, type: 1, modifiers: 0))
162+
}
163+
164+
@Test
165+
func applyDeleteManyDeltas() throws {
166+
// Delete the first two tokens from the initial state.
167+
// Token[0] and token[1] together use 10 integers.
168+
let deltas = try makeDelta(resultId: "3", edits: [.init(start: 0, deleteCount: 10, data: [])])
169+
_ = storage.applyDelta(deltas)
79170

80-
171+
let state = try #require(storage.state)
172+
#expect(state.tokens.count == 1)
173+
#expect(state.resultId == "3")
174+
// The only remaining token should be the original third token.
175+
#expect(state.tokens[0] == SemanticToken(line: 2, char: 8, length: 10, type: 1, modifiers: 0))
81176
}
82177

83178
@Test
84-
func invalidatedRanges() {
179+
func applyInsertAndDeleteDeltas() throws {
180+
// Combined test: insert a token at the beginning and delete the last token.
181+
// Edit 1: Insert a new token at the beginning.
182+
let insertion = DeltaEdit(start: 0, deleteCount: 0, data: [0, 2, 3, 0, 1])
183+
// Edit 2: Delete the token that starts at raw data index 10 (the third token in the original state).
184+
let deletion = DeltaEdit(start: 10, deleteCount: 5, data: [])
185+
let deltas = try makeDelta(resultId: "4", edits: [insertion, deletion])
186+
_ = storage.applyDelta(deltas)
85187

188+
let state = try #require(storage.state)
189+
#expect(state.tokens.count == 3)
190+
#expect(storage.lastResultId == "4")
191+
// The new inserted token becomes the first token.
192+
#expect(state.tokens[0] == SemanticToken(line: 0, char: 2, length: 3, type: 0, modifiers: 1))
193+
// The original first token is shifted (its character offset increased by 2).
194+
#expect(state.tokens[1] == SemanticToken(line: 0, char: 2, length: 10, type: 0, modifiers: 0))
195+
// The second token from the original state remains unchanged.
196+
#expect(state.tokens[2] == semanticTokens[1])
86197
}
87198
}
88199
}

0 commit comments

Comments
 (0)