Exemplo n.º 1
0
        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());
                }
            }