File: Formatting\FormatCommandHandler.Paste.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;
using System.Threading;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Commanding;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Formatting;
 
internal partial class FormatCommandHandler
{
    public CommandState GetCommandState(PasteCommandArgs args, Func<CommandState> nextHandler)
        => nextHandler();
 
    public void ExecuteCommand(PasteCommandArgs args, Action nextHandler, CommandExecutionContext context)
    {
        using var _ = context.OperationContext.AddScope(allowCancellation: true, EditorFeaturesResources.Formatting_pasted_text);
        var caretPosition = args.TextView.GetCaretPoint(args.SubjectBuffer);
 
        nextHandler();
 
        var cancellationToken = context.OperationContext.UserCancellationToken;
        if (cancellationToken.IsCancellationRequested)
        {
            return;
        }
 
        try
        {
            ExecuteCommandWorker(args, caretPosition, cancellationToken);
        }
        catch (OperationCanceledException)
        {
            // According to Editor command handler API guidelines, it's best if we return early if cancellation
            // is requested instead of throwing. Otherwise, we could end up in an invalid state due to already
            // calling nextHandler().
        }
    }
 
    private void ExecuteCommandWorker(PasteCommandArgs args, SnapshotPoint? caretPosition, CancellationToken cancellationToken)
    {
        if (!caretPosition.HasValue)
            return;
 
        var subjectBuffer = args.SubjectBuffer;
        if (!subjectBuffer.TryGetWorkspace(out var workspace) ||
            !workspace.CanApplyChange(ApplyChangesKind.ChangeDocument))
        {
            return;
        }
 
        var document = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
        if (document == null)
            return;
 
        if (!_globalOptions.GetOption(FormattingOptionsStorage.FormatOnPaste, document.Project.Language))
            return;
 
        var solution = document.Project.Solution;
        var services = solution.Services;
        var formattingRuleService = services.GetService<IHostDependentFormattingRuleFactoryService>();
        if (formattingRuleService != null && formattingRuleService.ShouldNotFormatOrCommitOnPaste(document.Id))
            return;
 
        var formattingService = document.GetLanguageService<IFormattingInteractionService>();
        if (formattingService == null || !formattingService.SupportsFormatOnPaste)
            return;
 
        var trackingSpan = caretPosition.Value.Snapshot.CreateTrackingSpan(caretPosition.Value.Position, 0, SpanTrackingMode.EdgeInclusive);
        var span = trackingSpan.GetSpan(subjectBuffer.CurrentSnapshot).Span.ToTextSpan();
 
        // Note: C# always completes synchronously, TypeScript is async
        var changes = formattingService.GetFormattingChangesOnPasteAsync(document, subjectBuffer, span, cancellationToken).WaitAndGetResult(cancellationToken);
        if (changes.IsEmpty)
            return;
 
        subjectBuffer.ApplyChanges(changes);
    }
}