File: Completion\CompletionProviders\ExplicitInterfaceTypeCompletionProvider.cs
Web Access
Project: src\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.Features)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Completion.Providers;
using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers;
 
[ExportCompletionProvider(nameof(ExplicitInterfaceTypeCompletionProvider), LanguageNames.CSharp), Shared]
[ExtensionOrder(After = nameof(ExplicitInterfaceMemberCompletionProvider))]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed partial class ExplicitInterfaceTypeCompletionProvider() : AbstractSymbolCompletionProvider<CSharpSyntaxContext>
{
    internal override string Language => LanguageNames.CSharp;
 
    public override bool IsInsertionTrigger(SourceText text, int insertedCharacterPosition, CompletionOptions options)
        => CompletionUtilities.IsTriggerAfterSpaceOrStartOfWordCharacter(text, insertedCharacterPosition, options);
 
    public override ImmutableHashSet<char> TriggerCharacters { get; } = CompletionUtilities.SpaceTriggerCharacter;
 
    protected override (string displayText, string suffix, string insertionText) GetDisplayAndSuffixAndInsertionText(ISymbol symbol, CSharpSyntaxContext context)
        => CompletionUtilities.GetDisplayAndSuffixAndInsertionText(symbol, context);
 
    public override async Task ProvideCompletionsAsync(CompletionContext context)
    {
        try
        {
            var completionCount = context.Items.Count;
            await base.ProvideCompletionsAsync(context).ConfigureAwait(false);
 
            if (completionCount < context.Items.Count)
            {
                // If we added any items, then add a suggestion mode item as this is a location 
                // where a member name could be written, and we should not interfere with that.
                context.SuggestionModeItem = CreateSuggestionModeItem(
                    CSharpFeaturesResources.member_name,
                    CSharpFeaturesResources.Autoselect_disabled_due_to_member_declaration);
            }
        }
        catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, ErrorSeverity.General))
        {
            // nop
        }
    }
 
    protected override Task<ImmutableArray<SymbolAndSelectionInfo>> GetSymbolsAsync(
        CompletionContext? completionContext, CSharpSyntaxContext context, int position, CompletionOptions options, CancellationToken cancellationToken)
    {
        var targetToken = context.TargetToken;
 
        // Don't want to offer this after "async" (even though the compiler may parse that as a type).
        if (SyntaxFacts.GetContextualKeywordKind(targetToken.ValueText) == SyntaxKind.AsyncKeyword)
            return SpecializedTasks.EmptyImmutableArray<SymbolAndSelectionInfo>();
 
        var potentialTypeNode = targetToken.Parent;
        if (targetToken.IsKind(SyntaxKind.GreaterThanToken) && potentialTypeNode is TypeArgumentListSyntax typeArgumentList)
            potentialTypeNode = typeArgumentList.Parent;
 
        var typeNode = potentialTypeNode as TypeSyntax;
 
        while (typeNode != null)
        {
            if (typeNode.Parent is TypeSyntax parentType && parentType.Span.End < position)
            {
                typeNode = parentType;
            }
            else
            {
                break;
            }
        }
 
        if (typeNode == null)
            return SpecializedTasks.EmptyImmutableArray<SymbolAndSelectionInfo>();
 
        // We weren't after something that looked like a type.
        var tokenBeforeType = typeNode.GetFirstToken().GetPreviousToken();
 
        if (!IsPreviousTokenValid(tokenBeforeType))
            return SpecializedTasks.EmptyImmutableArray<SymbolAndSelectionInfo>();
 
        var typeDeclaration = typeNode.GetAncestor<TypeDeclarationSyntax>();
        if (typeDeclaration == null)
            return SpecializedTasks.EmptyImmutableArray<SymbolAndSelectionInfo>();
 
        // Looks syntactically good.  See what interfaces our containing class/struct/interface has
        Debug.Assert(IsClassOrStructOrInterfaceOrRecord(typeDeclaration));
 
        var semanticModel = context.SemanticModel;
        var namedType = semanticModel.GetDeclaredSymbol(typeDeclaration, cancellationToken);
        Contract.ThrowIfNull(namedType);
 
        using var _ = PooledHashSet<ISymbol>.GetInstance(out var interfaceSet);
        foreach (var directInterface in namedType.Interfaces)
        {
            interfaceSet.Add(directInterface);
            interfaceSet.AddRange(directInterface.AllInterfaces);
        }
 
        return Task.FromResult(interfaceSet.SelectAsArray(t => new SymbolAndSelectionInfo(Symbol: t, Preselect: false)));
    }
 
    private static bool IsPreviousTokenValid(SyntaxToken tokenBeforeType)
    {
        while (tokenBeforeType.Kind() is SyntaxKind.AsyncKeyword or SyntaxKind.StaticKeyword)
            tokenBeforeType = tokenBeforeType.GetPreviousToken();
 
        // Show us after the open brace for a class/struct/interface
        if (tokenBeforeType.Kind() == SyntaxKind.OpenBraceToken)
            return IsClassOrStructOrInterfaceOrRecord(tokenBeforeType.GetRequiredParent());
 
        if (tokenBeforeType.Kind() is SyntaxKind.CloseBraceToken or SyntaxKind.SemicolonToken)
        {
            // Check that we're after a class/struct/interface member.
            var memberDeclaration = tokenBeforeType.GetAncestor<MemberDeclarationSyntax>();
            return memberDeclaration?.GetLastToken() == tokenBeforeType &&
                   IsClassOrStructOrInterfaceOrRecord(memberDeclaration.GetRequiredParent());
        }
 
        return false;
    }
 
    private static bool IsClassOrStructOrInterfaceOrRecord(SyntaxNode node)
        => node.Kind() is SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration or
            SyntaxKind.InterfaceDeclaration or SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration;
 
    protected override CompletionItem CreateItem(
        CompletionContext completionContext,
        string displayText,
        string displayTextSuffix,
        string insertionText,
        ImmutableArray<SymbolAndSelectionInfo> symbols,
        CSharpSyntaxContext context,
        SupportedPlatformData? supportedPlatformData)
    {
        return CreateItemDefault(displayText, displayTextSuffix, insertionText, symbols, context, supportedPlatformData);
    }
}