GoToDefinitionHelpers.cs 6.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Navigation;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Navigation;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Editor.Implementation.GoToDefinition
{
    internal static class GoToDefinitionHelpers
    {
        public static bool TryGoToDefinition(
            ISymbol symbol,
            Project project,
            IEnumerable<Lazy<INavigableItemsPresenter>> presenters,
            ITypeSymbol containingTypeSymbol,
            bool throwOnHiddenDefinition,
            CancellationToken cancellationToken)
        {
            var alias = symbol as IAliasSymbol;
            if (alias != null)
            {
                var ns = alias.Target as INamespaceSymbol;
                if (ns != null && ns.IsGlobalNamespace)
                {
                    return false;
                }
            }

            // VB global import aliases have a synthesized SyntaxTree.
            // We can't go to the definition of the alias, so use the target type.

            var solution = project.Solution;
            if (symbol is IAliasSymbol &&
                NavigableItemFactory.GetPreferredSourceLocations(solution, symbol).All(l => project.Solution.GetDocument(l.SourceTree) == null))
            {
                symbol = ((IAliasSymbol)symbol).Target;
            }

            var definition = SymbolFinder.FindSourceDefinitionAsync(symbol, solution, cancellationToken).WaitAndGetResult(cancellationToken);
            cancellationToken.ThrowIfCancellationRequested();

            symbol = definition ?? symbol;

            if (TryThirdPartyNavigation(symbol, solution, containingTypeSymbol))
            {
                return true;
            }

            // If it is a partial method declaration with no body, choose to go to the implementation
            // that has a method body.
            if (symbol is IMethodSymbol)
            {
                symbol = ((IMethodSymbol)symbol).PartialImplementationPart ?? symbol;
            }

            var preferredSourceLocations = NavigableItemFactory.GetPreferredSourceLocations(solution, symbol).ToArray();
            if (!preferredSourceLocations.Any())
            {
                // If there are no visible source locations, then tell the host about the symbol and 
                // allow it to navigate to it.  THis will either navigate to any non-visible source
                // locations, or it can appropriately deal with metadata symbols for hosts that can go 
                // to a metadata-as-source view.

                var symbolNavigationService = solution.Workspace.Services.GetService<ISymbolNavigationService>();
R
Ravi Chande 已提交
73
                return symbolNavigationService.TryNavigateToSymbol(symbol, project, cancellationToken: cancellationToken, usePreviewTab: true);
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
            }

            // If we have a single location, then just navigate to it.
            if (preferredSourceLocations.Length == 1)
            {
                var firstItem = preferredSourceLocations[0];
                var workspace = project.Solution.Workspace;
                var navigationService = workspace.Services.GetService<IDocumentNavigationService>();

                if (navigationService.CanNavigateToSpan(workspace, solution.GetDocument(firstItem.SourceTree).Id, firstItem.SourceSpan))
                {
                    return navigationService.TryNavigateToSpan(workspace, solution.GetDocument(firstItem.SourceTree).Id, firstItem.SourceSpan, usePreviewTab: true);
                }
                else
                {
                    if (throwOnHiddenDefinition)
                    {
                        const int E_FAIL = -2147467259;
                        throw new COMException(EditorFeaturesResources.TheDefinitionOfTheObjectIsHidden, E_FAIL);
                    }
                    else
                    {
                        return false;
                    }
                }
            }
            else
            {
                // We have multiple viable source locations, so ask the host what to do. Most hosts
                // will simply display the results to the user and allow them to choose where to 
                // go.

                if (presenters.Any())
                {
                    presenters.First().Value.DisplayResult(
                        preferredSourceLocations.Select(location => NavigableItemFactory.GetItemFromSymbolLocation(solution, symbol, location)).ToList());

                    return true;
                }

                return false;
            }
        }

        private static bool TryThirdPartyNavigation(ISymbol symbol, Solution solution, ITypeSymbol containingTypeSymbol)
        {
            // Allow third parties to navigate to all symbols except types/constructors
            // if we are navigating from the corresponding type.

            if (containingTypeSymbol != null &&
                (symbol is ITypeSymbol || symbol.IsConstructor()))
            {
                var candidateTypeSymbol = symbol is ITypeSymbol
                    ? symbol
                    : symbol.ContainingType;

                if (containingTypeSymbol == candidateTypeSymbol)
                {
                    // We are navigating from the same type, so don't allow third parties to perform the navigation.
                    // This ensures that if we navigate to a class from within that class, we'll stay in the same file
                    // rather than navigate to, say, XAML.
                    return false;
                }
            }

            var symbolNavigationService = solution.Workspace.Services.GetService<ISymbolNavigationService>();

            // Notify of navigation so third parties can intercept the navigation
            return symbolNavigationService.TrySymbolNavigationNotify(symbol, solution);
        }
    }
}