提交 57a2bf36 编写于 作者: C CyrusNajmabadi

Start refactoring how goto-impl works.

上级 d4636265
......@@ -2,8 +2,8 @@
using System.Collections.Generic;
using System.Composition;
using Microsoft.CodeAnalysis.Editor.FindReferences;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Implementation.FindReferences;
using Microsoft.CodeAnalysis.Host.Mef;
namespace Microsoft.CodeAnalysis.Editor.CSharp.FindReferences
......
......@@ -3,8 +3,8 @@
using System;
using System.Collections.Generic;
using System.Composition;
using Microsoft.CodeAnalysis.Editor.GoToImplementation;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Implementation.GoToImplementation;
using Microsoft.CodeAnalysis.Host.Mef;
namespace Microsoft.CodeAnalysis.Editor.CSharp.GoToImplementation
......@@ -14,9 +14,19 @@ internal sealed class CSharpGoToImplementationService : AbstractGoToImplementati
{
[ImportingConstructor]
public CSharpGoToImplementationService(
[ImportMany]IEnumerable<Lazy<INavigableItemsPresenter>> presenters,
[ImportMany]IEnumerable<Lazy<IStreamingFindUsagesPresenter>> streamingPresenters)
: base(presenters, streamingPresenters)
[ImportMany]IEnumerable<Lazy<INavigableItemsPresenter>> presenters)
: base(presenters)
{
}
}
[ExportLanguageService(typeof(IStreamingFindImplementationsService), LanguageNames.CSharp), Shared]
internal sealed class CSharpStreamingFindImplementationsService : AbstractGoToImplementationService
{
[ImportingConstructor]
public CSharpStreamingFindImplementationsService(
[ImportMany]IEnumerable<Lazy<INavigableItemsPresenter>> presenters)
: base(presenters)
{
}
}
......
......@@ -108,6 +108,7 @@
<InternalsVisibleToMoq Include="DynamicProxyGenAssembly2" />
</ItemGroup>
<ItemGroup>
<Compile Include="FindUsages\FindUsagesHelpers.cs" />
<Compile Include="FindUsages\FindUsagesContext.cs" />
<Compile Include="FindUsages\IFindUsagesContext.cs" />
<Compile Include="Implementation\NavigateTo\AbstractNavigateToItemDisplay.cs" />
......@@ -256,9 +257,9 @@
<Compile Include="Implementation\Diagnostics\DiagnosticsSuggestionTaggerProvider.cs" />
<Compile Include="Implementation\Diagnostics\SuggestionAdornmentManagerProvider.cs" />
<Compile Include="Implementation\Diagnostics\SuggestionTag.cs" />
<Compile Include="Implementation\FindReferences\AbstractFindReferencesService.ProgressAdapter.cs" />
<Compile Include="Implementation\GoToImplementation\AbstractGoToImplementationService.cs" />
<Compile Include="Implementation\GoToImplementation\IGoToImplementationService.cs" />
<Compile Include="FindReferences\AbstractFindReferencesService.ProgressAdapter.cs" />
<Compile Include="GoToImplementation\AbstractGoToImplementationService.cs" />
<Compile Include="GoToImplementation\IGoToImplementationService.cs" />
<Compile Include="Implementation\Intellisense\Completion\BraceCompletionMetadata.cs" />
<Compile Include="Implementation\Intellisense\Completion\OptionSetExtensions.cs" />
<Compile Include="Implementation\Intellisense\Completion\Presentation\ClassificationTags.cs" />
......@@ -279,7 +280,7 @@
<Compile Include="Implementation\Preview\PreviewReferenceHighlightingTaggerProvider.cs" />
<Compile Include="Implementation\Suggestions\FixAllSuggestedAction.FixAllCodeAction.cs" />
<Compile Include="Implementation\Suggestions\FixMultipleOccurrencesService.cs" />
<Compile Include="Implementation\GoToImplementation\GoToImplementationCommandHandler.cs" />
<Compile Include="GoToImplementation\GoToImplementationCommandHandler.cs" />
<Compile Include="Implementation\TodoComment\ITodoListProvider.cs" />
<Compile Include="Implementation\TodoComment\TodoItem.cs" />
<Compile Include="Implementation\TodoComment\TodoItemsUpdatedArgs.cs" />
......@@ -376,9 +377,9 @@
<Compile Include="Implementation\EndConstructGeneration\IEndConstructGenerationService.cs" />
<Compile Include="Implementation\ExtractInterface\AbstractExtractInterfaceCommandHandler.cs" />
<Compile Include="Implementation\ExtractMethod\AbstractExtractMethodCommandHandler.cs" />
<Compile Include="Implementation\FindReferences\AbstractFindReferencesService.cs" />
<Compile Include="Implementation\FindReferences\FindReferencesCommandHandler.cs" />
<Compile Include="Implementation\FindReferences\IFindReferencesService.cs" />
<Compile Include="FindReferences\AbstractFindReferencesService.cs" />
<Compile Include="FindReferences\FindReferencesCommandHandler.cs" />
<Compile Include="FindReferences\IFindReferencesService.cs" />
<Compile Include="Implementation\ForegroundNotification\ForegroundNotificationService.cs" />
<Compile Include="Implementation\Formatting\FormatCommandHandler.cs" />
<Compile Include="Implementation\Formatting\FormatCommandHandler.ReturnKey.cs" />
......
......@@ -9,7 +9,7 @@
using Microsoft.CodeAnalysis.Navigation;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Editor.Implementation.FindReferences
namespace Microsoft.CodeAnalysis.Editor.FindReferences
{
internal abstract partial class AbstractFindReferencesService
{
......
......@@ -3,18 +3,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.FindUsages;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Editor.SymbolMapping;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.Navigation;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Editor.Implementation.FindReferences
namespace Microsoft.CodeAnalysis.Editor.FindReferences
{
internal abstract partial class AbstractFindReferencesService :
ForegroundThreadAffinitizedObject, IFindReferencesService, IStreamingFindReferencesService
......@@ -30,47 +29,13 @@ internal abstract partial class AbstractFindReferencesService :
_navigableItemPresenters = navigableItemPresenters;
}
/// <summary>
/// Common helper for both the synchronous and streaming versions of FAR.
/// It returns the symbol we want to search for and the solution we should
/// be searching.
///
/// Note that the <see cref="Solution"/> returned may absolutely *not* be
/// the same as <code>document.Project.Solution</code>. This is because
/// there may be symbol mapping involved (for example in Metadata-As-Source
/// scenarios).
/// </summary>
private async Task<Tuple<ISymbol, Project>> GetRelevantSymbolAndProjectAtPositionAsync(
Document document, int position, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var symbol = await SymbolFinder.FindSymbolAtPositionAsync(document, position, cancellationToken: cancellationToken).ConfigureAwait(false);
if (symbol == null)
{
return null;
}
// If this document is not in the primary workspace, we may want to search for results
// in a solution different from the one we started in. Use the starting workspace's
// ISymbolMappingService to get a context for searching in the proper solution.
var mappingService = document.Project.Solution.Workspace.Services.GetService<ISymbolMappingService>();
var mapping = await mappingService.MapSymbolAsync(document, symbol, cancellationToken).ConfigureAwait(false);
if (mapping == null)
{
return null;
}
return Tuple.Create(mapping.Symbol, mapping.Project);
}
private async Task<Tuple<IEnumerable<ReferencedSymbol>, Solution>> FindReferencedSymbolsAsync(
Document document, int position, IWaitContext waitContext)
{
var cancellationToken = waitContext.CancellationToken;
var symbolAndProject = await GetRelevantSymbolAndProjectAtPositionAsync(document, position, cancellationToken).ConfigureAwait(false);
var symbolAndProject = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync(
document, position, cancellationToken).ConfigureAwait(false);
if (symbolAndProject == null)
{
return null;
......@@ -98,7 +63,6 @@ public bool TryFindReferences(Document document, int position, IWaitContext wait
{
var cancellationToken = waitContext.CancellationToken;
// Otherwise, fall back to displaying SymbolFinder based references.
var result = this.FindReferencedSymbolsAsync(document, position, waitContext).WaitAndGetResult(cancellationToken);
return TryDisplayReferences(result);
}
......@@ -164,7 +128,7 @@ private bool TryDisplayReferences(Tuple<IEnumerable<ReferencedSymbol>, Solution>
cancellationToken.ThrowIfCancellationRequested();
// Find the symbol we want to search and the solution we want to search in.
var symbolAndProject = await GetRelevantSymbolAndProjectAtPositionAsync(
var symbolAndProject = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync(
document, position, cancellationToken).ConfigureAwait(false);
if (symbolAndProject == null)
{
......
......@@ -16,7 +16,7 @@
using Microsoft.VisualStudio.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Editor.Implementation.FindReferences
namespace Microsoft.CodeAnalysis.Editor.FindReferences
{
[ExportCommandHandler(PredefinedCommandHandlerNames.FindReferences, ContentTypeNames.RoslynContentType)]
internal class FindReferencesCommandHandler : ICommandHandler<FindReferencesCommandArgs>
......@@ -83,7 +83,7 @@ private bool TryExecuteCommand(int caretPosition, Document document)
var streamingEnabled = document.Project.Solution.Workspace.Options.GetOption(FeatureOnOffOptions.StreamingFindReferences, document.Project.Language);
if (streamingEnabled && streamingService != null && streamingPresenter != null)
{
StreamingFindReferences(document, streamingService, streamingPresenter, caretPosition);
StreamingFindReferences(document, caretPosition, streamingService, streamingPresenter);
return true;
}
......@@ -113,8 +113,9 @@ private IStreamingFindUsagesPresenter GetStreamingPresenter()
}
private async void StreamingFindReferences(
Document document, IStreamingFindReferencesService service,
IStreamingFindUsagesPresenter presenter, int caretPosition)
Document document, int caretPosition,
IStreamingFindReferencesService service,
IStreamingFindUsagesPresenter presenter)
{
try
{
......
// 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.Threading;
using System.Threading.Tasks;
using Roslyn.Utilities;
......@@ -14,6 +15,10 @@ protected FindUsagesContext()
{
}
public virtual void ReportMessage(string message)
{
}
public virtual void SetSearchLabel(string displayName)
{
}
......
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.SymbolMapping;
using Microsoft.CodeAnalysis.FindSymbols;
namespace Microsoft.CodeAnalysis.Editor.FindUsages
{
internal static class FindUsagesHelpers
{
/// <summary>
/// Common helper for both the synchronous and streaming versions of FAR.
/// It returns the symbol we want to search for and the solution we should
/// be searching.
///
/// Note that the <see cref="Solution"/> returned may absolutely *not* be
/// the same as <code>document.Project.Solution</code>. This is because
/// there may be symbol mapping involved (for example in Metadata-As-Source
/// scenarios).
/// </summary>
public static async Task<Tuple<ISymbol, Project>> GetRelevantSymbolAndProjectAtPositionAsync(
Document document, int position, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var symbol = await SymbolFinder.FindSymbolAtPositionAsync(document, position, cancellationToken: cancellationToken).ConfigureAwait(false);
if (symbol == null)
{
return null;
}
// If this document is not in the primary workspace, we may want to search for results
// in a solution different from the one we started in. Use the starting workspace's
// ISymbolMappingService to get a context for searching in the proper solution.
var mappingService = document.Project.Solution.Workspace.Services.GetService<ISymbolMappingService>();
var mapping = await mappingService.MapSymbolAsync(document, symbol, cancellationToken).ConfigureAwait(false);
if (mapping == null)
{
return null;
}
return Tuple.Create(mapping.Symbol, mapping.Project);
}
}
}
\ No newline at end of file
......@@ -9,6 +9,14 @@ internal interface IFindUsagesContext
{
CancellationToken CancellationToken { get; }
/// <summary>
/// Report a message to be displayed to the user.
/// </summary>
void ReportMessage(string message);
/// <summary>
/// Set the title of the window that results are displayed in.
/// </summary>
void SetSearchLabel(string displayName);
Task OnDefinitionFoundAsync(DefinitionItem definition);
......
......@@ -2,9 +2,13 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.FindUsages;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Implementation.GoToDefinition;
using Microsoft.CodeAnalysis.Editor.SymbolMapping;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.FindUsages;
......@@ -13,48 +17,74 @@
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Editor.Implementation.GoToImplementation
namespace Microsoft.CodeAnalysis.Editor.GoToImplementation
{
internal abstract class AbstractGoToImplementationService : IGoToImplementationService
internal abstract class AbstractGoToImplementationService :
IGoToImplementationService, IStreamingFindImplementationsService
{
private readonly IEnumerable<Lazy<INavigableItemsPresenter>> _navigableItemPresenters;
private readonly IEnumerable<Lazy<IStreamingFindUsagesPresenter>> _streamingPresenters;
public AbstractGoToImplementationService(
IEnumerable<Lazy<INavigableItemsPresenter>> navigableItemPresenters,
IEnumerable<Lazy<IStreamingFindUsagesPresenter>> streamingPresenters)
IEnumerable<Lazy<INavigableItemsPresenter>> navigableItemPresenters)
{
_navigableItemPresenters = navigableItemPresenters;
_streamingPresenters = streamingPresenters;
}
public bool TryGoToImplementation(Document document, int position, CancellationToken cancellationToken, out string message)
{
var symbol = SymbolFinder.FindSymbolAtPositionAsync(document, position, cancellationToken).WaitAndGetResult(cancellationToken);
if (symbol != null)
var result = this.FindImplementationsAsync(document, position, cancellationToken).WaitAndGetResult(cancellationToken);
if (result == null)
{
// Map the symbol if necessary back to the originating workspace if we're invoking from something
// like metadata as source
var mappingService = document.Project.Solution.Workspace.Services.GetRequiredService<ISymbolMappingService>();
var mapping = mappingService.MapSymbolAsync(document, symbol, cancellationToken).WaitAndGetResult(cancellationToken);
message = EditorFeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret;
return false;
}
if (mapping != null)
if (result.Value.message != null)
{
return TryGoToImplementationOnMappedSymbol(mapping, cancellationToken, out message);
message = result.Value.message;
return false;
}
return TryGoToImplementations(
result.Value.symbol, result.Value.project,
result.Value.implementations, cancellationToken, out message);
}
message = EditorFeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret;
return false;
public async Task<(ISymbol symbol, Project project, ImmutableArray<ISymbol> implementations, string message)?> FindImplementationsAsync(Document document, int position, CancellationToken cancellationToken)
{
var symbolAndProject = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync(
document, position, cancellationToken).ConfigureAwait(false);
if (symbolAndProject == null)
{
return null;
}
return await FindImplementationsAsync(
symbolAndProject.Item1, symbolAndProject.Item2, cancellationToken).ConfigureAwait(false);
}
private async Task<(ISymbol symbol, Project project, ImmutableArray<ISymbol> implementations, string message)?> FindImplementationsAsync(
ISymbol symbol, Project project, CancellationToken cancellationToken)
{
var implementations = await FindImplementationsWorkerAsync(
symbol, project, cancellationToken).ConfigureAwait(false);
var filteredSymbols = implementations.WhereAsArray(
s => !s.IsAbstract && s.Locations.Any(l => l.IsInSource));
return filteredSymbols.Length == 0
? (symbol, project, filteredSymbols, EditorFeaturesResources.The_symbol_has_no_implementations)
: (symbol, project, filteredSymbols, null);
}
private bool TryGoToImplementationOnMappedSymbol(SymbolMappingResult mapping, CancellationToken cancellationToken, out string message)
private async Task<ImmutableArray<ISymbol>> FindImplementationsWorkerAsync(
ISymbol symbol, Project project, CancellationToken cancellationToken)
{
if (mapping.Symbol.IsInterfaceType() || mapping.Symbol.IsImplementableMember())
var solution = project.Solution;
if (symbol.IsInterfaceType() || symbol.IsImplementableMember())
{
var implementations =
SymbolFinder.FindImplementationsAsync(mapping.Symbol, mapping.Solution, cancellationToken: cancellationToken)
.WaitAndGetResult(cancellationToken);
var implementations = await SymbolFinder.FindImplementationsAsync(
symbol, solution, cancellationToken: cancellationToken).ConfigureAwait(false);
// It's important we use a HashSet here -- we may have cases in an inheritence hierarchy where more than one method
// in an overrides chain implements the same interface method, and we want to duplicate those. The easiest way to do it
......@@ -69,125 +99,80 @@ private bool TryGoToImplementationOnMappedSymbol(SymbolMappingResult mapping, Ca
// of the method. We should also include those.
if (implementation.IsOverridable())
{
implementationsAndOverrides.AddRange(
SymbolFinder.FindOverridesAsync(implementation, mapping.Solution, cancellationToken: cancellationToken).WaitAndGetResult(cancellationToken));
var overrides = await SymbolFinder.FindOverridesAsync(
implementation, solution, cancellationToken: cancellationToken).ConfigureAwait(false);
implementationsAndOverrides.AddRange(overrides);
}
}
return TryGoToImplementations(implementationsAndOverrides, mapping, cancellationToken, out message);
return implementationsAndOverrides.ToImmutableArray();
}
else if ((mapping.Symbol as INamedTypeSymbol)?.TypeKind == TypeKind.Class)
else if ((symbol as INamedTypeSymbol)?.TypeKind == TypeKind.Class)
{
var implementations =
SymbolFinder.FindDerivedClassesAsync((INamedTypeSymbol)mapping.Symbol, mapping.Solution, cancellationToken: cancellationToken)
.WaitAndGetResult(cancellationToken)
.Concat(mapping.Symbol);
var derivedClasses = await SymbolFinder.FindDerivedClassesAsync(
(INamedTypeSymbol)symbol, solution, cancellationToken: cancellationToken).ConfigureAwait(false);
var implementations = derivedClasses.Concat(symbol);
return TryGoToImplementations(implementations, mapping, cancellationToken, out message);
return implementations.ToImmutableArray();
}
else if (mapping.Symbol.IsOverridable())
else if (symbol.IsOverridable())
{
var implementations =
SymbolFinder.FindOverridesAsync(mapping.Symbol, mapping.Solution, cancellationToken: cancellationToken)
.WaitAndGetResult(cancellationToken)
.Concat(mapping.Symbol);
var overrides = await SymbolFinder.FindOverridesAsync(
symbol, solution, cancellationToken: cancellationToken).ConfigureAwait(false);
var implementations = overrides.Concat(symbol);
return TryGoToImplementations(implementations, mapping, cancellationToken, out message);
return implementations.ToImmutableArray();
}
else
{
// This is something boring like a regular method or type, so we'll just go there directly
if (GoToDefinition.GoToDefinitionHelpers.TryGoToDefinition(
mapping.Symbol, mapping.Project,
_navigableItemPresenters, _streamingPresenters, cancellationToken))
{
message = null;
return true;
}
return ImmutableArray.Create(symbol);
//if (GoToDefinitionHelpers.TryGoToDefinition(
// symbol, project,
// _navigableItemPresenters, _streamingPresenters, cancellationToken))
//{
// message = null;
// return true;
//}
message = EditorFeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret;
return false;
//message = EditorFeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret;
//return false;
}
}
private bool TryGoToImplementations(IEnumerable<ISymbol> candidateImplementations, SymbolMappingResult mapping, CancellationToken cancellationToken, out string message)
private bool TryGoToImplementations(
ISymbol symbol, Project project, ImmutableArray<ISymbol> implementations, CancellationToken cancellationToken, out string message)
{
var implementations = candidateImplementations
.Where(s => !s.IsAbstract && s.Locations.Any(l => l.IsInSource))
.ToList();
if (implementations.Count == 0)
if (implementations.Length == 0)
{
message = EditorFeaturesResources.The_symbol_has_no_implementations;
return false;
}
else if (implementations.Count == 1)
else if (implementations.Length == 1)
{
GoToDefinition.GoToDefinitionHelpers.TryGoToDefinition(
implementations.Single(), mapping.Project,
_navigableItemPresenters, _streamingPresenters, cancellationToken);
GoToDefinitionHelpers.TryGoToDefinition(
implementations.Single(), project, _navigableItemPresenters,
SpecializedCollections.EmptyEnumerable<Lazy<IStreamingFindUsagesPresenter>>(),
cancellationToken);
message = null;
return true;
}
else
{
return TryPresentInFindUsagesPresenter(mapping, implementations, cancellationToken, out message) ||
TryPresentInNavigableItemsPresenter(mapping, implementations, out message);
}
}
private bool TryPresentInFindUsagesPresenter(
SymbolMappingResult mapping, List<ISymbol> implementations, CancellationToken cancellationToken, out string message)
{
message = null;
var presenter = GetFindUsagesPresenter();
if (presenter == null)
{
return false;
}
var definitionItems = implementations.Select(s =>
s.ToDefinitionItem(mapping.Solution)).ToImmutableArrayOrEmpty();
var context = presenter.StartSearch(EditorFeaturesResources.Go_To_Implementation);
try
{
foreach (var item in definitionItems)
{
context.OnDefinitionFoundAsync(item).Wait(cancellationToken);
}
}
finally
{
context.OnCompletedAsync().Wait(cancellationToken);
}
return true;
}
private IStreamingFindUsagesPresenter GetFindUsagesPresenter()
{
try
{
return _streamingPresenters.FirstOrDefault()?.Value;
}
catch
{
return null;
return TryPresentInNavigableItemsPresenter(symbol, project, implementations, out message);
}
}
private bool TryPresentInNavigableItemsPresenter(
SymbolMappingResult mapping, List<ISymbol> implementations, out string message)
ISymbol symbol, Project project, ImmutableArray<ISymbol> implementations, out string message)
{
// We have multiple symbols, so we'll build a list of all preferred locations for all the symbols
var navigableItems = implementations.SelectMany(
implementation => CreateItemsForImplementation(implementation, mapping.Solution));
implementation => CreateItemsForImplementation(implementation, project.Solution));
var presenter = _navigableItemPresenters.First();
var taggedParts = NavigableItemFactory.GetSymbolDisplayTaggedParts(mapping.Project, mapping.Symbol);
var taggedParts = NavigableItemFactory.GetSymbolDisplayTaggedParts(project, symbol);
presenter.Value.DisplayResult(taggedParts.JoinText(), navigableItems);
message = null;
......@@ -203,5 +188,32 @@ private static IEnumerable<INavigableItem> CreateItemsForImplementation(ISymbol
implementation,
displayTaggedParts: symbolDisplayService.ToDisplayParts(implementation).ToTaggedText());
}
public async Task FindImplementationsAsync(Document document, int position, IFindUsagesContext context)
{
var tuple = await FindImplementationsAsync(
document, position, context.CancellationToken).ConfigureAwait(false);
if (tuple == null)
{
context.ReportMessage(EditorFeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret);
return;
}
var message = tuple.Value.message;
if (message != null)
{
context.ReportMessage(message);
return;
}
var project = tuple.Value.project;
foreach (var implementation in tuple.Value.implementations)
{
var definitionItem = implementation.ToDefinitionItem(project.Solution);
await context.OnDefinitionFoundAsync(definitionItem).ConfigureAwait(false);
}
}
}
}
\ No newline at end of file
// 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.Generic;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Commands;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Options;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.Notification;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Editor.GoToImplementation
{
[ExportCommandHandler(PredefinedCommandHandlerNames.GoToImplementation,
ContentTypeNames.RoslynContentType)]
internal sealed class GoToImplementationCommandHandler : ICommandHandler<GoToImplementationCommandArgs>
{
private readonly IWaitIndicator _waitIndicator;
private readonly IEnumerable<Lazy<IStreamingFindUsagesPresenter>> _streamingPresenters;
private readonly IAsynchronousOperationListener _asyncListener;
[ImportingConstructor]
public GoToImplementationCommandHandler(
IWaitIndicator waitIndicator,
[ImportMany] IEnumerable<Lazy<IStreamingFindUsagesPresenter>> streamingPresenters,
[ImportMany] IEnumerable<Lazy<IAsynchronousOperationListener, FeatureMetadata>> asyncListeners)
{
_waitIndicator = waitIndicator;
_streamingPresenters = streamingPresenters;
_asyncListener = new AggregateAsynchronousOperationListener(
asyncListeners, FeatureAttribute.GoToImplementation);
}
public CommandState GetCommandState(GoToImplementationCommandArgs args, Func<CommandState> nextHandler)
{
// Because this is expensive to compute, we just always say yes
return CommandState.Available;
}
public void ExecuteCommand(GoToImplementationCommandArgs args, Action nextHandler)
{
var caret = args.TextView.GetCaretPoint(args.SubjectBuffer);
if (caret.HasValue)
{
var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
if (document != null)
{
ExecuteCommand(document, caret.Value);
return;
}
}
nextHandler();
}
private void ExecuteCommand(Document document, int caretPosition)
{
var streamingService = document.Project.LanguageServices.GetService<IStreamingFindImplementationsService>();
var synchronousService = document.Project.LanguageServices.GetService<IGoToImplementationService>();
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.
var streamingEnabled = document.Project.Solution.Workspace.Options.GetOption(FeatureOnOffOptions.StreamingGoToImplementation, document.Project.Language);
var canUseStreamingWindow = streamingEnabled && streamingService != null && streamingPresenter != null;
var canUseSynchronousWindow = synchronousService != null;
if (canUseStreamingWindow || canUseSynchronousWindow)
{
// We have all the cheap stuff, so let's do expensive stuff now
string messageToShow = null;
_waitIndicator.Wait(
EditorFeaturesResources.Go_To_Implementation,
EditorFeaturesResources.Locating_implementations,
allowCancel: true,
action: context =>
{
if (canUseStreamingWindow)
{
StreamingGoToImplementation(
document, caretPosition,
streamingService, streamingPresenter,
context.CancellationToken, out messageToShow);
}
else
{
synchronousService.TryGoToImplementation(
document, caretPosition, context.CancellationToken, out messageToShow);
}
});
if (messageToShow != null)
{
var notificationService = document.Project.Solution.Workspace.Services.GetService<INotificationService>();
notificationService.SendNotification(messageToShow,
title: EditorFeaturesResources.Go_To_Implementation,
severity: NotificationSeverity.Information);
}
}
}
private void StreamingGoToImplementation(
Document document, int caretPosition,
IStreamingFindImplementationsService streamingService,
IStreamingFindUsagesPresenter streamingPresenter,
CancellationToken cancellationToken,
out string messageToShow)
{
var goToImplContext = new GoToImplementationContext(cancellationToken);
streamingService.FindImplementationsAsync(document, caretPosition, goToImplContext).Wait(cancellationToken);
// If finding implementations reported a message, then just stop and show that
// message to the user.
messageToShow = goToImplContext.Message;
if (messageToShow != null)
{
return;
}
// Ignore any definitions that we can't navigate to.
var definitions = goToImplContext.GetDefinitionItems()
.WhereAsArray(d => d.CanNavigateTo());
// See if there's a third party external item we can navigate to. If so, defer
// to that item and finish.
var externalItems = definitions.WhereAsArray(d => d.IsExternal);
foreach (var item in externalItems)
{
if (item.TryNavigateTo())
{
return;
}
}
var nonExternalItems = definitions.WhereAsArray(d => !d.IsExternal);
if (nonExternalItems.Length == 0)
{
return;
}
if (nonExternalItems.Length == 1 &&
nonExternalItems[0].SourceSpans.Length <= 1)
{
// There was only one location to navigate to. Just directly go to that location.
nonExternalItems[0].TryNavigateTo();
return;
}
// We have multiple definitions, or we have definitions with multiple locations.
// Present this to the user so they can decide where they want to go to.
var context = streamingPresenter.StartSearch(EditorFeaturesResources.Go_To_Implementation);
foreach (var definition in nonExternalItems)
{
context.OnDefinitionFoundAsync(definition).Wait(cancellationToken);
}
// 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 somethign wrong has happened
// and we don't want to run any more code code in this particular context.
context.OnCompletedAsync().Wait(cancellationToken);
}
private IStreamingFindUsagesPresenter GetStreamingPresenter()
{
try
{
return _streamingPresenters.FirstOrDefault()?.Value;
}
catch
{
return null;
}
}
private class GoToImplementationContext : FindUsagesContext
{
private readonly object _gate = new object();
private readonly ImmutableArray<DefinitionItem>.Builder _definitionItems =
ImmutableArray.CreateBuilder<DefinitionItem>();
public override CancellationToken CancellationToken { get; }
public GoToImplementationContext(CancellationToken cancellationToken)
{
CancellationToken = cancellationToken;
}
public string Message { get; private set; }
public override void ReportMessage(string message)
=> Message = message;
public ImmutableArray<DefinitionItem> GetDefinitionItems()
{
lock (_gate)
{
return _definitionItems.ToImmutableArray();
}
}
public override Task OnDefinitionFoundAsync(DefinitionItem definition)
{
lock (_gate)
{
_definitionItems.Add(definition);
}
return SpecializedTasks.EmptyTask;
}
}
}
}
\ No newline at end of file
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.Host;
namespace Microsoft.CodeAnalysis.Editor
......@@ -14,4 +16,9 @@ internal interface IGoToImplementationService : ILanguageService
/// <returns>True if navigating to the implementation of the symbol at the provided position succeeds. False, otherwise.</returns>
bool TryGoToImplementation(Document document, int position, CancellationToken cancellationToken, out string message);
}
internal interface IStreamingFindImplementationsService : ILanguageService
{
Task FindImplementationsAsync(Document document, int position, IFindUsagesContext context);
}
}
// 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.ComponentModel.Composition;
using Microsoft.CodeAnalysis.Editor.Commands;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Notification;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.Editor.Implementation.GoToImplementation
{
[ExportCommandHandler(PredefinedCommandHandlerNames.GoToImplementation,
ContentTypeNames.RoslynContentType)]
internal sealed class GoToImplementationCommandHandler : ICommandHandler<GoToImplementationCommandArgs>
{
private readonly IWaitIndicator _waitIndicator;
[ImportingConstructor]
public GoToImplementationCommandHandler(
IWaitIndicator waitIndicator)
{
_waitIndicator = waitIndicator;
}
public CommandState GetCommandState(GoToImplementationCommandArgs args, Func<CommandState> nextHandler)
{
// Because this is expensive to compute, we just always say yes
return CommandState.Available;
}
public void ExecuteCommand(GoToImplementationCommandArgs args, Action nextHandler)
{
var caret = args.TextView.GetCaretPoint(args.SubjectBuffer);
if (caret.HasValue)
{
var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
if (document != null)
{
var service = document.Project.LanguageServices.GetService<IGoToImplementationService>();
if (service != null)
{
// We have all the cheap stuff, so let's do expensive stuff now
string messageToShow = null;
bool succeeded = false;
_waitIndicator.Wait(
EditorFeaturesResources.Go_To_Implementation,
EditorFeaturesResources.Locating_implementations,
allowCancel: true,
action: context => succeeded = service.TryGoToImplementation(document, caret.Value, context.CancellationToken, out messageToShow));
if (messageToShow != null)
{
var notificationService = document.Project.Solution.Workspace.Services.GetService<INotificationService>();
notificationService.SendNotification(messageToShow,
title: EditorFeaturesResources.Go_To_Implementation,
severity: NotificationSeverity.Information);
}
}
return;
}
}
nextHandler();
}
}
}
......@@ -87,9 +87,15 @@ internal static class FeatureOnOffOptions
/// implemented this feature yet.
/// </summary>
[ExportOption]
public static readonly PerLanguageOption<bool> RefactoringVerification = new PerLanguageOption<bool>(nameof(FeatureOnOffOptions), nameof(RefactoringVerification), defaultValue: false);
public static readonly PerLanguageOption<bool> RefactoringVerification = new PerLanguageOption<bool>(
nameof(FeatureOnOffOptions), nameof(RefactoringVerification), defaultValue: false);
[ExportOption]
public static readonly PerLanguageOption<bool> StreamingFindReferences = new PerLanguageOption<bool>(nameof(FeatureOnOffOptions), nameof(StreamingFindReferences), defaultValue: true);
public static readonly PerLanguageOption<bool> StreamingFindReferences = new PerLanguageOption<bool>(
nameof(FeatureOnOffOptions), nameof(StreamingFindReferences), defaultValue: true);
[ExportOption]
public static readonly PerLanguageOption<bool> StreamingGoToImplementation = new PerLanguageOption<bool>(
nameof(FeatureOnOffOptions), nameof(StreamingGoToImplementation), defaultValue: true);
}
}
\ No newline at end of file
......@@ -9,15 +9,20 @@ internal partial class FeatureAttribute
public const string BraceHighlighting = nameof(BraceHighlighting);
public const string CallHierarchy = nameof(CallHierarchy);
public const string Classification = nameof(Classification);
public const string CodeModel = nameof(CodeModel);
public const string CompletionSet = nameof(CompletionSet);
public const string DesignerAttribute = nameof(DesignerAttribute);
public const string ErrorSquiggles = nameof(ErrorSquiggles);
public const string DiagnosticService = nameof(DiagnosticService);
public const string ErrorList = nameof(ErrorList);
public const string FindReferences = nameof(FindReferences);
public const string TodoCommentList = nameof(TodoCommentList);
public const string ErrorSquiggles = nameof(ErrorSquiggles);
public const string EventHookup = nameof(EventHookup);
public const string FindReferences = nameof(FindReferences);
public const string GlobalOperation = nameof(GlobalOperation);
public const string GoToImplementation = nameof(GoToImplementation);
public const string GraphProvider = nameof(GraphProvider);
public const string InfoBar = nameof(InfoBar);
public const string KeywordHighlighting = nameof(KeywordHighlighting);
public const string LightBulb = nameof(LightBulb);
public const string LineSeparators = nameof(LineSeparators);
public const string NavigateTo = nameof(NavigateTo);
public const string NavigationBar = nameof(NavigationBar);
......@@ -26,15 +31,11 @@ internal partial class FeatureAttribute
public const string ReferenceHighlighting = nameof(ReferenceHighlighting);
public const string Rename = nameof(Rename);
public const string RenameTracking = nameof(RenameTracking);
public const string RuleSetEditor = nameof(RuleSetEditor);
public const string SignatureHelp = nameof(SignatureHelp);
public const string Snippets = nameof(Snippets);
public const string SolutionCrawler = nameof(SolutionCrawler);
public const string TodoCommentList = nameof(TodoCommentList);
public const string Workspace = nameof(Workspace);
public const string LightBulb = nameof(LightBulb);
public const string CodeModel = nameof(CodeModel);
public const string GlobalOperation = nameof(GlobalOperation);
public const string DiagnosticService = nameof(DiagnosticService);
public const string RuleSetEditor = nameof(RuleSetEditor);
public const string InfoBar = nameof(InfoBar);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册