File: Completion\Providers\AbstractPartialTypeCompletionProvider.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.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.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Completion.Providers;
 
internal abstract partial class AbstractPartialTypeCompletionProvider<TSyntaxContext> : LSPCompletionProvider
    where TSyntaxContext : SyntaxContext
{
    protected AbstractPartialTypeCompletionProvider()
    {
    }
 
    public sealed override async Task ProvideCompletionsAsync(CompletionContext completionContext)
    {
        try
        {
            var document = completionContext.Document;
            var position = completionContext.Position;
            var cancellationToken = completionContext.CancellationToken;
 
            var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
            var node = GetPartialTypeSyntaxNode(tree, position, cancellationToken);
 
            if (node != null)
            {
                var semanticModel = await document.ReuseExistingSpeculativeModelAsync(node, cancellationToken).ConfigureAwait(false);
                if (semanticModel.GetDeclaredSymbol(node, cancellationToken) is INamedTypeSymbol declaredSymbol)
                {
                    var syntaxContextService = document.GetRequiredLanguageService<ISyntaxContextService>();
                    var syntaxContext = (TSyntaxContext)syntaxContextService.CreateContext(document, semanticModel, position, cancellationToken);
                    var symbols = LookupCandidateSymbols(syntaxContext, declaredSymbol, cancellationToken);
                    var items = symbols?.Select(s => CreateCompletionItem(s, syntaxContext));
 
                    if (items != null)
                    {
                        completionContext.AddItems(items);
                    }
                }
            }
        }
        catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, ErrorSeverity.General))
        {
            // nop
        }
    }
 
    private CompletionItem CreateCompletionItem(INamedTypeSymbol symbol, TSyntaxContext context)
    {
        var (displayText, suffix, insertionText) = GetDisplayAndSuffixAndInsertionText(symbol, context);
 
        return SymbolCompletionItem.CreateWithSymbolId(
            displayText: displayText,
            displayTextSuffix: suffix,
            insertionText: insertionText,
            symbols: ImmutableArray.Create(symbol),
            contextPosition: context.Position,
            properties: GetProperties(symbol, context),
            rules: CompletionItemRules.Default);
    }
 
    protected abstract ImmutableArray<KeyValuePair<string, string>> GetProperties(INamedTypeSymbol symbol, TSyntaxContext context);
 
    protected abstract SyntaxNode? GetPartialTypeSyntaxNode(SyntaxTree tree, int position, CancellationToken cancellationToken);
 
    protected abstract (string displayText, string suffix, string insertionText) GetDisplayAndSuffixAndInsertionText(INamedTypeSymbol symbol, TSyntaxContext context);
 
    protected virtual IEnumerable<INamedTypeSymbol>? LookupCandidateSymbols(TSyntaxContext context, INamedTypeSymbol declaredSymbol, CancellationToken cancellationToken)
    {
        if (declaredSymbol == null)
        {
            throw new ArgumentNullException(nameof(declaredSymbol));
        }
 
        var semanticModel = context.SemanticModel;
 
        if (declaredSymbol.ContainingSymbol is not INamespaceOrTypeSymbol containingSymbol)
            return [];
 
        return semanticModel.LookupNamespacesAndTypes(context.Position, containingSymbol)
                            .OfType<INamedTypeSymbol>()
                            .Where(symbol => declaredSymbol.TypeKind == symbol.TypeKind &&
                                             NotNewDeclaredMember(symbol, context) &&
                                             InSameProject(symbol, semanticModel.Compilation));
    }
 
    private static bool InSameProject(INamedTypeSymbol symbol, Compilation compilation)
        => symbol.DeclaringSyntaxReferences.Any(static (r, compilation) => compilation.SyntaxTrees.Contains(r.SyntaxTree), compilation);
 
    private static bool NotNewDeclaredMember(INamedTypeSymbol symbol, TSyntaxContext context)
    {
        return symbol.DeclaringSyntaxReferences
                     .Select(reference => reference.GetSyntax())
                     .Any(node => !(node.SyntaxTree == context.SyntaxTree && node.Span.IntersectsWith(context.Position)));
    }
 
    internal override Task<CompletionDescription> GetDescriptionWorkerAsync(Document document, CompletionItem item, CompletionOptions options, SymbolDescriptionOptions displayOptions, CancellationToken cancellationToken)
        => SymbolCompletionItem.GetDescriptionAsync(item, document, displayOptions, cancellationToken);
 
    public override Task<TextChange?> GetTextChangeAsync(Document document, CompletionItem selectedItem, char? ch, CancellationToken cancellationToken)
    {
        var insertionText = SymbolCompletionItem.GetInsertionText(selectedItem);
        return Task.FromResult<TextChange?>(new TextChange(selectedItem.Span, insertionText));
    }
}