multicursor.test.ts 17.1 KB
Newer Older
A
Alex Dima 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
A
Alex Dima 已提交
6
import { Event } from 'vs/base/common/event';
7
import { Range } from 'vs/editor/common/core/range';
A
Alex Dima 已提交
8
import { Selection } from 'vs/editor/common/core/selection';
9
import { Handler } from 'vs/editor/common/editorCommon';
A
Alex Dima 已提交
10
import { EndOfLineSequence } from 'vs/editor/common/model';
11
import { CommonFindController } from 'vs/editor/contrib/find/findController';
A
Alex Dima 已提交
12 13 14 15
import { AddSelectionToNextFindMatchAction, InsertCursorAbove, InsertCursorBelow, MultiCursorSelectionController, SelectHighlightsAction } from 'vs/editor/contrib/multicursor/multicursor';
import { TestCodeEditor, withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IStorageService } from 'vs/platform/storage/common/storage';
A
Alex Dima 已提交
16 17 18 19

suite('Multicursor', () => {

	test('issue #2205: Multi-cursor pastes in reverse order', () => {
A
Alex Dima 已提交
20
		withTestCodeEditor([
A
Alex Dima 已提交
21 22 23 24 25 26
			'abc',
			'def'
		], {}, (editor, cursor) => {
			let addCursorUpAction = new InsertCursorAbove();

			editor.setSelection(new Selection(2, 1, 2, 1));
27
			addCursorUpAction.run(null!, editor, {});
A
Alex Dima 已提交
28 29
			assert.equal(cursor.getSelections().length, 2);

30 31 32 33 34 35 36
			editor.trigger('test', Handler.Paste, {
				text: '1\n2',
				multicursorText: [
					'1',
					'2'
				]
			});
A
Alex Dima 已提交
37
			// cursorCommand(cursor, H.Paste, { text: '1\n2' });
38 39
			assert.equal(editor.getModel()!.getLineContent(1), '1abc');
			assert.equal(editor.getModel()!.getLineContent(2), '2def');
A
Alex Dima 已提交
40 41 42 43
		});
	});

	test('issue #1336: Insert cursor below on last line adds a cursor to the end of the current line', () => {
A
Alex Dima 已提交
44
		withTestCodeEditor([
A
Alex Dima 已提交
45 46 47
			'abc'
		], {}, (editor, cursor) => {
			let addCursorDownAction = new InsertCursorBelow();
48
			addCursorDownAction.run(null!, editor, {});
A
Alex Dima 已提交
49 50 51 52 53
			assert.equal(cursor.getSelections().length, 1);
		});
	});

});
54 55 56 57 58 59 60 61

function fromRange(rng: Range): number[] {
	return [rng.startLineNumber, rng.startColumn, rng.endLineNumber, rng.endColumn];
}

suite('Multicursor selection', () => {
	let queryState: { [key: string]: any; } = {};
	let serviceCollection = new ServiceCollection();
62
	serviceCollection.set(IStorageService, {
63
		_serviceBrand: undefined,
64
		onDidChangeStorage: Event.None,
65
		onWillSaveState: Event.None,
66 67
		get: (key: string) => queryState[key],
		getBoolean: (key: string) => !!queryState[key],
68
		getNumber: (key: string) => undefined!,
B
Benjamin Pasero 已提交
69
		store: (key: string, value: any) => { queryState[key] = value; return Promise.resolve(); },
70
		remove: (key) => undefined,
71 72
		logStorage: () => undefined,
		migrate: (toWorkspace) => Promise.resolve(undefined)
73
	} as IStorageService);
74 75

	test('issue #8817: Cursor position changes when you cancel multicursor', () => {
A
Alex Dima 已提交
76
		withTestCodeEditor([
77 78 79 80 81
			'var x = (3 * 5)',
			'var y = (3 * 5)',
			'var z = (3 * 5)',
		], { serviceCollection: serviceCollection }, (editor, cursor) => {

82 83
			let findController = editor.registerAndInstantiateContribution<CommonFindController>(CommonFindController.ID, CommonFindController);
			let multiCursorSelectController = editor.registerAndInstantiateContribution<MultiCursorSelectionController>(MultiCursorSelectionController.ID, MultiCursorSelectionController);
84 85 86 87
			let selectHighlightsAction = new SelectHighlightsAction();

			editor.setSelection(new Selection(2, 9, 2, 16));

88 89
			selectHighlightsAction.run(null!, editor);
			assert.deepEqual(editor.getSelections()!.map(fromRange), [
90 91 92 93 94 95 96
				[2, 9, 2, 16],
				[1, 9, 1, 16],
				[3, 9, 3, 16],
			]);

			editor.trigger('test', 'removeSecondaryCursors', null);

97
			assert.deepEqual(fromRange(editor.getSelection()!), [2, 9, 2, 16]);
98 99 100 101 102 103 104

			multiCursorSelectController.dispose();
			findController.dispose();
		});
	});

	test('issue #5400: "Select All Occurrences of Find Match" does not select all if find uses regex', () => {
A
Alex Dima 已提交
105
		withTestCodeEditor([
106 107 108 109 110 111
			'something',
			'someething',
			'someeething',
			'nothing'
		], { serviceCollection: serviceCollection }, (editor, cursor) => {

112 113
			let findController = editor.registerAndInstantiateContribution<CommonFindController>(CommonFindController.ID, CommonFindController);
			let multiCursorSelectController = editor.registerAndInstantiateContribution<MultiCursorSelectionController>(MultiCursorSelectionController.ID, MultiCursorSelectionController);
114 115 116 117 118
			let selectHighlightsAction = new SelectHighlightsAction();

			editor.setSelection(new Selection(1, 1, 1, 1));
			findController.getState().change({ searchString: 'some+thing', isRegex: true, isRevealed: true }, false);

119 120
			selectHighlightsAction.run(null!, editor);
			assert.deepEqual(editor.getSelections()!.map(fromRange), [
121 122 123 124 125 126 127
				[1, 1, 1, 10],
				[2, 1, 2, 11],
				[3, 1, 3, 12],
			]);

			assert.equal(findController.getState().searchString, 'some+thing');

128
			multiCursorSelectController.dispose();
129 130 131 132 133
			findController.dispose();
		});
	});

	test('AddSelectionToNextFindMatchAction can work with multiline', () => {
A
Alex Dima 已提交
134
		withTestCodeEditor([
135 136 137 138 139 140 141 142 143 144 145
			'',
			'qwe',
			'rty',
			'',
			'qwe',
			'',
			'rty',
			'qwe',
			'rty'
		], { serviceCollection: serviceCollection }, (editor, cursor) => {

146 147
			let findController = editor.registerAndInstantiateContribution<CommonFindController>(CommonFindController.ID, CommonFindController);
			let multiCursorSelectController = editor.registerAndInstantiateContribution<MultiCursorSelectionController>(MultiCursorSelectionController.ID, MultiCursorSelectionController);
148 149 150 151
			let addSelectionToNextFindMatch = new AddSelectionToNextFindMatchAction();

			editor.setSelection(new Selection(2, 1, 3, 4));

152 153
			addSelectionToNextFindMatch.run(null!, editor);
			assert.deepEqual(editor.getSelections()!.map(fromRange), [
154 155 156 157 158 159
				[2, 1, 3, 4],
				[8, 1, 9, 4]
			]);

			editor.trigger('test', 'removeSecondaryCursors', null);

160
			assert.deepEqual(fromRange(editor.getSelection()!), [2, 1, 3, 4]);
161 162 163 164 165 166 167

			multiCursorSelectController.dispose();
			findController.dispose();
		});
	});

	test('issue #6661: AddSelectionToNextFindMatchAction can work with touching ranges', () => {
A
Alex Dima 已提交
168
		withTestCodeEditor([
169 170 171 172 173
			'abcabc',
			'abc',
			'abcabc',
		], { serviceCollection: serviceCollection }, (editor, cursor) => {

174 175
			let findController = editor.registerAndInstantiateContribution<CommonFindController>(CommonFindController.ID, CommonFindController);
			let multiCursorSelectController = editor.registerAndInstantiateContribution<MultiCursorSelectionController>(MultiCursorSelectionController.ID, MultiCursorSelectionController);
176 177 178 179
			let addSelectionToNextFindMatch = new AddSelectionToNextFindMatchAction();

			editor.setSelection(new Selection(1, 1, 1, 4));

180 181
			addSelectionToNextFindMatch.run(null!, editor);
			assert.deepEqual(editor.getSelections()!.map(fromRange), [
182 183 184 185
				[1, 1, 1, 4],
				[1, 4, 1, 7]
			]);

186 187 188 189
			addSelectionToNextFindMatch.run(null!, editor);
			addSelectionToNextFindMatch.run(null!, editor);
			addSelectionToNextFindMatch.run(null!, editor);
			assert.deepEqual(editor.getSelections()!.map(fromRange), [
190 191 192 193 194 195 196 197
				[1, 1, 1, 4],
				[1, 4, 1, 7],
				[2, 1, 2, 4],
				[3, 1, 3, 4],
				[3, 4, 3, 7]
			]);

			editor.trigger('test', Handler.Type, { text: 'z' });
198
			assert.deepEqual(editor.getSelections()!.map(fromRange), [
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
				[1, 2, 1, 2],
				[1, 3, 1, 3],
				[2, 2, 2, 2],
				[3, 2, 3, 2],
				[3, 3, 3, 3]
			]);
			assert.equal(editor.getValue(), [
				'zz',
				'z',
				'zz',
			].join('\n'));

			multiCursorSelectController.dispose();
			findController.dispose();
		});
	});

	test('issue #23541: Multiline Ctrl+D does not work in CRLF files', () => {
A
Alex Dima 已提交
217
		withTestCodeEditor([
218 219 220 221 222 223 224 225 226 227 228
			'',
			'qwe',
			'rty',
			'',
			'qwe',
			'',
			'rty',
			'qwe',
			'rty'
		], { serviceCollection: serviceCollection }, (editor, cursor) => {

229
			editor.getModel()!.setEOL(EndOfLineSequence.CRLF);
230

231 232
			let findController = editor.registerAndInstantiateContribution<CommonFindController>(CommonFindController.ID, CommonFindController);
			let multiCursorSelectController = editor.registerAndInstantiateContribution<MultiCursorSelectionController>(MultiCursorSelectionController.ID, MultiCursorSelectionController);
233 234 235 236
			let addSelectionToNextFindMatch = new AddSelectionToNextFindMatchAction();

			editor.setSelection(new Selection(2, 1, 3, 4));

237 238
			addSelectionToNextFindMatch.run(null!, editor);
			assert.deepEqual(editor.getSelections()!.map(fromRange), [
239 240 241 242 243 244
				[2, 1, 3, 4],
				[8, 1, 9, 4]
			]);

			editor.trigger('test', 'removeSecondaryCursors', null);

245
			assert.deepEqual(fromRange(editor.getSelection()!), [2, 1, 3, 4]);
246 247 248 249 250 251

			multiCursorSelectController.dispose();
			findController.dispose();
		});
	});

A
Alex Dima 已提交
252 253
	function testMulticursor(text: string[], callback: (editor: TestCodeEditor, findController: CommonFindController) => void): void {
		withTestCodeEditor(text, { serviceCollection: serviceCollection }, (editor, cursor) => {
254 255
			let findController = editor.registerAndInstantiateContribution<CommonFindController>(CommonFindController.ID, CommonFindController);
			let multiCursorSelectController = editor.registerAndInstantiateContribution<MultiCursorSelectionController>(MultiCursorSelectionController.ID, MultiCursorSelectionController);
256

257
			callback(editor, findController);
258 259 260 261 262 263

			multiCursorSelectController.dispose();
			findController.dispose();
		});
	}

A
Alex Dima 已提交
264
	function testAddSelectionToNextFindMatchAction(text: string[], callback: (editor: TestCodeEditor, action: AddSelectionToNextFindMatchAction, findController: CommonFindController) => void): void {
265 266 267 268 269 270
		testMulticursor(text, (editor, findController) => {
			let action = new AddSelectionToNextFindMatchAction();
			callback(editor, action, findController);
		});
	}

271 272 273 274 275 276 277 278 279 280 281
	test('AddSelectionToNextFindMatchAction starting with single collapsed selection', () => {
		const text = [
			'abc pizza',
			'abc house',
			'abc bar'
		];
		testAddSelectionToNextFindMatchAction(text, (editor, action, findController) => {
			editor.setSelections([
				new Selection(1, 2, 1, 2),
			]);

282
			action.run(null!, editor);
283 284 285 286
			assert.deepEqual(editor.getSelections(), [
				new Selection(1, 1, 1, 4),
			]);

287
			action.run(null!, editor);
288 289 290 291 292
			assert.deepEqual(editor.getSelections(), [
				new Selection(1, 1, 1, 4),
				new Selection(2, 1, 2, 4),
			]);

293
			action.run(null!, editor);
294 295 296 297 298 299
			assert.deepEqual(editor.getSelections(), [
				new Selection(1, 1, 1, 4),
				new Selection(2, 1, 2, 4),
				new Selection(3, 1, 3, 4),
			]);

300
			action.run(null!, editor);
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
			assert.deepEqual(editor.getSelections(), [
				new Selection(1, 1, 1, 4),
				new Selection(2, 1, 2, 4),
				new Selection(3, 1, 3, 4),
			]);
		});
	});

	test('AddSelectionToNextFindMatchAction starting with two selections, one being collapsed 1)', () => {
		const text = [
			'abc pizza',
			'abc house',
			'abc bar'
		];
		testAddSelectionToNextFindMatchAction(text, (editor, action, findController) => {
			editor.setSelections([
				new Selection(1, 1, 1, 4),
				new Selection(2, 2, 2, 2),
			]);

321
			action.run(null!, editor);
322 323 324 325 326
			assert.deepEqual(editor.getSelections(), [
				new Selection(1, 1, 1, 4),
				new Selection(2, 1, 2, 4),
			]);

327
			action.run(null!, editor);
328 329 330 331 332 333
			assert.deepEqual(editor.getSelections(), [
				new Selection(1, 1, 1, 4),
				new Selection(2, 1, 2, 4),
				new Selection(3, 1, 3, 4),
			]);

334
			action.run(null!, editor);
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
			assert.deepEqual(editor.getSelections(), [
				new Selection(1, 1, 1, 4),
				new Selection(2, 1, 2, 4),
				new Selection(3, 1, 3, 4),
			]);
		});
	});

	test('AddSelectionToNextFindMatchAction starting with two selections, one being collapsed 2)', () => {
		const text = [
			'abc pizza',
			'abc house',
			'abc bar'
		];
		testAddSelectionToNextFindMatchAction(text, (editor, action, findController) => {
			editor.setSelections([
				new Selection(1, 2, 1, 2),
				new Selection(2, 1, 2, 4),
			]);

355
			action.run(null!, editor);
356 357 358 359 360
			assert.deepEqual(editor.getSelections(), [
				new Selection(1, 1, 1, 4),
				new Selection(2, 1, 2, 4),
			]);

361
			action.run(null!, editor);
362 363 364 365 366 367
			assert.deepEqual(editor.getSelections(), [
				new Selection(1, 1, 1, 4),
				new Selection(2, 1, 2, 4),
				new Selection(3, 1, 3, 4),
			]);

368
			action.run(null!, editor);
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
			assert.deepEqual(editor.getSelections(), [
				new Selection(1, 1, 1, 4),
				new Selection(2, 1, 2, 4),
				new Selection(3, 1, 3, 4),
			]);
		});
	});

	test('AddSelectionToNextFindMatchAction starting with all collapsed selections', () => {
		const text = [
			'abc pizza',
			'abc house',
			'abc bar'
		];
		testAddSelectionToNextFindMatchAction(text, (editor, action, findController) => {
			editor.setSelections([
				new Selection(1, 2, 1, 2),
				new Selection(2, 2, 2, 2),
				new Selection(3, 1, 3, 1),
			]);

390
			action.run(null!, editor);
391 392 393 394 395 396
			assert.deepEqual(editor.getSelections(), [
				new Selection(1, 1, 1, 4),
				new Selection(2, 1, 2, 4),
				new Selection(3, 1, 3, 4),
			]);

397
			action.run(null!, editor);
398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
			assert.deepEqual(editor.getSelections(), [
				new Selection(1, 1, 1, 4),
				new Selection(2, 1, 2, 4),
				new Selection(3, 1, 3, 4),
			]);
		});
	});

	test('AddSelectionToNextFindMatchAction starting with all collapsed selections on different words', () => {
		const text = [
			'abc pizza',
			'abc house',
			'abc bar'
		];
		testAddSelectionToNextFindMatchAction(text, (editor, action, findController) => {
			editor.setSelections([
				new Selection(1, 6, 1, 6),
				new Selection(2, 6, 2, 6),
				new Selection(3, 6, 3, 6),
			]);

419
			action.run(null!, editor);
420 421 422 423 424 425
			assert.deepEqual(editor.getSelections(), [
				new Selection(1, 5, 1, 10),
				new Selection(2, 5, 2, 10),
				new Selection(3, 5, 3, 8),
			]);

426
			action.run(null!, editor);
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
			assert.deepEqual(editor.getSelections(), [
				new Selection(1, 5, 1, 10),
				new Selection(2, 5, 2, 10),
				new Selection(3, 5, 3, 8),
			]);
		});
	});

	test('issue #20651: AddSelectionToNextFindMatchAction case insensitive', () => {
		const text = [
			'test',
			'testte',
			'Test',
			'testte',
			'test'
		];
		testAddSelectionToNextFindMatchAction(text, (editor, action, findController) => {
			editor.setSelections([
				new Selection(1, 1, 1, 5),
			]);

448
			action.run(null!, editor);
449 450 451 452 453
			assert.deepEqual(editor.getSelections(), [
				new Selection(1, 1, 1, 5),
				new Selection(2, 1, 2, 5),
			]);

454
			action.run(null!, editor);
455 456 457 458 459 460
			assert.deepEqual(editor.getSelections(), [
				new Selection(1, 1, 1, 5),
				new Selection(2, 1, 2, 5),
				new Selection(3, 1, 3, 5),
			]);

461
			action.run(null!, editor);
462 463 464 465 466 467 468
			assert.deepEqual(editor.getSelections(), [
				new Selection(1, 1, 1, 5),
				new Selection(2, 1, 2, 5),
				new Selection(3, 1, 3, 5),
				new Selection(4, 1, 4, 5),
			]);

469
			action.run(null!, editor);
470 471 472 473 474 475 476 477
			assert.deepEqual(editor.getSelections(), [
				new Selection(1, 1, 1, 5),
				new Selection(2, 1, 2, 5),
				new Selection(3, 1, 3, 5),
				new Selection(4, 1, 4, 5),
				new Selection(5, 1, 5, 5),
			]);

478
			action.run(null!, editor);
479 480 481 482 483 484 485 486 487
			assert.deepEqual(editor.getSelections(), [
				new Selection(1, 1, 1, 5),
				new Selection(2, 1, 2, 5),
				new Selection(3, 1, 3, 5),
				new Selection(4, 1, 4, 5),
				new Selection(5, 1, 5, 5),
			]);
		});
	});
488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505

	suite('Find state disassociation', () => {

		const text = [
			'app',
			'apples',
			'whatsapp',
			'app',
			'App',
			' app'
		];

		test('enters mode', () => {
			testAddSelectionToNextFindMatchAction(text, (editor, action, findController) => {
				editor.setSelections([
					new Selection(1, 2, 1, 2),
				]);

506
				action.run(null!, editor);
507 508 509 510
				assert.deepEqual(editor.getSelections(), [
					new Selection(1, 1, 1, 4),
				]);

511
				action.run(null!, editor);
512 513 514 515 516
				assert.deepEqual(editor.getSelections(), [
					new Selection(1, 1, 1, 4),
					new Selection(4, 1, 4, 4),
				]);

517
				action.run(null!, editor);
518 519 520 521 522 523 524 525 526 527 528 529 530 531
				assert.deepEqual(editor.getSelections(), [
					new Selection(1, 1, 1, 4),
					new Selection(4, 1, 4, 4),
					new Selection(6, 2, 6, 5),
				]);
			});
		});

		test('leaves mode when selection changes', () => {
			testAddSelectionToNextFindMatchAction(text, (editor, action, findController) => {
				editor.setSelections([
					new Selection(1, 2, 1, 2),
				]);

532
				action.run(null!, editor);
533 534 535 536
				assert.deepEqual(editor.getSelections(), [
					new Selection(1, 1, 1, 4),
				]);

537
				action.run(null!, editor);
538 539 540 541 542 543 544 545 546 547
				assert.deepEqual(editor.getSelections(), [
					new Selection(1, 1, 1, 4),
					new Selection(4, 1, 4, 4),
				]);

				// change selection
				editor.setSelections([
					new Selection(1, 1, 1, 4),
				]);

548
				action.run(null!, editor);
549 550 551 552 553 554 555
				assert.deepEqual(editor.getSelections(), [
					new Selection(1, 1, 1, 4),
					new Selection(2, 1, 2, 4),
				]);
			});
		});

556 557 558 559 560 561 562
		test('Select Highlights respects mode ', () => {
			testMulticursor(text, (editor, findController) => {
				let action = new SelectHighlightsAction();
				editor.setSelections([
					new Selection(1, 2, 1, 2),
				]);

563
				action.run(null!, editor);
564 565 566 567 568 569
				assert.deepEqual(editor.getSelections(), [
					new Selection(1, 1, 1, 4),
					new Selection(4, 1, 4, 4),
					new Selection(6, 2, 6, 5),
				]);

570
				action.run(null!, editor);
571 572 573 574 575 576 577 578
				assert.deepEqual(editor.getSelections(), [
					new Selection(1, 1, 1, 4),
					new Selection(4, 1, 4, 4),
					new Selection(6, 2, 6, 5),
				]);
			});
		});

579
	});
580
});