Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/tokens/contiguousTokensStore.ts
3296 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 { Position } from '../core/position.js';
8
import { IRange } from '../core/range.js';
9
import { ContiguousTokensEditing, EMPTY_LINE_TOKENS, toUint32Array } from './contiguousTokensEditing.js';
10
import { LineTokens } from './lineTokens.js';
11
import { ILanguageIdCodec } from '../languages.js';
12
import { LanguageId, FontStyle, ColorId, StandardTokenType, MetadataConsts, TokenMetadata } from '../encodedTokenAttributes.js';
13
import { ITextModel } from '../model.js';
14
import { ContiguousMultilineTokens } from './contiguousMultilineTokens.js';
15
16
/**
17
* Represents contiguous tokens in a text model.
18
*/
19
export class ContiguousTokensStore {
20
private _lineTokens: (Uint32Array | ArrayBuffer | null)[];
21
private _len: number;
22
private readonly _languageIdCodec: ILanguageIdCodec;
23
24
constructor(languageIdCodec: ILanguageIdCodec) {
25
this._lineTokens = [];
26
this._len = 0;
27
this._languageIdCodec = languageIdCodec;
28
}
29
30
public flush(): void {
31
this._lineTokens = [];
32
this._len = 0;
33
}
34
35
get hasTokens(): boolean {
36
return this._lineTokens.length > 0;
37
}
38
39
public getTokens(topLevelLanguageId: string, lineIndex: number, lineText: string): LineTokens {
40
let rawLineTokens: Uint32Array | ArrayBuffer | null = null;
41
if (lineIndex < this._len) {
42
rawLineTokens = this._lineTokens[lineIndex];
43
}
44
45
if (rawLineTokens !== null && rawLineTokens !== EMPTY_LINE_TOKENS) {
46
return new LineTokens(toUint32Array(rawLineTokens), lineText, this._languageIdCodec);
47
}
48
49
const lineTokens = new Uint32Array(2);
50
lineTokens[0] = lineText.length;
51
lineTokens[1] = getDefaultMetadata(this._languageIdCodec.encodeLanguageId(topLevelLanguageId));
52
return new LineTokens(lineTokens, lineText, this._languageIdCodec);
53
}
54
55
private static _massageTokens(topLevelLanguageId: LanguageId, lineTextLength: number, _tokens: Uint32Array | ArrayBuffer | null): Uint32Array | ArrayBuffer {
56
57
const tokens = _tokens ? toUint32Array(_tokens) : null;
58
59
if (lineTextLength === 0) {
60
let hasDifferentLanguageId = false;
61
if (tokens && tokens.length > 1) {
62
hasDifferentLanguageId = (TokenMetadata.getLanguageId(tokens[1]) !== topLevelLanguageId);
63
}
64
65
if (!hasDifferentLanguageId) {
66
return EMPTY_LINE_TOKENS;
67
}
68
}
69
70
if (!tokens || tokens.length === 0) {
71
const tokens = new Uint32Array(2);
72
tokens[0] = lineTextLength;
73
tokens[1] = getDefaultMetadata(topLevelLanguageId);
74
return tokens.buffer;
75
}
76
77
// Ensure the last token covers the end of the text
78
tokens[tokens.length - 2] = lineTextLength;
79
80
if (tokens.byteOffset === 0 && tokens.byteLength === tokens.buffer.byteLength) {
81
// Store directly the ArrayBuffer pointer to save an object
82
return tokens.buffer;
83
}
84
return tokens;
85
}
86
87
private _ensureLine(lineIndex: number): void {
88
while (lineIndex >= this._len) {
89
this._lineTokens[this._len] = null;
90
this._len++;
91
}
92
}
93
94
private _deleteLines(start: number, deleteCount: number): void {
95
if (deleteCount === 0) {
96
return;
97
}
98
if (start + deleteCount > this._len) {
99
deleteCount = this._len - start;
100
}
101
this._lineTokens.splice(start, deleteCount);
102
this._len -= deleteCount;
103
}
104
105
private _insertLines(insertIndex: number, insertCount: number): void {
106
if (insertCount === 0) {
107
return;
108
}
109
const lineTokens: (Uint32Array | ArrayBuffer | null)[] = [];
110
for (let i = 0; i < insertCount; i++) {
111
lineTokens[i] = null;
112
}
113
this._lineTokens = arrays.arrayInsert(this._lineTokens, insertIndex, lineTokens);
114
this._len += insertCount;
115
}
116
117
public setTokens(topLevelLanguageId: string, lineIndex: number, lineTextLength: number, _tokens: Uint32Array | ArrayBuffer | null, checkEquality: boolean): boolean {
118
const tokens = ContiguousTokensStore._massageTokens(this._languageIdCodec.encodeLanguageId(topLevelLanguageId), lineTextLength, _tokens);
119
this._ensureLine(lineIndex);
120
const oldTokens = this._lineTokens[lineIndex];
121
this._lineTokens[lineIndex] = tokens;
122
123
if (checkEquality) {
124
return !ContiguousTokensStore._equals(oldTokens, tokens);
125
}
126
return false;
127
}
128
129
private static _equals(_a: Uint32Array | ArrayBuffer | null, _b: Uint32Array | ArrayBuffer | null) {
130
if (!_a || !_b) {
131
return !_a && !_b;
132
}
133
134
const a = toUint32Array(_a);
135
const b = toUint32Array(_b);
136
137
if (a.length !== b.length) {
138
return false;
139
}
140
for (let i = 0, len = a.length; i < len; i++) {
141
if (a[i] !== b[i]) {
142
return false;
143
}
144
}
145
return true;
146
}
147
148
//#region Editing
149
150
public acceptEdit(range: IRange, eolCount: number, firstLineLength: number): void {
151
this._acceptDeleteRange(range);
152
this._acceptInsertText(new Position(range.startLineNumber, range.startColumn), eolCount, firstLineLength);
153
}
154
155
private _acceptDeleteRange(range: IRange): void {
156
157
const firstLineIndex = range.startLineNumber - 1;
158
if (firstLineIndex >= this._len) {
159
return;
160
}
161
162
if (range.startLineNumber === range.endLineNumber) {
163
if (range.startColumn === range.endColumn) {
164
// Nothing to delete
165
return;
166
}
167
168
this._lineTokens[firstLineIndex] = ContiguousTokensEditing.delete(this._lineTokens[firstLineIndex], range.startColumn - 1, range.endColumn - 1);
169
return;
170
}
171
172
this._lineTokens[firstLineIndex] = ContiguousTokensEditing.deleteEnding(this._lineTokens[firstLineIndex], range.startColumn - 1);
173
174
const lastLineIndex = range.endLineNumber - 1;
175
let lastLineTokens: Uint32Array | ArrayBuffer | null = null;
176
if (lastLineIndex < this._len) {
177
lastLineTokens = ContiguousTokensEditing.deleteBeginning(this._lineTokens[lastLineIndex], range.endColumn - 1);
178
}
179
180
// Take remaining text on last line and append it to remaining text on first line
181
this._lineTokens[firstLineIndex] = ContiguousTokensEditing.append(this._lineTokens[firstLineIndex], lastLineTokens);
182
183
// Delete middle lines
184
this._deleteLines(range.startLineNumber, range.endLineNumber - range.startLineNumber);
185
}
186
187
private _acceptInsertText(position: Position, eolCount: number, firstLineLength: number): void {
188
189
if (eolCount === 0 && firstLineLength === 0) {
190
// Nothing to insert
191
return;
192
}
193
194
const lineIndex = position.lineNumber - 1;
195
if (lineIndex >= this._len) {
196
return;
197
}
198
199
if (eolCount === 0) {
200
// Inserting text on one line
201
this._lineTokens[lineIndex] = ContiguousTokensEditing.insert(this._lineTokens[lineIndex], position.column - 1, firstLineLength);
202
return;
203
}
204
205
this._lineTokens[lineIndex] = ContiguousTokensEditing.deleteEnding(this._lineTokens[lineIndex], position.column - 1);
206
this._lineTokens[lineIndex] = ContiguousTokensEditing.insert(this._lineTokens[lineIndex], position.column - 1, firstLineLength);
207
208
this._insertLines(position.lineNumber, eolCount);
209
}
210
211
//#endregion
212
213
public setMultilineTokens(tokens: ContiguousMultilineTokens[], textModel: ITextModel): { changes: { fromLineNumber: number; toLineNumber: number }[] } {
214
if (tokens.length === 0) {
215
return { changes: [] };
216
}
217
218
const ranges: { fromLineNumber: number; toLineNumber: number }[] = [];
219
220
for (let i = 0, len = tokens.length; i < len; i++) {
221
const element = tokens[i];
222
let minChangedLineNumber = 0;
223
let maxChangedLineNumber = 0;
224
let hasChange = false;
225
for (let lineNumber = element.startLineNumber; lineNumber <= element.endLineNumber; lineNumber++) {
226
if (hasChange) {
227
this.setTokens(textModel.getLanguageId(), lineNumber - 1, textModel.getLineLength(lineNumber), element.getLineTokens(lineNumber), false);
228
maxChangedLineNumber = lineNumber;
229
} else {
230
const lineHasChange = this.setTokens(textModel.getLanguageId(), lineNumber - 1, textModel.getLineLength(lineNumber), element.getLineTokens(lineNumber), true);
231
if (lineHasChange) {
232
hasChange = true;
233
minChangedLineNumber = lineNumber;
234
maxChangedLineNumber = lineNumber;
235
}
236
}
237
}
238
if (hasChange) {
239
ranges.push({ fromLineNumber: minChangedLineNumber, toLineNumber: maxChangedLineNumber, });
240
}
241
}
242
243
return { changes: ranges };
244
}
245
}
246
247
function getDefaultMetadata(topLevelLanguageId: LanguageId): number {
248
return (
249
(topLevelLanguageId << MetadataConsts.LANGUAGEID_OFFSET)
250
| (StandardTokenType.Other << MetadataConsts.TOKEN_TYPE_OFFSET)
251
| (FontStyle.None << MetadataConsts.FONT_STYLE_OFFSET)
252
| (ColorId.DefaultForeground << MetadataConsts.FOREGROUND_OFFSET)
253
| (ColorId.DefaultBackground << MetadataConsts.BACKGROUND_OFFSET)
254
// If there is no grammar, we just take a guess and try to match brackets.
255
| (MetadataConsts.BALANCED_BRACKETS_MASK)
256
) >>> 0;
257
}
258
259