Path: blob/main/src/vs/editor/test/browser/viewModel/viewModelImpl.test.ts
5242 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 { Range } from '../../../common/core/range.js';9import { EndOfLineSequence, PositionAffinity } from '../../../common/model.js';10import { ViewEventHandler } from '../../../common/viewEventHandler.js';11import { ViewEvent } from '../../../common/viewEvents.js';12import { testViewModel } from './testViewModel.js';13import { DisposableStore } from '../../../../base/common/lifecycle.js';14import { createTextModel } from '../../common/testTextModel.js';15import { createCodeEditorServices, instantiateTestCodeEditor } from '../testCodeEditor.js';1617suite('ViewModel', () => {1819ensureNoDisposablesAreLeakedInTestSuite();2021test('issue #21073: SplitLinesCollection: attempt to access a \'newer\' model', () => {22const text = [''];23const opts = {24lineNumbersMinChars: 125};26testViewModel(text, opts, (viewModel, model) => {27assert.strictEqual(viewModel.getLineCount(), 1);2829viewModel.setViewport(1, 1, 1);3031model.applyEdits([{32range: new Range(1, 1, 1, 1),33text: [34'line01',35'line02',36'line03',37'line04',38'line05',39'line06',40'line07',41'line08',42'line09',43'line10',44].join('\n')45}]);4647assert.strictEqual(viewModel.getLineCount(), 10);48});49});5051test('issue #44805: SplitLinesCollection: attempt to access a \'newer\' model', () => {52const text = [''];53testViewModel(text, {}, (viewModel, model) => {54assert.strictEqual(viewModel.getLineCount(), 1);5556model.pushEditOperations([], [{57range: new Range(1, 1, 1, 1),58text: '\ninsert1'59}], () => ([]));6061model.pushEditOperations([], [{62range: new Range(1, 1, 1, 1),63text: '\ninsert2'64}], () => ([]));6566model.pushEditOperations([], [{67range: new Range(1, 1, 1, 1),68text: '\ninsert3'69}], () => ([]));7071const viewLineCount: number[] = [];7273viewLineCount.push(viewModel.getLineCount());74const eventHandler = new class extends ViewEventHandler {75override handleEvents(events: ViewEvent[]): void {76// Access the view model77viewLineCount.push(viewModel.getLineCount());78}79};80viewModel.addViewEventHandler(eventHandler);81model.undo();82viewLineCount.push(viewModel.getLineCount());8384assert.deepStrictEqual(viewLineCount, [4, 1, 1, 1, 1]);8586viewModel.removeViewEventHandler(eventHandler);87eventHandler.dispose();88});89});9091test('view models react first to model changes', () => {92const initialText = [93'Hello',94'world'95];96const disposables = new DisposableStore();9798const model = disposables.add(createTextModel(initialText.join('\n')));99const instantiationService = createCodeEditorServices(disposables);100const ed1 = disposables.add(instantiateTestCodeEditor(instantiationService, model));101disposables.add(instantiateTestCodeEditor(instantiationService, model));102103// Add a nasty listener which modifies the model during the model change event104let isFirst = true;105disposables.add(ed1.onDidChangeModelContent((e) => {106if (isFirst) {107isFirst = false;108// delete the \n109model.applyEdits([{ range: new Range(1, 6, 2, 1), text: '' }]);110}111}));112113model.applyEdits([{ range: new Range(2, 6, 2, 6), text: '!' }]);114115disposables.dispose();116});117118test('issue #44805: No visible lines via API call', () => {119const text = [120'line1',121'line2',122'line3'123];124testViewModel(text, {}, (viewModel, model) => {125assert.strictEqual(viewModel.getLineCount(), 3);126viewModel.setHiddenAreas([new Range(1, 1, 3, 1)]);127assert.ok(viewModel.getVisibleRanges() !== null);128});129});130131test('issue #44805: No visible lines via undoing', () => {132const text = [133''134];135testViewModel(text, {}, (viewModel, model) => {136assert.strictEqual(viewModel.getLineCount(), 1);137138model.pushEditOperations([], [{139range: new Range(1, 1, 1, 1),140text: 'line1\nline2\nline3'141}], () => ([]));142143viewModel.setHiddenAreas([new Range(1, 1, 1, 1)]);144assert.strictEqual(viewModel.getLineCount(), 2);145146model.undo();147assert.ok(viewModel.getVisibleRanges() !== null);148});149});150151function assertGetPlainTextToCopy(text: string[], ranges: Range[], emptySelectionClipboard: boolean, expected: string | string[]): void {152testViewModel(text, {}, (viewModel, model) => {153const actual = viewModel.getPlainTextToCopy(ranges, emptySelectionClipboard, false);154assert.deepStrictEqual(actual.sourceText, expected);155});156}157158const USUAL_TEXT = [159'',160'line2',161'line3',162'line4',163''164];165166test('getPlainTextToCopy 0/1', () => {167assertGetPlainTextToCopy(168USUAL_TEXT,169[170new Range(2, 2, 2, 2)171],172false,173''174);175});176177test('getPlainTextToCopy 0/1 - emptySelectionClipboard', () => {178assertGetPlainTextToCopy(179USUAL_TEXT,180[181new Range(2, 2, 2, 2)182],183true,184'line2\n'185);186});187188test('getPlainTextToCopy 1/1', () => {189assertGetPlainTextToCopy(190USUAL_TEXT,191[192new Range(2, 2, 2, 6)193],194false,195'ine2'196);197});198199test('getPlainTextToCopy 1/1 - emptySelectionClipboard', () => {200assertGetPlainTextToCopy(201USUAL_TEXT,202[203new Range(2, 2, 2, 6)204],205true,206'ine2'207);208});209210test('getPlainTextToCopy 0/2', () => {211assertGetPlainTextToCopy(212USUAL_TEXT,213[214new Range(2, 2, 2, 2),215new Range(3, 2, 3, 2),216],217false,218''219);220});221222test('getPlainTextToCopy 0/2 - emptySelectionClipboard', () => {223assertGetPlainTextToCopy(224USUAL_TEXT,225[226new Range(2, 2, 2, 2),227new Range(3, 2, 3, 2),228],229true,230[231'line2\n',232'line3\n'233]234);235});236237test('issue #256039: getPlainTextToCopy with multiple cursors and empty selections should return array', () => {238// Bug: When copying with multiple cursors (empty selections) with emptySelectionClipboard enabled,239// the result should be an array so that pasting with "editor.multiCursorPaste": "full"240// correctly distributes each line to the corresponding cursor.241// Without the fix, this returns 'line2\nline3\n' (a single string).242// With the fix, this returns ['line2\n', 'line3\n'] (an array).243assertGetPlainTextToCopy(244USUAL_TEXT,245[246new Range(2, 1, 2, 1),247new Range(3, 1, 3, 1),248],249true,250['line2\n', 'line3\n']251);252});253254test('getPlainTextToCopy 1/2', () => {255assertGetPlainTextToCopy(256USUAL_TEXT,257[258new Range(2, 2, 2, 6),259new Range(3, 2, 3, 2),260],261false,262'ine2'263);264});265266test('getPlainTextToCopy 1/2 - emptySelectionClipboard', () => {267assertGetPlainTextToCopy(268USUAL_TEXT,269[270new Range(2, 2, 2, 6),271new Range(3, 2, 3, 2),272],273true,274['ine2', 'line3\n']275);276});277278test('getPlainTextToCopy 2/2', () => {279assertGetPlainTextToCopy(280USUAL_TEXT,281[282new Range(2, 2, 2, 6),283new Range(3, 2, 3, 6),284],285false,286['ine2', 'ine3']287);288});289290test('getPlainTextToCopy 2/2 reversed', () => {291assertGetPlainTextToCopy(292USUAL_TEXT,293[294new Range(3, 2, 3, 6),295new Range(2, 2, 2, 6),296],297false,298['ine2', 'ine3']299);300});301302test('getPlainTextToCopy 0/3 - emptySelectionClipboard', () => {303assertGetPlainTextToCopy(304USUAL_TEXT,305[306new Range(2, 2, 2, 2),307new Range(2, 3, 2, 3),308new Range(3, 2, 3, 2),309],310true,311[312'line2\n',313'line3\n'314]315);316});317318test('issue #22688 - always use CRLF for clipboard on Windows', () => {319testViewModel(USUAL_TEXT, {}, (viewModel, model) => {320model.setEOL(EndOfLineSequence.LF);321const actual = viewModel.getPlainTextToCopy([new Range(2, 1, 5, 1)], true, true);322assert.deepStrictEqual(actual.sourceText, 'line2\r\nline3\r\nline4\r\n');323});324});325326test('issue #40926: Incorrect spacing when inserting new line after multiple folded blocks of code', () => {327testViewModel(328[329'foo = {',330' foobar: function() {',331' this.foobar();',332' },',333' foobar: function() {',334' this.foobar();',335' },',336' foobar: function() {',337' this.foobar();',338' },',339'}',340], {}, (viewModel, model) => {341viewModel.setHiddenAreas([342new Range(3, 1, 3, 1),343new Range(6, 1, 6, 1),344new Range(9, 1, 9, 1),345]);346347model.applyEdits([348{ range: new Range(4, 7, 4, 7), text: '\n ' },349{ range: new Range(7, 7, 7, 7), text: '\n ' },350{ range: new Range(10, 7, 10, 7), text: '\n ' }351]);352353assert.strictEqual(viewModel.getLineCount(), 11);354}355);356});357358test('normalizePosition with multiple touching injected text', () => {359testViewModel(360[361'just some text'362],363{},364(viewModel, model) => {365model.deltaDecorations([], [366{367range: new Range(1, 8, 1, 8),368options: {369description: 'test',370before: {371content: 'bar'372},373showIfCollapsed: true374}375},376{377range: new Range(1, 8, 1, 8),378options: {379description: 'test',380before: {381content: 'bz'382},383showIfCollapsed: true384}385},386]);387388// just sobarbzme text389390assert.deepStrictEqual(viewModel.normalizePosition(new Position(1, 8), PositionAffinity.None), new Position(1, 8));391assert.deepStrictEqual(viewModel.normalizePosition(new Position(1, 9), PositionAffinity.None), new Position(1, 8));392assert.deepStrictEqual(viewModel.normalizePosition(new Position(1, 11), PositionAffinity.None), new Position(1, 11));393assert.deepStrictEqual(viewModel.normalizePosition(new Position(1, 12), PositionAffinity.None), new Position(1, 11));394assert.deepStrictEqual(viewModel.normalizePosition(new Position(1, 13), PositionAffinity.None), new Position(1, 13));395396assert.deepStrictEqual(viewModel.normalizePosition(new Position(1, 8), PositionAffinity.Left), new Position(1, 8));397assert.deepStrictEqual(viewModel.normalizePosition(new Position(1, 9), PositionAffinity.Left), new Position(1, 8));398assert.deepStrictEqual(viewModel.normalizePosition(new Position(1, 11), PositionAffinity.Left), new Position(1, 8));399assert.deepStrictEqual(viewModel.normalizePosition(new Position(1, 12), PositionAffinity.Left), new Position(1, 8));400assert.deepStrictEqual(viewModel.normalizePosition(new Position(1, 13), PositionAffinity.Left), new Position(1, 8));401402assert.deepStrictEqual(viewModel.normalizePosition(new Position(1, 8), PositionAffinity.Right), new Position(1, 13));403assert.deepStrictEqual(viewModel.normalizePosition(new Position(1, 9), PositionAffinity.Right), new Position(1, 13));404assert.deepStrictEqual(viewModel.normalizePosition(new Position(1, 11), PositionAffinity.Right), new Position(1, 13));405assert.deepStrictEqual(viewModel.normalizePosition(new Position(1, 12), PositionAffinity.Right), new Position(1, 13));406assert.deepStrictEqual(viewModel.normalizePosition(new Position(1, 13), PositionAffinity.Right), new Position(1, 13));407}408);409});410411test('issue #193262: Incorrect implementation of modifyPosition', () => {412testViewModel(413[414'just some text'415],416{417wordWrap: 'wordWrapColumn',418wordWrapColumn: 5419},420(viewModel, model) => {421assert.deepStrictEqual(422new Position(3, 1),423viewModel.modifyPosition(new Position(3, 2), -1)424);425}426);427});428});429430431