// 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.Concurrent; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Navigation; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.FindUsages { internal abstract partial class AbstractFindUsagesService { private class FindLiteralsProgressAdapter : IStreamingFindLiteralReferencesProgress { private readonly IFindUsagesContext _context; private readonly DefinitionItem _definition; public FindLiteralsProgressAdapter( IFindUsagesContext context, DefinitionItem definition) { _context = context; _definition = definition; } public async Task OnReferenceFoundAsync(Document document, TextSpan span) { var documentSpan = await ClassifiedSpansAndHighlightSpan.GetClassifiedDocumentSpanAsync( document, span, _context.CancellationToken).ConfigureAwait(false); await _context.OnReferenceFoundAsync(new SourceReferenceItem( _definition, documentSpan, isWrittenTo: false)).ConfigureAwait(false); } public Task ReportProgressAsync(int current, int maximum) => _context.ReportProgressAsync(current, maximum); } /// /// Forwards IFindReferencesProgress calls to an IFindUsagesContext instance. /// private class FindReferencesProgressAdapter : ForegroundThreadAffinitizedObject, IStreamingFindReferencesProgress { private readonly Solution _solution; private readonly IFindUsagesContext _context; /// /// We will hear about definition symbols many times while performing FAR. We'll /// here about it first when the FAR engine discovers the symbol, and then for every /// reference it finds to the symbol. However, we only want to create and pass along /// a single instance of for that definition no matter /// how many times we see it. /// /// This dictionary allows us to make that mapping once and then keep it around for /// all future callbacks. /// private readonly Dictionary _definitionToItem = new Dictionary(MetadataUnifyingEquivalenceComparer.Instance); private readonly SemaphoreSlim _gate = new SemaphoreSlim(initialCount: 1); public FindReferencesProgressAdapter(Solution solution, IFindUsagesContext context) { _solution = solution; _context = context; } // Do nothing functions. The streaming far service doesn't care about // any of these. public Task OnStartedAsync() => SpecializedTasks.EmptyTask; public Task OnCompletedAsync() => SpecializedTasks.EmptyTask; public Task OnFindInDocumentStartedAsync(Document document) => SpecializedTasks.EmptyTask; public Task OnFindInDocumentCompletedAsync(Document document) => SpecializedTasks.EmptyTask; // Simple context forwarding functions. public Task ReportProgressAsync(int current, int maximum) => _context.ReportProgressAsync(current, maximum); // More complicated forwarding functions. These need to map from the symbols // used by the FAR engine to the INavigableItems used by the streaming FAR // feature. private async Task GetDefinitionItemAsync(SymbolAndProjectId definition) { using (await _gate.DisposableWaitAsync(_context.CancellationToken).ConfigureAwait(false)) { if (!_definitionToItem.TryGetValue(definition.Symbol, out var definitionItem)) { definitionItem = await definition.Symbol.ToClassifiedDefinitionItemAsync( _solution, includeHiddenLocations: false, cancellationToken: _context.CancellationToken).ConfigureAwait(false); _definitionToItem[definition.Symbol] = definitionItem; } return definitionItem; } } public async Task OnDefinitionFoundAsync(SymbolAndProjectId definition) { var definitionItem = await GetDefinitionItemAsync(definition).ConfigureAwait(false); await _context.OnDefinitionFoundAsync(definitionItem).ConfigureAwait(false); } public async Task OnReferenceFoundAsync(SymbolAndProjectId definition, ReferenceLocation location) { // Ignore duplicate locations. We don't want to clutter the UI with them. if (location.IsDuplicateReferenceLocation) { return; } var definitionItem = await GetDefinitionItemAsync(definition).ConfigureAwait(false); var referenceItem = await location.TryCreateSourceReferenceItemAsync( definitionItem, includeHiddenLocations: false, cancellationToken: _context.CancellationToken).ConfigureAwait(false); if (referenceItem != null) { await _context.OnReferenceFoundAsync(referenceItem).ConfigureAwait(false); } } public async Task CallThirdPartyExtensionsAsync(CancellationToken cancellationToken) { var factory = _solution.Workspace.Services.GetService(); foreach (var definition in _definitionToItem.Values) { var item = factory.GetThirdPartyDefinitionItem( _solution, definition, cancellationToken); if (item != null) { // ConfigureAwait(true) because we want to come back on the // same thread after calling into extensions. await _context.OnDefinitionFoundAsync(item).ConfigureAwait(true); } } } } } }