Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/tokens/contiguousMultilineTokens.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 { readUInt32BE, writeUInt32BE } from '../../../base/common/buffer.js';
8
import { Position } from '../core/position.js';
9
import { IRange } from '../core/range.js';
10
import { countEOL } from '../core/misc/eolCounter.js';
11
import { ContiguousTokensEditing } from './contiguousTokensEditing.js';
12
import { LineRange } from '../core/ranges/lineRange.js';
13
14
/**
15
* Represents contiguous tokens over a contiguous range of lines.
16
*/
17
export class ContiguousMultilineTokens {
18
public static deserialize(buff: Uint8Array, offset: number, result: ContiguousMultilineTokens[]): number {
19
const view32 = new Uint32Array(buff.buffer);
20
const startLineNumber = readUInt32BE(buff, offset); offset += 4;
21
const count = readUInt32BE(buff, offset); offset += 4;
22
const tokens: Uint32Array[] = [];
23
for (let i = 0; i < count; i++) {
24
const byteCount = readUInt32BE(buff, offset); offset += 4;
25
tokens.push(view32.subarray(offset / 4, offset / 4 + byteCount / 4));
26
offset += byteCount;
27
}
28
result.push(new ContiguousMultilineTokens(startLineNumber, tokens));
29
return offset;
30
}
31
32
/**
33
* The start line number for this block of tokens.
34
*/
35
private _startLineNumber: number;
36
37
/**
38
* The tokens are stored in a binary format. There is an element for each line,
39
* so `tokens[index]` contains all tokens on line `startLineNumber + index`.
40
*
41
* On a specific line, each token occupies two array indices. For token i:
42
* - at offset 2*i => endOffset
43
* - at offset 2*i + 1 => metadata
44
*
45
*/
46
private _tokens: (Uint32Array | ArrayBuffer | null)[];
47
48
/**
49
* (Inclusive) start line number for these tokens.
50
*/
51
public get startLineNumber(): number {
52
return this._startLineNumber;
53
}
54
55
/**
56
* (Inclusive) end line number for these tokens.
57
*/
58
public get endLineNumber(): number {
59
return this._startLineNumber + this._tokens.length - 1;
60
}
61
62
constructor(startLineNumber: number, tokens: Uint32Array[]) {
63
this._startLineNumber = startLineNumber;
64
this._tokens = tokens;
65
}
66
67
getLineRange(): LineRange {
68
return new LineRange(this._startLineNumber, this._startLineNumber + this._tokens.length);
69
}
70
71
/**
72
* @see {@link _tokens}
73
*/
74
public getLineTokens(lineNumber: number): Uint32Array | ArrayBuffer | null {
75
return this._tokens[lineNumber - this._startLineNumber];
76
}
77
78
public appendLineTokens(lineTokens: Uint32Array): void {
79
this._tokens.push(lineTokens);
80
}
81
82
public serializeSize(): number {
83
let result = 0;
84
result += 4; // 4 bytes for the start line number
85
result += 4; // 4 bytes for the line count
86
for (let i = 0; i < this._tokens.length; i++) {
87
const lineTokens = this._tokens[i];
88
if (!(lineTokens instanceof Uint32Array)) {
89
throw new Error(`Not supported!`);
90
}
91
result += 4; // 4 bytes for the byte count
92
result += lineTokens.byteLength;
93
}
94
return result;
95
}
96
97
public serialize(destination: Uint8Array, offset: number): number {
98
writeUInt32BE(destination, this._startLineNumber, offset); offset += 4;
99
writeUInt32BE(destination, this._tokens.length, offset); offset += 4;
100
for (let i = 0; i < this._tokens.length; i++) {
101
const lineTokens = this._tokens[i];
102
if (!(lineTokens instanceof Uint32Array)) {
103
throw new Error(`Not supported!`);
104
}
105
writeUInt32BE(destination, lineTokens.byteLength, offset); offset += 4;
106
destination.set(new Uint8Array(lineTokens.buffer), offset); offset += lineTokens.byteLength;
107
}
108
return offset;
109
}
110
111
public applyEdit(range: IRange, text: string): void {
112
const [eolCount, firstLineLength] = countEOL(text);
113
this._acceptDeleteRange(range);
114
this._acceptInsertText(new Position(range.startLineNumber, range.startColumn), eolCount, firstLineLength);
115
}
116
117
private _acceptDeleteRange(range: IRange): void {
118
if (range.startLineNumber === range.endLineNumber && range.startColumn === range.endColumn) {
119
// Nothing to delete
120
return;
121
}
122
123
const firstLineIndex = range.startLineNumber - this._startLineNumber;
124
const lastLineIndex = range.endLineNumber - this._startLineNumber;
125
126
if (lastLineIndex < 0) {
127
// this deletion occurs entirely before this block, so we only need to adjust line numbers
128
const deletedLinesCount = lastLineIndex - firstLineIndex;
129
this._startLineNumber -= deletedLinesCount;
130
return;
131
}
132
133
if (firstLineIndex >= this._tokens.length) {
134
// this deletion occurs entirely after this block, so there is nothing to do
135
return;
136
}
137
138
if (firstLineIndex < 0 && lastLineIndex >= this._tokens.length) {
139
// this deletion completely encompasses this block
140
this._startLineNumber = 0;
141
this._tokens = [];
142
return;
143
}
144
145
if (firstLineIndex === lastLineIndex) {
146
// a delete on a single line
147
this._tokens[firstLineIndex] = ContiguousTokensEditing.delete(this._tokens[firstLineIndex], range.startColumn - 1, range.endColumn - 1);
148
return;
149
}
150
151
if (firstLineIndex >= 0) {
152
// The first line survives
153
this._tokens[firstLineIndex] = ContiguousTokensEditing.deleteEnding(this._tokens[firstLineIndex], range.startColumn - 1);
154
155
if (lastLineIndex < this._tokens.length) {
156
// The last line survives
157
const lastLineTokens = ContiguousTokensEditing.deleteBeginning(this._tokens[lastLineIndex], range.endColumn - 1);
158
159
// Take remaining text on last line and append it to remaining text on first line
160
this._tokens[firstLineIndex] = ContiguousTokensEditing.append(this._tokens[firstLineIndex], lastLineTokens);
161
162
// Delete middle lines
163
this._tokens.splice(firstLineIndex + 1, lastLineIndex - firstLineIndex);
164
} else {
165
// The last line does not survive
166
167
// Take remaining text on last line and append it to remaining text on first line
168
this._tokens[firstLineIndex] = ContiguousTokensEditing.append(this._tokens[firstLineIndex], null);
169
170
// Delete lines
171
this._tokens = this._tokens.slice(0, firstLineIndex + 1);
172
}
173
} else {
174
// The first line does not survive
175
176
const deletedBefore = -firstLineIndex;
177
this._startLineNumber -= deletedBefore;
178
179
// Remove beginning from last line
180
this._tokens[lastLineIndex] = ContiguousTokensEditing.deleteBeginning(this._tokens[lastLineIndex], range.endColumn - 1);
181
182
// Delete lines
183
this._tokens = this._tokens.slice(lastLineIndex);
184
}
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 - this._startLineNumber;
195
196
if (lineIndex < 0) {
197
// this insertion occurs before this block, so we only need to adjust line numbers
198
this._startLineNumber += eolCount;
199
return;
200
}
201
202
if (lineIndex >= this._tokens.length) {
203
// this insertion occurs after this block, so there is nothing to do
204
return;
205
}
206
207
if (eolCount === 0) {
208
// Inserting text on one line
209
this._tokens[lineIndex] = ContiguousTokensEditing.insert(this._tokens[lineIndex], position.column - 1, firstLineLength);
210
return;
211
}
212
213
this._tokens[lineIndex] = ContiguousTokensEditing.deleteEnding(this._tokens[lineIndex], position.column - 1);
214
this._tokens[lineIndex] = ContiguousTokensEditing.insert(this._tokens[lineIndex], position.column - 1, firstLineLength);
215
216
this._insertLines(position.lineNumber, eolCount);
217
}
218
219
private _insertLines(insertIndex: number, insertCount: number): void {
220
if (insertCount === 0) {
221
return;
222
}
223
const lineTokens: (Uint32Array | ArrayBuffer | null)[] = [];
224
for (let i = 0; i < insertCount; i++) {
225
lineTokens[i] = null;
226
}
227
this._tokens = arrays.arrayInsert(this._tokens, insertIndex, lineTokens);
228
}
229
}
230
231