Path: blob/main/src/vs/editor/test/common/model/modelInjectedText.test.ts
5240 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 { mock } from '../../../../base/test/common/mock.js';7import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';8import { EditOperation } from '../../../common/core/editOperation.js';9import { Range } from '../../../common/core/range.js';10import { InternalModelContentChangeEvent, LineInjectedText, ModelInjectedTextChangedEvent, ModelRawChange, RawContentChangedType } from '../../../common/textModelEvents.js';11import { IViewModel } from '../../../common/viewModel.js';12import { createTextModel } from '../testTextModel.js';1314suite('Editor Model - Injected Text Events', () => {15const store = ensureNoDisposablesAreLeakedInTestSuite();1617test('Basic', () => {18const thisModel = store.add(createTextModel('First Line\nSecond Line'));1920const recordedChanges = new Array<unknown>();2122const spyViewModel = new class extends mock<IViewModel>() {23override onDidChangeContentOrInjectedText(e: InternalModelContentChangeEvent | ModelInjectedTextChangedEvent) {24const changes = (e instanceof InternalModelContentChangeEvent ? e.rawContentChangedEvent.changes : e.changes);25for (const change of changes) {26recordedChanges.push(mapChange(change));27}28}29override emitContentChangeEvent(_e: InternalModelContentChangeEvent | ModelInjectedTextChangedEvent): void { }30};31thisModel.registerViewModel(spyViewModel);3233// Initial decoration34let decorations = thisModel.deltaDecorations([], [{35options: {36after: { content: 'injected1' },37description: 'test1',38showIfCollapsed: true39},40range: new Range(1, 1, 1, 1),41}]);42assert.deepStrictEqual(recordedChanges.splice(0), [43{44kind: 'lineChanged',45line: '[injected1]First Line',46lineNumber: 1,47}48]);4950// Decoration change51decorations = thisModel.deltaDecorations(decorations, [{52options: {53after: { content: 'injected1' },54description: 'test1',55showIfCollapsed: true56},57range: new Range(2, 1, 2, 1),58}, {59options: {60after: { content: 'injected2' },61description: 'test2',62showIfCollapsed: true63},64range: new Range(2, 2, 2, 2),65}]);66assert.deepStrictEqual(recordedChanges.splice(0), [67{68kind: 'lineChanged',69line: 'First Line',70lineNumber: 1,71},72{73kind: 'lineChanged',74line: '[injected1]S[injected2]econd Line',75lineNumber: 2,76}77]);7879// Simple Insert80thisModel.applyEdits([EditOperation.replace(new Range(2, 2, 2, 2), 'Hello')]);81assert.deepStrictEqual(recordedChanges.splice(0), [82{83kind: 'lineChanged',84line: '[injected1]SHello[injected2]econd Line',85lineNumber: 2,86}87]);8889// Multi-Line Insert90thisModel.pushEditOperations(null, [EditOperation.replace(new Range(2, 2, 2, 2), '\n\n\n')], null);91assert.deepStrictEqual(thisModel.getAllDecorations(undefined).map(d => ({ description: d.options.description, range: d.range.toString() })), [{92'description': 'test1',93'range': '[2,1 -> 2,1]'94},95{96'description': 'test2',97'range': '[2,2 -> 5,6]'98}]);99assert.deepStrictEqual(recordedChanges.splice(0), [100{101kind: 'lineChanged',102line: '[injected1]S',103lineNumber: 2,104},105{106fromLineNumber: 3,107kind: 'linesInserted',108lines: [109'',110'',111'Hello[injected2]econd Line',112]113}114]);115116117// Multi-Line Replace118thisModel.pushEditOperations(null, [EditOperation.replace(new Range(3, 1, 5, 1), '\n\n\n\n\n\n\n\n\n\n\n\n\n')], null);119assert.deepStrictEqual(recordedChanges.splice(0), [120{121'kind': 'lineChanged',122'line': '',123'lineNumber': 5,124},125{126'kind': 'lineChanged',127'line': '',128'lineNumber': 4,129},130{131'kind': 'lineChanged',132'line': '',133'lineNumber': 3,134},135{136'fromLineNumber': 6,137'kind': 'linesInserted',138'lines': [139'',140'',141'',142'',143'',144'',145'',146'',147'',148'',149'Hello[injected2]econd Line',150]151}152]);153154// Multi-Line Replace undo155assert.strictEqual(thisModel.undo(), undefined);156assert.deepStrictEqual(recordedChanges.splice(0), [157{158kind: 'lineChanged',159line: '[injected1]SHello[injected2]econd Line',160lineNumber: 2,161},162{163kind: 'linesDeleted',164}165]);166167thisModel.unregisterViewModel(spyViewModel);168});169});170171function mapChange(change: ModelRawChange): unknown {172if (change.changeType === RawContentChangedType.LineChanged) {173(change.injectedText || []).every(e => {174assert.deepStrictEqual(e.lineNumber, change.lineNumber);175});176177return {178kind: 'lineChanged',179line: getDetail(change.detail, change.injectedText),180lineNumber: change.lineNumber,181};182} else if (change.changeType === RawContentChangedType.LinesInserted) {183return {184kind: 'linesInserted',185lines: change.detail.map((e, idx) => getDetail(e, change.injectedTexts[idx])),186fromLineNumber: change.fromLineNumber187};188} else if (change.changeType === RawContentChangedType.LinesDeleted) {189return {190kind: 'linesDeleted',191};192} else if (change.changeType === RawContentChangedType.EOLChanged) {193return {194kind: 'eolChanged'195};196} else if (change.changeType === RawContentChangedType.Flush) {197return {198kind: 'flush'199};200}201return { kind: 'unknown' };202}203204function getDetail(line: string, injectedTexts: LineInjectedText[] | null): string {205return LineInjectedText.applyInjectedText(line, (injectedTexts || []).map(t => t.withText(`[${t.options.content}]`)));206}207208209