File: FormattingContext.IndentationData.cs
Web Access
Project: ..\..\..\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.Diagnostics;
using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Formatting
{
    internal partial class FormattingContext
    {
        /// <summary>
        /// data that will be used in an interval tree related to indentation.
        /// </summary>
        private abstract class IndentationData
        {
            public IndentationData(TextSpan textSpan)
                => this.TextSpan = textSpan;
 
            public TextSpan TextSpan { get; }
            public abstract int Indentation { get; }
 
            public IndentationData WithTextSpan(TextSpan span)
                => span == TextSpan ? this : WithTextSpanCore(span);
 
            protected abstract IndentationData WithTextSpanCore(TextSpan span);
        }
 
        private sealed class SimpleIndentationData : IndentationData
        {
            private readonly int _indentation;
 
            public SimpleIndentationData(TextSpan textSpan, int indentation)
                : base(textSpan)
            {
                _indentation = indentation;
            }
 
            public override int Indentation => _indentation;
 
            protected override IndentationData WithTextSpanCore(TextSpan span)
            {
                return new SimpleIndentationData(span, _indentation);
            }
        }
 
        private sealed class RelativeIndentationData : IndentationData
        {
            private const int UninitializedIndentationDelta = int.MinValue;
 
            private readonly FormattingContext _formattingContext;
            private readonly Func<FormattingContext, IndentBlockOperation, SyntaxToken> _effectiveBaseTokenGetter;
            private readonly Func<FormattingContext, IndentBlockOperation, SyntaxToken, int> _indentationDeltaGetter;
            private readonly Func<FormattingContext, SyntaxToken, int> _baseIndentationGetter;
 
            /// <summary>
            /// Caches the value produced by <see cref="GetOrComputeIndentationDelta"/>.
            /// </summary>
            /// <value>
            /// <see cref="UninitializedIndentationDelta"/> if the field is not yet initialized; otherwise, the value
            /// returned from <see cref="_indentationDeltaGetter"/>.
            /// </value>
            private int _lazyIndentationDelta;
 
            public RelativeIndentationData(FormattingContext formattingContext, int inseparableRegionSpanStart, TextSpan textSpan, IndentBlockOperation operation, Func<FormattingContext, IndentBlockOperation, SyntaxToken> effectiveBaseTokenGetter, Func<FormattingContext, IndentBlockOperation, SyntaxToken, int> indentationDeltaGetter, Func<FormattingContext, SyntaxToken, int> baseIndentationGetter)
                : base(textSpan)
            {
                _formattingContext = formattingContext;
                _effectiveBaseTokenGetter = effectiveBaseTokenGetter;
                _indentationDeltaGetter = indentationDeltaGetter;
                _baseIndentationGetter = baseIndentationGetter;
 
                _lazyIndentationDelta = UninitializedIndentationDelta;
 
                this.Operation = operation;
                this.InseparableRegionSpan = TextSpan.FromBounds(inseparableRegionSpanStart, textSpan.End);
            }
 
            private RelativeIndentationData(FormattingContext formattingContext, int inseparableRegionSpanStart, TextSpan textSpan, IndentBlockOperation operation, Func<FormattingContext, IndentBlockOperation, SyntaxToken> effectiveBaseTokenGetter, Func<FormattingContext, IndentBlockOperation, SyntaxToken, int> indentationDeltaGetter, Func<FormattingContext, SyntaxToken, int> baseIndentationGetter, int lazyIndentationDelta)
                : base(textSpan)
            {
                _formattingContext = formattingContext;
                _effectiveBaseTokenGetter = effectiveBaseTokenGetter;
                _indentationDeltaGetter = indentationDeltaGetter;
                _baseIndentationGetter = baseIndentationGetter;
 
                _lazyIndentationDelta = lazyIndentationDelta;
 
                this.Operation = operation;
                this.InseparableRegionSpan = TextSpan.FromBounds(inseparableRegionSpanStart, textSpan.End);
            }
 
            public TextSpan InseparableRegionSpan { get; }
            public IndentBlockOperation Operation { get; }
 
            public SyntaxToken EndToken
            {
                get { return this.Operation.EndToken; }
            }
 
            private int GetOrComputeIndentationDelta()
            {
                return LazyInitialization.EnsureInitialized(
                    ref _lazyIndentationDelta,
                    UninitializedIndentationDelta,
                    static self => self._indentationDeltaGetter(
                        self._formattingContext,
                        self.Operation,
                        self._effectiveBaseTokenGetter(self._formattingContext, self.Operation)),
                    this);
            }
 
            public override int Indentation => GetOrComputeIndentationDelta() + _baseIndentationGetter(_formattingContext, _effectiveBaseTokenGetter(_formattingContext, Operation));
 
            protected override IndentationData WithTextSpanCore(TextSpan span)
            {
                return new RelativeIndentationData(_formattingContext, InseparableRegionSpan.Start, span, Operation, _effectiveBaseTokenGetter, _indentationDeltaGetter, _baseIndentationGetter, _lazyIndentationDelta);
            }
        }
 
        /// <summary>
        /// Represents an indentation in which a fixed offset (<see cref="Adjustment"/>) is applied to a reference
        /// indentation amount (<see cref="BaseIndentationData"/>).
        /// </summary>
        private sealed class AdjustedIndentationData : IndentationData
        {
            public AdjustedIndentationData(TextSpan textSpan, IndentationData baseIndentationData, int adjustment)
                : base(textSpan)
            {
                Debug.Assert(adjustment != 0, $"Indentation with no adjustment should be represented by {nameof(BaseIndentationData)} directly.");
                Debug.Assert(baseIndentationData is not AdjustedIndentationData, $"Indentation data should only involve one layer of adjustment (multiples can be combined by adding the {nameof(Adjustment)} fields.");
 
                BaseIndentationData = baseIndentationData;
                Adjustment = adjustment;
            }
 
            /// <summary>
            /// The reference indentation data which needs to be adjusted.
            /// </summary>
            public IndentationData BaseIndentationData { get; }
 
            /// <summary>
            /// The adjustment to apply to the <see cref="IndentationData.Indentation"/> value providede by
            /// <see cref="BaseIndentationData"/>.
            /// </summary>
            public int Adjustment { get; }
 
            public override int Indentation => BaseIndentationData.Indentation + Adjustment;
 
            protected override IndentationData WithTextSpanCore(TextSpan span)
            {
                return new AdjustedIndentationData(span, BaseIndentationData, Adjustment);
            }
        }
    }
}