Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/snippet/test/browser/snippetController2.test.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
import assert from 'assert';
6
import { mock } from '../../../../../base/test/common/mock.js';
7
import { CoreEditingCommands } from '../../../../browser/coreCommands.js';
8
import { ICodeEditor } from '../../../../browser/editorBrowser.js';
9
import { Selection } from '../../../../common/core/selection.js';
10
import { Range } from '../../../../common/core/range.js';
11
import { Handler } from '../../../../common/editorCommon.js';
12
import { TextModel } from '../../../../common/model/textModel.js';
13
import { SnippetController2 } from '../../browser/snippetController2.js';
14
import { createTestCodeEditor, ITestCodeEditor } from '../../../../test/browser/testCodeEditor.js';
15
import { createTextModel } from '../../../../test/common/testTextModel.js';
16
import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
17
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
18
import { InstantiationService } from '../../../../../platform/instantiation/common/instantiationService.js';
19
import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js';
20
import { MockContextKeyService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js';
21
import { ILabelService } from '../../../../../platform/label/common/label.js';
22
import { ILogService, NullLogService } from '../../../../../platform/log/common/log.js';
23
import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js';
24
import { EndOfLineSequence } from '../../../../common/model.js';
25
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
26
27
suite('SnippetController2', function () {
28
29
/** @deprecated */
30
function assertSelections(editor: ICodeEditor, ...s: Selection[]) {
31
for (const selection of editor.getSelections()!) {
32
const actual = s.shift()!;
33
assert.ok(selection.equalsSelection(actual), `actual=${selection.toString()} <> expected=${actual.toString()}`);
34
}
35
assert.strictEqual(s.length, 0);
36
}
37
38
function assertContextKeys(service: MockContextKeyService, inSnippet: boolean, hasPrev: boolean, hasNext: boolean): void {
39
const state = getContextState(service);
40
assert.strictEqual(state.inSnippet, inSnippet, `inSnippetMode`);
41
assert.strictEqual(state.hasPrev, hasPrev, `HasPrevTabstop`);
42
assert.strictEqual(state.hasNext, hasNext, `HasNextTabstop`);
43
}
44
45
function getContextState(service: MockContextKeyService = contextKeys) {
46
return {
47
inSnippet: SnippetController2.InSnippetMode.getValue(service),
48
hasPrev: SnippetController2.HasPrevTabstop.getValue(service),
49
hasNext: SnippetController2.HasNextTabstop.getValue(service),
50
};
51
}
52
53
let ctrl: SnippetController2;
54
let editor: ITestCodeEditor;
55
let model: TextModel;
56
let contextKeys: MockContextKeyService;
57
let instaService: IInstantiationService;
58
59
setup(function () {
60
contextKeys = new MockContextKeyService();
61
model = createTextModel('if\n $state\nfi');
62
const serviceCollection = new ServiceCollection(
63
[ILabelService, new class extends mock<ILabelService>() { }],
64
[IWorkspaceContextService, new class extends mock<IWorkspaceContextService>() {
65
override getWorkspace() {
66
return { id: 'foo', folders: [] };
67
}
68
}],
69
[ILogService, new NullLogService()],
70
[IContextKeyService, contextKeys],
71
);
72
instaService = new InstantiationService(serviceCollection);
73
editor = createTestCodeEditor(model, { serviceCollection });
74
editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 5, 2, 5)]);
75
assert.strictEqual(model.getEOL(), '\n');
76
});
77
78
teardown(function () {
79
model.dispose();
80
ctrl.dispose();
81
});
82
83
ensureNoDisposablesAreLeakedInTestSuite();
84
85
test('creation', () => {
86
ctrl = instaService.createInstance(SnippetController2, editor);
87
assertContextKeys(contextKeys, false, false, false);
88
});
89
90
test('insert, insert -> abort', function () {
91
ctrl = instaService.createInstance(SnippetController2, editor);
92
93
ctrl.insert('foo${1:bar}foo$0');
94
assertContextKeys(contextKeys, true, false, true);
95
assertSelections(editor, new Selection(1, 4, 1, 7), new Selection(2, 8, 2, 11));
96
97
ctrl.cancel();
98
assertContextKeys(contextKeys, false, false, false);
99
assertSelections(editor, new Selection(1, 4, 1, 7), new Selection(2, 8, 2, 11));
100
});
101
102
test('insert, insert -> tab, tab, done', function () {
103
ctrl = instaService.createInstance(SnippetController2, editor);
104
105
ctrl.insert('${1:one}${2:two}$0');
106
assertContextKeys(contextKeys, true, false, true);
107
108
ctrl.next();
109
assertContextKeys(contextKeys, true, true, true);
110
111
ctrl.next();
112
assertContextKeys(contextKeys, false, false, false);
113
114
editor.trigger('test', 'type', { text: '\t' });
115
assert.strictEqual(SnippetController2.InSnippetMode.getValue(contextKeys), false);
116
assert.strictEqual(SnippetController2.HasNextTabstop.getValue(contextKeys), false);
117
assert.strictEqual(SnippetController2.HasPrevTabstop.getValue(contextKeys), false);
118
});
119
120
test('insert, insert -> cursor moves out (left/right)', function () {
121
ctrl = instaService.createInstance(SnippetController2, editor);
122
123
ctrl.insert('foo${1:bar}foo$0');
124
assertContextKeys(contextKeys, true, false, true);
125
assertSelections(editor, new Selection(1, 4, 1, 7), new Selection(2, 8, 2, 11));
126
127
// bad selection change
128
editor.setSelections([new Selection(1, 12, 1, 12), new Selection(2, 16, 2, 16)]);
129
assertContextKeys(contextKeys, false, false, false);
130
});
131
132
test('insert, insert -> cursor moves out (up/down)', function () {
133
ctrl = instaService.createInstance(SnippetController2, editor);
134
135
ctrl.insert('foo${1:bar}foo$0');
136
assertContextKeys(contextKeys, true, false, true);
137
assertSelections(editor, new Selection(1, 4, 1, 7), new Selection(2, 8, 2, 11));
138
139
// bad selection change
140
editor.setSelections([new Selection(2, 4, 2, 7), new Selection(3, 8, 3, 11)]);
141
assertContextKeys(contextKeys, false, false, false);
142
});
143
144
test('insert, insert -> cursors collapse', function () {
145
ctrl = instaService.createInstance(SnippetController2, editor);
146
147
ctrl.insert('foo${1:bar}foo$0');
148
assert.strictEqual(SnippetController2.InSnippetMode.getValue(contextKeys), true);
149
assertSelections(editor, new Selection(1, 4, 1, 7), new Selection(2, 8, 2, 11));
150
151
// bad selection change
152
editor.setSelections([new Selection(1, 4, 1, 7)]);
153
assertContextKeys(contextKeys, false, false, false);
154
});
155
156
test('insert, insert plain text -> no snippet mode', function () {
157
ctrl = instaService.createInstance(SnippetController2, editor);
158
159
ctrl.insert('foobar');
160
assertContextKeys(contextKeys, false, false, false);
161
assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11));
162
});
163
164
test('insert, delete snippet text', function () {
165
ctrl = instaService.createInstance(SnippetController2, editor);
166
167
ctrl.insert('${1:foobar}$0');
168
assertContextKeys(contextKeys, true, false, true);
169
assertSelections(editor, new Selection(1, 1, 1, 7), new Selection(2, 5, 2, 11));
170
171
editor.trigger('test', 'cut', {});
172
assertContextKeys(contextKeys, true, false, true);
173
assertSelections(editor, new Selection(1, 1, 1, 1), new Selection(2, 5, 2, 5));
174
175
editor.trigger('test', 'type', { text: 'abc' });
176
assertContextKeys(contextKeys, true, false, true);
177
178
ctrl.next();
179
assertContextKeys(contextKeys, false, false, false);
180
181
editor.trigger('test', 'tab', {});
182
assertContextKeys(contextKeys, false, false, false);
183
184
// editor.trigger('test', 'type', { text: 'abc' });
185
// assertContextKeys(contextKeys, false, false, false);
186
});
187
188
test('insert, nested trivial snippet', function () {
189
ctrl = instaService.createInstance(SnippetController2, editor);
190
ctrl.insert('${1:foo}bar$0');
191
assertContextKeys(contextKeys, true, false, true);
192
assertSelections(editor, new Selection(1, 1, 1, 4), new Selection(2, 5, 2, 8));
193
194
ctrl.insert('FOO$0');
195
assertSelections(editor, new Selection(1, 4, 1, 4), new Selection(2, 8, 2, 8));
196
assertContextKeys(contextKeys, true, false, true);
197
198
ctrl.next();
199
assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11));
200
assertContextKeys(contextKeys, false, false, false);
201
});
202
203
test('insert, nested snippet', function () {
204
ctrl = instaService.createInstance(SnippetController2, editor);
205
ctrl.insert('${1:foobar}$0');
206
assertContextKeys(contextKeys, true, false, true);
207
assertSelections(editor, new Selection(1, 1, 1, 7), new Selection(2, 5, 2, 11));
208
209
ctrl.insert('far$1boo$0');
210
assertSelections(editor, new Selection(1, 4, 1, 4), new Selection(2, 8, 2, 8));
211
assertContextKeys(contextKeys, true, false, true);
212
213
ctrl.next();
214
assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11));
215
assertContextKeys(contextKeys, true, true, true);
216
217
ctrl.next();
218
assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11));
219
assertContextKeys(contextKeys, false, false, false);
220
});
221
222
test('insert, nested plain text', function () {
223
ctrl = instaService.createInstance(SnippetController2, editor);
224
ctrl.insert('${1:foobar}$0');
225
assertContextKeys(contextKeys, true, false, true);
226
assertSelections(editor, new Selection(1, 1, 1, 7), new Selection(2, 5, 2, 11));
227
228
ctrl.insert('farboo');
229
assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11));
230
assertContextKeys(contextKeys, true, false, true);
231
232
ctrl.next();
233
assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11));
234
assertContextKeys(contextKeys, false, false, false);
235
});
236
237
test('Nested snippets without final placeholder jumps to next outer placeholder, #27898', function () {
238
ctrl = instaService.createInstance(SnippetController2, editor);
239
240
ctrl.insert('for(const ${1:element} of ${2:array}) {$0}');
241
assertContextKeys(contextKeys, true, false, true);
242
assertSelections(editor, new Selection(1, 11, 1, 18), new Selection(2, 15, 2, 22));
243
244
ctrl.next();
245
assertContextKeys(contextKeys, true, true, true);
246
assertSelections(editor, new Selection(1, 22, 1, 27), new Selection(2, 26, 2, 31));
247
248
ctrl.insert('document');
249
assertContextKeys(contextKeys, true, true, true);
250
assertSelections(editor, new Selection(1, 30, 1, 30), new Selection(2, 34, 2, 34));
251
252
ctrl.next();
253
assertContextKeys(contextKeys, false, false, false);
254
});
255
256
test('Inconsistent tab stop behaviour with recursive snippets and tab / shift tab, #27543', function () {
257
ctrl = instaService.createInstance(SnippetController2, editor);
258
ctrl.insert('1_calize(${1:nl}, \'${2:value}\')$0');
259
260
assertContextKeys(contextKeys, true, false, true);
261
assertSelections(editor, new Selection(1, 10, 1, 12), new Selection(2, 14, 2, 16));
262
263
ctrl.insert('2_calize(${1:nl}, \'${2:value}\')$0');
264
265
assertSelections(editor, new Selection(1, 19, 1, 21), new Selection(2, 23, 2, 25));
266
267
ctrl.next(); // inner `value`
268
assertSelections(editor, new Selection(1, 24, 1, 29), new Selection(2, 28, 2, 33));
269
270
ctrl.next(); // inner `$0`
271
assertSelections(editor, new Selection(1, 31, 1, 31), new Selection(2, 35, 2, 35));
272
273
ctrl.next(); // outer `value`
274
assertSelections(editor, new Selection(1, 34, 1, 39), new Selection(2, 38, 2, 43));
275
276
ctrl.prev(); // inner `$0`
277
assertSelections(editor, new Selection(1, 31, 1, 31), new Selection(2, 35, 2, 35));
278
});
279
280
test('Snippet tabstop selecting content of previously entered variable only works when separated by space, #23728', function () {
281
ctrl = instaService.createInstance(SnippetController2, editor);
282
283
model.setValue('');
284
editor.setSelection(new Selection(1, 1, 1, 1));
285
286
ctrl.insert('import ${2:${1:module}} from \'${1:module}\'$0');
287
288
assertContextKeys(contextKeys, true, false, true);
289
assertSelections(editor, new Selection(1, 8, 1, 14), new Selection(1, 21, 1, 27));
290
291
ctrl.insert('foo');
292
assertSelections(editor, new Selection(1, 11, 1, 11), new Selection(1, 21, 1, 21));
293
294
ctrl.next(); // ${2:...}
295
assertSelections(editor, new Selection(1, 8, 1, 11));
296
});
297
298
test('HTML Snippets Combine, #32211', function () {
299
ctrl = instaService.createInstance(SnippetController2, editor);
300
301
model.setValue('');
302
model.updateOptions({ insertSpaces: false, tabSize: 4, trimAutoWhitespace: false });
303
editor.setSelection(new Selection(1, 1, 1, 1));
304
305
ctrl.insert(`
306
<!DOCTYPE html>
307
<html lang="en">
308
<head>
309
<meta charset="UTF-8">
310
<meta name="viewport" content="width=\${2:device-width}, initial-scale=\${3:1.0}">
311
<meta http-equiv="X-UA-Compatible" content="\${5:ie=edge}">
312
<title>\${7:Document}</title>
313
</head>
314
<body>
315
\${8}
316
</body>
317
</html>
318
`);
319
ctrl.next();
320
ctrl.next();
321
ctrl.next();
322
ctrl.next();
323
assertSelections(editor, new Selection(11, 5, 11, 5));
324
325
ctrl.insert('<input type="${2:text}">');
326
assertSelections(editor, new Selection(11, 18, 11, 22));
327
});
328
329
test('Problems with nested snippet insertion #39594', function () {
330
ctrl = instaService.createInstance(SnippetController2, editor);
331
332
model.setValue('');
333
editor.setSelection(new Selection(1, 1, 1, 1));
334
335
ctrl.insert('$1 = ConvertTo-Json $1');
336
assertSelections(editor, new Selection(1, 1, 1, 1), new Selection(1, 19, 1, 19));
337
338
editor.setSelection(new Selection(1, 19, 1, 19));
339
340
// snippet mode should stop because $1 has two occurrences
341
// and we only have one selection left
342
assertContextKeys(contextKeys, false, false, false);
343
});
344
345
test('Problems with nested snippet insertion #39594 (part2)', function () {
346
// ensure selection-change-to-cancel logic isn't too aggressive
347
ctrl = instaService.createInstance(SnippetController2, editor);
348
349
model.setValue('a-\naaa-');
350
editor.setSelections([new Selection(2, 5, 2, 5), new Selection(1, 3, 1, 3)]);
351
352
ctrl.insert('log($1);$0');
353
assertSelections(editor, new Selection(2, 9, 2, 9), new Selection(1, 7, 1, 7));
354
assertContextKeys(contextKeys, true, false, true);
355
});
356
357
test('“Nested” snippets terminating abruptly in VSCode 1.19.2. #42012', function () {
358
359
ctrl = instaService.createInstance(SnippetController2, editor);
360
model.setValue('');
361
editor.setSelection(new Selection(1, 1, 1, 1));
362
ctrl.insert('var ${2:${1:name}} = ${1:name} + 1;${0}');
363
364
assertSelections(editor, new Selection(1, 5, 1, 9), new Selection(1, 12, 1, 16));
365
assertContextKeys(contextKeys, true, false, true);
366
367
ctrl.next();
368
assertContextKeys(contextKeys, true, true, true);
369
});
370
371
test('Placeholders order #58267', function () {
372
373
ctrl = instaService.createInstance(SnippetController2, editor);
374
model.setValue('');
375
editor.setSelection(new Selection(1, 1, 1, 1));
376
ctrl.insert('\\pth{$1}$0');
377
378
assertSelections(editor, new Selection(1, 6, 1, 6));
379
assertContextKeys(contextKeys, true, false, true);
380
381
ctrl.insert('\\itv{${1:left}}{${2:right}}{${3:left_value}}{${4:right_value}}$0');
382
assertSelections(editor, new Selection(1, 11, 1, 15));
383
384
ctrl.next();
385
assertSelections(editor, new Selection(1, 17, 1, 22));
386
387
ctrl.next();
388
assertSelections(editor, new Selection(1, 24, 1, 34));
389
390
ctrl.next();
391
assertSelections(editor, new Selection(1, 36, 1, 47));
392
393
ctrl.next();
394
assertSelections(editor, new Selection(1, 48, 1, 48));
395
396
ctrl.next();
397
assertSelections(editor, new Selection(1, 49, 1, 49));
398
assertContextKeys(contextKeys, false, false, false);
399
});
400
401
test('Must tab through deleted tab stops in snippets #31619', function () {
402
ctrl = instaService.createInstance(SnippetController2, editor);
403
model.setValue('');
404
editor.setSelection(new Selection(1, 1, 1, 1));
405
ctrl.insert('foo${1:a${2:bar}baz}end$0');
406
assertSelections(editor, new Selection(1, 4, 1, 11));
407
408
editor.trigger('test', Handler.Cut, null);
409
assertSelections(editor, new Selection(1, 4, 1, 4));
410
411
ctrl.next();
412
assertSelections(editor, new Selection(1, 7, 1, 7));
413
assertContextKeys(contextKeys, false, false, false);
414
});
415
416
test('Cancelling snippet mode should discard added cursors #68512 (soft cancel)', function () {
417
ctrl = instaService.createInstance(SnippetController2, editor);
418
model.setValue('');
419
editor.setSelection(new Selection(1, 1, 1, 1));
420
421
ctrl.insert('.REGION ${2:FUNCTION_NAME}\nCREATE.FUNCTION ${1:VOID} ${2:FUNCTION_NAME}(${3:})\n\t${4:}\nEND\n.ENDREGION$0');
422
assertSelections(editor, new Selection(2, 17, 2, 21));
423
424
ctrl.next();
425
assertSelections(editor, new Selection(1, 9, 1, 22), new Selection(2, 22, 2, 35));
426
assertContextKeys(contextKeys, true, true, true);
427
428
editor.setSelections([new Selection(1, 22, 1, 22), new Selection(2, 35, 2, 35)]);
429
assertContextKeys(contextKeys, true, true, true);
430
431
editor.setSelections([new Selection(2, 1, 2, 1), new Selection(2, 36, 2, 36)]);
432
assertContextKeys(contextKeys, false, false, false);
433
assertSelections(editor, new Selection(2, 1, 2, 1), new Selection(2, 36, 2, 36));
434
});
435
436
test('Cancelling snippet mode should discard added cursors #68512 (hard cancel)', function () {
437
ctrl = instaService.createInstance(SnippetController2, editor);
438
model.setValue('');
439
editor.setSelection(new Selection(1, 1, 1, 1));
440
441
ctrl.insert('.REGION ${2:FUNCTION_NAME}\nCREATE.FUNCTION ${1:VOID} ${2:FUNCTION_NAME}(${3:})\n\t${4:}\nEND\n.ENDREGION$0');
442
assertSelections(editor, new Selection(2, 17, 2, 21));
443
444
ctrl.next();
445
assertSelections(editor, new Selection(1, 9, 1, 22), new Selection(2, 22, 2, 35));
446
assertContextKeys(contextKeys, true, true, true);
447
448
editor.setSelections([new Selection(1, 22, 1, 22), new Selection(2, 35, 2, 35)]);
449
assertContextKeys(contextKeys, true, true, true);
450
451
ctrl.cancel(true);
452
assertContextKeys(contextKeys, false, false, false);
453
assertSelections(editor, new Selection(1, 22, 1, 22));
454
});
455
456
test('User defined snippet tab stops ignored #72862', function () {
457
ctrl = instaService.createInstance(SnippetController2, editor);
458
model.setValue('');
459
editor.setSelection(new Selection(1, 1, 1, 1));
460
461
ctrl.insert('export default $1');
462
assertContextKeys(contextKeys, true, false, true);
463
});
464
465
test('Optional tabstop in snippets #72358', function () {
466
ctrl = instaService.createInstance(SnippetController2, editor);
467
model.setValue('');
468
editor.setSelection(new Selection(1, 1, 1, 1));
469
470
ctrl.insert('${1:prop: {$2\\},}\nmore$0');
471
assertContextKeys(contextKeys, true, false, true);
472
473
assertSelections(editor, new Selection(1, 1, 1, 10));
474
editor.trigger('test', Handler.Cut, {});
475
476
assertSelections(editor, new Selection(1, 1, 1, 1));
477
478
ctrl.next();
479
assertSelections(editor, new Selection(2, 5, 2, 5));
480
assertContextKeys(contextKeys, false, false, false);
481
});
482
483
test('issue #90135: confusing trim whitespace edits', function () {
484
ctrl = instaService.createInstance(SnippetController2, editor);
485
model.setValue('');
486
editor.runCommand(CoreEditingCommands.Tab, null);
487
488
ctrl.insert('\nfoo');
489
assertSelections(editor, new Selection(2, 8, 2, 8));
490
});
491
492
test('issue #145727: insertSnippet can put snippet selections in wrong positions (1 of 2)', function () {
493
ctrl = instaService.createInstance(SnippetController2, editor);
494
model.setValue('');
495
editor.runCommand(CoreEditingCommands.Tab, null);
496
497
ctrl.insert('\naProperty: aClass<${2:boolean}> = new aClass<${2:boolean}>();\n', { adjustWhitespace: false });
498
assertSelections(editor, new Selection(2, 19, 2, 26), new Selection(2, 41, 2, 48));
499
});
500
501
test('issue #145727: insertSnippet can put snippet selections in wrong positions (2 of 2)', function () {
502
ctrl = instaService.createInstance(SnippetController2, editor);
503
model.setValue('');
504
editor.runCommand(CoreEditingCommands.Tab, null);
505
506
ctrl.insert('\naProperty: aClass<${2:boolean}> = new aClass<${2:boolean}>();\n');
507
// This will insert \n aProperty....
508
assertSelections(editor, new Selection(2, 23, 2, 30), new Selection(2, 45, 2, 52));
509
});
510
511
test('leading TAB by snippets won\'t replace by spaces #101870', function () {
512
ctrl = instaService.createInstance(SnippetController2, editor);
513
model.setValue('');
514
model.updateOptions({ insertSpaces: true, tabSize: 4 });
515
ctrl.insert('\tHello World\n\tNew Line');
516
assert.strictEqual(model.getValue(), ' Hello World\n New Line');
517
});
518
519
test('leading TAB by snippets won\'t replace by spaces #101870 (part 2)', function () {
520
ctrl = instaService.createInstance(SnippetController2, editor);
521
model.setValue('');
522
model.updateOptions({ insertSpaces: true, tabSize: 4 });
523
ctrl.insert('\tHello World\n\tNew Line\n${1:\tmore}');
524
assert.strictEqual(model.getValue(), ' Hello World\n New Line\n more');
525
});
526
527
test.skip('Snippet transformation does not work after inserting variable using intellisense, #112362', function () {
528
529
{
530
// HAPPY - no nested snippet
531
ctrl = instaService.createInstance(SnippetController2, editor);
532
model.setValue('');
533
model.updateOptions({ insertSpaces: true, tabSize: 4 });
534
ctrl.insert('$1\n\n${1/([A-Za-z0-9]+): ([A-Za-z]+).*/$1: \'$2\',/gm}');
535
536
assertSelections(editor, new Selection(1, 1, 1, 1), new Selection(3, 1, 3, 1));
537
editor.trigger('test', 'type', { text: 'foo: number;' });
538
ctrl.next();
539
assert.strictEqual(model.getValue(), `foo: number;\n\nfoo: 'number',`);
540
}
541
542
ctrl = instaService.createInstance(SnippetController2, editor);
543
model.setValue('');
544
model.updateOptions({ insertSpaces: true, tabSize: 4 });
545
ctrl.insert('$1\n\n${1/([A-Za-z0-9]+): ([A-Za-z]+).*/$1: \'$2\',/gm}');
546
547
assertSelections(editor, new Selection(1, 1, 1, 1), new Selection(3, 1, 3, 1));
548
editor.trigger('test', 'type', { text: 'foo: ' });
549
ctrl.insert('number;');
550
ctrl.next();
551
assert.strictEqual(model.getValue(), `foo: number;\n\nfoo: 'number',`);
552
// editor.trigger('test', 'type', { text: ';' });
553
});
554
555
suite('createEditsAndSnippetsFromEdits', function () {
556
557
test('apply, tab, done', function () {
558
559
ctrl = instaService.createInstance(SnippetController2, editor);
560
561
model.setValue('foo("bar")');
562
563
ctrl.apply([
564
{ range: new Range(1, 5, 1, 10), template: '$1' },
565
{ range: new Range(1, 1, 1, 1), template: 'const ${1:new_const} = "bar";\n' }
566
]);
567
568
assert.strictEqual(model.getValue(), "const new_const = \"bar\";\nfoo(new_const)");
569
assertContextKeys(contextKeys, true, false, true);
570
assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 7, 1, 16), new Selection(2, 5, 2, 14)]);
571
572
ctrl.next();
573
assertContextKeys(contextKeys, false, false, false);
574
assert.deepStrictEqual(editor.getSelections(), [new Selection(2, 14, 2, 14)]);
575
});
576
577
test('apply, tab, done with special final tabstop', function () {
578
579
model.setValue('foo("bar")');
580
581
ctrl = instaService.createInstance(SnippetController2, editor);
582
ctrl.apply([
583
{ range: new Range(1, 5, 1, 10), template: '$1' },
584
{ range: new Range(1, 1, 1, 1), template: 'const ${1:new_const}$0 = "bar";\n' }
585
]);
586
587
assert.strictEqual(model.getValue(), "const new_const = \"bar\";\nfoo(new_const)");
588
assertContextKeys(contextKeys, true, false, true);
589
assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 7, 1, 16), new Selection(2, 5, 2, 14)]);
590
591
ctrl.next();
592
assertContextKeys(contextKeys, false, false, false);
593
assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 16, 1, 16)]);
594
});
595
596
test('apply, tab, tab, done', function () {
597
598
model.setValue('foo\nbar');
599
600
ctrl = instaService.createInstance(SnippetController2, editor);
601
ctrl.apply([
602
{ range: new Range(1, 4, 1, 4), template: '${3}' },
603
{ range: new Range(2, 4, 2, 4), template: '$3' },
604
{ range: new Range(1, 1, 1, 1), template: '### ${2:Header}\n' }
605
]);
606
607
assert.strictEqual(model.getValue(), "### Header\nfoo\nbar");
608
assert.deepStrictEqual(getContextState(), { inSnippet: true, hasPrev: false, hasNext: true });
609
assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 5, 1, 11)]);
610
611
ctrl.next();
612
assert.deepStrictEqual(getContextState(), { inSnippet: true, hasPrev: true, hasNext: true });
613
assert.deepStrictEqual(editor.getSelections(), [new Selection(2, 4, 2, 4), new Selection(3, 4, 3, 4)]);
614
615
ctrl.next();
616
assert.deepStrictEqual(getContextState(), { inSnippet: false, hasPrev: false, hasNext: false });
617
assert.deepStrictEqual(editor.getSelections(), [new Selection(3, 4, 3, 4)]);
618
});
619
620
test('nested into apply works', function () {
621
622
ctrl = instaService.createInstance(SnippetController2, editor);
623
model.setValue('onetwo');
624
625
editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 1, 2, 1)]);
626
627
ctrl.apply([{
628
range: new Range(1, 7, 1, 7),
629
template: '$0${1:three}'
630
}]);
631
632
assert.strictEqual(model.getValue(), 'onetwothree');
633
assert.deepStrictEqual(getContextState(), { inSnippet: true, hasPrev: false, hasNext: true });
634
assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 7, 1, 12)]);
635
636
ctrl.insert('foo$1bar$1');
637
assert.strictEqual(model.getValue(), 'onetwofoobar');
638
assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 10, 1, 10), new Selection(1, 13, 1, 13)]);
639
assert.deepStrictEqual(getContextState(), ({ inSnippet: true, hasPrev: false, hasNext: true }));
640
641
ctrl.next();
642
assert.deepStrictEqual(getContextState(), ({ inSnippet: true, hasPrev: true, hasNext: true }));
643
assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 13, 1, 13)]);
644
645
ctrl.next();
646
assert.deepStrictEqual(getContextState(), { inSnippet: false, hasPrev: false, hasNext: false });
647
assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 7, 1, 7)]);
648
649
});
650
651
test('nested into insert abort "outer" snippet', function () {
652
653
ctrl = instaService.createInstance(SnippetController2, editor);
654
model.setValue('one\ntwo');
655
656
editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 1, 2, 1)]);
657
658
ctrl.insert('foo${1:bar}bazz${1:bang}');
659
assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 4, 1, 7), new Selection(1, 11, 1, 14), new Selection(2, 4, 2, 7), new Selection(2, 11, 2, 14)]);
660
assert.deepStrictEqual(getContextState(), { inSnippet: true, hasPrev: false, hasNext: true });
661
662
ctrl.apply([{
663
range: new Range(1, 4, 1, 7),
664
template: '$0A'
665
}]);
666
667
assert.strictEqual(model.getValue(), 'fooAbazzbarone\nfoobarbazzbartwo');
668
assert.deepStrictEqual(getContextState(), { inSnippet: false, hasPrev: false, hasNext: false });
669
assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 4, 1, 4)]);
670
});
671
672
test('nested into "insert" abort "outer" snippet (2)', function () {
673
674
ctrl = instaService.createInstance(SnippetController2, editor);
675
model.setValue('one\ntwo');
676
677
editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 1, 2, 1)]);
678
679
ctrl.insert('foo${1:bar}bazz${1:bang}');
680
assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 4, 1, 7), new Selection(1, 11, 1, 14), new Selection(2, 4, 2, 7), new Selection(2, 11, 2, 14)]);
681
assert.deepStrictEqual(getContextState(), { inSnippet: true, hasPrev: false, hasNext: true });
682
683
const edits = [{
684
range: new Range(1, 4, 1, 7),
685
template: 'A'
686
}, {
687
range: new Range(1, 11, 1, 14),
688
template: 'B'
689
}, {
690
range: new Range(2, 4, 2, 7),
691
template: 'C'
692
}, {
693
range: new Range(2, 11, 2, 14),
694
template: 'D'
695
}];
696
ctrl.apply(edits);
697
698
assert.strictEqual(model.getValue(), "fooAbazzBone\nfooCbazzDtwo");
699
assert.deepStrictEqual(getContextState(), { inSnippet: false, hasPrev: false, hasNext: false });
700
assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 5, 1, 5), new Selection(1, 10, 1, 10), new Selection(2, 5, 2, 5), new Selection(2, 10, 2, 10)]);
701
});
702
});
703
704
test('Bug: cursor position $0 with user snippets #163808', function () {
705
706
ctrl = instaService.createInstance(SnippetController2, editor);
707
model.setValue('');
708
709
ctrl.insert('<Element1 Attr1="foo" $1>\n <Element2 Attr1="$2"/>\n$0"\n</Element1>');
710
assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 23, 1, 23)]);
711
712
ctrl.insert('Qualifier="$0"');
713
assert.strictEqual(model.getValue(), '<Element1 Attr1="foo" Qualifier="">\n <Element2 Attr1=""/>\n"\n</Element1>');
714
assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 34, 1, 34)]);
715
716
});
717
718
test('EOL-Sequence (CRLF) shifts tab stop in isFileTemplate snippets #167386', function () {
719
ctrl = instaService.createInstance(SnippetController2, editor);
720
model.setValue('');
721
model.setEOL(EndOfLineSequence.CRLF);
722
723
ctrl.apply([{
724
range: model.getFullModelRange(),
725
template: 'line 54321${1:FOO}\nline 54321${1:FOO}\n(no tab stop)\nline 54321${1:FOO}\nline 54321'
726
}]);
727
728
assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 11, 1, 14), new Selection(2, 11, 2, 14), new Selection(4, 11, 4, 14)]);
729
730
});
731
732
test('"Surround With" code action snippets use incorrect indentation levels and styles #169319', function () {
733
model.setValue('function foo(f, x, condition) {\n f();\n return x;\n}');
734
const sel = new Range(2, 5, 3, 14);
735
editor.setSelection(sel);
736
ctrl = instaService.createInstance(SnippetController2, editor);
737
ctrl.apply([{
738
range: sel,
739
template: 'if (${1:condition}) {\n\t$TM_SELECTED_TEXT$0\n}'
740
}]);
741
742
assert.strictEqual(model.getValue(), `function foo(f, x, condition) {\n if (condition) {\n f();\n return x;\n }\n}`);
743
});
744
});
745
746