diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/SearchGraphQuery.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/SearchGraphQuery.cs index e69c3f09c26e4437d76d77f8f562c9777c3a85d6..f926980c4841b6a5e1af7426c71474daadf0ff39 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/SearchGraphQuery.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/SearchGraphQuery.cs @@ -7,9 +7,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.FindSymbols; -using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.VisualStudio.GraphModel; using Roslyn.Utilities; @@ -108,36 +106,27 @@ private async Task AddLinkedNodeForMemberAsync(Project project, ISymb internal async Task> FindNavigableSourceSymbolsAsync( Project project, CancellationToken cancellationToken) { - var results = ArrayBuilder.GetInstance(); + var declarations = await DeclarationFinder.FindSourceDeclarationsWithPatternAsync( + project, _searchPattern, SymbolFilter.TypeAndMember, cancellationToken).ConfigureAwait(false); + + var symbols = declarations.SelectAsArray(d => d.Symbol); - // The compiler API only supports a predicate which is given a symbol's name. Because - // we only have the name, and nothing else, we need to check it against the last segment - // of the pattern. i.e. if the pattern is 'Console.WL' and we are given 'WriteLine', then - // we don't want to check the whole pattern against it (as it will clearly fail), instead - // we only want to check the 'WL' portion. Then, after we get all the candidate symbols - // we'll check if the full name matches the full pattern. - var patternMatcher = new PatternMatcher(_searchPattern); - var symbols = await SymbolFinder.FindSourceDeclarationsAsync( - project, k => !patternMatcher.GetMatchesForLastSegmentOfPattern(k).IsDefaultOrEmpty, SymbolFilter.TypeAndMember, cancellationToken).ConfigureAwait(false); - - symbols = symbols.Where(s => - !s.IsConstructor() - && !s.IsStaticConstructor() // not constructors, they get matched on type name - && !(s is INamespaceSymbol) // not namespaces - && s.Locations.Any(loc => loc.IsInSource)); // only source symbols + var results = ArrayBuilder.GetInstance(); foreach (var symbol in symbols) { cancellationToken.ThrowIfCancellationRequested(); - // As an optimization, don't bother getting the container for this symbol if this - // isn't a dotted pattern. Getting the container could cause lots of string - // allocations that we don't if we're never going to check it. - var matches = !patternMatcher.IsDottedPattern - ? new PatternMatches(patternMatcher.GetMatches(GetSearchName(symbol))) - : patternMatcher.GetMatches(GetSearchName(symbol), GetContainer(symbol)); + // Ignore constructors and namespaces. We don't want to expose them through this API. + if (symbol.IsConstructor() || + symbol.IsStaticConstructor() || + symbol is INamespaceSymbol) + { + continue; + } - if (matches.IsEmpty) + // Ignore symbols that have no source location. We don't want to expose them through this API. + if (!symbol.Locations.Any(loc => loc.IsInSource)) { continue; } @@ -168,38 +157,5 @@ private async Task AddLinkedNodeForMemberAsync(Project project, ISymb return results.ToImmutableAndFree(); } - - public static readonly SymbolDisplayFormat DottedNameFormat = - new SymbolDisplayFormat( - globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, - typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, - delegateStyle: SymbolDisplayDelegateStyle.NameOnly, - extensionMethodStyle: SymbolDisplayExtensionMethodStyle.StaticMethod, - propertyStyle: SymbolDisplayPropertyStyle.NameOnly); - - private static string GetContainer(ISymbol symbol) - { - var container = symbol.ContainingSymbol; - if (container == null) - { - return null; - } - - return container.ToDisplayString(DottedNameFormat); - } - - private static string GetSearchName(ISymbol symbol) - { - if (symbol.IsConstructor() || symbol.IsStaticConstructor()) - { - return symbol.ContainingType.Name; - } - else if (symbol.IsIndexer() && symbol.Name == WellKnownMemberNames.Indexer) - { - return "this"; - } - - return symbol.Name; - } } -} +} \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs index fc7d7d57a526a7996c91658db20888cd7961b722..6874c667c720a008f63deaa8caeb94312f9ebd64 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs @@ -7,7 +7,9 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols @@ -82,6 +84,31 @@ internal static partial class DeclarationFinder project, name, ignoreCase, criteria, cancellationToken).ConfigureAwait(false); } + public static async Task> FindSourceDeclarationsWithPatternAsync( + Project project, string pattern, SymbolFilter criteria, CancellationToken cancellationToken) + { + if (project == null) + { + throw new ArgumentNullException(nameof(project)); + } + + if (pattern == null) + { + throw new ArgumentNullException(nameof(pattern)); + } + + var (succeded, results) = await TryFindSourceDeclarationsWithPatternInRemoteProcessAsync( + project, pattern, criteria, cancellationToken).ConfigureAwait(false); + + if (succeded) + { + return results; + } + + return await FindSourceDeclarationsWithPatternInCurrentProcessAsync( + project, pattern, criteria, cancellationToken).ConfigureAwait(false); + } + #endregion #region Remote Dispatch @@ -95,7 +122,7 @@ internal static partial class DeclarationFinder if (session != null) { var result = await session.InvokeAsync( - nameof(IRemoteSymbolFinder.FindSolutionSourceDeclarationsWithNormalQuery), + nameof(IRemoteSymbolFinder.FindSolutionSourceDeclarationsWithNormalQueryAsync), name, ignoreCase, criteria).ConfigureAwait(false); var rehydrated = await RehydrateAsync( @@ -114,7 +141,7 @@ internal static partial class DeclarationFinder if (session != null) { var result = await session.InvokeAsync( - nameof(IRemoteSymbolFinder.FindProjectSourceDeclarationsWithNormalQuery), + nameof(IRemoteSymbolFinder.FindProjectSourceDeclarationsWithNormalQueryAsync), project.Id, name, ignoreCase, criteria).ConfigureAwait(false); var rehydrated = await RehydrateAsync( @@ -126,6 +153,25 @@ internal static partial class DeclarationFinder return (false, ImmutableArray.Empty); } + private static async Task<(bool, ImmutableArray)> TryFindSourceDeclarationsWithPatternInRemoteProcessAsync( + Project project, string pattern, SymbolFilter criteria, CancellationToken cancellationToken) + { + var session = await SymbolFinder.TryGetRemoteSessionAsync(project.Solution, cancellationToken).ConfigureAwait(false); + if (session != null) + { + var result = await session.InvokeAsync( + nameof(IRemoteSymbolFinder.FindProjectSourceDeclarationsWithPatternAsync), + project.Id, pattern, criteria).ConfigureAwait(false); + + var rehydrated = await RehydrateAsync( + project.Solution, result, cancellationToken).ConfigureAwait(false); + + return (true, rehydrated); + } + + return (false, ImmutableArray.Empty); + } + #endregion #region Local processing @@ -159,6 +205,81 @@ internal static partial class DeclarationFinder return list.ToImmutableAndFree(); } + internal static async Task> FindSourceDeclarationsWithPatternInCurrentProcessAsync( + Project project, string pattern, SymbolFilter criteria, CancellationToken cancellationToken) + { + // The compiler API only supports a predicate which is given a symbol's name. Because + // we only have the name, and nothing else, we need to check it against the last segment + // of the pattern. i.e. if the pattern is 'Console.WL' and we are given 'WriteLine', then + // we don't want to check the whole pattern against it (as it will clearly fail), instead + // we only want to check the 'WL' portion. Then, after we get all the candidate symbols + // we'll check if the full name matches the full pattern. + var patternMatcher = new PatternMatcher(pattern); + var query = SearchQuery.CreateCustom( + k => !patternMatcher.GetMatchesForLastSegmentOfPattern(k).IsDefaultOrEmpty); + + var symbolAndProjectIds = await SymbolFinder.FindSourceDeclarationsWithCustomQueryAsync( + project, query, criteria, cancellationToken).ConfigureAwait(false); + + var result = ArrayBuilder.GetInstance(); + + // Now see if the symbols the compiler returned actually match the full pattern. + foreach (var symbolAndProjectId in symbolAndProjectIds) + { + var symbol = symbolAndProjectId.Symbol; + + // As an optimization, don't bother getting the container for this symbol if this + // isn't a dotted pattern. Getting the container could cause lots of string + // allocations that we don't if we're never going to check it. + var matches = !patternMatcher.IsDottedPattern + ? new PatternMatches(patternMatcher.GetMatches(GetSearchName(symbol))) + : patternMatcher.GetMatches(GetSearchName(symbol), GetContainer(symbol)); + + if (matches.IsEmpty) + { + // Didn't actually match the full pattern, ignore it. + continue; + } + + result.Add(symbolAndProjectId); + } + + return result.ToImmutableAndFree(); + } + + private static string GetSearchName(ISymbol symbol) + { + if (symbol.IsConstructor() || symbol.IsStaticConstructor()) + { + return symbol.ContainingType.Name; + } + else if (symbol.IsIndexer() && symbol.Name == WellKnownMemberNames.Indexer) + { + return "this"; + } + + return symbol.Name; + } + + private static string GetContainer(ISymbol symbol) + { + var container = symbol.ContainingSymbol; + if (container == null) + { + return null; + } + + return container.ToDisplayString(DottedNameFormat); + } + + private static readonly SymbolDisplayFormat DottedNameFormat = + new SymbolDisplayFormat( + globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, + delegateStyle: SymbolDisplayDelegateStyle.NameOnly, + extensionMethodStyle: SymbolDisplayExtensionMethodStyle.StaticMethod, + propertyStyle: SymbolDisplayPropertyStyle.NameOnly); + #endregion } } \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinder.cs index fcf5d42a3c20d812142762a6b9f7797b18549672..482c6430e4725c7787e433f177c02b8a4e2e294d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinder.cs @@ -13,10 +13,13 @@ internal interface IRemoteSymbolFinder Task FindAllDeclarationsWithNormalQueryAsync( ProjectId projectId, string name, SearchKind searchKind, SymbolFilter criteria); - Task FindSolutionSourceDeclarationsWithNormalQuery( + Task FindSolutionSourceDeclarationsWithNormalQueryAsync( string name, bool ignoreCase, SymbolFilter criteria); - Task FindProjectSourceDeclarationsWithNormalQuery( + Task FindProjectSourceDeclarationsWithNormalQueryAsync( ProjectId projectId, string name, bool ignoreCase, SymbolFilter criteria); + + Task FindProjectSourceDeclarationsWithPatternAsync( + ProjectId projectId, string pattern, SymbolFilter criteria); } } \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Declarations_CustomQueries.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Declarations_CustomQueries.cs index 14a18522a491ec3824a4597501444fe7032c61f8..0b21f95bbab5ecde69cb215e6fdbe95a0eb8de5f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Declarations_CustomQueries.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Declarations_CustomQueries.cs @@ -80,7 +80,7 @@ public static async Task> FindSourceDeclarationsAsync(Proje return declarations.SelectAsArray(d => d.Symbol); } - private static async Task> FindSourceDeclarationsWithCustomQueryAsync( + internal static async Task> FindSourceDeclarationsWithCustomQueryAsync( Project project, SearchQuery query, SymbolFilter filter, CancellationToken cancellationToken) { if (project == null) diff --git a/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysisService_SymbolFinder.cs b/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysisService_SymbolFinder.cs index 5b2a7f92fbc17ff0bc77088a48ecb872025674af..5cb222f3951b5e067b2ec457310b9e0d562fe436 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysisService_SymbolFinder.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysisService_SymbolFinder.cs @@ -46,7 +46,7 @@ public async Task FindLiteralReferencesAsync(object value) return result.Select(SerializableSymbolAndProjectId.Dehydrate).ToArray(); } - public async Task FindSolutionSourceDeclarationsWithNormalQuery( + public async Task FindSolutionSourceDeclarationsWithNormalQueryAsync( string name, bool ignoreCase, SymbolFilter criteria) { var solution = await GetSolutionAsync().ConfigureAwait(false); @@ -56,7 +56,7 @@ public async Task FindLiteralReferencesAsync(object value) return result.Select(SerializableSymbolAndProjectId.Dehydrate).ToArray(); } - public async Task FindProjectSourceDeclarationsWithNormalQuery( + public async Task FindProjectSourceDeclarationsWithNormalQueryAsync( ProjectId projectId, string name, bool ignoreCase, SymbolFilter criteria) { var solution = await GetSolutionAsync().ConfigureAwait(false); @@ -68,6 +68,18 @@ public async Task FindLiteralReferencesAsync(object value) return result.Select(SerializableSymbolAndProjectId.Dehydrate).ToArray(); } + public async Task FindProjectSourceDeclarationsWithPatternAsync( + ProjectId projectId, string pattern, SymbolFilter criteria) + { + var solution = await GetSolutionAsync().ConfigureAwait(false); + var project = solution.GetProject(projectId); + + var result = await DeclarationFinder.FindSourceDeclarationsWithPatternInCurrentProcessAsync( + project, pattern, criteria, CancellationToken).ConfigureAwait(false); + + return result.Select(SerializableSymbolAndProjectId.Dehydrate).ToArray(); + } + private class FindLiteralReferencesProgressCallback : IStreamingFindLiteralReferencesProgress { private readonly CodeAnalysisService _service;