Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder.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 { CharCode } from '../../../../base/common/charCode.js';
7
import { IDisposable } from '../../../../base/common/lifecycle.js';
8
import * as strings from '../../../../base/common/strings.js';
9
import { DefaultEndOfLine, ITextBuffer, ITextBufferBuilder, ITextBufferFactory } from '../../model.js';
10
import { StringBuffer, createLineStarts, createLineStartsFast } from './pieceTreeBase.js';
11
import { PieceTreeTextBuffer } from './pieceTreeTextBuffer.js';
12
13
class PieceTreeTextBufferFactory implements ITextBufferFactory {
14
15
constructor(
16
private readonly _chunks: StringBuffer[],
17
private readonly _bom: string,
18
private readonly _cr: number,
19
private readonly _lf: number,
20
private readonly _crlf: number,
21
private readonly _containsRTL: boolean,
22
private readonly _containsUnusualLineTerminators: boolean,
23
private readonly _isBasicASCII: boolean,
24
private readonly _normalizeEOL: boolean
25
) { }
26
27
private _getEOL(defaultEOL: DefaultEndOfLine): '\r\n' | '\n' {
28
const totalEOLCount = this._cr + this._lf + this._crlf;
29
const totalCRCount = this._cr + this._crlf;
30
if (totalEOLCount === 0) {
31
// This is an empty file or a file with precisely one line
32
return (defaultEOL === DefaultEndOfLine.LF ? '\n' : '\r\n');
33
}
34
if (totalCRCount > totalEOLCount / 2) {
35
// More than half of the file contains \r\n ending lines
36
return '\r\n';
37
}
38
// At least one line more ends in \n
39
return '\n';
40
}
41
42
public create(defaultEOL: DefaultEndOfLine): { textBuffer: ITextBuffer; disposable: IDisposable } {
43
const eol = this._getEOL(defaultEOL);
44
const chunks = this._chunks;
45
46
if (this._normalizeEOL &&
47
((eol === '\r\n' && (this._cr > 0 || this._lf > 0))
48
|| (eol === '\n' && (this._cr > 0 || this._crlf > 0)))
49
) {
50
// Normalize pieces
51
for (let i = 0, len = chunks.length; i < len; i++) {
52
const str = chunks[i].buffer.replace(/\r\n|\r|\n/g, eol);
53
const newLineStart = createLineStartsFast(str);
54
chunks[i] = new StringBuffer(str, newLineStart);
55
}
56
}
57
58
const textBuffer = new PieceTreeTextBuffer(chunks, this._bom, eol, this._containsRTL, this._containsUnusualLineTerminators, this._isBasicASCII, this._normalizeEOL);
59
return { textBuffer: textBuffer, disposable: textBuffer };
60
}
61
62
public getFirstLineText(lengthLimit: number): string {
63
return this._chunks[0].buffer.substr(0, lengthLimit).split(/\r\n|\r|\n/)[0];
64
}
65
}
66
67
export class PieceTreeTextBufferBuilder implements ITextBufferBuilder {
68
private readonly chunks: StringBuffer[];
69
private BOM: string;
70
71
private _hasPreviousChar: boolean;
72
private _previousChar: number;
73
private readonly _tmpLineStarts: number[];
74
75
private cr: number;
76
private lf: number;
77
private crlf: number;
78
private containsRTL: boolean;
79
private containsUnusualLineTerminators: boolean;
80
private isBasicASCII: boolean;
81
82
constructor() {
83
this.chunks = [];
84
this.BOM = '';
85
86
this._hasPreviousChar = false;
87
this._previousChar = 0;
88
this._tmpLineStarts = [];
89
90
this.cr = 0;
91
this.lf = 0;
92
this.crlf = 0;
93
this.containsRTL = false;
94
this.containsUnusualLineTerminators = false;
95
this.isBasicASCII = true;
96
}
97
98
public acceptChunk(chunk: string): void {
99
if (chunk.length === 0) {
100
return;
101
}
102
103
if (this.chunks.length === 0) {
104
if (strings.startsWithUTF8BOM(chunk)) {
105
this.BOM = strings.UTF8_BOM_CHARACTER;
106
chunk = chunk.substr(1);
107
}
108
}
109
110
const lastChar = chunk.charCodeAt(chunk.length - 1);
111
if (lastChar === CharCode.CarriageReturn || (lastChar >= 0xD800 && lastChar <= 0xDBFF)) {
112
// last character is \r or a high surrogate => keep it back
113
this._acceptChunk1(chunk.substr(0, chunk.length - 1), false);
114
this._hasPreviousChar = true;
115
this._previousChar = lastChar;
116
} else {
117
this._acceptChunk1(chunk, false);
118
this._hasPreviousChar = false;
119
this._previousChar = lastChar;
120
}
121
}
122
123
private _acceptChunk1(chunk: string, allowEmptyStrings: boolean): void {
124
if (!allowEmptyStrings && chunk.length === 0) {
125
// Nothing to do
126
return;
127
}
128
129
if (this._hasPreviousChar) {
130
this._acceptChunk2(String.fromCharCode(this._previousChar) + chunk);
131
} else {
132
this._acceptChunk2(chunk);
133
}
134
}
135
136
private _acceptChunk2(chunk: string): void {
137
const lineStarts = createLineStarts(this._tmpLineStarts, chunk);
138
139
this.chunks.push(new StringBuffer(chunk, lineStarts.lineStarts));
140
this.cr += lineStarts.cr;
141
this.lf += lineStarts.lf;
142
this.crlf += lineStarts.crlf;
143
144
if (!lineStarts.isBasicASCII) {
145
// this chunk contains non basic ASCII characters
146
this.isBasicASCII = false;
147
if (!this.containsRTL) {
148
this.containsRTL = strings.containsRTL(chunk);
149
}
150
if (!this.containsUnusualLineTerminators) {
151
this.containsUnusualLineTerminators = strings.containsUnusualLineTerminators(chunk);
152
}
153
}
154
}
155
156
public finish(normalizeEOL: boolean = true): PieceTreeTextBufferFactory {
157
this._finish();
158
return new PieceTreeTextBufferFactory(
159
this.chunks,
160
this.BOM,
161
this.cr,
162
this.lf,
163
this.crlf,
164
this.containsRTL,
165
this.containsUnusualLineTerminators,
166
this.isBasicASCII,
167
normalizeEOL
168
);
169
}
170
171
private _finish(): void {
172
if (this.chunks.length === 0) {
173
this._acceptChunk1('', true);
174
}
175
176
if (this._hasPreviousChar) {
177
this._hasPreviousChar = false;
178
// recreate last chunk
179
const lastChunk = this.chunks[this.chunks.length - 1];
180
lastChunk.buffer += String.fromCharCode(this._previousChar);
181
const newLineStarts = createLineStartsFast(lastChunk.buffer);
182
lastChunk.lineStarts = newLineStarts;
183
if (this._previousChar === CharCode.CarriageReturn) {
184
this.cr++;
185
}
186
}
187
}
188
}
189
190