File: NavigationBar\AbstractNavigationBarItemService.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.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Text;
using static Microsoft.CodeAnalysis.NavigationBar.RoslynNavigationBarItem;
 
namespace Microsoft.CodeAnalysis.NavigationBar;
 
internal abstract class AbstractNavigationBarItemService : INavigationBarItemService
{
    protected abstract Task<ImmutableArray<RoslynNavigationBarItem>> GetItemsInCurrentProcessAsync(Document document, bool supportsCodeGeneration, CancellationToken cancellationToken);
 
    public async Task<ImmutableArray<RoslynNavigationBarItem>> GetItemsAsync(Document document, bool supportsCodeGeneration, bool frozenPartialSemantics, CancellationToken cancellationToken)
    {
        var client = await RemoteHostClient.TryGetClientAsync(document.Project, cancellationToken).ConfigureAwait(false);
        if (client != null)
        {
            // Call the project overload.  We don't need the full solution synchronized over to the OOP
            // in order to get accurate navbar contents for this document.
            var documentId = document.Id;
            var result = await client.TryInvokeAsync<IRemoteNavigationBarItemService, ImmutableArray<SerializableNavigationBarItem>>(
                document.Project,
                (service, solutionInfo, cancellationToken) => service.GetItemsAsync(solutionInfo, documentId, supportsCodeGeneration, frozenPartialSemantics, cancellationToken),
                cancellationToken).ConfigureAwait(false);
 
            return result.HasValue
                ? result.Value.SelectAsArray(v => v.Rehydrate())
                : [];
        }
 
        var items = await GetItemsInCurrentProcessAsync(document, supportsCodeGeneration, cancellationToken).ConfigureAwait(false);
        return items;
    }
 
    protected static SymbolItemLocation? GetSymbolLocation(
        Solution solution, ISymbol symbol, SyntaxTree tree, Func<SyntaxReference, TextSpan> computeFullSpan)
    {
        return GetSymbolLocation(solution, symbol, tree, computeFullSpan, symbol.DeclaringSyntaxReferences);
    }
 
    private static SymbolItemLocation? GetSymbolLocation(
        Solution solution, ISymbol symbol, SyntaxTree tree,
        Func<SyntaxReference, TextSpan> computeFullSpan,
        ImmutableArray<SyntaxReference> allReferences)
    {
        if (allReferences.Length == 0)
            return null;
 
        // See if there are any references in the starting file. We always prefer those for any symbol we find.
        var referencesInCurrentFile = allReferences.WhereAsArray(r => r.SyntaxTree == tree);
        if (referencesInCurrentFile.Length > 0)
        {
            // the symbol had one or more declarations in this file.  We want to include all those spans in what we
            // return so that if the use enters any of its spans we highlight it in the list.  An example of having
            // multiple locations in the same file would be a a partial type with multiple parts in the same file.
 
            // If we're not able to find a narrower navigation location in this file though then just navigate to
            // the first reference itself.
            var navigationLocationSpan = symbol.Locations.FirstOrDefault(loc => loc.SourceTree == tree)?.SourceSpan ??
                                         referencesInCurrentFile.First().Span;
 
            var spans = referencesInCurrentFile.SelectAsArray(r => computeFullSpan(r));
            return new SymbolItemLocation((spans, navigationLocationSpan), otherDocumentInfo: null);
        }
        else
        {
            // the symbol was defined in another file altogether.  We don't care about it's full span
            // (since that is only needed for intersecting with the caret.  Instead, we just need a 
            // reasonable location to navigate them to.  First try to find a narrow location to navigate to.
            // And, if we can't, just go to the first reference we can find.
            var navigationLocation = symbol.Locations.FirstOrDefault(loc => loc.SourceTree != null && loc.SourceTree != tree) ??
                                     Location.Create(allReferences.First().SyntaxTree, allReferences.First().Span);
 
            var documentId = solution.GetDocumentId(navigationLocation.SourceTree);
            if (documentId == null)
                return null;
 
            return new SymbolItemLocation(inDocumentInfo: null, (documentId, navigationLocation.SourceSpan));
        }
    }
 
    protected static SymbolItemLocation? GetSymbolLocation(
        Solution solution, ISymbol symbol, SyntaxTree tree, ISymbolDeclarationService symbolDeclarationService)
    {
        return GetSymbolLocation(solution, symbol, tree, r => r.GetSyntax().FullSpan, symbolDeclarationService.GetDeclarations(symbol));
    }
}