GoToDefinitionHelpers.cs 9.8 KB
Newer Older
1 2 3 4
// 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;
5
using System.Collections.Immutable;
6 7 8
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
9
using System.Threading.Tasks;
10 11 12 13
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Navigation;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Navigation;
14
using Microsoft.CodeAnalysis.Options;
15 16 17 18 19 20 21 22 23
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Editor.Implementation.GoToDefinition
{
    internal static class GoToDefinitionHelpers
    {
        public static bool TryGoToDefinition(
            ISymbol symbol,
            Project project,
24
            IEnumerable<Lazy<INavigableDefinitionProvider>> externalDefinitionProviders,
25
            IEnumerable<Lazy<INavigableItemsPresenter>> presenters,
26 27 28
            CancellationToken cancellationToken,
            bool thirdPartyNavigationAllowed = true,
            bool throwOnHiddenDefinition = false)
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
        {
            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;

55
            if (thirdPartyNavigationAllowed && TryThirdPartyNavigation(symbol, solution))
56 57 58 59 60 61 62 63 64 65 66
            {
                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;
            }

67 68
            var options = project.Solution.Workspace.Options;

69
            var preferredSourceLocations = NavigableItemFactory.GetPreferredSourceLocations(solution, symbol).ToArray();
70
            var title = NavigableItemFactory.GetSymbolDisplayString(project, symbol);
71 72
            if (!preferredSourceLocations.Any())
            {
73 74 75 76 77 78
                // Attempt to find source locations from external definition providers.
                if (thirdPartyNavigationAllowed && externalDefinitionProviders.Any())
                {
                    var externalSourceDefinitions = FindExternalDefinitionsAsync(symbol, project, externalDefinitionProviders, cancellationToken).WaitAndGetResult(cancellationToken).ToImmutableArray();
                    if (externalSourceDefinitions.Length > 0)
                    {
79
                        return TryGoToDefinition(externalSourceDefinitions, title, project.Solution.Workspace.Options, presenters, throwOnHiddenDefinition);
80 81 82
                    }
                }

83
                // If there are no visible source locations, then tell the host about the symbol and 
84
                // allow it to navigate to it.  This will either navigate to any non-visible source
85 86 87 88
                // 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>();
89 90
                return symbolNavigationService.TryNavigateToSymbol(
                    symbol, project,
91
                    options: options.WithChangedOption(NavigationOptions.PreferProvisionalTab, true),
92
                    cancellationToken: cancellationToken);
93 94
            }

95
            var navigableItems = preferredSourceLocations.Select(location => NavigableItemFactory.GetItemFromSymbolLocation(solution, symbol, location)).ToImmutableArray();
96
            return TryGoToDefinition(navigableItems, title, project.Solution.Workspace.Options, presenters, throwOnHiddenDefinition);
97 98
        }

99
        private static bool TryGoToDefinition(
100 101
            ImmutableArray<INavigableItem> navigableItems,
            string title,
102
            OptionSet options,
103 104 105
            IEnumerable<Lazy<INavigableItemsPresenter>> presenters,
            bool throwOnHiddenDefinition)
        {
106 107
            Contract.ThrowIfNull(options);

108
            // If we have a single location, then just navigate to it.
109
            if (navigableItems.Length == 1 && navigableItems[0].Document != null)
110
            {
111
                var firstItem = navigableItems[0];
112
                var workspace = firstItem.Document.Project.Solution.Workspace;
113 114
                var navigationService = workspace.Services.GetService<IDocumentNavigationService>();

115
                if (navigationService.CanNavigateToSpan(workspace, firstItem.Document.Id, firstItem.SourceSpan))
116
                {
117 118
                    return navigationService.TryNavigateToSpan(
                        workspace,
119
                        documentId: firstItem.Document.Id,
120
                        textSpan: firstItem.SourceSpan,
121
                        options: options.WithChangedOption(NavigationOptions.PreferProvisionalTab, true));
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
                }
                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())
                {
144
                    presenters.First().Value.DisplayResult(title, navigableItems);
145 146 147 148 149 150 151
                    return true;
                }

                return false;
            }
        }

152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
        public static async Task<IEnumerable<INavigableItem>> FindExternalDefinitionsAsync(Document document, int position, IEnumerable<Lazy<INavigableDefinitionProvider>> externalDefinitionProviders, CancellationToken cancellationToken)
        {
            foreach (var definitionProvider in externalDefinitionProviders)
            {
                var definitions = await definitionProvider.Value.FindDefinitionsAsync(document, position, cancellationToken).ConfigureAwait(false);
                if (definitions != null && definitions.Any())
                {
                    var preferredDefinitions = NavigableItemFactory.GetPreferredNavigableItems(document.Project.Solution, definitions);
                    if (preferredDefinitions.Any())
                    {
                        return preferredDefinitions;
                    }
                }
            }

            return SpecializedCollections.EmptyEnumerable<INavigableItem>();
        }

        public static async Task<IEnumerable<INavigableItem>> FindExternalDefinitionsAsync(ISymbol symbol, Project project, IEnumerable<Lazy<INavigableDefinitionProvider>> externalDefinitionProviders, CancellationToken cancellationToken)
        {
            foreach (var definitionProvider in externalDefinitionProviders)
            {
                var definitions = await definitionProvider.Value.FindDefinitionsAsync(project, symbol, cancellationToken).ConfigureAwait(false);
                if (definitions != null && definitions.Any())
                {
                    var preferredDefinitions = NavigableItemFactory.GetPreferredNavigableItems(project.Solution, definitions);
                    if (preferredDefinitions.Any())
                    {
                        return preferredDefinitions;
                    }
                }
            }

            return SpecializedCollections.EmptyEnumerable<INavigableItem>();
        }

        public static bool TryExternalGoToDefinition(Document document, int position, IEnumerable<Lazy<INavigableDefinitionProvider>> externalDefinitionProviders, IEnumerable<Lazy<INavigableItemsPresenter>> presenters, CancellationToken cancellationToken)
        {
            var externalDefinitions = FindExternalDefinitionsAsync(document, position, externalDefinitionProviders, cancellationToken).WaitAndGetResult(cancellationToken);
            if (externalDefinitions != null && externalDefinitions.Any())
            {
                var itemsArray = externalDefinitions.ToImmutableArrayOrEmpty();
                var title = itemsArray[0].DisplayString;
195
                return TryGoToDefinition(externalDefinitions.ToImmutableArrayOrEmpty(), title, document.Project.Solution.Workspace.Options, presenters, throwOnHiddenDefinition: true);
196 197 198 199 200
            }

            return false;
        }

201
        private static bool TryThirdPartyNavigation(ISymbol symbol, Solution solution)
202 203 204 205 206 207 208 209
        {
            var symbolNavigationService = solution.Workspace.Services.GetService<ISymbolNavigationService>();

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