File: EncapsulateField\AbstractEncapsulateFieldCommandHandler.cs
Web Access
Project: src\src\EditorFeatures\Core\Microsoft.CodeAnalysis.EditorFeatures.csproj (Microsoft.CodeAnalysis.EditorFeatures)
// 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();
        }
    }
}