Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// 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 Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Commanding;
using Microsoft.VisualStudio.Text.Editor.Commanding;

namespace Microsoft.CodeAnalysis.GoOrFind;

internal abstract class AbstractGoOrFindCommandHandler<TCommandArgs>(
IGoOrFindNavigationService navigationService) : ICommandHandler<TCommandArgs>
where TCommandArgs : EditorCommandArgs
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

base command handler for GoToBase/GoToImpl/FindRefs.

defers most operations to the provided IGoOrFindNavigationService passed in.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this type basically jsut bridges from from the ICommandHandler/TCommandArgs world to the core roslyn world.

{
private readonly IGoOrFindNavigationService _navigationService = navigationService;

public string DisplayName => _navigationService.DisplayName;

public CommandState GetCommandState(TCommandArgs args)
{
var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
return _navigationService.IsAvailable(document)
? CommandState.Available
: CommandState.Unspecified;
}

public bool ExecuteCommand(TCommandArgs args, CommandExecutionContext context)
{
var subjectBuffer = args.SubjectBuffer;
var caret = args.TextView.GetCaretPoint(subjectBuffer);
if (!caret.HasValue)
return false;

var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
if (!_navigationService.IsAvailable(document))
return false;

return _navigationService.ExecuteCommand(document, caret.Value.Position);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Classification;
Expand All @@ -17,22 +18,22 @@
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Threading;
using Microsoft.VisualStudio.Commanding;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor.Commanding;
using Microsoft.VisualStudio.Threading;

namespace Microsoft.CodeAnalysis.GoToDefinition;
namespace Microsoft.CodeAnalysis.GoOrFind;

internal abstract class AbstractGoOrFindCommandHandler<TLanguageService, TCommandArgs>(
/// <summary>
/// Core service responsible for handling an operation (like 'go to base, go to impl, find references')
/// and trying to navigate quickly to them if possible, or show their results in the find-usages window.
/// </summary>
internal abstract class AbstractGoOrFindNavigationService<TLanguageService>(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the core code for the absstract handler was broken out into this abstract service. This service has no command-handler dependencies (though it does have eidtor dependencies) and thus can be used simply by the SolutionExplorer rewrite i'm in the middle of.

IThreadingContext threadingContext,
IStreamingFindUsagesPresenter streamingPresenter,
IAsynchronousOperationListener listener,
IGlobalOptionService globalOptions) : ICommandHandler<TCommandArgs>
IGlobalOptionService globalOptions)
: IGoOrFindNavigationService
where TLanguageService : class, ILanguageService
where TCommandArgs : EditorCommandArgs
{
private readonly IThreadingContext _threadingContext = threadingContext;
private readonly IStreamingFindUsagesPresenter _streamingPresenter = streamingPresenter;
Expand Down Expand Up @@ -80,41 +81,25 @@ protected virtual StreamingFindUsagesPresenterOptions GetStreamingPresenterOptio

protected abstract Task FindActionAsync(IFindUsagesContext context, Document document, TLanguageService service, int caretPosition, CancellationToken cancellationToken);

private static (Document?, TLanguageService?) GetDocumentAndService(ITextSnapshot snapshot)
{
var document = snapshot.GetOpenDocumentInCurrentContextWithChanges();
return (document, document?.GetLanguageService<TLanguageService>());
}
public bool IsAvailable([NotNullWhen(true)] Document? document)
=> document?.GetLanguageService<TLanguageService>() != null;

public CommandState GetCommandState(TCommandArgs args)
{
var (_, service) = GetDocumentAndService(args.SubjectBuffer.CurrentSnapshot);
return service != null
? CommandState.Available
: CommandState.Unspecified;
}

public bool ExecuteCommand(TCommandArgs args, CommandExecutionContext context)
public bool ExecuteCommand(Document document, int position)
{
_threadingContext.ThrowIfNotOnUIThread();

var subjectBuffer = args.SubjectBuffer;
var caret = args.TextView.GetCaretPoint(subjectBuffer);
if (!caret.HasValue)
if (document is null)
return false;

var (document, service) = GetDocumentAndService(subjectBuffer.CurrentSnapshot);
var service = document.GetLanguageService<TLanguageService>();
if (service == null)
return false;

Contract.ThrowIfNull(document);

// cancel any prior find-refs that might be in progress.
var cancellationToken = _cancellationSeries.CreateNext();

// we're going to return immediately from ExecuteCommand and kick off our own async work to invoke the
// operation. Once this returns, the editor will close the threaded wait dialog it created.
_inProgressCommand = ExecuteCommandAsync(document, service, caret.Value.Position, cancellationToken);
_inProgressCommand = ExecuteCommandAsync(document, service, position, cancellationToken);
return true;
}

Expand Down Expand Up @@ -291,9 +276,9 @@ internal TestAccessor GetTestAccessor()

internal readonly struct TestAccessor
{
private readonly AbstractGoOrFindCommandHandler<TLanguageService, TCommandArgs> _instance;
private readonly AbstractGoOrFindNavigationService<TLanguageService> _instance;

internal TestAccessor(AbstractGoOrFindCommandHandler<TLanguageService, TCommandArgs> instance)
internal TestAccessor(AbstractGoOrFindNavigationService<TLanguageService> instance)
=> _instance = instance;

internal ref Func<CancellationToken, Task>? DelayHook
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// 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.ComponentModel.Composition;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.GoOrFind;
using Microsoft.VisualStudio.Commanding;
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
using Microsoft.VisualStudio.Utilities;

namespace Microsoft.CodeAnalysis.FindReferences;

[Export(typeof(ICommandHandler))]
[ContentType(ContentTypeNames.RoslynContentType)]
[Name(PredefinedCommandHandlerNames.FindReferences)]
[method: ImportingConstructor]
[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
internal sealed class FindReferencesCommandHandler(FindReferencesNavigationService navigationService)
: AbstractGoOrFindCommandHandler<FindReferencesCommandArgs>(navigationService);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the command handler is now trivial. it just pulls in the appropriate service and defers to it.

Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,24 @@
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.GoToDefinition;
using Microsoft.CodeAnalysis.GoOrFind;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.Commanding;
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
using Microsoft.VisualStudio.Utilities;

namespace Microsoft.CodeAnalysis.FindReferences;

[Export(typeof(ICommandHandler))]
[ContentType(ContentTypeNames.RoslynContentType)]
[Name(PredefinedCommandHandlerNames.FindReferences)]
[Export(typeof(FindReferencesNavigationService))]
[method: ImportingConstructor]
[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
internal sealed class FindReferencesCommandHandler(
internal sealed class FindReferencesNavigationService(
IThreadingContext threadingContext,
IStreamingFindUsagesPresenter streamingPresenter,
IAsynchronousOperationListenerProvider listenerProvider,
IGlobalOptionService globalOptions) : AbstractGoOrFindCommandHandler<IFindUsagesService, FindReferencesCommandArgs>(
IGlobalOptionService globalOptions) : AbstractGoOrFindNavigationService<IFindUsagesService>(
threadingContext,
streamingPresenter,
listenerProvider.GetListener(FeatureAttribute.FindReferences),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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.ComponentModel.Composition;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.GoOrFind;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
using Microsoft.VisualStudio.Utilities;
using VSCommanding = Microsoft.VisualStudio.Commanding;

namespace Microsoft.CodeAnalysis.GoToBase;

[Export(typeof(VSCommanding.ICommandHandler))]
[ContentType(ContentTypeNames.RoslynContentType)]
[Name(PredefinedCommandHandlerNames.GoToBase)]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class GoToBaseCommandHandler(GoToBaseNavigationService navigationService)
: AbstractGoOrFindCommandHandler<GoToBaseCommandArgs>(navigationService);
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,26 @@
using System.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.GoToDefinition;
using Microsoft.CodeAnalysis.GoOrFind;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
using Microsoft.VisualStudio.Utilities;
using VSCommanding = Microsoft.VisualStudio.Commanding;

namespace Microsoft.CodeAnalysis.GoToBase;

[Export(typeof(VSCommanding.ICommandHandler))]
[ContentType(ContentTypeNames.RoslynContentType)]
[Name(PredefinedCommandHandlerNames.GoToBase)]
[Export(typeof(GoToBaseNavigationService))]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class GoToBaseCommandHandler(
internal sealed class GoToBaseNavigationService(
IThreadingContext threadingContext,
IStreamingFindUsagesPresenter streamingPresenter,
IAsynchronousOperationListenerProvider listenerProvider,
IGlobalOptionService globalOptions) : AbstractGoOrFindCommandHandler<IGoToBaseService, GoToBaseCommandArgs>(
IGlobalOptionService globalOptions)
: AbstractGoOrFindNavigationService<IGoToBaseService>(
threadingContext,
streamingPresenter,
listenerProvider.GetListener(FeatureAttribute.GoToBase),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// 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.ComponentModel.Composition;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.Commanding.Commands;
using Microsoft.CodeAnalysis.GoOrFind;
using Microsoft.CodeAnalysis.GoToDefinition;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.VisualStudio.Commanding;
using Microsoft.VisualStudio.Utilities;

namespace Microsoft.CodeAnalysis.GoToImplementation;

[Export(typeof(ICommandHandler))]
[ContentType(ContentTypeNames.RoslynContentType)]
[Name(PredefinedCommandHandlerNames.GoToImplementation)]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class GoToImplementationCommandHandler(GoToImplementationNavigationService navigationService)
: AbstractGoOrFindCommandHandler<GoToImplementationCommandArgs>(navigationService);
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,25 @@
using System.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.Commanding.Commands;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.GoToDefinition;
using Microsoft.CodeAnalysis.GoOrFind;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.Commanding;
using Microsoft.VisualStudio.Utilities;

namespace Microsoft.CodeAnalysis.GoToImplementation;

[Export(typeof(ICommandHandler))]
[ContentType(ContentTypeNames.RoslynContentType)]
[Name(PredefinedCommandHandlerNames.GoToImplementation)]
[Export(typeof(GoToImplementationNavigationService))]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class GoToImplementationCommandHandler(
internal sealed class GoToImplementationNavigationService(
IThreadingContext threadingContext,
IStreamingFindUsagesPresenter streamingPresenter,
IAsynchronousOperationListenerProvider listenerProvider,
IGlobalOptionService globalOptions) : AbstractGoOrFindCommandHandler<IFindUsagesService, GoToImplementationCommandArgs>(
IGlobalOptionService globalOptions) : AbstractGoOrFindNavigationService<IFindUsagesService>(
threadingContext,
streamingPresenter,
listenerProvider.GetListener(FeatureAttribute.GoToImplementation),
Expand Down
15 changes: 15 additions & 0 deletions src/EditorFeatures/Core/GoOrFind/IGoOrFindNavigationService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// 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.Diagnostics.CodeAnalysis;

namespace Microsoft.CodeAnalysis.GoOrFind;

internal interface IGoOrFindNavigationService
{
string DisplayName { get; }

bool IsAvailable([NotNullWhen(true)] Document? document);
bool ExecuteCommand(Document document, int position);
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,11 @@ public async Task TestFindReferencesAsynchronousCall()
var listenerProvider = workspace.ExportProvider.GetExportedValue<IAsynchronousOperationListenerProvider>();

var handler = new FindReferencesCommandHandler(
workspace.ExportProvider.GetExportedValue<IThreadingContext>(),
presenter,
listenerProvider,
workspace.GlobalOptions);
new FindReferencesNavigationService(
workspace.ExportProvider.GetExportedValue<IThreadingContext>(),
presenter,
listenerProvider,
workspace.GlobalOptions));

var textView = workspace.Documents[0].GetTextView();
textView.Caret.MoveTo(new SnapshotPoint(textView.TextSnapshot, 7));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@ class C

Dim context = New FindUsagesTestContext()
Dim commandHandler = New FindReferencesCommandHandler(
workspace.ExportProvider.GetExportedValue(Of IThreadingContext)(),
New MockStreamingFindReferencesPresenter(context),
listenerProvider,
workspace.GlobalOptions)
New FindReferencesNavigationService(
workspace.ExportProvider.GetExportedValue(Of IThreadingContext)(),
New MockStreamingFindReferencesPresenter(context),
listenerProvider,
workspace.GlobalOptions))

Dim document = workspace.CurrentSolution.GetDocument(testDocument.Id)
commandHandler.ExecuteCommand(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.Design;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
Expand Down Expand Up @@ -1017,11 +1017,11 @@ public async Task ConfigureAsyncNavigation(AsyncNavigationKind kind, Cancellatio
};

var componentModelService = await GetRequiredGlobalServiceAsync<SComponentModel, IComponentModel>(cancellationToken);
var commandHandlers = componentModelService.DefaultExportProvider.GetExports<ICommandHandler, NameMetadata>();
var goToImplementation = (GoToImplementationCommandHandler)commandHandlers.Single(handler => handler.Metadata.Name == PredefinedCommandHandlerNames.GoToImplementation).Value;

var goToImplementation = componentModelService.DefaultExportProvider.GetExportedValue<GoToImplementationNavigationService>();
goToImplementation.GetTestAccessor().DelayHook = delayHook;

var goToBase = (GoToBaseCommandHandler)commandHandlers.Single(handler => handler.Metadata.Name == PredefinedCommandHandlerNames.GoToBase).Value;
var goToBase = componentModelService.DefaultExportProvider.GetExportedValue<GoToBaseNavigationService>();
goToBase.GetTestAccessor().DelayHook = delayHook;
}

Expand Down
Loading