|
// 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 disable
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.Commanding;
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands.Navigation;
using Microsoft.VisualStudio.Utilities;
using Roslyn.Utilities;
using VSCommanding = Microsoft.VisualStudio.Commanding;
namespace Microsoft.CodeAnalysis.Editor.Implementation.NavigationCommandHandlers
{
[Export(typeof(VSCommanding.ICommandHandler))]
[ContentType(ContentTypeNames.RoslynContentType)]
[Name(nameof(FindReferencesOfOverloadsCommandHandler))]
internal sealed class FindReferencesOfOverloadsCommandHandler :
AbstractNavigationCommandHandler<FindReferencesOfOverloadsCommandArgs>
{
private readonly IAsynchronousOperationListener _asyncListener;
private readonly IThreadingContext _threadingContext;
public override string DisplayName => nameof(FindReferencesOfOverloadsCommandHandler);
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public FindReferencesOfOverloadsCommandHandler(
[ImportMany] IEnumerable<Lazy<IStreamingFindUsagesPresenter>> streamingPresenters,
IAsynchronousOperationListenerProvider listenerProvider,
IThreadingContext threadingContext)
: base(streamingPresenters)
{
Contract.ThrowIfNull(streamingPresenters);
Contract.ThrowIfNull(listenerProvider);
Contract.ThrowIfNull(threadingContext);
_asyncListener = listenerProvider.GetListener(FeatureAttribute.FindReferences);
_threadingContext = threadingContext;
}
protected override bool TryExecuteCommand(int caretPosition, Document document, CommandExecutionContext context)
{
var streamingService = document.GetLanguageService<IFindUsagesService>();
var streamingPresenter = GetStreamingPresenter();
//See if we're running on a host that can provide streaming results.
// We'll both need a FAR service that can stream results to us, and
// a presenter that can accept streamed results.
if (streamingService != null && streamingPresenter != null)
{
_ = StreamingFindReferencesAsync(document, caretPosition, streamingPresenter);
return true;
}
return false;
}
private static async Task<ISymbol[]> GatherSymbolsAsync(ISymbol symbol, Microsoft.CodeAnalysis.Solution solution, CancellationToken token)
{
var implementations = await SymbolFinder.FindImplementationsAsync(symbol, solution, null, token).ConfigureAwait(false);
var result = new ISymbol[implementations.Count() + 1];
result[0] = symbol;
var i = 1;
foreach (var item in implementations)
{
result[i++] = item;
}
return result;
}
private async Task StreamingFindReferencesAsync(
Document document, int caretPosition, IStreamingFindUsagesPresenter presenter)
{
try
{
// first, let's see if we even have a comment, otherwise there's no use in starting a search
var relevantSymbol = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync(document, caretPosition, new CancellationToken()).ConfigureAwait(false);
var symbol = relevantSymbol?.symbol;
if (symbol == null)
return; // would be useful if we could notify the user why we didn't do anything
// maybe using something like an info bar?
var findUsagesService = document.GetLanguageService<IFindUsagesService>();
using var token = _asyncListener.BeginAsyncOperation(nameof(StreamingFindReferencesAsync));
var (context, cancellationToken) = presenter.StartSearch(EditorFeaturesResources.Find_References, supportsReferences: true);
using (Logger.LogBlock(
FunctionId.CommandHandler_FindAllReference,
KeyValueLogMessage.Create(LogType.UserAction, m => m["type"] = "streaming"),
cancellationToken))
{
var symbolsToLookup = new List<ISymbol>();
foreach (var curSymbol in symbol.ContainingType.GetMembers()
.Where(m => m.Kind == symbol.Kind && m.Name == symbol.Name))
{
if (!document.Project.TryGetCompilation(out var compilation))
{
// TODO: should we do anything more here?
continue;
}
foreach (var sym in SymbolFinder.FindSimilarSymbols(curSymbol, compilation, cancellationToken))
{
// assumption here is, that FindSimilarSymbols returns symbols inside same project
var symbolsToAdd = await GatherSymbolsAsync(sym, document.Project.Solution, cancellationToken).ConfigureAwait(false);
symbolsToLookup.AddRange(symbolsToAdd);
}
}
foreach (var candidate in symbolsToLookup)
{
await AbstractFindUsagesService.FindSymbolReferencesAsync(context, candidate, document.Project, cancellationToken).ConfigureAwait(false);
}
// Note: we don't need to put this in a finally. The only time we might not hit
// this is if cancellation or another error gets thrown. In the former case,
// that means that a new search has started. We don't care about telling the
// context it has completed. In the latter case something wrong has happened
// and we don't want to run any more code in this particular context.
await context.OnCompletedAsync(cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
}
catch (Exception e) when (FatalError.ReportAndCatch(e))
{
}
}
}
}
|