提交 805d0785 编写于 作者: A Alex Dima

Remove ChunksTextBuffer

上级 ef7763a2
/*---------------------------------------------------------------------------------------------
* 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<string>(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;
}
/*---------------------------------------------------------------------------------------------
* 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++;
}
}
}
}
......@@ -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();
}
......
......@@ -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<T>(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);
......
/*---------------------------------------------------------------------------------------------
* 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);
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册