File: CallHierarchy\CallHierarchyProvider.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices.csproj (Microsoft.VisualStudio.LanguageServices)
// 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.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CallHierarchy;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.GoToDefinition;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.Language.CallHierarchy;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities;
using Microsoft.VisualStudio.Utilities;
 
namespace Microsoft.CodeAnalysis.Editor.Implementation.CallHierarchy;
 
[Export(typeof(CallHierarchyProvider))]
internal sealed partial class CallHierarchyProvider
{
    public readonly IAsynchronousOperationListener AsyncListener;
    public readonly IUIThreadOperationExecutor ThreadOperationExecutor;
    private readonly Lazy<IStreamingFindUsagesPresenter> _streamingPresenter;
 
    public IThreadingContext ThreadingContext { get; }
    public IGlyphService GlyphService { get; }
 
    [ImportingConstructor]
    [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
    public CallHierarchyProvider(
        IThreadingContext threadingContext,
        IUIThreadOperationExecutor threadOperationExecutor,
        IAsynchronousOperationListenerProvider listenerProvider,
        IGlyphService glyphService,
        Lazy<IStreamingFindUsagesPresenter> streamingPresenter)
    {
        AsyncListener = listenerProvider.GetListener(FeatureAttribute.CallHierarchy);
        ThreadingContext = threadingContext;
        ThreadOperationExecutor = threadOperationExecutor;
        GlyphService = glyphService;
        _streamingPresenter = streamingPresenter;
    }
 
    public async Task<CallHierarchyItem?> CreateItemAsync(
        ISymbol symbol, Project project, ImmutableArray<Location> callsites, CancellationToken cancellationToken)
    {
        var service = project.GetRequiredLanguageService<ICallHierarchyService>();
        var descriptor = await service.CreateItemAsync(symbol, project, cancellationToken).ConfigureAwait(false);
        return descriptor != null
            ? await CreateItemAsync(descriptor, project.Solution.Workspace, callsites, cancellationToken).ConfigureAwait(false)
            : null;
    }
 
    public async Task<CallHierarchyItem?> CreateItemAsync(
        CallHierarchyItemDescriptor descriptor, Workspace workspace, ImmutableArray<Location> callsites, CancellationToken cancellationToken)
    {
        var resolved = await descriptor.ItemId.TryResolveAsync(workspace.CurrentSolution, cancellationToken).ConfigureAwait(false);
        if (resolved == null)
            return null;
 
        var (symbol, project) = resolved.Value;
        var location = await GoToDefinitionHelpers.GetDefinitionLocationAsync(
            symbol, project.Solution, this.ThreadingContext, _streamingPresenter.Value, cancellationToken).ConfigureAwait(false);
        return new CallHierarchyItem(
            this,
            descriptor,
            location,
            await CreateSearchCategoryEntriesAsync(descriptor, symbol, workspace.CurrentSolution, cancellationToken).ConfigureAwait(false),
            () => descriptor.Glyph.GetImageSource(GlyphService),
            symbol.ToDisplayString(),
            project.Name,
            callsites,
            project);
    }
 
    public FieldInitializerItem CreateInitializerItem(IEnumerable<CallHierarchyDetail> details)
    {
        return new FieldInitializerItem(EditorFeaturesResources.Initializers,
                                        "__" + EditorFeaturesResources.Initializers,
                                        Glyph.FieldPublic.GetImageSource(GlyphService),
                                        details);
    }
 
    public async Task<ImmutableArray<CallHierarchySearchResult>> SearchAsync(
        Workspace workspace,
        CallHierarchySearchDescriptor searchDescriptor,
        CallHierarchySearchScope searchScope,
        IImmutableSet<Document>? documents,
        CancellationToken cancellationToken)
    {
        var project = workspace.CurrentSolution.GetProject(searchDescriptor.ItemId.ProjectId);
        if (project == null)
            return [];
 
        documents ??= IncludeDocuments(searchScope, project);
        var service = project.GetRequiredLanguageService<ICallHierarchyService>();
        return await service.SearchIncomingCallsAsync(workspace.CurrentSolution, searchDescriptor, documents, cancellationToken).ConfigureAwait(false);
    }
 
    private static IImmutableSet<Document>? IncludeDocuments(CallHierarchySearchScope scope, Project project)
    {
        if (scope is CallHierarchySearchScope.CurrentDocument or CallHierarchySearchScope.CurrentProject)
        {
            var documentTrackingService = project.Solution.Services.GetRequiredService<IDocumentTrackingService>();
            var activeDocument = documentTrackingService.TryGetActiveDocument();
            if (activeDocument != null)
            {
                if (scope == CallHierarchySearchScope.CurrentProject)
                {
                    var currentProject = project.Solution.GetProject(activeDocument.ProjectId);
                    if (currentProject != null)
                        return ImmutableHashSet.CreateRange(currentProject.Documents);
                }
                else
                {
                    var currentDocument = project.Solution.GetDocument(activeDocument);
                    if (currentDocument != null)
                        return ImmutableHashSet.Create(currentDocument);
                }
 
                return ImmutableHashSet<Document>.Empty;
            }
        }
 
        return null;
    }
 
    private static async Task<ImmutableArray<CallHierarchySearchCategoryEntry>> CreateSearchCategoryEntriesAsync(
        CallHierarchyItemDescriptor descriptor,
        ISymbol symbol,
        Solution solution,
        CancellationToken cancellationToken)
    {
        var builder = ImmutableArray.CreateBuilder<CallHierarchySearchCategoryEntry>(descriptor.SupportedSearchDescriptors.Length);
        foreach (var searchDescriptor in descriptor.SupportedSearchDescriptors)
        {
            builder.Add(await CreateSearchCategoryEntryAsync(searchDescriptor, symbol, solution, cancellationToken).ConfigureAwait(false));
        }
 
        return builder.MoveToImmutable();
    }
 
    private static async Task<CallHierarchySearchCategoryEntry> CreateSearchCategoryEntryAsync(
        CallHierarchySearchDescriptor descriptor,
        ISymbol symbol,
        Solution solution,
        CancellationToken cancellationToken)
    {
        var relatedSymbol = descriptor.Relationship switch
        {
            CallHierarchyRelationshipKind.BaseMember or CallHierarchyRelationshipKind.InterfaceImplementations
                => (await descriptor.ItemId.TryResolveAsync(solution, cancellationToken).ConfigureAwait(false))?.Symbol,
            _ => null,
        };
 
        var displayName = descriptor.Relationship switch
        {
            CallHierarchyRelationshipKind.Callers => string.Format(EditorFeaturesResources.Calls_To_0, symbol.Name),
            CallHierarchyRelationshipKind.CallsToOverrides => EditorFeaturesResources.Calls_To_Overrides,
            CallHierarchyRelationshipKind.BaseMember => string.Format(EditorFeaturesResources.Calls_To_Base_Member_0, relatedSymbol?.ToDisplayString() ?? symbol.ToDisplayString()),
            CallHierarchyRelationshipKind.InterfaceImplementations => string.Format(EditorFeaturesResources.Calls_To_Interface_Implementation_0, relatedSymbol?.ToDisplayString() ?? symbol.ToDisplayString()),
            CallHierarchyRelationshipKind.Implementations => string.Format(EditorFeaturesResources.Implements_0, symbol.Name),
            CallHierarchyRelationshipKind.Overrides => EditorFeaturesResources.Overrides_,
            CallHierarchyRelationshipKind.FieldReferences => string.Format(EditorFeaturesResources.References_To_Field_0, symbol.Name),
            _ => throw new InvalidOperationException(),
        };
 
        var searchCategory = descriptor.Relationship switch
        {
            CallHierarchyRelationshipKind.Callers => CallHierarchyPredefinedSearchCategoryNames.Callers,
            CallHierarchyRelationshipKind.InterfaceImplementations => CallHierarchyPredefinedSearchCategoryNames.InterfaceImplementations,
            CallHierarchyRelationshipKind.Overrides => CallHierarchyPredefinedSearchCategoryNames.Overrides,
            _ => displayName,
        };
 
        return new CallHierarchySearchCategoryEntry(descriptor, searchCategory, displayName);
    }
}