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); } } } }
/// <summary> /// Re-positions the given position by an integral number of text units, but it does /// not guarantee that position is snapped to TextUnit boundary. /// This method assumes that input position is already snapped to appropriate TextUnit boundary. /// </summary> /// <param name="position">The position to move</param> /// <param name="unit">Text units to step by</param> /// <param name="count">Number of units to step over. Also specifies the direction of moving: /// forward if positive, backward otherwise</param> /// <returns>The actual number of units the position was moved over</returns> private int MovePositionByUnits(ITextPointer position, TextUnit unit, int count) { ITextView textView; int moved = 0; int absCount = (count == int.MinValue) ? int.MaxValue : Math.Abs(count); LogicalDirection direction = (count > 0) ? LogicalDirection.Forward : LogicalDirection.Backward; // This method assumes that position is already snapped to appropriate TextUnit. switch (unit) { case TextUnit.Character: while (moved < absCount) { if (!TextPointerBase.MoveToNextInsertionPosition(position, direction)) { break; } moved++; } break; case TextUnit.Word: while (moved < absCount) { if (!MoveToNextWordBoundary(position, direction)) { break; } moved++; } break; case TextUnit.Format: // Formatting changes can be introduced by elements. Hence it is fair to // assume that formatting boundaries are defined by non-text context. while (moved < absCount) { ITextPointer positionOrig = position.CreatePointer(); // First skip all text in given direction. while (position.GetPointerContext(direction) == TextPointerContext.Text) { if (!position.MoveToNextContextPosition(direction)) { break; } } // Move to next context if (!position.MoveToNextContextPosition(direction)) { break; } // Skip all formatting elements and position the pointer next to text. while (position.GetPointerContext(direction) != TextPointerContext.Text) { if (!position.MoveToNextContextPosition(direction)) { break; } } // If moving backwards, position the pointer at the beginning of formatting range. if (direction == LogicalDirection.Backward) { while (position.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.Text) { if (!position.MoveToNextContextPosition(LogicalDirection.Backward)) { break; } } } if (position.GetPointerContext(direction) != TextPointerContext.None) { moved++; } else { position.MoveToPosition(positionOrig); break; } } // Adjust logical direction to point to the following text (forward or backward movement). // If we don't do this, we'll normalize in the wrong direction and get stuck in a loop // if caller tries to advance again. position.SetLogicalDirection(LogicalDirection.Forward); break; case TextUnit.Line: // Position is snapped to nearest line boundary. But since line information // is based on the layout, position is not changed, if: // a) it is not currently in the view, or // b) containing line cannot be found. textView = _textAdaptor.GetUpdatedTextView(); if (textView != null && textView.IsValid && textView.Contains(position)) { // ITextPointer.MoveToLineBoundary can't handle Table row end positions. // Mimic TextEditor's caret navigation code and move into the preceding // TableCell. if (TextPointerBase.IsAtRowEnd(position)) { position.MoveToNextInsertionPosition(LogicalDirection.Backward); } moved = position.MoveToLineBoundary(count); MoveToInsertionPosition(position, LogicalDirection.Forward); if (moved < 0) { moved = -moved; // Will be reversed below. } } break; case TextUnit.Paragraph: // Utilize TextRange logic to determine paragraph boundaries. ITextRange paragraphRange = new TextRange(position, position); paragraphRange.SelectParagraph(position); while (moved < absCount) { position.MoveToPosition(direction == LogicalDirection.Forward ? paragraphRange.End : paragraphRange.Start); if (!position.MoveToNextInsertionPosition(direction)) { break; } moved++; paragraphRange.SelectParagraph(position); position.MoveToPosition(paragraphRange.Start); // Position it always at the beginning of the paragraph. } break; case TextUnit.Page: // But since page information is based on the layout, position is not changed, if: // a) it is not currently in the view, or // b) containing page cannot be found. // Page movement is possible only in multi-page scenario. textView = _textAdaptor.GetUpdatedTextView(); if (textView != null && textView.IsValid && textView.Contains(position)) { if (textView is MultiPageTextView) { // Get embedded page ITextView for given position. ITextView pageTextView = ((MultiPageTextView)textView).GetPageTextViewFromPosition(position); ReadOnlyCollection<TextSegment> textSegments = pageTextView.TextSegments; while (moved < absCount) { if (textSegments == null || textSegments.Count == 0) { break; } // Move the position to appropriate edge. if (direction == LogicalDirection.Backward) { position.MoveToPosition(textSegments[0].Start); MoveToInsertionPosition(position, LogicalDirection.Backward); } else { position.MoveToPosition(textSegments[textSegments.Count - 1].End); MoveToInsertionPosition(position, LogicalDirection.Forward); } // Try to move the position to the next page. ITextPointer positionTemp = position.CreatePointer(); if (!positionTemp.MoveToNextInsertionPosition(direction)) { break; } else { // MoveToNextInsertionPosition may return 'true' and move the position // in oposite direction. if (direction == LogicalDirection.Forward) { if (positionTemp.CompareTo(position) <= 0) { break; } } else { if (positionTemp.CompareTo(position) >= 0) { break; } } } // Get embedded page ITextView for given position. if (!textView.Contains(positionTemp)) { break; } pageTextView = ((MultiPageTextView)textView).GetPageTextViewFromPosition(positionTemp); textSegments = pageTextView.TextSegments; moved++; } } } break; case TextUnit.Document: // This method assumes that position is already snapped to appropriate TextUnit. break; } return (direction == LogicalDirection.Forward) ? moved : -moved; }
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); } } } }