Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/tokens/sparseTokensStore.ts
3294 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as arrays from '../../../base/common/arrays.js';
7
import { IRange, Range } from '../core/range.js';
8
import { LineTokens } from './lineTokens.js';
9
import { SparseMultilineTokens } from './sparseMultilineTokens.js';
10
import { ILanguageIdCodec } from '../languages.js';
11
import { MetadataConsts } from '../encodedTokenAttributes.js';
12
import { ITextModel } from '../model.js';
13
14
/**
15
* Represents sparse tokens in a text model.
16
*/
17
export class SparseTokensStore {
18
19
private _pieces: SparseMultilineTokens[];
20
private _isComplete: boolean;
21
private readonly _languageIdCodec: ILanguageIdCodec;
22
23
constructor(languageIdCodec: ILanguageIdCodec) {
24
this._pieces = [];
25
this._isComplete = false;
26
this._languageIdCodec = languageIdCodec;
27
}
28
29
public flush(): void {
30
this._pieces = [];
31
this._isComplete = false;
32
}
33
34
public isEmpty(): boolean {
35
return (this._pieces.length === 0);
36
}
37
38
public set(pieces: SparseMultilineTokens[] | null, isComplete: boolean, textModel: ITextModel | undefined = undefined): void {
39
this._pieces = pieces || [];
40
this._isComplete = isComplete;
41
42
if (textModel) {
43
for (const p of this._pieces) {
44
p.reportIfInvalid(textModel);
45
}
46
}
47
}
48
49
public setPartial(_range: Range, pieces: SparseMultilineTokens[]): Range {
50
// console.log(`setPartial ${_range} ${pieces.map(p => p.toString()).join(', ')}`);
51
52
let range = _range;
53
if (pieces.length > 0) {
54
const _firstRange = pieces[0].getRange();
55
const _lastRange = pieces[pieces.length - 1].getRange();
56
if (!_firstRange || !_lastRange) {
57
return _range;
58
}
59
range = _range.plusRange(_firstRange).plusRange(_lastRange);
60
}
61
62
let insertPosition: { index: number } | null = null;
63
for (let i = 0, len = this._pieces.length; i < len; i++) {
64
const piece = this._pieces[i];
65
if (piece.endLineNumber < range.startLineNumber) {
66
// this piece is before the range
67
continue;
68
}
69
70
if (piece.startLineNumber > range.endLineNumber) {
71
// this piece is after the range, so mark the spot before this piece
72
// as a good insertion position and stop looping
73
insertPosition = insertPosition || { index: i };
74
break;
75
}
76
77
// this piece might intersect with the range
78
piece.removeTokens(range);
79
80
if (piece.isEmpty()) {
81
// remove the piece if it became empty
82
this._pieces.splice(i, 1);
83
i--;
84
len--;
85
continue;
86
}
87
88
if (piece.endLineNumber < range.startLineNumber) {
89
// after removal, this piece is before the range
90
continue;
91
}
92
93
if (piece.startLineNumber > range.endLineNumber) {
94
// after removal, this piece is after the range
95
insertPosition = insertPosition || { index: i };
96
continue;
97
}
98
99
// after removal, this piece contains the range
100
const [a, b] = piece.split(range);
101
if (a.isEmpty()) {
102
// this piece is actually after the range
103
insertPosition = insertPosition || { index: i };
104
continue;
105
}
106
if (b.isEmpty()) {
107
// this piece is actually before the range
108
continue;
109
}
110
this._pieces.splice(i, 1, a, b);
111
i++;
112
len++;
113
114
insertPosition = insertPosition || { index: i };
115
}
116
117
insertPosition = insertPosition || { index: this._pieces.length };
118
119
if (pieces.length > 0) {
120
this._pieces = arrays.arrayInsert(this._pieces, insertPosition.index, pieces);
121
}
122
123
// console.log(`I HAVE ${this._pieces.length} pieces`);
124
// console.log(`${this._pieces.map(p => p.toString()).join('\n')}`);
125
126
return range;
127
}
128
129
public isComplete(): boolean {
130
return this._isComplete;
131
}
132
133
public addSparseTokens(lineNumber: number, aTokens: LineTokens): LineTokens {
134
if (aTokens.getTextLength() === 0) {
135
// Don't do anything for empty lines
136
return aTokens;
137
}
138
139
const pieces = this._pieces;
140
141
if (pieces.length === 0) {
142
return aTokens;
143
}
144
145
const pieceIndex = SparseTokensStore._findFirstPieceWithLine(pieces, lineNumber);
146
const bTokens = pieces[pieceIndex].getLineTokens(lineNumber);
147
148
if (!bTokens) {
149
return aTokens;
150
}
151
152
const aLen = aTokens.getCount();
153
const bLen = bTokens.getCount();
154
155
let aIndex = 0;
156
const result: number[] = [];
157
let resultLen = 0;
158
let lastEndOffset = 0;
159
160
const emitToken = (endOffset: number, metadata: number) => {
161
if (endOffset === lastEndOffset) {
162
return;
163
}
164
lastEndOffset = endOffset;
165
result[resultLen++] = endOffset;
166
result[resultLen++] = metadata;
167
};
168
169
for (let bIndex = 0; bIndex < bLen; bIndex++) {
170
// bTokens is not validated yet, but aTokens is. We want to make sure that the LineTokens we return
171
// are valid, so we clamp the ranges to ensure that.
172
const bStartCharacter = Math.min(bTokens.getStartCharacter(bIndex), aTokens.getTextLength());
173
const bEndCharacter = Math.min(bTokens.getEndCharacter(bIndex), aTokens.getTextLength());
174
const bMetadata = bTokens.getMetadata(bIndex);
175
176
const bMask = (
177
((bMetadata & MetadataConsts.SEMANTIC_USE_ITALIC) ? MetadataConsts.ITALIC_MASK : 0)
178
| ((bMetadata & MetadataConsts.SEMANTIC_USE_BOLD) ? MetadataConsts.BOLD_MASK : 0)
179
| ((bMetadata & MetadataConsts.SEMANTIC_USE_UNDERLINE) ? MetadataConsts.UNDERLINE_MASK : 0)
180
| ((bMetadata & MetadataConsts.SEMANTIC_USE_STRIKETHROUGH) ? MetadataConsts.STRIKETHROUGH_MASK : 0)
181
| ((bMetadata & MetadataConsts.SEMANTIC_USE_FOREGROUND) ? MetadataConsts.FOREGROUND_MASK : 0)
182
| ((bMetadata & MetadataConsts.SEMANTIC_USE_BACKGROUND) ? MetadataConsts.BACKGROUND_MASK : 0)
183
) >>> 0;
184
const aMask = (~bMask) >>> 0;
185
186
// push any token from `a` that is before `b`
187
while (aIndex < aLen && aTokens.getEndOffset(aIndex) <= bStartCharacter) {
188
emitToken(aTokens.getEndOffset(aIndex), aTokens.getMetadata(aIndex));
189
aIndex++;
190
}
191
192
// push the token from `a` if it intersects the token from `b`
193
if (aIndex < aLen && aTokens.getStartOffset(aIndex) < bStartCharacter) {
194
emitToken(bStartCharacter, aTokens.getMetadata(aIndex));
195
}
196
197
// skip any tokens from `a` that are contained inside `b`
198
while (aIndex < aLen && aTokens.getEndOffset(aIndex) < bEndCharacter) {
199
emitToken(aTokens.getEndOffset(aIndex), (aTokens.getMetadata(aIndex) & aMask) | (bMetadata & bMask));
200
aIndex++;
201
}
202
203
if (aIndex < aLen) {
204
emitToken(bEndCharacter, (aTokens.getMetadata(aIndex) & aMask) | (bMetadata & bMask));
205
if (aTokens.getEndOffset(aIndex) === bEndCharacter) {
206
// `a` ends exactly at the same spot as `b`!
207
aIndex++;
208
}
209
} else {
210
const aMergeIndex = Math.min(Math.max(0, aIndex - 1), aLen - 1);
211
212
// push the token from `b`
213
emitToken(bEndCharacter, (aTokens.getMetadata(aMergeIndex) & aMask) | (bMetadata & bMask));
214
}
215
}
216
217
// push the remaining tokens from `a`
218
while (aIndex < aLen) {
219
emitToken(aTokens.getEndOffset(aIndex), aTokens.getMetadata(aIndex));
220
aIndex++;
221
}
222
223
return new LineTokens(new Uint32Array(result), aTokens.getLineContent(), this._languageIdCodec);
224
}
225
226
private static _findFirstPieceWithLine(pieces: SparseMultilineTokens[], lineNumber: number): number {
227
let low = 0;
228
let high = pieces.length - 1;
229
230
while (low < high) {
231
let mid = low + Math.floor((high - low) / 2);
232
233
if (pieces[mid].endLineNumber < lineNumber) {
234
low = mid + 1;
235
} else if (pieces[mid].startLineNumber > lineNumber) {
236
high = mid - 1;
237
} else {
238
while (mid > low && pieces[mid - 1].startLineNumber <= lineNumber && lineNumber <= pieces[mid - 1].endLineNumber) {
239
mid--;
240
}
241
return mid;
242
}
243
}
244
245
return low;
246
}
247
248
public acceptEdit(range: IRange, eolCount: number, firstLineLength: number, lastLineLength: number, firstCharCode: number): void {
249
for (const piece of this._pieces) {
250
piece.acceptEdit(range, eolCount, firstLineLength, lastLineLength, firstCharCode);
251
}
252
}
253
}
254
255