Merge from vscode 073a24de05773f2261f89172987002dc0ae2f1cd (#9711)

This commit is contained in:
Anthony Dresser
2020-03-24 00:24:15 -07:00
committed by GitHub
parent 29741d684e
commit 89ef1b0c2e
226 changed files with 6161 additions and 3288 deletions

View File

@@ -1348,7 +1348,7 @@ suite('Editor Controller - Regression tests', () => {
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello world ');
assertCursor(cursor, new Position(1, 13));
assertCursor(cursor, new Selection(1, 12, 1, 13));
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello world');

View File

@@ -54,7 +54,7 @@ for (let fileSize of fileSizes) {
fn: (textBuffer) => {
// for line model, this loop doesn't reflect the real situation.
for (const edit of edits) {
textBuffer.applyEdits([edit], false);
textBuffer.applyEdits([edit], false, false);
}
}
});
@@ -67,7 +67,7 @@ for (let fileSize of fileSizes) {
},
preCycle: (textBuffer) => {
for (const edit of edits) {
textBuffer.applyEdits([edit], false);
textBuffer.applyEdits([edit], false, false);
}
return textBuffer;
},
@@ -91,7 +91,7 @@ for (let fileSize of fileSizes) {
},
preCycle: (textBuffer) => {
for (const edit of edits) {
textBuffer.applyEdits([edit], false);
textBuffer.applyEdits([edit], false, false);
}
return textBuffer;
},
@@ -121,7 +121,7 @@ for (let fileSize of fileSizes) {
},
preCycle: (textBuffer) => {
for (const edit of edits) {
textBuffer.applyEdits([edit], false);
textBuffer.applyEdits([edit], false, false);
}
return textBuffer;
},
@@ -134,4 +134,4 @@ for (let fileSize of fileSizes) {
editsSuite.run();
}
}
}

View File

@@ -41,10 +41,10 @@ for (let fileSize of fileSizes) {
return textBuffer;
},
fn: (textBuffer) => {
textBuffer.applyEdits(edits.slice(0, i), false);
textBuffer.applyEdits(edits.slice(0, i), false, false);
}
});
}
replaceSuite.run();
}
}

View File

@@ -1104,7 +1104,7 @@ suite('EditorModel - EditableTextModel.applyEdits', () => {
{ 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' }
]);
], true);
model.applyEdits(undoEdits);

View File

@@ -17,7 +17,7 @@ export function testApplyEditsWithSyncedModels(original: string[], edits: IIdent
assertSyncedModels(originalStr, (model, assertMirrorModels) => {
// Apply edits & collect inverse edits
let inverseEdits = model.applyEdits(edits);
let inverseEdits = model.applyEdits(edits, true);
// Assert edits produced expected result
assert.deepEqual(model.getValue(EndOfLinePreference.LF), expectedStr);
@@ -25,7 +25,7 @@ export function testApplyEditsWithSyncedModels(original: string[], edits: IIdent
assertMirrorModels();
// Apply the inverse edits
let inverseInverseEdits = model.applyEdits(inverseEdits);
let inverseInverseEdits = model.applyEdits(inverseEdits, true);
// Assert the inverse edits brought back model to original state
assert.deepEqual(model.getValue(EndOfLinePreference.LF), originalStr);
@@ -36,8 +36,8 @@ export function testApplyEditsWithSyncedModels(original: string[], edits: IIdent
identifier: edit.identifier,
range: edit.range,
text: edit.text,
forceMoveMarkers: edit.forceMoveMarkers,
isAutoWhitespaceEdit: edit.isAutoWhitespaceEdit
forceMoveMarkers: edit.forceMoveMarkers || false,
isAutoWhitespaceEdit: edit.isAutoWhitespaceEdit || false
};
};
// Assert the inverse of the inverse edits are the original edits

View File

@@ -18,7 +18,10 @@ suite('PieceTreeTextBuffer._getInverseEdits', () => {
range: new Range(startLineNumber, startColumn, endLineNumber, endColumn),
rangeOffset: 0,
rangeLength: 0,
lines: text,
text: text ? text.join('\n') : '',
eolCount: text ? text.length - 1 : 0,
firstLineLength: text ? text[0].length : 0,
lastLineLength: text ? text[text.length - 1].length : 0,
forceMoveMarkers: false,
isAutoWhitespaceEdit: false
};
@@ -269,7 +272,10 @@ suite('PieceTreeTextBuffer._toSingleEditOperation', () => {
range: new Range(startLineNumber, startColumn, endLineNumber, endColumn),
rangeOffset: rangeOffset,
rangeLength: rangeLength,
lines: text,
text: text ? text.join('\n') : '',
eolCount: text ? text.length - 1 : 0,
firstLineLength: text ? text[0].length : 0,
lastLineLength: text ? text[text.length - 1].length : 0,
forceMoveMarkers: false,
isAutoWhitespaceEdit: false
};

View File

@@ -330,7 +330,7 @@ suite('Editor Model - Model', () => {
let res = thisModel.applyEdits([
{ range: new Range(2, 1, 2, 1), text: 'a' },
{ range: new Range(1, 1, 1, 1), text: 'b' },
]);
], true);
assert.deepEqual(res[0].range, new Range(2, 1, 2, 2));
assert.deepEqual(res[1].range, new Range(1, 1, 1, 2));

View File

@@ -50,14 +50,14 @@ suite('Editor Model - Model Edit Operation', () => {
function assertSingleEditOp(singleEditOp: IIdentifiedSingleEditOperation, editedLines: string[]) {
let editOp = [singleEditOp];
let inverseEditOp = model.applyEdits(editOp);
let inverseEditOp = model.applyEdits(editOp, true);
assert.equal(model.getLineCount(), editedLines.length);
for (let i = 0; i < editedLines.length; i++) {
assert.equal(model.getLineContent(i + 1), editedLines[i]);
}
let originalOp = model.applyEdits(inverseEditOp);
let originalOp = model.applyEdits(inverseEditOp, true);
assert.equal(model.getLineCount(), 5);
assert.equal(model.getLineContent(1), LINE1);
@@ -71,8 +71,8 @@ suite('Editor Model - Model Edit Operation', () => {
identifier: edit.identifier,
range: edit.range,
text: edit.text,
forceMoveMarkers: edit.forceMoveMarkers,
isAutoWhitespaceEdit: edit.isAutoWhitespaceEdit
forceMoveMarkers: edit.forceMoveMarkers || false,
isAutoWhitespaceEdit: edit.isAutoWhitespaceEdit || false
};
};
assert.deepEqual(originalOp.map(simplifyEdit), editOp.map(simplifyEdit));

View File

@@ -0,0 +1,269 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { compressConsecutiveTextChanges, TextChange } from 'vs/editor/common/model/textChange';
const GENERATE_TESTS = false;
interface IGeneratedEdit {
offset: number;
length: number;
text: string;
}
suite('TextChangeCompressor', () => {
function getResultingContent(initialContent: string, edits: IGeneratedEdit[]): string {
let content = initialContent;
for (let i = edits.length - 1; i >= 0; i--) {
content = (
content.substring(0, edits[i].offset) +
edits[i].text +
content.substring(edits[i].offset + edits[i].length)
);
}
return content;
}
function getTextChanges(initialContent: string, edits: IGeneratedEdit[]): TextChange[] {
let content = initialContent;
let changes: TextChange[] = new Array<TextChange>(edits.length);
let deltaOffset = 0;
for (let i = 0; i < edits.length; i++) {
let edit = edits[i];
let position = edit.offset + deltaOffset;
let length = edit.length;
let text = edit.text;
let oldText = content.substr(position, length);
content = (
content.substr(0, position) +
text +
content.substr(position + length)
);
changes[i] = new TextChange(edit.offset, oldText, position, text);
deltaOffset += text.length - length;
}
return changes;
}
function assertCompression(initialText: string, edit1: IGeneratedEdit[], edit2: IGeneratedEdit[]): void {
let tmpText = getResultingContent(initialText, edit1);
let chg1 = getTextChanges(initialText, edit1);
let finalText = getResultingContent(tmpText, edit2);
let chg2 = getTextChanges(tmpText, edit2);
let compressedTextChanges = compressConsecutiveTextChanges(chg1, chg2);
// Check that the compression was correct
let compressedDoTextEdits: IGeneratedEdit[] = compressedTextChanges.map((change) => {
return {
offset: change.oldPosition,
length: change.oldLength,
text: change.newText
};
});
let actualDoResult = getResultingContent(initialText, compressedDoTextEdits);
assert.equal(actualDoResult, finalText);
let compressedUndoTextEdits: IGeneratedEdit[] = compressedTextChanges.map((change) => {
return {
offset: change.newPosition,
length: change.newLength,
text: change.oldText
};
});
let actualUndoResult = getResultingContent(finalText, compressedUndoTextEdits);
assert.equal(actualUndoResult, initialText);
}
test('simple 1', () => {
assertCompression(
'',
[{ offset: 0, length: 0, text: 'h' }],
[{ offset: 1, length: 0, text: 'e' }]
);
});
test('simple 2', () => {
assertCompression(
'|',
[{ offset: 0, length: 0, text: 'h' }],
[{ offset: 2, length: 0, text: 'e' }]
);
});
test('complex1', () => {
assertCompression(
'abcdefghij',
[
{ offset: 0, length: 3, text: 'qh' },
{ offset: 5, length: 0, text: '1' },
{ offset: 8, length: 2, text: 'X' }
],
[
{ offset: 1, length: 0, text: 'Z' },
{ offset: 3, length: 3, text: 'Y' },
]
);
});
test('gen1', () => {
assertCompression(
'kxm',
[{ offset: 0, length: 1, text: 'tod_neu' }],
[{ offset: 1, length: 2, text: 'sag_e' }]
);
});
test('gen2', () => {
assertCompression(
'kpb_r_v',
[{ offset: 5, length: 2, text: 'a_jvf_l' }],
[{ offset: 10, length: 2, text: 'w' }]
);
});
test('gen3', () => {
assertCompression(
'slu_w',
[{ offset: 4, length: 1, text: '_wfw' }],
[{ offset: 3, length: 5, text: '' }]
);
});
test('gen4', () => {
assertCompression(
'_e',
[{ offset: 2, length: 0, text: 'zo_b' }],
[{ offset: 1, length: 3, text: 'tra' }]
);
});
test('gen5', () => {
assertCompression(
'ssn_',
[{ offset: 0, length: 2, text: 'tat_nwe' }],
[{ offset: 2, length: 6, text: 'jm' }]
);
});
test('gen6', () => {
assertCompression(
'kl_nru',
[{ offset: 4, length: 1, text: '' }],
[{ offset: 1, length: 4, text: '__ut' }]
);
});
const _a = 'a'.charCodeAt(0);
const _z = 'z'.charCodeAt(0);
function getRandomInt(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function getRandomString(minLength: number, maxLength: number): string {
const length = getRandomInt(minLength, maxLength);
let r = '';
for (let i = 0; i < length; i++) {
r += String.fromCharCode(getRandomInt(_a, _z));
}
return r;
}
function getRandomEOL(): string {
switch (getRandomInt(1, 3)) {
case 1: return '\r';
case 2: return '\n';
case 3: return '\r\n';
}
throw new Error(`not possible`);
}
function getRandomBuffer(small: boolean): string {
let lineCount = getRandomInt(1, small ? 3 : 10);
let lines: string[] = [];
for (let i = 0; i < lineCount; i++) {
lines.push(getRandomString(0, small ? 3 : 10) + getRandomEOL());
}
return lines.join('');
}
function getRandomEdits(content: string, min: number = 1, max: number = 5): IGeneratedEdit[] {
let result: IGeneratedEdit[] = [];
let cnt = getRandomInt(min, max);
let maxOffset = content.length;
while (cnt > 0 && maxOffset > 0) {
let offset = getRandomInt(0, maxOffset);
let length = getRandomInt(0, maxOffset - offset);
let text = getRandomBuffer(true);
result.push({
offset: offset,
length: length,
text: text
});
maxOffset = offset;
cnt--;
}
result.reverse();
return result;
}
class GeneratedTest {
private readonly _content: string;
private readonly _edits1: IGeneratedEdit[];
private readonly _edits2: IGeneratedEdit[];
constructor() {
this._content = getRandomBuffer(false).replace(/\n/g, '_');
this._edits1 = getRandomEdits(this._content, 1, 5).map((e) => { return { offset: e.offset, length: e.length, text: e.text.replace(/\n/g, '_') }; });
let tmp = getResultingContent(this._content, this._edits1);
this._edits2 = getRandomEdits(tmp, 1, 5).map((e) => { return { offset: e.offset, length: e.length, text: e.text.replace(/\n/g, '_') }; });
}
public print(): void {
console.log(`assertCompression(${JSON.stringify(this._content)}, ${JSON.stringify(this._edits1)}, ${JSON.stringify(this._edits2)});`);
}
public assert(): void {
assertCompression(this._content, this._edits1, this._edits2);
}
}
if (GENERATE_TESTS) {
let testNumber = 0;
while (true) {
testNumber++;
console.log(`------RUNNING TextChangeCompressor TEST ${testNumber}`);
let test = new GeneratedTest();
try {
test.assert();
} catch (err) {
console.log(err);
test.print();
break;
}
}
}
});

View File

@@ -9,10 +9,11 @@ import * as platform from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { createStringBuilder } from 'vs/editor/common/core/stringBuilder';
import { DefaultEndOfLine } from 'vs/editor/common/model';
import { createTextBuffer } from 'vs/editor/common/model/textModel';
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
import { ModelServiceImpl, MAINTAIN_UNDO_REDO_STACK } from 'vs/editor/common/services/modelServiceImpl';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
@@ -33,7 +34,8 @@ suite('ModelService', () => {
configService.setUserConfiguration('files', { 'eol': '\n' });
configService.setUserConfiguration('files', { 'eol': '\r\n' }, URI.file(platform.isWindows ? 'c:\\myroot' : '/myroot'));
modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), new UndoRedoService(new TestDialogService(), new TestNotificationService()));
const dialogService = new TestDialogService();
modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), new UndoRedoService(dialogService, new TestNotificationService()), dialogService);
});
teardown(() => {
@@ -307,6 +309,75 @@ suite('ModelService', () => {
];
assertComputeEdits(file1, file2);
});
if (MAINTAIN_UNDO_REDO_STACK) {
test('maintains undo for same resource and same content', () => {
const resource = URI.parse('file://test.txt');
// create a model
const model1 = modelService.createModel('text', null, resource);
// make an edit
model1.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]);
assert.equal(model1.getValue(), 'text1');
// dispose it
modelService.destroyModel(resource);
// create a new model with the same content
const model2 = modelService.createModel('text1', null, resource);
// undo
model2.undo();
assert.equal(model2.getValue(), 'text');
});
test('maintains version id and alternative version id for same resource and same content', () => {
const resource = URI.parse('file://test.txt');
// create a model
const model1 = modelService.createModel('text', null, resource);
// make an edit
model1.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]);
assert.equal(model1.getValue(), 'text1');
const versionId = model1.getVersionId();
const alternativeVersionId = model1.getAlternativeVersionId();
// dispose it
modelService.destroyModel(resource);
// create a new model with the same content
const model2 = modelService.createModel('text1', null, resource);
assert.equal(model2.getVersionId(), versionId);
assert.equal(model2.getAlternativeVersionId(), alternativeVersionId);
});
}
test('does not maintain undo for same resource and different content', () => {
const resource = URI.parse('file://test.txt');
// create a model
const model1 = modelService.createModel('text', null, resource);
// make an edit
model1.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]);
assert.equal(model1.getValue(), 'text1');
// dispose it
modelService.destroyModel(resource);
// create a new model with the same content
const model2 = modelService.createModel('text2', null, resource);
// undo
model2.undo();
assert.equal(model2.getValue(), 'text2');
});
test('setValue should clear undo stack', () => {
const resource = URI.parse('file://test.txt');
const model = modelService.createModel('text', null, resource);
model.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]);
assert.equal(model.getValue(), 'text1');
model.setValue('text2');
model.undo();
assert.equal(model.getValue(), 'text2');
});
});
function assertComputeEdits(lines1: string[], lines2: string[]): void {