Path: blob/main/src/vs/editor/contrib/suggest/test/browser/suggestModel.test.ts
4798 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*--------------------------------------------------------------------------------------------*/4import assert from 'assert';5import { Event } from '../../../../../base/common/event.js';6import { Disposable, DisposableStore, toDisposable } from '../../../../../base/common/lifecycle.js';7import { URI } from '../../../../../base/common/uri.js';8import { mock } from '../../../../../base/test/common/mock.js';9import { CoreEditingCommands } from '../../../../browser/coreCommands.js';10import { EditOperation } from '../../../../common/core/editOperation.js';11import { Position } from '../../../../common/core/position.js';12import { Range } from '../../../../common/core/range.js';13import { Selection } from '../../../../common/core/selection.js';14import { Handler } from '../../../../common/editorCommon.js';15import { ITextModel } from '../../../../common/model.js';16import { TextModel } from '../../../../common/model/textModel.js';17import { CompletionItemKind, CompletionItemProvider, CompletionList, CompletionTriggerKind, EncodedTokenizationResult, IState, TokenizationRegistry } from '../../../../common/languages.js';18import { MetadataConsts } from '../../../../common/encodedTokenAttributes.js';19import { ILanguageConfigurationService } from '../../../../common/languages/languageConfigurationRegistry.js';20import { NullState } from '../../../../common/languages/nullTokenize.js';21import { ILanguageService } from '../../../../common/languages/language.js';22import { SnippetController2 } from '../../../snippet/browser/snippetController2.js';23import { SuggestController } from '../../browser/suggestController.js';24import { ISuggestMemoryService } from '../../browser/suggestMemory.js';25import { LineContext, SuggestModel } from '../../browser/suggestModel.js';26import { ISelectedSuggestion } from '../../browser/suggestWidget.js';27import { createTestCodeEditor, ITestCodeEditor } from '../../../../test/browser/testCodeEditor.js';28import { createModelServices, createTextModel, instantiateTextModel } from '../../../../test/common/testTextModel.js';29import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js';30import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js';31import { MockKeybindingService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js';32import { ILabelService } from '../../../../../platform/label/common/label.js';33import { InMemoryStorageService, IStorageService } from '../../../../../platform/storage/common/storage.js';34import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';35import { NullTelemetryService } from '../../../../../platform/telemetry/common/telemetryUtils.js';36import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js';37import { LanguageFeaturesService } from '../../../../common/services/languageFeaturesService.js';38import { ILanguageFeaturesService } from '../../../../common/services/languageFeatures.js';39import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';40import { getSnippetSuggestSupport, setSnippetSuggestSupport } from '../../browser/suggest.js';41import { IEnvironmentService } from '../../../../../platform/environment/common/environment.js';42import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';434445function createMockEditor(model: TextModel, languageFeaturesService: ILanguageFeaturesService): ITestCodeEditor {4647const storeService = new InMemoryStorageService();48const editor = createTestCodeEditor(model, {49serviceCollection: new ServiceCollection(50[ILanguageFeaturesService, languageFeaturesService],51[ITelemetryService, NullTelemetryService],52[IStorageService, storeService],53[IKeybindingService, new MockKeybindingService()],54[ISuggestMemoryService, new class implements ISuggestMemoryService {55declare readonly _serviceBrand: undefined;56memorize(): void {57}58select(): number {59return -1;60}61}],62[ILabelService, new class extends mock<ILabelService>() { }],63[IWorkspaceContextService, new class extends mock<IWorkspaceContextService>() { }],64[IEnvironmentService, new class extends mock<IEnvironmentService>() {65override isBuilt: boolean = true;66override isExtensionDevelopment: boolean = false;67}],68),69});70const ctrl = editor.registerAndInstantiateContribution(SnippetController2.ID, SnippetController2);71editor.hasWidgetFocus = () => true;7273editor.registerDisposable(ctrl);74editor.registerDisposable(storeService);75return editor;76}7778suite('SuggestModel - Context', function () {79const OUTER_LANGUAGE_ID = 'outerMode';80const INNER_LANGUAGE_ID = 'innerMode';8182class OuterMode extends Disposable {83public readonly languageId = OUTER_LANGUAGE_ID;84constructor(85@ILanguageService languageService: ILanguageService,86@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService,87) {88super();89this._register(languageService.registerLanguage({ id: this.languageId }));90this._register(languageConfigurationService.register(this.languageId, {}));9192this._register(TokenizationRegistry.register(this.languageId, {93getInitialState: (): IState => NullState,94tokenize: undefined!,95tokenizeEncoded: (line: string, hasEOL: boolean, state: IState): EncodedTokenizationResult => {96const tokensArr: number[] = [];97let prevLanguageId: string | undefined = undefined;98for (let i = 0; i < line.length; i++) {99const languageId = (line.charAt(i) === 'x' ? INNER_LANGUAGE_ID : OUTER_LANGUAGE_ID);100const encodedLanguageId = languageService.languageIdCodec.encodeLanguageId(languageId);101if (prevLanguageId !== languageId) {102tokensArr.push(i);103tokensArr.push((encodedLanguageId << MetadataConsts.LANGUAGEID_OFFSET));104}105prevLanguageId = languageId;106}107108const tokens = new Uint32Array(tokensArr.length);109for (let i = 0; i < tokens.length; i++) {110tokens[i] = tokensArr[i];111}112return new EncodedTokenizationResult(tokens, [], state);113}114}));115}116}117118class InnerMode extends Disposable {119public readonly languageId = INNER_LANGUAGE_ID;120constructor(121@ILanguageService languageService: ILanguageService,122@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService123) {124super();125this._register(languageService.registerLanguage({ id: this.languageId }));126this._register(languageConfigurationService.register(this.languageId, {}));127}128}129130const assertAutoTrigger = (model: TextModel, offset: number, expected: boolean, message?: string): void => {131const pos = model.getPositionAt(offset);132const editor = createMockEditor(model, new LanguageFeaturesService());133editor.setPosition(pos);134assert.strictEqual(LineContext.shouldAutoTrigger(editor), expected, message);135editor.dispose();136};137138let disposables: DisposableStore;139140setup(() => {141disposables = new DisposableStore();142});143144teardown(function () {145disposables.dispose();146});147148ensureNoDisposablesAreLeakedInTestSuite();149150test('Context - shouldAutoTrigger', function () {151const model = createTextModel('Das Pferd frisst keinen Gurkensalat - Philipp Reis 1861.\nWer hat\'s erfunden?');152disposables.add(model);153154assertAutoTrigger(model, 3, true, 'end of word, Das|');155assertAutoTrigger(model, 4, false, 'no word Das |');156assertAutoTrigger(model, 1, true, 'typing a single character before a word: D|as');157assertAutoTrigger(model, 55, false, 'number, 1861|');158model.dispose();159});160161test('shouldAutoTrigger at embedded language boundaries', () => {162const disposables = new DisposableStore();163const instantiationService = createModelServices(disposables);164const outerMode = disposables.add(instantiationService.createInstance(OuterMode));165disposables.add(instantiationService.createInstance(InnerMode));166167const model = disposables.add(instantiateTextModel(instantiationService, 'a<xx>a<x>', outerMode.languageId));168169assertAutoTrigger(model, 1, true, 'a|<x — should trigger at end of word');170assertAutoTrigger(model, 2, false, 'a<|x — should NOT trigger at start of word');171assertAutoTrigger(model, 3, true, 'a<x|x — should trigger after typing a single character before a word');172assertAutoTrigger(model, 4, true, 'a<xx|> — should trigger at boundary between languages');173assertAutoTrigger(model, 5, false, 'a<xx>|a — should NOT trigger at start of word');174assertAutoTrigger(model, 6, true, 'a<xx>a|< — should trigger at end of word');175assertAutoTrigger(model, 8, true, 'a<xx>a<x|> — should trigger at end of word at boundary');176177disposables.dispose();178});179});180181suite('SuggestModel - TriggerAndCancelOracle', function () {182183184function getDefaultSuggestRange(model: ITextModel, position: Position) {185const wordUntil = model.getWordUntilPosition(position);186return new Range(position.lineNumber, wordUntil.startColumn, position.lineNumber, wordUntil.endColumn);187}188189const alwaysEmptySupport: CompletionItemProvider = {190_debugDisplayName: 'test',191provideCompletionItems(doc, pos): CompletionList {192return {193incomplete: false,194suggestions: []195};196}197};198199const alwaysSomethingSupport: CompletionItemProvider = {200_debugDisplayName: 'test',201provideCompletionItems(doc, pos): CompletionList {202return {203incomplete: false,204suggestions: [{205label: doc.getWordUntilPosition(pos).word,206kind: CompletionItemKind.Property,207insertText: 'foofoo',208range: getDefaultSuggestRange(doc, pos)209}]210};211}212};213214let disposables: DisposableStore;215let model: TextModel;216const languageFeaturesService = new LanguageFeaturesService();217const registry = languageFeaturesService.completionProvider;218219setup(function () {220disposables = new DisposableStore();221model = createTextModel('abc def', undefined, undefined, URI.parse('test:somefile.ttt'));222disposables.add(model);223});224225teardown(() => {226disposables.dispose();227});228229ensureNoDisposablesAreLeakedInTestSuite();230231function withOracle(callback: (model: SuggestModel, editor: ITestCodeEditor) => any): Promise<any> {232233return new Promise((resolve, reject) => {234const editor = createMockEditor(model, languageFeaturesService);235const oracle = editor.invokeWithinContext(accessor => accessor.get(IInstantiationService).createInstance(SuggestModel, editor));236disposables.add(oracle);237disposables.add(editor);238239try {240resolve(callback(oracle, editor));241} catch (err) {242reject(err);243}244});245}246247function assertEvent<E>(event: Event<E>, action: () => any, assert: (e: E) => any) {248return new Promise((resolve, reject) => {249const sub = event(e => {250sub.dispose();251try {252resolve(assert(e));253} catch (err) {254reject(err);255}256});257try {258action();259} catch (err) {260sub.dispose();261reject(err);262}263});264}265266test('events - cancel/trigger', function () {267return withOracle(model => {268269return Promise.all([270271assertEvent(model.onDidTrigger, function () {272model.trigger({ auto: true });273}, function (event) {274assert.strictEqual(event.auto, true);275276return assertEvent(model.onDidCancel, function () {277model.cancel();278}, function (event) {279assert.strictEqual(event.retrigger, false);280});281}),282283assertEvent(model.onDidTrigger, function () {284model.trigger({ auto: true });285}, function (event) {286assert.strictEqual(event.auto, true);287}),288289assertEvent(model.onDidTrigger, function () {290model.trigger({ auto: false });291}, function (event) {292assert.strictEqual(event.auto, false);293})294]);295});296});297298299test('events - suggest/empty', function () {300301disposables.add(registry.register({ scheme: 'test' }, alwaysEmptySupport));302303return withOracle(model => {304return Promise.all([305assertEvent(model.onDidCancel, function () {306model.trigger({ auto: true });307}, function (event) {308assert.strictEqual(event.retrigger, false);309}),310assertEvent(model.onDidSuggest, function () {311model.trigger({ auto: false });312}, function (event) {313assert.strictEqual(event.triggerOptions.auto, false);314assert.strictEqual(event.isFrozen, false);315assert.strictEqual(event.completionModel.items.length, 0);316})317]);318});319});320321test('trigger - on type', function () {322323disposables.add(registry.register({ scheme: 'test' }, alwaysSomethingSupport));324325return withOracle((model, editor) => {326return assertEvent(model.onDidSuggest, () => {327editor.setPosition({ lineNumber: 1, column: 4 });328editor.trigger('keyboard', Handler.Type, { text: 'd' });329330}, event => {331assert.strictEqual(event.triggerOptions.auto, true);332assert.strictEqual(event.completionModel.items.length, 1);333const [first] = event.completionModel.items;334335assert.strictEqual(first.provider, alwaysSomethingSupport);336});337});338});339340test('#17400: Keep filtering suggestModel.ts after space', function () {341342disposables.add(registry.register({ scheme: 'test' }, {343_debugDisplayName: 'test',344provideCompletionItems(doc, pos): CompletionList {345return {346incomplete: false,347suggestions: [{348label: 'My Table',349kind: CompletionItemKind.Property,350insertText: 'My Table',351range: getDefaultSuggestRange(doc, pos)352}]353};354}355}));356357model.setValue('');358359return withOracle((model, editor) => {360361return assertEvent(model.onDidSuggest, () => {362// make sure completionModel starts here!363model.trigger({ auto: true });364}, event => {365366return assertEvent(model.onDidSuggest, () => {367editor.setPosition({ lineNumber: 1, column: 1 });368editor.trigger('keyboard', Handler.Type, { text: 'My' });369370}, event => {371assert.strictEqual(event.triggerOptions.auto, true);372assert.strictEqual(event.completionModel.items.length, 1);373const [first] = event.completionModel.items;374assert.strictEqual(first.completion.label, 'My Table');375376return assertEvent(model.onDidSuggest, () => {377editor.setPosition({ lineNumber: 1, column: 3 });378editor.trigger('keyboard', Handler.Type, { text: ' ' });379380}, event => {381assert.strictEqual(event.triggerOptions.auto, true);382assert.strictEqual(event.completionModel.items.length, 1);383const [first] = event.completionModel.items;384assert.strictEqual(first.completion.label, 'My Table');385});386});387});388});389});390391test('#21484: Trigger character always force a new completion session', function () {392393disposables.add(registry.register({ scheme: 'test' }, {394_debugDisplayName: 'test',395provideCompletionItems(doc, pos): CompletionList {396return {397incomplete: false,398suggestions: [{399label: 'foo.bar',400kind: CompletionItemKind.Property,401insertText: 'foo.bar',402range: Range.fromPositions(pos.with(undefined, 1), pos)403}]404};405}406}));407408disposables.add(registry.register({ scheme: 'test' }, {409_debugDisplayName: 'test',410triggerCharacters: ['.'],411provideCompletionItems(doc, pos): CompletionList {412return {413incomplete: false,414suggestions: [{415label: 'boom',416kind: CompletionItemKind.Property,417insertText: 'boom',418range: Range.fromPositions(419pos.delta(0, doc.getLineContent(pos.lineNumber)[pos.column - 2] === '.' ? 0 : -1),420pos421)422}]423};424}425}));426427model.setValue('');428429return withOracle(async (model, editor) => {430431await assertEvent(model.onDidSuggest, () => {432editor.setPosition({ lineNumber: 1, column: 1 });433editor.trigger('keyboard', Handler.Type, { text: 'foo' });434435}, event => {436assert.strictEqual(event.triggerOptions.auto, true);437assert.strictEqual(event.completionModel.items.length, 1);438const [first] = event.completionModel.items;439assert.strictEqual(first.completion.label, 'foo.bar');440441});442443await assertEvent(model.onDidSuggest, () => {444editor.trigger('keyboard', Handler.Type, { text: '.' });445446}, event => {447// SYNC448assert.strictEqual(event.triggerOptions.auto, true);449assert.strictEqual(event.completionModel.items.length, 1);450const [first] = event.completionModel.items;451assert.strictEqual(first.completion.label, 'foo.bar');452});453454await assertEvent(model.onDidSuggest, () => {455// nothing -> triggered by the trigger character typing (see above)456457}, event => {458// ASYNC459assert.strictEqual(event.triggerOptions.auto, true);460assert.strictEqual(event.completionModel.items.length, 2);461const [first, second] = event.completionModel.items;462assert.strictEqual(first.completion.label, 'foo.bar');463assert.strictEqual(second.completion.label, 'boom');464});465});466});467468test('Intellisense Completion doesn\'t respect space after equal sign (.html file), #29353 [1/2]', function () {469470disposables.add(registry.register({ scheme: 'test' }, alwaysSomethingSupport));471472return withOracle((model, editor) => {473474editor.getModel()!.setValue('fo');475editor.setPosition({ lineNumber: 1, column: 3 });476477return assertEvent(model.onDidSuggest, () => {478model.trigger({ auto: false });479}, event => {480assert.strictEqual(event.triggerOptions.auto, false);481assert.strictEqual(event.isFrozen, false);482assert.strictEqual(event.completionModel.items.length, 1);483484return assertEvent(model.onDidCancel, () => {485editor.trigger('keyboard', Handler.Type, { text: '+' });486}, event => {487assert.strictEqual(event.retrigger, false);488});489});490});491});492493test('Intellisense Completion doesn\'t respect space after equal sign (.html file), #29353 [2/2]', function () {494495disposables.add(registry.register({ scheme: 'test' }, alwaysSomethingSupport));496497return withOracle((model, editor) => {498499editor.getModel()!.setValue('fo');500editor.setPosition({ lineNumber: 1, column: 3 });501502return assertEvent(model.onDidSuggest, () => {503model.trigger({ auto: false });504}, event => {505assert.strictEqual(event.triggerOptions.auto, false);506assert.strictEqual(event.isFrozen, false);507assert.strictEqual(event.completionModel.items.length, 1);508509return assertEvent(model.onDidCancel, () => {510editor.trigger('keyboard', Handler.Type, { text: ' ' });511}, event => {512assert.strictEqual(event.retrigger, false);513});514});515});516});517518test('Incomplete suggestion results cause re-triggering when typing w/o further context, #28400 (1/2)', function () {519520disposables.add(registry.register({ scheme: 'test' }, {521_debugDisplayName: 'test',522provideCompletionItems(doc, pos): CompletionList {523return {524incomplete: true,525suggestions: [{526label: 'foo',527kind: CompletionItemKind.Property,528insertText: 'foo',529range: Range.fromPositions(pos.with(undefined, 1), pos)530}]531};532}533}));534535return withOracle((model, editor) => {536537editor.getModel()!.setValue('foo');538editor.setPosition({ lineNumber: 1, column: 4 });539540return assertEvent(model.onDidSuggest, () => {541model.trigger({ auto: false });542}, event => {543assert.strictEqual(event.triggerOptions.auto, false);544assert.strictEqual(event.completionModel.getIncompleteProvider().size, 1);545assert.strictEqual(event.completionModel.items.length, 1);546547return assertEvent(model.onDidCancel, () => {548editor.trigger('keyboard', Handler.Type, { text: ';' });549}, event => {550assert.strictEqual(event.retrigger, false);551});552});553});554});555556test('Incomplete suggestion results cause re-triggering when typing w/o further context, #28400 (2/2)', function () {557558disposables.add(registry.register({ scheme: 'test' }, {559_debugDisplayName: 'test',560provideCompletionItems(doc, pos): CompletionList {561return {562incomplete: true,563suggestions: [{564label: 'foo;',565kind: CompletionItemKind.Property,566insertText: 'foo',567range: Range.fromPositions(pos.with(undefined, 1), pos)568}]569};570}571}));572573return withOracle((model, editor) => {574575editor.getModel()!.setValue('foo');576editor.setPosition({ lineNumber: 1, column: 4 });577578return assertEvent(model.onDidSuggest, () => {579model.trigger({ auto: false });580}, event => {581assert.strictEqual(event.triggerOptions.auto, false);582assert.strictEqual(event.completionModel.getIncompleteProvider().size, 1);583assert.strictEqual(event.completionModel.items.length, 1);584585return assertEvent(model.onDidSuggest, () => {586// while we cancel incrementally enriching the set of587// completions we still filter against those that we have588// until now589editor.trigger('keyboard', Handler.Type, { text: ';' });590}, event => {591assert.strictEqual(event.triggerOptions.auto, false);592assert.strictEqual(event.completionModel.getIncompleteProvider().size, 1);593assert.strictEqual(event.completionModel.items.length, 1);594595});596});597});598});599600test('Trigger character is provided in suggest context', function () {601let triggerCharacter = '';602disposables.add(registry.register({ scheme: 'test' }, {603_debugDisplayName: 'test',604triggerCharacters: ['.'],605provideCompletionItems(doc, pos, context): CompletionList {606assert.strictEqual(context.triggerKind, CompletionTriggerKind.TriggerCharacter);607triggerCharacter = context.triggerCharacter!;608return {609incomplete: false,610suggestions: [611{612label: 'foo.bar',613kind: CompletionItemKind.Property,614insertText: 'foo.bar',615range: Range.fromPositions(pos.with(undefined, 1), pos)616}617]618};619}620}));621622model.setValue('');623624return withOracle((model, editor) => {625626return assertEvent(model.onDidSuggest, () => {627editor.setPosition({ lineNumber: 1, column: 1 });628editor.trigger('keyboard', Handler.Type, { text: 'foo.' });629}, event => {630assert.strictEqual(triggerCharacter, '.');631});632});633});634635test('Mac press and hold accent character insertion does not update suggestions, #35269', function () {636disposables.add(registry.register({ scheme: 'test' }, {637_debugDisplayName: 'test',638provideCompletionItems(doc, pos): CompletionList {639return {640incomplete: true,641suggestions: [{642label: 'abc',643kind: CompletionItemKind.Property,644insertText: 'abc',645range: Range.fromPositions(pos.with(undefined, 1), pos)646}, {647label: 'äbc',648kind: CompletionItemKind.Property,649insertText: 'äbc',650range: Range.fromPositions(pos.with(undefined, 1), pos)651}]652};653}654}));655656model.setValue('');657return withOracle((model, editor) => {658659return assertEvent(model.onDidSuggest, () => {660editor.setPosition({ lineNumber: 1, column: 1 });661editor.trigger('keyboard', Handler.Type, { text: 'a' });662}, event => {663assert.strictEqual(event.completionModel.items.length, 1);664assert.strictEqual(event.completionModel.items[0].completion.label, 'abc');665666return assertEvent(model.onDidSuggest, () => {667editor.executeEdits('test', [EditOperation.replace(new Range(1, 1, 1, 2), 'ä')]);668669}, event => {670// suggest model changed to äbc671assert.strictEqual(event.completionModel.items.length, 1);672assert.strictEqual(event.completionModel.items[0].completion.label, 'äbc');673674});675});676});677});678679test('Backspace should not always cancel code completion, #36491', function () {680disposables.add(registry.register({ scheme: 'test' }, alwaysSomethingSupport));681682return withOracle(async (model, editor) => {683await assertEvent(model.onDidSuggest, () => {684editor.setPosition({ lineNumber: 1, column: 4 });685editor.trigger('keyboard', Handler.Type, { text: 'd' });686687}, event => {688assert.strictEqual(event.triggerOptions.auto, true);689assert.strictEqual(event.completionModel.items.length, 1);690const [first] = event.completionModel.items;691692assert.strictEqual(first.provider, alwaysSomethingSupport);693});694695await assertEvent(model.onDidSuggest, () => {696editor.runCommand(CoreEditingCommands.DeleteLeft, null);697698}, event => {699assert.strictEqual(event.triggerOptions.auto, true);700assert.strictEqual(event.completionModel.items.length, 1);701const [first] = event.completionModel.items;702703assert.strictEqual(first.provider, alwaysSomethingSupport);704});705});706});707708test('Text changes for completion CodeAction are affected by the completion #39893', function () {709disposables.add(registry.register({ scheme: 'test' }, {710_debugDisplayName: 'test',711provideCompletionItems(doc, pos): CompletionList {712return {713incomplete: true,714suggestions: [{715label: 'bar',716kind: CompletionItemKind.Property,717insertText: 'bar',718range: Range.fromPositions(pos.delta(0, -2), pos),719additionalTextEdits: [{720text: ', bar',721range: { startLineNumber: 1, endLineNumber: 1, startColumn: 17, endColumn: 17 }722}]723}]724};725}726}));727728model.setValue('ba; import { foo } from "./b"');729730return withOracle(async (sugget, editor) => {731class TestCtrl extends SuggestController {732_insertSuggestion_publicForTest(item: ISelectedSuggestion, flags: number = 0) {733super._insertSuggestion(item, flags);734}735}736const ctrl = <TestCtrl>editor.registerAndInstantiateContribution(TestCtrl.ID, TestCtrl);737editor.registerAndInstantiateContribution(SnippetController2.ID, SnippetController2);738739await assertEvent(sugget.onDidSuggest, () => {740editor.setPosition({ lineNumber: 1, column: 3 });741sugget.trigger({ auto: false });742}, event => {743744assert.strictEqual(event.completionModel.items.length, 1);745const [first] = event.completionModel.items;746assert.strictEqual(first.completion.label, 'bar');747748ctrl._insertSuggestion_publicForTest({ item: first, index: 0, model: event.completionModel });749});750751assert.strictEqual(752model.getValue(),753'bar; import { foo, bar } from "./b"'754);755});756});757758test('Completion unexpectedly triggers on second keypress of an edit group in a snippet #43523', function () {759760disposables.add(registry.register({ scheme: 'test' }, alwaysSomethingSupport));761762return withOracle((model, editor) => {763return assertEvent(model.onDidSuggest, () => {764editor.setValue('d');765editor.setSelection(new Selection(1, 1, 1, 2));766editor.trigger('keyboard', Handler.Type, { text: 'e' });767768}, event => {769assert.strictEqual(event.triggerOptions.auto, true);770assert.strictEqual(event.completionModel.items.length, 1);771const [first] = event.completionModel.items;772773assert.strictEqual(first.provider, alwaysSomethingSupport);774});775});776});777778779test('Fails to render completion details #47988', function () {780781let disposeA = 0;782let disposeB = 0;783784disposables.add(registry.register({ scheme: 'test' }, {785_debugDisplayName: 'test',786provideCompletionItems(doc, pos) {787return {788incomplete: true,789suggestions: [{790kind: CompletionItemKind.Folder,791label: 'CompleteNot',792insertText: 'Incomplete',793sortText: 'a',794range: getDefaultSuggestRange(doc, pos)795}],796dispose() { disposeA += 1; }797};798}799}));800disposables.add(registry.register({ scheme: 'test' }, {801_debugDisplayName: 'test',802provideCompletionItems(doc, pos) {803return {804incomplete: false,805suggestions: [{806kind: CompletionItemKind.Folder,807label: 'Complete',808insertText: 'Complete',809sortText: 'z',810range: getDefaultSuggestRange(doc, pos)811}],812dispose() { disposeB += 1; }813};814},815resolveCompletionItem(item) {816return item;817},818}));819820return withOracle(async (model, editor) => {821822await assertEvent(model.onDidSuggest, () => {823editor.setValue('');824editor.setSelection(new Selection(1, 1, 1, 1));825editor.trigger('keyboard', Handler.Type, { text: 'c' });826827}, event => {828assert.strictEqual(event.triggerOptions.auto, true);829assert.strictEqual(event.completionModel.items.length, 2);830assert.strictEqual(disposeA, 0);831assert.strictEqual(disposeB, 0);832});833834await assertEvent(model.onDidSuggest, () => {835editor.trigger('keyboard', Handler.Type, { text: 'o' });836}, event => {837assert.strictEqual(event.triggerOptions.auto, true);838assert.strictEqual(event.completionModel.items.length, 2);839840// clean up841model.clear();842assert.strictEqual(disposeA, 2); // provide got called two times!843assert.strictEqual(disposeB, 1);844});845846});847});848849850test('Trigger (full) completions when (incomplete) completions are already active #99504', function () {851852let countA = 0;853let countB = 0;854855disposables.add(registry.register({ scheme: 'test' }, {856_debugDisplayName: 'test',857provideCompletionItems(doc, pos) {858countA += 1;859return {860incomplete: false, // doesn't matter if incomplete or not861suggestions: [{862kind: CompletionItemKind.Class,863label: 'Z aaa',864insertText: 'Z aaa',865range: new Range(1, 1, pos.lineNumber, pos.column)866}],867};868}869}));870disposables.add(registry.register({ scheme: 'test' }, {871_debugDisplayName: 'test',872provideCompletionItems(doc, pos) {873countB += 1;874if (!doc.getWordUntilPosition(pos).word.startsWith('a')) {875return;876}877return {878incomplete: false,879suggestions: [{880kind: CompletionItemKind.Folder,881label: 'aaa',882insertText: 'aaa',883range: getDefaultSuggestRange(doc, pos)884}],885};886},887}));888889return withOracle(async (model, editor) => {890891await assertEvent(model.onDidSuggest, () => {892editor.setValue('');893editor.setSelection(new Selection(1, 1, 1, 1));894editor.trigger('keyboard', Handler.Type, { text: 'Z' });895896}, event => {897assert.strictEqual(event.triggerOptions.auto, true);898assert.strictEqual(event.completionModel.items.length, 1);899assert.strictEqual(event.completionModel.items[0].textLabel, 'Z aaa');900});901902await assertEvent(model.onDidSuggest, () => {903// started another word: Z a|904// item should be: Z aaa, aaa905editor.trigger('keyboard', Handler.Type, { text: ' a' });906}, event => {907assert.strictEqual(event.triggerOptions.auto, true);908assert.strictEqual(event.completionModel.items.length, 2);909assert.strictEqual(event.completionModel.items[0].textLabel, 'Z aaa');910assert.strictEqual(event.completionModel.items[1].textLabel, 'aaa');911912assert.strictEqual(countA, 1); // should we keep the suggestions from the "active" provider?, Yes! See: #106573913assert.strictEqual(countB, 2);914});915});916});917918test('registerCompletionItemProvider with letters as trigger characters block other completion items to show up #127815', async function () {919920disposables.add(registry.register({ scheme: 'test' }, {921_debugDisplayName: 'test',922provideCompletionItems(doc, pos) {923return {924suggestions: [{925kind: CompletionItemKind.Class,926label: 'AAAA',927insertText: 'WordTriggerA',928range: new Range(pos.lineNumber, pos.column, pos.lineNumber, pos.column)929}],930};931}932}));933disposables.add(registry.register({ scheme: 'test' }, {934_debugDisplayName: 'test',935triggerCharacters: ['a', '.'],936provideCompletionItems(doc, pos) {937return {938suggestions: [{939kind: CompletionItemKind.Class,940label: 'AAAA',941insertText: 'AutoTriggerA',942range: new Range(pos.lineNumber, pos.column, pos.lineNumber, pos.column)943}],944};945},946}));947948return withOracle(async (model, editor) => {949950await assertEvent(model.onDidSuggest, () => {951editor.setValue('');952editor.setSelection(new Selection(1, 1, 1, 1));953editor.trigger('keyboard', Handler.Type, { text: '.' });954955}, event => {956assert.strictEqual(event.triggerOptions.auto, true);957assert.strictEqual(event.completionModel.items.length, 1);958});959960961editor.getModel().setValue('');962963await assertEvent(model.onDidSuggest, () => {964editor.setValue('');965editor.setSelection(new Selection(1, 1, 1, 1));966editor.trigger('keyboard', Handler.Type, { text: 'a' });967968}, event => {969assert.strictEqual(event.triggerOptions.auto, true);970assert.strictEqual(event.completionModel.items.length, 2);971});972});973});974975test('Unexpected suggest scoring #167242', async function () {976disposables.add(registry.register('*', {977// word-based978_debugDisplayName: 'test',979provideCompletionItems(doc, pos) {980const word = doc.getWordUntilPosition(pos);981return {982suggestions: [{983kind: CompletionItemKind.Text,984label: 'pull',985insertText: 'pull',986range: new Range(pos.lineNumber, word.startColumn, pos.lineNumber, word.endColumn)987}],988};989}990}));991disposables.add(registry.register({ scheme: 'test' }, {992// JSON-based993_debugDisplayName: 'test',994provideCompletionItems(doc, pos) {995return {996suggestions: [{997kind: CompletionItemKind.Class,998label: 'git.pull',999insertText: 'git.pull',1000range: new Range(pos.lineNumber, 1, pos.lineNumber, pos.column)1001}],1002};1003},1004}));10051006return withOracle(async function (model, editor) {10071008await assertEvent(model.onDidSuggest, () => {1009editor.setValue('gi');1010editor.setSelection(new Selection(1, 3, 1, 3));1011editor.trigger('keyboard', Handler.Type, { text: 't' });10121013}, event => {1014assert.strictEqual(event.triggerOptions.auto, true);1015assert.strictEqual(event.completionModel.items.length, 1);1016assert.strictEqual(event.completionModel.items[0].textLabel, 'git.pull');1017});10181019editor.trigger('keyboard', Handler.Type, { text: '.' });10201021await assertEvent(model.onDidSuggest, () => {1022editor.trigger('keyboard', Handler.Type, { text: 'p' });10231024}, event => {1025assert.strictEqual(event.triggerOptions.auto, true);1026assert.strictEqual(event.completionModel.items.length, 1);1027assert.strictEqual(event.completionModel.items[0].textLabel, 'git.pull');1028});1029});1030});10311032test('Completion list closes unexpectedly when typing a digit after a word separator #169390', function () {10331034const requestCounts = [0, 0];10351036disposables.add(registry.register({ scheme: 'test' }, {1037_debugDisplayName: 'test',10381039provideCompletionItems(doc, pos) {1040requestCounts[0] += 1;1041return {1042suggestions: [{1043kind: CompletionItemKind.Text,1044label: 'foo-20',1045insertText: 'foo-20',1046range: new Range(pos.lineNumber, 1, pos.lineNumber, pos.column)1047}, {1048kind: CompletionItemKind.Text,1049label: 'foo-hello',1050insertText: 'foo-hello',1051range: new Range(pos.lineNumber, 1, pos.lineNumber, pos.column)1052}],1053};1054}1055}));1056disposables.add(registry.register({ scheme: 'test' }, {1057_debugDisplayName: 'test',1058triggerCharacters: ['2'],1059provideCompletionItems(doc, pos, ctx) {1060requestCounts[1] += 1;1061if (ctx.triggerKind !== CompletionTriggerKind.TriggerCharacter) {1062return;1063}1064return {1065suggestions: [{1066kind: CompletionItemKind.Class,1067label: 'foo-210',1068insertText: 'foo-210',1069range: new Range(pos.lineNumber, 1, pos.lineNumber, pos.column)1070}],1071};1072},1073}));10741075return withOracle(async function (model, editor) {10761077await assertEvent(model.onDidSuggest, () => {1078editor.setValue('foo');1079editor.setSelection(new Selection(1, 4, 1, 4));1080model.trigger({ auto: false });10811082}, event => {1083assert.strictEqual(event.triggerOptions.auto, false);1084assert.strictEqual(event.completionModel.items.length, 2);1085assert.strictEqual(event.completionModel.items[0].textLabel, 'foo-20');1086assert.strictEqual(event.completionModel.items[1].textLabel, 'foo-hello');1087});10881089editor.trigger('keyboard', Handler.Type, { text: '-' });109010911092await assertEvent(model.onDidSuggest, () => {1093editor.trigger('keyboard', Handler.Type, { text: '2' });10941095}, event => {1096assert.strictEqual(event.triggerOptions.auto, true);1097assert.strictEqual(event.completionModel.items.length, 2);1098assert.strictEqual(event.completionModel.items[0].textLabel, 'foo-20');1099assert.strictEqual(event.completionModel.items[1].textLabel, 'foo-210');1100assert.deepStrictEqual(requestCounts, [1, 2]);1101});1102});1103});11041105test('Set refilter-flag, keep triggerKind', function () {11061107disposables.add(registry.register({ scheme: 'test' }, {1108_debugDisplayName: 'test',1109triggerCharacters: ['.'],1110provideCompletionItems(doc, pos, ctx) {1111return {1112suggestions: [{1113label: doc.getWordUntilPosition(pos).word || 'hello',1114kind: CompletionItemKind.Property,1115insertText: 'foofoo',1116range: getDefaultSuggestRange(doc, pos)1117}]1118};1119},1120}));11211122return withOracle(async function (model, editor) {11231124await assertEvent(model.onDidSuggest, () => {1125editor.setValue('foo');1126editor.setSelection(new Selection(1, 4, 1, 4));1127editor.trigger('keyboard', Handler.Type, { text: 'o' });112811291130}, event => {1131assert.strictEqual(event.triggerOptions.auto, true);1132assert.strictEqual(event.triggerOptions.triggerCharacter, undefined);1133assert.strictEqual(event.triggerOptions.triggerKind, undefined);1134assert.strictEqual(event.completionModel.items.length, 1);1135});11361137await assertEvent(model.onDidSuggest, () => {1138editor.trigger('keyboard', Handler.Type, { text: '.' });11391140}, event => {1141assert.strictEqual(event.triggerOptions.auto, true);1142assert.strictEqual(event.triggerOptions.refilter, undefined);1143assert.strictEqual(event.triggerOptions.triggerCharacter, '.');1144assert.strictEqual(event.triggerOptions.triggerKind, CompletionTriggerKind.TriggerCharacter);1145assert.strictEqual(event.completionModel.items.length, 1);1146});11471148await assertEvent(model.onDidSuggest, () => {1149editor.trigger('keyboard', Handler.Type, { text: 'h' });11501151}, event => {1152assert.strictEqual(event.triggerOptions.auto, true);1153assert.strictEqual(event.triggerOptions.refilter, true);1154assert.strictEqual(event.triggerOptions.triggerCharacter, '.');1155assert.strictEqual(event.triggerOptions.triggerKind, CompletionTriggerKind.TriggerCharacter);1156assert.strictEqual(event.completionModel.items.length, 1);1157});1158});1159});11601161test('Snippets gone from IntelliSense #173244', function () {11621163const snippetProvider: CompletionItemProvider = {1164_debugDisplayName: 'test',1165provideCompletionItems(doc, pos, ctx) {1166return {1167suggestions: [{1168label: 'log',1169kind: CompletionItemKind.Snippet,1170insertText: 'log',1171range: getDefaultSuggestRange(doc, pos)1172}]1173};1174}1175};1176const old = setSnippetSuggestSupport(snippetProvider);11771178disposables.add(toDisposable(() => {1179if (getSnippetSuggestSupport() === snippetProvider) {1180setSnippetSuggestSupport(old);1181}1182}));11831184disposables.add(registry.register({ scheme: 'test' }, {1185_debugDisplayName: 'test',1186triggerCharacters: ['.'],1187provideCompletionItems(doc, pos, ctx) {1188return {1189suggestions: [{1190label: 'locals',1191kind: CompletionItemKind.Property,1192insertText: 'locals',1193range: getDefaultSuggestRange(doc, pos)1194}],1195incomplete: true1196};1197},1198}));11991200return withOracle(async function (model, editor) {12011202await assertEvent(model.onDidSuggest, () => {1203editor.setValue('');1204editor.setSelection(new Selection(1, 1, 1, 1));1205editor.trigger('keyboard', Handler.Type, { text: 'l' });120612071208}, event => {1209assert.strictEqual(event.triggerOptions.auto, true);1210assert.strictEqual(event.triggerOptions.triggerCharacter, undefined);1211assert.strictEqual(event.triggerOptions.triggerKind, undefined);1212assert.strictEqual(event.completionModel.items.length, 2);1213assert.strictEqual(event.completionModel.items[0].textLabel, 'locals');1214assert.strictEqual(event.completionModel.items[1].textLabel, 'log');1215});12161217await assertEvent(model.onDidSuggest, () => {1218editor.trigger('keyboard', Handler.Type, { text: 'o' });12191220}, event => {1221assert.strictEqual(event.triggerOptions.triggerKind, CompletionTriggerKind.TriggerForIncompleteCompletions);1222assert.strictEqual(event.triggerOptions.auto, true);1223assert.strictEqual(event.completionModel.items.length, 2);1224assert.strictEqual(event.completionModel.items[0].textLabel, 'locals');1225assert.strictEqual(event.completionModel.items[1].textLabel, 'log');1226});12271228});1229});1230});123112321233