File: System\windows\Documents\TextEditorSelection.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: A component of TextEditor supporting selection and navigation
//
 
namespace System.Windows.Documents
{
    using MS.Internal;
    using System.Globalization;
    using System.Threading;
    using System.ComponentModel;
    using System.Text;
    using System.Collections; // ArrayList
    using System.Runtime.InteropServices;
    using System.Security; // SecurityCritical attribute.
 
    using System.Windows.Threading;
    using System.Windows.Input;
    using System.Windows.Controls; // ScrollChangedEventArgs
    using System.Windows.Controls.Primitives;  // CharacterCasing, TextBoxBase
    using System.Windows.Media;
    using System.Windows.Markup;
 
    using MS.Utility;
    using MS.Win32;
    using MS.Internal.Documents;
    using MS.Internal.Commands; // CommandHelpers
 
    /// <summary>
    /// Text editing service for controls.
    /// </summary>
    internal static class TextEditorSelection
    {
        //------------------------------------------------------
        //
        //  Class Internal Methods
        //
        //------------------------------------------------------
 
        #region Class Internal Methods
 
        // Registers all text editing command handlers for a given control type
 
        internal static void _RegisterClassHandlers(Type controlType, bool registerEventListeners)
        {
            // Shared handlers used multiple times below.
            ExecutedRoutedEventHandler nyiCommandHandler = new ExecutedRoutedEventHandler(OnNYICommand);
            CanExecuteRoutedEventHandler queryStatusCaretNavigationHandler = new CanExecuteRoutedEventHandler(OnQueryStatusCaretNavigation);
            CanExecuteRoutedEventHandler queryStatusKeyboardSelectionHandler = new CanExecuteRoutedEventHandler(OnQueryStatusKeyboardSelection);
            
            // Standard Commands: Select All
            // -----------------------------
            CommandHelpers.RegisterCommandHandler(controlType, ApplicationCommands.SelectAll, new ExecutedRoutedEventHandler(OnSelectAll), queryStatusKeyboardSelectionHandler, KeySelectAll, nameof(SR.KeySelectAllDisplayString));
 
            // Editing Commands : Caret Navigation
            // -----------------------------------
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveRightByCharacter, new ExecutedRoutedEventHandler(OnMoveRightByCharacter), queryStatusCaretNavigationHandler, KeyGesture.CreateFromResourceStrings(KeyMoveRightByCharacter, nameof(SR.KeyMoveRightByCharacterDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveLeftByCharacter, new ExecutedRoutedEventHandler(OnMoveLeftByCharacter), queryStatusCaretNavigationHandler, KeyGesture.CreateFromResourceStrings(KeyMoveLeftByCharacter, nameof(SR.KeyMoveLeftByCharacterDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveRightByWord, new ExecutedRoutedEventHandler(OnMoveRightByWord), queryStatusCaretNavigationHandler, KeyGesture.CreateFromResourceStrings(KeyMoveRightByWord, nameof(SR.KeyMoveRightByWordDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveLeftByWord, new ExecutedRoutedEventHandler(OnMoveLeftByWord), queryStatusCaretNavigationHandler, KeyGesture.CreateFromResourceStrings(KeyMoveLeftByWord, nameof(SR.KeyMoveLeftByWordDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveDownByLine, new ExecutedRoutedEventHandler(OnMoveDownByLine), queryStatusCaretNavigationHandler, KeyGesture.CreateFromResourceStrings(KeyMoveDownByLine, nameof(SR.KeyMoveDownByLineDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveUpByLine, new ExecutedRoutedEventHandler(OnMoveUpByLine), queryStatusCaretNavigationHandler, KeyGesture.CreateFromResourceStrings(KeyMoveUpByLine, nameof(SR.KeyMoveUpByLineDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveDownByParagraph, new ExecutedRoutedEventHandler(OnMoveDownByParagraph), queryStatusCaretNavigationHandler, KeyGesture.CreateFromResourceStrings(KeyMoveDownByParagraph, nameof(SR.KeyMoveDownByParagraphDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveUpByParagraph, new ExecutedRoutedEventHandler(OnMoveUpByParagraph), queryStatusCaretNavigationHandler, KeyGesture.CreateFromResourceStrings(KeyMoveUpByParagraph, nameof(SR.KeyMoveUpByParagraphDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveDownByPage, new ExecutedRoutedEventHandler(OnMoveDownByPage), queryStatusCaretNavigationHandler, KeyGesture.CreateFromResourceStrings(KeyMoveDownByPage, nameof(SR.KeyMoveDownByPageDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveUpByPage, new ExecutedRoutedEventHandler(OnMoveUpByPage), queryStatusCaretNavigationHandler, KeyGesture.CreateFromResourceStrings(KeyMoveUpByPage, nameof(SR.KeyMoveUpByPageDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToLineStart, new ExecutedRoutedEventHandler(OnMoveToLineStart), queryStatusCaretNavigationHandler, KeyGesture.CreateFromResourceStrings(KeyMoveToLineStart, nameof(SR.KeyMoveToLineStartDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToLineEnd, new ExecutedRoutedEventHandler(OnMoveToLineEnd), queryStatusCaretNavigationHandler, KeyGesture.CreateFromResourceStrings(KeyMoveToLineEnd, nameof(SR.KeyMoveToLineEndDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToColumnStart, nyiCommandHandler, queryStatusCaretNavigationHandler, KeyGesture.CreateFromResourceStrings(KeyMoveToColumnStart, nameof(SR.KeyMoveToColumnStartDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToColumnEnd, nyiCommandHandler, queryStatusCaretNavigationHandler, KeyGesture.CreateFromResourceStrings(KeyMoveToColumnEnd, nameof(SR.KeyMoveToColumnEndDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToWindowTop, nyiCommandHandler, queryStatusCaretNavigationHandler, KeyGesture.CreateFromResourceStrings(KeyMoveToWindowTop, nameof(SR.KeyMoveToWindowTopDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToWindowBottom, nyiCommandHandler, queryStatusCaretNavigationHandler, KeyGesture.CreateFromResourceStrings(KeyMoveToWindowBottom, nameof(SR.KeyMoveToWindowBottomDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToDocumentStart, new ExecutedRoutedEventHandler(OnMoveToDocumentStart), queryStatusCaretNavigationHandler, KeyGesture.CreateFromResourceStrings(KeyMoveToDocumentStart, nameof(SR.KeyMoveToDocumentStartDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToDocumentEnd, new ExecutedRoutedEventHandler(OnMoveToDocumentEnd), queryStatusCaretNavigationHandler, KeyGesture.CreateFromResourceStrings(KeyMoveToDocumentEnd, nameof(SR.KeyMoveToDocumentEndDisplayString)));
 
            // Editing Commands: Selection Building
            // ------------------------------------
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectRightByCharacter, new ExecutedRoutedEventHandler(OnSelectRightByCharacter), queryStatusKeyboardSelectionHandler, KeyGesture.CreateFromResourceStrings(KeySelectRightByCharacter, nameof(SR.KeySelectRightByCharacterDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectLeftByCharacter, new ExecutedRoutedEventHandler(OnSelectLeftByCharacter), queryStatusKeyboardSelectionHandler, KeyGesture.CreateFromResourceStrings(KeySelectLeftByCharacter, nameof(SR.KeySelectLeftByCharacterDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectRightByWord, new ExecutedRoutedEventHandler(OnSelectRightByWord), queryStatusKeyboardSelectionHandler, KeyGesture.CreateFromResourceStrings(KeySelectRightByWord, nameof(SR.KeySelectRightByWordDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectLeftByWord, new ExecutedRoutedEventHandler(OnSelectLeftByWord), queryStatusKeyboardSelectionHandler, KeyGesture.CreateFromResourceStrings(KeySelectLeftByWord, nameof(SR.KeySelectLeftByWordDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectDownByLine, new ExecutedRoutedEventHandler(OnSelectDownByLine), queryStatusKeyboardSelectionHandler, KeyGesture.CreateFromResourceStrings(KeySelectDownByLine, nameof(SR.KeySelectDownByLineDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectUpByLine, new ExecutedRoutedEventHandler(OnSelectUpByLine), queryStatusKeyboardSelectionHandler, KeyGesture.CreateFromResourceStrings(KeySelectUpByLine, nameof(SR.KeySelectUpByLineDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectDownByParagraph, new ExecutedRoutedEventHandler(OnSelectDownByParagraph), queryStatusKeyboardSelectionHandler, KeyGesture.CreateFromResourceStrings(KeySelectDownByParagraph, nameof(SR.KeySelectDownByParagraphDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectUpByParagraph, new ExecutedRoutedEventHandler(OnSelectUpByParagraph), queryStatusKeyboardSelectionHandler, KeyGesture.CreateFromResourceStrings(KeySelectUpByParagraph, nameof(SR.KeySelectUpByParagraphDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectDownByPage, new ExecutedRoutedEventHandler(OnSelectDownByPage), queryStatusKeyboardSelectionHandler, KeyGesture.CreateFromResourceStrings(KeySelectDownByPage, nameof(SR.KeySelectDownByPageDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectUpByPage, new ExecutedRoutedEventHandler(OnSelectUpByPage), queryStatusKeyboardSelectionHandler, KeyGesture.CreateFromResourceStrings(KeySelectUpByPage, nameof(SR.KeySelectUpByPageDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToLineStart, new ExecutedRoutedEventHandler(OnSelectToLineStart), queryStatusKeyboardSelectionHandler, KeyGesture.CreateFromResourceStrings(KeySelectToLineStart, nameof(SR.KeySelectToLineStartDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToLineEnd, new ExecutedRoutedEventHandler(OnSelectToLineEnd), queryStatusKeyboardSelectionHandler, KeyGesture.CreateFromResourceStrings(KeySelectToLineEnd, nameof(SR.KeySelectToLineEndDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToColumnStart, nyiCommandHandler, queryStatusKeyboardSelectionHandler, KeyGesture.CreateFromResourceStrings(KeySelectToColumnStart, nameof(SR.KeySelectToColumnStartDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToColumnEnd, nyiCommandHandler, queryStatusKeyboardSelectionHandler, KeyGesture.CreateFromResourceStrings(KeySelectToColumnEnd, nameof(SR.KeySelectToColumnEndDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToWindowTop, nyiCommandHandler, queryStatusKeyboardSelectionHandler, KeyGesture.CreateFromResourceStrings(KeySelectToWindowTop, nameof(SR.KeySelectToWindowTopDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToWindowBottom, nyiCommandHandler, queryStatusKeyboardSelectionHandler, KeyGesture.CreateFromResourceStrings(KeySelectToWindowBottom, nameof(SR.KeySelectToWindowBottomDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToDocumentStart, new ExecutedRoutedEventHandler(OnSelectToDocumentStart), queryStatusKeyboardSelectionHandler, KeyGesture.CreateFromResourceStrings(KeySelectToDocumentStart, nameof(SR.KeySelectToDocumentStartDisplayString)));
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToDocumentEnd, new ExecutedRoutedEventHandler(OnSelectToDocumentEnd), queryStatusKeyboardSelectionHandler, KeyGesture.CreateFromResourceStrings(KeySelectToDocumentEnd, nameof(SR.KeySelectToDocumentEndDisplayString)));
        }
        
        /// <summary>
        /// Clears the suggestedX variable of passed TextEditor.
        /// </summary>
        /// <param name="This">TextEditor</param>
        internal static void _ClearSuggestedX(TextEditor This)
        {
            // Discard stored horizontal position.
            // By setting to NaN, we indicate that the first following vertical movement
            // must define suggestedX from the current moving position.
            This._suggestedX = Double.NaN;
 
            This._NextLineAdvanceMovingPosition = null;
        }
 
        // Returns a normalized line range from TextView for a given position.
        // Note: In current contract, line range returned by TextView.GetLineRange() is not guaranteed to be normalized.
        // This helper does appropriate correction and returns a normalized line range.
        internal static TextSegment GetNormalizedLineRange(ITextView textView, ITextPointer position)
        {
            TextSegment lineRange = textView.GetLineRange(position);
            if (lineRange.IsNull)
            {
                if (!typeof(BlockUIContainer).IsAssignableFrom(position.ParentType))
                {
                    return lineRange;
                }
 
                ITextPointer lineStart = position.CreatePointer(LogicalDirection.Forward);
                lineStart.MoveToElementEdge(ElementEdge.AfterStart);
                ITextPointer lineEnd = position.CreatePointer(LogicalDirection.Backward);
                lineEnd.MoveToElementEdge(ElementEdge.BeforeEnd);
                lineRange = new TextSegment(lineStart, lineEnd);
                return lineRange;
            }
 
            // Normalize line range
            ITextRange textRange = new TextRange(lineRange.Start, lineRange.End);
            return new TextSegment(textRange.Start, textRange.End);
        }
 
        // Returns true if a textview is potentially paginated.
        internal static bool IsPaginated(ITextView textview)
        {
            return !(textview is TextBoxView);
        }
 
        #endregion Class Internal Methods
 
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
 
        #region Private Methods
 
        /// <summary>
        /// </summary>
        private static void OnSelectAll(object target, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(target);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            TextEditorTyping._FlushPendingInputItems(This);
 
            using (This.Selection.DeclareChangeBlock(true /* disableScroll */))
            {
                This.Selection.Select(This.TextContainer.Start, This.TextContainer.End);
 
                // Forget previously suggested horizontal position
                TextEditorSelection._ClearSuggestedX(This);
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(This);
 
                // Clear springload formatting
                ClearSpringloadFormatting(This);
            }
        }
 
        // ................................................................
        //
        // Editing Commands: Caret Navigation
        //
        // ................................................................
 
        /// <summary>
        /// MoveRightByCharacter command event handler.
        /// </summary>
        private static void OnMoveRightByCharacter(object target, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(target);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Backward : LogicalDirection.Forward;
            MoveToCharacterLogicalDirection(This, movementDirection, /*extend:*/false);
        }
 
        /// <summary>
        /// MoveLeftByCharacter command event handler.
        /// </summary>
        private static void OnMoveLeftByCharacter(object target, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(target);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Forward : LogicalDirection.Backward;
            MoveToCharacterLogicalDirection(This, movementDirection, /*extend:*/false);
        }
 
 
        /// <summary>
        /// </summary>
        private static void OnMoveRightByWord(object target, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(target);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            // Navigate word to the logical forward.
            LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Backward : LogicalDirection.Forward;
            NavigateWordLogicalDirection(This, movementDirection);
        }
 
        /// <summary>
        /// </summary>
        private static void OnMoveLeftByWord(object target, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(target);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            // Navigate word to the logical backward.
            LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Forward : LogicalDirection.Backward;
            NavigateWordLogicalDirection(This, movementDirection);
        }
 
        private static void OnMoveDownByLine(object sender, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(sender);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            TextEditorTyping._FlushPendingInputItems(This);
 
            // We need a non-dirty layout to walk lines.
            if (!This.Selection.End.ValidateLayout())
            {
                return;
            }
 
            using (This.Selection.DeclareChangeBlock())
            {
                if (!This.Selection.IsEmpty)
                {
                    // If the selection is non-empty, collapse it.
                    // Collapsing must happen to selection END - not to its moving position.
                    // It is Word behavior for setting a cratet on moving down from nonempty selection.
 
                    // When Selection.End is moving position we must adjust it
                    // for LineEnd condition - choose inner position within a line.
                    ITextPointer position = TextEditorSelection.GetEndInner(This);
 
                    This.Selection.SetCaretToPosition(position, position.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true);
                    TextEditorSelection._ClearSuggestedX(This); // So that when we will request suggestedX below it will take it from the new moving position
                }
                Invariant.Assert(This.Selection.IsEmpty);
 
                // When the caret is at RowEnd position, we start by moving it into the last cell of this row.
                AdjustCaretAtTableRowEnd(This);
 
                ITextPointer originalMovingPosition;
                double suggestedX = TextEditorSelection.GetSuggestedX(This, out originalMovingPosition);
 
                // Continue only if we have a moving position with valid layout
                if (originalMovingPosition == null)
                {
                    return;
                }
 
                // Extend the selection edge.
                double newSuggestedX;
                int linesMoved;
                ITextPointer newMovingPosition = This.TextView.GetPositionAtNextLine(This.Selection.MovingPosition, suggestedX, +1, out newSuggestedX, out linesMoved);
                Invariant.Assert(newMovingPosition != null);
 
                if (linesMoved != 0)
                {
                    // Update suggestedX
                    TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX);
 
                    // Move insertion point to next or previous line
                    This.Selection.SetCaretToPosition(newMovingPosition, newMovingPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true);
                }
                else
                {
                    if (TextPointerBase.IsInAnchoredBlock(originalMovingPosition))
                    {
                        // TextView treats AnchoredBlock elements as hard structural boundaries.
                        // As a result GetPositionAtNextLine() does not work from the first/last line within an AnchoredBlock.
 
                        // If line move wasn't successful because our moving position is at the end of an AnchoredBlock,
                        // move insertion point so that it crosses AnchoredBlock boundary.
                        // If there is no next position after the AnchoredBlock, move to current line end.
 
                        ITextPointer lineEndPosition = GetPositionAtLineEnd(originalMovingPosition);
                        ITextPointer nextPosition = lineEndPosition.GetNextInsertionPosition(LogicalDirection.Forward);
                        This.Selection.SetCaretToPosition(nextPosition != null ? nextPosition : lineEndPosition,
                            originalMovingPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true);
                    }
                    else if (IsPaginated(This.TextView))
                    {
                        // If line move wasn't successful because it is not in the view, bring the next line into view.
                        This.TextView.BringLineIntoViewCompleted += new BringLineIntoViewCompletedEventHandler(HandleMoveByLineCompleted);
                        This.TextView.BringLineIntoViewAsync(newMovingPosition, newSuggestedX, +1, This);
                    }
                }
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(This);
 
                // Clear springload formatting
                ClearSpringloadFormatting(This);
            }
        }
 
        private static void OnMoveUpByLine(object sender, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(sender);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            TextEditorTyping._FlushPendingInputItems(This);
 
            // We need a non-dirty layout to walk lines.
            if (!This.Selection.Start.ValidateLayout())
            {
                return;
            }
 
            using (This.Selection.DeclareChangeBlock())
            {
                if (!This.Selection.IsEmpty)
                {
                    // If the selection is non-empty, collapse it.
                    // Collapsing must happen to selection START - not to its moving position.
                    // It is Word behavior for setting a cratet on moving down from nonempty selection.
                    ITextPointer position = TextEditorSelection.GetStartInner(This);
 
                    This.Selection.SetCaretToPosition(position, position.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true);
                    TextEditorSelection._ClearSuggestedX(This); // So that when we will request suggestedX below it will take it from the new moving position
                }
                Invariant.Assert(This.Selection.IsEmpty);
 
                // When the caret is at RowEnd position, we start by moving it into the last cell of this row.
                AdjustCaretAtTableRowEnd(This);
 
                ITextPointer originalMovingPosition;
                double suggestedX = TextEditorSelection.GetSuggestedX(This, out originalMovingPosition);
 
                // Continue only if we have a moving position with valid layout
                if (originalMovingPosition == null)
                {
                    return;
                }
 
                // Extend the selection edge.
                double newSuggestedX;
                int linesMoved;
                ITextPointer newMovingPosition = This.TextView.GetPositionAtNextLine(This.Selection.MovingPosition, suggestedX, -1, out newSuggestedX, out linesMoved);
                Invariant.Assert(newMovingPosition != null);
 
                if (linesMoved != 0)
                {
                    // Update suggestedX
                    TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX);
 
                    // Move insertion point to next or previous line
                    //  position must be normalized not randomly here (bug)
                    This.Selection.SetCaretToPosition(newMovingPosition, newMovingPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true);
                }
                else
                {
                    if (TextPointerBase.IsInAnchoredBlock(originalMovingPosition))
                    {
                        // TextView treats AnchoredBlock elements as hard structural boundaries.
                        // As a result GetPositionAtNextLine() does not work from the first/last line within an AnchoredBlock.
 
                        // If line move wasn't successful because our moving position is at the end of an AnchoredBlock,
                        // move insertion point to a position before the AnchoredBlock boundary.
                        // If there is no previous position before the AnchoredBlock, move to current line start.
 
                        ITextPointer lineStartPosition = GetPositionAtLineStart(originalMovingPosition);
                        ITextPointer previousPosition = lineStartPosition.GetNextInsertionPosition(LogicalDirection.Backward);
                        This.Selection.SetCaretToPosition(previousPosition != null ? previousPosition : lineStartPosition,
                            originalMovingPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true);
                    }
                    else if (IsPaginated(This.TextView))
                    {
                        // If line move wasn't successful because it is not in the view, bring the previous line into view.
                        This.TextView.BringLineIntoViewCompleted += new BringLineIntoViewCompletedEventHandler(HandleMoveByLineCompleted);
                        This.TextView.BringLineIntoViewAsync(newMovingPosition, newSuggestedX, -1, This);
                    }
                }
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(This);
 
                // Clear springload formatting
                ClearSpringloadFormatting(This);
            }
        }
 
        private static void OnMoveDownByParagraph(object sender, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(sender);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            TextEditorTyping._FlushPendingInputItems(This);
 
            using (This.Selection.DeclareChangeBlock())
            {
                // Forget previously suggested horizontal position
                TextEditorSelection._ClearSuggestedX(This);
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(This);
 
                // Clear springload formatting
                ClearSpringloadFormatting(This);
 
                // Move/extend selection in requested direction
                if (!This.Selection.IsEmpty)
                {
                    // If the selection is non-empty and ends on a word boundary, collapse it to that boundary.
                    // Collapsing must happen to selection END - not to its moving position.
                    // It is Word behavior for setting a cratet on moving down from nonempty selection.
 
                    // When Selection.End is moving position we must adjust it
                    // for LineEnd condition - choose inner position within a line.
                    ITextPointer position = TextEditorSelection.GetEndInner(This);
 
                    This.Selection.SetCaretToPosition(position, position.LogicalDirection, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false);
                }
 
                ITextPointer movingPointer = This.Selection.MovingPosition.CreatePointer();
                ITextRange paragraphRange = new TextRange(movingPointer, movingPointer);
                paragraphRange.SelectParagraph(movingPointer);
 
                movingPointer.MoveToPosition(paragraphRange.End);
                if (movingPointer.MoveToNextInsertionPosition(LogicalDirection.Forward))
                {
                    // Next paragraph found. Set selection to its start
                    paragraphRange.SelectParagraph(movingPointer);
                    This.Selection.SetCaretToPosition(paragraphRange.Start, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false);
                }
                else
                {
                    // Next paragraph does not exist. Set selectionn to the end of current paragraph
                    This.Selection.SetCaretToPosition(paragraphRange.End, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false);
                }
            }
        }
 
        private static void OnMoveUpByParagraph(object sender, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(sender);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            TextEditorTyping._FlushPendingInputItems(This);
 
            using (This.Selection.DeclareChangeBlock())
            {
                // Forget previously suggested horizontal position
                TextEditorSelection._ClearSuggestedX(This);
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(This);
 
                // Clear springload formatting
                ClearSpringloadFormatting(This);
 
                // Move/extend selection in requested direction
                if (!This.Selection.IsEmpty)
                {
                    // If the selection is non-empty and ends on a word boundary, collapse it to that boundary.
                    // Collapsing must happen to selection START - not to its moving position.
                    // It is Word behavior for setting a cratet on moving down from nonempty selection.
                    ITextPointer position = TextEditorSelection.GetStartInner(This);
 
                    This.Selection.SetCaretToPosition(position, position.LogicalDirection, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false);
                }
                ITextPointer movingPointer = This.Selection.MovingPosition.CreatePointer();
                ITextRange paragraphRange = new TextRange(movingPointer, movingPointer);
                paragraphRange.SelectParagraph(movingPointer);
 
                if (This.Selection.Start.CompareTo(paragraphRange.Start) > 0)
                {
                    // We are in the middle of a paragraph. Move to its start
                    This.Selection.SetCaretToPosition(paragraphRange.Start, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false);
                }
                else
                {
                    movingPointer.MoveToPosition(paragraphRange.Start);
                    if (movingPointer.MoveToNextInsertionPosition(LogicalDirection.Backward))
                    {
                        // Previous paragraph found. Set selection to its start
                        paragraphRange.SelectParagraph(movingPointer);
                        This.Selection.SetCaretToPosition(paragraphRange.Start, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false);
                    }
                }
            }
        }
 
        private static void OnMoveDownByPage(object sender, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(sender);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            TextEditorTyping._FlushPendingInputItems(This);
 
            // We need a non-dirty layout to walk pages.
            if (!This.Selection.End.ValidateLayout())
            {
                return;
            }
 
            using (This.Selection.DeclareChangeBlock())
            {
                if (!This.Selection.IsEmpty)
                {
                    // If the selection is non-empty and ends on a word boundary, collapse it to that boundary.
                    // Collapsing must happen to selection END - not to its moving position.
                    // It is Word behavior for setting a cratet on moving down from nonempty selection.
 
                    // When Selection.End is moving position we must adjust it
                    // for LineEnd condition - choose inner position within a line.
                    ITextPointer position = TextEditorSelection.GetEndInner(This);
 
                    This.Selection.SetCaretToPosition(position, position.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true);
                }
 
                ITextPointer movingPosition;
                double suggestedX = TextEditorSelection.GetSuggestedX(This, out movingPosition);
 
                // Continue only if we have a moving position with valid layout
                if (movingPosition == null)
                {
                    return;
                }
 
                ITextPointer targetPosition;
                double newSuggestedX;
                double pageHeight = (double)This.UiScope.GetValue(TextEditor.PageHeightProperty);
 
                // Presence of page height property on TextEditor instructs us to use simple bottomless version of
                // pagination (TextBox/RichTextBox).
                // Otherwise, when page height = 0, we use TextView implementation of GetPositionAtNextPage().
 
                // Ideally, we should remove PageHeight property from TextEditor and
                // textview should implement GetPositionAtNextPage() for bottomless scrollviewer.
                if (pageHeight == 0)
                {
                    if (IsPaginated(This.TextView))
                    {
                        int pagesMoved;
                        Point newSuggestedOffset;
 
                        // Get suggested Y for moving position
                        double suggestedY = GetSuggestedYFromPosition(This, movingPosition);
 
                        targetPosition = This.TextView.GetPositionAtNextPage(movingPosition, new Point(GetViewportXOffset(This.TextView, suggestedX), suggestedY), +1, out newSuggestedOffset, out pagesMoved);
                        newSuggestedX = newSuggestedOffset.X;
                        Invariant.Assert(targetPosition != null);
 
                        if (pagesMoved != 0)
                        {
                            // Update suggestedX
                            TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX);
 
                            // If shift key isn't down, collapse the range.
                            This.Selection.SetCaretToPosition(targetPosition, targetPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/false);
                        }
                        else if (IsPaginated(This.TextView))
                        {
                            // If page move wasn't successful because it is not in the view, bring the next page into view.
                            This.TextView.BringPageIntoViewCompleted += new BringPageIntoViewCompletedEventHandler(HandleMoveByPageCompleted);
                            This.TextView.BringPageIntoViewAsync(targetPosition, newSuggestedOffset, +1, This);
                        }
                    }
                }
                else
                {
                    // Calculate target position - at a specified distance from current movingPosition
                    Rect targetRect = This.TextView.GetRectangleFromTextPosition(movingPosition);
                    Point targetPoint = new Point(GetViewportXOffset(This.TextView, suggestedX), targetRect.Top + pageHeight);
                    targetPosition = This.TextView.GetTextPositionFromPoint(targetPoint, /*snapToText:*/true);
 
                    if (targetPosition == null)
                    {
                        return;
                    }
 
                    // Check if the new position really moving forward;
                    // otherwise force it to the very end of container.
                    if (targetPosition.CompareTo(movingPosition) <= 0)
                    {
                        targetPosition = This.TextContainer.End;
                        TextEditorSelection._ClearSuggestedX(This);
                    }
 
                    // Fire a page up/down command on the renderScope, so any ScrollViewer will pick it up
                    ScrollBar.PageDownCommand.Execute(null, This.TextView.RenderScope);
 
                    // We need the page down to happen before the caret moves.  Force a layout update
                    // so the command queue gets processed.
                    This.TextView.RenderScope.UpdateLayout();
 
                    // If shift key isn't down, collapse the range.
                    This.Selection.SetCaretToPosition(targetPosition, targetPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/false);
                }
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(This);
 
                // Clear springload formatting
                ClearSpringloadFormatting(This);
            }
        }
 
        private static void OnMoveUpByPage(object sender, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(sender);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            TextEditorTyping._FlushPendingInputItems(This);
 
            // We need a non-dirty layout to walk pages.
            if (!This.Selection.Start.ValidateLayout())
            {
                return;
            }
 
            using (This.Selection.DeclareChangeBlock())
            {
                if (!This.Selection.IsEmpty)
                {
                    // If the selection is non-empty and ends on a word boundary, collapse it to that boundary.
                    // Collapsing must happen to selection START - not to its moving position.
                    // It is Word behavior for setting a cratet on moving down from nonempty selection.
                    ITextPointer position = TextEditorSelection.GetStartInner(This);
 
                    This.Selection.SetCaretToPosition(position, position.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true);
                }
 
                ITextPointer movingPosition;
                double suggestedX = TextEditorSelection.GetSuggestedX(This, out movingPosition);
 
                // Continue only if we have a moving position with valid layout
                if (movingPosition == null)
                {
                    return;
                }
 
                ITextPointer targetPosition;
                double newSuggestedX;
                double pageHeight = (double)This.UiScope.GetValue(TextEditor.PageHeightProperty);
 
                // Presence of page height property on TextEditor instructs us to use simple bottomless version of
                // pagination (TextBox/RichTextBox).
                // Otherwise, when page height = 0, we use TextView implementation of GetPositionAtNextPage().
                if (pageHeight == 0)
                {
                    if (IsPaginated(This.TextView))
                    {
                        int pagesMoved;
                        Point newSuggestedOffset;
 
                        // Get suggested Y for moving position
                        double suggestedY = GetSuggestedYFromPosition(This, movingPosition);
 
                        targetPosition = This.TextView.GetPositionAtNextPage(movingPosition, new Point(GetViewportXOffset(This.TextView, suggestedX), suggestedY), -1, out newSuggestedOffset, out pagesMoved);
                        newSuggestedX = newSuggestedOffset.X;
                        Invariant.Assert(targetPosition != null);
 
                        if (pagesMoved != 0)
                        {
                            // Update suggestedX
                            TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX);
 
                            // If shift key isn't down, collapse the range.
                            This.Selection.SetCaretToPosition(targetPosition, targetPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/false);
                        }
                        else if (IsPaginated(This.TextView))
                        {
                            // If page move wasn't successful because it is not in the view, bring the next page into view.
                            This.TextView.BringPageIntoViewCompleted += new BringPageIntoViewCompletedEventHandler(HandleMoveByPageCompleted);
                            This.TextView.BringPageIntoViewAsync(targetPosition, newSuggestedOffset, -1, This);
                        }
                    }
                }
                else
                {
                    // Calculate target position - at a specified distance from current movingPosition
                    Rect targetRect = This.TextView.GetRectangleFromTextPosition(movingPosition);
                    Point targetPoint = new Point(GetViewportXOffset(This.TextView, suggestedX), targetRect.Bottom - pageHeight);
                    targetPosition = This.TextView.GetTextPositionFromPoint(targetPoint, /*snapToText:*/true);
 
                    if (targetPosition == null)
                    {
                        return;
                    }
 
                    // Check if the new position really moving forward;
                    // otherwise force it to the very end of container.
                    if (targetPosition.CompareTo(movingPosition) >= 0)
                    {
                        targetPosition = This.TextContainer.Start;
                        TextEditorSelection._ClearSuggestedX(This);
                    }
 
                    // Fire a page up/down command on the renderScope, so any ScrollViewer will pick it up
                    ScrollBar.PageUpCommand.Execute(null, This.TextView.RenderScope);
 
                    // We need the page down to happen before the caret moves.  Force a layout update
                    // so the command queue gets processed.
                    This.TextView.RenderScope.UpdateLayout();
 
                    // If shift key isn't down, collapse the range.
                    This.Selection.SetCaretToPosition(targetPosition, targetPosition.LogicalDirection, /*allowStopAtLineEnd:*/
                                                      true, /*allowStopNearSpace:*/false);
                }
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(This);
 
                // Clear springload formatting
                ClearSpringloadFormatting(This);
            }
        }
 
        /// <summary>
        /// </summary>
        private static void OnMoveToLineStart(object target, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(target);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            TextEditorTyping._FlushPendingInputItems(This);
 
            // When getting start position, we need to take care of end-of-line case
            // and adjust start position according to its orientation.
            ITextPointer startPositionInner = TextEditorSelection.GetStartInner(This);
 
            // We need a non-dirty layout to walk pages.
            if (!startPositionInner.ValidateLayout())
            {
                return;
            }
 
            // Standard behavior, move to begin/end of line.
            TextSegment lineRange = TextEditorSelection.GetNormalizedLineRange(This.TextView, startPositionInner);
            if (lineRange.IsNull)
            {
                return;
            }
 
            using (This.Selection.DeclareChangeBlock())
            {
                // Note caret direction here: must be forward to keep caret on the same line
                //  Position must be normalized not randomly (bug)
 
                // Create caret position normalized forward - towards the very first character of the line
                ITextPointer caretPosition = lineRange.Start.GetFrozenPointer(LogicalDirection.Forward);
 
                // Set caret to beginning of a line
                This.Selection.SetCaretToPosition(caretPosition, LogicalDirection.Forward, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true);
 
                // Forget previously suggested horizontal position
                TextEditorSelection._ClearSuggestedX(This);
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(This);
 
                // Clear springload formatting
                ClearSpringloadFormatting(This);
            }
        }
 
        /// <summary>
        /// </summary>
        private static void OnMoveToLineEnd(object target, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(target);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            TextEditorTyping._FlushPendingInputItems(This);
 
            // When getting end position, we need to take care of end-of-line case
            // and adjust end position according to its orientation.
            ITextPointer endPositionInner = TextEditorSelection.GetEndInner(This);
 
            // We need a non-dirty layout to walk pages.
            if (!endPositionInner.ValidateLayout())
            {
                return;
            }
 
            TextSegment lineRange = TextEditorSelection.GetNormalizedLineRange(This.TextView, endPositionInner);
            if (lineRange.IsNull)
            {
                return;
            }
 
            using (This.Selection.DeclareChangeBlock())
            {
                // Note caret direction here: must be backward to keep caret on the same line when it is wrapped by flow.
                // Orientation must be Backward when the line is wrapped by flow, otherwise - normal Forward
                LogicalDirection orientation = TextPointerBase.IsNextToPlainLineBreak(lineRange.End, LogicalDirection.Backward) ? LogicalDirection.Forward : LogicalDirection.Backward;
 
                // Create caret position normalized the same way as orientation
                ITextPointer caretPosition = lineRange.End.GetFrozenPointer(orientation);
 
                // Set caret to the end of line
                This.Selection.SetCaretToPosition(caretPosition, orientation, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true);
 
                // Forget previously suggested horizontal position
                TextEditorSelection._ClearSuggestedX(This);
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(This);
 
                // Clear springload formatting
                ClearSpringloadFormatting(This);
            }
        }
 
        /// <summary>
        /// </summary>
        private static void OnMoveToDocumentStart(object target, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(target);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            TextEditorTyping._FlushPendingInputItems(This);
 
            using (This.Selection.DeclareChangeBlock())
            {
                This.Selection.SetCaretToPosition(This.TextContainer.Start, LogicalDirection.Forward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false);
 
                // Forget previously suggested horizontal position
                TextEditorSelection._ClearSuggestedX(This);
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(This);
 
                // Clear springload formatting
                ClearSpringloadFormatting(This);
            }
        }
 
        /// <summary>
        /// </summary>
        private static void OnMoveToDocumentEnd(object target, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(target);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            TextEditorTyping._FlushPendingInputItems(This);
 
            using (This.Selection.DeclareChangeBlock())
            {
                // Orientation is standard - forward, because text cannot wrap by flow at this position
                This.Selection.SetCaretToPosition(This.TextContainer.End, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false);
 
                // Forget previously suggested horizontal position
                TextEditorSelection._ClearSuggestedX(This);
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(This);
 
                // Clear springload formatting
                ClearSpringloadFormatting(This);
            }
        }
 
        // ................................................................
        //
        // Editing Commands: Selection Building
        //
        // ................................................................
 
        /// <summary>
        /// SelectRightByCharacter command event handler.
        /// </summary>
        private static void OnSelectRightByCharacter(object target, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(target);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Backward : LogicalDirection.Forward;
            MoveToCharacterLogicalDirection(This, movementDirection, /*extend:*/true);
        }
 
        /// <summary>
        /// SelectLeftByCharacter command event handler.
        /// </summary>
        private static void OnSelectLeftByCharacter(object target, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(target);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Forward : LogicalDirection.Backward;
            MoveToCharacterLogicalDirection(This, movementDirection, /*extend:*/true);
        }
 
        /// <summary>
        /// </summary>
        private static void OnSelectRightByWord(object target, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(target);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            // Extend word to the logical forward.
            LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Backward : LogicalDirection.Forward;
            ExtendWordLogicalDirection(This, movementDirection);
        }
 
        /// <summary>
        /// </summary>
        private static void OnSelectLeftByWord(object target, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(target);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            // Extend word to the logical backward.
            LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Forward : LogicalDirection.Backward;
            ExtendWordLogicalDirection(This, movementDirection);
        }
 
        private static void OnSelectDownByLine(object sender, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(sender);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            // We need a non-dirty layout to walk lines.
            TextEditorTyping._FlushPendingInputItems(This);
 
            using (This.Selection.DeclareChangeBlock())
            {
                if (This.Selection.ExtendToNextTableRow(LogicalDirection.Forward))
                {
                    // This is table selection case. Vertical extension.
                    // Table selection has been successfully extended in vertical direction.
                    // Nothing more to do.
                }
                else
                {
                    ITextPointer originalMovingPosition;
                    double suggestedX = TextEditorSelection.GetSuggestedX(This, out originalMovingPosition);
 
                    // Continue only if we have a moving position with valid layout
                    if (originalMovingPosition == null)
                    {
                        return;
                    }
 
                    if (This._NextLineAdvanceMovingPosition != null &&
                        This._IsNextLineAdvanceMovingPositionAtDocumentHead)
                    {
                        // Moving position is at the beginning of text container
                        // as a result of previous Shift+Up extension;
                        // so now we need to return to a positing within the current line
                        ExtendSelectionAndBringIntoView(This._NextLineAdvanceMovingPosition, This);
                        This._NextLineAdvanceMovingPosition = null;
                    }
                    else
                    {
                        // When the moving position is at RowEnd position, we start by moving it into the last cell of this row.
                        ITextPointer newMovingPosition = AdjustPositionAtTableRowEnd(originalMovingPosition);
 
                        // Find a position in the next line
                        double newSuggestedX;
                        int linesMoved;
 
                        newMovingPosition = This.TextView.GetPositionAtNextLine(newMovingPosition, suggestedX, +1, out newSuggestedX, out linesMoved);
                        Invariant.Assert(newMovingPosition != null);
 
                        if (linesMoved != 0)
                        {
                            // Update suggestedX
                            TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX);
 
                            // Shift key is down - Extend the selection edge
                            AdjustMovingPositionForSelectDownByLine(This, newMovingPosition, originalMovingPosition, newSuggestedX);
                        }
                        else
                        {
                            if (TextPointerBase.IsInAnchoredBlock(originalMovingPosition))
                            {
                                // TextView treats AnchoredBlock elements as hard structural boundaries.
                                // As a result GetPositionAtNextLine() does not work from the first/last line within an AnchoredBlock.
 
                                // If line move wasn't successful because our moving position is at the end of an AnchoredBlock,
                                // expand selection so that it crosses AnchoredBlock boundary.
                                // If there is no next position after the AnchoredBlock, expand to current line end.
 
                                ITextPointer lineEndPosition = GetPositionAtLineEnd(originalMovingPosition);
                                ITextPointer nextPosition = lineEndPosition.GetNextInsertionPosition(LogicalDirection.Forward);
 
                                // Extend selection and bring new position into view if needed (for paginated viewers)
                                ExtendSelectionAndBringIntoView(nextPosition != null ? nextPosition : lineEndPosition, This);
                            }
                            else if (IsPaginated(This.TextView))
                            {
                                // If line move wasn't successful because it is not in the view, bring the next line into view.
                                This.TextView.BringLineIntoViewCompleted += new BringLineIntoViewCompletedEventHandler(HandleSelectByLineCompleted);
                                This.TextView.BringLineIntoViewAsync(newMovingPosition, newSuggestedX, +1, This);
                            }
                            else
                            {
                                // Remember where we were so that we can return if a line up follows.
                                if (This._NextLineAdvanceMovingPosition == null)
                                {
                                    This._NextLineAdvanceMovingPosition = originalMovingPosition;
                                    This._IsNextLineAdvanceMovingPositionAtDocumentHead = false;
                                }
                                // No more lines in this direction. Move to end of current line.
                                ExtendSelectionAndBringIntoView(GetPositionAtLineEnd(newMovingPosition), This);
                            }
                        }
                    }
                }
    
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(This);
 
                // Clear springload formatting
                ClearSpringloadFormatting(This);
            }
        }
 
        // Helper for OnSelectDownByLine.  Updates the selection moving position
        // during select down by line.
        private static void AdjustMovingPositionForSelectDownByLine(TextEditor This, ITextPointer newMovingPosition, ITextPointer originalMovingPosition, double suggestedX)
        {
            int newComparedToOld = newMovingPosition.CompareTo(originalMovingPosition);
 
            // Note: we compare orientations of equal positions to handle a case
            // when original position was at the end of line (after its linebreak with backward orientation)
            // as a result of Shift+End selection; and the new position is in the beginning of the next line,
            // which is essentially the same position but oriented differently.
            // In such a case the new position is good enough to go there.
            // We certainly don't want to go to the end of the document in this case.
            if (newComparedToOld > 0 || newComparedToOld == 0 && newMovingPosition.LogicalDirection != originalMovingPosition.LogicalDirection)
            {
                // We have another line in a given direction; move to it
 
                // If the destination exactly preceeds a line break, expand to include
                // the line break if we haven't reached our desired suggestedX.
                if (TextPointerBase.IsNextToAnyBreak(newMovingPosition, LogicalDirection.Forward) ||
                    newMovingPosition.GetNextInsertionPosition(LogicalDirection.Forward) == null)
                {
                    double newPositionX = GetAbsoluteXOffset(This.TextView, newMovingPosition);
                    FlowDirection paragraphFlowDirection = GetScopingParagraphFlowDirection(newMovingPosition);
                    FlowDirection controlFlowDirection = This.UiScope.FlowDirection;
 
                    if ((paragraphFlowDirection == controlFlowDirection && newPositionX < suggestedX) ||
                        (paragraphFlowDirection != controlFlowDirection && newPositionX > suggestedX))
                    {
                        newMovingPosition = newMovingPosition.GetInsertionPosition(LogicalDirection.Forward);
                        newMovingPosition = newMovingPosition.GetNextInsertionPosition(LogicalDirection.Forward);
 
                        // If we're at the last Paragraph, move to document end to include
                        // the final paragraph break.
                        if (newMovingPosition == null)
                        {
                            newMovingPosition = originalMovingPosition.TextContainer.End;
                        }
 
                        newMovingPosition = newMovingPosition.GetFrozenPointer(LogicalDirection.Backward);
                    }
                }
 
                ExtendSelectionAndBringIntoView(newMovingPosition, This);
            }
            else
            {
                // Remember where we were so that we can return if a line up follows.
                if (This._NextLineAdvanceMovingPosition == null)
                {
                    This._NextLineAdvanceMovingPosition = originalMovingPosition;
                    This._IsNextLineAdvanceMovingPositionAtDocumentHead = false;
                }
 
                // No more lines in this direction. Move to end of current line.
                newMovingPosition = GetPositionAtLineEnd(originalMovingPosition);
 
                if (newMovingPosition.GetNextInsertionPosition(LogicalDirection.Forward) == null)
                {
                    // Move to the final implicit line at end-of-doc.
                    newMovingPosition = newMovingPosition.TextContainer.End;
                }
 
                ExtendSelectionAndBringIntoView(newMovingPosition, This);
            }
        }
 
        private static void OnSelectUpByLine(object sender, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(sender);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            // We need a non-dirty layout to walk lines.
            TextEditorTyping._FlushPendingInputItems(This);
 
            using (This.Selection.DeclareChangeBlock())
            {
                if (This.Selection.ExtendToNextTableRow(LogicalDirection.Backward))
                {
                    // This is table selection case. Vertical extension.
                    // Table selection has been successfully extended in vertical direction.
                    // Nothing more to do.
                }
                else
                {
                    ITextPointer originalMovingPosition;
                    double suggestedX = TextEditorSelection.GetSuggestedX(This, out originalMovingPosition);
 
                    // Continue only if we have a moving position with valid layout
                    if (originalMovingPosition == null)
                    {
                        return;
                    }
 
                    if (This._NextLineAdvanceMovingPosition != null &&
                        !This._IsNextLineAdvanceMovingPositionAtDocumentHead)
                    {
                        // Moving position is at the end of text container
                        // as a result of previous Shift+Down extension;
                        // so now we need to return to a positing within the current line
                        ExtendSelectionAndBringIntoView(This._NextLineAdvanceMovingPosition, This);
                        This._NextLineAdvanceMovingPosition = null;
                    }
                    else
                    {
                        // When the moving position is at RowEnd position, we start by moving it into the last cell of this row.
                        ITextPointer newMovingPosition = AdjustPositionAtTableRowEnd(originalMovingPosition);
 
                        // Extend the selection edge.
                        double newSuggestedX;
                        int linesMoved;
 
                        newMovingPosition = This.TextView.GetPositionAtNextLine(newMovingPosition, suggestedX, -1, out newSuggestedX, out linesMoved);
                        Invariant.Assert(newMovingPosition != null);
 
                        if (linesMoved != 0)
                        {
                            // Update suggestedX
                            TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX);
 
                            int newComparedToOld = newMovingPosition.CompareTo(originalMovingPosition);
 
                            // Shift key is down - Extend the selection edge
                            if (newComparedToOld < 0 || newComparedToOld == 0 && newMovingPosition.LogicalDirection != originalMovingPosition.LogicalDirection)
                            {
                                // Note: we compare orientations of equal positions to handle a case
                                // when original position was at the end of line (after its linebreak with backward orientation)
                                // as a result of Shift+End selection; and the new position is in the beginning of the next line,
                                // which is essentially the same position but oriented differently.
                                // In such a case the new position is good enough to go there.
                                // We certainly don't want to go to the end of the document in this case.
 
                                // We have another line in a given direction; move to it
                                ExtendSelectionAndBringIntoView(newMovingPosition, This);
                            }
                            else
                            {
                                // No more lines in this direction. Move to current line start.
                                ExtendSelectionAndBringIntoView(GetPositionAtLineStart(originalMovingPosition), This);
                            }
                        }
                        else
                        {
                            if (TextPointerBase.IsInAnchoredBlock(originalMovingPosition))
                            {
                                // TextView treats AnchoredBlock elements as hard structural boundaries.
                                // As a result GetPositionAtNextLine() does not work from the first/last line within an AnchoredBlock.
 
                                // If line move wasn't successful because our moving position is at the start of an AnchoredBlock,
                                // expand selection so that it crosses AnchoredBlock boundary.
                                // If there is no previous position before the AnchoredBlock, expand to current line start.
 
                                ITextPointer lineStartPosition = GetPositionAtLineStart(originalMovingPosition);
                                ITextPointer previousPosition = lineStartPosition.GetNextInsertionPosition(LogicalDirection.Backward);
 
                                // Extend selection and bring new position into view if needed (for paginated viewers)
                                ExtendSelectionAndBringIntoView(previousPosition != null ? previousPosition : lineStartPosition, This);
                            }
                            else if (IsPaginated(This.TextView))
                            {
                                // If line move wasn't successful because it is not in the view, bring the previous line into view.
                                This.TextView.BringLineIntoViewCompleted += new BringLineIntoViewCompletedEventHandler(HandleSelectByLineCompleted);
                                This.TextView.BringLineIntoViewAsync(newMovingPosition, newSuggestedX, -1, This);
                            }
                            else
                            {
                                // Remember where we were so that we can return if a line down follows.
                                if (This._NextLineAdvanceMovingPosition == null)
                                {
                                    This._NextLineAdvanceMovingPosition = originalMovingPosition;
                                    This._IsNextLineAdvanceMovingPositionAtDocumentHead = true;
                                }
                                // No more lines in this direction. Move to start of current line.
                                ExtendSelectionAndBringIntoView(GetPositionAtLineStart(newMovingPosition), This);
                            }
                        }
                    }
                }
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(This);
 
                // Clear springload formatting
                ClearSpringloadFormatting(This);
            }
        }
 
        private static void OnSelectDownByParagraph(object sender, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(sender);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            TextEditorTyping._FlushPendingInputItems(This);
 
            using (This.Selection.DeclareChangeBlock())
            {
                // Forget previously suggested horizontal position
                TextEditorSelection._ClearSuggestedX(This);
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(This);
 
                // Clear springload formatting
                ClearSpringloadFormatting(This);
 
                ITextPointer movingPointer = This.Selection.MovingPosition.CreatePointer();
                ITextRange paragraphRange = new TextRange(movingPointer, movingPointer);
                paragraphRange.SelectParagraph(movingPointer);
 
                movingPointer.MoveToPosition(paragraphRange.End);
                if (movingPointer.MoveToNextInsertionPosition(LogicalDirection.Forward))
                {
                    // Next paragraph found. Set selection to its start
                    paragraphRange.SelectParagraph(movingPointer);
                    ExtendSelectionAndBringIntoView(paragraphRange.Start, This);
                }
                else
                {
                    // Next paragraph does not exist. Set selectionn to the end of current paragraph
                    ExtendSelectionAndBringIntoView(paragraphRange.End, This);
                }
            }
        }
 
        private static void OnSelectUpByParagraph(object sender, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(sender);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            TextEditorTyping._FlushPendingInputItems(This);
 
            using (This.Selection.DeclareChangeBlock())
            {
                // Forget previously suggested horizontal position
                TextEditorSelection._ClearSuggestedX(This);
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(This);
 
                // Clear springload formatting
                ClearSpringloadFormatting(This);
 
                ITextPointer movingPointer = This.Selection.MovingPosition.CreatePointer();
                ITextRange paragraphRange = new TextRange(movingPointer, movingPointer);
                paragraphRange.SelectParagraph(movingPointer);
 
                if (movingPointer.CompareTo(paragraphRange.Start) > 0)
                {
                    // We are in the middle of a paragraph. Move to its start
                    ExtendSelectionAndBringIntoView(paragraphRange.Start, This);
                }
                else
                {
                    movingPointer.MoveToPosition(paragraphRange.Start);
                    if (movingPointer.MoveToNextInsertionPosition(LogicalDirection.Backward))
                    {
                        // Previous paragraph found. Set selection to its start
                        paragraphRange.SelectParagraph(movingPointer);
                        ExtendSelectionAndBringIntoView(paragraphRange.Start, This);
                    }
                }
            }
        }
 
        private static void OnSelectDownByPage(object sender, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(sender);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            TextEditorTyping._FlushPendingInputItems(This);
 
            ITextPointer movingPosition;
            double suggestedX = TextEditorSelection.GetSuggestedX(This, out movingPosition);
 
            // Continue only if we have a moving position with valid layout.
            if (movingPosition == null)
            {
                return;
            }
 
            using (This.Selection.DeclareChangeBlock())
            {
                ITextPointer targetPosition;
                double newSuggestedX;
                double pageHeight = (double)This.UiScope.GetValue(TextEditor.PageHeightProperty);
 
                // Presence of page height property on TextEditor instructs us to use simple bottomless version of
                // pagination (TextBox/RichTextBox).
                // Otherwise, when page height = 0, we use TextView implementation of GetPositionAtNextPage().
                if (pageHeight == 0)
                {
                    if (IsPaginated(This.TextView))
                    {
                        int pagesMoved;
                        Point newSuggestedOffset;
 
                        // Get suggested Y for moving position
                        double suggestedY = GetSuggestedYFromPosition(This, movingPosition);
 
                        targetPosition = This.TextView.GetPositionAtNextPage(movingPosition, new Point(GetViewportXOffset(This.TextView, suggestedX), suggestedY), +1, out newSuggestedOffset, out pagesMoved);
                        newSuggestedX = newSuggestedOffset.X;
                        Invariant.Assert(targetPosition != null);
 
                        if (pagesMoved != 0)
                        {
                            // Update suggestedX
                            TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX);
                            ExtendSelectionAndBringIntoView(targetPosition, This);
                        }
                        else if (IsPaginated(This.TextView))
                        {
                            // If page move wasn't successful because it is not in the view, bring the next page into view.
                            This.TextView.BringPageIntoViewCompleted += new BringPageIntoViewCompletedEventHandler(HandleSelectByPageCompleted);
                            This.TextView.BringPageIntoViewAsync(targetPosition, newSuggestedOffset, +1, This);
                        }
                        else
                        {
                            // No more pages in this direction. Move to container end.
                            ExtendSelectionAndBringIntoView(targetPosition.TextContainer.End, This);
                        }
                    }
                }
                else
                {
                    // Calculate target position - at a specified distance from current movingPosition
                    Rect targetRect = This.TextView.GetRectangleFromTextPosition(movingPosition);
                    Point targetPoint = new Point(GetViewportXOffset(This.TextView, suggestedX), targetRect.Top + pageHeight);
                    targetPosition = This.TextView.GetTextPositionFromPoint(targetPoint, /*snapToText:*/true);
 
                    if (targetPosition == null)
                    {
                        return;
                    }
 
                    // Check if the new position really moving forward;
                    // otherwise force it to the very end of container.
                    if (targetPosition.CompareTo(movingPosition) <= 0)
                    {
                        targetPosition = This.TextContainer.End;
                    }
 
                    // Extend the selection edge.
                    ExtendSelectionAndBringIntoView(targetPosition, This);
 
                    // Fire a page up/down command on the renderScope, so any ScrollViewer will pick it up
                    ScrollBar.PageDownCommand.Execute(null, This.TextView.RenderScope);
                }
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(This);
 
                // Clear springload formatting
                ClearSpringloadFormatting(This);
            }
        }
 
        private static void OnSelectUpByPage(object sender, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(sender);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            TextEditorTyping._FlushPendingInputItems(This);
 
            ITextPointer movingPosition;
            double suggestedX = TextEditorSelection.GetSuggestedX(This, out movingPosition);
 
            // Continue only if we have a moving position with valid layout.
            if (movingPosition == null)
            {
                return;
            }
 
            using (This.Selection.DeclareChangeBlock())
            {
                ITextPointer targetPosition;
                double newSuggestedX;
                double pageHeight = (double)This.UiScope.GetValue(TextEditor.PageHeightProperty);
 
                // Presence of page height property on TextEditor instructs us to use simple bottomless version of
                // pagination (TextBox/RichTextBox).
                // Otherwise, when page height = 0, we use TextView implementation of GetPositionAtNextPage().
                if (pageHeight == 0)
                {
                    if (IsPaginated(This.TextView))
                    {
                        int pagesMoved;
                        Point newSuggestedOffset;
 
                        // Get suggested Y for moving position
                        double suggestedY = GetSuggestedYFromPosition(This, movingPosition);
 
                        targetPosition = This.TextView.GetPositionAtNextPage(movingPosition, new Point(GetViewportXOffset(This.TextView, suggestedX), suggestedY), -1, out newSuggestedOffset, out pagesMoved);
                        newSuggestedX = newSuggestedOffset.X;
                        Invariant.Assert(targetPosition != null);
 
                        if (pagesMoved != 0)
                        {
                            // Update suggestedX
                            TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX);
                            ExtendSelectionAndBringIntoView(targetPosition, This);
                        }
                        else if (IsPaginated(This.TextView))
                        {
                            // If page move wasn't successful because it is not in the view, bring the next page into view.
                            This.TextView.BringPageIntoViewCompleted += new BringPageIntoViewCompletedEventHandler(HandleSelectByPageCompleted);
                            This.TextView.BringPageIntoViewAsync(targetPosition, newSuggestedOffset, -1, This);
                        }
                        else
                        {
                            // No more pages in this direction. Move to container start.
                            ExtendSelectionAndBringIntoView(targetPosition.TextContainer.Start, This);
                        }
                    }
                }
                else
                {
                    // Calculate target position - at a specified distance from current movingPosition
                    Rect targetRect = This.TextView.GetRectangleFromTextPosition(movingPosition);
                    Point targetPoint = new Point(GetViewportXOffset(This.TextView, suggestedX), targetRect.Bottom - pageHeight);
                    targetPosition = This.TextView.GetTextPositionFromPoint(targetPoint, /*snapToText:*/true);
 
                    if (targetPosition == null)
                    {
                        return;
                    }
 
                    // Check if the new position really moving forward;
                    // otherwise force it to the very end of container.
                    if (targetPosition.CompareTo(movingPosition) >= 0)
                    {
                        targetPosition = This.TextContainer.Start;
                    }
 
                    // Extend the selection edge.
                    ExtendSelectionAndBringIntoView(targetPosition, This);
 
                    // Fire a page up/down command on the renderScope, so any ScrollViewer will pick it up
                    ScrollBar.PageUpCommand.Execute(null, This.TextView.RenderScope);
                }
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(This);
 
                // Clear springload formatting
                ClearSpringloadFormatting(This);
            }
        }
 
        /// <summary>
        /// </summary>
        private static void OnSelectToLineStart(object target, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(target);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            TextEditorTyping._FlushPendingInputItems(This);
 
            // When getting moving position, we need to take care of end-of-line case
            // and adjust moving position according to its orientation.
            ITextPointer movingPositionInner = TextEditorSelection.GetMovingPositionInner(This);
 
            // We need a non-dirty layout to walk pages.
            if (!movingPositionInner.ValidateLayout())
            {
                return;
            }
 
            TextSegment lineRange = TextEditorSelection.GetNormalizedLineRange(This.TextView, movingPositionInner);
            if (lineRange.IsNull)
            {
                return;
            }
 
            using (This.Selection.DeclareChangeBlock())
            {
                // Extend the selection from the active end (oriented forward)
                ExtendSelectionAndBringIntoView(lineRange.Start.CreatePointer(LogicalDirection.Forward), This);
 
                // Forget previously suggested horizontal position
                TextEditorSelection._ClearSuggestedX(This);
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(This);
 
                // Clear springload formatting
                ClearSpringloadFormatting(This);
            }
        }
 
        /// <summary>
        /// </summary>
        private static void OnSelectToLineEnd(object target, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(target);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            TextEditorTyping._FlushPendingInputItems(This);
 
            // When getting moving position, we need to take care of end-of-line case
            // and adjust moving position according to its orientation.
            ITextPointer movingPositionInner = TextEditorSelection.GetMovingPositionInner(This);
 
            // We need a non-dirty layout to walk pages.
            if (!movingPositionInner.ValidateLayout())
            {
                return;
            }
 
            TextSegment lineRange = TextEditorSelection.GetNormalizedLineRange(This.TextView, movingPositionInner);
            if (lineRange.IsNull)
            {
                return;
            }
 
            // The selection end is on the other side of a line break, don't move it.
            if (lineRange.End.CompareTo(This.Selection.End) < 0)
            {
                return;
            }
 
            using (This.Selection.DeclareChangeBlock())
            {
                ITextPointer destination = lineRange.End;
 
                if (TextPointerBase.IsNextToPlainLineBreak(destination, LogicalDirection.Forward) ||
                    TextPointerBase.IsNextToRichLineBreak(destination, LogicalDirection.Forward))
                {
                    // Extend to include any following line break if the anchor position lies at the start of a line.
 
                    if (This.Selection.AnchorPosition.ValidateLayout())
                    {
                        TextSegment anchorLineRange = TextEditorSelection.GetNormalizedLineRange(This.TextView, This.Selection.AnchorPosition);
                        if (!lineRange.IsNull && anchorLineRange.Start.CompareTo(This.Selection.AnchorPosition) == 0)
                        {
                            destination = destination.GetNextInsertionPosition(LogicalDirection.Forward);
                        }
                    }
                }
                else if (TextPointerBase.IsNextToParagraphBreak(destination, LogicalDirection.Forward) &&
                         TextPointerBase.IsNextToParagraphBreak(This.Selection.AnchorPosition, LogicalDirection.Backward))
                {
                    // Extend to include any following Paragraph break if the anchor position lies at the start of a Paragraph.
 
                    ITextPointer newDestination = destination.GetNextInsertionPosition(LogicalDirection.Forward);
 
                    if (newDestination == null)
                    {
                        // We are at the end of container - extend to include position after last paragraph
                        destination = destination.TextContainer.End;
                    }
                    else
                    {
                        destination = newDestination;
                    }
                }
 
                // Set orientation towards line content
                destination = destination.GetFrozenPointer(LogicalDirection.Backward);
 
                // Extend the selection from the active end.
                ExtendSelectionAndBringIntoView(destination, This);
 
                // Forget previously suggested horizontal position
                TextEditorSelection._ClearSuggestedX(This);
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(This);
 
                // Clear springload formatting
                ClearSpringloadFormatting(This);
            }
        }
 
        /// <summary>
        /// </summary>
        private static void OnSelectToDocumentStart(object target, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(target);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            TextEditorTyping._FlushPendingInputItems(This);
 
            using (This.Selection.DeclareChangeBlock())
            {
                ExtendSelectionAndBringIntoView(This.TextContainer.Start, This);
 
                // Forget previously suggested horizontal position
                TextEditorSelection._ClearSuggestedX(This);
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(This);
 
                // Clear springload formatting
                ClearSpringloadFormatting(This);
            }
        }
 
        /// <summary>
        /// </summary>
        private static void OnSelectToDocumentEnd(object target, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(target);
 
            if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source))
            {
                return;
            }
 
            TextEditorTyping._FlushPendingInputItems(This);
 
            using (This.Selection.DeclareChangeBlock())
            {
                ExtendSelectionAndBringIntoView(This.TextContainer.End, This);
 
                // Forget previously suggested horizontal position
                TextEditorSelection._ClearSuggestedX(This);
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(This);
 
                // Clear springload formatting
                ClearSpringloadFormatting(This);
            }
        }
 
        /// <summary>
        /// Handler for ITextView.BringLineIntoViewCompleted event.
        /// </summary>
        private static void HandleMoveByLineCompleted(object sender, BringLineIntoViewCompletedEventArgs e)
        {
            Invariant.Assert(sender is ITextView);
            ((ITextView)sender).BringLineIntoViewCompleted -= new BringLineIntoViewCompletedEventHandler(HandleMoveByLineCompleted);
 
            if (e != null && !e.Cancelled && e.Error == null)
            {
                TextEditor This = e.UserState as TextEditor;
 
                if (This == null || !This._IsEnabled)
                {
                    return;
                }
 
                TextEditorTyping._FlushPendingInputItems(This);
 
                using (This.Selection.DeclareChangeBlock())
                {
                    // Update suggestedX
                    TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, e.NewSuggestedX);
 
                    // Move insertion point to next or previous line
                    This.Selection.SetCaretToPosition(e.NewPosition, e.NewPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true);
                }
            }
        }
 
        /// <summary>
        /// Handler for ITextView.BringPageIntoViewCompleted event.
        /// </summary>
        private static void HandleMoveByPageCompleted(object sender, BringPageIntoViewCompletedEventArgs e)
        {
            Invariant.Assert(sender is ITextView);
            ((ITextView)sender).BringPageIntoViewCompleted -= new BringPageIntoViewCompletedEventHandler(HandleMoveByPageCompleted);
 
            if (e != null && !e.Cancelled && e.Error == null)
            {
                TextEditor This = e.UserState as TextEditor;
 
                if (This == null || !This._IsEnabled)
                {
                    return;
                }
 
                TextEditorTyping._FlushPendingInputItems(This);
 
                using (This.Selection.DeclareChangeBlock())
                {
                    // Update suggestedX
                    TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, e.NewSuggestedOffset.X);
 
                    // Move insertion point to next or previous page
                    This.Selection.SetCaretToPosition(e.NewPosition, e.NewPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true);
                }
            }
        }
 
        /// <summary>
        /// Handler for ITextView.BringLineIntoViewCompleted event.
        /// </summary>
        private static void HandleSelectByLineCompleted(object sender, BringLineIntoViewCompletedEventArgs e)
        {
            TextEditor This;
 
            Invariant.Assert(sender is ITextView);
            ((ITextView)sender).BringLineIntoViewCompleted -= new BringLineIntoViewCompletedEventHandler(HandleSelectByLineCompleted);
 
            if (e != null && !e.Cancelled && e.Error == null)
            {
                This = e.UserState as TextEditor;
 
                if (This == null || !This._IsEnabled)
                {
                    return;
                }
 
                TextEditorTyping._FlushPendingInputItems(This);
 
                using (This.Selection.DeclareChangeBlock())
                {
                    // Update suggestedX
                    TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, e.NewSuggestedX);
                    int newComparedToOld = e.NewPosition.CompareTo(e.Position);
 
                    if (e.Count < 0) // Moving up if count < 0, moving down otherwise
                    {
                        // Shift key is down - Extend the selection edge
                        if (newComparedToOld < 0 || newComparedToOld == 0 && e.NewPosition.LogicalDirection != e.Position.LogicalDirection)
                        {
                            // Note: we compare orientations of equal positions to handle a case
                            // when original position was at the end of line (after its linebreak with backward orientation)
                            // as a result of Shift+End selection; and the new position is in the beginning of the next line,
                            // which is essentially the same position but oriented differently.
                            // In such a case the new position is good enough to go there.
                            // We certainly don't want to go to the end of the document in this case.
 
                            // We have another line in a given direction; move to it
                            ExtendSelectionAndBringIntoView(e.NewPosition, This);
                        }
                        else
                        {
                            // Remember where we were so that we can return if a line down follows.
                            if (This._NextLineAdvanceMovingPosition == null)
                            {
                                This._NextLineAdvanceMovingPosition = e.Position;
                                This._IsNextLineAdvanceMovingPositionAtDocumentHead = true;
                            }
 
                            // No more lines in this direction. Move to start of current line.
                            ExtendSelectionAndBringIntoView(GetPositionAtLineStart(e.NewPosition), This);
                        }
                    }
                    else
                    {
                        AdjustMovingPositionForSelectDownByLine(This, e.NewPosition, e.Position, e.NewSuggestedX);
                    }
                }
            }
        }
 
        /// <summary>
        /// Handler for ITextView.BringPageIntoViewCompleted event.
        /// </summary>
        private static void HandleSelectByPageCompleted(object sender, BringPageIntoViewCompletedEventArgs e)
        {
            TextEditor This;
 
            Invariant.Assert(sender is ITextView);
            ((ITextView)sender).BringPageIntoViewCompleted -= new BringPageIntoViewCompletedEventHandler(HandleSelectByPageCompleted);
 
            if (e != null && !e.Cancelled && e.Error == null)
            {
                This = e.UserState as TextEditor;
 
                if (This == null || !This._IsEnabled)
                {
                    return;
                }
 
                TextEditorTyping._FlushPendingInputItems(This);
 
                using (This.Selection.DeclareChangeBlock())
                {
                    // Update suggestedX
                    TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, e.NewSuggestedOffset.X);
                    int newComparedToOld = e.NewPosition.CompareTo(e.Position);
 
                    if (e.Count < 0) // Moving up if count < 0, moving down otherwise
                    {
                        // Shift key is down - Extend the selection edge
                        if (newComparedToOld < 0)
                        {
                            // We have another page in a given direction; move to it
                            ExtendSelectionAndBringIntoView(e.NewPosition, This);
                        }
                        else
                        {
                            // No more pages in this direction. Move to container start.
                            ExtendSelectionAndBringIntoView(e.NewPosition.TextContainer.Start, This);
                        }
                    }
                    else
                    {
                        // Shift key is down - Extend the selection edge
                        if (newComparedToOld > 0)
                        {
                            // We have another page in a given direction; move to it
                            ExtendSelectionAndBringIntoView(e.NewPosition, This);
                        }
                        else
                        {
                            // No more pages in this direction. Move to container end.
                            ExtendSelectionAndBringIntoView(e.NewPosition.TextContainer.End, This);
                        }
                    }
                }
            }
        }
 
        // ----------------------------------------------------------
        //
        // Misceleneous Commands
        //
        // ----------------------------------------------------------
 
        #region Misceleneous Commands
 
        /// <summary>
        /// Keyboard selection commands QueryStatus handler
        /// </summary>
        private static void OnQueryStatusKeyboardSelection(object target, CanExecuteRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(target);
 
            if (This == null || !This._IsEnabled)
            {
                return;
            }
 
            args.CanExecute = true;
        }
 
        /// <summary>
        /// Caret navigation commands QueryStatus handler
        /// </summary>
        private static void OnQueryStatusCaretNavigation(object target, CanExecuteRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(target);
 
            if (This == null || !This._IsEnabled)
            {
                return;
            }
 
            // We want to disable caret navigation for readonly content, when caret is not visible.
            if (This.IsReadOnly && !This.IsReadOnlyCaretVisible)
            {
                return;
            }
 
            args.CanExecute = true;
        }
 
        /// <summary>
        /// Placeholder for commands which are not yet implemented
        /// </summary>
        /// <param name="source"></param>
        /// <param name="args"></param>
        private static void OnNYICommand(object source, ExecutedRoutedEventArgs args)
        {
        }
 
        #endregion Misceleneous Commands
 
        // ----------------------------------------------------------
        //
        // Misceleneous Private Methods
        //
        // ----------------------------------------------------------
 
        #region Misceleneous Private Methods
 
        // Helper for clearing springload formatting
        private static void ClearSpringloadFormatting(TextEditor This)
        {
            if (This.Selection is TextSelection)
            {
                ((TextSelection)This.Selection).ClearSpringloadFormatting();
            }
        }
 
        /// <summary>
        /// Return true if the flow direction is RightToLeft.
        /// </summary>
        private static bool IsFlowDirectionRightToLeftThenTopToBottom(TextEditor textEditor)
        {
            Invariant.Assert(textEditor != null);
 
            ITextPointer position = textEditor.Selection.MovingPosition.CreatePointer();
 
            // Skip any scoping formatting elements.
            while (TextSchema.IsFormattingType(position.ParentType))
            {
                position.MoveToElementEdge(ElementEdge.AfterEnd);
            }
 
            FlowDirection flowDirection = (FlowDirection)position.GetValue(FlowDocument.FlowDirectionProperty);
 
            return (flowDirection == FlowDirection.RightToLeft);
        }
 
        /// <summary>
        /// Move or extend to character according to the logical direction.
        /// </summary>
        private static void MoveToCharacterLogicalDirection(TextEditor textEditor, LogicalDirection direction, bool extend)
        {
            Invariant.Assert(textEditor != null);
 
            TextEditorTyping._FlushPendingInputItems(textEditor);
 
            // Move selection in requested direction
            using (textEditor.Selection.DeclareChangeBlock())
            {
                if (extend)
                {
                    // Extend the selection edge. Shift key should be pressed in this case.
                    textEditor.Selection.ExtendToNextInsertionPosition(direction);
 
                    // Bring selection moving position into view
                    BringIntoView(textEditor.Selection.MovingPosition, textEditor);
                }
                else
                {
                    // Note that when the selection is non-empty we just collapse it, i.e. use one of its ends
                    ITextPointer movingEnd = (direction == LogicalDirection.Forward ? textEditor.Selection.End : textEditor.Selection.Start);
 
                    if (textEditor.Selection.IsEmpty)
                    {
                        movingEnd = movingEnd.GetNextInsertionPosition(direction);
                    }
 
                    if (movingEnd != null)
                    {
                        // Identify an orientation toward content as a character just passed by this move
                        LogicalDirection contentDirection = direction == LogicalDirection.Forward ? LogicalDirection.Backward : LogicalDirection.Forward;
 
                        // Set caret next to a text character just passed
                        movingEnd = movingEnd.GetInsertionPosition(contentDirection);
 
                        // By disallowing to stop near spaces we suppress our "next-to-space" formatting heuristics
                        // which would consider space character as formatting insignificant.
                        // This means that when we pass a space by keyboard we will respect its formatting,
                        // while when we click next to a space we always ignore space's formatting
                        // and prefer neighboring non-space character formatting instead.
                        textEditor.Selection.SetCaretToPosition(movingEnd, contentDirection, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false);
                    }
                }
 
                // Remember to scroll the text if we're outside the viewport.
                textEditor.Selection.OnCaretNavigation();
 
                // Forget previously suggested horizontal position
                TextEditorSelection._ClearSuggestedX(textEditor);
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(textEditor);
 
                // Clear springload formatting
                ClearSpringloadFormatting(textEditor);
            }
        }
 
        /// <summary>
        /// Navigate word according to the logical direction.
        /// </summary>
        private static void NavigateWordLogicalDirection(TextEditor textEditor, LogicalDirection direction)
        {
            Invariant.Assert(textEditor != null);
 
            TextEditorTyping._FlushPendingInputItems(textEditor);
 
            using (textEditor.Selection.DeclareChangeBlock())
            {
                // Forget previously suggested horizontal position
                TextEditorSelection._ClearSuggestedX(textEditor);
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(textEditor);
 
                // Clear springload formatting
                ClearSpringloadFormatting(textEditor);
 
                if (direction == LogicalDirection.Forward)
                {
                    // Move/extend selection in requested direction
                    if (!textEditor.Selection.IsEmpty && TextPointerBase.IsAtWordBoundary(textEditor.Selection.End, LogicalDirection.Forward))
                    {
                        // If the selection is non-empty and ends on a word boundary, collapse it to that boundary.
                        textEditor.Selection.SetCaretToPosition(textEditor.Selection.End, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false);
                        // Note that we are using a "smart" version of SetCaretToPosition
                        // to choose caret orientation on formattinng switches appropriately.
                    }
                    else
                    {
                        ITextPointer wordBoundary = textEditor.Selection.End.CreatePointer();
                        TextPointerBase.MoveToNextWordBoundary(wordBoundary, LogicalDirection.Forward);
 
                        textEditor.Selection.SetCaretToPosition(wordBoundary, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false);
                        // Note that we are using a "smart" version of SetCaretToPosition
                        // to choose caret orientation on formattinng switches appropriately.
                    }
                }
                else
                {
                    // Move/extend selection in requested direction
                    if (!textEditor.Selection.IsEmpty && TextPointerBase.IsAtWordBoundary(textEditor.Selection.Start, LogicalDirection.Forward))
                    {
                        // If the selection is non-empty and starts at word boundary, collapse it to that boundary.
                        textEditor.Selection.SetCaretToPosition(textEditor.Selection.Start, LogicalDirection.Forward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false);
                        // Note that we are using a "smart" version of SetCaretToPosition
                        // to choose caret orientation on formattinng switches appropriately.
                    }
                    else
                    {
                        ITextPointer wordBoundary = textEditor.Selection.Start.CreatePointer();
                        TextPointerBase.MoveToNextWordBoundary(wordBoundary, LogicalDirection.Backward);
 
                        textEditor.Selection.SetCaretToPosition(wordBoundary, LogicalDirection.Forward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false);
                        // Note that we are using a "smart" version of SetCaretToPosition
                        // to choose caret orientation on formattinng switches appropriately.
                    }
                }
 
                // Remember to scroll the text if we're outside the viewport.
                textEditor.Selection.OnCaretNavigation();
 
                // Forget previously suggested horizontal position
                TextEditorSelection._ClearSuggestedX(textEditor);
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(textEditor);
 
                // Clear springload formatting
                ClearSpringloadFormatting(textEditor);
            }
        }
 
        /// <summary>
        /// Extend word according to the logical direction.
        /// </summary>
        private static void ExtendWordLogicalDirection(TextEditor textEditor, LogicalDirection direction)
        {
            Invariant.Assert(textEditor != null);
 
            TextEditorTyping._FlushPendingInputItems(textEditor);
 
            using (textEditor.Selection.DeclareChangeBlock())
            {
                // Forget previously suggested horizontal position
                TextEditorSelection._ClearSuggestedX(textEditor);
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(textEditor);
 
                // Clear springload formatting
                ClearSpringloadFormatting(textEditor);
 
                // Move/extend selection in requested direction
                ITextPointer wordBoundary = textEditor.Selection.MovingPosition.CreatePointer();
                TextPointerBase.MoveToNextWordBoundary(wordBoundary, direction);
 
                wordBoundary.SetLogicalDirection(direction == LogicalDirection.Forward ? LogicalDirection.Backward : LogicalDirection.Forward);
 
                // Extend selection and bring new position into view if needed (for paginated viewers)
                ExtendSelectionAndBringIntoView(wordBoundary, textEditor);
 
                // Remember to scroll the text if we're outside the viewport.
                textEditor.Selection.OnCaretNavigation();
 
                // Forget previously suggested horizontal position
                TextEditorSelection._ClearSuggestedX(textEditor);
 
                // Discard typing undo unit merging
                TextEditorTyping._BreakTypingSequence(textEditor);
 
                // Clear springload formatting
                ClearSpringloadFormatting(textEditor);
            }
        }
 
        /// <summary>
        /// Returns a moving position and the suggestedX value for it.
        /// </summary>
        /// <param name="This">TextEditor</param>
        /// <param name="innerMovingPosition">Inner moving position if it has valid layout, otherwise null.</param>
        /// <returns>Returns suggestedX when moving position has valid layout, Double.NaN otherwise.</returns>
        private static Double GetSuggestedX(TextEditor This, out ITextPointer innerMovingPosition)
        {
            // When getting moving position, we need to take care of end-of-line case
            // and adjust moving position according to its orientation.
            innerMovingPosition = TextEditorSelection.GetMovingPositionInner(This);
 
            // We need a non-dirty layout to walk pages.
            if (!innerMovingPosition.ValidateLayout())
            {
                innerMovingPosition = null;
                return Double.NaN; // This value is not supposed to be used by a caller.
            }
 
            if (Double.IsNaN(This._suggestedX))
            {
                This._suggestedX = GetAbsoluteXOffset(This.TextView, innerMovingPosition);
 
                // If the original moving position is on the other side of a line break,
                // add in the pixel width of the line break visualization.
                // Note this logic implicitly only modifies suggested x when the
                // selection is non-empty.
                if (This.Selection.MovingPosition.CompareTo(innerMovingPosition) > 0)
                {
                    double breakWidth = (double)innerMovingPosition.GetValue(TextElement.FontSizeProperty) * CaretElement.c_endOfParaMagicMultiplier;
 
                    FlowDirection paragraphFlowDirection = GetScopingParagraphFlowDirection(innerMovingPosition);
                    FlowDirection controlFlowDirection = This.UiScope.FlowDirection;
 
                    if (paragraphFlowDirection != controlFlowDirection)
                    {
                        // Adjust for X-axis flip on Paragraphs with non-default flow direction.
                        breakWidth = -breakWidth;
                    }
 
                    This._suggestedX += breakWidth; 
                }
            }
 
            return This._suggestedX;
        }
 
        /// <summary>
        /// Returns a suggested Y-value for a given position.
        /// </summary>
        /// <param name="This">TextEditor</param>
        /// <param name="position">Position for which suggested Y is needed</param>
        /// <returns>If position is null, returns Double.NaN. This method will not find the moving position. It is meant to be
        /// used after the moving position has been calculated with GetSuggestedX</returns>
        private static Double GetSuggestedYFromPosition(TextEditor This, ITextPointer position)
        {
            double suggestedY = Double.NaN;
            if (position != null)
            {
                suggestedY = This.TextView.GetRectangleFromTextPosition(position).Y;
            }
 
            return suggestedY;
        }
 
        /// <summary>
        /// Update suggestedX value if it has changed due to selection moving position crossing a page or column boundary.
        /// </summary>
        /// <param name="This">TextEditor</param>
        /// <param name="newSuggestedX">New suggestedX value</param>
        private static void UpdateSuggestedXOnColumnOrPageBoundary(TextEditor This, double newSuggestedX)
        {
            if (This._suggestedX != newSuggestedX)
            {
                This._suggestedX = newSuggestedX;
            }
        }
 
        /// <summary>
        /// Returns a position belonging to currently selected line.
        /// On a line boundary it depends on the movingPosition orientation.
        /// When the position is at the end of Selection and oriented backward
        /// we consider it belonging to a previous line.
        /// When it is oriented forward in the same location,
        /// it belongs to the next line.
        /// This method is supposed to perform appropriate correction -
        /// returns a position which is inside (inner) of the desired visual line.
        /// </summary>
        private static ITextPointer GetMovingPositionInner(TextEditor This)
        {
            ITextPointer movingPosition = This.Selection.MovingPosition;
 
            if (!(movingPosition is DocumentSequenceTextPointer || movingPosition is FixedTextPointer) &&
                movingPosition.LogicalDirection == LogicalDirection.Backward &&
                This.Selection.Start.CompareTo(movingPosition) < 0 &&
                TextPointerBase.IsNextToAnyBreak(movingPosition, LogicalDirection.Backward))
            {
                movingPosition = movingPosition.GetNextInsertionPosition(LogicalDirection.Backward);
 
                // When the end of selection was after the linebreak of empty line in plainn text
                // we need to change its orientation to not put it at the end of preceding line.
                if (TextPointerBase.IsNextToPlainLineBreak(movingPosition, LogicalDirection.Backward))
                {
                    movingPosition = movingPosition.GetFrozenPointer(LogicalDirection.Forward);
                }
            }
            else if (TextPointerBase.IsAfterLastParagraph(movingPosition))
            {
                movingPosition = movingPosition.GetInsertionPosition(movingPosition.LogicalDirection);
            }
 
            return movingPosition;
        }
 
        // Returns a position in the beginning of a selection which
        // belongs to visually selected line.
        // Its is Start position of a selection, only its orientation
        // must be Forward when selection is non-empty.
        private static ITextPointer GetStartInner(TextEditor This)
        {
            return This.Selection.IsEmpty ? This.Selection.Start : This.Selection.Start.GetFrozenPointer(LogicalDirection.Forward);
        }
 
 
        // Returns a position in the end of a selection,
        // which belongs to visually selected line.
        // It may be different from Selection.End when
        // selection ends immediately after line break
        // and oriented backward, i.e. belongs visually
        // to previous line.
        private static ITextPointer GetEndInner(TextEditor This)
        {
            ITextPointer end = This.Selection.End;
            if (end.CompareTo(This.Selection.MovingPosition) == 0)
            {
                end = GetMovingPositionInner(This);
            }
            return end;
        }
 
        // Returns a position at the start of current line of movingPosition.
        private static ITextPointer GetPositionAtLineStart(ITextPointer position)
        {
            TextSegment lineRange = position.TextContainer.TextView.GetLineRange(position);
            // When the moving position passed to this method is at hard structual boundaries such as AnchoredBlock or TableCell,
            // TextView returns a null line range.
            return lineRange.IsNull ? position : lineRange.Start;
        }
 
        // Returns a position at the end of current line of movingPosition.
        private static ITextPointer GetPositionAtLineEnd(ITextPointer position)
        {
            TextSegment lineRange = position.TextContainer.TextView.GetLineRange(position);
            return lineRange.IsNull ? position : lineRange.End;
        }
 
        // Helper to extend selection edge to passed position and bring it into view for paginated viewers.
        private static void ExtendSelectionAndBringIntoView(ITextPointer position, TextEditor textEditor)
        {
            textEditor.Selection.ExtendToPosition(position);
            BringIntoView(position, textEditor);
        }
 
        private static void BringIntoView(ITextPointer position, TextEditor textEditor)
        {
            double pageHeight = (double)textEditor.UiScope.GetValue(TextEditor.PageHeightProperty);
 
            if (pageHeight == 0 && // Check for paginated viewer case
                textEditor.TextView != null && textEditor.TextView.IsValid && !textEditor.TextView.Contains(position) && IsPaginated(textEditor.TextView))
            {
                // This will bring the position into view when it is in another page for paginated viewers.
                textEditor.TextView.BringPositionIntoViewAsync(position, textEditor);
            }
        }
 
        // If the caret is currently at a Table row end, move it inside
        // the last cell.
        // This is useful when trying to navigate by line, because ITextView
        // cannot handle the special end of row position.
        private static void AdjustCaretAtTableRowEnd(TextEditor This)
        {
            if (This.Selection.IsEmpty && TextPointerBase.IsAtRowEnd(This.Selection.Start))
            {
                ITextPointer position = This.Selection.Start.GetNextInsertionPosition(LogicalDirection.Backward);
                if (position != null)
                {
                    This.Selection.SetCaretToPosition(position, LogicalDirection.Forward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false);
                }
            }
        }
 
        // If a position is currently at a Table row end, move it inside
        // the last cell.
        // This is useful when trying to navigate by line, because ITextView
        // cannot handle the special end of row position.
        private static ITextPointer AdjustPositionAtTableRowEnd(ITextPointer position)
        {
            if (TextPointerBase.IsAtRowEnd(position))
            {
                ITextPointer cellEnd = position.GetNextInsertionPosition(LogicalDirection.Backward);
                if (cellEnd != null)
                {
                    position = cellEnd;
                }
            }
 
            return position;
        }
 
        // Returns the FlowDirection of the closest scoping Block.
        private static FlowDirection GetScopingParagraphFlowDirection(ITextPointer position)
        {
            ITextPointer navigator = position.CreatePointer();
 
            while (typeof(Inline).IsAssignableFrom(navigator.ParentType))
            {
                navigator.MoveToElementEdge(ElementEdge.BeforeStart);
            }
 
            return (FlowDirection)navigator.GetValue(FrameworkElement.FlowDirectionProperty);
        }
 
        // Returns the x offset, relative to the left edge of the document
        // of an ITextPointer.
        //
        // This is distinct from ITextView.GetRectFromPosition, which returns
        // a point relative to the viewport, which may be offset by a scrollviewer.
        private static double GetAbsoluteXOffset(ITextView textview, ITextPointer position)
        {
            double x = textview.GetRectangleFromTextPosition(position).X;
 
            // this test only succeeds for TextBoxView.
            // We need to extend ITextView to make the check explicit.
            // Notably, RichTextbox is missed here. 
            if (textview is TextBoxView) // Extra strict....this could be removed in the future.
            {
                IScrollInfo scrollInfo = textview as IScrollInfo;
                if (scrollInfo != null)
                {
                    x += scrollInfo.HorizontalOffset;
                }
            }
 
            return x;
        }
 
        // Returns the x offset, relative to the viewport, of an x position
        // in document coordinates.
        private static double GetViewportXOffset(ITextView textview, double suggestedX)
        {
            // this test only succeeds for TextBoxView.
            // We need to extend ITextView to make the check explicit.
            // Notably, RichTextbox is missed here. 
            if (textview is TextBoxView) // Extra strict....this could be removed in the future.
            {
                IScrollInfo scrollInfo = textview as IScrollInfo;
                if (scrollInfo != null)
                {
                    suggestedX -= scrollInfo.HorizontalOffset;
                }
            }
 
            return suggestedX;
        }
 
        #endregion Misceleneous Private Methods
 
        #endregion Private methods
 
        private const string KeyMoveDownByLine = "Down";
        private const string KeyMoveDownByPage = "PageDown";
        private const string KeyMoveDownByParagraph = "Ctrl+Down";
        private const string KeyMoveLeftByCharacter = "Left";
        private const string KeyMoveLeftByWord = "Ctrl+Left";
        private const string KeyMoveRightByCharacter = "Right";
        private const string KeyMoveRightByWord = "Ctrl+Right";
        private const string KeyMoveToColumnEnd = "Alt+PageDown";
        private const string KeyMoveToColumnStart = "Alt+PageUp";
        private const string KeyMoveToDocumentEnd = "Ctrl+End";
        private const string KeyMoveToDocumentStart = "Ctrl+Home";
        private const string KeyMoveToLineEnd = "End";
        private const string KeyMoveToLineStart = "Home";
        private const string KeyMoveToWindowBottom = "Alt+Ctrl+PageDown";
        private const string KeyMoveToWindowTop = "Alt+Ctrl+PageUp";
        private const string KeyMoveUpByLine = "Up";
        private const string KeyMoveUpByPage = "PageUp";
        private const string KeyMoveUpByParagraph = "Ctrl+Up";
        private const string KeySelectAll = "Ctrl+A";
        private const string KeySelectDownByLine = "Shift+Down";
        private const string KeySelectDownByPage = "Shift+PageDown";
        private const string KeySelectDownByParagraph = "Ctrl+Shift+Down";
        private const string KeySelectLeftByCharacter = "Shift+Left";
        private const string KeySelectLeftByWord = "Ctrl+Shift+Left";
        private const string KeySelectRightByCharacter = "Shift+Right";
        private const string KeySelectRightByWord = "Ctrl+Shift+Right";
        private const string KeySelectToColumnEnd = "Alt+Shift+PageDown";
        private const string KeySelectToColumnStart = "Alt+Shift+PageUp";
        private const string KeySelectToDocumentEnd = "Ctrl+Shift+End";
        private const string KeySelectToDocumentStart = "Ctrl+Shift+Home";
        private const string KeySelectToLineEnd = "Shift+End";
        private const string KeySelectToLineStart = "Shift+Home";
        private const string KeySelectToWindowBottom = "Alt+Ctrl+Shift+PageDown";
        private const string KeySelectToWindowTop = "Alt+Ctrl+Shift+PageUp";
        private const string KeySelectUpByLine = "Shift+Up";
        private const string KeySelectUpByPage = "Shift+PageUp";
        private const string KeySelectUpByParagraph = "Ctrl+Shift+Up";
    }
}