Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/suggest/test/browser/suggestController.test.ts
4798 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
6
import assert from 'assert';
7
import { timeout } from '../../../../../base/common/async.js';
8
import { Event } from '../../../../../base/common/event.js';
9
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
10
import { URI } from '../../../../../base/common/uri.js';
11
import { mock } from '../../../../../base/test/common/mock.js';
12
import { Range } from '../../../../common/core/range.js';
13
import { Selection } from '../../../../common/core/selection.js';
14
import { TextModel } from '../../../../common/model/textModel.js';
15
import { CompletionItemInsertTextRule, CompletionItemKind } from '../../../../common/languages.js';
16
import { IEditorWorkerService } from '../../../../common/services/editorWorker.js';
17
import { SnippetController2 } from '../../../snippet/browser/snippetController2.js';
18
import { SuggestController } from '../../browser/suggestController.js';
19
import { ISuggestMemoryService } from '../../browser/suggestMemory.js';
20
import { createTestCodeEditor, ITestCodeEditor } from '../../../../test/browser/testCodeEditor.js';
21
import { createTextModel } from '../../../../test/common/testTextModel.js';
22
import { IMenu, IMenuService } from '../../../../../platform/actions/common/actions.js';
23
import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js';
24
import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js';
25
import { MockKeybindingService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js';
26
import { ILabelService } from '../../../../../platform/label/common/label.js';
27
import { ILogService, NullLogService } from '../../../../../platform/log/common/log.js';
28
import { InMemoryStorageService, IStorageService } from '../../../../../platform/storage/common/storage.js';
29
import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';
30
import { NullTelemetryService } from '../../../../../platform/telemetry/common/telemetryUtils.js';
31
import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js';
32
import { LanguageFeaturesService } from '../../../../common/services/languageFeaturesService.js';
33
import { ILanguageFeaturesService } from '../../../../common/services/languageFeatures.js';
34
import { IEnvironmentService } from '../../../../../platform/environment/common/environment.js';
35
import { DeleteLinesAction } from '../../../linesOperations/browser/linesOperations.js';
36
37
suite('SuggestController', function () {
38
39
const disposables = new DisposableStore();
40
41
let controller: SuggestController;
42
let editor: ITestCodeEditor;
43
let model: TextModel;
44
const languageFeaturesService = new LanguageFeaturesService();
45
46
teardown(function () {
47
48
disposables.clear();
49
});
50
51
// ensureNoDisposablesAreLeakedInTestSuite();
52
53
setup(function () {
54
55
const serviceCollection = new ServiceCollection(
56
[ILanguageFeaturesService, languageFeaturesService],
57
[ITelemetryService, NullTelemetryService],
58
[ILogService, new NullLogService()],
59
[IStorageService, disposables.add(new InMemoryStorageService())],
60
[IKeybindingService, new MockKeybindingService()],
61
[IEditorWorkerService, new class extends mock<IEditorWorkerService>() {
62
override computeWordRanges() {
63
return Promise.resolve({});
64
}
65
}],
66
[ISuggestMemoryService, new class extends mock<ISuggestMemoryService>() {
67
override memorize(): void { }
68
override select(): number { return 0; }
69
}],
70
[IMenuService, new class extends mock<IMenuService>() {
71
override createMenu() {
72
return new class extends mock<IMenu>() {
73
override onDidChange = Event.None;
74
override dispose() { }
75
};
76
}
77
}],
78
[ILabelService, new class extends mock<ILabelService>() { }],
79
[IWorkspaceContextService, new class extends mock<IWorkspaceContextService>() { }],
80
[IEnvironmentService, new class extends mock<IEnvironmentService>() {
81
override isBuilt: boolean = true;
82
override isExtensionDevelopment: boolean = false;
83
}],
84
);
85
86
model = disposables.add(createTextModel('', undefined, undefined, URI.from({ scheme: 'test-ctrl', path: '/path.tst' })));
87
editor = disposables.add(createTestCodeEditor(model, { serviceCollection }));
88
89
editor.registerAndInstantiateContribution(SnippetController2.ID, SnippetController2);
90
controller = editor.registerAndInstantiateContribution(SuggestController.ID, SuggestController);
91
});
92
93
test('postfix completion reports incorrect position #86984', async function () {
94
disposables.add(languageFeaturesService.completionProvider.register({ scheme: 'test-ctrl' }, {
95
_debugDisplayName: 'test',
96
provideCompletionItems(doc, pos) {
97
return {
98
suggestions: [{
99
kind: CompletionItemKind.Snippet,
100
label: 'let',
101
insertText: 'let ${1:name} = foo$0',
102
insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet,
103
range: { startLineNumber: 1, startColumn: 9, endLineNumber: 1, endColumn: 11 },
104
additionalTextEdits: [{
105
text: '',
106
range: { startLineNumber: 1, startColumn: 5, endLineNumber: 1, endColumn: 9 }
107
}]
108
}]
109
};
110
}
111
}));
112
113
editor.setValue(' foo.le');
114
editor.setSelection(new Selection(1, 11, 1, 11));
115
116
// trigger
117
const p1 = Event.toPromise(controller.model.onDidSuggest);
118
controller.triggerSuggest();
119
await p1;
120
121
//
122
const p2 = Event.toPromise(controller.model.onDidCancel);
123
controller.acceptSelectedSuggestion(false, false);
124
await p2;
125
126
assert.strictEqual(editor.getValue(), ' let name = foo');
127
});
128
129
test('use additionalTextEdits sync when possible', async function () {
130
131
disposables.add(languageFeaturesService.completionProvider.register({ scheme: 'test-ctrl' }, {
132
_debugDisplayName: 'test',
133
provideCompletionItems(doc, pos) {
134
return {
135
suggestions: [{
136
kind: CompletionItemKind.Snippet,
137
label: 'let',
138
insertText: 'hello',
139
range: Range.fromPositions(pos),
140
additionalTextEdits: [{
141
text: 'I came sync',
142
range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }
143
}]
144
}]
145
};
146
},
147
async resolveCompletionItem(item) {
148
return item;
149
}
150
}));
151
152
editor.setValue('hello\nhallo');
153
editor.setSelection(new Selection(2, 6, 2, 6));
154
155
// trigger
156
const p1 = Event.toPromise(controller.model.onDidSuggest);
157
controller.triggerSuggest();
158
await p1;
159
160
//
161
const p2 = Event.toPromise(controller.model.onDidCancel);
162
controller.acceptSelectedSuggestion(false, false);
163
await p2;
164
165
// insertText happens sync!
166
assert.strictEqual(editor.getValue(), 'I came synchello\nhallohello');
167
});
168
169
test('resolve additionalTextEdits async when needed', async function () {
170
171
let resolveCallCount = 0;
172
173
disposables.add(languageFeaturesService.completionProvider.register({ scheme: 'test-ctrl' }, {
174
_debugDisplayName: 'test',
175
provideCompletionItems(doc, pos) {
176
return {
177
suggestions: [{
178
kind: CompletionItemKind.Snippet,
179
label: 'let',
180
insertText: 'hello',
181
range: Range.fromPositions(pos)
182
}]
183
};
184
},
185
async resolveCompletionItem(item) {
186
resolveCallCount += 1;
187
await timeout(10);
188
item.additionalTextEdits = [{
189
text: 'I came late',
190
range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }
191
}];
192
return item;
193
}
194
}));
195
196
editor.setValue('hello\nhallo');
197
editor.setSelection(new Selection(2, 6, 2, 6));
198
199
// trigger
200
const p1 = Event.toPromise(controller.model.onDidSuggest);
201
controller.triggerSuggest();
202
await p1;
203
204
//
205
const p2 = Event.toPromise(controller.model.onDidCancel);
206
controller.acceptSelectedSuggestion(false, false);
207
await p2;
208
209
// insertText happens sync!
210
assert.strictEqual(editor.getValue(), 'hello\nhallohello');
211
assert.strictEqual(resolveCallCount, 1);
212
213
// additional edits happened after a litte wait
214
await timeout(20);
215
assert.strictEqual(editor.getValue(), 'I came latehello\nhallohello');
216
217
// single undo stop
218
editor.getModel()?.undo();
219
assert.strictEqual(editor.getValue(), 'hello\nhallo');
220
});
221
222
test('resolve additionalTextEdits async when needed (typing)', async function () {
223
224
let resolveCallCount = 0;
225
let resolve: Function = () => { };
226
disposables.add(languageFeaturesService.completionProvider.register({ scheme: 'test-ctrl' }, {
227
_debugDisplayName: 'test',
228
provideCompletionItems(doc, pos) {
229
return {
230
suggestions: [{
231
kind: CompletionItemKind.Snippet,
232
label: 'let',
233
insertText: 'hello',
234
range: Range.fromPositions(pos)
235
}]
236
};
237
},
238
async resolveCompletionItem(item) {
239
resolveCallCount += 1;
240
await new Promise(_resolve => resolve = _resolve);
241
item.additionalTextEdits = [{
242
text: 'I came late',
243
range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }
244
}];
245
return item;
246
}
247
}));
248
249
editor.setValue('hello\nhallo');
250
editor.setSelection(new Selection(2, 6, 2, 6));
251
252
// trigger
253
const p1 = Event.toPromise(controller.model.onDidSuggest);
254
controller.triggerSuggest();
255
await p1;
256
257
//
258
const p2 = Event.toPromise(controller.model.onDidCancel);
259
controller.acceptSelectedSuggestion(false, false);
260
await p2;
261
262
// insertText happens sync!
263
assert.strictEqual(editor.getValue(), 'hello\nhallohello');
264
assert.strictEqual(resolveCallCount, 1);
265
266
// additional edits happened after a litte wait
267
assert.ok(editor.getSelection()?.equalsSelection(new Selection(2, 11, 2, 11)));
268
editor.trigger('test', 'type', { text: 'TYPING' });
269
270
assert.strictEqual(editor.getValue(), 'hello\nhallohelloTYPING');
271
272
resolve();
273
await timeout(10);
274
assert.strictEqual(editor.getValue(), 'I came latehello\nhallohelloTYPING');
275
assert.ok(editor.getSelection()?.equalsSelection(new Selection(2, 17, 2, 17)));
276
});
277
278
// additional edit come late and are AFTER the selection -> cancel
279
test('resolve additionalTextEdits async when needed (simple conflict)', async function () {
280
281
let resolveCallCount = 0;
282
let resolve: Function = () => { };
283
disposables.add(languageFeaturesService.completionProvider.register({ scheme: 'test-ctrl' }, {
284
_debugDisplayName: 'test',
285
provideCompletionItems(doc, pos) {
286
return {
287
suggestions: [{
288
kind: CompletionItemKind.Snippet,
289
label: 'let',
290
insertText: 'hello',
291
range: Range.fromPositions(pos)
292
}]
293
};
294
},
295
async resolveCompletionItem(item) {
296
resolveCallCount += 1;
297
await new Promise(_resolve => resolve = _resolve);
298
item.additionalTextEdits = [{
299
text: 'I came late',
300
range: { startLineNumber: 1, startColumn: 6, endLineNumber: 1, endColumn: 6 }
301
}];
302
return item;
303
}
304
}));
305
306
editor.setValue('');
307
editor.setSelection(new Selection(1, 1, 1, 1));
308
309
// trigger
310
const p1 = Event.toPromise(controller.model.onDidSuggest);
311
controller.triggerSuggest();
312
await p1;
313
314
//
315
const p2 = Event.toPromise(controller.model.onDidCancel);
316
controller.acceptSelectedSuggestion(false, false);
317
await p2;
318
319
// insertText happens sync!
320
assert.strictEqual(editor.getValue(), 'hello');
321
assert.strictEqual(resolveCallCount, 1);
322
323
resolve();
324
await timeout(10);
325
assert.strictEqual(editor.getValue(), 'hello');
326
});
327
328
// additional edit come late and are AFTER the position at which the user typed -> cancelled
329
test('resolve additionalTextEdits async when needed (conflict)', async function () {
330
331
let resolveCallCount = 0;
332
let resolve: Function = () => { };
333
disposables.add(languageFeaturesService.completionProvider.register({ scheme: 'test-ctrl' }, {
334
_debugDisplayName: 'test',
335
provideCompletionItems(doc, pos) {
336
return {
337
suggestions: [{
338
kind: CompletionItemKind.Snippet,
339
label: 'let',
340
insertText: 'hello',
341
range: Range.fromPositions(pos)
342
}]
343
};
344
},
345
async resolveCompletionItem(item) {
346
resolveCallCount += 1;
347
await new Promise(_resolve => resolve = _resolve);
348
item.additionalTextEdits = [{
349
text: 'I came late',
350
range: { startLineNumber: 1, startColumn: 2, endLineNumber: 1, endColumn: 2 }
351
}];
352
return item;
353
}
354
}));
355
356
editor.setValue('hello\nhallo');
357
editor.setSelection(new Selection(2, 6, 2, 6));
358
359
// trigger
360
const p1 = Event.toPromise(controller.model.onDidSuggest);
361
controller.triggerSuggest();
362
await p1;
363
364
//
365
const p2 = Event.toPromise(controller.model.onDidCancel);
366
controller.acceptSelectedSuggestion(false, false);
367
await p2;
368
369
// insertText happens sync!
370
assert.strictEqual(editor.getValue(), 'hello\nhallohello');
371
assert.strictEqual(resolveCallCount, 1);
372
373
// additional edits happened after a litte wait
374
editor.setSelection(new Selection(1, 1, 1, 1));
375
editor.trigger('test', 'type', { text: 'TYPING' });
376
377
assert.strictEqual(editor.getValue(), 'TYPINGhello\nhallohello');
378
379
resolve();
380
await timeout(10);
381
assert.strictEqual(editor.getValue(), 'TYPINGhello\nhallohello');
382
assert.ok(editor.getSelection()?.equalsSelection(new Selection(1, 7, 1, 7)));
383
});
384
385
test('resolve additionalTextEdits async when needed (cancel)', async function () {
386
387
const resolve: Function[] = [];
388
disposables.add(languageFeaturesService.completionProvider.register({ scheme: 'test-ctrl' }, {
389
_debugDisplayName: 'test',
390
provideCompletionItems(doc, pos) {
391
return {
392
suggestions: [{
393
kind: CompletionItemKind.Snippet,
394
label: 'let',
395
insertText: 'hello',
396
range: Range.fromPositions(pos)
397
}, {
398
kind: CompletionItemKind.Snippet,
399
label: 'let',
400
insertText: 'hallo',
401
range: Range.fromPositions(pos)
402
}]
403
};
404
},
405
async resolveCompletionItem(item) {
406
await new Promise(_resolve => resolve.push(_resolve));
407
item.additionalTextEdits = [{
408
text: 'additionalTextEdits',
409
range: { startLineNumber: 1, startColumn: 2, endLineNumber: 1, endColumn: 2 }
410
}];
411
return item;
412
}
413
}));
414
415
editor.setValue('abc');
416
editor.setSelection(new Selection(1, 1, 1, 1));
417
418
// trigger
419
const p1 = Event.toPromise(controller.model.onDidSuggest);
420
controller.triggerSuggest();
421
await p1;
422
423
//
424
const p2 = Event.toPromise(controller.model.onDidCancel);
425
controller.acceptSelectedSuggestion(true, false);
426
await p2;
427
428
// insertText happens sync!
429
assert.strictEqual(editor.getValue(), 'helloabc');
430
431
// next
432
controller.acceptNextSuggestion();
433
434
// resolve additional edits (MUST be cancelled)
435
resolve.forEach(fn => fn);
436
resolve.length = 0;
437
await timeout(10);
438
439
// next suggestion used
440
assert.strictEqual(editor.getValue(), 'halloabc');
441
});
442
443
test('Completion edits are applied inconsistently when additionalTextEdits and textEdit start at the same offset #143888', async function () {
444
445
446
disposables.add(languageFeaturesService.completionProvider.register({ scheme: 'test-ctrl' }, {
447
_debugDisplayName: 'test',
448
provideCompletionItems(doc, pos) {
449
return {
450
suggestions: [{
451
kind: CompletionItemKind.Text,
452
label: 'MyClassName',
453
insertText: 'MyClassName',
454
range: Range.fromPositions(pos),
455
additionalTextEdits: [{
456
range: Range.fromPositions(pos),
457
text: 'import "my_class.txt";\n'
458
}]
459
}]
460
};
461
}
462
}));
463
464
editor.setValue('');
465
editor.setSelection(new Selection(1, 1, 1, 1));
466
467
// trigger
468
const p1 = Event.toPromise(controller.model.onDidSuggest);
469
controller.triggerSuggest();
470
await p1;
471
472
//
473
const p2 = Event.toPromise(controller.model.onDidCancel);
474
controller.acceptSelectedSuggestion(true, false);
475
await p2;
476
477
// insertText happens sync!
478
assert.strictEqual(editor.getValue(), 'import "my_class.txt";\nMyClassName');
479
480
});
481
482
test('Pressing enter on autocomplete should always apply the selected dropdown completion, not a different, hidden one #161883', async function () {
483
disposables.add(languageFeaturesService.completionProvider.register({ scheme: 'test-ctrl' }, {
484
_debugDisplayName: 'test',
485
provideCompletionItems(doc, pos) {
486
487
const word = doc.getWordUntilPosition(pos);
488
const range = new Range(pos.lineNumber, word.startColumn, pos.lineNumber, word.endColumn);
489
490
return {
491
suggestions: [{
492
kind: CompletionItemKind.Text,
493
label: 'filterBankSize',
494
insertText: 'filterBankSize',
495
sortText: 'a',
496
range
497
}, {
498
kind: CompletionItemKind.Text,
499
label: 'filter',
500
insertText: 'filter',
501
sortText: 'b',
502
range
503
}]
504
};
505
}
506
}));
507
508
editor.setValue('filte');
509
editor.setSelection(new Selection(1, 6, 1, 6));
510
511
const p1 = Event.toPromise(controller.model.onDidSuggest);
512
controller.triggerSuggest();
513
514
const { completionModel } = await p1;
515
assert.strictEqual(completionModel.items.length, 2);
516
517
const [first, second] = completionModel.items;
518
assert.strictEqual(first.textLabel, 'filterBankSize');
519
assert.strictEqual(second.textLabel, 'filter');
520
521
assert.deepStrictEqual(editor.getSelection(), new Selection(1, 6, 1, 6));
522
editor.trigger('keyboard', 'type', { text: 'r' }); // now filter "overtakes" filterBankSize because it is fully matched
523
assert.deepStrictEqual(editor.getSelection(), new Selection(1, 7, 1, 7));
524
525
controller.acceptSelectedSuggestion(false, false);
526
assert.strictEqual(editor.getValue(), 'filter');
527
});
528
529
test('Fast autocomple typing selects the previous autocomplete suggestion, #71795', async function () {
530
disposables.add(languageFeaturesService.completionProvider.register({ scheme: 'test-ctrl' }, {
531
_debugDisplayName: 'test',
532
provideCompletionItems(doc, pos) {
533
534
const word = doc.getWordUntilPosition(pos);
535
const range = new Range(pos.lineNumber, word.startColumn, pos.lineNumber, word.endColumn);
536
537
return {
538
suggestions: [{
539
kind: CompletionItemKind.Text,
540
label: 'false',
541
insertText: 'false',
542
range
543
}, {
544
kind: CompletionItemKind.Text,
545
label: 'float',
546
insertText: 'float',
547
range
548
}, {
549
kind: CompletionItemKind.Text,
550
label: 'for',
551
insertText: 'for',
552
range
553
}, {
554
kind: CompletionItemKind.Text,
555
label: 'foreach',
556
insertText: 'foreach',
557
range
558
}]
559
};
560
}
561
}));
562
563
editor.setValue('f');
564
editor.setSelection(new Selection(1, 2, 1, 2));
565
566
const p1 = Event.toPromise(controller.model.onDidSuggest);
567
controller.triggerSuggest();
568
569
const { completionModel } = await p1;
570
assert.strictEqual(completionModel.items.length, 4);
571
572
const [first, second, third, fourth] = completionModel.items;
573
assert.strictEqual(first.textLabel, 'false');
574
assert.strictEqual(second.textLabel, 'float');
575
assert.strictEqual(third.textLabel, 'for');
576
assert.strictEqual(fourth.textLabel, 'foreach');
577
578
assert.deepStrictEqual(editor.getSelection(), new Selection(1, 2, 1, 2));
579
editor.trigger('keyboard', 'type', { text: 'o' }); // filters`false` and `float`
580
assert.deepStrictEqual(editor.getSelection(), new Selection(1, 3, 1, 3));
581
582
controller.acceptSelectedSuggestion(false, false);
583
assert.strictEqual(editor.getValue(), 'for');
584
});
585
586
test.skip('Suggest widget gets orphaned in editor #187779', async function () {
587
588
disposables.add(languageFeaturesService.completionProvider.register({ scheme: 'test-ctrl' }, {
589
_debugDisplayName: 'test',
590
provideCompletionItems(doc, pos) {
591
592
const word = doc.getLineContent(pos.lineNumber);
593
const range = new Range(pos.lineNumber, 1, pos.lineNumber, pos.column);
594
595
return {
596
suggestions: [{
597
kind: CompletionItemKind.Text,
598
label: word,
599
insertText: word,
600
range
601
}]
602
};
603
}
604
}));
605
606
editor.setValue(`console.log(example.)\nconsole.log(EXAMPLE.not)`);
607
editor.setSelection(new Selection(1, 21, 1, 21));
608
609
const p1 = Event.toPromise(controller.model.onDidSuggest);
610
controller.triggerSuggest();
611
612
await p1;
613
614
const p2 = Event.toPromise(controller.model.onDidCancel);
615
new DeleteLinesAction().run(null!, editor);
616
617
await p2;
618
});
619
620
test('Ranges where additionalTextEdits are applied are not appropriate when characters are typed #177591', async function () {
621
disposables.add(languageFeaturesService.completionProvider.register({ scheme: 'test-ctrl' }, {
622
_debugDisplayName: 'test',
623
provideCompletionItems(doc, pos) {
624
return {
625
suggestions: [{
626
kind: CompletionItemKind.Snippet,
627
label: 'aaa',
628
insertText: 'aaa',
629
range: Range.fromPositions(pos),
630
additionalTextEdits: [{
631
range: Range.fromPositions(pos.delta(0, 10)),
632
text: 'aaa'
633
}]
634
}]
635
};
636
}
637
}));
638
639
{ // PART1 - no typing
640
editor.setValue(`123456789123456789`);
641
editor.setSelection(new Selection(1, 1, 1, 1));
642
const p1 = Event.toPromise(controller.model.onDidSuggest);
643
controller.triggerSuggest();
644
645
const e = await p1;
646
assert.strictEqual(e.completionModel.items.length, 1);
647
assert.strictEqual(e.completionModel.items[0].textLabel, 'aaa');
648
649
controller.acceptSelectedSuggestion(false, false);
650
651
assert.strictEqual(editor.getValue(), 'aaa1234567891aaa23456789');
652
}
653
654
{ // PART2 - typing
655
editor.setValue(`123456789123456789`);
656
editor.setSelection(new Selection(1, 1, 1, 1));
657
const p1 = Event.toPromise(controller.model.onDidSuggest);
658
controller.triggerSuggest();
659
660
const e = await p1;
661
assert.strictEqual(e.completionModel.items.length, 1);
662
assert.strictEqual(e.completionModel.items[0].textLabel, 'aaa');
663
664
editor.trigger('keyboard', 'type', { text: 'aa' });
665
666
controller.acceptSelectedSuggestion(false, false);
667
668
assert.strictEqual(editor.getValue(), 'aaa1234567891aaa23456789');
669
}
670
});
671
672
test.skip('[Bug] "No suggestions" persists while typing if the completion helper is set to return an empty list for empty content#3557', async function () {
673
let requestCount = 0;
674
675
disposables.add(languageFeaturesService.completionProvider.register({ scheme: 'test-ctrl' }, {
676
_debugDisplayName: 'test',
677
provideCompletionItems(doc, pos) {
678
requestCount += 1;
679
680
if (requestCount === 1) {
681
return undefined;
682
}
683
684
return {
685
suggestions: [{
686
kind: CompletionItemKind.Text,
687
label: 'foo',
688
insertText: 'foo',
689
range: new Range(pos.lineNumber, 1, pos.lineNumber, pos.column)
690
}],
691
};
692
}
693
}));
694
695
const p1 = Event.toPromise(controller.model.onDidSuggest);
696
controller.triggerSuggest();
697
698
const e1 = await p1;
699
assert.strictEqual(e1.completionModel.items.length, 0);
700
assert.strictEqual(requestCount, 1);
701
702
const p2 = Event.toPromise(controller.model.onDidSuggest);
703
editor.trigger('keyboard', 'type', { text: 'f' });
704
705
const e2 = await p2;
706
assert.strictEqual(e2.completionModel.items.length, 1);
707
assert.strictEqual(requestCount, 2);
708
709
});
710
});
711
712