AbstractGoToDefinitionService.cs 6.9 KB
Newer Older
J
Jonathon Marolf 已提交
1 2 3
// 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.
4

5
using System;
6
using System.Collections.Generic;
7
using System.Collections.Immutable;
8 9 10
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
11
using Microsoft.CodeAnalysis.Editor.FindUsages;
12 13
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.LanguageServices;
14
using Microsoft.CodeAnalysis.Navigation;
15 16 17
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;

18
namespace Microsoft.CodeAnalysis.Editor.GoToDefinition
19
{
I
isc30 已提交
20
    // GoToDefinition
21 22
    internal abstract class AbstractGoToDefinitionService : IGoToDefinitionService
    {
23 24 25 26 27
        /// <summary>
        /// Used to present go to definition results in <see cref="TryGoToDefinition(Document, int, CancellationToken)"/>
        /// This is lazily created as the LSP server only calls <see cref="FindDefinitionsAsync(Document, int, CancellationToken)"/>
        /// and therefore never needs to construct the presenter.
        /// </summary>
28
        private readonly Lazy<IStreamingFindUsagesPresenter> _streamingPresenter;
29

30
        protected AbstractGoToDefinitionService(Lazy<IStreamingFindUsagesPresenter> streamingPresenter)
31
        {
32
            _streamingPresenter = streamingPresenter;
33 34
        }

35 36
        public async Task<IEnumerable<INavigableItem>> FindDefinitionsAsync(
            Document document, int position, CancellationToken cancellationToken)
37
        {
R
Ravi Chande 已提交
38
            var symbolService = document.GetLanguageService<IGoToDefinitionSymbolService>();
C
Cyrus Najmabadi 已提交
39
            var (symbol, _) = await symbolService.GetSymbolAndBoundSpanAsync(document, position, includeType: true, cancellationToken).ConfigureAwait(false);
40

41
            // Try to compute source definitions from symbol.
42
            var items = symbol != null
43
                ? NavigableItemFactory.GetItemsFromPreferredSourceLocations(document.Project.Solution, symbol, displayTaggedParts: null, cancellationToken: cancellationToken)
44
                : null;
45

46 47
            // realize the list here so that the consumer await'ing the result doesn't lazily cause
            // them to be created on an inappropriate thread.
48
            return items?.ToList();
49 50 51 52
        }

        public bool TryGoToDefinition(Document document, int position, CancellationToken cancellationToken)
        {
53
            // Try to compute the referenced symbol and attempt to go to definition for the symbol.
R
Ravi Chande 已提交
54
            var symbolService = document.GetLanguageService<IGoToDefinitionSymbolService>();
I
isc30 已提交
55
            var (symbol, _) = symbolService.GetSymbolAndBoundSpanAsync(document, position, includeType: true, cancellationToken).WaitAndGetResult(cancellationToken);
56
            if (symbol is null)
C
CyrusNajmabadi 已提交
57
                return false;
58 59 60 61

            var remapped = TryRemapIfAlreadyOnDefinition(document, position, symbol, cancellationToken);
            if (remapped)
                return true;
62

C
CyrusNajmabadi 已提交
63 64 65 66
            var isThirdPartyNavigationAllowed = IsThirdPartyNavigationAllowed(symbol, position, document, cancellationToken);

            return GoToDefinitionHelpers.TryGoToDefinition(symbol,
                document.Project,
67
                _streamingPresenter.Value,
C
CyrusNajmabadi 已提交
68 69
                thirdPartyNavigationAllowed: isThirdPartyNavigationAllowed,
                cancellationToken: cancellationToken);
70 71
        }

72 73 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
        private bool TryRemapIfAlreadyOnDefinition(
            Document document, int position, 
            ISymbol symbol, CancellationToken cancellationToken)
        {
            var project = document.Project;
            var solution = project.Solution;

            // if the symbol only has a single source location, and we're already on it,
            // try to see if there's a better symbol we could navigate to.
            var sourceLocations = symbol.Locations.WhereAsArray(loc => loc.IsInSource);
            if (sourceLocations.Length != 1)
                return false;

            var definitionLocation = sourceLocations[0];
            if (!definitionLocation.SourceSpan.IntersectsWith(position))
                return false;

            var definitionTree = definitionLocation.SourceTree;
            var definitionDocument = solution.GetDocument(definitionTree);
            if (definitionDocument != document)
                return false;

            // Ok, found a match.  Look for better symbols we could show results for instead.
            var interfaceImpls = symbol.ExplicitOrImplicitInterfaceImplementations();
            if (interfaceImpls.Length == 0)
                return false;

            var definitions = interfaceImpls.SelectMany(
                i => GoToDefinitionHelpers.GetDefinitions(
                    i, project, thirdPartyNavigationAllowed: false, cancellationToken)).ToImmutableArray();

            var title = string.Format(EditorFeaturesResources._0_implemented_members,
                FindUsagesHelpers.GetDisplayName(symbol));

            return _streamingPresenter.Value.TryNavigateToOrPresentItemsAsync(
                solution.Workspace, title, definitions).WaitAndGetResult(cancellationToken);
        }

110
        private static bool IsThirdPartyNavigationAllowed(ISymbol symbolToNavigateTo, int caretPosition, Document document, CancellationToken cancellationToken)
111
        {
C
CyrusNajmabadi 已提交
112
            var syntaxRoot = document.GetSyntaxRootSynchronously(cancellationToken);
113 114 115 116 117 118
            var syntaxFactsService = document.GetLanguageService<ISyntaxFactsService>();
            var containingTypeDeclaration = syntaxFactsService.GetContainingTypeDeclaration(syntaxRoot, caretPosition);

            if (containingTypeDeclaration != null)
            {
                var semanticModel = document.GetSemanticModelAsync(cancellationToken).WaitAndGetResult(cancellationToken);
119 120 121 122

                // Allow third parties to navigate to all symbols except types/constructors
                // if we are navigating from the corresponding type.

C
CyrusNajmabadi 已提交
123
                if (semanticModel.GetDeclaredSymbol(containingTypeDeclaration, cancellationToken) is ITypeSymbol containingTypeSymbol &&
124 125 126 127 128 129
                    (symbolToNavigateTo is ITypeSymbol || symbolToNavigateTo.IsConstructor()))
                {
                    var candidateTypeSymbol = symbolToNavigateTo is ITypeSymbol
                        ? symbolToNavigateTo
                        : symbolToNavigateTo.ContainingType;

130
                    if (Equals(containingTypeSymbol, candidateTypeSymbol))
131 132 133 134 135 136 137
                    {
                        // 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;
                    }
                }
138 139
            }

140
            return true;
141 142 143
        }
    }
}