|
// 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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.BackgroundWorkIndicator;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Notification;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Commanding;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
using Microsoft.VisualStudio.Text.Operations;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.EncapsulateField;
internal abstract class AbstractEncapsulateFieldCommandHandler(
IThreadingContext threadingContext,
ITextBufferUndoManagerProvider undoManager,
IAsynchronousOperationListenerProvider listenerProvider) : ICommandHandler<EncapsulateFieldCommandArgs>
{
private readonly IThreadingContext _threadingContext = threadingContext;
private readonly ITextBufferUndoManagerProvider _undoManager = undoManager;
private readonly IAsynchronousOperationListener _listener = listenerProvider.GetListener(FeatureAttribute.EncapsulateField);
public string DisplayName => EditorFeaturesResources.Encapsulate_Field;
public CommandState GetCommandState(EncapsulateFieldCommandArgs args)
=> args.SubjectBuffer.SupportsRefactorings() ? CommandState.Available : CommandState.Unspecified;
public bool ExecuteCommand(EncapsulateFieldCommandArgs args, CommandExecutionContext context)
{
var textBuffer = args.SubjectBuffer;
if (!textBuffer.SupportsRefactorings())
return false;
var spans = args.TextView.Selection.GetSnapshotSpansOnBuffer(textBuffer);
if (spans.Count != 1)
return false;
var document = args.SubjectBuffer.AsTextContainer().GetOpenDocumentInCurrentContext();
if (document == null)
return false;
// Fire and forget
var token = _listener.BeginAsyncOperation(FeatureAttribute.EncapsulateField);
_ = ExecuteAsync(args, document, spans.Single()).CompletesAsyncOperation(token);
return true;
}
private async Task ExecuteAsync(
EncapsulateFieldCommandArgs args,
Document initialDocument,
SnapshotSpan span)
{
_threadingContext.ThrowIfNotOnUIThread();
var subjectBuffer = args.SubjectBuffer;
var workspace = initialDocument.Project.Solution.Workspace;
var indicatorFactory = workspace.Services.GetRequiredService<IBackgroundWorkIndicatorFactory>();
using var context = indicatorFactory.Create(
args.TextView, span, EditorFeaturesResources.Computing_Encapsulate_Field_information,
cancelOnEdit: true, cancelOnFocusLost: true);
var cancellationToken = context.UserCancellationToken;
var document = await subjectBuffer.CurrentSnapshot.GetFullyLoadedOpenDocumentInCurrentContextWithChangesAsync(context).ConfigureAwait(false);
Contract.ThrowIfNull(document);
var service = document.GetRequiredLanguageService<IEncapsulateFieldService>();
var result = await service.EncapsulateFieldsInSpanAsync(
document, span.Span.ToTextSpan(), useDefaultBehavior: true, cancellationToken).ConfigureAwait(false);
if (result == null)
{
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
// We are about to show a modal UI dialog so we should take over the command execution
// wait context. That means the command system won't attempt to show its own wait dialog
// and also will take it into consideration when measuring command handling duration.
context.TakeOwnership();
var notificationService = workspace.Services.GetRequiredService<INotificationService>();
notificationService.SendNotification(EditorFeaturesResources.Please_select_the_definition_of_the_field_to_encapsulate, severity: NotificationSeverity.Error);
return;
}
await ApplyChangeAsync(subjectBuffer, document, result, cancellationToken).ConfigureAwait(false);
}
private async Task ApplyChangeAsync(
ITextBuffer subjectBuffer,
Document document,
EncapsulateFieldResult result, CancellationToken cancellationToken)
{
var finalSolution = await result.GetSolutionAsync(cancellationToken).ConfigureAwait(false);
var solution = document.Project.Solution;
var workspace = solution.Workspace;
var previewService = workspace.Services.GetService<IPreviewDialogService>();
if (previewService != null)
{
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
finalSolution = previewService.PreviewChanges(
string.Format(EditorFeaturesResources.Preview_Changes_0, EditorFeaturesResources.Encapsulate_Field),
"vs.csharp.refactoring.preview",
EditorFeaturesResources.Encapsulate_Field_colon,
result.Name,
result.Glyph,
finalSolution,
solution);
}
if (finalSolution == null)
{
// User clicked cancel.
return;
}
using var undoTransaction = _undoManager.GetTextBufferUndoManager(subjectBuffer).TextBufferUndoHistory.CreateTransaction(EditorFeaturesResources.Encapsulate_Field);
if (workspace.TryApplyChanges(finalSolution))
{
undoTransaction.Complete();
}
else
{
undoTransaction.Cancel();
}
}
}
|