File: AbstractAggregatedFormattingResult.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.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Formatting
{
    internal abstract class AbstractAggregatedFormattingResult : IFormattingResult
    {
        protected readonly SyntaxNode Node;
 
        private readonly IList<AbstractFormattingResult> _formattingResults;
        private readonly SimpleIntervalTree<TextSpan, TextSpanIntervalIntrospector>? _formattingSpans;
 
        private readonly CancellableLazy<IList<TextChange>> _lazyTextChanges;
        private readonly CancellableLazy<SyntaxNode> _lazyNode;
 
        public AbstractAggregatedFormattingResult(
            SyntaxNode node,
            IList<AbstractFormattingResult> formattingResults,
            SimpleIntervalTree<TextSpan, TextSpanIntervalIntrospector>? formattingSpans)
        {
            Contract.ThrowIfNull(node);
            Contract.ThrowIfNull(formattingResults);
 
            this.Node = node;
            _formattingResults = formattingResults;
            _formattingSpans = formattingSpans;
 
            _lazyTextChanges = new CancellableLazy<IList<TextChange>>(CreateTextChanges);
            _lazyNode = new CancellableLazy<SyntaxNode>(CreateFormattedRoot);
        }
 
        /// <summary>
        /// rewrite the node with the given trivia information in the map
        /// </summary>
        protected abstract SyntaxNode Rewriter(Dictionary<ValueTuple<SyntaxToken, SyntaxToken>, TriviaData> changeMap, CancellationToken cancellationToken);
 
        protected SimpleIntervalTree<TextSpan, TextSpanIntervalIntrospector> GetFormattingSpans()
            => _formattingSpans ?? SimpleIntervalTree.Create(new TextSpanIntervalIntrospector(), _formattingResults.Select(r => r.FormattedSpan));
 
        #region IFormattingResult implementation
 
        public bool ContainsChanges
        {
            get
            {
                return this.GetTextChanges(CancellationToken.None).Count > 0;
            }
        }
 
        public IList<TextChange> GetTextChanges(CancellationToken cancellationToken)
            => _lazyTextChanges.GetValue(cancellationToken);
 
        public SyntaxNode GetFormattedRoot(CancellationToken cancellationToken)
            => _lazyNode.GetValue(cancellationToken);
 
        private IList<TextChange> CreateTextChanges(CancellationToken cancellationToken)
        {
            using (Logger.LogBlock(FunctionId.Formatting_AggregateCreateTextChanges, cancellationToken))
            {
                // quick check
                var changes = CreateTextChangesWorker(cancellationToken);
 
                // formatted spans and formatting spans are different, filter returns to formatting span
                return _formattingSpans == null
                    ? changes
                    : changes.Where(s => _formattingSpans.HasIntervalThatIntersectsWith(s.Span)).ToList();
            }
        }
 
        private IList<TextChange> CreateTextChangesWorker(CancellationToken cancellationToken)
        {
            if (_formattingResults.Count == 1)
            {
                return _formattingResults[0].GetTextChanges(cancellationToken);
            }
 
            // pre-allocate list
            var count = _formattingResults.Sum(r => r.GetTextChanges(cancellationToken).Count);
            var result = new List<TextChange>(count);
            foreach (var formattingResult in _formattingResults)
            {
                result.AddRange(formattingResult.GetTextChanges(cancellationToken));
            }
 
            return result;
        }
 
        private SyntaxNode CreateFormattedRoot(CancellationToken cancellationToken)
        {
            using (Logger.LogBlock(FunctionId.Formatting_AggregateCreateFormattedRoot, cancellationToken))
            {
                // create a map
                var map = new Dictionary<ValueTuple<SyntaxToken, SyntaxToken>, TriviaData>();
 
                _formattingResults.Do(result => result.GetChanges(cancellationToken).Do(change => map.Add(change.Item1, change.Item2)));
 
                return Rewriter(map, cancellationToken);
            }
        }
 
        #endregion
    }
}