// 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);
}
}
}
}
}
}