From 805d0785aaa50e7de44f6c71b7ef548fc88c7094 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 28 Mar 2018 11:41:58 +0200 Subject: [PATCH] Remove ChunksTextBuffer --- .../model/chunksTextBuffer/bufferPiece.ts | 323 ---- .../chunksTextBuffer/chunksTextBuffer.ts | 1526 ----------------- .../chunksTextBufferBuilder.ts | 186 -- src/vs/editor/common/model/textModel.ts | 7 +- .../common/model/benchmark/benchmarkUtils.ts | 3 +- .../chunksTextBuffer/bufferPiece.test.ts | 61 - 6 files changed, 2 insertions(+), 2104 deletions(-) delete mode 100644 src/vs/editor/common/model/chunksTextBuffer/bufferPiece.ts delete mode 100644 src/vs/editor/common/model/chunksTextBuffer/chunksTextBuffer.ts delete mode 100644 src/vs/editor/common/model/chunksTextBuffer/chunksTextBufferBuilder.ts delete mode 100644 src/vs/editor/test/common/model/chunksTextBuffer/bufferPiece.test.ts diff --git a/src/vs/editor/common/model/chunksTextBuffer/bufferPiece.ts b/src/vs/editor/common/model/chunksTextBuffer/bufferPiece.ts deleted file mode 100644 index 53063409aea..00000000000 --- a/src/vs/editor/common/model/chunksTextBuffer/bufferPiece.ts +++ /dev/null @@ -1,323 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import { CharCode } from 'vs/base/common/charCode'; - -export class LeafOffsetLenEdit { - constructor( - public readonly start: number, - public readonly length: number, - public readonly text: string - ) { } -} - -export class BufferPiece { - private readonly _str: string; - public get text(): string { return this._str; } - - private readonly _lineStarts: Uint32Array; - - constructor(str: string, lineStarts: Uint32Array = null) { - this._str = str; - if (lineStarts === null) { - this._lineStarts = createLineStartsFast(str); - } else { - this._lineStarts = lineStarts; - } - } - - public length(): number { - return this._str.length; - } - - public newLineCount(): number { - return this._lineStarts.length; - } - - public lineStartFor(relativeLineIndex: number): number { - return this._lineStarts[relativeLineIndex]; - } - - public charCodeAt(index: number): number { - return this._str.charCodeAt(index); - } - - public substr(from: number, length: number): string { - return this._str.substr(from, length); - } - - public findLineStartBeforeOffset(offset: number): number { - if (this._lineStarts.length === 0 || offset < this._lineStarts[0]) { - return -1; - } - - let low = 0, high = this._lineStarts.length - 1; - - while (low < high) { - let mid = low + Math.ceil((high - low) / 2); - let lineStart = this._lineStarts[mid]; - - if (offset === lineStart) { - return mid; - } else if (offset < lineStart) { - high = mid - 1; - } else { - low = mid; - } - } - - return low; - } - - public findLineFirstNonWhitespaceIndex(searchStartOffset: number): number { - for (let i = searchStartOffset, len = this._str.length; i < len; i++) { - const chCode = this._str.charCodeAt(i); - if (chCode === CharCode.CarriageReturn || chCode === CharCode.LineFeed) { - // Reached EOL - return -2; - } - if (chCode !== CharCode.Space && chCode !== CharCode.Tab) { - return i; - } - } - return -1; - } - - public findLineLastNonWhitespaceIndex(searchStartOffset: number): number { - for (let i = searchStartOffset - 1; i >= 0; i--) { - const chCode = this._str.charCodeAt(i); - if (chCode === CharCode.CarriageReturn || chCode === CharCode.LineFeed) { - // Reached EOL - return -2; - } - if (chCode !== CharCode.Space && chCode !== CharCode.Tab) { - return i; - } - } - return -1; - } - - public static normalizeEOL(target: BufferPiece, eol: '\r\n' | '\n'): BufferPiece { - return new BufferPiece(target._str.replace(/\r\n|\r|\n/g, eol)); - } - - public static deleteLastChar(target: BufferPiece): BufferPiece { - const targetCharsLength = target.length(); - const targetLineStartsLength = target.newLineCount(); - const targetLineStarts = target._lineStarts; - - let newLineStartsLength; - if (targetLineStartsLength > 0 && targetLineStarts[targetLineStartsLength - 1] === targetCharsLength) { - newLineStartsLength = targetLineStartsLength - 1; - } else { - newLineStartsLength = targetLineStartsLength; - } - - let newLineStarts = new Uint32Array(newLineStartsLength); - newLineStarts.set(targetLineStarts); - - return new BufferPiece( - target._str.substr(0, targetCharsLength - 1), - newLineStarts - ); - } - - public static insertFirstChar(target: BufferPiece, character: number): BufferPiece { - const targetLineStartsLength = target.newLineCount(); - const targetLineStarts = target._lineStarts; - const insertLineStart = ((character === CharCode.CarriageReturn && (targetLineStartsLength === 0 || targetLineStarts[0] !== 1 || target.charCodeAt(0) !== CharCode.LineFeed)) || (character === CharCode.LineFeed)); - - const newLineStartsLength = (insertLineStart ? targetLineStartsLength + 1 : targetLineStartsLength); - let newLineStarts = new Uint32Array(newLineStartsLength); - - if (insertLineStart) { - newLineStarts[0] = 1; - for (let i = 0; i < targetLineStartsLength; i++) { - newLineStarts[i + 1] = targetLineStarts[i] + 1; - } - } else { - for (let i = 0; i < targetLineStartsLength; i++) { - newLineStarts[i] = targetLineStarts[i] + 1; - } - } - - return new BufferPiece( - String.fromCharCode(character) + target._str, - newLineStarts - ); - } - - public static join(first: BufferPiece, second: BufferPiece): BufferPiece { - const firstCharsLength = first._str.length; - - const firstLineStartsLength = first._lineStarts.length; - const secondLineStartsLength = second._lineStarts.length; - - const firstLineStarts = first._lineStarts; - const secondLineStarts = second._lineStarts; - - const newLineStartsLength = firstLineStartsLength + secondLineStartsLength; - let newLineStarts = new Uint32Array(newLineStartsLength); - newLineStarts.set(firstLineStarts, 0); - for (let i = 0; i < secondLineStartsLength; i++) { - newLineStarts[i + firstLineStartsLength] = secondLineStarts[i] + firstCharsLength; - } - - return new BufferPiece(first._str + second._str, newLineStarts); - } - - public static replaceOffsetLen(target: BufferPiece, edits: LeafOffsetLenEdit[], idealLeafLength: number, maxLeafLength: number, result: BufferPiece[]): void { - const editsSize = edits.length; - const originalCharsLength = target.length(); - if (editsSize === 1 && edits[0].text.length === 0 && edits[0].start === 0 && edits[0].length === originalCharsLength) { - // special case => deleting everything - return; - } - - let pieces: string[] = new Array(2 * editsSize + 1); - let originalFromIndex = 0; - let piecesTextLength = 0; - for (let i = 0; i < editsSize; i++) { - const edit = edits[i]; - - const originalText = target._str.substr(originalFromIndex, edit.start - originalFromIndex); - pieces[2 * i] = originalText; - piecesTextLength += originalText.length; - - originalFromIndex = edit.start + edit.length; - pieces[2 * i + 1] = edit.text; - piecesTextLength += edit.text.length; - } - - // maintain the chars that survive to the right of the last edit - let text = target._str.substr(originalFromIndex, originalCharsLength - originalFromIndex); - pieces[2 * editsSize] = text; - piecesTextLength += text.length; - - let targetDataLength = piecesTextLength > maxLeafLength ? idealLeafLength : piecesTextLength; - let targetDataOffset = 0; - - let data: string = ''; - - for (let pieceIndex = 0, pieceCount = pieces.length; pieceIndex < pieceCount; pieceIndex++) { - const pieceText = pieces[pieceIndex]; - const pieceLength = pieceText.length; - if (pieceLength === 0) { - continue; - } - - let pieceOffset = 0; - while (pieceOffset < pieceLength) { - if (targetDataOffset >= targetDataLength) { - result.push(new BufferPiece(data)); - targetDataLength = piecesTextLength > maxLeafLength ? idealLeafLength : piecesTextLength; - targetDataOffset = 0; - data = ''; - } - - let writingCnt = min(pieceLength - pieceOffset, targetDataLength - targetDataOffset); - data += pieceText.substr(pieceOffset, writingCnt); - pieceOffset += writingCnt; - targetDataOffset += writingCnt; - piecesTextLength -= writingCnt; - - // check that the buffer piece does not end in a \r or high surrogate - if (targetDataOffset === targetDataLength && piecesTextLength > 0) { - const lastChar = data.charCodeAt(targetDataLength - 1); - if (lastChar === CharCode.CarriageReturn || (0xD800 <= lastChar && lastChar <= 0xDBFF)) { - // move lastChar over to next buffer piece - targetDataLength -= 1; - pieceOffset -= 1; - targetDataOffset -= 1; - piecesTextLength += 1; - data = data.substr(0, data.length - 1); - } - } - } - } - - result.push(new BufferPiece(data)); - } -} - -function min(a: number, b: number): number { - return (a < b ? a : b); -} - -export function createUint32Array(arr: number[]): Uint32Array { - let r = new Uint32Array(arr.length); - r.set(arr, 0); - return r; -} - -export class LineStarts { - constructor( - public readonly lineStarts: Uint32Array, - public readonly cr: number, - public readonly lf: number, - public readonly crlf: number, - public readonly isBasicASCII: boolean - ) { } -} - -export function createLineStartsFast(str: string): Uint32Array { - let r: number[] = [], rLength = 0; - for (let i = 0, len = str.length; i < len; i++) { - const chr = str.charCodeAt(i); - - if (chr === CharCode.CarriageReturn) { - if (i + 1 < len && str.charCodeAt(i + 1) === CharCode.LineFeed) { - // \r\n... case - r[rLength++] = i + 2; - i++; // skip \n - } else { - // \r... case - r[rLength++] = i + 1; - } - } else if (chr === CharCode.LineFeed) { - r[rLength++] = i + 1; - } - } - return createUint32Array(r); -} - -export function createLineStarts(r: number[], str: string): LineStarts { - r.length = 0; - - let rLength = 0; - let cr = 0, lf = 0, crlf = 0; - let isBasicASCII = true; - for (let i = 0, len = str.length; i < len; i++) { - const chr = str.charCodeAt(i); - - if (chr === CharCode.CarriageReturn) { - if (i + 1 < len && str.charCodeAt(i + 1) === CharCode.LineFeed) { - // \r\n... case - crlf++; - r[rLength++] = i + 2; - i++; // skip \n - } else { - cr++; - // \r... case - r[rLength++] = i + 1; - } - } else if (chr === CharCode.LineFeed) { - lf++; - r[rLength++] = i + 1; - } else { - if (isBasicASCII) { - if (chr !== CharCode.Tab && (chr < 32 || chr > 126)) { - isBasicASCII = false; - } - } - } - } - - const result = new LineStarts(createUint32Array(r), cr, lf, crlf, isBasicASCII); - r.length = 0; - - return result; -} diff --git a/src/vs/editor/common/model/chunksTextBuffer/chunksTextBuffer.ts b/src/vs/editor/common/model/chunksTextBuffer/chunksTextBuffer.ts deleted file mode 100644 index 046f3387ea5..00000000000 --- a/src/vs/editor/common/model/chunksTextBuffer/chunksTextBuffer.ts +++ /dev/null @@ -1,1526 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import { CharCode } from 'vs/base/common/charCode'; -import { ITextBuffer, EndOfLinePreference, IIdentifiedSingleEditOperation, ApplyEditsResult, ISingleEditOperationIdentifier, IInternalModelContentChange } from 'vs/editor/common/model'; -import { BufferPiece, LeafOffsetLenEdit } from 'vs/editor/common/model/chunksTextBuffer/bufferPiece'; -import { Position } from 'vs/editor/common/core/position'; -import { Range } from 'vs/editor/common/core/range'; -import * as strings from 'vs/base/common/strings'; -import { ITextSnapshot } from 'vs/platform/files/common/files'; - -export interface IValidatedEditOperation { - sortIndex: number; - identifier: ISingleEditOperationIdentifier; - range: Range; - rangeOffset: number; - rangeLength: number; - lines: string[]; - forceMoveMarkers: boolean; - isAutoWhitespaceEdit: boolean; -} - -export class ChunksTextBuffer implements ITextBuffer { - - private _BOM: string; - private _actual: Buffer; - private _mightContainRTL: boolean; - private _mightContainNonBasicASCII: boolean; - - constructor(pieces: BufferPiece[], _averageChunkSize: number, BOM: string, eol: '\r\n' | '\n', containsRTL: boolean, isBasicASCII: boolean) { - this._BOM = BOM; - const averageChunkSize = Math.floor(Math.min(65536.0, Math.max(128.0, _averageChunkSize))); - const delta = Math.floor(averageChunkSize / 3); - const min = averageChunkSize - delta; - const max = 2 * min; - this._actual = new Buffer(pieces, min, max, eol); - this._mightContainRTL = containsRTL; - this._mightContainNonBasicASCII = !isBasicASCII; - } - - equals(other: ITextBuffer): boolean { - if (!(other instanceof ChunksTextBuffer)) { - return false; - } - return this._actual.equals(other._actual); - } - mightContainRTL(): boolean { - return this._mightContainRTL; - } - mightContainNonBasicASCII(): boolean { - return this._mightContainNonBasicASCII; - } - getBOM(): string { - return this._BOM; - } - getEOL(): string { - return this._actual.getEOL(); - } - getOffsetAt(lineNumber: number, column: number): number { - return this._actual.convertPositionToOffset(lineNumber, column); - } - getPositionAt(offset: number): Position { - return this._actual.convertOffsetToPosition(offset); - } - getRangeAt(offset: number, length: number): Range { - return this._actual.convertOffsetLenToRange(offset, length); - } - getValueInRange(range: Range, eol: EndOfLinePreference): string { - if (range.isEmpty()) { - return ''; - } - - const text = this._actual.getValueInRange(range); - switch (eol) { - case EndOfLinePreference.TextDefined: - return text; - case EndOfLinePreference.LF: - if (this.getEOL() === '\n') { - return text; - } else { - return text.replace(/\r\n/g, '\n'); - } - case EndOfLinePreference.CRLF: - if (this.getEOL() === '\r\n') { - return text; - } else { - return text.replace(/\n/g, '\r\n'); - } - } - return null; - } - - public createSnapshot(preserveBOM: boolean): ITextSnapshot { - return this._actual.createSnapshot(preserveBOM ? this._BOM : ''); - } - - getValueLengthInRange(range: Range, eol: EndOfLinePreference): number { - if (range.isEmpty()) { - return 0; - } - const eolCount = range.endLineNumber - range.startLineNumber; - const result = this._actual.getValueLengthInRange(range); - switch (eol) { - case EndOfLinePreference.TextDefined: - return result; - case EndOfLinePreference.LF: - if (this.getEOL() === '\n') { - return result; - } else { - return result - eolCount; // \r\n => \n - } - case EndOfLinePreference.CRLF: - if (this.getEOL() === '\r\n') { - return result; - } else { - return result + eolCount; // \n => \r\n - } - } - return 0; - } - - public getLength(): number { - return this._actual.getLength(); - } - - getLineCount(): number { - return this._actual.getLineCount(); - } - - getLinesContent(): string[] { - return this._actual.getLinesContent(); - } - - getLineContent(lineNumber: number): string { - return this._actual.getLineContent(lineNumber); - } - - getLineCharCode(lineNumber: number, index: number): number { - return this._actual.getLineCharCode(lineNumber, index); - } - - getLineLength(lineNumber: number): number { - return this._actual.getLineLength(lineNumber); - } - - getLineFirstNonWhitespaceColumn(lineNumber: number): number { - const result = this._actual.getLineFirstNonWhitespaceIndex(lineNumber); - if (result === -1) { - return 0; - } - return result + 1; - } - getLineLastNonWhitespaceColumn(lineNumber: number): number { - const result = this._actual.getLineLastNonWhitespaceIndex(lineNumber); - if (result === -1) { - return 0; - } - return result + 1; - } - setEOL(newEOL: '\r\n' | '\n'): void { - if (this.getEOL() === newEOL) { - // nothing to do... - return; - } - this._actual.setEOL(newEOL); - } - - private static _sortOpsAscending(a: IValidatedEditOperation, b: IValidatedEditOperation): number { - let r = Range.compareRangesUsingEnds(a.range, b.range); - if (r === 0) { - return a.sortIndex - b.sortIndex; - } - return r; - } - - private static _sortOpsDescending(a: IValidatedEditOperation, b: IValidatedEditOperation): number { - let r = Range.compareRangesUsingEnds(a.range, b.range); - if (r === 0) { - return b.sortIndex - a.sortIndex; - } - return -r; - } - - applyEdits(rawOperations: IIdentifiedSingleEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult { - if (rawOperations.length === 0) { - return new ApplyEditsResult([], [], []); - } - - let mightContainRTL = this._mightContainRTL; - let mightContainNonBasicASCII = this._mightContainNonBasicASCII; - let canReduceOperations = true; - - let operations: IValidatedEditOperation[] = []; - for (let i = 0; i < rawOperations.length; i++) { - let op = rawOperations[i]; - if (canReduceOperations && op._isTracked) { - canReduceOperations = false; - } - let validatedRange = op.range; - if (!mightContainRTL && op.text) { - // check if the new inserted text contains RTL - mightContainRTL = strings.containsRTL(op.text); - } - if (!mightContainNonBasicASCII && op.text) { - mightContainNonBasicASCII = !strings.isBasicASCII(op.text); - } - operations[i] = { - sortIndex: i, - identifier: op.identifier || null, - range: validatedRange, - rangeOffset: this.getOffsetAt(validatedRange.startLineNumber, validatedRange.startColumn), - rangeLength: this.getValueLengthInRange(validatedRange, EndOfLinePreference.TextDefined), - lines: op.text ? op.text.split(/\r\n|\r|\n/) : null, - forceMoveMarkers: op.forceMoveMarkers || false, - isAutoWhitespaceEdit: op.isAutoWhitespaceEdit || false - }; - } - - // Sort operations ascending - operations.sort(ChunksTextBuffer._sortOpsAscending); - - for (let i = 0, count = operations.length - 1; i < count; i++) { - let rangeEnd = operations[i].range.getEndPosition(); - let nextRangeStart = operations[i + 1].range.getStartPosition(); - - if (nextRangeStart.isBefore(rangeEnd)) { - // overlapping ranges - throw new Error('Overlapping ranges are not allowed!'); - } - } - - if (canReduceOperations) { - operations = this._reduceOperations(operations); - } - - // Delta encode operations - let reverseRanges = ChunksTextBuffer._getInverseEditRanges(operations); - let newTrimAutoWhitespaceCandidates: { lineNumber: number, oldContent: string }[] = []; - - for (let i = 0; i < operations.length; i++) { - let op = operations[i]; - let reverseRange = reverseRanges[i]; - - if (recordTrimAutoWhitespace && op.isAutoWhitespaceEdit && op.range.isEmpty()) { - // Record already the future line numbers that might be auto whitespace removal candidates on next edit - for (let lineNumber = reverseRange.startLineNumber; lineNumber <= reverseRange.endLineNumber; lineNumber++) { - let currentLineContent = ''; - if (lineNumber === reverseRange.startLineNumber) { - currentLineContent = this.getLineContent(op.range.startLineNumber); - if (strings.firstNonWhitespaceIndex(currentLineContent) !== -1) { - continue; - } - } - newTrimAutoWhitespaceCandidates.push({ lineNumber: lineNumber, oldContent: currentLineContent }); - } - } - } - - let reverseOperations: IIdentifiedSingleEditOperation[] = []; - for (let i = 0; i < operations.length; i++) { - let op = operations[i]; - let reverseRange = reverseRanges[i]; - - reverseOperations[i] = { - identifier: op.identifier, - range: reverseRange, - text: this.getValueInRange(op.range, EndOfLinePreference.TextDefined), - forceMoveMarkers: op.forceMoveMarkers - }; - } - - this._mightContainRTL = mightContainRTL; - this._mightContainNonBasicASCII = mightContainNonBasicASCII; - - const contentChanges = this._doApplyEdits(operations); - - let trimAutoWhitespaceLineNumbers: number[] = null; - if (recordTrimAutoWhitespace && newTrimAutoWhitespaceCandidates.length > 0) { - // sort line numbers auto whitespace removal candidates for next edit descending - newTrimAutoWhitespaceCandidates.sort((a, b) => b.lineNumber - a.lineNumber); - - trimAutoWhitespaceLineNumbers = []; - for (let i = 0, len = newTrimAutoWhitespaceCandidates.length; i < len; i++) { - let lineNumber = newTrimAutoWhitespaceCandidates[i].lineNumber; - if (i > 0 && newTrimAutoWhitespaceCandidates[i - 1].lineNumber === lineNumber) { - // Do not have the same line number twice - continue; - } - - let prevContent = newTrimAutoWhitespaceCandidates[i].oldContent; - let lineContent = this.getLineContent(lineNumber); - - if (lineContent.length === 0 || lineContent === prevContent || strings.firstNonWhitespaceIndex(lineContent) !== -1) { - continue; - } - - trimAutoWhitespaceLineNumbers.push(lineNumber); - } - } - - return new ApplyEditsResult( - reverseOperations, - contentChanges, - trimAutoWhitespaceLineNumbers - ); - } - - /** - * Transform operations such that they represent the same logic edit, - * but that they also do not cause OOM crashes. - */ - private _reduceOperations(operations: IValidatedEditOperation[]): IValidatedEditOperation[] { - if (operations.length < 1000) { - // We know from empirical testing that a thousand edits work fine regardless of their shape. - return operations; - } - - // At one point, due to how events are emitted and how each operation is handled, - // some operations can trigger a high ammount of temporary string allocations, - // that will immediately get edited again. - // e.g. a formatter inserting ridiculous ammounts of \n on a model with a single line - // Therefore, the strategy is to collapse all the operations into a huge single edit operation - return [this._toSingleEditOperation(operations)]; - } - - _toSingleEditOperation(operations: IValidatedEditOperation[]): IValidatedEditOperation { - let forceMoveMarkers = false, - firstEditRange = operations[0].range, - lastEditRange = operations[operations.length - 1].range, - entireEditRange = new Range(firstEditRange.startLineNumber, firstEditRange.startColumn, lastEditRange.endLineNumber, lastEditRange.endColumn), - lastEndLineNumber = firstEditRange.startLineNumber, - lastEndColumn = firstEditRange.startColumn, - result: string[] = []; - - for (let i = 0, len = operations.length; i < len; i++) { - let operation = operations[i], - range = operation.range; - - forceMoveMarkers = forceMoveMarkers || operation.forceMoveMarkers; - - // (1) -- Push old text - for (let lineNumber = lastEndLineNumber; lineNumber < range.startLineNumber; lineNumber++) { - if (lineNumber === lastEndLineNumber) { - result.push(this.getLineContent(lineNumber).substring(lastEndColumn - 1)); - } else { - result.push('\n'); - result.push(this.getLineContent(lineNumber)); - } - } - - if (range.startLineNumber === lastEndLineNumber) { - result.push(this.getLineContent(range.startLineNumber).substring(lastEndColumn - 1, range.startColumn - 1)); - } else { - result.push('\n'); - result.push(this.getLineContent(range.startLineNumber).substring(0, range.startColumn - 1)); - } - - // (2) -- Push new text - if (operation.lines) { - for (let j = 0, lenJ = operation.lines.length; j < lenJ; j++) { - if (j !== 0) { - result.push('\n'); - } - result.push(operation.lines[j]); - } - } - - lastEndLineNumber = operation.range.endLineNumber; - lastEndColumn = operation.range.endColumn; - } - - return { - sortIndex: 0, - identifier: operations[0].identifier, - range: entireEditRange, - rangeOffset: this.getOffsetAt(entireEditRange.startLineNumber, entireEditRange.startColumn), - rangeLength: this.getValueLengthInRange(entireEditRange, EndOfLinePreference.TextDefined), - lines: result.join('').split('\n'), - forceMoveMarkers: forceMoveMarkers, - isAutoWhitespaceEdit: false - }; - } - - private _doApplyEdits(operations: IValidatedEditOperation[]): IInternalModelContentChange[] { - - // Sort operations descending - operations.sort(ChunksTextBuffer._sortOpsDescending); - - let contentChanges: IInternalModelContentChange[] = []; - let edits: OffsetLenEdit[] = []; - - for (let i = 0, len = operations.length; i < len; i++) { - const op = operations[i]; - - const text = (op.lines ? op.lines.join(this.getEOL()) : ''); - edits[i] = new OffsetLenEdit(op.sortIndex, op.rangeOffset, op.rangeLength, text); - - const startLineNumber = op.range.startLineNumber; - const startColumn = op.range.startColumn; - const endLineNumber = op.range.endLineNumber; - const endColumn = op.range.endColumn; - - if (startLineNumber === endLineNumber && startColumn === endColumn && (!op.lines || op.lines.length === 0)) { - // no-op - continue; - } - - contentChanges.push({ - range: op.range, - rangeLength: op.rangeLength, - text: text, - rangeOffset: op.rangeOffset, - forceMoveMarkers: op.forceMoveMarkers - }); - } - - this._actual.replaceOffsetLen(edits); - - return contentChanges; - } - - /** - * Assumes `operations` are validated and sorted ascending - */ - public static _getInverseEditRanges(operations: IValidatedEditOperation[]): Range[] { - let result: Range[] = []; - - let prevOpEndLineNumber: number; - let prevOpEndColumn: number; - let prevOp: IValidatedEditOperation = null; - for (let i = 0, len = operations.length; i < len; i++) { - let op = operations[i]; - - let startLineNumber: number; - let startColumn: number; - - if (prevOp) { - if (prevOp.range.endLineNumber === op.range.startLineNumber) { - startLineNumber = prevOpEndLineNumber; - startColumn = prevOpEndColumn + (op.range.startColumn - prevOp.range.endColumn); - } else { - startLineNumber = prevOpEndLineNumber + (op.range.startLineNumber - prevOp.range.endLineNumber); - startColumn = op.range.startColumn; - } - } else { - startLineNumber = op.range.startLineNumber; - startColumn = op.range.startColumn; - } - - let resultRange: Range; - - if (op.lines && op.lines.length > 0) { - // the operation inserts something - let lineCount = op.lines.length; - let firstLine = op.lines[0]; - let lastLine = op.lines[lineCount - 1]; - - if (lineCount === 1) { - // single line insert - resultRange = new Range(startLineNumber, startColumn, startLineNumber, startColumn + firstLine.length); - } else { - // multi line insert - resultRange = new Range(startLineNumber, startColumn, startLineNumber + lineCount - 1, lastLine.length + 1); - } - } else { - // There is nothing to insert - resultRange = new Range(startLineNumber, startColumn, startLineNumber, startColumn); - } - - prevOpEndLineNumber = resultRange.endLineNumber; - prevOpEndColumn = resultRange.endColumn; - - result.push(resultRange); - prevOp = op; - } - - return result; - } -} - - -class BufferNodes { - - public length: Uint32Array; - public newLineCount: Uint32Array; - - constructor(count: number) { - this.length = new Uint32Array(count); - this.newLineCount = new Uint32Array(count); - } - -} - -class BufferCursor { - constructor( - public offset: number, - public leafIndex: number, - public leafStartOffset: number, - public leafStartNewLineCount: number - ) { } - - public set(offset: number, leafIndex: number, leafStartOffset: number, leafStartNewLineCount: number) { - this.offset = offset; - this.leafIndex = leafIndex; - this.leafStartOffset = leafStartOffset; - this.leafStartNewLineCount = leafStartNewLineCount; - } -} - -class OffsetLenEdit { - constructor( - public readonly initialIndex: number, - public readonly offset: number, - public length: number, - public text: string - ) { } -} - -class InternalOffsetLenEdit { - constructor( - public readonly startLeafIndex: number, - public readonly startInnerOffset: number, - public readonly endLeafIndex: number, - public readonly endInnerOffset: number, - public text: string - ) { } -} - -class LeafReplacement { - constructor( - public readonly startLeafIndex: number, - public readonly endLeafIndex: number, - public readonly replacements: BufferPiece[] - ) { } -} - -const BUFFER_CURSOR_POOL_SIZE = 10; -const BufferCursorPool = new class { - private _pool: BufferCursor[]; - private _len: number; - - constructor() { - this._pool = []; - for (let i = 0; i < BUFFER_CURSOR_POOL_SIZE; i++) { - this._pool[i] = new BufferCursor(0, 0, 0, 0); - } - this._len = this._pool.length; - } - - public put(cursor: BufferCursor): void { - if (this._len > this._pool.length) { - // oh, well - return; - } - this._pool[this._len++] = cursor; - } - - public take(): BufferCursor { - if (this._len === 0) { - // oh, well - console.log(`insufficient BufferCursor pool`); - return new BufferCursor(0, 0, 0, 0); - } - const result = this._pool[this._len - 1]; - this._pool[this._len--] = null; - return result; - } -}; - -class BufferSnapshot implements ITextSnapshot { - - private readonly _pieces: BufferPiece[]; - private readonly _piecesLength: number; - private readonly _BOM: string; - private _piecesIndex: number; - - constructor(pieces: BufferPiece[], BOM: string) { - this._pieces = pieces; - this._piecesLength = this._pieces.length; - this._BOM = BOM; - this._piecesIndex = 0; - } - - public read(): string { - if (this._piecesIndex >= this._piecesLength) { - return null; - } - - let result: string = null; - if (this._piecesIndex === 0) { - result = this._BOM + this._pieces[this._piecesIndex].text; - } else { - result = this._pieces[this._piecesIndex].text; - } - - this._piecesIndex++; - return result; - } -} - -class Buffer { - - private _minLeafLength: number; - private _maxLeafLength: number; - private _idealLeafLength: number; - - private _eol: '\r\n' | '\n'; - private _eolLength: number; - - private _leafs: BufferPiece[]; - private _nodes: BufferNodes; - private _nodesCount: number; - private _leafsStart: number; - private _leafsEnd: number; - - constructor(pieces: BufferPiece[], minLeafLength: number, maxLeafLength: number, eol: '\r\n' | '\n') { - if (!(2 * minLeafLength >= maxLeafLength)) { - throw new Error(`assertion violation`); - } - - this._minLeafLength = minLeafLength; - this._maxLeafLength = maxLeafLength; - this._idealLeafLength = (minLeafLength + maxLeafLength) >>> 1; - - this._eol = eol; - this._eolLength = this._eol.length; - - this._leafs = pieces; - this._nodes = null; - this._nodesCount = 0; - this._leafsStart = 0; - this._leafsEnd = 0; - - this._rebuildNodes(); - } - - equals(other: Buffer): boolean { - return Buffer.equals(this, other); - } - - private static equals(a: Buffer, b: Buffer): boolean { - const aLength = a.getLength(); - const bLength = b.getLength(); - if (aLength !== bLength) { - return false; - } - if (a.getLineCount() !== b.getLineCount()) { - return false; - } - - let remaining = aLength; - let aLeafIndex = -1, aLeaf = null, aLeafLength = 0, aLeafRemaining = 0; - let bLeafIndex = -1, bLeaf = null, bLeafLength = 0, bLeafRemaining = 0; - - while (remaining > 0) { - if (aLeafRemaining === 0) { - aLeafIndex++; - aLeaf = a._leafs[aLeafIndex]; - aLeafLength = aLeaf.length(); - aLeafRemaining = aLeafLength; - } - - if (bLeafRemaining === 0) { - bLeafIndex++; - bLeaf = b._leafs[bLeafIndex]; - bLeafLength = bLeaf.length(); - bLeafRemaining = bLeafLength; - } - - let consuming = Math.min(aLeafRemaining, bLeafRemaining); - - let aStr = aLeaf.substr(aLeafLength - aLeafRemaining, consuming); - let bStr = bLeaf.substr(bLeafLength - bLeafRemaining, consuming); - - if (aStr !== bStr) { - return false; - } - - remaining -= consuming; - aLeafRemaining -= consuming; - bLeafRemaining -= consuming; - } - - return true; - } - - public getEOL(): string { - return this._eol; - } - - private _rebuildNodes() { - const leafsCount = this._leafs.length; - - this._nodesCount = (1 << log2(leafsCount)); - this._leafsStart = this._nodesCount; - this._leafsEnd = this._leafsStart + leafsCount; - - this._nodes = new BufferNodes(this._nodesCount); - for (let i = this._nodesCount - 1; i >= 1; i--) { - this._updateSingleNode(i); - } - } - - private _updateSingleNode(nodeIndex: number): void { - const left = LEFT_CHILD(nodeIndex); - const right = RIGHT_CHILD(nodeIndex); - - let length = 0; - let newLineCount = 0; - - if (this.IS_NODE(left)) { - length += this._nodes.length[left]; - newLineCount += this._nodes.newLineCount[left]; - } else if (this.IS_LEAF(left)) { - const leaf = this._leafs[this.NODE_TO_LEAF_INDEX(left)]; - length += leaf.length(); - newLineCount += leaf.newLineCount(); - } - - if (this.IS_NODE(right)) { - length += this._nodes.length[right]; - newLineCount += this._nodes.newLineCount[right]; - } else if (this.IS_LEAF(right)) { - const leaf = this._leafs[this.NODE_TO_LEAF_INDEX(right)]; - length += leaf.length(); - newLineCount += leaf.newLineCount(); - } - - this._nodes.length[nodeIndex] = length; - this._nodes.newLineCount[nodeIndex] = newLineCount; - } - - private _findOffset(offset: number, result: BufferCursor): boolean { - if (offset > this._nodes.length[1]) { - return false; - } - - let it = 1; - let searchOffset = offset; - let leafStartOffset = 0; - let leafStartNewLineCount = 0; - while (!this.IS_LEAF(it)) { - const left = LEFT_CHILD(it); - const right = RIGHT_CHILD(it); - - let leftNewLineCount = 0; - let leftLength = 0; - if (this.IS_NODE(left)) { - leftNewLineCount = this._nodes.newLineCount[left]; - leftLength = this._nodes.length[left]; - } else if (this.IS_LEAF(left)) { - const leaf = this._leafs[this.NODE_TO_LEAF_INDEX(left)]; - leftNewLineCount = leaf.newLineCount(); - leftLength = leaf.length(); - } - - let rightLength = 0; - if (this.IS_NODE(right)) { - rightLength += this._nodes.length[right]; - } else if (this.IS_LEAF(right)) { - rightLength += this._leafs[this.NODE_TO_LEAF_INDEX(right)].length(); - } - - if (searchOffset < leftLength || rightLength === 0) { - // go left - it = left; - } else { - // go right - searchOffset -= leftLength; - leafStartOffset += leftLength; - leafStartNewLineCount += leftNewLineCount; - it = right; - } - } - it = this.NODE_TO_LEAF_INDEX(it); - - result.set(offset, it, leafStartOffset, leafStartNewLineCount); - return true; - } - - private _findOffsetCloseAfter(offset: number, start: BufferCursor, result: BufferCursor): boolean { - if (offset > this._nodes.length[1]) { - return false; - } - - let innerOffset = offset - start.leafStartOffset; - const leafsCount = this._leafs.length; - - let leafIndex = start.leafIndex; - let leafStartOffset = start.leafStartOffset; - let leafStartNewLineCount = start.leafStartNewLineCount; - - while (true) { - const leaf = this._leafs[leafIndex]; - - if (innerOffset < leaf.length() || (innerOffset === leaf.length() && leafIndex + 1 === leafsCount)) { - result.set(offset, leafIndex, leafStartOffset, leafStartNewLineCount); - return true; - } - - leafIndex++; - - if (leafIndex >= leafsCount) { - result.set(offset, leafIndex, leafStartOffset, leafStartNewLineCount); - return true; - } - - leafStartOffset += leaf.length(); - leafStartNewLineCount += leaf.newLineCount(); - innerOffset -= leaf.length(); - } - } - - private _findLineStart(lineNumber: number, result: BufferCursor): boolean { - let lineIndex = lineNumber - 1; - if (lineIndex < 0 || lineIndex > this._nodes.newLineCount[1]) { - result.set(0, 0, 0, 0); - return false; - } - - let it = 1; - let leafStartOffset = 0; - let leafStartNewLineCount = 0; - while (!this.IS_LEAF(it)) { - const left = LEFT_CHILD(it); - const right = RIGHT_CHILD(it); - - let leftNewLineCount = 0; - let leftLength = 0; - if (this.IS_NODE(left)) { - leftNewLineCount = this._nodes.newLineCount[left]; - leftLength = this._nodes.length[left]; - } else if (this.IS_LEAF(left)) { - const leaf = this._leafs[this.NODE_TO_LEAF_INDEX(left)]; - leftNewLineCount = leaf.newLineCount(); - leftLength = leaf.length(); - } - - if (lineIndex <= leftNewLineCount) { - // go left - it = left; - continue; - } - - // go right - lineIndex -= leftNewLineCount; - leafStartOffset += leftLength; - leafStartNewLineCount += leftNewLineCount; - it = right; - } - it = this.NODE_TO_LEAF_INDEX(it); - - const innerLineStartOffset = (lineIndex === 0 ? 0 : this._leafs[it].lineStartFor(lineIndex - 1)); - - result.set(leafStartOffset + innerLineStartOffset, it, leafStartOffset, leafStartNewLineCount); - return true; - } - - private _findLineEnd(start: BufferCursor, lineNumber: number, result: BufferCursor): void { - let innerLineIndex = lineNumber - 1 - start.leafStartNewLineCount; - const leafsCount = this._leafs.length; - - let leafIndex = start.leafIndex; - let leafStartOffset = start.leafStartOffset; - let leafStartNewLineCount = start.leafStartNewLineCount; - while (true) { - const leaf = this._leafs[leafIndex]; - - if (innerLineIndex < leaf.newLineCount()) { - const lineEndOffset = this._leafs[leafIndex].lineStartFor(innerLineIndex); - result.set(leafStartOffset + lineEndOffset, leafIndex, leafStartOffset, leafStartNewLineCount); - return; - } - - leafIndex++; - - if (leafIndex >= leafsCount) { - result.set(leafStartOffset + leaf.length(), leafIndex - 1, leafStartOffset, leafStartNewLineCount); - return; - } - - leafStartOffset += leaf.length(); - leafStartNewLineCount += leaf.newLineCount(); - innerLineIndex = 0; - } - } - - private _findLine(lineNumber: number, start: BufferCursor, end: BufferCursor): boolean { - if (!this._findLineStart(lineNumber, start)) { - return false; - } - - this._findLineEnd(start, lineNumber, end); - return true; - } - - public getLength(): number { - return this._nodes.length[1]; - } - - public getLineCount(): number { - return this._nodes.newLineCount[1] + 1; - } - - public getLineContent(lineNumber: number): string { - const start = BufferCursorPool.take(); - const end = BufferCursorPool.take(); - - if (!this._findLine(lineNumber, start, end)) { - BufferCursorPool.put(start); - BufferCursorPool.put(end); - throw new Error(`Line not found`); - } - - let result: string; - if (lineNumber === this.getLineCount()) { - // last line is not trailed by an eol - result = this.extractString(start, end.offset - start.offset); - } else { - result = this.extractString(start, end.offset - start.offset - this._eolLength); - } - - BufferCursorPool.put(start); - BufferCursorPool.put(end); - return result; - } - - public getLineCharCode(lineNumber: number, index: number): number { - const start = BufferCursorPool.take(); - - if (!this._findLineStart(lineNumber, start)) { - BufferCursorPool.put(start); - throw new Error(`Line not found`); - } - - const tmp = BufferCursorPool.take(); - this._findOffsetCloseAfter(start.offset + index, start, tmp); - const result = this._leafs[tmp.leafIndex].charCodeAt(tmp.offset - tmp.leafStartNewLineCount); - BufferCursorPool.put(tmp); - - BufferCursorPool.put(start); - return result; - } - - public getLineLength(lineNumber: number): number { - const start = BufferCursorPool.take(); - const end = BufferCursorPool.take(); - - if (!this._findLine(lineNumber, start, end)) { - BufferCursorPool.put(start); - BufferCursorPool.put(end); - throw new Error(`Line not found`); - } - - let result: number; - if (lineNumber === this.getLineCount()) { - // last line is not trailed by an eol - result = end.offset - start.offset; - } else { - result = end.offset - start.offset - this._eolLength; - } - - BufferCursorPool.put(start); - BufferCursorPool.put(end); - return result; - } - - public getLineFirstNonWhitespaceIndex(lineNumber: number): number { - const start = BufferCursorPool.take(); - - if (!this._findLineStart(lineNumber, start)) { - BufferCursorPool.put(start); - throw new Error(`Line not found`); - } - - let leafIndex = start.leafIndex; - let searchStartOffset = start.offset - start.leafStartOffset; - BufferCursorPool.put(start); - - const leafsCount = this._leafs.length; - let totalDelta = 0; - while (true) { - const leaf = this._leafs[leafIndex]; - - const leafResult = leaf.findLineFirstNonWhitespaceIndex(searchStartOffset); - if (leafResult === -2) { - // reached EOL - return -1; - } - if (leafResult !== -1) { - return (leafResult - searchStartOffset) + totalDelta; - } - - leafIndex++; - - if (leafIndex >= leafsCount) { - return -1; - } - - totalDelta += (leaf.length() - searchStartOffset); - searchStartOffset = 0; - } - } - - public getLineLastNonWhitespaceIndex(lineNumber: number): number { - const start = BufferCursorPool.take(); - const end = BufferCursorPool.take(); - - if (!this._findLineStart(lineNumber, start)) { - BufferCursorPool.put(start); - BufferCursorPool.put(end); - throw new Error(`Line not found`); - } - - this._findLineEnd(start, lineNumber, end); - - const startOffset = start.offset; - const endOffset = end.offset; - let leafIndex = end.leafIndex; - let searchStartOffset = end.offset - end.leafStartOffset - this._eolLength; - - BufferCursorPool.put(start); - BufferCursorPool.put(end); - - let totalDelta = 0; - while (true) { - const leaf = this._leafs[leafIndex]; - - const leafResult = leaf.findLineLastNonWhitespaceIndex(searchStartOffset); - if (leafResult === -2) { - // reached EOL - return -1; - } - if (leafResult !== -1) { - const delta = (searchStartOffset - 1 - leafResult); - const absoluteOffset = (endOffset - this._eolLength) - delta - totalDelta; - return absoluteOffset - startOffset; - } - - leafIndex--; - - if (leafIndex < 0) { - return -1; - } - - totalDelta += searchStartOffset; - searchStartOffset = leaf.length(); - } - } - - public getLinesContent(): string[] { - let result: string[] = new Array(this.getLineCount()); - let resultIndex = 0; - - let currentLine = ''; - for (let leafIndex = 0, leafsCount = this._leafs.length; leafIndex < leafsCount; leafIndex++) { - const leaf = this._leafs[leafIndex]; - const leafNewLineCount = leaf.newLineCount(); - - if (leafNewLineCount === 0) { - // special case => push entire leaf text - currentLine += leaf.text; - continue; - } - - let leafSubstrOffset = 0; - for (let newLineIndex = 0; newLineIndex < leafNewLineCount; newLineIndex++) { - const newLineStart = leaf.lineStartFor(newLineIndex); - currentLine += leaf.substr(leafSubstrOffset, newLineStart - leafSubstrOffset - this._eolLength); - result[resultIndex++] = currentLine; - - currentLine = ''; - leafSubstrOffset = newLineStart; - } - currentLine += leaf.substr(leafSubstrOffset, leaf.length()); - } - result[resultIndex++] = currentLine; - - return result; - } - - public extractString(start: BufferCursor, len: number): string { - if (!(start.offset + len <= this._nodes.length[1])) { - throw new Error(`assertion violation`); - } - - let innerLeafOffset = start.offset - start.leafStartOffset; - let leafIndex = start.leafIndex; - let res = ''; - while (len > 0) { - const leaf = this._leafs[leafIndex]; - const cnt = Math.min(len, leaf.length() - innerLeafOffset); - res += leaf.substr(innerLeafOffset, cnt); - - len -= cnt; - innerLeafOffset = 0; - - if (len === 0) { - break; - } - - leafIndex++; - } - return res; - } - - private _getOffsetAt(lineNumber: number, column: number, result: BufferCursor): boolean { - const lineStart = BufferCursorPool.take(); - - if (!this._findLineStart(lineNumber, lineStart)) { - BufferCursorPool.put(lineStart); - return false; - } - - const startOffset = lineStart.offset + column - 1; - if (!this._findOffsetCloseAfter(startOffset, lineStart, result)) { - BufferCursorPool.put(lineStart); - return false; - } - - BufferCursorPool.put(lineStart); - return true; - } - - public convertPositionToOffset(lineNumber: number, column: number): number { - const r = BufferCursorPool.take(); - - if (!this._findLineStart(lineNumber, r)) { - BufferCursorPool.put(r); - throw new Error(`Position not found`); - } - - const result = r.offset + column - 1; - - BufferCursorPool.put(r); - return result; - } - - /** - * returns `lineNumber` - */ - private _findLineStartBeforeOffsetInLeaf(offset: number, leafIndex: number, leafStartOffset: number, leafStartNewLineCount: number, result: BufferCursor): number { - const leaf = this._leafs[leafIndex]; - const lineStartIndex = leaf.findLineStartBeforeOffset(offset - leafStartOffset); - const lineStartOffset = leafStartOffset + leaf.lineStartFor(lineStartIndex); - - result.set(lineStartOffset, leafIndex, leafStartOffset, leafStartNewLineCount); - return leafStartNewLineCount + lineStartIndex + 2; - } - - /** - * returns `lineNumber`. - */ - private _findLineStartBeforeOffset(offset: number, location: BufferCursor, result: BufferCursor): number { - - let leafIndex = location.leafIndex; - let leafStartOffset = location.leafStartOffset; - let leafStartNewLineCount = location.leafStartNewLineCount; - while (true) { - const leaf = this._leafs[leafIndex]; - - if (leaf.newLineCount() >= 1 && leaf.lineStartFor(0) + leafStartOffset <= offset) { - // must be in this leaf - return this._findLineStartBeforeOffsetInLeaf(offset, leafIndex, leafStartOffset, leafStartNewLineCount, result); - } - - // continue looking in previous leaf - leafIndex--; - - if (leafIndex < 0) { - result.set(0, 0, 0, 0); - return 1; - } - - leafStartOffset -= this._leafs[leafIndex].length(); - leafStartNewLineCount -= this._leafs[leafIndex].newLineCount(); - } - } - - public convertOffsetToPosition(offset: number): Position { - const r = BufferCursorPool.take(); - const lineStart = BufferCursorPool.take(); - - if (!this._findOffset(offset, r)) { - BufferCursorPool.put(r); - BufferCursorPool.put(lineStart); - throw new Error(`Offset not found`); - } - - const lineNumber = this._findLineStartBeforeOffset(offset, r, lineStart); - const column = offset - lineStart.offset + 1; - - BufferCursorPool.put(r); - BufferCursorPool.put(lineStart); - - return new Position(lineNumber, column); - } - - public convertOffsetLenToRange(offset: number, len: number): Range { - const r = BufferCursorPool.take(); - const lineStart = BufferCursorPool.take(); - - if (!this._findOffset(offset, r)) { - BufferCursorPool.put(r); - BufferCursorPool.put(lineStart); - throw new Error(`Offset not found`); - } - const startLineNumber = this._findLineStartBeforeOffset(offset, r, lineStart); - const startColumn = offset - lineStart.offset + 1; - - if (!this._findOffset(offset + len, r)) { - BufferCursorPool.put(r); - BufferCursorPool.put(lineStart); - throw new Error(`Offset not found`); - } - const endLineNumber = this._findLineStartBeforeOffset(offset + len, r, lineStart); - const endColumn = offset + len - lineStart.offset + 1; - - BufferCursorPool.put(r); - BufferCursorPool.put(lineStart); - - return new Range(startLineNumber, startColumn, endLineNumber, endColumn); - } - - public getValueInRange(range: Range): string { - const start = BufferCursorPool.take(); - - if (!this._getOffsetAt(range.startLineNumber, range.startColumn, start)) { - BufferCursorPool.put(start); - throw new Error(`Line not found`); - } - - const endOffset = this.convertPositionToOffset(range.endLineNumber, range.endColumn); - const result = this.extractString(start, endOffset - start.offset); - - BufferCursorPool.put(start); - return result; - } - - public createSnapshot(BOM: string): ITextSnapshot { - return new BufferSnapshot(this._leafs, BOM); - } - - public getValueLengthInRange(range: Range): number { - const startOffset = this.convertPositionToOffset(range.startLineNumber, range.startColumn); - const endOffset = this.convertPositionToOffset(range.endLineNumber, range.endColumn); - return endOffset - startOffset; - } - - //#region Editing - - private _mergeAdjacentEdits(edits: OffsetLenEdit[]): OffsetLenEdit[] { - // Check if we must merge adjacent edits - let merged: OffsetLenEdit[] = [], mergedLength = 0; - let prev = edits[0]; - for (let i = 1, len = edits.length; i < len; i++) { - const curr = edits[i]; - if (prev.offset + prev.length === curr.offset) { - // merge into `prev` - prev.length = prev.length + curr.length; - prev.text = prev.text + curr.text; - } else { - merged[mergedLength++] = prev; - prev = curr; - } - } - merged[mergedLength++] = prev; - - return merged; - } - - private _resolveEdits(edits: OffsetLenEdit[]): InternalOffsetLenEdit[] { - edits = this._mergeAdjacentEdits(edits); - - let result: InternalOffsetLenEdit[] = []; - let tmp = new BufferCursor(0, 0, 0, 0); - let tmp2 = new BufferCursor(0, 0, 0, 0); - for (let i = 0, len = edits.length; i < len; i++) { - const edit = edits[i]; - - let text = edit.text; - - this._findOffset(edit.offset, tmp); - let startLeafIndex = tmp.leafIndex; - let startInnerOffset = tmp.offset - tmp.leafStartOffset; - if (startInnerOffset > 0) { - const startLeaf = this._leafs[startLeafIndex]; - const charBefore = startLeaf.charCodeAt(startInnerOffset - 1); - if (charBefore === CharCode.CarriageReturn) { - // include the replacement of \r in the edit - text = '\r' + text; - - this._findOffsetCloseAfter(edit.offset - 1, tmp, tmp2); - startLeafIndex = tmp2.leafIndex; - startInnerOffset = tmp2.offset - tmp2.leafStartOffset; - // this._findOffset(edit.offset - 1, tmp); - // startLeafIndex = tmp.leafIndex; - // startInnerOffset = tmp.offset - tmp.leafStartOffset; - } - } - - this._findOffset(edit.offset + edit.length, tmp); - let endLeafIndex = tmp.leafIndex; - let endInnerOffset = tmp.offset - tmp.leafStartOffset; - const endLeaf = this._leafs[endLeafIndex]; - if (endInnerOffset < endLeaf.length()) { - const charAfter = endLeaf.charCodeAt(endInnerOffset); - if (charAfter === CharCode.LineFeed) { - // include the replacement of \n in the edit - text = text + '\n'; - - this._findOffsetCloseAfter(edit.offset + edit.length + 1, tmp, tmp2); - endLeafIndex = tmp2.leafIndex; - endInnerOffset = tmp2.offset - tmp2.leafStartOffset; - // this._findOffset(edit.offset + edit.length + 1, tmp); - // endLeafIndex = tmp.leafIndex; - // endInnerOffset = tmp.offset - tmp.leafStartOffset; - } - } - - result[i] = new InternalOffsetLenEdit( - startLeafIndex, startInnerOffset, - endLeafIndex, endInnerOffset, - text - ); - } - - return result; - } - - private _pushLeafReplacement(startLeafIndex: number, endLeafIndex: number, replacements: LeafReplacement[]): LeafReplacement { - const res = new LeafReplacement(startLeafIndex, endLeafIndex, []); - replacements.push(res); - return res; - } - - private _flushLeafEdits(accumulatedLeafIndex: number, accumulatedLeafEdits: LeafOffsetLenEdit[], replacements: LeafReplacement[]): void { - if (accumulatedLeafEdits.length > 0) { - const rep = this._pushLeafReplacement(accumulatedLeafIndex, accumulatedLeafIndex, replacements); - BufferPiece.replaceOffsetLen(this._leafs[accumulatedLeafIndex], accumulatedLeafEdits, this._idealLeafLength, this._maxLeafLength, rep.replacements); - } - accumulatedLeafEdits.length = 0; - } - - private _pushLeafEdits(start: number, length: number, text: string, accumulatedLeafEdits: LeafOffsetLenEdit[]): void { - if (length !== 0 || text.length !== 0) { - accumulatedLeafEdits.push(new LeafOffsetLenEdit(start, length, text)); - } - } - - private _appendLeaf(leaf: BufferPiece, leafs: BufferPiece[], prevLeaf: BufferPiece): BufferPiece { - if (prevLeaf === null) { - leafs.push(leaf); - prevLeaf = leaf; - return prevLeaf; - } - - let prevLeafLength = prevLeaf.length(); - let currLeafLength = leaf.length(); - - if ((prevLeafLength < this._minLeafLength || currLeafLength < this._minLeafLength) && prevLeafLength + currLeafLength <= this._maxLeafLength) { - const joinedLeaf = BufferPiece.join(prevLeaf, leaf); - leafs[leafs.length - 1] = joinedLeaf; - prevLeaf = joinedLeaf; - return prevLeaf; - } - - const lastChar = prevLeaf.charCodeAt(prevLeafLength - 1); - const firstChar = leaf.charCodeAt(0); - - if ( - (lastChar >= 0xd800 && lastChar <= 0xdbff) || (lastChar === CharCode.CarriageReturn && firstChar === CharCode.LineFeed) - ) { - const modifiedPrevLeaf = BufferPiece.deleteLastChar(prevLeaf); - leafs[leafs.length - 1] = modifiedPrevLeaf; - - const modifiedLeaf = BufferPiece.insertFirstChar(leaf, lastChar); - leaf = modifiedLeaf; - } - - leafs.push(leaf); - prevLeaf = leaf; - return prevLeaf; - } - - private static _compareEdits(a: OffsetLenEdit, b: OffsetLenEdit): number { - if (a.offset === b.offset) { - if (a.length === b.length) { - return (a.initialIndex - b.initialIndex); - } - return (a.length - b.length); - } - return a.offset - b.offset; - } - - public replaceOffsetLen(_edits: OffsetLenEdit[]): void { - _edits.sort(Buffer._compareEdits); - - const initialLeafLength = this._leafs.length; - const edits = this._resolveEdits(_edits); - - let accumulatedLeafIndex = 0; - let accumulatedLeafEdits: LeafOffsetLenEdit[] = []; - let replacements: LeafReplacement[] = []; - - for (let i = 0, len = edits.length; i < len; i++) { - const edit = edits[i]; - - const startLeafIndex = edit.startLeafIndex; - const endLeafIndex = edit.endLeafIndex; - - if (startLeafIndex !== accumulatedLeafIndex) { - this._flushLeafEdits(accumulatedLeafIndex, accumulatedLeafEdits, replacements); - accumulatedLeafIndex = startLeafIndex; - } - - const leafEditStart = edit.startInnerOffset; - const leafEditEnd = (startLeafIndex === endLeafIndex ? edit.endInnerOffset : this._leafs[startLeafIndex].length()); - this._pushLeafEdits(leafEditStart, leafEditEnd - leafEditStart, edit.text, accumulatedLeafEdits); - - if (startLeafIndex < endLeafIndex) { - this._flushLeafEdits(accumulatedLeafIndex, accumulatedLeafEdits, replacements); - accumulatedLeafIndex = endLeafIndex; - - // delete leafs in the middle - if (startLeafIndex + 1 < endLeafIndex) { - this._pushLeafReplacement(startLeafIndex + 1, endLeafIndex - 1, replacements); - } - - // delete on last leaf - const leafEditStart = 0; - const leafEditEnd = edit.endInnerOffset; - this._pushLeafEdits(leafEditStart, leafEditEnd - leafEditStart, '', accumulatedLeafEdits); - } - } - this._flushLeafEdits(accumulatedLeafIndex, accumulatedLeafEdits, replacements); - - let leafs: BufferPiece[] = []; - let leafIndex = 0; - let prevLeaf: BufferPiece = null; - - for (let i = 0, len = replacements.length; i < len; i++) { - const replaceStartLeafIndex = replacements[i].startLeafIndex; - const replaceEndLeafIndex = replacements[i].endLeafIndex; - const innerLeafs = replacements[i].replacements; - - // add leafs to the left of this replace op. - while (leafIndex < replaceStartLeafIndex) { - prevLeaf = this._appendLeaf(this._leafs[leafIndex], leafs, prevLeaf); - leafIndex++; - } - - // delete leafs that get replaced. - while (leafIndex <= replaceEndLeafIndex) { - leafIndex++; - } - - // add new leafs. - for (let j = 0, lenJ = innerLeafs.length; j < lenJ; j++) { - prevLeaf = this._appendLeaf(innerLeafs[j], leafs, prevLeaf); - } - } - - // add remaining leafs to the right of the last replacement. - while (leafIndex < initialLeafLength) { - prevLeaf = this._appendLeaf(this._leafs[leafIndex], leafs, prevLeaf); - leafIndex++; - } - - if (leafs.length === 0) { - // don't leave behind an empty leafs array - leafs.push(new BufferPiece('')); - } - - this._leafs = leafs; - this._rebuildNodes(); - } - - public setEOL(newEOL: '\r\n' | '\n'): void { - let leafs: BufferPiece[] = []; - for (let i = 0, len = this._leafs.length; i < len; i++) { - leafs[i] = BufferPiece.normalizeEOL(this._leafs[i], newEOL); - } - this._leafs = leafs; - this._rebuildNodes(); - this._eol = newEOL; - this._eolLength = this._eol.length; - } - - //#endregion - - private IS_NODE(i: number): boolean { - return (i < this._nodesCount); - } - private IS_LEAF(i: number): boolean { - return (i >= this._leafsStart && i < this._leafsEnd); - } - private NODE_TO_LEAF_INDEX(i: number): number { - return (i - this._leafsStart); - } - // private LEAF_TO_NODE_INDEX(i: number): number { - // return (i + this._leafsStart); - // } -} - -function log2(n: number): number { - let v = 1; - for (let pow = 1; ; pow++) { - v = v << 1; - if (v >= n) { - return pow; - } - } - // return -1; -} - -function LEFT_CHILD(i: number): number { - return (i << 1); -} - -function RIGHT_CHILD(i: number): number { - return (i << 1) + 1; -} diff --git a/src/vs/editor/common/model/chunksTextBuffer/chunksTextBufferBuilder.ts b/src/vs/editor/common/model/chunksTextBuffer/chunksTextBufferBuilder.ts deleted file mode 100644 index 12eaf167b28..00000000000 --- a/src/vs/editor/common/model/chunksTextBuffer/chunksTextBufferBuilder.ts +++ /dev/null @@ -1,186 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import * as strings from 'vs/base/common/strings'; -import { ITextBufferBuilder, ITextBufferFactory, ITextBuffer, DefaultEndOfLine } from 'vs/editor/common/model'; -import { BufferPiece, createLineStarts } from 'vs/editor/common/model/chunksTextBuffer/bufferPiece'; -import { ChunksTextBuffer } from 'vs/editor/common/model/chunksTextBuffer/chunksTextBuffer'; -import { CharCode } from 'vs/base/common/charCode'; - -export class TextBufferFactory implements ITextBufferFactory { - - constructor( - private readonly _pieces: BufferPiece[], - private readonly _averageChunkSize: number, - private readonly _BOM: string, - private readonly _cr: number, - private readonly _lf: number, - private readonly _crlf: number, - private readonly _containsRTL: boolean, - private readonly _isBasicASCII: boolean, - ) { - } - - /** - * if text source is empty or with precisely one line, returns null. No end of line is detected. - * if text source contains more lines ending with '\r\n', returns '\r\n'. - * Otherwise returns '\n'. More lines end with '\n'. - */ - private _getEOL(defaultEOL: DefaultEndOfLine): '\r\n' | '\n' { - const totalEOLCount = this._cr + this._lf + this._crlf; - const totalCRCount = this._cr + this._crlf; - if (totalEOLCount === 0) { - // This is an empty file or a file with precisely one line - return (defaultEOL === DefaultEndOfLine.LF ? '\n' : '\r\n'); - } - if (totalCRCount > totalEOLCount / 2) { - // More than half of the file contains \r\n ending lines - return '\r\n'; - } - // At least one line more ends in \n - return '\n'; - } - - public create(defaultEOL: DefaultEndOfLine): ITextBuffer { - const eol = this._getEOL(defaultEOL); - let pieces = this._pieces; - - if ( - (eol === '\r\n' && (this._cr > 0 || this._lf > 0)) - || (eol === '\n' && (this._cr > 0 || this._crlf > 0)) - ) { - // Normalize pieces - for (let i = 0, len = pieces.length; i < len; i++) { - pieces[i] = BufferPiece.normalizeEOL(pieces[i], eol); - } - } - return new ChunksTextBuffer(pieces, this._averageChunkSize, this._BOM, eol, this._containsRTL, this._isBasicASCII); - } - - public getFirstLineText(lengthLimit: number): string { - const firstPiece = this._pieces[0]; - if (firstPiece.newLineCount() === 0) { - return firstPiece.substr(0, lengthLimit); - } - - const firstEOLOffset = firstPiece.lineStartFor(0); - return firstPiece.substr(0, Math.min(lengthLimit, firstEOLOffset)); - } -} - -export class ChunksTextBufferBuilder implements ITextBufferBuilder { - - private _rawPieces: BufferPiece[]; - private _hasPreviousChar: boolean; - private _previousChar: number; - private _averageChunkSize: number; - private _tmpLineStarts: number[]; - - private BOM: string; - private cr: number; - private lf: number; - private crlf: number; - private containsRTL: boolean; - private isBasicASCII: boolean; - - constructor() { - this._rawPieces = []; - this._hasPreviousChar = false; - this._previousChar = 0; - this._averageChunkSize = 0; - this._tmpLineStarts = []; - - this.BOM = ''; - this.cr = 0; - this.lf = 0; - this.crlf = 0; - this.containsRTL = false; - this.isBasicASCII = true; - } - - public acceptChunk(chunk: string): void { - if (chunk.length === 0) { - return; - } - - if (this._rawPieces.length === 0) { - if (strings.startsWithUTF8BOM(chunk)) { - this.BOM = strings.UTF8_BOM_CHARACTER; - chunk = chunk.substr(1); - } - } - - this._averageChunkSize = (this._averageChunkSize * this._rawPieces.length + chunk.length) / (this._rawPieces.length + 1); - - const lastChar = chunk.charCodeAt(chunk.length - 1); - if (lastChar === CharCode.CarriageReturn || (lastChar >= 0xd800 && lastChar <= 0xdbff)) { - // last character is \r or a high surrogate => keep it back - this._acceptChunk1(chunk.substr(0, chunk.length - 1), false); - this._hasPreviousChar = true; - this._previousChar = lastChar; - } else { - this._acceptChunk1(chunk, false); - this._hasPreviousChar = false; - this._previousChar = lastChar; - } - } - - private _acceptChunk1(chunk: string, allowEmptyStrings: boolean): void { - if (!allowEmptyStrings && chunk.length === 0) { - // Nothing to do - return; - } - - if (this._hasPreviousChar) { - this._acceptChunk2(chunk + String.fromCharCode(this._previousChar)); - } else { - this._acceptChunk2(chunk); - } - } - - private _acceptChunk2(chunk: string): void { - const lineStarts = createLineStarts(this._tmpLineStarts, chunk); - - this._rawPieces.push(new BufferPiece(chunk, lineStarts.lineStarts)); - this.cr += lineStarts.cr; - this.lf += lineStarts.lf; - this.crlf += lineStarts.crlf; - - if (this.isBasicASCII) { - this.isBasicASCII = lineStarts.isBasicASCII; - } - if (!this.isBasicASCII && !this.containsRTL) { - // No need to check if is basic ASCII - this.containsRTL = strings.containsRTL(chunk); - } - } - - public finish(): TextBufferFactory { - this._finish(); - return new TextBufferFactory(this._rawPieces, this._averageChunkSize, this.BOM, this.cr, this.lf, this.crlf, this.containsRTL, this.isBasicASCII); - } - - private _finish(): void { - if (this._rawPieces.length === 0) { - // no chunks => forcefully go through accept chunk - this._acceptChunk1('', true); - return; - } - - if (this._hasPreviousChar) { - this._hasPreviousChar = false; - - // recreate last chunk - const lastPiece = this._rawPieces[this._rawPieces.length - 1]; - const tmp = new BufferPiece(String.fromCharCode(this._previousChar)); - const newLastPiece = BufferPiece.join(lastPiece, tmp); - this._rawPieces[this._rawPieces.length - 1] = newLastPiece; - if (this._previousChar === CharCode.CarriageReturn) { - this.cr++; - } - } - } -} diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index c3881ab2db5..2156fff0083 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -35,12 +35,10 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IStringStream, ITextSnapshot } from 'vs/platform/files/common/files'; import { LinesTextBufferBuilder } from 'vs/editor/common/model/linesTextBuffer/linesTextBufferBuilder'; import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; -import { ChunksTextBufferBuilder } from 'vs/editor/common/model/chunksTextBuffer/chunksTextBufferBuilder'; export enum TextBufferType { LinesArray, - PieceTree, - Chunks + PieceTree } // Here is the master switch for the text buffer implementation: export const OPTIONS = { @@ -51,9 +49,6 @@ function createTextBufferBuilder() { if (OPTIONS.TEXT_BUFFER_IMPLEMENTATION === TextBufferType.PieceTree) { return new PieceTreeTextBufferBuilder(); } - if (OPTIONS.TEXT_BUFFER_IMPLEMENTATION === TextBufferType.Chunks) { - return new ChunksTextBufferBuilder(); - } return new LinesTextBufferBuilder(); } diff --git a/src/vs/editor/test/common/model/benchmark/benchmarkUtils.ts b/src/vs/editor/test/common/model/benchmark/benchmarkUtils.ts index f466e4d21c8..8ef7124ce16 100644 --- a/src/vs/editor/test/common/model/benchmark/benchmarkUtils.ts +++ b/src/vs/editor/test/common/model/benchmark/benchmarkUtils.ts @@ -6,7 +6,6 @@ import { ITextBufferBuilder, ITextBufferFactory, ITextBuffer, DefaultEndOfLine } from 'vs/editor/common/model'; import { LinesTextBufferBuilder } from 'vs/editor/common/model/linesTextBuffer/linesTextBufferBuilder'; import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; -import { ChunksTextBufferBuilder } from 'vs/editor/common/model/chunksTextBuffer/chunksTextBufferBuilder'; export function doBenchmark(id: string, ts: T[], fn: (t: T) => void) { let columns: string[] = [id]; @@ -57,7 +56,7 @@ export class BenchmarkSuite { for (let i = 0; i < this.benchmarks.length; i++) { let benchmark = this.benchmarks[i]; let columns: string[] = [benchmark.name]; - [new LinesTextBufferBuilder(), new PieceTreeTextBufferBuilder(), new ChunksTextBufferBuilder()].forEach((builder: ITextBufferBuilder) => { + [new LinesTextBufferBuilder(), new PieceTreeTextBufferBuilder()].forEach((builder: ITextBufferBuilder) => { let timeDiffTotal = 0.0; for (let j = 0; j < this.iterations; j++) { let factory = benchmark.buildBuffer(builder); diff --git a/src/vs/editor/test/common/model/chunksTextBuffer/bufferPiece.test.ts b/src/vs/editor/test/common/model/chunksTextBuffer/bufferPiece.test.ts deleted file mode 100644 index a6cc8f6f22d..00000000000 --- a/src/vs/editor/test/common/model/chunksTextBuffer/bufferPiece.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import * as assert from 'assert'; -import { BufferPiece } from 'vs/editor/common/model/chunksTextBuffer/bufferPiece'; - -suite('BufferPiece', () => { - test('findLineStartBeforeOffset', () => { - let piece = new BufferPiece([ - 'Line1\r\n', - 'l2\n', - 'another\r', - 'and\r\n', - 'finally\n', - 'last' - ].join('')); - - assert.equal(piece.length(), 35); - assert.deepEqual(piece.findLineStartBeforeOffset(0), -1); - assert.deepEqual(piece.findLineStartBeforeOffset(1), -1); - assert.deepEqual(piece.findLineStartBeforeOffset(2), -1); - assert.deepEqual(piece.findLineStartBeforeOffset(3), -1); - assert.deepEqual(piece.findLineStartBeforeOffset(4), -1); - assert.deepEqual(piece.findLineStartBeforeOffset(5), -1); - assert.deepEqual(piece.findLineStartBeforeOffset(6), -1); - assert.deepEqual(piece.findLineStartBeforeOffset(7), 0); - assert.deepEqual(piece.findLineStartBeforeOffset(8), 0); - assert.deepEqual(piece.findLineStartBeforeOffset(9), 0); - assert.deepEqual(piece.findLineStartBeforeOffset(10), 1); - assert.deepEqual(piece.findLineStartBeforeOffset(11), 1); - assert.deepEqual(piece.findLineStartBeforeOffset(12), 1); - assert.deepEqual(piece.findLineStartBeforeOffset(13), 1); - assert.deepEqual(piece.findLineStartBeforeOffset(14), 1); - assert.deepEqual(piece.findLineStartBeforeOffset(15), 1); - assert.deepEqual(piece.findLineStartBeforeOffset(16), 1); - assert.deepEqual(piece.findLineStartBeforeOffset(17), 1); - assert.deepEqual(piece.findLineStartBeforeOffset(18), 2); - assert.deepEqual(piece.findLineStartBeforeOffset(19), 2); - assert.deepEqual(piece.findLineStartBeforeOffset(20), 2); - assert.deepEqual(piece.findLineStartBeforeOffset(21), 2); - assert.deepEqual(piece.findLineStartBeforeOffset(22), 2); - assert.deepEqual(piece.findLineStartBeforeOffset(23), 3); - assert.deepEqual(piece.findLineStartBeforeOffset(24), 3); - assert.deepEqual(piece.findLineStartBeforeOffset(25), 3); - assert.deepEqual(piece.findLineStartBeforeOffset(26), 3); - assert.deepEqual(piece.findLineStartBeforeOffset(27), 3); - assert.deepEqual(piece.findLineStartBeforeOffset(28), 3); - assert.deepEqual(piece.findLineStartBeforeOffset(29), 3); - assert.deepEqual(piece.findLineStartBeforeOffset(30), 3); - assert.deepEqual(piece.findLineStartBeforeOffset(31), 4); - assert.deepEqual(piece.findLineStartBeforeOffset(32), 4); - assert.deepEqual(piece.findLineStartBeforeOffset(33), 4); - assert.deepEqual(piece.findLineStartBeforeOffset(34), 4); - assert.deepEqual(piece.findLineStartBeforeOffset(35), 4); - assert.deepEqual(piece.findLineStartBeforeOffset(36), 4); - }); -});