GoToDefinitionHelpers.cs 5.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// 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 Roslyn.Utilities;

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

51
            if (thirdPartyNavigationAllowed && TryThirdPartyNavigation(symbol, solution))
52 53 54 55 56 57 58 59 60 61 62
            {
                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;
            }

63 64
            var options = project.Solution.Workspace.Options;

65 66 67 68 69 70 71 72 73
            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>();
74 75 76 77
                return symbolNavigationService.TryNavigateToSymbol(
                    symbol, project,
                    options: options.WithChangedOption(NavigationOptions.UsePreviewTab, true),
                    cancellationToken: cancellationToken);
78 79 80 81 82 83 84 85 86 87 88
            }

            // 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))
                {
89 90 91 92 93
                    return navigationService.TryNavigateToSpan(
                        workspace,
                        documentId: solution.GetDocument(firstItem.SourceTree).Id,
                        textSpan: firstItem.SourceSpan,
                        options: options.WithChangedOption(NavigationOptions.UsePreviewTab, true));
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
                }
                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())
                {
116
                    presenters.First().Value.DisplayResult(NavigableItemFactory.GetSymbolDisplayString(project, symbol),
117 118 119 120 121 122 123 124 125
                        preferredSourceLocations.Select(location => NavigableItemFactory.GetItemFromSymbolLocation(solution, symbol, location)).ToList());

                    return true;
                }

                return false;
            }
        }

126
        private static bool TryThirdPartyNavigation(ISymbol symbol, Solution solution)
127 128 129 130 131 132 133 134
        {
            var symbolNavigationService = solution.Workspace.Services.GetService<ISymbolNavigationService>();

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