From e0747c42b72ea68c02afbb147f3c8d946597a906 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 2 May 2020 11:52:14 -0700 Subject: [PATCH] Move more of the find-references user-facing operation OOP. --- ...stractFindUsagesService.ProgressAdapter.cs | 7 +- ...AbstractFindUsagesService.SymbolMoniker.cs | 5 +- .../FindUsages/AbstractFindUsagesService.cs | 70 +++++-- .../Core/FindUsages/FindUsagesHelpers.cs | 12 +- .../FindUsages/IRemoteFindUsagesService.cs | 185 ++++++++++++++++++ .../Core/GoToBase/AbstractGoToBaseService.cs | 10 +- .../Portable/FindUsages/DefinitionItem.cs | 1 + .../AbstractObjectBrowserLibraryManager.cs | 11 +- .../CodeAnalysisService_FindUsages.cs | 125 ++++++++++++ 9 files changed, 388 insertions(+), 38 deletions(-) create mode 100644 src/EditorFeatures/Core/FindUsages/IRemoteFindUsagesService.cs create mode 100644 src/Workspaces/Remote/ServiceHub/Services/CodeAnalysisService_FindUsages.cs diff --git a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs index 2563ced0e50..0ccbc0881c0 100644 --- a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs +++ b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Navigation; @@ -49,7 +48,7 @@ public async Task OnReferenceFoundAsync(Document document, TextSpan span) /// /// Forwards IFindReferencesProgress calls to an IFindUsagesContext instance. /// - private class FindReferencesProgressAdapter : ForegroundThreadAffinitizedObject, IStreamingFindReferencesProgress + private class FindReferencesProgressAdapter : IStreamingFindReferencesProgress { private readonly Solution _solution; private readonly IFindUsagesContext _context; @@ -74,9 +73,7 @@ public IStreamingProgressTracker ProgressTracker => _context.ProgressTracker; public FindReferencesProgressAdapter( - IThreadingContext threadingContext, Solution solution, - IFindUsagesContext context, FindReferencesSearchOptions options) - : base(threadingContext) + Solution solution, IFindUsagesContext context, FindReferencesSearchOptions options) { _solution = solution; _context = context; diff --git a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.SymbolMoniker.cs b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.SymbolMoniker.cs index e8406acfc7a..a3f037c32ba 100644 --- a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.SymbolMoniker.cs +++ b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.SymbolMoniker.cs @@ -21,8 +21,7 @@ internal abstract partial class AbstractFindUsagesService private static async Task FindSymbolMonikerReferencesAsync( IFindSymbolMonikerUsagesService monikerUsagesService, ISymbol definition, - IFindUsagesContext context, - CancellationToken cancellationToken) + IFindUsagesContext context) { var moniker = SymbolMoniker.TryCreate(definition); if (moniker == null) @@ -42,7 +41,7 @@ internal abstract partial class AbstractFindUsagesService var monikers = ImmutableArray.Create(moniker); var first = true; - await foreach (var referenceItem in monikerUsagesService.FindReferencesByMoniker(definitionItem, monikers, cancellationToken)) + await foreach (var referenceItem in monikerUsagesService.FindReferencesByMoniker(definitionItem, monikers, context.CancellationToken)) { if (first) { diff --git a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.cs b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.cs index 6848cdbbb14..bfc4e9c3a4e 100644 --- a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.cs +++ b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -121,17 +122,15 @@ protected AbstractFindUsagesService(IThreadingContext threadingContext) cancellationToken.ThrowIfCancellationRequested(); // Find the symbol we want to search and the solution we want to search in. - var symbolAndSolutionOpt = await FindUsagesHelpers.GetRelevantSymbolAndSolutionAtPositionAsync( + var symbolAndProjectOpt = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync( document, position, cancellationToken).ConfigureAwait(false); - if (symbolAndSolutionOpt == null) + if (symbolAndProjectOpt == null) return; - var (symbol, solution) = symbolAndSolutionOpt.Value; + var (symbol, project) = symbolAndProjectOpt.Value; await FindSymbolReferencesAsync( - _threadingContext, context, - symbol, solution, - cancellationToken).ConfigureAwait(false); + context, symbol, project).ConfigureAwait(false); } /// @@ -139,9 +138,9 @@ protected AbstractFindUsagesService(IThreadingContext threadingContext) /// and want to push all the references to it into the Streaming-Find-References window. /// public static async Task FindSymbolReferencesAsync( - IThreadingContext threadingContext, IFindUsagesContext context, - ISymbol symbol, Solution solution, CancellationToken cancellationToken) + IFindUsagesContext context, ISymbol symbol, Project project) { + var solution = project.Solution; var monikerUsagesService = solution.Workspace.Services.GetRequiredService(); await context.SetSearchTitleAsync(string.Format(EditorFeaturesResources._0_references, @@ -153,17 +152,64 @@ protected AbstractFindUsagesService(IThreadingContext threadingContext) // 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 progress = new FindReferencesProgressAdapter(threadingContext, solution, context, options); - var normalFindReferencesTask = SymbolFinder.FindReferencesAsync( - symbol, solution, progress, documents: null, options, cancellationToken); + 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, cancellationToken); + 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 FindReferencesServerCallback(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-proc. + 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) { diff --git a/src/EditorFeatures/Core/FindUsages/FindUsagesHelpers.cs b/src/EditorFeatures/Core/FindUsages/FindUsagesHelpers.cs index b80755df56e..2ee5022088b 100644 --- a/src/EditorFeatures/Core/FindUsages/FindUsagesHelpers.cs +++ b/src/EditorFeatures/Core/FindUsages/FindUsagesHelpers.cs @@ -31,7 +31,7 @@ public static string GetDisplayName(ISymbol symbol) /// there may be symbol mapping involved (for example in Metadata-As-Source /// scenarios). /// - public static async Task<(ISymbol symbol, Solution solution)?> GetRelevantSymbolAndSolutionAtPositionAsync( + public static async Task<(ISymbol symbol, Project project)?> GetRelevantSymbolAndProjectAtPositionAsync( Document document, int position, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -49,19 +49,19 @@ public static string GetDisplayName(ISymbol symbol) if (mapping == null) return null; - return (mapping.Symbol, mapping.Project.Solution); + return (mapping.Symbol, mapping.Project); } public static async Task<(Solution solution, ISymbol symbol, ImmutableArray implementations, string message)?> FindSourceImplementationsAsync(Document document, int position, CancellationToken cancellationToken) { - var symbolAndSolutionOpt = await GetRelevantSymbolAndSolutionAtPositionAsync( + var symbolAndProjectOpt = await GetRelevantSymbolAndProjectAtPositionAsync( document, position, cancellationToken).ConfigureAwait(false); - if (symbolAndSolutionOpt == null) + if (symbolAndProjectOpt == null) return null; - var (symbol, solution) = symbolAndSolutionOpt.Value; + var (symbol, project) = symbolAndProjectOpt.Value; return await FindSourceImplementationsAsync( - solution, symbol, cancellationToken).ConfigureAwait(false); + project.Solution, symbol, cancellationToken).ConfigureAwait(false); } private static async Task<(Solution solution, ISymbol symbol, ImmutableArray implementations, string message)?> FindSourceImplementationsAsync( diff --git a/src/EditorFeatures/Core/FindUsages/IRemoteFindUsagesService.cs b/src/EditorFeatures/Core/FindUsages/IRemoteFindUsagesService.cs new file mode 100644 index 00000000000..7f3970a2979 --- /dev/null +++ b/src/EditorFeatures/Core/FindUsages/IRemoteFindUsagesService.cs @@ -0,0 +1,185 @@ +// 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. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.FindUsages; +using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.FindUsages +{ + internal interface IRemoteFindUsagesService + { + Task FindReferencesAsync( + PinnedSolutionInfo solutionInfo, + SerializableSymbolAndProjectId symbolAndProjectIdArg, + SerializableFindReferencesSearchOptions options, + CancellationToken cancellationToken); + } + + internal class FindReferencesServerCallback + { + private readonly Solution _solution; + private readonly IFindUsagesContext _context; + private readonly Dictionary _idToDefinition = new Dictionary(); + + public FindReferencesServerCallback(Solution solution, IFindUsagesContext context) + { + _solution = solution; + _context = context; + } + + public Task AddItemsAsync(int count) + => _context.ProgressTracker.AddItemsAsync(count); + + public Task ItemCompletedAsync() + => _context.ProgressTracker.ItemCompletedAsync(); + + public Task ReportMessageAsync(string message) + => _context.ReportMessageAsync(message); + + [Obsolete] + public Task ReportProgressAsync(int current, int maximum) + => _context.ReportProgressAsync(current, maximum); + + public Task SetSearchTitleAsync(string title) + => _context.SetSearchTitleAsync(title); + + public Task OnDefinitionFoundAsync(SerializableDefinitionItem definition) + { + var id = definition.Id; + var rehydrated = definition.Rehydrate(_solution); + + lock (_idToDefinition) + { + _idToDefinition.Add(id, rehydrated); + } + + return _context.OnDefinitionFoundAsync(rehydrated); + } + + public Task OnReferenceFoundAsync(SerializableSourceReferenceItem reference) + => _context.OnReferenceFoundAsync(reference.Rehydrate(_solution, GetDefinition(reference.DefinitionId))); + + private DefinitionItem GetDefinition(int definitionId) + { + lock (_idToDefinition) + { + Contract.ThrowIfFalse(_idToDefinition.ContainsKey(definitionId)); + return _idToDefinition[definitionId]; + } + } + } + + internal class SerializableDocumentSpan + { + public DocumentId DocumentId; + public TextSpan SourceSpan; + + public static SerializableDocumentSpan Dehydrate(DocumentSpan documentSpan) + => new SerializableDocumentSpan + { + DocumentId = documentSpan.Document.Id, + SourceSpan = documentSpan.SourceSpan, + }; + + public DocumentSpan Rehydrate(Solution solution) + => new DocumentSpan(solution.GetDocument(DocumentId), SourceSpan); + } + + internal class SerializableTaggedText + { + public string Tag; + public string Text; + public TaggedTextStyle Style; + public string NavigationTarget; + public string NavigationHint; + + public static SerializableTaggedText Dehydrate(TaggedText text) + => new SerializableTaggedText + { + Tag = text.Tag, + Text = text.Text, + Style = text.Style, + NavigationTarget = text.NavigationTarget, + NavigationHint = text.NavigationHint, + }; + + public TaggedText Rehydrate() + => new TaggedText(Tag, Text, Style, NavigationTarget, NavigationHint); + } + + internal class SerializableDefinitionItem + { + public int Id; + public string[] Tags; + public SerializableTaggedText[] DisplayParts; + public SerializableTaggedText[] NameDisplayParts; + public SerializableTaggedText[] OriginationParts; + public SerializableDocumentSpan[] SourceSpans; + public (string key, string value)[] Properties; + public (string key, string value)[] DisplayableProperties; + public bool DisplayIfNoReferences; + + public static SerializableDefinitionItem Dehydrate(int id, DefinitionItem item) + => new SerializableDefinitionItem + { + Id = id, + Tags = item.Tags.ToArray(), + DisplayParts = item.DisplayParts.Select(p => SerializableTaggedText.Dehydrate(p)).ToArray(), + NameDisplayParts = item.NameDisplayParts.Select(p => SerializableTaggedText.Dehydrate(p)).ToArray(), + OriginationParts = item.OriginationParts.Select(p => SerializableTaggedText.Dehydrate(p)).ToArray(), + SourceSpans = item.SourceSpans.Select(ss => SerializableDocumentSpan.Dehydrate(ss)).ToArray(), + Properties = item.Properties.Select(kvp => (kvp.Key, kvp.Value)).ToArray(), + DisplayableProperties = item.DisplayableProperties.Select(kvp => (kvp.Key, kvp.Value)).ToArray(), + DisplayIfNoReferences = item.DisplayIfNoReferences, + }; + + public DefinitionItem Rehydrate(Solution solution) + => new DefinitionItem.DefaultDefinitionItem( + Tags.ToImmutableArray(), + DisplayParts.SelectAsArray(dp => dp.Rehydrate()), + NameDisplayParts.SelectAsArray(dp => dp.Rehydrate()), + OriginationParts.SelectAsArray(dp => dp.Rehydrate()), + SourceSpans.SelectAsArray(ss => ss.Rehydrate(solution)), + Properties.ToImmutableDictionary(t => t.key, t => t.value), + DisplayableProperties.ToImmutableDictionary(t => t.key, t => t.value), + DisplayIfNoReferences); + } + + internal class SerializableSourceReferenceItem + { + public int DefinitionId; + public SerializableDocumentSpan SourceSpan; + public SymbolUsageInfo SymbolUsageInfo; + public (string Key, string Value)[] AdditionalProperties; + + public static SerializableSourceReferenceItem Dehydrate( + int definitionId, SourceReferenceItem item) + { + return new SerializableSourceReferenceItem + { + DefinitionId = definitionId, + SourceSpan = SerializableDocumentSpan.Dehydrate(item.SourceSpan), + SymbolUsageInfo = item.SymbolUsageInfo, + AdditionalProperties = item.AdditionalProperties.Select(kvp => (kvp.Key, kvp.Value)).ToArray(), + }; + } + + public SourceReferenceItem Rehydrate(Solution solution, DefinitionItem definition) + { + return new SourceReferenceItem( + definition, + SourceSpan.Rehydrate(solution), + SymbolUsageInfo, + AdditionalProperties.ToImmutableDictionary(t => t.Key, t => t.Value)); + } + } +} diff --git a/src/EditorFeatures/Core/GoToBase/AbstractGoToBaseService.cs b/src/EditorFeatures/Core/GoToBase/AbstractGoToBaseService.cs index 89294028738..9113774907e 100644 --- a/src/EditorFeatures/Core/GoToBase/AbstractGoToBaseService.cs +++ b/src/EditorFeatures/Core/GoToBase/AbstractGoToBaseService.cs @@ -16,20 +16,20 @@ internal abstract partial class AbstractGoToBaseService : IGoToBaseService public async Task FindBasesAsync(Document document, int position, IFindUsagesContext context) { var cancellationToken = context.CancellationToken; - var symbolAndSolutionOpt = await FindUsagesHelpers.GetRelevantSymbolAndSolutionAtPositionAsync( + var symbolAndProjectOpt = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync( document, position, cancellationToken).ConfigureAwait(false); - if (symbolAndSolutionOpt == null) + if (symbolAndProjectOpt == null) { await context.ReportMessageAsync( EditorFeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret).ConfigureAwait(false); return; } - var (symbol, solution) = symbolAndSolutionOpt.Value; + var (symbol, project) = symbolAndProjectOpt.Value; - var bases = FindBaseHelpers.FindBases( - symbol, solution, cancellationToken); + var solution = project.Solution; + var bases = FindBaseHelpers.FindBases(symbol, solution, cancellationToken); await context.SetSearchTitleAsync( string.Format(EditorFeaturesResources._0_bases, diff --git a/src/Features/Core/Portable/FindUsages/DefinitionItem.cs b/src/Features/Core/Portable/FindUsages/DefinitionItem.cs index 5c6f39552c2..1ef090d8ca6 100644 --- a/src/Features/Core/Portable/FindUsages/DefinitionItem.cs +++ b/src/Features/Core/Portable/FindUsages/DefinitionItem.cs @@ -152,6 +152,7 @@ internal abstract partial class DefinitionItem Contract.ThrowIfFalse(Properties.ContainsKey(MetadataSymbolOriginatingProjectIdDebugName)); } } + public abstract bool CanNavigateTo(Workspace workspace); public abstract bool TryNavigateTo(Workspace workspace, NavigationBehavior navigationBehavior); diff --git a/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs b/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs index c178ab77d6d..e2207266b48 100644 --- a/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs +++ b/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs @@ -528,7 +528,7 @@ protected override bool TryExec(Guid commandGroup, uint commandId) // thread. await Task.Run(async () => { - await FindReferencesAsync(_threadingContext, symbolListItem, project, context, cancellationToken).ConfigureAwait(false); + await FindReferencesAsync(_threadingContext, symbolListItem, project, context).ConfigureAwait(false); }, cancellationToken).ConfigureAwait(false); // Note: we don't need to put this in a finally. The only time we might not hit @@ -546,15 +546,12 @@ protected override bool TryExec(Guid commandGroup, uint commandId) } } - private static async Task FindReferencesAsync(IThreadingContext threadingContext, SymbolListItem symbolListItem, Project project, CodeAnalysis.FindUsages.FindUsagesContext context, CancellationToken cancellationToken) + private static async Task FindReferencesAsync(IThreadingContext threadingContext, SymbolListItem symbolListItem, Project project, CodeAnalysis.FindUsages.FindUsagesContext context) { - var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + var compilation = await project.GetCompilationAsync(context.CancellationToken).ConfigureAwait(false); var symbol = symbolListItem.ResolveSymbol(compilation); if (symbol != null) - { - await AbstractFindUsagesService.FindSymbolReferencesAsync( - threadingContext, context, symbol, project.Solution, cancellationToken).ConfigureAwait(false); - } + await AbstractFindUsagesService.FindSymbolReferencesAsync(context, symbol, project).ConfigureAwait(false); } } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysisService_FindUsages.cs b/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysisService_FindUsages.cs new file mode 100644 index 00000000000..faf7057c258 --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysisService_FindUsages.cs @@ -0,0 +1,125 @@ +// 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. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.FindUsages; +using Microsoft.CodeAnalysis.FindUsages; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Remote +{ + // root level service for all Roslyn services + internal partial class CodeAnalysisService : IRemoteFindUsagesService + { + public Task FindReferencesAsync(PinnedSolutionInfo solutionInfo, + SerializableSymbolAndProjectId symbolAndProjectIdArg, + SerializableFindReferencesSearchOptions options, + CancellationToken cancellationToken) + { + return RunServiceAsync(async () => + { + using (UserOperationBooster.Boost()) + { + var solution = await GetSolutionAsync(solutionInfo, cancellationToken).ConfigureAwait(false); + + var symbol = await symbolAndProjectIdArg.TryRehydrateAsync( + solution, cancellationToken).ConfigureAwait(false); + var project = solution.GetProject(symbolAndProjectIdArg.ProjectId); + + var context = new RemoteFindUsageContext(solution, EndPoint, cancellationToken); + + if (symbol == null) + return; + + await AbstractFindUsagesService.FindReferencesAsync( + context, symbol, project, options.Rehydrate()).ConfigureAwait(false); + } + }, cancellationToken); + } + + private class RemoteFindUsageContext : IFindUsagesContext, IStreamingProgressTracker + { + private readonly Solution _solution; + private readonly RemoteEndPoint _endPoint; + private readonly Dictionary _definitionItemToId = new Dictionary(); + + public CancellationToken CancellationToken { get; } + + public RemoteFindUsageContext(Solution solution, RemoteEndPoint endPoint, CancellationToken cancellationToken) + { + _solution = solution; + _endPoint = endPoint; + CancellationToken = cancellationToken; + } + + #region IStreamingProgressTracker + + public Task AddItemsAsync(int count) + => _endPoint.InvokeAsync(nameof(AddItemsAsync), new object[] { count }, CancellationToken); + + public Task ItemCompletedAsync() + => _endPoint.InvokeAsync(nameof(ItemCompletedAsync), Array.Empty(), CancellationToken); + + #endregion + + #region IFindUsagesContext + + public IStreamingProgressTracker ProgressTracker => this; + + public Task ReportMessageAsync(string message) + => _endPoint.InvokeAsync(nameof(ReportMessageAsync), new object[] { message }, CancellationToken); + + public Task ReportProgressAsync(int current, int maximum) + => _endPoint.InvokeAsync(nameof(ReportProgressAsync), new object[] { current, maximum }, CancellationToken); + + public Task SetSearchTitleAsync(string title) + => _endPoint.InvokeAsync(nameof(SetSearchTitleAsync), new object[] { title }, CancellationToken); + + public Task OnExternalReferenceFoundAsync(ExternalReferenceItem reference) + => throw ExceptionUtilities.Unreachable; + + public Task OnDefinitionFoundAsync(DefinitionItem definition) + { + var id = GetOrAddDefinitionItemId(definition); + return _endPoint.InvokeAsync(nameof(OnDefinitionFoundAsync), + new object[] + { + SerializableDefinitionItem.Dehydrate(id, definition), + }, + CancellationToken); + } + + private int GetOrAddDefinitionItemId(DefinitionItem item) + { + lock (_definitionItemToId) + { + if (!_definitionItemToId.TryGetValue(item, out var id)) + { + id = _definitionItemToId.Count; + _definitionItemToId.Add(item, id); + } + + return id; + } + } + + public Task OnReferenceFoundAsync(SourceReferenceItem reference) + { + var definitionItem = GetOrAddDefinitionItemId(reference.Definition); + return _endPoint.InvokeAsync(nameof(OnReferenceFoundAsync), + new object[] + { + SerializableSourceReferenceItem.Dehydrate(definitionItem, reference), + }, + CancellationToken); + } + + #endregion + } + } +} -- GitLab