mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge from vscode 073a24de05773f2261f89172987002dc0ae2f1cd (#9711)
This commit is contained in:
@@ -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');
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
269
src/vs/editor/test/common/model/textChange.test.ts
Normal file
269
src/vs/editor/test/common/model/textChange.test.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user