Path: blob/main/src/vs/editor/contrib/inlineCompletions/test/browser/editKind.test.ts
4798 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 { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';7import { Position } from '../../../../common/core/position.js';8import { OffsetRange } from '../../../../common/core/ranges/offsetRange.js';9import { StringEdit } from '../../../../common/core/edits/stringEdit.js';10import { createTextModel } from '../../../../test/common/testTextModel.js';11import { computeEditKind, InsertProperties, DeleteProperties, ReplaceProperties } from '../../browser/model/editKind.js';1213suite('computeEditKind', () => {14ensureNoDisposablesAreLeakedInTestSuite();1516suite('Insert operations', () => {17test('single character insert - syntactical', () => {18const model = createTextModel('hello world');19const edit = StringEdit.insert(5, ';');20const result = computeEditKind(edit, model);2122assert.ok(result);23assert.strictEqual(result.edits.length, 1);24assert.strictEqual(result.edits[0].operation, 'insert');25assert.strictEqual(result.edits[0].charactersInserted, 1);26assert.strictEqual(result.edits[0].charactersDeleted, 0);27assert.strictEqual(result.edits[0].linesInserted, 0);28assert.strictEqual(result.edits[0].linesDeleted, 0);29const props = result.edits[0].properties as InsertProperties;30assert.strictEqual(props.textShape.kind, 'singleLine');31if (props.textShape.kind === 'singleLine') {32assert.strictEqual(props.textShape.isSingleCharacter, true);33assert.strictEqual(props.textShape.singleCharacterKind, 'syntactical');34}35model.dispose();36});3738test('single character insert - identifier', () => {39const model = createTextModel('hello world');40const edit = StringEdit.insert(5, 'a');41const result = computeEditKind(edit, model);4243assert.ok(result);44assert.strictEqual(result.edits.length, 1);45assert.strictEqual(result.edits[0].operation, 'insert');46const props = result.edits[0].properties as InsertProperties;47if (props.textShape.kind === 'singleLine') {48assert.strictEqual(props.textShape.isSingleCharacter, true);49assert.strictEqual(props.textShape.singleCharacterKind, 'identifier');50}51model.dispose();52});5354test('single character insert - whitespace', () => {55const model = createTextModel('hello world');56const edit = StringEdit.insert(5, ' ');57const result = computeEditKind(edit, model);5859assert.ok(result);60assert.strictEqual(result.edits.length, 1);61assert.strictEqual(result.edits[0].operation, 'insert');62const props = result.edits[0].properties as InsertProperties;63if (props.textShape.kind === 'singleLine') {64assert.strictEqual(props.textShape.isSingleCharacter, true);65assert.strictEqual(props.textShape.singleCharacterKind, 'whitespace');66}67model.dispose();68});6970test('word insert', () => {71const model = createTextModel('hello world');72const edit = StringEdit.insert(5, 'foo');73const result = computeEditKind(edit, model);7475assert.ok(result);76assert.strictEqual(result.edits.length, 1);77assert.strictEqual(result.edits[0].operation, 'insert');78const props = result.edits[0].properties as InsertProperties;79if (props.textShape.kind === 'singleLine') {80assert.strictEqual(props.textShape.isWord, true);81assert.strictEqual(props.textShape.isMultipleWords, false);82}83model.dispose();84});8586test('multiple words insert', () => {87const model = createTextModel('hello world');88const edit = StringEdit.insert(5, 'foo bar baz');89const result = computeEditKind(edit, model);9091assert.ok(result);92assert.strictEqual(result.edits.length, 1);93assert.strictEqual(result.edits[0].operation, 'insert');94const props = result.edits[0].properties as InsertProperties;95if (props.textShape.kind === 'singleLine') {96assert.strictEqual(props.textShape.isMultipleWords, true);97}98model.dispose();99});100101test('multi-line insert', () => {102const model = createTextModel('hello world');103const edit = StringEdit.insert(5, 'line1\nline2\nline3');104const result = computeEditKind(edit, model);105106assert.ok(result);107assert.strictEqual(result.edits.length, 1);108assert.strictEqual(result.edits[0].operation, 'insert');109assert.strictEqual(result.edits[0].charactersInserted, 17);110assert.strictEqual(result.edits[0].charactersDeleted, 0);111assert.strictEqual(result.edits[0].linesInserted, 2);112assert.strictEqual(result.edits[0].linesDeleted, 0);113const props = result.edits[0].properties as InsertProperties;114assert.strictEqual(props.textShape.kind, 'multiLine');115if (props.textShape.kind === 'multiLine') {116assert.strictEqual(props.textShape.lineCount, 3);117}118model.dispose();119});120121test('insert at end of line', () => {122const model = createTextModel('hello');123const edit = StringEdit.insert(5, ' world');124const result = computeEditKind(edit, model);125126assert.ok(result);127assert.strictEqual(result.edits.length, 1);128assert.strictEqual(result.edits[0].operation, 'insert');129const props = result.edits[0].properties as InsertProperties;130assert.strictEqual(props.locationShape, 'endOfLine');131model.dispose();132});133134test('insert on empty line', () => {135const model = createTextModel('hello\n\nworld');136const edit = StringEdit.insert(6, 'text');137const result = computeEditKind(edit, model);138139assert.ok(result);140assert.strictEqual(result.edits.length, 1);141assert.strictEqual(result.edits[0].operation, 'insert');142const props = result.edits[0].properties as InsertProperties;143assert.strictEqual(props.locationShape, 'emptyLine');144model.dispose();145});146147test('insert at start of line', () => {148const model = createTextModel('hello world');149const edit = StringEdit.insert(0, 'prefix');150const result = computeEditKind(edit, model);151152assert.ok(result);153assert.strictEqual(result.edits.length, 1);154assert.strictEqual(result.edits[0].operation, 'insert');155const props = result.edits[0].properties as InsertProperties;156assert.strictEqual(props.locationShape, 'startOfLine');157model.dispose();158});159160test('insert in middle of line', () => {161const model = createTextModel('hello world');162const edit = StringEdit.insert(5, '_');163const result = computeEditKind(edit, model);164165assert.ok(result);166assert.strictEqual(result.edits.length, 1);167assert.strictEqual(result.edits[0].operation, 'insert');168const props = result.edits[0].properties as InsertProperties;169assert.strictEqual(props.locationShape, 'middleOfLine');170model.dispose();171});172173test('insert relative to cursor - at cursor', () => {174const model = createTextModel('hello world');175const edit = StringEdit.insert(5, 'text');176const cursor = new Position(1, 6); // column is 1-based177const result = computeEditKind(edit, model, cursor);178179assert.ok(result);180assert.strictEqual(result.edits.length, 1);181assert.strictEqual(result.edits[0].operation, 'insert');182const props = result.edits[0].properties as InsertProperties;183assert.ok(props.relativeToCursor);184assert.strictEqual(props.relativeToCursor.atCursor, true);185model.dispose();186});187188test('insert relative to cursor - before cursor on same line', () => {189const model = createTextModel('hello world');190const edit = StringEdit.insert(2, 'text');191const cursor = new Position(1, 8);192const result = computeEditKind(edit, model, cursor);193194assert.ok(result);195assert.strictEqual(result.edits.length, 1);196assert.strictEqual(result.edits[0].operation, 'insert');197const props = result.edits[0].properties as InsertProperties;198assert.ok(props.relativeToCursor);199assert.strictEqual(props.relativeToCursor.beforeCursorOnSameLine, true);200model.dispose();201});202203test('insert relative to cursor - after cursor on same line', () => {204const model = createTextModel('hello world');205const edit = StringEdit.insert(8, 'text');206const cursor = new Position(1, 4);207const result = computeEditKind(edit, model, cursor);208209assert.ok(result);210assert.strictEqual(result.edits.length, 1);211assert.strictEqual(result.edits[0].operation, 'insert');212const props = result.edits[0].properties as InsertProperties;213assert.ok(props.relativeToCursor);214assert.strictEqual(props.relativeToCursor.afterCursorOnSameLine, true);215model.dispose();216});217218test('insert relative to cursor - lines above', () => {219const model = createTextModel('line1\nline2\nline3');220const edit = StringEdit.insert(0, 'text');221const cursor = new Position(3, 1);222const result = computeEditKind(edit, model, cursor);223224assert.ok(result);225assert.strictEqual(result.edits.length, 1);226assert.strictEqual(result.edits[0].operation, 'insert');227const props = result.edits[0].properties as InsertProperties;228assert.ok(props.relativeToCursor);229assert.strictEqual(props.relativeToCursor.linesAbove, 2);230model.dispose();231});232233test('insert relative to cursor - lines below', () => {234const model = createTextModel('line1\nline2\nline3');235const edit = StringEdit.insert(12, 'text'); // after 'line2\n'236const cursor = new Position(1, 1);237const result = computeEditKind(edit, model, cursor);238239assert.ok(result);240assert.strictEqual(result.edits.length, 1);241assert.strictEqual(result.edits[0].operation, 'insert');242const props = result.edits[0].properties as InsertProperties;243assert.ok(props.relativeToCursor);244assert.strictEqual(props.relativeToCursor.linesBelow, 2);245model.dispose();246});247248test('duplicated whitespace insert', () => {249const model = createTextModel('hello');250const edit = StringEdit.insert(5, ' ');251const result = computeEditKind(edit, model);252253assert.ok(result);254assert.strictEqual(result.edits.length, 1);255assert.strictEqual(result.edits[0].operation, 'insert');256const props = result.edits[0].properties as InsertProperties;257if (props.textShape.kind === 'singleLine') {258assert.strictEqual(props.textShape.hasDuplicatedWhitespace, true);259}260model.dispose();261});262});263264suite('Delete operations', () => {265test('single character delete - identifier', () => {266const model = createTextModel('hello world');267const edit = StringEdit.delete(new OffsetRange(4, 5)); // delete 'o'268const result = computeEditKind(edit, model);269270assert.ok(result);271assert.strictEqual(result.edits.length, 1);272assert.strictEqual(result.edits[0].operation, 'delete');273const props = result.edits[0].properties as DeleteProperties;274if (props.textShape.kind === 'singleLine') {275assert.strictEqual(props.textShape.isSingleCharacter, true);276assert.strictEqual(props.textShape.singleCharacterKind, 'identifier');277}278model.dispose();279});280281test('single character delete - syntactical', () => {282const model = createTextModel('hello;world');283const edit = StringEdit.delete(new OffsetRange(5, 6)); // delete ';'284const result = computeEditKind(edit, model);285286assert.ok(result);287assert.strictEqual(result.edits.length, 1);288assert.strictEqual(result.edits[0].operation, 'delete');289const props = result.edits[0].properties as DeleteProperties;290if (props.textShape.kind === 'singleLine') {291assert.strictEqual(props.textShape.isSingleCharacter, true);292assert.strictEqual(props.textShape.singleCharacterKind, 'syntactical');293}294model.dispose();295});296297test('word delete', () => {298const model = createTextModel('hello world');299const edit = StringEdit.delete(new OffsetRange(0, 5)); // delete 'hello'300const result = computeEditKind(edit, model);301302assert.ok(result);303assert.strictEqual(result.edits.length, 1);304assert.strictEqual(result.edits[0].operation, 'delete');305assert.strictEqual(result.edits[0].charactersInserted, 0);306assert.strictEqual(result.edits[0].charactersDeleted, 5);307assert.strictEqual(result.edits[0].linesInserted, 0);308assert.strictEqual(result.edits[0].linesDeleted, 0);309const props = result.edits[0].properties as DeleteProperties;310if (props.textShape.kind === 'singleLine') {311assert.strictEqual(props.textShape.isWord, true);312}313model.dispose();314});315316test('multi-line delete', () => {317const model = createTextModel('line1\nline2\nline3');318const edit = StringEdit.delete(new OffsetRange(0, 12)); // delete 'line1\nline2\n'319const result = computeEditKind(edit, model);320321assert.ok(result);322assert.strictEqual(result.edits.length, 1);323assert.strictEqual(result.edits[0].operation, 'delete');324assert.strictEqual(result.edits[0].charactersInserted, 0);325assert.strictEqual(result.edits[0].charactersDeleted, 12);326assert.strictEqual(result.edits[0].linesInserted, 0);327assert.strictEqual(result.edits[0].linesDeleted, 2);328const props = result.edits[0].properties as DeleteProperties;329assert.strictEqual(props.textShape.kind, 'multiLine');330model.dispose();331});332333test('delete entire line content', () => {334const model = createTextModel('hello');335const edit = StringEdit.delete(new OffsetRange(0, 5));336const result = computeEditKind(edit, model);337338assert.ok(result);339assert.strictEqual(result.edits.length, 1);340assert.strictEqual(result.edits[0].operation, 'delete');341const props = result.edits[0].properties as DeleteProperties;342assert.strictEqual(props.deletesEntireLineContent, true);343model.dispose();344});345});346347suite('Replace operations', () => {348test('word to word replacement', () => {349const model = createTextModel('hello world');350const edit = StringEdit.replace(new OffsetRange(0, 5), 'goodbye');351const result = computeEditKind(edit, model);352353assert.ok(result);354assert.strictEqual(result.edits.length, 1);355assert.strictEqual(result.edits[0].operation, 'replace');356assert.strictEqual(result.edits[0].charactersInserted, 7);357assert.strictEqual(result.edits[0].charactersDeleted, 5);358assert.strictEqual(result.edits[0].linesInserted, 0);359assert.strictEqual(result.edits[0].linesDeleted, 0);360const props = result.edits[0].properties as ReplaceProperties;361assert.strictEqual(props.isWordToWordReplacement, true);362model.dispose();363});364365test('additive replacement', () => {366const model = createTextModel('hi world');367const edit = StringEdit.replace(new OffsetRange(0, 2), 'hello');368const result = computeEditKind(edit, model);369370assert.ok(result);371assert.strictEqual(result.edits.length, 1);372assert.strictEqual(result.edits[0].operation, 'replace');373assert.strictEqual(result.edits[0].charactersInserted, 5);374assert.strictEqual(result.edits[0].charactersDeleted, 2);375const props = result.edits[0].properties as ReplaceProperties;376assert.strictEqual(props.isAdditive, true);377assert.strictEqual(props.isSubtractive, false);378model.dispose();379});380381test('subtractive replacement', () => {382const model = createTextModel('hello world');383const edit = StringEdit.replace(new OffsetRange(0, 5), 'hi');384const result = computeEditKind(edit, model);385386assert.ok(result);387assert.strictEqual(result.edits.length, 1);388assert.strictEqual(result.edits[0].operation, 'replace');389assert.strictEqual(result.edits[0].charactersInserted, 2);390assert.strictEqual(result.edits[0].charactersDeleted, 5);391const props = result.edits[0].properties as ReplaceProperties;392assert.strictEqual(props.isSubtractive, true);393assert.strictEqual(props.isAdditive, false);394model.dispose();395});396397test('single line to multi-line replacement', () => {398const model = createTextModel('hello world');399const edit = StringEdit.replace(new OffsetRange(0, 5), 'line1\nline2');400const result = computeEditKind(edit, model);401402assert.ok(result);403assert.strictEqual(result.edits.length, 1);404assert.strictEqual(result.edits[0].operation, 'replace');405assert.strictEqual(result.edits[0].linesInserted, 1);406assert.strictEqual(result.edits[0].linesDeleted, 0);407const props = result.edits[0].properties as ReplaceProperties;408assert.strictEqual(props.isSingleLineToMultiLine, true);409model.dispose();410});411412test('multi-line to single line replacement', () => {413const model = createTextModel('line1\nline2\nline3');414const edit = StringEdit.replace(new OffsetRange(0, 12), 'hello');415const result = computeEditKind(edit, model);416417assert.ok(result);418assert.strictEqual(result.edits.length, 1);419assert.strictEqual(result.edits[0].operation, 'replace');420assert.strictEqual(result.edits[0].linesInserted, 0);421assert.strictEqual(result.edits[0].linesDeleted, 2);422const props = result.edits[0].properties as ReplaceProperties;423assert.strictEqual(props.isMultiLineToSingleLine, true);424model.dispose();425});426427test('single line to single line replacement', () => {428const model = createTextModel('hello world');429const edit = StringEdit.replace(new OffsetRange(0, 5), 'goodbye');430const result = computeEditKind(edit, model);431432assert.ok(result);433assert.strictEqual(result.edits.length, 1);434assert.strictEqual(result.edits[0].operation, 'replace');435const props = result.edits[0].properties as ReplaceProperties;436assert.strictEqual(props.isSingleLineToSingleLine, true);437model.dispose();438});439});440441suite('Empty edit', () => {442test('empty edit returns undefined', () => {443const model = createTextModel('hello world');444const edit = StringEdit.empty;445const result = computeEditKind(edit, model);446447assert.strictEqual(result, undefined);448model.dispose();449});450});451452suite('Multiple replacements', () => {453test('multiple inserts', () => {454const model = createTextModel('hello world');455const edit = new StringEdit([456StringEdit.insert(0, 'A').replacements[0],457StringEdit.insert(5, 'B').replacements[0],458]);459const result = computeEditKind(edit, model);460461assert.ok(result);462assert.strictEqual(result.edits.length, 2);463assert.strictEqual(result.edits[0].operation, 'insert');464assert.strictEqual(result.edits[1].operation, 'insert');465model.dispose();466});467468test('mixed operations', () => {469const model = createTextModel('hello world');470const edit = new StringEdit([471StringEdit.insert(0, 'prefix').replacements[0],472StringEdit.delete(new OffsetRange(5, 6)).replacements[0],473]);474const result = computeEditKind(edit, model);475476assert.ok(result);477assert.strictEqual(result.edits.length, 2);478assert.strictEqual(result.edits[0].operation, 'insert');479assert.strictEqual(result.edits[1].operation, 'delete');480model.dispose();481});482});483});484485486