File: Wrapping\BinaryExpression\BinaryExpressionCodeActionComputer.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// 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.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Wrapping.BinaryExpression;
 
internal abstract partial class AbstractBinaryExpressionWrapper<TBinaryExpressionSyntax>
{
    private sealed class BinaryExpressionCodeActionComputer :
        AbstractCodeActionComputer<AbstractBinaryExpressionWrapper<TBinaryExpressionSyntax>>
    {
        private readonly ImmutableArray<SyntaxNodeOrToken> _exprsAndOperators;
 
        /// <summary>
        /// trivia to place at the end of a node prior to a chunk that is wrapped.
        /// For C# this will just be a newline.  For VB this will include a line-
        /// continuation character.
        /// </summary>
        private readonly SyntaxTriviaList _newlineBeforeOperatorTrivia;
 
        /// <summary>
        /// The indent trivia to insert if we are trying to align wrapped code with the 
        /// start of the original expression.
        /// </summary>
        private readonly SyntaxTriviaList _indentAndAlignTrivia;
 
        /// <summary>
        /// The indent trivia to insert if we are trying to simply smart-indent all wrapped
        /// parts of the expression.
        /// </summary>
        private readonly AsyncLazy<SyntaxTriviaList> _smartIndentTrivia;
 
        public BinaryExpressionCodeActionComputer(
            AbstractBinaryExpressionWrapper<TBinaryExpressionSyntax> service,
            Document document,
            SourceText originalSourceText,
            SyntaxWrappingOptions options,
            TBinaryExpressionSyntax binaryExpression,
            ImmutableArray<SyntaxNodeOrToken> exprsAndOperators)
            : base(service, document, originalSourceText, options)
        {
            _exprsAndOperators = exprsAndOperators;
 
            var generator = SyntaxGenerator.GetGenerator(document);
 
            _newlineBeforeOperatorTrivia = service.GetNewLineBeforeOperatorTrivia(NewLineTrivia);
 
            _indentAndAlignTrivia = new SyntaxTriviaList(generator.Whitespace(
                OriginalSourceText.GetOffset(binaryExpression.Span.Start)
                                  .CreateIndentationString(options.FormattingOptions.UseTabs, options.FormattingOptions.TabSize)));
 
            _smartIndentTrivia = AsyncLazy.Create(async cancellationToken => new SyntaxTriviaList(generator.Whitespace(
                await GetSmartIndentationAfterAsync(_exprsAndOperators[1], cancellationToken).ConfigureAwait(false))));
        }
 
        protected override async Task<ImmutableArray<WrappingGroup>> ComputeWrappingGroupsAsync(CancellationToken cancellationToken)
            => [new WrappingGroup(
                isInlinable: true,
                [
                    await GetWrapCodeActionAsync(align: false, cancellationToken).ConfigureAwait(false),
                    await GetWrapCodeActionAsync(align: true, cancellationToken).ConfigureAwait(false),
                    await GetUnwrapCodeActionAsync(cancellationToken).ConfigureAwait(false),
                ])];
 
        private async Task<WrapItemsAction> GetWrapCodeActionAsync(bool align, CancellationToken cancellationToken)
            => await TryCreateCodeActionAsync(await GetWrapEditsAsync(align, cancellationToken).ConfigureAwait(false), FeaturesResources.Wrapping,
                    align ? FeaturesResources.Wrap_and_align_expression : FeaturesResources.Wrap_expression,
                    cancellationToken).ConfigureAwait(false);
 
        private Task<WrapItemsAction> GetUnwrapCodeActionAsync(CancellationToken cancellationToken)
            => TryCreateCodeActionAsync(GetUnwrapEdits(), FeaturesResources.Wrapping, FeaturesResources.Unwrap_expression, cancellationToken);
 
        private async Task<ImmutableArray<Edit>> GetWrapEditsAsync(bool align, CancellationToken cancellationToken)
        {
            using var _ = ArrayBuilder<Edit>.GetInstance(out var result);
            var indentationTrivia = align
                ? _indentAndAlignTrivia
                : await _smartIndentTrivia.GetValueAsync(cancellationToken).ConfigureAwait(false);
 
            for (var i = 1; i < _exprsAndOperators.Length; i += 2)
            {
                var left = _exprsAndOperators[i - 1].AsNode();
                var opToken = _exprsAndOperators[i].AsToken();
                var right = _exprsAndOperators[i + 1].AsNode();
 
                if (Options.OperatorPlacement == OperatorPlacementWhenWrappingPreference.BeginningOfLine)
                {
                    // convert: 
                    //      (a == b) && (c == d) to
                    //
                    //      (a == b)
                    //      && (c == d)
                    result.Add(Edit.UpdateBetween(left, _newlineBeforeOperatorTrivia, indentationTrivia, opToken));
                    result.Add(Edit.UpdateBetween(opToken, SingleWhitespaceTrivia, NoTrivia, right));
                }
                else
                {
                    // convert: 
                    //      (a == b) && (c == d) to
                    //
                    //      (a == b) &&
                    //      (c == d)
                    result.Add(Edit.UpdateBetween(left, SingleWhitespaceTrivia, NoTrivia, opToken));
                    result.Add(Edit.UpdateBetween(opToken, NewLineTrivia, indentationTrivia, right));
                }
            }
 
            return result.ToImmutableAndClear();
        }
 
        private ImmutableArray<Edit> GetUnwrapEdits()
        {
            var count = _exprsAndOperators.Length - 1;
            var result = new FixedSizeArrayBuilder<Edit>(count);
 
            for (var i = 0; i < count; i++)
            {
                result.Add(Edit.UpdateBetween(
                    _exprsAndOperators[i], SingleWhitespaceTrivia,
                    NoTrivia, _exprsAndOperators[i + 1]));
            }
 
            return result.MoveToImmutable();
        }
    }
}