Skip to content

Commit 4b5f9cf

Browse files
imbantdbaeumer
andauthored
fix “Semantic tokens that are not in ascending order will not be highlighted” (#1467)
* Add a non-delta-encoded data attribute, which is used when the push() function is called in non-character stream order; it is eventually delta-encoded at build time * Minor code review --------- Co-authored-by: Dirk Bäumer <[email protected]>
1 parent 635616d commit 4b5f9cf

File tree

1 file changed

+112
-9
lines changed

1 file changed

+112
-9
lines changed

server/src/common/semanticTokens.ts

Lines changed: 112 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,9 @@ export class SemanticTokensBuilder {
117117

118118
private _prevLine!: number;
119119
private _prevChar!: number;
120+
private _dataIsSortedAndDeltaEncoded!: boolean;
120121
private _data!: number[];
122+
private _dataNonDelta!: number[];
121123
private _dataLen!: number;
122124

123125
private _prevData: number[] | undefined;
@@ -132,24 +134,35 @@ export class SemanticTokensBuilder {
132134
this._prevLine = 0;
133135
this._prevChar = 0;
134136
this._data = [];
137+
this._dataNonDelta = [];
135138
this._dataLen = 0;
139+
this._dataIsSortedAndDeltaEncoded = true;
136140
}
137141

138142
public push(line: number, char: number, length: number, tokenType: number, tokenModifiers: number): void {
143+
if (this._dataIsSortedAndDeltaEncoded && (line < this._prevLine || (line === this._prevLine && char < this._prevChar))) {
144+
// push calls were ordered and are no longer ordered
145+
this._dataIsSortedAndDeltaEncoded = false;
146+
147+
this._dataNonDelta = SemanticTokensBuilder._deltaDecode(this._data);
148+
}
149+
139150
let pushLine = line;
140151
let pushChar = char;
141-
if (this._dataLen > 0) {
152+
if (this._dataIsSortedAndDeltaEncoded && this._dataLen > 0) {
142153
pushLine -= this._prevLine;
143154
if (pushLine === 0) {
144155
pushChar -= this._prevChar;
145156
}
146157
}
147158

148-
this._data[this._dataLen++] = pushLine;
149-
this._data[this._dataLen++] = pushChar;
150-
this._data[this._dataLen++] = length;
151-
this._data[this._dataLen++] = tokenType;
152-
this._data[this._dataLen++] = tokenModifiers;
159+
const dataSource = this._dataIsSortedAndDeltaEncoded ? this._data : this._dataNonDelta;
160+
161+
dataSource[this._dataLen++] = pushLine;
162+
dataSource[this._dataLen++] = pushChar;
163+
dataSource[this._dataLen++] = length;
164+
dataSource[this._dataLen++] = tokenType;
165+
dataSource[this._dataLen++] = tokenModifiers;
153166

154167
this._prevLine = line;
155168
this._prevChar = char;
@@ -159,18 +172,108 @@ export class SemanticTokensBuilder {
159172
return this._id.toString();
160173
}
161174

175+
private static _deltaDecode(data: number[]): number[] {
176+
// Remove delta encoding from data
177+
const tokenCount = (data.length / 5) | 0;
178+
let prevLine = 0;
179+
let prevChar = 0;
180+
const result: number[] = [];
181+
for (let i = 0; i < tokenCount; i++) {
182+
const dstOffset = 5 * i;
183+
let line = data[dstOffset];
184+
let char = data[dstOffset + 1];
185+
186+
if (line === 0) {
187+
// on the same line as previous token
188+
line = prevLine;
189+
char += prevChar;
190+
} else {
191+
// on a different line than previous token
192+
line += prevLine;
193+
}
194+
195+
const length = data[dstOffset + 2];
196+
const tokenType = data[dstOffset + 3];
197+
const tokenModifiers = data[dstOffset + 4];
198+
199+
result[dstOffset + 0] = line;
200+
result[dstOffset + 1] = char;
201+
result[dstOffset + 2] = length;
202+
result[dstOffset + 3] = tokenType;
203+
result[dstOffset + 4] = tokenModifiers;
204+
205+
prevLine = line;
206+
prevChar = char;
207+
}
208+
209+
return result;
210+
}
211+
212+
private static _sortAndDeltaEncode(data: number[]): number[] {
213+
const pos: number[] = [];
214+
const tokenCount = (data.length / 5) | 0;
215+
for (let i = 0; i < tokenCount; i++) {
216+
pos[i] = i;
217+
}
218+
pos.sort((a, b) => {
219+
const aLine = data[5 * a];
220+
const bLine = data[5 * b];
221+
if (aLine === bLine) {
222+
const aChar = data[5 * a + 1];
223+
const bChar = data[5 * b + 1];
224+
return aChar - bChar;
225+
}
226+
return aLine - bLine;
227+
});
228+
const result = [];
229+
let prevLine = 0;
230+
let prevChar = 0;
231+
for (let i = 0; i < tokenCount; i++) {
232+
const srcOffset = 5 * pos[i];
233+
const line = data[srcOffset + 0];
234+
const char = data[srcOffset + 1];
235+
const length = data[srcOffset + 2];
236+
const tokenType = data[srcOffset + 3];
237+
const tokenModifiers = data[srcOffset + 4];
238+
239+
const pushLine = line - prevLine;
240+
const pushChar = (pushLine === 0 ? char - prevChar : char);
241+
242+
const dstOffset = 5 * i;
243+
result[dstOffset + 0] = pushLine;
244+
result[dstOffset + 1] = pushChar;
245+
result[dstOffset + 2] = length;
246+
result[dstOffset + 3] = tokenType;
247+
result[dstOffset + 4] = tokenModifiers;
248+
249+
prevLine = line;
250+
prevChar = char;
251+
}
252+
253+
return result;
254+
}
255+
256+
private getFinalDataDelta(): number[] {
257+
if (this._dataIsSortedAndDeltaEncoded) {
258+
return this._data;
259+
} else {
260+
return SemanticTokensBuilder._sortAndDeltaEncode(this._dataNonDelta);
261+
}
262+
}
263+
162264
public previousResult(id: string) {
163265
if (this.id === id) {
164-
this._prevData = this._data;
266+
this._prevData = this.getFinalDataDelta();
165267
}
166268
this.initialize();
167269
}
168270

169271
public build(): SemanticTokens {
170272
this._prevData = undefined;
273+
171274
return {
172275
resultId: this.id,
173-
data: this._data
276+
data: this.getFinalDataDelta()
174277
};
175278
}
176279

@@ -182,7 +285,7 @@ export class SemanticTokensBuilder {
182285
if (this._prevData !== undefined) {
183286
return {
184287
resultId: this.id,
185-
edits: (new SemanticTokensDiff(this._prevData, this._data)).computeDiff()
288+
edits: (new SemanticTokensDiff(this._prevData, this.getFinalDataDelta())).computeDiff()
186289
};
187290
} else {
188291
return this.build();

0 commit comments

Comments
 (0)