mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-17 13:50:46 +01:00
409 lines
13 KiB
TypeScript
409 lines
13 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* 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 URI from 'vs/base/common/uri';
|
|
import { ExtHostDocumentData } from 'vs/workbench/api/node/extHostDocuments';
|
|
import { Position } from 'vs/workbench/api/node/extHostTypes';
|
|
import { Range as CodeEditorRange } from 'vs/editor/common/core/range';
|
|
import * as EditorCommon from 'vs/editor/common/editorCommon';
|
|
|
|
|
|
suite('ExtHostDocument', () => {
|
|
|
|
let data: ExtHostDocumentData;
|
|
|
|
function assertPositionAt(offset: number, line: number, character: number) {
|
|
let position = data.positionAt(offset);
|
|
assert.equal(position.line, line);
|
|
assert.equal(position.character, character);
|
|
}
|
|
|
|
function assertOffsetAt(line: number, character: number, offset: number) {
|
|
let pos = new Position(line, character);
|
|
let actual = data.offsetAt(pos);
|
|
assert.equal(actual, offset);
|
|
}
|
|
|
|
setup(function () {
|
|
data = new ExtHostDocumentData(undefined, URI.file(''), [
|
|
'This is line one', //16
|
|
'and this is line number two', //27
|
|
'it is followed by #3', //20
|
|
'and finished with the fourth.', //29
|
|
], '\n', 'text', 1, false);
|
|
});
|
|
|
|
test('readonly-ness', function () {
|
|
|
|
assert.throws(() => data.document.uri = null);
|
|
assert.throws(() => data.document.fileName = 'foofile');
|
|
assert.throws(() => data.document.isDirty = false);
|
|
assert.throws(() => data.document.isUntitled = false);
|
|
assert.throws(() => data.document.languageId = 'dddd');
|
|
assert.throws(() => data.document.lineCount = 9);
|
|
});
|
|
|
|
test('lines', function () {
|
|
|
|
assert.equal(data.document.lineCount, 4);
|
|
assert.throws(() => data.document.lineCount = 9);
|
|
|
|
assert.throws(() => data.lineAt(-1));
|
|
assert.throws(() => data.lineAt(data.document.lineCount));
|
|
assert.throws(() => data.lineAt(Number.MAX_VALUE));
|
|
assert.throws(() => data.lineAt(Number.MIN_VALUE));
|
|
assert.throws(() => data.lineAt(0.8));
|
|
|
|
let line = data.lineAt(0);
|
|
assert.equal(line.lineNumber, 0);
|
|
assert.equal(line.text.length, 16);
|
|
assert.equal(line.text, 'This is line one');
|
|
assert.equal(line.isEmptyOrWhitespace, false);
|
|
assert.equal(line.firstNonWhitespaceCharacterIndex, 0);
|
|
|
|
data.onEvents([{
|
|
range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 },
|
|
text: '\t ',
|
|
eol: undefined,
|
|
isRedoing: undefined,
|
|
isUndoing: undefined,
|
|
versionId: undefined,
|
|
rangeLength: undefined,
|
|
}]);
|
|
|
|
// line didn't change
|
|
assert.equal(line.text, 'This is line one');
|
|
assert.equal(line.firstNonWhitespaceCharacterIndex, 0);
|
|
|
|
// fetch line again
|
|
line = data.lineAt(0);
|
|
assert.equal(line.text, '\t This is line one');
|
|
assert.equal(line.firstNonWhitespaceCharacterIndex, 2);
|
|
});
|
|
|
|
test('line, issue #5704', function () {
|
|
|
|
let line = data.document.lineAt(0);
|
|
let {range, rangeIncludingLineBreak} = line;
|
|
assert.equal(range.end.line, 0);
|
|
assert.equal(range.end.character, 16);
|
|
assert.equal(rangeIncludingLineBreak.end.line, 1);
|
|
assert.equal(rangeIncludingLineBreak.end.character, 0);
|
|
|
|
line = data.document.lineAt(data.document.lineCount - 1);
|
|
range = line.range;
|
|
rangeIncludingLineBreak = line.rangeIncludingLineBreak;
|
|
assert.equal(range.end.line, 3);
|
|
assert.equal(range.end.character, 29);
|
|
assert.equal(rangeIncludingLineBreak.end.line, 3);
|
|
assert.equal(rangeIncludingLineBreak.end.character, 29);
|
|
|
|
});
|
|
|
|
test('offsetAt', function () {
|
|
assertOffsetAt(0, 0, 0);
|
|
assertOffsetAt(0, 1, 1);
|
|
assertOffsetAt(0, 16, 16);
|
|
assertOffsetAt(1, 0, 17);
|
|
assertOffsetAt(1, 3, 20);
|
|
assertOffsetAt(2, 0, 45);
|
|
assertOffsetAt(4, 29, 95);
|
|
assertOffsetAt(4, 30, 95);
|
|
assertOffsetAt(4, Number.MAX_VALUE, 95);
|
|
assertOffsetAt(5, 29, 95);
|
|
assertOffsetAt(Number.MAX_VALUE, 29, 95);
|
|
assertOffsetAt(Number.MAX_VALUE, Number.MAX_VALUE, 95);
|
|
});
|
|
|
|
test('offsetAt, after remove', function () {
|
|
|
|
data.onEvents([{
|
|
range: { startLineNumber: 1, startColumn: 3, endLineNumber: 1, endColumn: 6 },
|
|
text: '',
|
|
eol: undefined,
|
|
isRedoing: undefined,
|
|
isUndoing: undefined,
|
|
versionId: undefined,
|
|
rangeLength: undefined,
|
|
}]);
|
|
|
|
assertOffsetAt(0, 1, 1);
|
|
assertOffsetAt(0, 13, 13);
|
|
assertOffsetAt(1, 0, 14);
|
|
});
|
|
|
|
test('offsetAt, after replace', function () {
|
|
|
|
data.onEvents([{
|
|
range: { startLineNumber: 1, startColumn: 3, endLineNumber: 1, endColumn: 6 },
|
|
text: 'is could be',
|
|
eol: undefined,
|
|
isRedoing: undefined,
|
|
isUndoing: undefined,
|
|
versionId: undefined,
|
|
rangeLength: undefined,
|
|
}]);
|
|
|
|
assertOffsetAt(0, 1, 1);
|
|
assertOffsetAt(0, 24, 24);
|
|
assertOffsetAt(1, 0, 25);
|
|
});
|
|
|
|
test('offsetAt, after insert line', function () {
|
|
|
|
data.onEvents([{
|
|
range: { startLineNumber: 1, startColumn: 3, endLineNumber: 1, endColumn: 6 },
|
|
text: 'is could be\na line with number',
|
|
eol: undefined,
|
|
isRedoing: undefined,
|
|
isUndoing: undefined,
|
|
versionId: undefined,
|
|
rangeLength: undefined,
|
|
}]);
|
|
|
|
assertOffsetAt(0, 1, 1);
|
|
assertOffsetAt(0, 13, 13);
|
|
assertOffsetAt(1, 0, 14);
|
|
assertOffsetAt(1, 18, 13 + 1 + 18);
|
|
assertOffsetAt(1, 29, 13 + 1 + 29);
|
|
assertOffsetAt(2, 0, 13 + 1 + 29 + 1);
|
|
});
|
|
|
|
test('offsetAt, after remove line', function () {
|
|
|
|
data.onEvents([{
|
|
range: { startLineNumber: 1, startColumn: 3, endLineNumber: 2, endColumn: 6 },
|
|
text: '',
|
|
eol: undefined,
|
|
isRedoing: undefined,
|
|
isUndoing: undefined,
|
|
versionId: undefined,
|
|
rangeLength: undefined,
|
|
}]);
|
|
|
|
assertOffsetAt(0, 1, 1);
|
|
assertOffsetAt(0, 2, 2);
|
|
assertOffsetAt(1, 0, 25);
|
|
});
|
|
|
|
test('positionAt', function () {
|
|
assertPositionAt(0, 0, 0);
|
|
assertPositionAt(Number.MIN_VALUE, 0, 0);
|
|
assertPositionAt(1, 0, 1);
|
|
assertPositionAt(16, 0, 16);
|
|
assertPositionAt(17, 1, 0);
|
|
assertPositionAt(20, 1, 3);
|
|
assertPositionAt(45, 2, 0);
|
|
assertPositionAt(95, 3, 29);
|
|
assertPositionAt(96, 3, 29);
|
|
assertPositionAt(99, 3, 29);
|
|
assertPositionAt(Number.MAX_VALUE, 3, 29);
|
|
});
|
|
|
|
test('getWordRangeAtPosition', function () {
|
|
data = new ExtHostDocumentData(undefined, URI.file(''), [
|
|
'aaaa bbbb cccc abc'
|
|
], '\n', 'text', 1, false);
|
|
|
|
let range = data.getWordRangeAtPosition(new Position(0, 2));
|
|
assert.equal(range.start.line, 0);
|
|
assert.equal(range.start.character, 0);
|
|
assert.equal(range.end.line, 0);
|
|
assert.equal(range.end.character, 4);
|
|
|
|
range = data.getWordRangeAtPosition(new Position(0, 2), /.*/);
|
|
assert.equal(range.start.line, 0);
|
|
assert.equal(range.start.character, 0);
|
|
assert.equal(range.end.line, 0);
|
|
assert.equal(range.end.character, 4);
|
|
|
|
range = data.getWordRangeAtPosition(new Position(0, 2), /a+.+?c/);
|
|
assert.equal(range.start.line, 0);
|
|
assert.equal(range.start.character, 0);
|
|
assert.equal(range.end.line, 0);
|
|
assert.equal(range.end.character, 11);
|
|
|
|
range = data.getWordRangeAtPosition(new Position(0, 17), /a+.+?c/);
|
|
assert.equal(range.start.line, 0);
|
|
assert.equal(range.start.character, 15);
|
|
assert.equal(range.end.line, 0);
|
|
assert.equal(range.end.character, 18);
|
|
|
|
range = data.getWordRangeAtPosition(new Position(0, 11), /yy/);
|
|
assert.equal(range, undefined);
|
|
});
|
|
});
|
|
|
|
enum AssertDocumentLineMappingDirection {
|
|
OffsetToPosition,
|
|
PositionToOffset
|
|
}
|
|
|
|
suite('ExtHostDocument updates line mapping', () => {
|
|
|
|
function positionToStr(position: { line: number; character: number; }): string {
|
|
return '(' + position.line + ',' + position.character + ')';
|
|
}
|
|
|
|
function assertDocumentLineMapping(doc: ExtHostDocumentData, direction: AssertDocumentLineMappingDirection): void {
|
|
let allText = doc.getText();
|
|
|
|
let line = 0, character = 0, previousIsCarriageReturn = false;
|
|
for (let offset = 0; offset <= allText.length; offset++) {
|
|
// The position coordinate system cannot express the position between \r and \n
|
|
let position = new Position(line, character + (previousIsCarriageReturn ? -1 : 0));
|
|
|
|
if (direction === AssertDocumentLineMappingDirection.OffsetToPosition) {
|
|
let actualPosition = doc.positionAt(offset);
|
|
assert.equal(positionToStr(actualPosition), positionToStr(position), 'positionAt mismatch for offset ' + offset);
|
|
} else {
|
|
// The position coordinate system cannot express the position between \r and \n
|
|
let expectedOffset = offset + (previousIsCarriageReturn ? -1 : 0);
|
|
let actualOffset = doc.offsetAt(position);
|
|
assert.equal(actualOffset, expectedOffset, 'offsetAt mismatch for position ' + positionToStr(position));
|
|
}
|
|
|
|
if (allText.charAt(offset) === '\n') {
|
|
line++;
|
|
character = 0;
|
|
} else {
|
|
character++;
|
|
}
|
|
|
|
previousIsCarriageReturn = (allText.charAt(offset) === '\r');
|
|
}
|
|
}
|
|
|
|
function createChangeEvent(range: CodeEditorRange, text: string, eol?: string): EditorCommon.IModelContentChangedEvent2 {
|
|
return {
|
|
range: range,
|
|
text: text,
|
|
eol: eol,
|
|
isRedoing: undefined,
|
|
isUndoing: undefined,
|
|
versionId: undefined,
|
|
rangeLength: undefined,
|
|
};
|
|
}
|
|
|
|
function testLineMappingDirectionAfterEvents(lines: string[], eol: string, direction: AssertDocumentLineMappingDirection, events: EditorCommon.IModelContentChangedEvent2[]): void {
|
|
let myDocument = new ExtHostDocumentData(undefined, URI.file(''), lines.slice(0), eol, 'text', 1, false);
|
|
assertDocumentLineMapping(myDocument, direction);
|
|
|
|
myDocument.onEvents(events);
|
|
assertDocumentLineMapping(myDocument, direction);
|
|
}
|
|
|
|
function testLineMappingAfterEvents(lines: string[], events: EditorCommon.IModelContentChangedEvent2[]): void {
|
|
testLineMappingDirectionAfterEvents(lines, '\n', AssertDocumentLineMappingDirection.PositionToOffset, events);
|
|
testLineMappingDirectionAfterEvents(lines, '\n', AssertDocumentLineMappingDirection.OffsetToPosition, events);
|
|
|
|
testLineMappingDirectionAfterEvents(lines, '\r\n', AssertDocumentLineMappingDirection.PositionToOffset, events);
|
|
testLineMappingDirectionAfterEvents(lines, '\r\n', AssertDocumentLineMappingDirection.OffsetToPosition, events);
|
|
}
|
|
|
|
test('line mapping', () => {
|
|
testLineMappingAfterEvents([
|
|
'This is line one',
|
|
'and this is line number two',
|
|
'it is followed by #3',
|
|
'and finished with the fourth.',
|
|
], []);
|
|
});
|
|
|
|
test('after remove', () => {
|
|
testLineMappingAfterEvents([
|
|
'This is line one',
|
|
'and this is line number two',
|
|
'it is followed by #3',
|
|
'and finished with the fourth.',
|
|
], [createChangeEvent(new CodeEditorRange(1, 3, 1, 6), '')]);
|
|
});
|
|
|
|
test('after replace', () => {
|
|
testLineMappingAfterEvents([
|
|
'This is line one',
|
|
'and this is line number two',
|
|
'it is followed by #3',
|
|
'and finished with the fourth.',
|
|
], [createChangeEvent(new CodeEditorRange(1, 3, 1, 6), 'is could be')]);
|
|
});
|
|
|
|
test('after insert line', () => {
|
|
testLineMappingAfterEvents([
|
|
'This is line one',
|
|
'and this is line number two',
|
|
'it is followed by #3',
|
|
'and finished with the fourth.',
|
|
], [createChangeEvent(new CodeEditorRange(1, 3, 1, 6), 'is could be\na line with number')]);
|
|
});
|
|
|
|
test('after insert two lines', () => {
|
|
testLineMappingAfterEvents([
|
|
'This is line one',
|
|
'and this is line number two',
|
|
'it is followed by #3',
|
|
'and finished with the fourth.',
|
|
], [createChangeEvent(new CodeEditorRange(1, 3, 1, 6), 'is could be\na line with number\nyet another line')]);
|
|
});
|
|
|
|
test('after remove line', () => {
|
|
testLineMappingAfterEvents([
|
|
'This is line one',
|
|
'and this is line number two',
|
|
'it is followed by #3',
|
|
'and finished with the fourth.',
|
|
], [createChangeEvent(new CodeEditorRange(1, 3, 2, 6), '')]);
|
|
});
|
|
|
|
test('after remove two lines', () => {
|
|
testLineMappingAfterEvents([
|
|
'This is line one',
|
|
'and this is line number two',
|
|
'it is followed by #3',
|
|
'and finished with the fourth.',
|
|
], [createChangeEvent(new CodeEditorRange(1, 3, 3, 6), '')]);
|
|
});
|
|
|
|
test('after deleting entire content', () => {
|
|
testLineMappingAfterEvents([
|
|
'This is line one',
|
|
'and this is line number two',
|
|
'it is followed by #3',
|
|
'and finished with the fourth.',
|
|
], [createChangeEvent(new CodeEditorRange(1, 3, 4, 30), '')]);
|
|
});
|
|
|
|
test('after replacing entire content', () => {
|
|
testLineMappingAfterEvents([
|
|
'This is line one',
|
|
'and this is line number two',
|
|
'it is followed by #3',
|
|
'and finished with the fourth.',
|
|
], [createChangeEvent(new CodeEditorRange(1, 3, 4, 30), 'some new text\nthat\nspans multiple lines')]);
|
|
});
|
|
|
|
test('after changing EOL to CRLF', () => {
|
|
testLineMappingAfterEvents([
|
|
'This is line one',
|
|
'and this is line number two',
|
|
'it is followed by #3',
|
|
'and finished with the fourth.',
|
|
], [createChangeEvent(new CodeEditorRange(1, 1, 1, 1), '', '\r\n')]);
|
|
});
|
|
|
|
test('after changing EOL to LF', () => {
|
|
testLineMappingAfterEvents([
|
|
'This is line one',
|
|
'and this is line number two',
|
|
'it is followed by #3',
|
|
'and finished with the fourth.',
|
|
], [createChangeEvent(new CodeEditorRange(1, 1, 1, 1), '', '\n')]);
|
|
});
|
|
});
|