private 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))
// Extract a local function to avoid creating a closure for code path of cache hit. static AsyncLazy <SyntaxContext> GetLazySyntaxContextWithSpeculativeModel(Document document, SharedSyntaxContextsWithSpeculativeModel self) { return(self._cache.GetOrAdd(document, d => AsyncLazy.Create(cancellationToken => CompletionHelper.CreateSyntaxContextWithExistingSpeculativeModelAsync(d, self._position, cancellationToken), cacheResult: true))); }
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(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, sharedContext, 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()); } }