Path: blob/main/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { CharCode } from '../../../../base/common/charCode.js';6import { IDisposable } from '../../../../base/common/lifecycle.js';7import * as strings from '../../../../base/common/strings.js';8import { DefaultEndOfLine, ITextBuffer, ITextBufferBuilder, ITextBufferFactory } from '../../model.js';9import { StringBuffer, createLineStarts, createLineStartsFast } from './pieceTreeBase.js';10import { PieceTreeTextBuffer } from './pieceTreeTextBuffer.js';1112class PieceTreeTextBufferFactory implements ITextBufferFactory {1314constructor(15private readonly _chunks: StringBuffer[],16private readonly _bom: string,17private readonly _cr: number,18private readonly _lf: number,19private readonly _crlf: number,20private readonly _containsRTL: boolean,21private readonly _containsUnusualLineTerminators: boolean,22private readonly _isBasicASCII: boolean,23private readonly _normalizeEOL: boolean24) { }2526private _getEOL(defaultEOL: DefaultEndOfLine): '\r\n' | '\n' {27const totalEOLCount = this._cr + this._lf + this._crlf;28const totalCRCount = this._cr + this._crlf;29if (totalEOLCount === 0) {30// This is an empty file or a file with precisely one line31return (defaultEOL === DefaultEndOfLine.LF ? '\n' : '\r\n');32}33if (totalCRCount > totalEOLCount / 2) {34// More than half of the file contains \r\n ending lines35return '\r\n';36}37// At least one line more ends in \n38return '\n';39}4041public create(defaultEOL: DefaultEndOfLine): { textBuffer: ITextBuffer; disposable: IDisposable } {42const eol = this._getEOL(defaultEOL);43const chunks = this._chunks;4445if (this._normalizeEOL &&46((eol === '\r\n' && (this._cr > 0 || this._lf > 0))47|| (eol === '\n' && (this._cr > 0 || this._crlf > 0)))48) {49// Normalize pieces50for (let i = 0, len = chunks.length; i < len; i++) {51const str = chunks[i].buffer.replace(/\r\n|\r|\n/g, eol);52const newLineStart = createLineStartsFast(str);53chunks[i] = new StringBuffer(str, newLineStart);54}55}5657const textBuffer = new PieceTreeTextBuffer(chunks, this._bom, eol, this._containsRTL, this._containsUnusualLineTerminators, this._isBasicASCII, this._normalizeEOL);58return { textBuffer: textBuffer, disposable: textBuffer };59}6061public getFirstLineText(lengthLimit: number): string {62return this._chunks[0].buffer.substr(0, lengthLimit).split(/\r\n|\r|\n/)[0];63}64}6566export class PieceTreeTextBufferBuilder implements ITextBufferBuilder {67private readonly chunks: StringBuffer[];68private BOM: string;6970private _hasPreviousChar: boolean;71private _previousChar: number;72private readonly _tmpLineStarts: number[];7374private cr: number;75private lf: number;76private crlf: number;77private containsRTL: boolean;78private containsUnusualLineTerminators: boolean;79private isBasicASCII: boolean;8081constructor() {82this.chunks = [];83this.BOM = '';8485this._hasPreviousChar = false;86this._previousChar = 0;87this._tmpLineStarts = [];8889this.cr = 0;90this.lf = 0;91this.crlf = 0;92this.containsRTL = false;93this.containsUnusualLineTerminators = false;94this.isBasicASCII = true;95}9697public acceptChunk(chunk: string): void {98if (chunk.length === 0) {99return;100}101102if (this.chunks.length === 0) {103if (strings.startsWithUTF8BOM(chunk)) {104this.BOM = strings.UTF8_BOM_CHARACTER;105chunk = chunk.substr(1);106}107}108109const lastChar = chunk.charCodeAt(chunk.length - 1);110if (lastChar === CharCode.CarriageReturn || (lastChar >= 0xD800 && lastChar <= 0xDBFF)) {111// last character is \r or a high surrogate => keep it back112this._acceptChunk1(chunk.substr(0, chunk.length - 1), false);113this._hasPreviousChar = true;114this._previousChar = lastChar;115} else {116this._acceptChunk1(chunk, false);117this._hasPreviousChar = false;118this._previousChar = lastChar;119}120}121122private _acceptChunk1(chunk: string, allowEmptyStrings: boolean): void {123if (!allowEmptyStrings && chunk.length === 0) {124// Nothing to do125return;126}127128if (this._hasPreviousChar) {129this._acceptChunk2(String.fromCharCode(this._previousChar) + chunk);130} else {131this._acceptChunk2(chunk);132}133}134135private _acceptChunk2(chunk: string): void {136const lineStarts = createLineStarts(this._tmpLineStarts, chunk);137138this.chunks.push(new StringBuffer(chunk, lineStarts.lineStarts));139this.cr += lineStarts.cr;140this.lf += lineStarts.lf;141this.crlf += lineStarts.crlf;142143if (!lineStarts.isBasicASCII) {144// this chunk contains non basic ASCII characters145this.isBasicASCII = false;146if (!this.containsRTL) {147this.containsRTL = strings.containsRTL(chunk);148}149if (!this.containsUnusualLineTerminators) {150this.containsUnusualLineTerminators = strings.containsUnusualLineTerminators(chunk);151}152}153}154155public finish(normalizeEOL: boolean = true): PieceTreeTextBufferFactory {156this._finish();157return new PieceTreeTextBufferFactory(158this.chunks,159this.BOM,160this.cr,161this.lf,162this.crlf,163this.containsRTL,164this.containsUnusualLineTerminators,165this.isBasicASCII,166normalizeEOL167);168}169170private _finish(): void {171if (this.chunks.length === 0) {172this._acceptChunk1('', true);173}174175if (this._hasPreviousChar) {176this._hasPreviousChar = false;177// recreate last chunk178const lastChunk = this.chunks[this.chunks.length - 1];179lastChunk.buffer += String.fromCharCode(this._previousChar);180const newLineStarts = createLineStartsFast(lastChunk.buffer);181lastChunk.lineStarts = newLineStarts;182if (this._previousChar === CharCode.CarriageReturn) {183this.cr++;184}185}186}187}188189190