Path: blob/main/src/vs/editor/test/common/model/editableTextModel.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 { IDisposable } from '../../../../base/common/lifecycle.js';7import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';8import { ISingleEditOperation } from '../../../common/core/editOperation.js';9import { Range } from '../../../common/core/range.js';10import { EndOfLinePreference, EndOfLineSequence } from '../../../common/model.js';11import { MirrorTextModel } from '../../../common/model/mirrorTextModel.js';12import { IModelContentChangedEvent } from '../../../common/textModelEvents.js';13import { assertSyncedModels, testApplyEditsWithSyncedModels } from './editableTextModelTestUtils.js';14import { createTextModel } from '../testTextModel.js';1516suite('EditorModel - EditableTextModel.applyEdits updates mightContainRTL', () => {1718ensureNoDisposablesAreLeakedInTestSuite();1920function testApplyEdits(original: string[], edits: ISingleEditOperation[], before: boolean, after: boolean): void {21const model = createTextModel(original.join('\n'));22model.setEOL(EndOfLineSequence.LF);2324assert.strictEqual(model.mightContainRTL(), before);2526model.applyEdits(edits);27assert.strictEqual(model.mightContainRTL(), after);28model.dispose();29}3031function editOp(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, text: string[]): ISingleEditOperation {32return {33range: new Range(startLineNumber, startColumn, endLineNumber, endColumn),34text: text.join('\n')35};36}3738test('start with RTL, insert LTR', () => {39testApplyEdits(['Hello,\n讝讜讛讬 注讜讘讚讛 诪讘讜住住转 砖讚注转讜'], [editOp(1, 1, 1, 1, ['hello'])], true, true);40});4142test('start with RTL, delete RTL', () => {43testApplyEdits(['Hello,\n讝讜讛讬 注讜讘讚讛 诪讘讜住住转 砖讚注转讜'], [editOp(1, 1, 10, 10, [''])], true, true);44});4546test('start with RTL, insert RTL', () => {47testApplyEdits(['Hello,\n讝讜讛讬 注讜讘讚讛 诪讘讜住住转 砖讚注转讜'], [editOp(1, 1, 1, 1, ['賴賳丕賰 丨賯賷賯丞 賲孬亘鬲丞 賲賳匕 夭賲賳 胤賵賷賱'])], true, true);48});4950test('start with LTR, insert LTR', () => {51testApplyEdits(['Hello,\nworld!'], [editOp(1, 1, 1, 1, ['hello'])], false, false);52});5354test('start with LTR, insert RTL 1', () => {55testApplyEdits(['Hello,\nworld!'], [editOp(1, 1, 1, 1, ['賴賳丕賰 丨賯賷賯丞 賲孬亘鬲丞 賲賳匕 夭賲賳 胤賵賷賱'])], false, true);56});5758test('start with LTR, insert RTL 2', () => {59testApplyEdits(['Hello,\nworld!'], [editOp(1, 1, 1, 1, ['讝讜讛讬 注讜讘讚讛 诪讘讜住住转 砖讚注转讜'])], false, true);60});61});626364suite('EditorModel - EditableTextModel.applyEdits updates mightContainNonBasicASCII', () => {6566ensureNoDisposablesAreLeakedInTestSuite();6768function testApplyEdits(original: string[], edits: ISingleEditOperation[], before: boolean, after: boolean): void {69const model = createTextModel(original.join('\n'));70model.setEOL(EndOfLineSequence.LF);7172assert.strictEqual(model.mightContainNonBasicASCII(), before);7374model.applyEdits(edits);75assert.strictEqual(model.mightContainNonBasicASCII(), after);76model.dispose();77}7879function editOp(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, text: string[]): ISingleEditOperation {80return {81range: new Range(startLineNumber, startColumn, endLineNumber, endColumn),82text: text.join('\n')83};84}8586test('start with NON-ASCII, insert ASCII', () => {87testApplyEdits(['Hello,\nZ眉rich'], [editOp(1, 1, 1, 1, ['hello', 'second line'])], true, true);88});8990test('start with NON-ASCII, delete NON-ASCII', () => {91testApplyEdits(['Hello,\nZ眉rich'], [editOp(1, 1, 10, 10, [''])], true, true);92});9394test('start with NON-ASCII, insert NON-ASCII', () => {95testApplyEdits(['Hello,\nZ眉rich'], [editOp(1, 1, 1, 1, ['Z眉rich'])], true, true);96});9798test('start with ASCII, insert ASCII', () => {99testApplyEdits(['Hello,\nworld!'], [editOp(1, 1, 1, 1, ['hello', 'second line'])], false, false);100});101102test('start with ASCII, insert NON-ASCII', () => {103testApplyEdits(['Hello,\nworld!'], [editOp(1, 1, 1, 1, ['Z眉rich', 'Z眉rich'])], false, true);104});105106});107108suite('EditorModel - EditableTextModel.applyEdits', () => {109110ensureNoDisposablesAreLeakedInTestSuite();111112function editOp(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, text: string[]): ISingleEditOperation {113return {114range: new Range(startLineNumber, startColumn, endLineNumber, endColumn),115text: text.join('\n'),116forceMoveMarkers: false117};118}119120test('high-low surrogates 1', () => {121testApplyEditsWithSyncedModels(122[123'馃摎some',124'very nice',125'text'126],127[128editOp(1, 2, 1, 2, ['a'])129],130[131'a馃摎some',132'very nice',133'text'134],135/*inputEditsAreInvalid*/true136);137});138test('high-low surrogates 2', () => {139testApplyEditsWithSyncedModels(140[141'馃摎some',142'very nice',143'text'144],145[146editOp(1, 2, 1, 3, ['a'])147],148[149'asome',150'very nice',151'text'152],153/*inputEditsAreInvalid*/true154);155});156test('high-low surrogates 3', () => {157testApplyEditsWithSyncedModels(158[159'馃摎some',160'very nice',161'text'162],163[164editOp(1, 1, 1, 2, ['a'])165],166[167'asome',168'very nice',169'text'170],171/*inputEditsAreInvalid*/true172);173});174test('high-low surrogates 4', () => {175testApplyEditsWithSyncedModels(176[177'馃摎some',178'very nice',179'text'180],181[182editOp(1, 1, 1, 3, ['a'])183],184[185'asome',186'very nice',187'text'188],189/*inputEditsAreInvalid*/true190);191});192193test('Bug 19872: Undo is funky', () => {194testApplyEditsWithSyncedModels(195[196'something',197' A',198'',199' B',200'something else'201],202[203editOp(2, 1, 2, 2, ['']),204editOp(3, 1, 4, 2, [''])205],206[207'something',208'A',209'B',210'something else'211]212);213});214215test('Bug 19872: Undo is funky (2)', () => {216testApplyEditsWithSyncedModels(217[218'something',219'A',220'B',221'something else'222],223[224editOp(2, 1, 2, 1, [' ']),225editOp(3, 1, 3, 1, ['', ' '])226],227[228'something',229' A',230'',231' B',232'something else'233]234);235});236237test('insert empty text', () => {238testApplyEditsWithSyncedModels(239[240'My First Line',241'\t\tMy Second Line',242' Third Line',243'',244'1'245],246[247editOp(1, 1, 1, 1, [''])248],249[250'My First Line',251'\t\tMy Second Line',252' Third Line',253'',254'1'255]256);257});258259test('last op is no-op', () => {260testApplyEditsWithSyncedModels(261[262'My First Line',263'\t\tMy Second Line',264' Third Line',265'',266'1'267],268[269editOp(1, 1, 1, 2, ['']),270editOp(4, 1, 4, 1, [''])271],272[273'y First Line',274'\t\tMy Second Line',275' Third Line',276'',277'1'278]279);280});281282test('insert text without newline 1', () => {283testApplyEditsWithSyncedModels(284[285'My First Line',286'\t\tMy Second Line',287' Third Line',288'',289'1'290],291[292editOp(1, 1, 1, 1, ['foo '])293],294[295'foo My First Line',296'\t\tMy Second Line',297' Third Line',298'',299'1'300]301);302});303304test('insert text without newline 2', () => {305testApplyEditsWithSyncedModels(306[307'My First Line',308'\t\tMy Second Line',309' Third Line',310'',311'1'312],313[314editOp(1, 3, 1, 3, [' foo'])315],316[317'My foo First Line',318'\t\tMy Second Line',319' Third Line',320'',321'1'322]323);324});325326test('insert one newline', () => {327testApplyEditsWithSyncedModels(328[329'My First Line',330'\t\tMy Second Line',331' Third Line',332'',333'1'334],335[336editOp(1, 4, 1, 4, ['', ''])337],338[339'My ',340'First Line',341'\t\tMy Second Line',342' Third Line',343'',344'1'345]346);347});348349test('insert text with one newline', () => {350testApplyEditsWithSyncedModels(351[352'My First Line',353'\t\tMy Second Line',354' Third Line',355'',356'1'357],358[359editOp(1, 3, 1, 3, [' new line', 'No longer'])360],361[362'My new line',363'No longer First Line',364'\t\tMy Second Line',365' Third Line',366'',367'1'368]369);370});371372test('insert text with two newlines', () => {373testApplyEditsWithSyncedModels(374[375'My First Line',376'\t\tMy Second Line',377' Third Line',378'',379'1'380],381[382editOp(1, 3, 1, 3, [' new line', 'One more line in the middle', 'No longer'])383],384[385'My new line',386'One more line in the middle',387'No longer First Line',388'\t\tMy Second Line',389' Third Line',390'',391'1'392]393);394});395396test('insert text with many newlines', () => {397testApplyEditsWithSyncedModels(398[399'My First Line',400'\t\tMy Second Line',401' Third Line',402'',403'1'404],405[406editOp(1, 3, 1, 3, ['', '', '', '', ''])407],408[409'My',410'',411'',412'',413' First Line',414'\t\tMy Second Line',415' Third Line',416'',417'1'418]419);420});421422test('insert multiple newlines', () => {423testApplyEditsWithSyncedModels(424[425'My First Line',426'\t\tMy Second Line',427' Third Line',428'',429'1'430],431[432editOp(1, 3, 1, 3, ['', '', '', '', '']),433editOp(3, 15, 3, 15, ['a', 'b'])434],435[436'My',437'',438'',439'',440' First Line',441'\t\tMy Second Line',442' Third Linea',443'b',444'',445'1'446]447);448});449450test('delete empty text', () => {451testApplyEditsWithSyncedModels(452[453'My First Line',454'\t\tMy Second Line',455' Third Line',456'',457'1'458],459[460editOp(1, 1, 1, 1, [''])461],462[463'My First Line',464'\t\tMy Second Line',465' Third Line',466'',467'1'468]469);470});471472test('delete text from one line', () => {473testApplyEditsWithSyncedModels(474[475'My First Line',476'\t\tMy Second Line',477' Third Line',478'',479'1'480],481[482editOp(1, 1, 1, 2, [''])483],484[485'y First Line',486'\t\tMy Second Line',487' Third Line',488'',489'1'490]491);492});493494test('delete text from one line 2', () => {495testApplyEditsWithSyncedModels(496[497'My First Line',498'\t\tMy Second Line',499' Third Line',500'',501'1'502],503[504editOp(1, 1, 1, 3, ['a'])505],506[507'a First Line',508'\t\tMy Second Line',509' Third Line',510'',511'1'512]513);514});515516test('delete all text from a line', () => {517testApplyEditsWithSyncedModels(518[519'My First Line',520'\t\tMy Second Line',521' Third Line',522'',523'1'524],525[526editOp(1, 1, 1, 14, [''])527],528[529'',530'\t\tMy Second Line',531' Third Line',532'',533'1'534]535);536});537538test('delete text from two lines', () => {539testApplyEditsWithSyncedModels(540[541'My First Line',542'\t\tMy Second Line',543' Third Line',544'',545'1'546],547[548editOp(1, 4, 2, 6, [''])549],550[551'My Second Line',552' Third Line',553'',554'1'555]556);557});558559test('delete text from many lines', () => {560testApplyEditsWithSyncedModels(561[562'My First Line',563'\t\tMy Second Line',564' Third Line',565'',566'1'567],568[569editOp(1, 4, 3, 5, [''])570],571[572'My Third Line',573'',574'1'575]576);577});578579test('delete everything', () => {580testApplyEditsWithSyncedModels(581[582'My First Line',583'\t\tMy Second Line',584' Third Line',585'',586'1'587],588[589editOp(1, 1, 5, 2, [''])590],591[592''593]594);595});596597test('two unrelated edits', () => {598testApplyEditsWithSyncedModels(599[600'My First Line',601'\t\tMy Second Line',602' Third Line',603'',604'123'605],606[607editOp(2, 1, 2, 3, ['\t']),608editOp(3, 1, 3, 5, [''])609],610[611'My First Line',612'\tMy Second Line',613'Third Line',614'',615'123'616]617);618});619620test('two edits on one line', () => {621testApplyEditsWithSyncedModels(622[623'\t\tfirst\t ',624'\t\tsecond line',625'\tthird line',626'fourth line',627'\t\t<!@#fifth#@!>\t\t'628],629[630editOp(5, 3, 5, 7, ['']),631editOp(5, 12, 5, 16, [''])632],633[634'\t\tfirst\t ',635'\t\tsecond line',636'\tthird line',637'fourth line',638'\t\tfifth\t\t'639]640);641});642643test('many edits', () => {644testApplyEditsWithSyncedModels(645[646'{"x" : 1}'647],648[649editOp(1, 2, 1, 2, ['\n ']),650editOp(1, 5, 1, 6, ['']),651editOp(1, 9, 1, 9, ['\n'])652],653[654'{',655' "x": 1',656'}'657]658);659});660661test('many edits reversed', () => {662testApplyEditsWithSyncedModels(663[664'{',665' "x": 1',666'}'667],668[669editOp(1, 2, 2, 3, ['']),670editOp(2, 6, 2, 6, [' ']),671editOp(2, 9, 3, 1, [''])672],673[674'{"x" : 1}'675]676);677});678679test('replacing newlines 1', () => {680testApplyEditsWithSyncedModels(681[682'{',683'"a": true,',684'',685'"b": true',686'}'687],688[689editOp(1, 2, 2, 1, ['', '\t']),690editOp(2, 11, 4, 1, ['', '\t'])691],692[693'{',694'\t"a": true,',695'\t"b": true',696'}'697]698);699});700701test('replacing newlines 2', () => {702testApplyEditsWithSyncedModels(703[704'some text',705'some more text',706'now comes an empty line',707'',708'after empty line',709'and the last line'710],711[712editOp(1, 5, 3, 1, [' text', 'some more text', 'some more text']),713editOp(3, 2, 4, 1, ['o more lines', 'asd', 'asd', 'asd']),714editOp(5, 1, 5, 6, ['zzzzzzzz']),715editOp(5, 11, 6, 16, ['1', '2', '3', '4'])716],717[718'some text',719'some more text',720'some more textno more lines',721'asd',722'asd',723'asd',724'zzzzzzzz empt1',725'2',726'3',727'4ne'728]729);730});731732test('advanced 1', () => {733testApplyEditsWithSyncedModels(734[735' { "d": [',736' null',737' ] /*comment*/',738' ,"e": /*comment*/ [null] }',739],740[741editOp(1, 1, 1, 2, ['']),742editOp(1, 3, 1, 10, ['', ' ']),743editOp(1, 16, 2, 14, ['', ' ']),744editOp(2, 18, 3, 9, ['', ' ']),745editOp(3, 22, 4, 9, ['']),746editOp(4, 10, 4, 10, ['', ' ']),747editOp(4, 28, 4, 28, ['', ' ']),748editOp(4, 32, 4, 32, ['', ' ']),749editOp(4, 33, 4, 34, ['', ''])750],751[752'{',753' "d": [',754' null',755' ] /*comment*/,',756' "e": /*comment*/ [',757' null',758' ]',759'}',760]761);762});763764test('advanced simplified', () => {765testApplyEditsWithSyncedModels(766[767' abc',768' ,def'769],770[771editOp(1, 1, 1, 4, ['']),772editOp(1, 7, 2, 2, ['']),773editOp(2, 3, 2, 3, ['', ''])774],775[776'abc,',777'def'778]779);780});781782test('issue #144', () => {783testApplyEditsWithSyncedModels(784[785'package caddy',786'',787'func main() {',788'\tfmt.Println("Hello World! :)")',789'}',790''791],792[793editOp(1, 1, 6, 1, [794'package caddy',795'',796'import "fmt"',797'',798'func main() {',799'\tfmt.Println("Hello World! :)")',800'}',801''802])803],804[805'package caddy',806'',807'import "fmt"',808'',809'func main() {',810'\tfmt.Println("Hello World! :)")',811'}',812''813]814);815});816817test('issue #2586 Replacing selected end-of-line with newline locks up the document', () => {818testApplyEditsWithSyncedModels(819[820'something',821'interesting'822],823[824editOp(1, 10, 2, 1, ['', ''])825],826[827'something',828'interesting'829]830);831});832833test('issue #3980', () => {834testApplyEditsWithSyncedModels(835[836'class A {',837' someProperty = false;',838' someMethod() {',839' this.someMethod();',840' }',841'}',842],843[844editOp(1, 8, 1, 9, ['', '']),845editOp(3, 17, 3, 18, ['', '']),846editOp(3, 18, 3, 18, [' ']),847editOp(4, 5, 4, 5, [' ']),848],849[850'class A',851'{',852' someProperty = false;',853' someMethod()',854' {',855' this.someMethod();',856' }',857'}',858]859);860});861862function testApplyEditsFails(original: string[], edits: ISingleEditOperation[]): void {863const model = createTextModel(original.join('\n'));864865let hasThrown = false;866try {867model.applyEdits(edits);868} catch (err) {869hasThrown = true;870}871assert.ok(hasThrown, 'expected model.applyEdits to fail.');872873model.dispose();874}875876test('touching edits: two inserts at the same position', () => {877testApplyEditsWithSyncedModels(878[879'hello world'880],881[882editOp(1, 1, 1, 1, ['a']),883editOp(1, 1, 1, 1, ['b']),884],885[886'abhello world'887]888);889});890891test('touching edits: insert and replace touching', () => {892testApplyEditsWithSyncedModels(893[894'hello world'895],896[897editOp(1, 1, 1, 1, ['b']),898editOp(1, 1, 1, 3, ['ab']),899],900[901'babllo world'902]903);904});905906test('overlapping edits: two overlapping replaces', () => {907testApplyEditsFails(908[909'hello world'910],911[912editOp(1, 1, 1, 2, ['b']),913editOp(1, 1, 1, 3, ['ab']),914]915);916});917918test('overlapping edits: two overlapping deletes', () => {919testApplyEditsFails(920[921'hello world'922],923[924editOp(1, 1, 1, 2, ['']),925editOp(1, 1, 1, 3, ['']),926]927);928});929930test('touching edits: two touching replaces', () => {931testApplyEditsWithSyncedModels(932[933'hello world'934],935[936editOp(1, 1, 1, 2, ['H']),937editOp(1, 2, 1, 3, ['E']),938],939[940'HEllo world'941]942);943});944945test('touching edits: two touching deletes', () => {946testApplyEditsWithSyncedModels(947[948'hello world'949],950[951editOp(1, 1, 1, 2, ['']),952editOp(1, 2, 1, 3, ['']),953],954[955'llo world'956]957);958});959960test('touching edits: insert and replace', () => {961testApplyEditsWithSyncedModels(962[963'hello world'964],965[966editOp(1, 1, 1, 1, ['H']),967editOp(1, 1, 1, 3, ['e']),968],969[970'Hello world'971]972);973});974975test('touching edits: replace and insert', () => {976testApplyEditsWithSyncedModels(977[978'hello world'979],980[981editOp(1, 1, 1, 3, ['H']),982editOp(1, 3, 1, 3, ['e']),983],984[985'Hello world'986]987);988});989990test('change while emitting events 1', () => {991let disposable!: IDisposable;992assertSyncedModels('Hello', (model, assertMirrorModels) => {993model.applyEdits([{994range: new Range(1, 6, 1, 6),995text: ' world!',996// forceMoveMarkers: false997}]);998999assertMirrorModels();10001001}, (model) => {1002let isFirstTime = true;1003disposable = model.onDidChangeContent(() => {1004if (!isFirstTime) {1005return;1006}1007isFirstTime = false;10081009model.applyEdits([{1010range: new Range(1, 13, 1, 13),1011text: ' How are you?',1012// forceMoveMarkers: false1013}]);1014});1015});1016disposable.dispose();1017});10181019test('change while emitting events 2', () => {1020let disposable!: IDisposable;1021assertSyncedModels('Hello', (model, assertMirrorModels) => {1022model.applyEdits([{1023range: new Range(1, 6, 1, 6),1024text: ' world!',1025// forceMoveMarkers: false1026}]);10271028assertMirrorModels();10291030}, (model) => {1031let isFirstTime = true;1032disposable = model.onDidChangeContent((e: IModelContentChangedEvent) => {1033if (!isFirstTime) {1034return;1035}1036isFirstTime = false;10371038model.applyEdits([{1039range: new Range(1, 13, 1, 13),1040text: ' How are you?',1041// forceMoveMarkers: false1042}]);1043});1044});1045disposable.dispose();1046});10471048test('issue #1580: Changes in line endings are not correctly reflected in the extension host, leading to invalid offsets sent to external refactoring tools', () => {1049const model = createTextModel('Hello\nWorld!');1050assert.strictEqual(model.getEOL(), '\n');10511052const mirrorModel2 = new MirrorTextModel(null!, model.getLinesContent(), model.getEOL(), model.getVersionId());1053let mirrorModel2PrevVersionId = model.getVersionId();10541055const disposable = model.onDidChangeContent((e: IModelContentChangedEvent) => {1056const versionId = e.versionId;1057if (versionId < mirrorModel2PrevVersionId) {1058console.warn('Model version id did not advance between edits (2)');1059}1060mirrorModel2PrevVersionId = versionId;1061mirrorModel2.onEvents(e);1062});10631064const assertMirrorModels = () => {1065assert.strictEqual(mirrorModel2.getText(), model.getValue(), 'mirror model 2 text OK');1066assert.strictEqual(mirrorModel2.version, model.getVersionId(), 'mirror model 2 version OK');1067};10681069model.setEOL(EndOfLineSequence.CRLF);1070assertMirrorModels();10711072disposable.dispose();1073model.dispose();1074mirrorModel2.dispose();1075});10761077test('issue #47733: Undo mangles unicode characters', () => {1078const model = createTextModel('\'馃憗\'');10791080model.applyEdits([1081{ range: new Range(1, 1, 1, 1), text: '"' },1082{ range: new Range(1, 2, 1, 2), text: '"' },1083]);10841085assert.strictEqual(model.getValue(EndOfLinePreference.LF), '"\'"馃憗\'');10861087assert.deepStrictEqual(model.validateRange(new Range(1, 3, 1, 4)), new Range(1, 3, 1, 4));10881089model.applyEdits([1090{ range: new Range(1, 1, 1, 2), text: null },1091{ range: new Range(1, 3, 1, 4), text: null },1092]);10931094assert.strictEqual(model.getValue(EndOfLinePreference.LF), '\'馃憗\'');10951096model.dispose();1097});10981099test('issue #48741: Broken undo stack with move lines up with multiple cursors', () => {1100const model = createTextModel([1101'line1',1102'line2',1103'line3',1104'',1105].join('\n'));11061107const undoEdits = model.applyEdits([1108{ range: new Range(4, 1, 4, 1), text: 'line3', },1109{ range: new Range(3, 1, 3, 6), text: null, },1110{ range: new Range(2, 1, 3, 1), text: null, },1111{ range: new Range(3, 6, 3, 6), text: '\nline2' }1112], true);11131114model.applyEdits(undoEdits);11151116assert.deepStrictEqual(model.getValue(), 'line1\nline2\nline3\n');11171118model.dispose();1119});1120});112111221123