File: RenameTracking\RenameTrackingTaggerProvider.RenameTrackingCodeAction.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.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Notification;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Text.Operations;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Editor.Implementation.RenameTracking;
 
internal sealed partial class RenameTrackingTaggerProvider
{
    private sealed class RenameTrackingCodeAction : CodeAction
    {
        private readonly string _title;
        private readonly IThreadingContext _threadingContext;
        private readonly Document _document;
        private readonly IEnumerable<IRefactorNotifyService> _refactorNotifyServices;
        private readonly ITextUndoHistoryRegistry _undoHistoryRegistry;
        private readonly IGlobalOptionService _globalOptions;
        private RenameTrackingCommitter _renameTrackingCommitter;
 
        public RenameTrackingCodeAction(
            IThreadingContext threadingContext,
            Document document,
            string title,
            IEnumerable<IRefactorNotifyService> refactorNotifyServices,
            ITextUndoHistoryRegistry undoHistoryRegistry,
            IGlobalOptionService globalOptions)
        {
            _threadingContext = threadingContext;
            _document = document;
            _title = title;
            _refactorNotifyServices = refactorNotifyServices;
            _undoHistoryRegistry = undoHistoryRegistry;
            _globalOptions = globalOptions;
 
            // Backdoor that allows this provider to use the high-priority bucket.
            this.CustomTags = this.CustomTags.Add(CodeAction.CanBeHighPriorityTag);
        }
 
        public override string Title => _title;
 
        protected sealed override CodeActionPriority ComputePriority()
            => CodeActionPriority.High;
 
        protected override Task<ImmutableArray<CodeActionOperation>> ComputeOperationsAsync(
            IProgress<CodeAnalysisProgress> progress, CancellationToken cancellationToken)
        {
            // Invoked directly without previewing.
            if (_renameTrackingCommitter == null)
            {
                if (!TryInitializeRenameTrackingCommitter(cancellationToken))
                {
                    return SpecializedTasks.EmptyImmutableArray<CodeActionOperation>();
                }
            }
 
            var committerOperation = new RenameTrackingCommitterOperation(_renameTrackingCommitter, _threadingContext);
            return Task.FromResult(ImmutableArray.Create<CodeActionOperation>(committerOperation));
        }
 
        protected override async Task<IEnumerable<CodeActionOperation>> ComputePreviewOperationsAsync(CancellationToken cancellationToken)
        {
            if (!_globalOptions.GetOption(RenameTrackingOptionsStorage.RenameTrackingPreview, _document.Project.Language) ||
                !TryInitializeRenameTrackingCommitter(cancellationToken))
            {
                return await SpecializedTasks.EmptyEnumerable<CodeActionOperation>().ConfigureAwait(false);
            }
 
            var solutionSet = await _renameTrackingCommitter.RenameSymbolAsync(cancellationToken).ConfigureAwait(false);
 
            return [new ApplyChangesOperation(solutionSet.RenamedSolution)];
        }
 
        private bool TryInitializeRenameTrackingCommitter(CancellationToken cancellationToken)
        {
            if (_document.TryGetText(out var text))
            {
                var textBuffer = text.Container.GetTextBuffer();
                if (textBuffer.Properties.TryGetProperty(typeof(StateMachine), out StateMachine stateMachine))
                {
                    if (!stateMachine.CanInvokeRename(out _, cancellationToken: cancellationToken))
                    {
                        // The rename tracking could be dismissed while a codefix is still cached
                        // in the lightbulb. If this happens, do not perform the rename requested
                        // and instead let the user know their fix will not be applied. 
                        _document.Project.Solution.Services.GetService<INotificationService>()
                            ?.SendNotification(EditorFeaturesResources.The_rename_tracking_session_was_cancelled_and_is_no_longer_available, severity: NotificationSeverity.Error);
                        return false;
                    }
 
                    var snapshotSpan = stateMachine.TrackingSession.TrackingSpan.GetSpan(stateMachine.Buffer.CurrentSnapshot);
                    var newName = snapshotSpan.GetText();
                    var displayText = string.Format(EditorFeaturesResources.Rename_0_to_1, stateMachine.TrackingSession.OriginalName, newName);
                    _renameTrackingCommitter = new RenameTrackingCommitter(stateMachine, snapshotSpan, _refactorNotifyServices, _undoHistoryRegistry, displayText);
                    return true;
                }
            }
 
            return false;
        }
 
        private sealed class RenameTrackingCommitterOperation(RenameTrackingCommitter committer, IThreadingContext threadingContext) : CodeActionOperation
        {
            private readonly RenameTrackingCommitter _committer = committer;
            private readonly IThreadingContext _threadingContext = threadingContext;
 
            internal override async Task<bool> TryApplyAsync(
                Workspace workspace, Solution originalSolution, IProgress<CodeAnalysisProgress> progressTracker, CancellationToken cancellationToken)
            {
                var error = await _committer.TryCommitAsync(cancellationToken).ConfigureAwait(false);
                if (error == null)
                    return true;
 
                await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
                var notificationService = workspace.Services.GetService<INotificationService>();
                notificationService.SendNotification(
                    error.Value.message, EditorFeaturesResources.Rename_Symbol, error.Value.severity);
                return false;
            }
        }
    }
}