File: MS\Internal\PtsHost\DtrList.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// 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.
 
//
// Description: Definition for DtrLits (Dirty Text Range List) which 
//              contains information about tree changes.
//
 
 
using System;
using System.Diagnostics;
 
namespace MS.Internal.PtsHost
{
    // ----------------------------------------------------------------------
    // DtrLits (Dirty Text Range List) manages sorted list of accumulated 
    // tree changes.
    // All entries in DtrList are sorted with respect to StartIndex.
    // StartIndex is always representing offset in the tree before any 
    // changes.
    // ----------------------------------------------------------------------
    internal sealed class DtrList
    {
        // ------------------------------------------------------------------
        // Construct a DtrList. The array of DTRs initially can store up to
        // 4 entries. Upon adding elements the capacity increased in multiples 
        // of two as required.
        // ------------------------------------------------------------------
        internal DtrList()
        {
            _dtrs = new DirtyTextRange[_defaultCapacity];
            _count = 0;
        }
 
        // ------------------------------------------------------------------
        // Merge new DTR with list of exising DTRs:
        // 1) Convert startIndex to index reflecting position before any changes.
        // 2) Merge it with existing list of DTRs.
        //
        //      dtr - New DTR to be merged with exising list of DTRs.
        // ------------------------------------------------------------------
        internal void Merge(DirtyTextRange dtr)
        {
            bool merge = false;
            int i = 0;
            int startIndexOld = dtr.StartIndex;
 
            // 1) Convert StartIndex to index reflecting position before any changes.
            //    And find out if there is a need to merge DTRs
            if (_count > 0)
            {
                while (i < _count)
                {
                    // a) New DTR starts before the next one. In this case there are
                    //    two possibilities:
                    //    * new DTR does not intersect with the beginning of the next DTR,
                    //      in this case insert new DTR before the next one.
                    //    * new DTR does intersect with the beginning of the next DTR,
                    //      in this case merge these 2 DTRs
                    if (startIndexOld < _dtrs[i].StartIndex)
                    {
                        if (startIndexOld + dtr.PositionsRemoved > _dtrs[i].StartIndex)
                        {
                            merge = true;
                        }
                        // else new dtr has to be inserted at position 'i'
                        break;
                    }
                    // b) New DTR starts in the range of the previous DTR. In this case
                    //    merge these 2 DTRs
                    else if (startIndexOld <= _dtrs[i].StartIndex + _dtrs[i].PositionsAdded)
                    {
                        // merge with existing dtr at position 'i'
                        merge = true;
                        break;
                    }
                    // c) No intersection has been found, go to the next DTR in the list.
                    startIndexOld -= _dtrs[i].PositionsAdded - _dtrs[i].PositionsRemoved;
                    ++i;
                }
                // Update dcp of the new DTR, to reflect position before any tree changes.
                dtr.StartIndex = startIndexOld;
            }
 
            // 2) Insert new DTR into the list, merge if necessary
            if (i < _count)
            {
                if (merge)
                {
                    // The simplest way to merge these two DTRs is to add together 
                    // cchAdded/cchDeleted form both DTRs, but it will invalidate more
                    // than required. Formula used below is more accurate.
 
                    // a) New DTR does intersect with the beginning of the next DTR,
                    //    in this case merge these 2 DTRs.
                    // * dcp = dcpN (since it starts before dcpO)
                    // * add = addN + addO - min(addO, delN - (dcpO - dcpN))
                    // * del = delN + delO - min(addO, delN - (dcpO - dcpN))
                    // NOTE: dcpO - dcpN is always <= delN
                    if (dtr.StartIndex < _dtrs[i].StartIndex)
                    {
                        int delta  = _dtrs[i].StartIndex - dtr.StartIndex;
                        int adjust = Math.Min(_dtrs[i].PositionsAdded, dtr.PositionsRemoved - delta);
                        _dtrs[i].StartIndex        = dtr.StartIndex;
                        _dtrs[i].PositionsAdded   += dtr.PositionsAdded   - adjust;
                        _dtrs[i].PositionsRemoved += dtr.PositionsRemoved - adjust;
                    }
                    // b) New DTR starts in the range of the previous DTR. In this case
                    //    merge these 2 DTRs.
                    // * dcp = dcpO (since it starts before dcpN)
                    // * add = addN + addO - min(delN, addO - (dcpN - dcpO))
                    // * del = delN + delO - min(delN, addO - (dcpN - dcpO))
                    // NOTE: dcpN - dcpO is always <= addO
                    else
                    {
                        int delta  = dtr.StartIndex - _dtrs[i].StartIndex;
                        int adjust = Math.Min(dtr.PositionsRemoved, _dtrs[i].PositionsAdded - delta);
                        //_dtrs[i].dcp: no need to change it
                        _dtrs[i].PositionsAdded   += dtr.PositionsAdded   - adjust;
                        _dtrs[i].PositionsRemoved += dtr.PositionsRemoved - adjust;
                    }
 
                    // Prefer not highlight layer change when merging
                    _dtrs[i].FromHighlightLayer &= dtr.FromHighlightLayer;
                }
                else
                {
                    // The new DTR has to be inserted before DTR at position 'i'.
                    if (_count == _dtrs.Length) { Resize(); }
                    Array.Copy(_dtrs, i, _dtrs, i+1, _count-i);
                    _dtrs[i] = dtr;
                    ++_count;
                }
                MergeWithNext(i);
            }
            else
            {
                // The new DTR has to be appended to the end of the list.
                if (_count == _dtrs.Length) { Resize(); }
                _dtrs[_count] = dtr;
                ++_count;
            }
 
#if TEXTPANELLAYOUTDEBUG
            System.Text.StringBuilder msg = new System.Text.StringBuilder();
            msg.Append("Merge DTR (" + dtr.StartIndex + "," + dtr.PositionsAdded + "," + dtr.PositionsRemoved + ") ->");
            for (i = 0; i < _count; i++)
            {
                msg.Append(" (" + _dtrs[i].StartIndex + "," + _dtrs[i].PositionsAdded + "," + _dtrs[i].PositionsRemoved + ")");
            }
            TextPanelDebug.Log(msg.ToString(), TextPanelDebug.Category.ContentChange);
#endif
        }
 
        /// <summary>
        /// Merges the DtrList into a single DirtyTextRange containing all
        /// ranges in the list.
        /// </summary>
        /// <returns>A DirtyTextRange containing all ranges in this list</returns>
        internal DirtyTextRange GetMergedRange()
        {
            if (_count > 0)
            {
                DirtyTextRange range = _dtrs[0];
 
                int previousOffset = range.StartIndex;
                int positionsAdded = range.PositionsAdded;
                int positionsRemoved = range.PositionsRemoved;
                bool fromHighlightLayer = range.FromHighlightLayer;
 
                for (int i = 1; i < _count; i++)
                {
                    range = _dtrs[i];
 
                    int rangeDistance = range.StartIndex - previousOffset;
                    positionsAdded = rangeDistance + range.PositionsAdded;
                    positionsRemoved = rangeDistance + range.PositionsRemoved;
 
                    // Prefer not from highlight layer when squashing
                    fromHighlightLayer &= range.FromHighlightLayer;
 
                    previousOffset = range.StartIndex;
                }
 
                return new DirtyTextRange(_dtrs[0].StartIndex, positionsAdded, positionsRemoved, fromHighlightLayer);
            }
 
            return new DirtyTextRange(0, 0, 0, false);
        }
 
        // ------------------------------------------------------------------
        // Retrieve list of dtrs from range.
        //
        //      dcpNew - Distance from the beginning of TextContainer after all
        //              tree changes.
        //      cchOld - Number of characters in the range, but before any 
        //              tree changes.
        //
        // Returns: List of DRTs for specified range.
        // ------------------------------------------------------------------
        internal DtrList DtrsFromRange(int dcpNew, int cchOld)
        {
            DtrList dtrs = null;
            int i = 0;
            int first, last;
            int positionsAdded = 0;
 
            // Find the first dtr intersecting with the specified range.
            // Since DTRs store dcp before any changes, during iteration
            // accumulate positionsAdded (number of characters added to the tree
            // up to the current point).
            while (i < _count)
            {
                if (dcpNew <= _dtrs[i].StartIndex + positionsAdded + _dtrs[i].PositionsAdded)
                {
                    break;
                }
                positionsAdded += _dtrs[i].PositionsAdded - _dtrs[i].PositionsRemoved;
                ++i;
            }
            first = i;
 
            // Find the last dtr intersecting with the specified range.
            // dcpNew-positionsAdded points to position before any tree changes, from
            // where we start counting cchOld.
            // Do not add characters (positionsAdded), since start position has been already found.
            while (i < _count)
            {
                if (dcpNew - positionsAdded + cchOld <= _dtrs[i].StartIndex + _dtrs[i].PositionsRemoved)
                {
                    // If there is no intersection with the current DTR, go to the previous one.
                    if (dcpNew - positionsAdded + cchOld < _dtrs[i].StartIndex)
                    {
                        --i;
                    }
                    break;
                }
                ++i;
            }
            last = (i < _count) ? i : _count-1;
 
            // If there are DTRs in the specified range, create new DtrList object
            if (last >= first)
            {
                dtrs = new DtrList();
                while (last >= first)
                {
                    // Since dcpNew is after tree changes, add positionsAdded to all dtrs
                    // to build dtr list relative to dcpNew position.
                    DirtyTextRange dtr = _dtrs[first];
                    dtr.StartIndex += positionsAdded;
                    dtrs.Append(dtr);
                    ++first;
                }
            }
            return dtrs;
        }
 
        // ------------------------------------------------------------------
        // Merge DRT at position 'index' with the next one, if possible.
        //
        //      index - Index of the DTR to be merged with next one.
        // ------------------------------------------------------------------
        private void MergeWithNext(int index)
        {
            while (index + 1 < _count)
            {
                DirtyTextRange dtrNext = _dtrs[index+1];
 
                // DTR starts in the range of the previous DTR. In this case
                // merge these 2 DTRs.
                if (dtrNext.StartIndex <= _dtrs[index].StartIndex + _dtrs[index].PositionsRemoved)
                {
                    //_dtrs[index].dcp: no need to change it
                    _dtrs[index].PositionsAdded += dtrNext.PositionsAdded;
                    _dtrs[index].PositionsRemoved += dtrNext.PositionsRemoved;
 
                    // Prefer not highlight layer change when merging
                    _dtrs[index].FromHighlightLayer &= dtrNext.FromHighlightLayer;
 
                    // Remove merged entry
                    for (int i = index + 2; i < _count; i++)
                    {
                        _dtrs[i - 1] = _dtrs[i];
                    }
 
                    --_count;
                }
                else
                {
                    break;
                }
            }
        }
 
        // ------------------------------------------------------------------
        // Append new DTR to the list.
        //
        //      dtr - DTR to be appended to the list.
        // ------------------------------------------------------------------
        private void Append(DirtyTextRange dtr)
        {
            if (_count == _dtrs.Length) { Resize(); }
            _dtrs[_count] = dtr;
            ++_count;
        }
 
        // ------------------------------------------------------------------
        // Increases the capacity of the DTR array.
        // <new size> = <current size>*2.
        // ------------------------------------------------------------------
        private void Resize()
        {
            Debug.Assert(_dtrs.Length > 0);
 
            // Allocate new array and copy all existing entries into it
            DirtyTextRange [] newdtrs = new DirtyTextRange[_dtrs.Length * 2];
            Array.Copy(_dtrs, newdtrs, _dtrs.Length);
            _dtrs = newdtrs;
        }
 
        // ------------------------------------------------------------------
        // Array like access to list of DTRs.
        // ------------------------------------------------------------------
        internal int Length 
        { 
            get { return _count; } 
        }
        internal DirtyTextRange this[int index]
        {
            get { return _dtrs[index]; }
        }
 
        // ------------------------------------------------------------------
        // Array of DTRs. This array stores sorted list of DTRs.
        // ------------------------------------------------------------------
        private DirtyTextRange [] _dtrs;
 
        // ------------------------------------------------------------------
        // Default capacity of the DTRs array. The array capacity is always
        // increased in multiples of two as required: 4*(2^N).
        // ------------------------------------------------------------------
        private const int _defaultCapacity = 4;
        private int _count;
    }
}