File: System\Windows\Documents\TextRangeEditLists.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.
 
using MS.Internal;
 
//
// Description: Internal static class representing a group of methods
//              for list editing
//
 
namespace System.Windows.Documents
{
    /// <summary>
    /// The TextRange class represents a pair of TextPositions, with many
    /// rich text editing operations exposed.
    /// </summary>
    internal static class TextRangeEditLists
    {
        // --------------------------------------------------------------------
        //
        // Internal Methods
        //
        // --------------------------------------------------------------------
 
        #region Internal Methods
 
        /// <summary>
        /// Merges two paragraphs followinng one another.
        /// The content of a second paragraph is moved into the end
        /// of the first one.
        /// </summary>
        /// <param name="firstParagraphOrBlockUIContainer">
        /// First of two merged paragraphs or BlockUIContainer.
        /// </param>
        /// <param name="secondParagraphOrBlockUIContainer">
        /// Second of two mered paragraphs or BlockUIContainer.
        /// </param>
        /// <returns>
        /// true if paragraphs have been merged; false if no actions where made.
        /// </returns>
        internal static bool MergeParagraphs(Block firstParagraphOrBlockUIContainer, Block secondParagraphOrBlockUIContainer)
        {
            if (!ParagraphsAreMergeable(firstParagraphOrBlockUIContainer, secondParagraphOrBlockUIContainer))
            {
                return false; // Cannot mearge these paragraphs.
            }
 
            // Store parent list item of a second paragraph -
            // to correct its structure after the merge
            ListItem secondListItem = secondParagraphOrBlockUIContainer.PreviousBlock == null ? secondParagraphOrBlockUIContainer.Parent as ListItem : null;
 
            if (secondListItem != null && secondListItem.PreviousListItem == null && secondParagraphOrBlockUIContainer.NextBlock is List)
            {
                // The second paragraph is a first list item in some list.
                // It has a sublists in it, so this sublist must be unindented
                // to avoid double bulleted line.
                List sublistOfSecondParagraph = (List)secondParagraphOrBlockUIContainer.NextBlock;
                if (sublistOfSecondParagraph.ElementEnd.CompareTo(secondListItem.ContentEnd) == 0)
                {
                    secondListItem.Reposition(null, null);
                }
                else
                {
                    secondListItem.Reposition(sublistOfSecondParagraph.ElementEnd, secondListItem.ContentEnd);
                }
                // At this point the schema is temporaty broken: the secondParagraph and the sublistOfSecondParagraph have List as a parent
                sublistOfSecondParagraph.Reposition(null, null);
                // The schema is repared as to sublistOfSecondParagraph concern, but still broken for secondParagraph - must be corrected in the following code
            }
 
            // Move the second paragraph out of its wrappers separating from the first paragraph (if any).
            // We can not use RepositionWithContent because it would destroy
            // all pointers and ranges within a moved paragraph.
            // Instead we reposition elements around the two paragraphs.
            while (secondParagraphOrBlockUIContainer.ElementStart.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart)
            {
                TextElement parentBlock = (TextElement)secondParagraphOrBlockUIContainer.Parent; 
                Invariant.Assert(parentBlock != null);
                Invariant.Assert(TextSchema.AllowsParagraphMerging(parentBlock.GetType()));
 
                if (secondParagraphOrBlockUIContainer.ElementEnd.CompareTo(parentBlock.ContentEnd) == 0)
                {
                    // Remove ancestor block if it becomes empty
                    parentBlock.Reposition(null, null);
                }
                else
                {
                    // Move ancestor's Start after the end of our paragraph
                    parentBlock.Reposition(secondParagraphOrBlockUIContainer.ElementEnd, parentBlock.ContentEnd);
                }
            }
 
            // Store a position after the second paragraph where list merging may be needed
            TextPointer positionAfterSecondParagraph = secondParagraphOrBlockUIContainer.ElementEnd.GetFrozenPointer(LogicalDirection.Forward);
 
            // Move the second paragraph to become an immediate following sibling of the first paragraph
            while (true)
            {
                TextElement previousBlock = secondParagraphOrBlockUIContainer.ElementStart.GetAdjacentElement(LogicalDirection.Backward) as TextElement;
                // Note: We cannot use Block.NextSibling property, because the structure is invalid during this process
 
                Invariant.Assert(previousBlock != null);
 
                if (previousBlock is Paragraph || previousBlock is BlockUIContainer)
                {
                    break;
                }
 
                Invariant.Assert(TextSchema.AllowsParagraphMerging(previousBlock.GetType()));
                previousBlock.Reposition(previousBlock.ContentStart, secondParagraphOrBlockUIContainer.ElementEnd);
            }
 
            // Now that paragraphs are next to each other merge them.
 
            // If one of paragraphs is empty we will apply special logic - to preserve a formatting from a non-empty one
            if (secondParagraphOrBlockUIContainer.TextRange.IsEmpty)
            {
                secondParagraphOrBlockUIContainer.RepositionWithContent(null);
            }
            else if (firstParagraphOrBlockUIContainer.TextRange.IsEmpty)
            {
                firstParagraphOrBlockUIContainer.RepositionWithContent(null);
            }
            else if (firstParagraphOrBlockUIContainer is Paragraph && secondParagraphOrBlockUIContainer is Paragraph)
            {
                // Do reposition magic for merging paragraph content
                // without destroying any pointers positioned in them.
                // Pull the second paragraph into the first one
                Invariant.Assert(firstParagraphOrBlockUIContainer.ElementEnd.CompareTo(secondParagraphOrBlockUIContainer.ElementStart) == 0);
                firstParagraphOrBlockUIContainer.Reposition(firstParagraphOrBlockUIContainer.ContentStart, secondParagraphOrBlockUIContainer.ElementEnd);
 
                // Store inline merging position
                TextPointer inlineMergingPosition = secondParagraphOrBlockUIContainer.ElementStart;
 
                // Now we can delete the second paragraph
                secondParagraphOrBlockUIContainer.Reposition(null, null);
 
                // Merge formatting elements at the point of paragraphs merging
                TextRangeEdit.MergeFormattingInlines(inlineMergingPosition);
            }
 
            // Merge ListItems wrapping first and second paragraphs.
            ListItem followingListItem = positionAfterSecondParagraph.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementStart 
                ? positionAfterSecondParagraph.GetAdjacentElement(LogicalDirection.Forward) as ListItem : null;
            if (followingListItem != null && followingListItem == secondListItem)
            {
                ListItem precedingListItem = positionAfterSecondParagraph.GetAdjacentElement(LogicalDirection.Backward) as ListItem;
                if (precedingListItem != null)
                {
                    // Merge the second list item with the preceding one
                    Invariant.Assert(positionAfterSecondParagraph.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementStart);
                    Invariant.Assert(positionAfterSecondParagraph.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementEnd);
                    precedingListItem.Reposition(precedingListItem.ContentStart, followingListItem.ElementEnd);
                    followingListItem.Reposition(null, null);
                }
            }
 
            // Merge lists at merge position
            MergeLists(positionAfterSecondParagraph);
 
            return true;
        }
 
        /// <summary>
        /// Like MergeLists, but will search over formatting elements when
        /// looking for Lists to merge.
        /// </summary>
        internal static bool MergeListsAroundNormalizedPosition(TextPointer mergePosition)
        {
            // Search forward for a List to merge with.
            TextPointer navigator = mergePosition.CreatePointer();
 
            while (navigator.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd)
            {
                navigator.MoveToNextContextPosition(LogicalDirection.Forward);
            }
 
            bool merged = MergeLists(navigator);
 
            // Search backward for a List to merge with.
            if (!merged)
            {
                navigator.MoveToPosition(mergePosition);
 
                while (navigator.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart)
                {
                    navigator.MoveToNextContextPosition(LogicalDirection.Backward);
                }
 
                merged = MergeLists(navigator);
            }
 
            return merged;
        }
 
        /// <summary>
        /// Merges two naighboring lists ending and starting at position mergePosition
        /// </summary>
        /// <param name="mergePosition">
        /// Position at with two List elements are expected to appear next to each other
        /// </param>
        /// <returns>
        /// true if there were two mergeable List elements and merge happened.
        /// false if there is no pair of List elements at the mergePosition.
        /// </returns>
        internal static bool MergeLists(TextPointer mergePosition)
        {
            if (mergePosition.GetPointerContext(LogicalDirection.Backward) != TextPointerContext.ElementEnd ||
                mergePosition.GetPointerContext(LogicalDirection.Forward) != TextPointerContext.ElementStart)
            {
                return false;
            }
 
            List precedingList = mergePosition.GetAdjacentElement(LogicalDirection.Backward) as List;
            List followingList = mergePosition.GetAdjacentElement(LogicalDirection.Forward) as List;
 
            if (precedingList == null || followingList == null)
            {
                return false;
            }
 
            precedingList.Reposition(precedingList.ContentStart, followingList.ElementEnd);
            followingList.Reposition(null, null);
 
            // We need to set appropriate FlowDirection property on the new List and its paragraph children. 
            // We take the FlowDirection value from the preceding list.
            TextRangeEdit.SetParagraphProperty(precedingList.ElementStart, precedingList.ElementEnd,
                Paragraph.FlowDirectionProperty, precedingList.GetValue(Paragraph.FlowDirectionProperty));
 
            return true;
        }
 
        // Returns true if all paragraphs in a range belong to the same block,
        // so they can be easily grouped and ungrouped.
        internal static bool IsListOperationApplicable(TextRange range)
        {
            // First check, if range start/end are parented by ListItems within the same parent list.
            if (IsRangeWithinSingleList(range))
            {
                return true;
            }
 
            // Adjust range end so that it does not affect a following paragraph.
            TextPointer end = (TextPointer)TextRangeEdit.GetAdjustedRangeEnd(range.Start, range.End);
 
            // Now try plain Paragraphs
            Block firstBlock = range.Start.ParagraphOrBlockUIContainer;
            Block lastBlock = end.ParagraphOrBlockUIContainer;
 
            if (firstBlock != null && lastBlock != null && firstBlock.Parent == lastBlock.Parent)
            {
                return true;
            }
 
            // Allow list editing at potential paragraph positions, this includes
            // positions in initial RichTextBox, empty TableCell, empty ListItem where paragraphs were not yet created.
            if (range.IsEmpty && TextPointerBase.IsAtPotentialParagraphPosition(range.Start))
            {
                return true;
            }
 
            return false;
        }
 
        internal static bool ConvertParagraphsToListItems(TextRange range, TextMarkerStyle markerStyle)
        {
            if (range.IsEmpty && TextPointerBase.IsAtPotentialParagraphPosition(range.Start))
            {
                TextPointer insertionPosition = TextRangeEditTables.EnsureInsertionPosition(range.Start);
                ((ITextRange)range).Select(insertionPosition, insertionPosition);
            }
 
            Block firstBlock = range.Start.ParagraphOrBlockUIContainer;
 
            TextPointer end = (TextPointer)TextRangeEdit.GetAdjustedRangeEnd(range.Start, range.End);
            Block lastBlock = end.ParagraphOrBlockUIContainer;
 
            // We assume that a range contains a sequence of one-level paragraphs.
            // Otherwise the operation is disabled.
            if (firstBlock == null || lastBlock == null || firstBlock.Parent != lastBlock.Parent ||
                firstBlock.Parent is ListItem && firstBlock.PreviousBlock == null)
            {
                // Either the paragraphs belong to different scopes or first of them has a bullet already.
                // We cannot convert them into bulleted lists.
                return false;
            }
 
            // Check that all top-level elements of selection are Paragraphs.
            // We do not apply the command to Tables or Sections.
            for (Block block = firstBlock; block != lastBlock && block != null; block = block.NextBlock)
            {
                if (block is Table || block is Section)
                {
                    return false;
                }
            }
 
            ListItem parentListItem = firstBlock.Parent as ListItem;
            if (parentListItem != null)
            {
                // Paragraphs are inside of ListItem already.
 
                // Split a current ListItem before each of selected blocks
                Block block = firstBlock;
                while (block != null)
                {
                    Block nextBlock = block == lastBlock ? null : block.ElementEnd.GetAdjacentElement(LogicalDirection.Forward) as Block;
 
                    Invariant.Assert(block.Parent is ListItem);
                    TextRangeEdit.SplitElement(block.ElementStart);
 
                    block = nextBlock;
                }
            }
            else
            {
                // Create a list around all paragraphs
                List list = new List();
                list.MarkerStyle = markerStyle;
                list.Apply(firstBlock, lastBlock);
 
                // Merge with neighboring lists
                //MergeLists(list.ElementEnd);  // start with End to not loose an instance of "list" during merging with the following list
                //MergeLists(list.ElementStart);
            }
 
            return true;
        }
 
        // Assumes that a range contains a sequence of same-level ListItems.
        // Converts all these ListItems into Paragraphs and
        // either adds them to preceding ListItem (as non-bulleted continuation)
        // or pulls them out of a List if they start in the beginning of a List
        internal static void ConvertListItemsToParagraphs(TextRange range)
        {
            ListItem firstListItem = TextPointerBase.GetListItem(range.Start);
            ListItem lastListItem = TextPointerBase.GetListItem((TextPointer)TextRangeEdit.GetAdjustedRangeEnd(range.Start, range.End));
 
            // The range must be in a sequence of ListItems belonging to one List wrapper
            if (firstListItem == null || lastListItem == null || firstListItem.Parent != lastListItem.Parent || !(firstListItem.Parent is List))
            {
                return;
            }
 
            List listToRemove = null;
 
            ListItem leadingListItem = firstListItem.PreviousListItem;
            if (leadingListItem != null)
            {
                // We have a leading ListItem, so pull selected items into it
                leadingListItem.Reposition(leadingListItem.ContentStart, lastListItem.ElementEnd);
            }
            else
            {
                // We do not have a leading ListItem. So pull selected items out of a list
 
                // Cut wrapping list after endListItem
                if (lastListItem.NextListItem != null)
                {
                    TextRangeEdit.SplitElement(lastListItem.ElementEnd);
                }
 
                // Set list to remove
                listToRemove = firstListItem.List;              
            }
 
            // Remove ListItems from all selected blocks
            ListItem listItem = firstListItem;
            while (listItem != null)
            {
                ListItem nextListItem = listItem.ElementEnd.GetAdjacentElement(LogicalDirection.Forward) as ListItem;
 
                // If this is an empty <ListItem></ListItem>, insert an explicit paragraph in it before deleting the list item.
                if (listItem.ContentStart.CompareTo(listItem.ContentEnd) == 0)
                {
                    TextRangeEditTables.EnsureInsertionPosition(listItem.ContentStart);
                }
 
                listItem.Reposition(null, null);
                listItem = listItem == lastListItem ? null : nextListItem;
            }
 
            // If we have a list to remove, remove it and set its FlowDirection to its children
            if (listToRemove != null)
            {
                FlowDirection flowDirection = (FlowDirection)listToRemove.GetValue(Paragraph.FlowDirectionProperty);                
                listToRemove.Reposition(null, null);
                TextRangeEdit.SetParagraphProperty(range.Start, range.End, Paragraph.FlowDirectionProperty, flowDirection);
            }
        }
 
        internal static void IndentListItems(TextRange range)
        {
            ListItem firstListItem = TextPointerBase.GetImmediateListItem(range.Start);
            ListItem lastListItem = TextPointerBase.GetImmediateListItem((TextPointer)TextRangeEdit.GetAdjustedRangeEnd(range.Start, range.End));
 
            // The range must be in a sequence of ListItems belonging to one List wrapper
            if (firstListItem == null || lastListItem == null || 
                firstListItem.Parent != lastListItem.Parent || 
                !(firstListItem.Parent is List))
            {
                return;
            }
 
            // Identify a ListItem which will become a leading item for this potential sublist
            ListItem leadingListItem = firstListItem.PreviousListItem;
            if (leadingListItem == null)
            {
                // There is no leading list item for this group. Indentation is impossible
                return;
            }
 
            // Get current List
            List list = (List)firstListItem.Parent;
 
            // Wrap these items into a List - inheriting all properties from our current list
            List indentedList = (List)TextRangeEdit.InsertElementClone(firstListItem.ElementStart, lastListItem.ElementEnd, list);
 
            // Wrap the leading ListItem to include the sublist
            leadingListItem.Reposition(leadingListItem.ContentStart, indentedList.ElementEnd);
 
            // Unwrap sublist from the last selected list item (to keep it on its level)
            Paragraph leadingParagraphOfLastItem = lastListItem.Blocks.FirstBlock as Paragraph;
            if (leadingParagraphOfLastItem != null)
            {
                // Unindenting all items of a sublist - if it is the only following element of a list
                List nestedListOfLastItem = leadingParagraphOfLastItem.NextBlock as List;
                if (nestedListOfLastItem != null && nestedListOfLastItem.NextBlock == null)
                {
                    lastListItem.Reposition(lastListItem.ContentStart, nestedListOfLastItem.ElementStart);
                    nestedListOfLastItem.Reposition(null, null);
                }
            }
 
            // Merge with neighboring lists
            MergeLists(indentedList.ElementStart);
            // No need in merging at nestedList.ElementEnd as ListItem ends there.
        }
 
        internal static bool UnindentListItems(TextRange range)
        {
            // If listitems in this range cross a list boundary, we cannot unindent them.
            if (!IsRangeWithinSingleList(range))
            {
                return false;
            }
 
            ListItem firstListItem = TextPointerBase.GetListItem(range.Start);
            ListItem lastListItem = TextPointerBase.GetListItem((TextPointer)TextRangeEdit.GetAdjustedRangeEnd(range.Start, range.End));
 
            // At this point it is possible that lastListItem is a child of
            // firstListItem.
            //
            // This is due to a special case in IsRangeWithinSingleList
            // which allows the input TextRange to cross List boundaries only
            // in the case where the TextRange ends adjacent to an insertion
            // position at the same level as the start, e.g.:
            //
            //    - parent item
            //      - start item (range starts here)
            //          - child item (range ends here)
            //      <start item must not have any siblings>
            //
            // Here we check for that special case and ensure that
            // lastListItem is at the same level as firstListItem.
 
            TextElement parent = (TextElement)lastListItem.Parent;
 
            while (parent != firstListItem.Parent)
            {
                lastListItem = parent as ListItem;
                parent = (TextElement)parent.Parent;
            }
            if (lastListItem == null)
            {
                // This can happen if the input is a fragment, a collection
                // of ListItems not parented by an outer List.
                return false;
            }
 
            // Cut wrapping list before startListItem
            if (firstListItem.PreviousListItem != null)
            {
                TextRangeEdit.SplitElement(firstListItem.ElementStart);
            }
 
            // Cut wrapping list after endListItem
            if (lastListItem.NextListItem != null)
            {
                TextRangeEdit.SplitElement(lastListItem.ElementEnd);
            }
 
            // Remove List wrapper from selected items
            List unindentedList = (List)firstListItem.Parent;
 
            // Check whether we have outer ListItem
            ListItem outerListItem = unindentedList.Parent as ListItem;
            if (outerListItem != null)
            {
                // Selected items belong to a nested list.
                // So we need to pull them to the level of enclosing ListItem, i.e. cut this ListItem.
                // In this case we also need to include trailing list into the last of selected items
 
                // Remove a wrapping List from selected items
                unindentedList.Reposition(null, null);
 
                // Remember end position of outerListItem to pull any trailing list or other blocks into the last of selected listitems
                TextPointer outerListItemEnd = outerListItem.ContentEnd;
 
                if (outerListItem.ContentStart.CompareTo(firstListItem.ElementStart) == 0)
                {
                    // There is nothing before first list item; so outer list item would be empty - just delete it
                    outerListItem.Reposition(null, null);
                }
                else
                {
                    // Wrap all stuff preceding firstListItem in outerListItem
                    outerListItem.Reposition(outerListItem.ContentStart, firstListItem.ElementStart);
                }
 
                if (outerListItemEnd.CompareTo(lastListItem.ElementEnd) == 0)
                {
                    // There are no following siblings to pull into last selected item; do nothing.
                }
                else
                {
                    // Pull trailing items (following siblings to the selected ones) into the last selected item
 
                    // Remember a position to merge any trailing list after lastListItem
                    TextPointer mergePosition = lastListItem.ContentEnd;
                    
                    // Reposition last selectd ListItem so that it includes trailing list (or other block) as its children
                    lastListItem.Reposition(lastListItem.ContentStart, outerListItemEnd);
 
                    // Merge any trailing list with a sublist outdented with our listitem
                    MergeLists(mergePosition);
                }
            }
            else
            {
                // Selected items are not in nested list.
                // We need to simply unwrap them and convert to paragraphs
 
                TextPointer start = unindentedList.ElementStart;
                TextPointer end = unindentedList.ElementEnd;
 
                // Save the list's FlowDirection value, to apply later to its children.
                object listFlowDirectionValue = unindentedList.GetValue(Paragraph.FlowDirectionProperty);
 
                // Remove a wrapping List from selected items
                unindentedList.Reposition(null, null);
 
                // Remove ListItems from all selected items
                ListItem listItem = firstListItem;
                while (listItem != null)
                {
                    ListItem nextListItem = listItem.ElementEnd.GetAdjacentElement(LogicalDirection.Forward) as ListItem;
 
                    // If this is an empty <ListItem></ListItem>, insert an explicit paragraph in it before deleting the list item.
                    if (listItem.ContentStart.CompareTo(listItem.ContentEnd) == 0)
                    {
                        TextRangeEditTables.EnsureInsertionPosition(listItem.ContentStart);
                    }
 
                    listItem.Reposition(null, null);
                    listItem = listItem == lastListItem ? null : nextListItem;
                }
 
                // Apply FlowDirection of the list just deleted to all its children.
                TextRangeEdit.SetParagraphProperty(start, end, Paragraph.FlowDirectionProperty, listFlowDirectionValue);                
 
                // Merge lists on boundaries
                MergeLists(start);
                MergeLists(end);
            }
 
            return true;
        }
 
        // Predicate which returns true if list items in this range are within the scope of the same parent list.
        private static bool IsRangeWithinSingleList(TextRange range)
        {
            ListItem startListItem = TextPointerBase.GetListItem(range.Start);
 
            // Adjust range end so that it does not affect a following paragraph.
            TextPointer end = (TextPointer)TextRangeEdit.GetAdjustedRangeEnd(range.Start, range.End);
            ListItem endListItem = TextPointerBase.GetListItem(end);
 
            // Check if the ListItems belong to one List wrapper.
            if (startListItem != null && endListItem != null && startListItem.Parent == endListItem.Parent)
            {
                return true;
            }
 
            // In case of nested lists, it may be the case that start and end list item do not belong to one list wrapper,
            // yet no visual list boundary is crossed.
            // e.g.
            //  * aa
            //      * bb
            //      * cc
            // Special case so that list operations are applicable in this scenario.
            if (startListItem != null && endListItem != null)
            {
                while (end.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd)
                {
                    if (end.Parent == startListItem.Parent)
                    {
                        return true;
                    }
                    end = end.GetNextContextPosition(LogicalDirection.Forward);
                }
            }
 
            return false;
        }
 
        // Checks whether two paragraphs are meargeable.
        // To be meargeable they need to be separated by s sequence of closing, then opening
        // tags only.
        // And all tags must be Sections/Lists/ListItems only.
        internal static bool ParagraphsAreMergeable(Block firstParagraphOrBlockUIContainer, Block secondParagraphOrBlockUIContainer)
        {
            if (firstParagraphOrBlockUIContainer == null || secondParagraphOrBlockUIContainer == null || 
                firstParagraphOrBlockUIContainer == secondParagraphOrBlockUIContainer)
            {
                return false; // nothing to merge
            }
 
            TextPointer position = firstParagraphOrBlockUIContainer.ElementEnd;
            TextPointer startOfSecondParagraph = secondParagraphOrBlockUIContainer.ElementStart;
 
            // Skip and check all closing tags (if any)
            while (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd)
            {
                if (!TextSchema.AllowsParagraphMerging(position.Parent.GetType()))
                {
                    return false; // Crossing hard-structured element. Paragraphs are not meargeable.
                }
 
                position = position.GetNextContextPosition(LogicalDirection.Forward);
            }
 
            // Skip and check all opening tags (if any)
            while (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementStart)
            {
                if (position.CompareTo(startOfSecondParagraph) == 0)
                {
                    // Successfully skipped all tags, and reached the seconfParagraph.
                    // The paragraphs are meargeable.
                    return true;
                }
 
                position = position.GetNextContextPosition(LogicalDirection.Forward);
 
                if (!TextSchema.AllowsParagraphMerging(position.Parent.GetType()))
                {
                    return false; // Crossing hardd-structured element. Paragraphs are not meargeable.
                }
            }
 
            // Non-tag run found. Paragraphs are not meargeable.
            return false;
        }
 
        // Checks if start and end positions are parented by a List.
        // If so, unindents list items between (start - start's list end) or (end's list start - end)  
        // until they are parented by a top level list.
        // Then, if needed, splits the list(s) at start and/or end positions.
        // Returns false if splitting is not successful due to a failing unindent operation on any nested lists.
        internal static bool SplitListsForFlowDirectionChange(TextPointer start, TextPointer end, object newFlowDirectionValue)
        {
            ListItem startListItem = start.GetListAncestor();
 
            // Unindent startListItem's list to prepare for a split, if the List's FlowDirection value is different.
            if (startListItem != null && 
                startListItem.List != null && // Check for unparented list items
                !TextSchema.ValuesAreEqual(/*newValue*/newFlowDirectionValue, /*currentValue*/startListItem.List.GetValue(Paragraph.FlowDirectionProperty)))
            {
                while (startListItem != null &&
                    startListItem.List != null &&
                    startListItem.List.Parent is ListItem)
                {
                    // startListItem is within a nested List.
                    if (!UnindentListItems(new TextRange(start, GetPositionAfterList(startListItem.List))))
                    {
                        return false;
                    }
                    startListItem = start.GetListAncestor();
                }
            }
            
            ListItem endListItem = end.GetListAncestor();
 
            // Unindent endListItem's list to prepare for a split, if the List's FlowDirection value is different.
            if (endListItem != null &&
                endListItem.List != null && 
                !TextSchema.ValuesAreEqual(/*newValue*/newFlowDirectionValue, /*currentValue*/endListItem.List.GetValue(Paragraph.FlowDirectionProperty)))
            {
                if (startListItem != null && startListItem.List != null &&
                    endListItem.List.ElementEnd.CompareTo(startListItem.List.ElementEnd) < 0)
                {
                    // endListItem's List is contained within startListItem's List. 
                    // No need to unindent endListItem.
                }
                else
                {
                    while (endListItem != null &&
                        endListItem.List !=  null && 
                        endListItem.List.Parent is ListItem)
                    {
                        // endListItem is within a nested List.
                        if (!UnindentListItems(new TextRange(endListItem.List.ContentStart, GetPositionAfterList(endListItem.List))))
                        {
                            return false;
                        }
                        endListItem = end.GetListAncestor();
                    }
                }
            }
 
            // Split list(s) at boundary position(s) if 
            //  1. startListItem is not the first list item within its list (or endListItem is not the last one)
            //  and
            //  2. start/end's parent List's flow direction value is different than the new value being set
 
            if ((startListItem = start.GetListAncestor()) != null && startListItem.PreviousListItem != null &&
                startListItem.List != null && // Check for unparented list items
                (!TextSchema.ValuesAreEqual(/*newValue*/newFlowDirectionValue, /*currentValue*/startListItem.List.GetValue(Paragraph.FlowDirectionProperty))))
            {
                Invariant.Assert(!(startListItem.List.Parent is ListItem), "startListItem's list must not be nested!");
                TextRangeEdit.SplitElement(startListItem.ElementStart);
            }
 
            if ((endListItem = end.GetListAncestor()) != null &&
                endListItem.List != null && // Check for unparented list items
                (!TextSchema.ValuesAreEqual(/*newValue*/newFlowDirectionValue, /*currentValue*/endListItem.List.GetValue(Paragraph.FlowDirectionProperty))))
            {
                // Walk up from endListItem to find the topmost listitem that contains it.
                if (endListItem.List.Parent is ListItem)
                {
                    while (endListItem.List != null && endListItem.List.Parent is ListItem)
                    {
                        endListItem = (ListItem)endListItem.List.Parent;
                    }
                }
                if (endListItem.List != null && endListItem.NextListItem != null)
                {
                    Invariant.Assert(!(endListItem.List.Parent is ListItem), "endListItem's list must not be nested!");
                    TextRangeEdit.SplitElement(endListItem.ElementEnd);
                }
            }
 
            return true;
        }
 
        // Finds an insertion position after the list
        private static TextPointer GetPositionAfterList(List list)
        {
            Invariant.Assert(list != null, "list cannot be null");
 
            TextPointer adjustedEnd = list.ElementEnd.GetInsertionPosition(LogicalDirection.Backward);
            if (adjustedEnd != null)
            {
                adjustedEnd = adjustedEnd.GetNextInsertionPosition(LogicalDirection.Forward);
            }
 
            if (adjustedEnd == null)
            {
                adjustedEnd = list.ElementEnd.TextContainer.End;
            }
 
            if (TextRangeEditTables.IsTableStructureCrossed(list.ElementEnd, adjustedEnd))
            {
                adjustedEnd = list.ContentEnd;
            }
 
            return adjustedEnd;
        }
 
        #endregion Internal Methods
    }
}