Merge VS Code 1.23.1 (#1520)

This commit is contained in:
Matt Irvine
2018-06-05 11:24:51 -07:00
committed by GitHub
parent e3baf5c443
commit 0c58f09e59
3651 changed files with 74249 additions and 48599 deletions

View File

@@ -4,9 +4,7 @@
*--------------------------------------------------------------------------------------------*/
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 +55,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 PieceTreeTextBufferBuilder()].forEach((builder: ITextBufferBuilder) => {
let timeDiffTotal = 0.0;
for (let j = 0; j < this.iterations; j++) {
let factory = benchmark.buildBuffer(builder);

View File

@@ -4,13 +4,11 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { LinesTextBufferBuilder } from 'vs/editor/common/model/linesTextBuffer/linesTextBufferBuilder';
import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder';
import { ITextBufferBuilder } from 'vs/editor/common/model';
import { generateRandomChunkWithLF } from 'vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils';
import { doBenchmark } from 'vs/editor/test/common/model/benchmark/benchmarkUtils';
let linesTextBufferBuilder = new LinesTextBufferBuilder();
let pieceTreeTextBufferBuilder = new PieceTreeTextBufferBuilder();
let chunks = [];
@@ -30,5 +28,5 @@ let modelBuildBenchmark = function (id: string, builders: ITextBufferBuilder[],
console.log(`|model builder\t|line buffer\t|piece table\t|`);
console.log('|---|---|---|');
for (let i of [10, 100]) {
modelBuildBenchmark(`${i} random chunks`, [linesTextBufferBuilder, pieceTreeTextBufferBuilder], i);
modelBuildBenchmark(`${i} random chunks`, [pieceTreeTextBufferBuilder], i);
}

View File

@@ -1,61 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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);
});
});

View File

@@ -7,7 +7,7 @@
import * as assert from 'assert';
import { Range } from 'vs/editor/common/core/range';
import { EndOfLineSequence, IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
import { EndOfLineSequence, IIdentifiedSingleEditOperation, EndOfLinePreference } from 'vs/editor/common/model';
import { TextModel } from 'vs/editor/common/model/textModel';
import { MirrorTextModel } from 'vs/editor/common/model/mirrorTextModel';
import { assertSyncedModels, testApplyEditsWithSyncedModels } from 'vs/editor/test/common/model/editableTextModelTestUtils';
@@ -1069,4 +1069,48 @@ suite('EditorModel - EditableTextModel.applyEdits', () => {
model.dispose();
mirrorModel2.dispose();
});
test('issue #47733: Undo mangles unicode characters', () => {
let model = createEditableTextModelFromString('\'👁\'');
model.applyEdits([
{ range: new Range(1, 1, 1, 1), text: '"' },
{ range: new Range(1, 2, 1, 2), text: '"' },
]);
assert.equal(model.getValue(EndOfLinePreference.LF), '"\'"👁\'');
assert.deepEqual(model.validateRange(new Range(1, 3, 1, 4)), new Range(1, 3, 1, 4));
model.applyEdits([
{ range: new Range(1, 1, 1, 2), text: null },
{ range: new Range(1, 3, 1, 4), text: null },
]);
assert.equal(model.getValue(EndOfLinePreference.LF), '\'👁\'');
model.dispose();
});
test('issue #48741: Broken undo stack with move lines up with multiple cursors', () => {
let model = createEditableTextModelFromString([
'line1',
'line2',
'line3',
'',
].join('\n'));
const undoEdits = model.applyEdits([
{ range: new Range(4, 1, 4, 1), text: 'line3', },
{ range: new Range(3, 1, 3, 6), text: null, },
{ range: new Range(2, 1, 3, 1), text: null, },
{ range: new Range(3, 6, 3, 6), text: '\nline2' }
]);
model.applyEdits(undoEdits);
assert.deepEqual(model.getValue(), 'line1\nline2\nline3\n');
model.dispose();
});
});

View File

@@ -31,8 +31,17 @@ export function testApplyEditsWithSyncedModels(original: string[], edits: IIdent
assert.deepEqual(model.getValue(EndOfLinePreference.LF), originalStr);
if (!inputEditsAreInvalid) {
const simplifyEdit = (edit: IIdentifiedSingleEditOperation) => {
return {
identifier: edit.identifier,
range: edit.range,
text: edit.text,
forceMoveMarkers: edit.forceMoveMarkers,
isAutoWhitespaceEdit: edit.isAutoWhitespaceEdit
};
};
// Assert the inverse of the inverse edits are the original edits
assert.deepEqual(inverseInverseEdits, edits);
assert.deepEqual(inverseInverseEdits.map(simplifyEdit), edits.map(simplifyEdit));
}
assertMirrorModels();

View File

@@ -7,9 +7,11 @@
import * as assert from 'assert';
import { Range } from 'vs/editor/common/core/range';
import { LinesTextBuffer, IValidatedEditOperation } from 'vs/editor/common/model/linesTextBuffer/linesTextBuffer';
import { PieceTreeTextBuffer, IValidatedEditOperation } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer';
import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
import { DefaultEndOfLine } from 'vs/editor/common/model';
suite('LinesTextBuffer._getInverseEdits', () => {
suite('PieceTreeTextBuffer._getInverseEdits', () => {
function editOp(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, text: string[]): IValidatedEditOperation {
return {
@@ -29,7 +31,7 @@ suite('LinesTextBuffer._getInverseEdits', () => {
}
function assertInverseEdits(ops: IValidatedEditOperation[], expected: Range[]): void {
var actual = LinesTextBuffer._getInverseEditRanges(ops);
var actual = PieceTreeTextBuffer._getInverseEditRanges(ops);
assert.deepEqual(actual, expected);
}
@@ -260,7 +262,7 @@ suite('LinesTextBuffer._getInverseEdits', () => {
});
});
suite('LinesTextBuffer._toSingleEditOperation', () => {
suite('PieceTreeTextBuffer._toSingleEditOperation', () => {
function editOp(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, rangeOffset: number, rangeLength: number, text: string[]): IValidatedEditOperation {
return {
@@ -276,13 +278,7 @@ suite('LinesTextBuffer._toSingleEditOperation', () => {
}
function testToSingleEditOperation(original: string[], edits: IValidatedEditOperation[], expected: IValidatedEditOperation): void {
const textBuffer = new LinesTextBuffer({
BOM: '',
EOL: '\n',
containsRTL: false,
isBasicASCII: true,
lines: original
});
const textBuffer = <PieceTreeTextBuffer>createTextBufferFactory(original.join('\n')).create(DefaultEndOfLine.LF);
const actual = textBuffer._toSingleEditOperation(edits);
assert.deepEqual(actual, expected);

View File

@@ -5,150 +5,69 @@
'use strict';
import * as assert from 'assert';
import { LinesTextBufferBuilder } from 'vs/editor/common/model/linesTextBuffer/linesTextBufferBuilder';
import { ITextModelCreationOptions } from 'vs/editor/common/model';
import { TextModel } from 'vs/editor/common/model/textModel';
import { DefaultEndOfLine } from 'vs/editor/common/model';
import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
import * as strings from 'vs/base/common/strings';
import { IRawTextSource } from 'vs/editor/common/model/linesTextBuffer/textSource';
import { PieceTreeTextBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer';
class RawTextSource {
public static fromString(rawText: string): IRawTextSource {
// Count the number of lines that end with \r\n
let carriageReturnCnt = 0;
let lastCarriageReturnIndex = -1;
while ((lastCarriageReturnIndex = rawText.indexOf('\r', lastCarriageReturnIndex + 1)) !== -1) {
carriageReturnCnt++;
}
export function testTextBufferFactory(text: string, eol: string, mightContainNonBasicASCII: boolean, mightContainRTL: boolean): void {
const textBuffer = <PieceTreeTextBuffer>createTextBufferFactory(text).create(DefaultEndOfLine.LF);
const containsRTL = strings.containsRTL(rawText);
const isBasicASCII = (containsRTL ? false : strings.isBasicASCII(rawText));
// Split the text into lines
const lines = rawText.split(/\r\n|\r|\n/);
// Remove the BOM (if present)
let BOM = '';
if (strings.startsWithUTF8BOM(lines[0])) {
BOM = strings.UTF8_BOM_CHARACTER;
lines[0] = lines[0].substr(1);
}
return {
BOM: BOM,
lines: lines,
containsRTL: containsRTL,
isBasicASCII: isBasicASCII,
totalCRCount: carriageReturnCnt
};
}
}
export function testModelBuilder(chunks: string[], opts: ITextModelCreationOptions = TextModel.DEFAULT_CREATION_OPTIONS): void {
let expectedTextSource = RawTextSource.fromString(chunks.join(''));
let builder = new LinesTextBufferBuilder();
for (let i = 0, len = chunks.length; i < len; i++) {
builder.acceptChunk(chunks[i]);
}
let actual = builder.finish();
assert.deepEqual(actual.rawTextSource, expectedTextSource);
assert.equal(textBuffer.mightContainNonBasicASCII(), mightContainNonBasicASCII);
assert.equal(textBuffer.mightContainRTL(), mightContainRTL);
assert.equal(textBuffer.getEOL(), eol);
}
suite('ModelBuilder', () => {
test('no chunks', () => {
testModelBuilder([]);
test('t1', () => {
testTextBufferFactory('', '\n', false, false);
});
test('single empty chunk', () => {
testModelBuilder(['']);
test('t2', () => {
testTextBufferFactory('Hello world', '\n', false, false);
});
test('single line in one chunk', () => {
testModelBuilder(['Hello world']);
test('t3', () => {
testTextBufferFactory('Hello world\nHow are you?', '\n', false, false);
});
test('single line in multiple chunks', () => {
testModelBuilder(['Hello', ' ', 'world']);
});
test('two lines in single chunk', () => {
testModelBuilder(['Hello world\nHow are you?']);
});
test('two lines in multiple chunks 1', () => {
testModelBuilder(['Hello worl', 'd\nHow are you?']);
});
test('two lines in multiple chunks 2', () => {
testModelBuilder(['Hello worl', 'd', '\n', 'H', 'ow are you?']);
});
test('two lines in multiple chunks 3', () => {
testModelBuilder(['Hello worl', 'd', '\nHow are you?']);
});
test('multiple lines in single chunks', () => {
testModelBuilder(['Hello world\nHow are you?\nIs everything good today?\nDo you enjoy the weather?']);
});
test('multiple lines in multiple chunks 1', () => {
testModelBuilder(['Hello world\nHow are you', '?\nIs everything good today?\nDo you enjoy the weather?']);
});
test('multiple lines in multiple chunks 1', () => {
testModelBuilder(['Hello world', '\nHow are you', '?\nIs everything good today?', '\nDo you enjoy the weather?']);
});
test('multiple lines in multiple chunks 1', () => {
testModelBuilder(['Hello world\n', 'How are you', '?\nIs everything good today?', '\nDo you enjoy the weather?']);
test('t4', () => {
testTextBufferFactory('Hello world\nHow are you?\nIs everything good today?\nDo you enjoy the weather?', '\n', false, false);
});
test('carriage return detection (1 \\r\\n 2 \\n)', () => {
testModelBuilder(['Hello world\r\n', 'How are you', '?\nIs everything good today?', '\nDo you enjoy the weather?']);
testTextBufferFactory('Hello world\r\nHow are you?\nIs everything good today?\nDo you enjoy the weather?', '\n', false, false);
});
test('carriage return detection (2 \\r\\n 1 \\n)', () => {
testModelBuilder(['Hello world\r\n', 'How are you', '?\r\nIs everything good today?', '\nDo you enjoy the weather?']);
testTextBufferFactory('Hello world\r\nHow are you?\r\nIs everything good today?\nDo you enjoy the weather?', '\r\n', false, false);
});
test('carriage return detection (3 \\r\\n 0 \\n)', () => {
testModelBuilder(['Hello world\r\n', 'How are you', '?\r\nIs everything good today?', '\r\nDo you enjoy the weather?']);
});
test('carriage return detection (isolated \\r)', () => {
testModelBuilder(['Hello world', '\r', '\n', 'How are you', '?', '\r', '\n', 'Is everything good today?', '\r', '\n', 'Do you enjoy the weather?']);
testTextBufferFactory('Hello world\r\nHow are you?\r\nIs everything good today?\r\nDo you enjoy the weather?', '\r\n', false, false);
});
test('BOM handling', () => {
testModelBuilder([strings.UTF8_BOM_CHARACTER + 'Hello world!']);
testTextBufferFactory(strings.UTF8_BOM_CHARACTER + 'Hello world!', '\n', false, false);
});
test('BOM handling', () => {
testModelBuilder([strings.UTF8_BOM_CHARACTER, 'Hello world!']);
});
test('RTL handling 1', () => {
testModelBuilder(['Hello world!', 'זוהי עובדה מבוססת שדעתו']);
testTextBufferFactory(strings.UTF8_BOM_CHARACTER + 'Hello world!', '\n', false, false);
});
test('RTL handling 2', () => {
testModelBuilder(['Hello world!זוהי עובדה מבוססת שדעתו']);
testTextBufferFactory('Hello world!זוהי עובדה מבוססת שדעתו', '\n', true, true);
});
test('RTL handling 3', () => {
testModelBuilder(['Hello world!זוהי \nעובדה מבוססת שדעתו']);
testTextBufferFactory('Hello world!זוהי \nעובדה מבוססת שדעתו', '\n', true, true);
});
test('ASCII handling 1', () => {
testModelBuilder(['Hello world!!\nHow do you do?']);
testTextBufferFactory('Hello world!!\nHow do you do?', '\n', false, false);
});
test('ASCII handling 1', () => {
testModelBuilder(['Hello world!!\nHow do you do?Züricha📚📚b']);
});
test('issue #32819: some special string cannot be displayed completely', () => {
testModelBuilder(['123']);
test('ASCII handling 2', () => {
testTextBufferFactory('Hello world!!\nHow do you do?Züricha📚📚b', '\n', true, false);
});
});

View File

@@ -1,97 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { testModelBuilder } from './linesTextBufferBuilder.test';
import { getRandomInt, getRandomEOLSequence, getRandomString } from 'vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils';
const GENERATE_TESTS = false;
suite('ModelBuilder Auto Tests', () => {
test('auto1', () => {
testModelBuilder(['sarjniow', '\r', '\nbpb', 'ofb', '\njzldgxx', '\r\nkzwfjysng']);
});
test('auto2', () => {
testModelBuilder(['i', 'yyernubi\r\niimgn\n', 'ut\r']);
});
});
function generateRandomFile(): string {
let lineCount = getRandomInt(1, 10);
let mixedEOLSequence = getRandomInt(1, 2) === 1 ? true : false;
let fixedEOL = getRandomEOLSequence();
let lines: string[] = [];
for (let i = 0; i < lineCount; i++) {
if (i !== 0) {
if (mixedEOLSequence) {
lines.push(getRandomEOLSequence());
} else {
lines.push(fixedEOL);
}
}
lines.push(getRandomString(0, 10));
}
return lines.join('');
}
function generateRandomChunks(file: string): string[] {
let result: string[] = [];
let cnt = getRandomInt(1, 20);
let maxOffset = file.length;
while (cnt > 0 && maxOffset > 0) {
let offset = getRandomInt(0, maxOffset);
result.unshift(file.substring(offset, maxOffset));
// let length = getRandomInt(0, maxOffset - offset);
// let text = generateFile(true);
// result.push({
// offset: offset,
// length: length,
// text: text
// });
maxOffset = offset;
cnt--;
}
if (maxOffset !== 0) {
result.unshift(file.substring(0, maxOffset));
}
return result;
}
function testRandomFile(file: string): boolean {
let tests = getRandomInt(5, 10);
for (let i = 0; i < tests; i++) {
let chunks = generateRandomChunks(file);
try {
testModelBuilder(chunks);
} catch (err) {
console.log(err);
console.log(JSON.stringify(chunks));
return false;
}
}
return true;
}
if (GENERATE_TESTS) {
let number = 1;
while (true) {
console.log('------BEGIN NEW TEST: ' + number);
if (!testRandomFile(generateRandomFile())) {
break;
}
console.log('------END NEW TEST: ' + (number++));
}
}

View File

@@ -13,6 +13,12 @@ import {
ModelRawLinesDeleted, ModelRawLinesInserted
} from 'vs/editor/common/model/textModelEvents';
import { TextModel } from 'vs/editor/common/model/textModel';
import { LanguageIdentifier, TokenizationRegistry, IState, MetadataConsts } from 'vs/editor/common/modes';
import { MockMode } from 'vs/editor/test/common/mocks/mockMode';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { TokenizationResult2 } from 'vs/editor/common/core/token';
import { NULL_STATE } from 'vs/editor/common/modes/nullMode';
import { dispose, Disposable } from 'vs/base/common/lifecycle';
// --------- utils
@@ -103,7 +109,7 @@ suite('Editor Model - Model', () => {
let e: ModelRawContentChangedEvent = null;
thisModel.onDidChangeRawContent((_e) => {
if (e !== null) {
assert.fail();
assert.fail('Unexpected assertion error');
}
e = _e;
});
@@ -122,7 +128,7 @@ suite('Editor Model - Model', () => {
let e: ModelRawContentChangedEvent = null;
thisModel.onDidChangeRawContent((_e) => {
if (e !== null) {
assert.fail();
assert.fail('Unexpected assertion error');
}
e = _e;
});
@@ -199,7 +205,7 @@ suite('Editor Model - Model', () => {
let e: ModelRawContentChangedEvent = null;
thisModel.onDidChangeRawContent((_e) => {
if (e !== null) {
assert.fail();
assert.fail('Unexpected assertion error');
}
e = _e;
});
@@ -218,7 +224,7 @@ suite('Editor Model - Model', () => {
let e: ModelRawContentChangedEvent = null;
thisModel.onDidChangeRawContent((_e) => {
if (e !== null) {
assert.fail();
assert.fail('Unexpected assertion error');
}
e = _e;
});
@@ -237,7 +243,7 @@ suite('Editor Model - Model', () => {
let e: ModelRawContentChangedEvent = null;
thisModel.onDidChangeRawContent((_e) => {
if (e !== null) {
assert.fail();
assert.fail('Unexpected assertion error');
}
e = _e;
});
@@ -257,7 +263,7 @@ suite('Editor Model - Model', () => {
let e: ModelRawContentChangedEvent = null;
thisModel.onDidChangeRawContent((_e) => {
if (e !== null) {
assert.fail();
assert.fail('Unexpected assertion error');
}
e = _e;
});
@@ -308,7 +314,7 @@ suite('Editor Model - Model', () => {
let e: ModelRawContentChangedEvent = null;
thisModel.onDidChangeRawContent((_e) => {
if (e !== null) {
assert.fail();
assert.fail('Unexpected assertion error');
}
e = _e;
});
@@ -322,6 +328,16 @@ suite('Editor Model - Model', () => {
false
));
});
test('issue #46342: Maintain edit operation order in applyEdits', () => {
let res = thisModel.applyEdits([
{ range: new Range(2, 1, 2, 1), text: 'a' },
{ range: new Range(1, 1, 1, 1), text: 'b' },
]);
assert.deepEqual(res[0].range, new Range(2, 1, 2, 2));
assert.deepEqual(res[1].range, new Range(1, 1, 1, 2));
});
});
@@ -365,18 +381,62 @@ suite('Editor Model - Model Line Separators', () => {
suite('Editor Model - Words', () => {
var thisModel: TextModel;
const OUTER_LANGUAGE_ID = new LanguageIdentifier('outerMode', 3);
const INNER_LANGUAGE_ID = new LanguageIdentifier('innerMode', 4);
class OuterMode extends MockMode {
constructor() {
super(OUTER_LANGUAGE_ID);
this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), {}));
this._register(TokenizationRegistry.register(this.getLanguageIdentifier().language, {
getInitialState: (): IState => NULL_STATE,
tokenize: undefined,
tokenize2: (line: string, state: IState): TokenizationResult2 => {
const tokensArr: number[] = [];
let prevLanguageId: LanguageIdentifier = undefined;
for (let i = 0; i < line.length; i++) {
const languageId = (line.charAt(i) === 'x' ? INNER_LANGUAGE_ID : OUTER_LANGUAGE_ID);
if (prevLanguageId !== languageId) {
tokensArr.push(i);
tokensArr.push((languageId.id << MetadataConsts.LANGUAGEID_OFFSET));
}
prevLanguageId = languageId;
}
const tokens = new Uint32Array(tokensArr.length);
for (let i = 0; i < tokens.length; i++) {
tokens[i] = tokensArr[i];
}
return new TokenizationResult2(tokens, state);
}
}));
}
}
class InnerMode extends MockMode {
constructor() {
super(INNER_LANGUAGE_ID);
this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), {}));
}
}
let disposables: Disposable[] = [];
setup(() => {
var text = ['This text has some words. '];
thisModel = TextModel.createFromString(text.join('\n'));
disposables = [];
});
teardown(() => {
thisModel.dispose();
dispose(disposables);
disposables = [];
});
test('Get word at position', () => {
const text = ['This text has some words. '];
const thisModel = TextModel.createFromString(text.join('\n'));
disposables.push(thisModel);
assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 1)), { word: 'This', startColumn: 1, endColumn: 5 });
assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 2)), { word: 'This', startColumn: 1, endColumn: 5 });
assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 4)), { word: 'This', startColumn: 1, endColumn: 5 });
@@ -389,4 +449,21 @@ suite('Editor Model - Words', () => {
assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 27)), null);
assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 28)), null);
});
test('getWordAtPosition at embedded language boundaries', () => {
const outerMode = new OuterMode();
const innerMode = new InnerMode();
disposables.push(outerMode, innerMode);
const model = TextModel.createFromString('ab<xx>ab<x>', undefined, outerMode.getLanguageIdentifier());
disposables.push(model);
assert.deepEqual(model.getWordAtPosition(new Position(1, 1)), { word: 'ab', startColumn: 1, endColumn: 3 });
assert.deepEqual(model.getWordAtPosition(new Position(1, 2)), { word: 'ab', startColumn: 1, endColumn: 3 });
assert.deepEqual(model.getWordAtPosition(new Position(1, 3)), { word: 'ab', startColumn: 1, endColumn: 3 });
assert.deepEqual(model.getWordAtPosition(new Position(1, 4)), { word: 'xx', startColumn: 4, endColumn: 6 });
assert.deepEqual(model.getWordAtPosition(new Position(1, 5)), { word: 'xx', startColumn: 4, endColumn: 6 });
assert.deepEqual(model.getWordAtPosition(new Position(1, 6)), { word: 'xx', startColumn: 4, endColumn: 6 });
assert.deepEqual(model.getWordAtPosition(new Position(1, 7)), { word: 'ab', startColumn: 7, endColumn: 9 });
});
});

View File

@@ -68,7 +68,16 @@ suite('Editor Model - Model Edit Operation', () => {
assert.equal(model.getLineContent(4), LINE4);
assert.equal(model.getLineContent(5), LINE5);
assert.deepEqual(originalOp, editOp);
const simplifyEdit = (edit: IIdentifiedSingleEditOperation) => {
return {
identifier: edit.identifier,
range: edit.range,
text: edit.text,
forceMoveMarkers: edit.forceMoveMarkers,
isAutoWhitespaceEdit: edit.isAutoWhitespaceEdit
};
};
assert.deepEqual(originalOp.map(simplifyEdit), editOp.map(simplifyEdit));
}
test('Insert inline', () => {

View File

@@ -12,6 +12,10 @@ import { DefaultEndOfLine } from 'vs/editor/common/model';
import { PieceTreeBase } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase';
import { SENTINEL, NodeColor, TreeNode } from 'vs/editor/common/model/pieceTreeTextBuffer/rbTreeBase';
import { PieceTreeTextBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer';
import { TextModel } from 'vs/editor/common/model/textModel';
import { ITextSnapshot } from 'vs/platform/files/common/files';
import { SearchData } from 'vs/editor/common/model/textModelSearch';
import { WordCharacterClassifier } from 'vs/editor/common/controller/wordCharacterClassifier';
const alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n';
@@ -1441,11 +1445,44 @@ suite('centralized lineStarts with CRLF', () => {
});
suite('random is unsupervised', () => {
test('splitting large change buffer', function () {
let pieceTable = createTextBuffer([''], false);
let str = '';
pieceTable.insert(0, 'WUZ\nXVZY\n');
str = str.substring(0, 0) + 'WUZ\nXVZY\n' + str.substring(0);
pieceTable.insert(8, '\r\r\nZXUWVW');
str = str.substring(0, 8) + '\r\r\nZXUWVW' + str.substring(8);
pieceTable.delete(10, 7);
str = str.substring(0, 10) + str.substring(10 + 7);
pieceTable.delete(10, 1);
str = str.substring(0, 10) + str.substring(10 + 1);
pieceTable.insert(4, 'VX\r\r\nWZVZ');
str = str.substring(0, 4) + 'VX\r\r\nWZVZ' + str.substring(4);
pieceTable.delete(11, 3);
str = str.substring(0, 11) + str.substring(11 + 3);
pieceTable.delete(12, 4);
str = str.substring(0, 12) + str.substring(12 + 4);
pieceTable.delete(8, 0);
str = str.substring(0, 8) + str.substring(8 + 0);
pieceTable.delete(10, 2);
str = str.substring(0, 10) + str.substring(10 + 2);
pieceTable.insert(0, 'VZXXZYZX\r');
str = str.substring(0, 0) + 'VZXXZYZX\r' + str.substring(0);
assert.equal(pieceTable.getLinesRawContent(), str);
testLineStarts(str, pieceTable);
testLinesContent(str, pieceTable);
assertTreeInvariants(pieceTable);
});
test('random insert delete', function () {
this.timeout(500000);
let str = '';
let pieceTable = createTextBuffer([str], false);
// let output = '';
for (let i = 0; i < 1000; i++) {
if (Math.random() < 0.6) {
// insert
@@ -1453,6 +1490,8 @@ suite('random is unsupervised', () => {
let pos = randomInt(str.length + 1);
pieceTable.insert(pos, text);
str = str.substring(0, pos) + text + str.substring(pos);
// output += `pieceTable.insert(${pos}, '${text.replace(/\n/g, '\\n').replace(/\r/g, '\\r')}');\n`;
// output += `str = str.substring(0, ${pos}) + '${text.replace(/\n/g, '\\n').replace(/\r/g, '\\r')}' + str.substring(${pos});\n`;
} else {
// delete
let pos = randomInt(str.length);
@@ -1462,8 +1501,12 @@ suite('random is unsupervised', () => {
);
pieceTable.delete(pos, length);
str = str.substring(0, pos) + str.substring(pos + length);
// output += `pieceTable.delete(${pos}, ${length});\n`;
// output += `str = str.substring(0, ${pos}) + str.substring(${pos} + ${length});\n`
}
}
// console.log(output);
assert.equal(pieceTable.getLinesRawContent(), str);
@@ -1567,6 +1610,37 @@ suite('buffer api', () => {
assert(!a.equal(b));
});
test('getLineCharCode - issue #45735', () => {
let pieceTable = createTextBuffer(['LINE1\nline2']);
assert.equal(pieceTable.getLineCharCode(1, 0), 'L'.charCodeAt(0), 'L');
assert.equal(pieceTable.getLineCharCode(1, 1), 'I'.charCodeAt(0), 'I');
assert.equal(pieceTable.getLineCharCode(1, 2), 'N'.charCodeAt(0), 'N');
assert.equal(pieceTable.getLineCharCode(1, 3), 'E'.charCodeAt(0), 'E');
assert.equal(pieceTable.getLineCharCode(1, 4), '1'.charCodeAt(0), '1');
assert.equal(pieceTable.getLineCharCode(1, 5), '\n'.charCodeAt(0), '\\n');
assert.equal(pieceTable.getLineCharCode(2, 0), 'l'.charCodeAt(0), 'l');
assert.equal(pieceTable.getLineCharCode(2, 1), 'i'.charCodeAt(0), 'i');
assert.equal(pieceTable.getLineCharCode(2, 2), 'n'.charCodeAt(0), 'n');
assert.equal(pieceTable.getLineCharCode(2, 3), 'e'.charCodeAt(0), 'e');
assert.equal(pieceTable.getLineCharCode(2, 4), '2'.charCodeAt(0), '2');
});
test('getLineCharCode - issue #47733', () => {
let pieceTable = createTextBuffer(['', 'LINE1\n', 'line2']);
assert.equal(pieceTable.getLineCharCode(1, 0), 'L'.charCodeAt(0), 'L');
assert.equal(pieceTable.getLineCharCode(1, 1), 'I'.charCodeAt(0), 'I');
assert.equal(pieceTable.getLineCharCode(1, 2), 'N'.charCodeAt(0), 'N');
assert.equal(pieceTable.getLineCharCode(1, 3), 'E'.charCodeAt(0), 'E');
assert.equal(pieceTable.getLineCharCode(1, 4), '1'.charCodeAt(0), '1');
assert.equal(pieceTable.getLineCharCode(1, 5), '\n'.charCodeAt(0), '\\n');
assert.equal(pieceTable.getLineCharCode(2, 0), 'l'.charCodeAt(0), 'l');
assert.equal(pieceTable.getLineCharCode(2, 1), 'i'.charCodeAt(0), 'i');
assert.equal(pieceTable.getLineCharCode(2, 2), 'n'.charCodeAt(0), 'n');
assert.equal(pieceTable.getLineCharCode(2, 3), 'e'.charCodeAt(0), 'e');
assert.equal(pieceTable.getLineCharCode(2, 4), '2'.charCodeAt(0), '2');
});
});
suite('search offset cache', () => {
@@ -1674,4 +1748,154 @@ suite('search offset cache', () => {
assertTreeInvariants(pieceTable);
});
});
function getValueInSnapshot(snapshot: ITextSnapshot) {
let ret = '';
let tmp = snapshot.read();
while (tmp !== null) {
ret += tmp;
tmp = snapshot.read();
}
return ret;
}
suite('snapshot', () => {
test('bug #45564, piece tree pieces should be immutable', () => {
const model = TextModel.createFromString('\n');
model.applyEdits([
{
range: new Range(2, 1, 2, 1),
text: '!'
}
]);
const snapshot = model.createSnapshot();
const snapshot1 = model.createSnapshot();
assert.equal(model.getLinesContent().join('\n'), getValueInSnapshot(snapshot));
model.applyEdits([
{
range: new Range(2, 1, 2, 2),
text: ''
}
]);
model.applyEdits([
{
range: new Range(2, 1, 2, 1),
text: '!'
}
]);
assert.equal(model.getLinesContent().join('\n'), getValueInSnapshot(snapshot1));
});
test('immutable snapshot 1', () => {
const model = TextModel.createFromString('abc\ndef');
const snapshot = model.createSnapshot();
model.applyEdits([
{
range: new Range(2, 1, 2, 4),
text: ''
}
]);
model.applyEdits([
{
range: new Range(1, 1, 2, 1),
text: 'abc\ndef'
}
]);
assert.equal(model.getLinesContent().join('\n'), getValueInSnapshot(snapshot));
});
test('immutable snapshot 2', () => {
const model = TextModel.createFromString('abc\ndef');
const snapshot = model.createSnapshot();
model.applyEdits([
{
range: new Range(2, 1, 2, 1),
text: '!'
}
]);
model.applyEdits([
{
range: new Range(2, 1, 2, 2),
text: ''
}
]);
assert.equal(model.getLinesContent().join('\n'), getValueInSnapshot(snapshot));
});
test('immutable snapshot 3', () => {
const model = TextModel.createFromString('abc\ndef');
model.applyEdits([
{
range: new Range(2, 4, 2, 4),
text: '!'
}
]);
const snapshot = model.createSnapshot();
model.applyEdits([
{
range: new Range(2, 5, 2, 5),
text: '!'
}
]);
assert.notEqual(model.getLinesContent().join('\n'), getValueInSnapshot(snapshot));
});
});
suite('chunk based search', () => {
test('#45892. For some cases, the buffer is empty but we still try to search', () => {
let pieceTree = createTextBuffer(['']);
pieceTree.delete(0, 1);
let ret = pieceTree.findMatchesLineByLine(new Range(1, 1, 1, 1), new SearchData(/abc/, new WordCharacterClassifier(',./'), 'abc'), true, 1000);
assert.equal(ret.length, 0);
});
test('#45770. FindInNode should not cross node boundary.', () => {
let pieceTree = createTextBuffer([
[
'balabalababalabalababalabalaba',
'balabalababalabalababalabalaba',
'',
'* [ ] task1',
'* [x] task2 balabalaba',
'* [ ] task 3'
].join('\n')
]);
pieceTree.delete(0, 62);
pieceTree.delete(16, 1);
pieceTree.insert(16, ' ');
let ret = pieceTree.findMatchesLineByLine(new Range(1, 1, 4, 13), new SearchData(/\[/gi, new WordCharacterClassifier(',./'), '['), true, 1000);
assert.equal(ret.length, 3);
assert.deepEqual(ret[0].range, new Range(2, 3, 2, 4));
assert.deepEqual(ret[1].range, new Range(3, 3, 3, 4));
assert.deepEqual(ret[2].range, new Range(4, 3, 4, 4));
});
test('search searching from the middle', () => {
let pieceTree = createTextBuffer([
[
'def',
'dbcabc'
].join('\n')
]);
pieceTree.delete(4, 1);
let ret = pieceTree.findMatchesLineByLine(new Range(2, 3, 2, 6), new SearchData(/a/gi, null, 'a'), true, 1000);
assert.equal(ret.length, 1);
assert.deepEqual(ret[0].range, new Range(2, 3, 2, 4));
pieceTree.delete(4, 1);
ret = pieceTree.findMatchesLineByLine(new Range(2, 2, 2, 5), new SearchData(/a/gi, null, 'a'), true, 1000);
assert.equal(ret.length, 1);
assert.deepEqual(ret[0].range, new Range(2, 2, 2, 3));
});
});

View File

@@ -8,18 +8,16 @@ import * as assert from 'assert';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel';
import { DefaultEndOfLine } from 'vs/editor/common/model';
import { UTF8_BOM_CHARACTER } from 'vs/base/common/strings';
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
function testGuessIndentation(defaultInsertSpaces: boolean, defaultTabSize: number, expectedInsertSpaces: boolean, expectedTabSize: number, text: string[], msg?: string): void {
var m = TextModel.createFromString(
var m = createTextModel(
text.join('\n'),
{
tabSize: defaultTabSize,
insertSpaces: defaultInsertSpaces,
detectIndentation: true,
defaultEOL: DefaultEndOfLine.LF,
trimAutoWhitespace: true
detectIndentation: true
}
);
var r = m.getOptions();
@@ -705,13 +703,9 @@ suite('Editor Model - TextModel', () => {
});
test('normalizeIndentation 1', () => {
let model = TextModel.createFromString('',
let model = createTextModel('',
{
detectIndentation: false,
tabSize: 4,
insertSpaces: false,
trimAutoWhitespace: true,
defaultEOL: DefaultEndOfLine.LF
insertSpaces: false
}
);
@@ -741,15 +735,7 @@ suite('Editor Model - TextModel', () => {
});
test('normalizeIndentation 2', () => {
let model = TextModel.createFromString('',
{
detectIndentation: false,
tabSize: 4,
insertSpaces: true,
trimAutoWhitespace: true,
defaultEOL: DefaultEndOfLine.LF
}
);
let model = createTextModel('');
assert.equal(model.normalizeIndentation('\ta'), ' a');
assert.equal(model.normalizeIndentation(' a'), ' a');

View File

@@ -366,6 +366,31 @@ suite('TextModelWithTokens regression tests', () => {
model.dispose();
registration.dispose();
});
test('issue #11856: Bracket matching does not work as expected if the opening brace symbol is contained in the closing brace symbol', () => {
const languageIdentifier = new LanguageIdentifier('testMode', LanguageId.PlainText);
let registration = LanguageConfigurationRegistry.register(languageIdentifier, {
brackets: [
['sequence', 'endsequence'],
['feature', 'endfeature']
]
});
let model = TextModel.createFromString([
'sequence "outer"',
' sequence "inner"',
' endsequence',
'endsequence',
].join('\n'), undefined, languageIdentifier);
let actual = model.matchBracket(new Position(3, 9));
assert.deepEqual(actual, [new Range(3, 6, 3, 17), new Range(2, 6, 2, 14)]);
model.dispose();
registration.dispose();
});
});
suite('TextModel.getLineIndentGuide', () => {
@@ -380,10 +405,39 @@ suite('TextModel.getLineIndentGuide', () => {
actual[line - 1] = [actualIndents[line - 1], model.getLineContent(line)];
}
// let expected = lines.map(l => l[0]);
assert.deepEqual(actual, lines);
// Also test getActiveIndentGuide
for (let lineNumber = 1; lineNumber <= model.getLineCount(); lineNumber++) {
let startLineNumber = lineNumber;
let endLineNumber = lineNumber;
let indent = actualIndents[lineNumber - 1];
if (indent !== 0) {
for (let i = lineNumber - 1; i >= 1; i--) {
const currIndent = actualIndents[i - 1];
if (currIndent >= indent) {
startLineNumber = i;
} else {
break;
}
}
for (let i = lineNumber + 1; i <= model.getLineCount(); i++) {
const currIndent = actualIndents[i - 1];
if (currIndent >= indent) {
endLineNumber = i;
} else {
break;
}
}
}
const expected = { startLineNumber, endLineNumber, indent };
const actual = model.getActiveIndentGuide(lineNumber, 1, model.getLineCount());
assert.deepEqual(actual, expected, `line number ${lineNumber}`);
}
model.dispose();
}