private protected async Task <CompletionList> GetCompletionsWithAvailabilityOfExpandedItemsAsync( Document document, int caretPosition, CompletionOptions options, OptionSet passThroughOptions, CompletionTrigger trigger, ImmutableHashSet <string>?roles, CancellationToken cancellationToken) { // We don't need SemanticModel here, just want to make sure it won't get GC'd before CompletionProviders are able to get it. (document, var semanticModel) = await GetDocumentWithFrozenPartialSemanticsAsync(document, cancellationToken).ConfigureAwait(false); var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var defaultItemSpan = GetDefaultCompletionListSpan(text, caretPosition); var providers = _providerManager.GetFilteredProviders(document.Project, roles, trigger, options); // Phase 1: Completion Providers decide if they are triggered based on textual analysis // Phase 2: Completion Providers use syntax to confirm they are triggered, or decide they are not actually triggered and should become an augmenting provider // Phase 3: Triggered Providers are asked for items // Phase 4: If any items were provided, all augmenting providers are asked for items // This allows a provider to be textually triggered but later decide to be an augmenting provider based on deeper syntactic analysis. var triggeredProviders = GetTriggeredProviders(document, providers, caretPosition, options, trigger, roles, text); var additionalAugmentingProviders = await GetAugmentingProviders(document, triggeredProviders, caretPosition, trigger, options, cancellationToken).ConfigureAwait(false); triggeredProviders = triggeredProviders.Except(additionalAugmentingProviders).ToImmutableArray(); // PERF: Many CompletionProviders compute identical contexts. This actually shows up on the 2-core typing test. // so we try to share a single SyntaxContext based on document/caretPosition among all providers to reduce repeat computation. var sharedContext = new SharedSyntaxContextsWithSpeculativeModel(document, caretPosition); // Now, ask all the triggered providers, in parallel, to populate a completion context. // Note: we keep any context with items *or* with a suggested item. var triggeredContexts = await ComputeNonEmptyCompletionContextsAsync( document, caretPosition, trigger, options, defaultItemSpan, triggeredProviders, sharedContext, cancellationToken).ConfigureAwait(false); // Nothing to do if we didn't even get any regular items back (i.e. 0 items or suggestion item only.) if (!triggeredContexts.Any(static cc => cc.Items.Count > 0))
/// <summary> /// Returns true if the character recently inserted or deleted in the text should trigger completion. /// </summary> /// <param name="project">The project containing the document and text</param> /// <param name="languageServices">Language services</param> /// <param name="text">The document text to trigger completion within </param> /// <param name="caretPosition">The position of the caret after the triggering action.</param> /// <param name="trigger">The potential triggering action.</param> /// <param name="options">Options.</param> /// <param name="passThroughOptions">Options originating either from external caller of the <see cref="CompletionService"/> or set externally to <see cref="Solution.Options"/>.</param> /// <param name="roles">Optional set of roles associated with the editor state.</param> /// <remarks> /// We pass the project here to retrieve information about the <see cref="Project.AnalyzerReferences"/>, /// <see cref="WorkspaceKind"/> and <see cref="Project.Language"/> which are fast operations. /// It should not be used for syntactic or semantic operations. /// </remarks> internal virtual bool ShouldTriggerCompletion( Project?project, HostLanguageServices languageServices, SourceText text, int caretPosition, CompletionTrigger trigger, CompletionOptions options, OptionSet passThroughOptions, ImmutableHashSet <string>?roles = null) { if (!options.TriggerOnTyping) { return(false); } if (trigger.Kind == CompletionTriggerKind.Deletion && SupportsTriggerOnDeletion(options)) { return(char.IsLetterOrDigit(trigger.Character) || trigger.Character == '.'); } var providers = _providerManager.GetFilteredProviders(project, roles, trigger, options); return(providers.Any(p => p.ShouldTriggerCompletion(languageServices, text, caretPosition, trigger, options, passThroughOptions))); }
private protected async Task <CompletionList> GetCompletionsWithAvailabilityOfExpandedItemsAsync( Document document, int caretPosition, CompletionOptions options, OptionSet passThroughOptions, CompletionTrigger trigger, ImmutableHashSet <string>?roles, CancellationToken cancellationToken) { // We don't need SemanticModel here, just want to make sure it won't get GC'd before CompletionProviders are able to get it. (document, var semanticModel) = await GetDocumentWithFrozenPartialSemanticsAsync(document, cancellationToken).ConfigureAwait(false); var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var defaultItemSpan = GetDefaultCompletionListSpan(text, caretPosition); var providers = _providerManager.GetFilteredProviders(document.Project, roles, trigger, options); // Phase 1: Completion Providers decide if they are triggered based on textual analysis // Phase 2: Completion Providers use syntax to confirm they are triggered, or decide they are not actually triggered and should become an augmenting provider // Phase 3: Triggered Providers are asked for items // Phase 4: If any items were provided, all augmenting providers are asked for items // This allows a provider to be textually triggered but later decide to be an augmenting provider based on deeper syntactic analysis. var triggeredProviders = GetTriggeredProviders(document, providers, caretPosition, options, trigger, roles, text); var additionalAugmentingProviders = await GetAugmentingProviders(document, triggeredProviders, caretPosition, trigger, options, cancellationToken).ConfigureAwait(false); triggeredProviders = triggeredProviders.Except(additionalAugmentingProviders).ToImmutableArray(); // Now, ask all the triggered providers, in parallel, to populate a completion context. // Note: we keep any context with items *or* with a suggested item. var triggeredContexts = await ComputeNonEmptyCompletionContextsAsync( document, caretPosition, trigger, options, defaultItemSpan, triggeredProviders, cancellationToken).ConfigureAwait(false); // Nothing to do if we didn't even get any regular items back (i.e. 0 items or suggestion item only.) if (!triggeredContexts.Any(cc => cc.Items.Count > 0)) { return(CompletionList.Empty); } // See if there were completion contexts provided that were exclusive. If so, then // that's all we'll return. var exclusiveContexts = triggeredContexts.Where(t => t.IsExclusive).ToImmutableArray(); if (!exclusiveContexts.IsEmpty) { return(MergeAndPruneCompletionLists(exclusiveContexts, defaultItemSpan, options, isExclusive: true)); } // Great! We had some items. Now we want to see if any of the other providers // would like to augment the completion list. For example, we might trigger // enum-completion on space. If enum completion results in any items, then // we'll want to augment the list with all the regular symbol completion items. var augmentingProviders = providers.Except(triggeredProviders).ToImmutableArray(); var augmentingContexts = await ComputeNonEmptyCompletionContextsAsync( document, caretPosition, trigger, options, defaultItemSpan, augmentingProviders, cancellationToken).ConfigureAwait(false); GC.KeepAlive(semanticModel); // Providers are ordered, but we processed them in our own order. Ensure that the // groups are properly ordered based on the original providers. var completionProviderToIndex = GetCompletionProviderToIndex(providers); var allContexts = triggeredContexts.Concat(augmentingContexts) .Sort((p1, p2) => completionProviderToIndex[p1.Provider] - completionProviderToIndex[p2.Provider]); return(MergeAndPruneCompletionLists(allContexts, defaultItemSpan, options, isExclusive: false)); ImmutableArray <CompletionProvider> GetTriggeredProviders( Document document, ConcatImmutableArray <CompletionProvider> providers, int caretPosition, CompletionOptions options, CompletionTrigger trigger, ImmutableHashSet <string>?roles, SourceText text) { switch (trigger.Kind) { case CompletionTriggerKind.Insertion: case CompletionTriggerKind.Deletion: if (ShouldTriggerCompletion(document.Project, document.Project.LanguageServices, text, caretPosition, trigger, options, passThroughOptions, roles)) { var triggeredProviders = providers.Where(p => p.ShouldTriggerCompletion(document.Project.LanguageServices, text, caretPosition, trigger, options, passThroughOptions)).ToImmutableArrayOrEmpty(); Debug.Assert(ValidatePossibleTriggerCharacterSet(trigger.Kind, triggeredProviders, document, text, caretPosition, options)); return(triggeredProviders.IsEmpty ? providers.ToImmutableArray() : triggeredProviders); } return(ImmutableArray <CompletionProvider> .Empty); default: return(providers.ToImmutableArray()); } }