From 05f93e2387669734f9f556bf134f9598d48d920f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 2 May 2020 17:11:05 -0700 Subject: [PATCH] Extract code into its own file. --- .../FindUsages/AbstractFindUsagesService.cs | 225 ---------------- ...bstractFindUsagesService_FindReferences.cs | 248 ++++++++++++++++++ 2 files changed, 248 insertions(+), 225 deletions(-) create mode 100644 src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService_FindReferences.cs diff --git a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.cs b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.cs index 24079567478..01653d535cb 100644 --- a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.cs +++ b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.cs @@ -103,230 +103,5 @@ public async Task FindImplementationsAsync(Document document, int position, IFin await context.OnDefinitionFoundAsync(definitionItem).ConfigureAwait(false); } } - - public async Task FindReferencesAsync( - Document document, int position, IFindUsagesContext context) - { - var definitionTrackingContext = new DefinitionTrackingContext(context); - - // Need ConfigureAwait(true) here so we get back to the UI thread before calling - // GetThirdPartyDefinitions. We need to call that on the UI thread to match behavior - // of how the language service always worked in the past. - // - // Any async calls before GetThirdPartyDefinitions must be ConfigureAwait(true). - await FindLiteralOrSymbolReferencesAsync( - document, position, definitionTrackingContext).ConfigureAwait(true); - - // After the FAR engine is done call into any third party extensions to see - // if they want to add results. - var thirdPartyDefinitions = GetThirdPartyDefinitions( - document.Project.Solution, definitionTrackingContext.GetDefinitions(), context.CancellationToken); - - // From this point on we can do ConfigureAwait(false) as we're not calling back - // into third parties anymore. - - foreach (var definition in thirdPartyDefinitions) - { - // Don't need ConfigureAwait(true) here - await context.OnDefinitionFoundAsync(definition).ConfigureAwait(false); - } - } - - private async Task FindLiteralOrSymbolReferencesAsync( - Document document, int position, IFindUsagesContext context) - { - // First, see if we're on a literal. If so search for literals in the solution with - // the same value. - var found = await TryFindLiteralReferencesAsync( - document, position, context).ConfigureAwait(false); - if (found) - { - return; - } - - // Wasn't a literal. Try again as a symbol. - await FindSymbolReferencesAsync( - document, position, context).ConfigureAwait(false); - } - - private ImmutableArray GetThirdPartyDefinitions( - Solution solution, - ImmutableArray definitions, - CancellationToken cancellationToken) - { - var factory = solution.Workspace.Services.GetRequiredService(); - return definitions.Select(d => factory.GetThirdPartyDefinitionItem(solution, d, cancellationToken)) - .WhereNotNull() - .ToImmutableArray(); - } - - private async Task FindSymbolReferencesAsync( - Document document, int position, IFindUsagesContext context) - { - var cancellationToken = context.CancellationToken; - cancellationToken.ThrowIfCancellationRequested(); - - // Find the symbol we want to search and the solution we want to search in. - var symbolAndProjectOpt = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync( - document, position, cancellationToken).ConfigureAwait(false); - if (symbolAndProjectOpt == null) - return; - - var (symbol, project) = symbolAndProjectOpt.Value; - - await FindSymbolReferencesAsync( - context, symbol, project).ConfigureAwait(false); - } - - /// - /// Public helper that we use from features like ObjectBrowser which start with a symbol - /// and want to push all the references to it into the Streaming-Find-References window. - /// - public static async Task FindSymbolReferencesAsync( - IFindUsagesContext context, ISymbol symbol, Project project) - { - var solution = project.Solution; - var monikerUsagesService = solution.Workspace.Services.GetRequiredService(); - - await context.SetSearchTitleAsync(string.Format(EditorFeaturesResources._0_references, - FindUsagesHelpers.GetDisplayName(symbol))).ConfigureAwait(false); - - var options = FindReferencesSearchOptions.GetFeatureOptionsForStartingSymbol(symbol); - - // Now call into the underlying FAR engine to find reference. The FAR - // engine will push results into the 'progress' instance passed into it. - // We'll take those results, massage them, and forward them along to the - // FindReferencesContext instance we were given. - var normalFindReferencesTask = FindReferencesAsync( - context, symbol, project, options); - - // Kick off work to search the online code index system in parallel - var codeIndexReferencesTask = FindSymbolMonikerReferencesAsync( - monikerUsagesService, symbol, context); - - await Task.WhenAll(normalFindReferencesTask, codeIndexReferencesTask).ConfigureAwait(false); - } - - public static async Task FindReferencesAsync( - IFindUsagesContext context, - ISymbol symbol, - Project project, - FindReferencesSearchOptions options) - { - var cancellationToken = context.CancellationToken; - var solution = project.Solution; - var client = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false); - if (client != null) - { - // Create a callback that we can pass to the server process to hear about the - // results as it finds them. When we hear about results we'll forward them to - // the 'progress' parameter which will then update the UI. - var serverCallback = new FindUsagesServerCallback(solution, context); - - var success = await client.TryRunRemoteAsync( - WellKnownServiceHubServices.CodeAnalysisService, - nameof(IRemoteFindUsagesService.FindReferencesAsync), - solution, - new object[] - { - SerializableSymbolAndProjectId.Create(symbol, project, cancellationToken), - SerializableFindReferencesSearchOptions.Dehydrate(options), - }, - serverCallback, - cancellationToken).ConfigureAwait(false); - - if (success) - return; - } - - // Couldn't effectively search in OOP. Perform the search in-process. - await FindReferencesInCurrentProcessAsync( - context, symbol, project, options).ConfigureAwait(false); - } - - private static Task FindReferencesInCurrentProcessAsync( - IFindUsagesContext context, - ISymbol symbol, - Project project, - FindReferencesSearchOptions options) - { - var progress = new FindReferencesProgressAdapter(project.Solution, context, options); - return SymbolFinder.FindReferencesAsync( - symbol, project.Solution, progress, documents: null, options, context.CancellationToken); - } - - private async Task TryFindLiteralReferencesAsync( - Document document, int position, IFindUsagesContext context) - { - var cancellationToken = context.CancellationToken; - cancellationToken.ThrowIfCancellationRequested(); - - var syntaxTree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - var syntaxFacts = document.GetRequiredLanguageService(); - - // Currently we only support FAR for numbers, strings and characters. We don't - // bother with true/false/null as those are likely to have way too many results - // to be useful. - var token = await syntaxTree.GetTouchingTokenAsync( - position, - t => syntaxFacts.IsNumericLiteral(t) || - syntaxFacts.IsCharacterLiteral(t) || - syntaxFacts.IsStringLiteral(t), - cancellationToken).ConfigureAwait(false); - - if (token.RawKind == 0) - { - return false; - } - - // Searching for decimals not supported currently. Our index can only store 64bits - // for numeric values, and a decimal won't fit within that. - var tokenValue = token.Value; - if (tokenValue == null || tokenValue is decimal) - return false; - - if (token.Parent is null) - return false; - - var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var symbol = semanticModel.GetSymbolInfo(token.Parent).Symbol ?? semanticModel.GetDeclaredSymbol(token.Parent); - - // Numeric labels are available in VB. In that case we want the normal FAR engine to - // do the searching. For these literals we want to find symbolic results and not - // numeric matches. - if (symbol is ILabelSymbol) - return false; - - // Use the literal to make the title. Trim literal if it's too long. - var title = syntaxFacts.ConvertToSingleLine(token.Parent).ToString(); - if (title.Length >= 10) - { - title = title.Substring(0, 10) + "..."; - } - - var searchTitle = string.Format(EditorFeaturesResources._0_references, title); - await context.SetSearchTitleAsync(searchTitle).ConfigureAwait(false); - - var solution = document.Project.Solution; - - // There will only be one 'definition' that all matching literal reference. - // So just create it now and report to the context what it is. - var definition = DefinitionItem.CreateNonNavigableItem( - ImmutableArray.Create(TextTags.StringLiteral), - ImmutableArray.Create(new TaggedText(TextTags.Text, searchTitle))); - - await context.OnDefinitionFoundAsync(definition).ConfigureAwait(false); - - var progressAdapter = new FindLiteralsProgressAdapter(context, definition); - - // Now call into the underlying FAR engine to find reference. The FAR - // engine will push results into the 'progress' instance passed into it. - // We'll take those results, massage them, and forward them along to the - // FindUsagesContext instance we were given. - await SymbolFinder.FindLiteralReferencesAsync( - tokenValue, Type.GetTypeCode(tokenValue.GetType()), solution, progressAdapter, cancellationToken).ConfigureAwait(false); - - return true; - } } } diff --git a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService_FindReferences.cs b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService_FindReferences.cs new file mode 100644 index 00000000000..591822182ec --- /dev/null +++ b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService_FindReferences.cs @@ -0,0 +1,248 @@ +// 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. + +#nullable enable + +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.FindUsages; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.FindUsages +{ + internal abstract partial class AbstractFindUsagesService + { + public async Task FindReferencesAsync( + Document document, int position, IFindUsagesContext context) + { + var definitionTrackingContext = new DefinitionTrackingContext(context); + + // Need ConfigureAwait(true) here so we get back to the UI thread before calling + // GetThirdPartyDefinitions. We need to call that on the UI thread to match behavior + // of how the language service always worked in the past. + // + // Any async calls before GetThirdPartyDefinitions must be ConfigureAwait(true). + await FindLiteralOrSymbolReferencesAsync( + document, position, definitionTrackingContext).ConfigureAwait(true); + + // After the FAR engine is done call into any third party extensions to see + // if they want to add results. + var thirdPartyDefinitions = GetThirdPartyDefinitions( + document.Project.Solution, definitionTrackingContext.GetDefinitions(), context.CancellationToken); + + // From this point on we can do ConfigureAwait(false) as we're not calling back + // into third parties anymore. + + foreach (var definition in thirdPartyDefinitions) + { + // Don't need ConfigureAwait(true) here + await context.OnDefinitionFoundAsync(definition).ConfigureAwait(false); + } + } + + private async Task FindLiteralOrSymbolReferencesAsync( + Document document, int position, IFindUsagesContext context) + { + // First, see if we're on a literal. If so search for literals in the solution with + // the same value. + var found = await TryFindLiteralReferencesAsync( + document, position, context).ConfigureAwait(false); + if (found) + { + return; + } + + // Wasn't a literal. Try again as a symbol. + await FindSymbolReferencesAsync( + document, position, context).ConfigureAwait(false); + } + + private ImmutableArray GetThirdPartyDefinitions( + Solution solution, + ImmutableArray definitions, + CancellationToken cancellationToken) + { + var factory = solution.Workspace.Services.GetRequiredService(); + return definitions.Select(d => factory.GetThirdPartyDefinitionItem(solution, d, cancellationToken)) + .WhereNotNull() + .ToImmutableArray(); + } + + private async Task FindSymbolReferencesAsync( + Document document, int position, IFindUsagesContext context) + { + var cancellationToken = context.CancellationToken; + cancellationToken.ThrowIfCancellationRequested(); + + // Find the symbol we want to search and the solution we want to search in. + var symbolAndProjectOpt = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync( + document, position, cancellationToken).ConfigureAwait(false); + if (symbolAndProjectOpt == null) + return; + + var (symbol, project) = symbolAndProjectOpt.Value; + + await FindSymbolReferencesAsync( + context, symbol, project).ConfigureAwait(false); + } + + /// + /// Public helper that we use from features like ObjectBrowser which start with a symbol + /// and want to push all the references to it into the Streaming-Find-References window. + /// + public static async Task FindSymbolReferencesAsync( + IFindUsagesContext context, ISymbol symbol, Project project) + { + var solution = project.Solution; + var monikerUsagesService = solution.Workspace.Services.GetRequiredService(); + + await context.SetSearchTitleAsync(string.Format(EditorFeaturesResources._0_references, + FindUsagesHelpers.GetDisplayName(symbol))).ConfigureAwait(false); + + var options = FindReferencesSearchOptions.GetFeatureOptionsForStartingSymbol(symbol); + + // Now call into the underlying FAR engine to find reference. The FAR + // engine will push results into the 'progress' instance passed into it. + // We'll take those results, massage them, and forward them along to the + // FindReferencesContext instance we were given. + var normalFindReferencesTask = FindReferencesAsync( + context, symbol, project, options); + + // Kick off work to search the online code index system in parallel + var codeIndexReferencesTask = FindSymbolMonikerReferencesAsync( + monikerUsagesService, symbol, context); + + await Task.WhenAll(normalFindReferencesTask, codeIndexReferencesTask).ConfigureAwait(false); + } + + public static async Task FindReferencesAsync( + IFindUsagesContext context, + ISymbol symbol, + Project project, + FindReferencesSearchOptions options) + { + var cancellationToken = context.CancellationToken; + var solution = project.Solution; + var client = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false); + if (client != null) + { + // Create a callback that we can pass to the server process to hear about the + // results as it finds them. When we hear about results we'll forward them to + // the 'progress' parameter which will then update the UI. + var serverCallback = new FindUsagesServerCallback(solution, context); + + var success = await client.TryRunRemoteAsync( + WellKnownServiceHubServices.CodeAnalysisService, + nameof(IRemoteFindUsagesService.FindReferencesAsync), + solution, + new object[] + { + SerializableSymbolAndProjectId.Create(symbol, project, cancellationToken), + SerializableFindReferencesSearchOptions.Dehydrate(options), + }, + serverCallback, + cancellationToken).ConfigureAwait(false); + + if (success) + return; + } + + // Couldn't effectively search in OOP. Perform the search in-process. + await FindReferencesInCurrentProcessAsync( + context, symbol, project, options).ConfigureAwait(false); + } + + private static Task FindReferencesInCurrentProcessAsync( + IFindUsagesContext context, + ISymbol symbol, + Project project, + FindReferencesSearchOptions options) + { + var progress = new FindReferencesProgressAdapter(project.Solution, context, options); + return SymbolFinder.FindReferencesAsync( + symbol, project.Solution, progress, documents: null, options, context.CancellationToken); + } + + private async Task TryFindLiteralReferencesAsync( + Document document, int position, IFindUsagesContext context) + { + var cancellationToken = context.CancellationToken; + cancellationToken.ThrowIfCancellationRequested(); + + var syntaxTree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var syntaxFacts = document.GetRequiredLanguageService(); + + // Currently we only support FAR for numbers, strings and characters. We don't + // bother with true/false/null as those are likely to have way too many results + // to be useful. + var token = await syntaxTree.GetTouchingTokenAsync( + position, + t => syntaxFacts.IsNumericLiteral(t) || + syntaxFacts.IsCharacterLiteral(t) || + syntaxFacts.IsStringLiteral(t), + cancellationToken).ConfigureAwait(false); + + if (token.RawKind == 0) + { + return false; + } + + // Searching for decimals not supported currently. Our index can only store 64bits + // for numeric values, and a decimal won't fit within that. + var tokenValue = token.Value; + if (tokenValue == null || tokenValue is decimal) + return false; + + if (token.Parent is null) + return false; + + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var symbol = semanticModel.GetSymbolInfo(token.Parent).Symbol ?? semanticModel.GetDeclaredSymbol(token.Parent); + + // Numeric labels are available in VB. In that case we want the normal FAR engine to + // do the searching. For these literals we want to find symbolic results and not + // numeric matches. + if (symbol is ILabelSymbol) + return false; + + // Use the literal to make the title. Trim literal if it's too long. + var title = syntaxFacts.ConvertToSingleLine(token.Parent).ToString(); + if (title.Length >= 10) + { + title = title.Substring(0, 10) + "..."; + } + + var searchTitle = string.Format(EditorFeaturesResources._0_references, title); + await context.SetSearchTitleAsync(searchTitle).ConfigureAwait(false); + + var solution = document.Project.Solution; + + // There will only be one 'definition' that all matching literal reference. + // So just create it now and report to the context what it is. + var definition = DefinitionItem.CreateNonNavigableItem( + ImmutableArray.Create(TextTags.StringLiteral), + ImmutableArray.Create(new TaggedText(TextTags.Text, searchTitle))); + + await context.OnDefinitionFoundAsync(definition).ConfigureAwait(false); + + var progressAdapter = new FindLiteralsProgressAdapter(context, definition); + + // Now call into the underlying FAR engine to find reference. The FAR + // engine will push results into the 'progress' instance passed into it. + // We'll take those results, massage them, and forward them along to the + // FindUsagesContext instance we were given. + await SymbolFinder.FindLiteralReferencesAsync( + tokenValue, Type.GetTypeCode(tokenValue.GetType()), solution, progressAdapter, cancellationToken).ConfigureAwait(false); + + return true; + } + } +} -- GitLab