Path: blob/main/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import { URI } from '../../../../base/common/uri.js';7import { ExtHostDocumentData } from '../../common/extHostDocumentData.js';8import { Position } from '../../common/extHostTypes.js';9import { Range } from '../../../../editor/common/core/range.js';10import { MainThreadDocumentsShape } from '../../common/extHost.protocol.js';11import { IModelChangedEvent } from '../../../../editor/common/model/mirrorTextModel.js';12import { mock } from '../../../../base/test/common/mock.js';13import * as perfData from './extHostDocumentData.test.perf-data.js';14import { setDefaultGetWordAtTextConfig } from '../../../../editor/common/core/wordHelper.js';15import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';1617suite('ExtHostDocumentData', () => {1819let data: ExtHostDocumentData;2021function assertPositionAt(offset: number, line: number, character: number) {22const position = data.document.positionAt(offset);23assert.strictEqual(position.line, line);24assert.strictEqual(position.character, character);25}2627function assertOffsetAt(line: number, character: number, offset: number) {28const pos = new Position(line, character);29const actual = data.document.offsetAt(pos);30assert.strictEqual(actual, offset);31}3233setup(function () {34data = new ExtHostDocumentData(undefined!, URI.file(''), [35'This is line one', //1636'and this is line number two', //2737'it is followed by #3', //2038'and finished with the fourth.', //2939], '\n', 1, 'text', false, 'utf8');40});4142ensureNoDisposablesAreLeakedInTestSuite();4344test('readonly-ness', () => {45assert.throws(() => (data as any).document.uri = null);46assert.throws(() => (data as any).document.fileName = 'foofile');47assert.throws(() => (data as any).document.isDirty = false);48assert.throws(() => (data as any).document.isUntitled = false);49assert.throws(() => (data as any).document.languageId = 'dddd');50assert.throws(() => (data as any).document.lineCount = 9);51});5253test('save, when disposed', function () {54let saved: URI;55const data = new ExtHostDocumentData(new class extends mock<MainThreadDocumentsShape>() {56override $trySaveDocument(uri: URI) {57assert.ok(!saved);58saved = uri;59return Promise.resolve(true);60}61}, URI.parse('foo:bar'), [], '\n', 1, 'text', true, 'utf8');6263return data.document.save().then(() => {64assert.strictEqual(saved.toString(), 'foo:bar');6566data.dispose();6768return data.document.save().then(() => {69assert.ok(false, 'expected failure');70}, err => {71assert.ok(err);72});73});74});7576test('read, when disposed', function () {77data.dispose();7879const { document } = data;80assert.strictEqual(document.lineCount, 4);81assert.strictEqual(document.lineAt(0).text, 'This is line one');82});8384test('lines', () => {8586assert.strictEqual(data.document.lineCount, 4);8788assert.throws(() => data.document.lineAt(-1));89assert.throws(() => data.document.lineAt(data.document.lineCount));90assert.throws(() => data.document.lineAt(Number.MAX_VALUE));91assert.throws(() => data.document.lineAt(Number.MIN_VALUE));92assert.throws(() => data.document.lineAt(0.8));9394let line = data.document.lineAt(0);95assert.strictEqual(line.lineNumber, 0);96assert.strictEqual(line.text.length, 16);97assert.strictEqual(line.text, 'This is line one');98assert.strictEqual(line.isEmptyOrWhitespace, false);99assert.strictEqual(line.firstNonWhitespaceCharacterIndex, 0);100101data.onEvents({102changes: [{103range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 },104rangeOffset: undefined!,105rangeLength: undefined!,106text: '\t '107}],108eol: undefined!,109versionId: undefined!,110isRedoing: false,111isUndoing: false,112});113114// line didn't change115assert.strictEqual(line.text, 'This is line one');116assert.strictEqual(line.firstNonWhitespaceCharacterIndex, 0);117118// fetch line again119line = data.document.lineAt(0);120assert.strictEqual(line.text, '\t This is line one');121assert.strictEqual(line.firstNonWhitespaceCharacterIndex, 2);122});123124test('line, issue #5704', function () {125126let line = data.document.lineAt(0);127let { range, rangeIncludingLineBreak } = line;128assert.strictEqual(range.end.line, 0);129assert.strictEqual(range.end.character, 16);130assert.strictEqual(rangeIncludingLineBreak.end.line, 1);131assert.strictEqual(rangeIncludingLineBreak.end.character, 0);132133line = data.document.lineAt(data.document.lineCount - 1);134range = line.range;135rangeIncludingLineBreak = line.rangeIncludingLineBreak;136assert.strictEqual(range.end.line, 3);137assert.strictEqual(range.end.character, 29);138assert.strictEqual(rangeIncludingLineBreak.end.line, 3);139assert.strictEqual(rangeIncludingLineBreak.end.character, 29);140141});142143test('offsetAt', () => {144assertOffsetAt(0, 0, 0);145assertOffsetAt(0, 1, 1);146assertOffsetAt(0, 16, 16);147assertOffsetAt(1, 0, 17);148assertOffsetAt(1, 3, 20);149assertOffsetAt(2, 0, 45);150assertOffsetAt(4, 29, 95);151assertOffsetAt(4, 30, 95);152assertOffsetAt(4, Number.MAX_VALUE, 95);153assertOffsetAt(5, 29, 95);154assertOffsetAt(Number.MAX_VALUE, 29, 95);155assertOffsetAt(Number.MAX_VALUE, Number.MAX_VALUE, 95);156});157158test('offsetAt, after remove', function () {159160data.onEvents({161changes: [{162range: { startLineNumber: 1, startColumn: 3, endLineNumber: 1, endColumn: 6 },163rangeOffset: undefined!,164rangeLength: undefined!,165text: ''166}],167eol: undefined!,168versionId: undefined!,169isRedoing: false,170isUndoing: false,171});172173assertOffsetAt(0, 1, 1);174assertOffsetAt(0, 13, 13);175assertOffsetAt(1, 0, 14);176});177178test('offsetAt, after replace', function () {179180data.onEvents({181changes: [{182range: { startLineNumber: 1, startColumn: 3, endLineNumber: 1, endColumn: 6 },183rangeOffset: undefined!,184rangeLength: undefined!,185text: 'is could be'186}],187eol: undefined!,188versionId: undefined!,189isRedoing: false,190isUndoing: false,191});192193assertOffsetAt(0, 1, 1);194assertOffsetAt(0, 24, 24);195assertOffsetAt(1, 0, 25);196});197198test('offsetAt, after insert line', function () {199200data.onEvents({201changes: [{202range: { startLineNumber: 1, startColumn: 3, endLineNumber: 1, endColumn: 6 },203rangeOffset: undefined!,204rangeLength: undefined!,205text: 'is could be\na line with number'206}],207eol: undefined!,208versionId: undefined!,209isRedoing: false,210isUndoing: false,211});212213assertOffsetAt(0, 1, 1);214assertOffsetAt(0, 13, 13);215assertOffsetAt(1, 0, 14);216assertOffsetAt(1, 18, 13 + 1 + 18);217assertOffsetAt(1, 29, 13 + 1 + 29);218assertOffsetAt(2, 0, 13 + 1 + 29 + 1);219});220221test('offsetAt, after remove line', function () {222223data.onEvents({224changes: [{225range: { startLineNumber: 1, startColumn: 3, endLineNumber: 2, endColumn: 6 },226rangeOffset: undefined!,227rangeLength: undefined!,228text: ''229}],230eol: undefined!,231versionId: undefined!,232isRedoing: false,233isUndoing: false,234});235236assertOffsetAt(0, 1, 1);237assertOffsetAt(0, 2, 2);238assertOffsetAt(1, 0, 25);239});240241test('positionAt', () => {242assertPositionAt(0, 0, 0);243assertPositionAt(Number.MIN_VALUE, 0, 0);244assertPositionAt(1, 0, 1);245assertPositionAt(16, 0, 16);246assertPositionAt(17, 1, 0);247assertPositionAt(20, 1, 3);248assertPositionAt(45, 2, 0);249assertPositionAt(95, 3, 29);250assertPositionAt(96, 3, 29);251assertPositionAt(99, 3, 29);252assertPositionAt(Number.MAX_VALUE, 3, 29);253});254255test('getWordRangeAtPosition', () => {256data = new ExtHostDocumentData(undefined!, URI.file(''), [257'aaaa bbbb+cccc abc'258], '\n', 1, 'text', false, 'utf8');259260let range = data.document.getWordRangeAtPosition(new Position(0, 2))!;261assert.strictEqual(range.start.line, 0);262assert.strictEqual(range.start.character, 0);263assert.strictEqual(range.end.line, 0);264assert.strictEqual(range.end.character, 4);265266// ignore bad regular expresson /.*/267assert.throws(() => data.document.getWordRangeAtPosition(new Position(0, 2), /.*/)!);268269range = data.document.getWordRangeAtPosition(new Position(0, 5), /[a-z+]+/)!;270assert.strictEqual(range.start.line, 0);271assert.strictEqual(range.start.character, 5);272assert.strictEqual(range.end.line, 0);273assert.strictEqual(range.end.character, 14);274275range = data.document.getWordRangeAtPosition(new Position(0, 17), /[a-z+]+/)!;276assert.strictEqual(range.start.line, 0);277assert.strictEqual(range.start.character, 15);278assert.strictEqual(range.end.line, 0);279assert.strictEqual(range.end.character, 18);280281range = data.document.getWordRangeAtPosition(new Position(0, 11), /yy/)!;282assert.strictEqual(range, undefined);283});284285test('getWordRangeAtPosition doesn\'t quite use the regex as expected, #29102', function () {286data = new ExtHostDocumentData(undefined!, URI.file(''), [287'some text here',288'/** foo bar */',289'function() {',290' "far boo"',291'}'292], '\n', 1, 'text', false, 'utf8');293294let range = data.document.getWordRangeAtPosition(new Position(0, 0), /\/\*.+\*\//);295assert.strictEqual(range, undefined);296297range = data.document.getWordRangeAtPosition(new Position(1, 0), /\/\*.+\*\//)!;298assert.strictEqual(range.start.line, 1);299assert.strictEqual(range.start.character, 0);300assert.strictEqual(range.end.line, 1);301assert.strictEqual(range.end.character, 14);302303range = data.document.getWordRangeAtPosition(new Position(3, 0), /("|').*\1/);304assert.strictEqual(range, undefined);305306range = data.document.getWordRangeAtPosition(new Position(3, 1), /("|').*\1/)!;307assert.strictEqual(range.start.line, 3);308assert.strictEqual(range.start.character, 1);309assert.strictEqual(range.end.line, 3);310assert.strictEqual(range.end.character, 10);311});312313314test('getWordRangeAtPosition can freeze the extension host #95319', function () {315316const regex = /(https?:\/\/github\.com\/(([^\s]+)\/([^\s]+))\/([^\s]+\/)?(issues|pull)\/([0-9]+))|(([^\s]+)\/([^\s]+))?#([1-9][0-9]*)($|[\s\:\;\-\(\=])/;317318data = new ExtHostDocumentData(undefined!, URI.file(''), [319perfData._$_$_expensive320], '\n', 1, 'text', false, 'utf8');321322// this test only ensures that we eventually give and timeout (when searching "funny" words and long lines)323// for the sake of speedy tests we lower the timeBudget here324const config = setDefaultGetWordAtTextConfig({ maxLen: 1000, windowSize: 15, timeBudget: 30 });325try {326let range = data.document.getWordRangeAtPosition(new Position(0, 1_177_170), regex)!;327assert.strictEqual(range, undefined);328329const pos = new Position(0, 1177170);330range = data.document.getWordRangeAtPosition(pos)!;331assert.ok(range);332assert.ok(range.contains(pos));333assert.strictEqual(data.document.getText(range), 'TaskDefinition');334335} finally {336config.dispose();337}338});339340test('Rename popup sometimes populates with text on the left side omitted #96013', function () {341342const regex = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\$\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g;343const line = 'int abcdefhijklmnopqwvrstxyz;';344345data = new ExtHostDocumentData(undefined!, URI.file(''), [346line347], '\n', 1, 'text', false, 'utf8');348349const range = data.document.getWordRangeAtPosition(new Position(0, 27), regex)!;350assert.strictEqual(range.start.line, 0);351assert.strictEqual(range.end.line, 0);352assert.strictEqual(range.start.character, 4);353assert.strictEqual(range.end.character, 28);354});355356test('Custom snippet $TM_SELECTED_TEXT not show suggestion #108892', function () {357358data = new ExtHostDocumentData(undefined!, URI.file(''), [359` <p><span xml:lang="en">Sheldon</span>, soprannominato "<span xml:lang="en">Shelly</span> dalla madre e dalla sorella, è nato a <span xml:lang="en">Galveston</span>, in <span xml:lang="en">Texas</span>, il 26 febbraio 1980 in un supermercato. È stato un bambino prodigio, come testimoniato dal suo quoziente d'intelligenza (187, di molto superiore alla norma) e dalla sua rapida carriera scolastica: si è diplomato all'eta di 11 anni approdando alla stessa età alla formazione universitaria e all'età di 16 anni ha ottenuto il suo primo dottorato di ricerca. All'inizio della serie e per gran parte di essa vive con il coinquilino Leonard nell'appartamento 4A al 2311 <span xml:lang="en">North Los Robles Avenue</span> di <span xml:lang="en">Pasadena</span>, per poi trasferirsi nell'appartamento di <span xml:lang="en">Penny</span> con <span xml:lang="en">Amy</span> nella decima stagione. Come più volte afferma lui stesso possiede una memoria eidetica e un orecchio assoluto. È stato educato da una madre estremamente religiosa e, in più occasioni, questo aspetto contrasta con il rigore scientifico di <span xml:lang="en">Sheldon</span>; tuttavia la donna sembra essere l'unica persona in grado di comandarlo a bacchetta.</p>`360], '\n', 1, 'text', false, 'utf8');361362const pos = new Position(0, 55);363const range = data.document.getWordRangeAtPosition(pos)!;364assert.strictEqual(range.start.line, 0);365assert.strictEqual(range.end.line, 0);366assert.strictEqual(range.start.character, 47);367assert.strictEqual(range.end.character, 61);368assert.strictEqual(data.document.getText(range), 'soprannominato');369});370});371372enum AssertDocumentLineMappingDirection {373OffsetToPosition,374PositionToOffset375}376377suite('ExtHostDocumentData updates line mapping', () => {378379function positionToStr(position: { line: number; character: number }): string {380return '(' + position.line + ',' + position.character + ')';381}382383function assertDocumentLineMapping(doc: ExtHostDocumentData, direction: AssertDocumentLineMappingDirection): void {384const allText = doc.getText();385386let line = 0, character = 0, previousIsCarriageReturn = false;387for (let offset = 0; offset <= allText.length; offset++) {388// The position coordinate system cannot express the position between \r and \n389const position: Position = new Position(line, character + (previousIsCarriageReturn ? -1 : 0));390391if (direction === AssertDocumentLineMappingDirection.OffsetToPosition) {392const actualPosition = doc.document.positionAt(offset);393assert.strictEqual(positionToStr(actualPosition), positionToStr(position), 'positionAt mismatch for offset ' + offset);394} else {395// The position coordinate system cannot express the position between \r and \n396const expectedOffset: number = offset + (previousIsCarriageReturn ? -1 : 0);397const actualOffset = doc.document.offsetAt(position);398assert.strictEqual(actualOffset, expectedOffset, 'offsetAt mismatch for position ' + positionToStr(position));399}400401if (allText.charAt(offset) === '\n') {402line++;403character = 0;404} else {405character++;406}407408previousIsCarriageReturn = (allText.charAt(offset) === '\r');409}410}411412function createChangeEvent(range: Range, text: string, eol?: string): IModelChangedEvent {413return {414changes: [{415range: range,416rangeOffset: undefined!,417rangeLength: undefined!,418text: text419}],420eol: eol!,421versionId: undefined!,422isRedoing: false,423isUndoing: false,424};425}426427function testLineMappingDirectionAfterEvents(lines: string[], eol: string, direction: AssertDocumentLineMappingDirection, e: IModelChangedEvent): void {428const myDocument = new ExtHostDocumentData(undefined!, URI.file(''), lines.slice(0), eol, 1, 'text', false, 'utf8');429assertDocumentLineMapping(myDocument, direction);430431myDocument.onEvents(e);432assertDocumentLineMapping(myDocument, direction);433}434435function testLineMappingAfterEvents(lines: string[], e: IModelChangedEvent): void {436testLineMappingDirectionAfterEvents(lines, '\n', AssertDocumentLineMappingDirection.PositionToOffset, e);437testLineMappingDirectionAfterEvents(lines, '\n', AssertDocumentLineMappingDirection.OffsetToPosition, e);438439testLineMappingDirectionAfterEvents(lines, '\r\n', AssertDocumentLineMappingDirection.PositionToOffset, e);440testLineMappingDirectionAfterEvents(lines, '\r\n', AssertDocumentLineMappingDirection.OffsetToPosition, e);441}442443ensureNoDisposablesAreLeakedInTestSuite();444445test('line mapping', () => {446testLineMappingAfterEvents([447'This is line one',448'and this is line number two',449'it is followed by #3',450'and finished with the fourth.',451], { changes: [], eol: undefined!, versionId: 7, isRedoing: false, isUndoing: false });452});453454test('after remove', () => {455testLineMappingAfterEvents([456'This is line one',457'and this is line number two',458'it is followed by #3',459'and finished with the fourth.',460], createChangeEvent(new Range(1, 3, 1, 6), ''));461});462463test('after replace', () => {464testLineMappingAfterEvents([465'This is line one',466'and this is line number two',467'it is followed by #3',468'and finished with the fourth.',469], createChangeEvent(new Range(1, 3, 1, 6), 'is could be'));470});471472test('after insert line', () => {473testLineMappingAfterEvents([474'This is line one',475'and this is line number two',476'it is followed by #3',477'and finished with the fourth.',478], createChangeEvent(new Range(1, 3, 1, 6), 'is could be\na line with number'));479});480481test('after insert two lines', () => {482testLineMappingAfterEvents([483'This is line one',484'and this is line number two',485'it is followed by #3',486'and finished with the fourth.',487], createChangeEvent(new Range(1, 3, 1, 6), 'is could be\na line with number\nyet another line'));488});489490test('after remove line', () => {491testLineMappingAfterEvents([492'This is line one',493'and this is line number two',494'it is followed by #3',495'and finished with the fourth.',496], createChangeEvent(new Range(1, 3, 2, 6), ''));497});498499test('after remove two lines', () => {500testLineMappingAfterEvents([501'This is line one',502'and this is line number two',503'it is followed by #3',504'and finished with the fourth.',505], createChangeEvent(new Range(1, 3, 3, 6), ''));506});507508test('after deleting entire content', () => {509testLineMappingAfterEvents([510'This is line one',511'and this is line number two',512'it is followed by #3',513'and finished with the fourth.',514], createChangeEvent(new Range(1, 3, 4, 30), ''));515});516517test('after replacing entire content', () => {518testLineMappingAfterEvents([519'This is line one',520'and this is line number two',521'it is followed by #3',522'and finished with the fourth.',523], createChangeEvent(new Range(1, 3, 4, 30), 'some new text\nthat\nspans multiple lines'));524});525526test('after changing EOL to CRLF', () => {527testLineMappingAfterEvents([528'This is line one',529'and this is line number two',530'it is followed by #3',531'and finished with the fourth.',532], createChangeEvent(new Range(1, 1, 1, 1), '', '\r\n'));533});534535test('after changing EOL to LF', () => {536testLineMappingAfterEvents([537'This is line one',538'and this is line number two',539'it is followed by #3',540'and finished with the fourth.',541], createChangeEvent(new Range(1, 1, 1, 1), '', '\n'));542});543});544545546