// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; using Unity.CodeEditor.Document; using Unity.CodeEditor.Platform; using Unity.CodeEditor.Rendering; using Unity.CodeEditor.Text; using Unity.CodeEditor.Utils; using UnityEngine; using UnityEngine.UIElements; using LogicalDirection = Unity.CodeEditor.Document.LogicalDirection; namespace Unity.CodeEditor.Editing { internal enum CaretMovementType { None, CharLeft, CharRight, Backspace, WordLeft, WordRight, LineUp, LineDown, PageUp, PageDown, LineStart, LineEnd, DocumentStart, DocumentEnd } internal class CaretNavigationCommandHandler { Dictionary _navigationCommands = new Dictionary(); internal CaretNavigationCommandHandler(TextArea textArea) { TextArea = textArea ?? throw new ArgumentNullException(nameof(textArea)); var keymap = PlatformHotkeyConfiguration.Current; _navigationCommands.Add(new KeyGesture(KeyCode.LeftArrow), CaretNavigationCommand.MoveLeftByCharacter); _navigationCommands.Add(new KeyGesture(KeyCode.LeftArrow, keymap.SelectionModifiers), CaretNavigationCommand.SelectLeftByCharacter); _navigationCommands.Add(new KeyGesture(KeyCode.LeftArrow, keymap.BoxSelectionModifiers | keymap.SelectionModifiers), CaretNavigationCommand.BoxSelectLeftByCharacter); _navigationCommands.Add(new KeyGesture(KeyCode.RightArrow), CaretNavigationCommand.MoveRightByCharacter); _navigationCommands.Add(new KeyGesture(KeyCode.RightArrow, keymap.SelectionModifiers), CaretNavigationCommand.SelectRightByCharacter); _navigationCommands.Add(new KeyGesture(KeyCode.RightArrow, keymap.BoxSelectionModifiers | keymap.SelectionModifiers), CaretNavigationCommand.BoxSelectRightByCharacter); _navigationCommands.Add(new KeyGesture(KeyCode.LeftArrow, keymap.WholeWordTextActionModifiers), CaretNavigationCommand.MoveLeftByWord); _navigationCommands.Add(new KeyGesture(KeyCode.LeftArrow, keymap.WholeWordTextActionModifiers | keymap.SelectionModifiers), CaretNavigationCommand.SelectLeftByWord); _navigationCommands.Add(new KeyGesture(KeyCode.LeftArrow, keymap.WholeWordTextActionModifiers | keymap.BoxSelectionModifiers | keymap.SelectionModifiers), CaretNavigationCommand.BoxSelectLeftByWord); _navigationCommands.Add(new KeyGesture(KeyCode.RightArrow, keymap.WholeWordTextActionModifiers), CaretNavigationCommand.MoveRightByWord); _navigationCommands.Add(new KeyGesture(KeyCode.RightArrow, keymap.WholeWordTextActionModifiers | keymap.SelectionModifiers), CaretNavigationCommand.SelectRightByWord); _navigationCommands.Add(new KeyGesture(KeyCode.RightArrow, keymap.WholeWordTextActionModifiers | keymap.BoxSelectionModifiers | keymap.SelectionModifiers), CaretNavigationCommand.BoxSelectRightByWord); _navigationCommands.Add(new KeyGesture(KeyCode.UpArrow), CaretNavigationCommand.MoveUpByLine); _navigationCommands.Add(new KeyGesture(KeyCode.UpArrow, keymap.SelectionModifiers), CaretNavigationCommand.SelectUpByLine); _navigationCommands.Add(new KeyGesture(KeyCode.UpArrow, keymap.BoxSelectionModifiers | keymap.SelectionModifiers), CaretNavigationCommand.BoxSelectUpByLine); _navigationCommands.Add(new KeyGesture(KeyCode.DownArrow), CaretNavigationCommand.MoveDownByLine); _navigationCommands.Add(new KeyGesture(KeyCode.DownArrow, keymap.SelectionModifiers), CaretNavigationCommand.SelectDownByLine); _navigationCommands.Add(new KeyGesture(KeyCode.DownArrow, keymap.BoxSelectionModifiers | keymap.SelectionModifiers), CaretNavigationCommand.BoxSelectDownByLine); _navigationCommands.Add(new KeyGesture(KeyCode.PageDown), CaretNavigationCommand.MoveDownByPage); _navigationCommands.Add(new KeyGesture(KeyCode.PageDown, keymap.SelectionModifiers), CaretNavigationCommand.SelectDownByPage); _navigationCommands.Add(new KeyGesture(KeyCode.PageUp), CaretNavigationCommand.MoveUpByPage); _navigationCommands.Add(new KeyGesture(KeyCode.PageUp, keymap.SelectionModifiers), CaretNavigationCommand.SelectUpByPage); keymap.MoveCursorToTheStartOfLine.ForEach(g => _navigationCommands.Add(g, CaretNavigationCommand.MoveToLineStart)); keymap.MoveCursorToTheStartOfLineWithSelection.ForEach(g => _navigationCommands.Add(g, CaretNavigationCommand.SelectToLineStart)); keymap.MoveCursorToTheEndOfLine.ForEach(g => _navigationCommands.Add(g, CaretNavigationCommand.MoveToLineEnd)); keymap.MoveCursorToTheEndOfLineWithSelection.ForEach(g => _navigationCommands.Add(g, CaretNavigationCommand.SelectToLineEnd)); _navigationCommands.Add(new KeyGesture(KeyCode.Home, keymap.BoxSelectionModifiers | keymap.SelectionModifiers), CaretNavigationCommand.BoxSelectToLineStart); _navigationCommands.Add(new KeyGesture(KeyCode.End, keymap.BoxSelectionModifiers | keymap.SelectionModifiers), CaretNavigationCommand.BoxSelectToLineEnd); keymap.MoveCursorToTheStartOfDocument.ForEach(g => _navigationCommands.Add(g, CaretNavigationCommand.MoveToDocumentStart)); keymap.MoveCursorToTheStartOfDocumentWithSelection.ForEach(g => _navigationCommands.Add(g, CaretNavigationCommand.SelectToDocumentStart)); keymap.MoveCursorToTheEndOfDocument.ForEach(g => _navigationCommands.Add(g, CaretNavigationCommand.MoveToDocumentEnd)); keymap.MoveCursorToTheEndOfDocumentWithSelection.ForEach(g => _navigationCommands.Add(g, CaretNavigationCommand.SelectToDocumentEnd)); keymap.SelectAll.ForEach(g => _navigationCommands.Add(g, CaretNavigationCommand.SelectAll)); } internal TextArea TextArea { get; } internal void OnKeyDown(KeyDownEvent e) { if (TextArea?.Document == null) return; CaretNavigationCommand command = GetEditingCommand(e); switch (command) { case CaretNavigationCommand.MoveLeftByCharacter: MoveLeftByCharacter(); e.StopPropagation(); break; case CaretNavigationCommand.SelectLeftByCharacter: SelectLeftByCharacter(); e.StopPropagation(); break; case CaretNavigationCommand.BoxSelectLeftByCharacter: BoxSelectLeftByCharacter(); e.StopPropagation(); break; case CaretNavigationCommand.MoveRightByCharacter: MoveRightByCharacter(); e.StopPropagation(); break; case CaretNavigationCommand.SelectRightByCharacter: SelectRightByCharacter(); e.StopPropagation(); break; case CaretNavigationCommand.BoxSelectRightByCharacter: BoxSelectRightByCharacter(); e.StopPropagation(); break; case CaretNavigationCommand.MoveLeftByWord: MoveLeftByWord(); e.StopPropagation(); break; case CaretNavigationCommand.SelectLeftByWord: SelectLeftByWord(); e.StopPropagation(); break; case CaretNavigationCommand.BoxSelectLeftByWord: BoxSelectLeftByWord(); e.StopPropagation(); break; case CaretNavigationCommand.MoveRightByWord: MoveRightByWord(); e.StopPropagation(); break; case CaretNavigationCommand.SelectRightByWord: SelectRightByWord(); e.StopPropagation(); break; case CaretNavigationCommand.BoxSelectRightByWord: BoxSelectRightByWord(); e.StopPropagation(); break; case CaretNavigationCommand.MoveUpByLine: MoveUpByLine(); e.StopPropagation(); break; case CaretNavigationCommand.SelectUpByLine: SelectUpByLine(); e.StopPropagation(); break; case CaretNavigationCommand.BoxSelectUpByLine: BoxSelectUpByLine(); e.StopPropagation(); break; case CaretNavigationCommand.MoveDownByLine: MoveDownByLine(); e.StopPropagation(); break; case CaretNavigationCommand.SelectDownByLine: SelectDownByLine(); e.StopPropagation(); break; case CaretNavigationCommand.BoxSelectDownByLine: BoxSelectDownByLine(); e.StopPropagation(); break; case CaretNavigationCommand.MoveDownByPage: MoveDownByPage(); e.StopPropagation(); break; case CaretNavigationCommand.SelectDownByPage: SelectDownByPage(); e.StopPropagation(); break; case CaretNavigationCommand.MoveUpByPage: MoveUpByPage(); e.StopPropagation(); break; case CaretNavigationCommand.SelectUpByPage: SelectUpByPage(); e.StopPropagation(); break; case CaretNavigationCommand.MoveToLineStart: MoveToLineStart(); e.StopPropagation(); break; case CaretNavigationCommand.SelectToLineStart: SelectToLineStart(); e.StopPropagation(); break; case CaretNavigationCommand.MoveToLineEnd: MoveToLineEnd(); e.StopPropagation(); break; case CaretNavigationCommand.SelectToLineEnd: SelectToLineEnd(); e.StopPropagation(); break; case CaretNavigationCommand.BoxSelectToLineStart: BoxSelectToLineStart(); e.StopPropagation(); break; case CaretNavigationCommand.BoxSelectToLineEnd: BoxSelectToLineEnd(); e.StopPropagation(); break; case CaretNavigationCommand.MoveToDocumentStart: MoveToDocumentStart(); e.StopPropagation(); break; case CaretNavigationCommand.SelectToDocumentStart: SelectToDocumentStart(); e.StopPropagation(); break; case CaretNavigationCommand.MoveToDocumentEnd: MoveToDocumentEnd(); e.StopPropagation(); break; case CaretNavigationCommand.SelectToDocumentEnd: SelectToDocumentEnd(); e.StopPropagation(); break; case CaretNavigationCommand.SelectAll: SelectAll(); e.StopPropagation(); break; } } CaretNavigationCommand GetEditingCommand(KeyDownEvent e) { KeyGesture keyGesture = new KeyGesture(e.keyCode, e.GetKeyModifiers()); if (_navigationCommands.TryGetValue(keyGesture, out CaretNavigationCommand command)) return command; return CaretNavigationCommand.None; } void MoveLeftByCharacter() { OnMoveCaret(TextArea, CaretMovementType.CharLeft); } void SelectLeftByCharacter() { OnMoveCaretExtendSelection(TextArea, CaretMovementType.CharLeft); } void BoxSelectLeftByCharacter() { OnMoveCaretBoxSelection(TextArea, CaretMovementType.CharLeft); } void MoveRightByCharacter() { OnMoveCaret(TextArea, CaretMovementType.CharRight); } void SelectRightByCharacter() { OnMoveCaretExtendSelection(TextArea, CaretMovementType.CharRight); } void BoxSelectRightByCharacter() { OnMoveCaretBoxSelection(TextArea, CaretMovementType.CharRight); } void MoveLeftByWord() { OnMoveCaret(TextArea, CaretMovementType.WordLeft); } void SelectLeftByWord() { OnMoveCaretExtendSelection(TextArea, CaretMovementType.WordLeft); } void BoxSelectLeftByWord() { OnMoveCaretBoxSelection(TextArea, CaretMovementType.WordLeft); } void MoveRightByWord() { OnMoveCaret(TextArea, CaretMovementType.WordRight); } void SelectRightByWord() { OnMoveCaretExtendSelection(TextArea, CaretMovementType.WordRight); } void BoxSelectRightByWord() { OnMoveCaretBoxSelection(TextArea, CaretMovementType.WordRight); } void MoveUpByLine() { OnMoveCaret(TextArea, CaretMovementType.LineUp); } void SelectUpByLine() { OnMoveCaretExtendSelection(TextArea, CaretMovementType.LineUp); } void BoxSelectUpByLine() { OnMoveCaretBoxSelection(TextArea, CaretMovementType.LineUp); } void MoveDownByLine() { OnMoveCaret(TextArea, CaretMovementType.LineDown); } void SelectDownByLine() { OnMoveCaretExtendSelection(TextArea, CaretMovementType.LineDown); } void BoxSelectDownByLine() { OnMoveCaretBoxSelection(TextArea, CaretMovementType.LineDown); } void MoveDownByPage() { OnMoveCaret(TextArea, CaretMovementType.PageDown); } void SelectDownByPage() { OnMoveCaretExtendSelection(TextArea, CaretMovementType.PageDown); } void MoveUpByPage() { OnMoveCaret(TextArea, CaretMovementType.PageUp); } void SelectUpByPage() { OnMoveCaretExtendSelection(TextArea, CaretMovementType.PageUp); } void MoveToLineStart() { OnMoveCaret(TextArea, CaretMovementType.LineStart); } void SelectToLineStart() { OnMoveCaretExtendSelection(TextArea, CaretMovementType.LineStart); } void MoveToLineEnd() { OnMoveCaret(TextArea, CaretMovementType.LineEnd); } void SelectToLineEnd() { OnMoveCaretExtendSelection(TextArea, CaretMovementType.LineEnd); } void BoxSelectToLineStart() { OnMoveCaretBoxSelection(TextArea, CaretMovementType.LineStart); } void BoxSelectToLineEnd() { OnMoveCaretBoxSelection(TextArea, CaretMovementType.LineEnd); } void MoveToDocumentStart() { OnMoveCaret(TextArea, CaretMovementType.DocumentStart); } void SelectToDocumentStart() { OnMoveCaretExtendSelection(TextArea, CaretMovementType.DocumentStart); } void MoveToDocumentEnd() { OnMoveCaret(TextArea, CaretMovementType.DocumentEnd); } void SelectToDocumentEnd() { OnMoveCaretExtendSelection(TextArea, CaretMovementType.DocumentEnd); } void SelectAll() { OnSelectAll(TextArea); } static void OnSelectAll(TextArea textArea) { textArea.Caret.Offset = textArea.Document.TextLength; textArea.Selection = Selection.Create(textArea, 0, textArea.Document.TextLength); } static void OnMoveCaret(TextArea textArea, CaretMovementType direction) { textArea.ClearSelection(); MoveCaret(textArea, direction); float borderSize = direction == CaretMovementType.CharRight || direction == CaretMovementType.WordRight || direction == CaretMovementType.CharLeft || direction == CaretMovementType.WordLeft || direction == CaretMovementType.Backspace ? textArea.TextView.WideSpaceWidth * 2 : 0; textArea.Caret.BringCaretToView(borderSize); } static void OnMoveCaretExtendSelection(TextArea textArea, CaretMovementType direction) { var oldPosition = textArea.Caret.Position; MoveCaret(textArea, direction); textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position); textArea.Caret.BringCaretToView(); } static void OnMoveCaretBoxSelection(TextArea textArea, CaretMovementType direction) { // First, convert the selection into a rectangle selection // (this is required so that virtual space gets enabled for the caret movement) if (textArea.Options.EnableRectangularSelection && !(textArea.Selection is RectangleSelection)) { textArea.Selection = textArea.Selection.IsEmpty ? new RectangleSelection(textArea, textArea.Caret.Position, textArea.Caret.Position) : new RectangleSelection(textArea, textArea.Selection.StartPosition, textArea.Caret.Position); } // Now move the caret and extend the selection var oldPosition = textArea.Caret.Position; MoveCaret(textArea, direction); textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position); textArea.Caret.BringCaretToView(); } #region Caret movement internal static void MoveCaret(TextArea textArea, CaretMovementType direction) { var desiredXPos = textArea.Caret.DesiredXPos; textArea.Caret.Position = GetNewCaretPosition(textArea.TextView, textArea.Caret.Position, direction, textArea.Selection.EnableVirtualSpace, ref desiredXPos); textArea.Caret.DesiredXPos = desiredXPos; } internal static TextViewPosition GetNewCaretPosition(TextView textView, TextViewPosition caretPosition, CaretMovementType direction, bool enableVirtualSpace, ref float desiredXPos) { switch (direction) { case CaretMovementType.None: return caretPosition; case CaretMovementType.DocumentStart: desiredXPos = float.NaN; return new TextViewPosition(0, 0); case CaretMovementType.DocumentEnd: desiredXPos = float.NaN; return new TextViewPosition(textView.Document.GetLocation(textView.Document.TextLength)); } var caretLine = textView.Document.GetLineByNumber(caretPosition.Line); var visualLine = textView.GetOrConstructVisualLine(caretLine); var textLine = visualLine.GetTextLine(caretPosition.VisualColumn, caretPosition.IsAtEndOfLine); switch (direction) { case CaretMovementType.CharLeft: desiredXPos = float.NaN; // do not move caret to previous line in virtual space if (caretPosition.VisualColumn == 0 && enableVirtualSpace) return caretPosition; return GetPrevCaretPosition(textView, caretPosition, visualLine, CaretPositioningMode.Normal, enableVirtualSpace); case CaretMovementType.Backspace: desiredXPos = float.NaN; return GetPrevCaretPosition(textView, caretPosition, visualLine, CaretPositioningMode.EveryCodepoint, enableVirtualSpace); case CaretMovementType.CharRight: desiredXPos = float.NaN; return GetNextCaretPosition(textView, caretPosition, visualLine, CaretPositioningMode.Normal, enableVirtualSpace); case CaretMovementType.WordLeft: desiredXPos = float.NaN; return GetPrevCaretPosition(textView, caretPosition, visualLine, CaretPositioningMode.WordStart, enableVirtualSpace); case CaretMovementType.WordRight: desiredXPos = float.NaN; return GetNextCaretPosition(textView, caretPosition, visualLine, CaretPositioningMode.WordStart, enableVirtualSpace); case CaretMovementType.LineUp: case CaretMovementType.LineDown: case CaretMovementType.PageUp: case CaretMovementType.PageDown: return GetUpDownCaretPosition(textView, caretPosition, direction, visualLine, textLine, enableVirtualSpace, ref desiredXPos); case CaretMovementType.LineStart: desiredXPos = float.NaN; return GetStartOfLineCaretPosition(caretPosition.VisualColumn, visualLine, textLine, enableVirtualSpace); case CaretMovementType.LineEnd: desiredXPos = float.NaN; return GetEndOfLineCaretPosition(visualLine, textLine); default: throw new NotSupportedException(direction.ToString()); } } #endregion #region Home/End static TextViewPosition GetStartOfLineCaretPosition(int oldVisualColumn, VisualLine visualLine, TextLine textLine, bool enableVirtualSpace) { var newVisualCol = visualLine.GetTextLineVisualStartColumn(textLine); if (newVisualCol == 0) newVisualCol = visualLine.GetNextCaretPosition(newVisualCol - 1, LogicalDirection.Forward, CaretPositioningMode.WordStart, enableVirtualSpace); if (newVisualCol < 0) throw ThrowUtil.NoValidCaretPosition(); // when the caret is already at the start of the text, jump to start before whitespace if (newVisualCol == oldVisualColumn) newVisualCol = 0; return visualLine.GetTextViewPosition(newVisualCol); } static TextViewPosition GetEndOfLineCaretPosition(VisualLine visualLine, TextLine textLine) { var newVisualCol = visualLine.GetTextLineVisualStartColumn(textLine) + textLine.Length - textLine.TrailingWhitespaceLength; var pos = visualLine.GetTextViewPosition(newVisualCol); pos.IsAtEndOfLine = true; return pos; } #endregion #region By-character / By-word movement static TextViewPosition GetNextCaretPosition(TextView textView, TextViewPosition caretPosition, VisualLine visualLine, CaretPositioningMode mode, bool enableVirtualSpace) { var pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Forward, mode, enableVirtualSpace); if (pos >= 0) { return visualLine.GetTextViewPosition(pos); } else { // move to start of next line var nextDocumentLine = visualLine.LastDocumentLine.NextLine; if (nextDocumentLine != null) { var nextLine = textView.GetOrConstructVisualLine(nextDocumentLine); pos = nextLine.GetNextCaretPosition(-1, LogicalDirection.Forward, mode, enableVirtualSpace); if (pos < 0) throw ThrowUtil.NoValidCaretPosition(); return nextLine.GetTextViewPosition(pos); } else { // at end of document System.Diagnostics.Debug.Assert(visualLine.LastDocumentLine.Offset + visualLine.LastDocumentLine.TotalLength == textView.Document.TextLength); return new TextViewPosition(textView.Document.GetLocation(textView.Document.TextLength)); } } } static TextViewPosition GetPrevCaretPosition(TextView textView, TextViewPosition caretPosition, VisualLine visualLine, CaretPositioningMode mode, bool enableVirtualSpace) { var pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Backward, mode, enableVirtualSpace); if (pos >= 0) { return visualLine.GetTextViewPosition(pos); } else { // move to end of previous line var previousDocumentLine = visualLine.FirstDocumentLine.PreviousLine; if (previousDocumentLine != null) { var previousLine = textView.GetOrConstructVisualLine(previousDocumentLine); pos = previousLine.GetNextCaretPosition(previousLine.VisualLength + 1, LogicalDirection.Backward, mode, enableVirtualSpace); if (pos < 0) throw ThrowUtil.NoValidCaretPosition(); return previousLine.GetTextViewPosition(pos); } else { // at start of document System.Diagnostics.Debug.Assert(visualLine.FirstDocumentLine.Offset == 0); return new TextViewPosition(0, 0); } } } #endregion #region Line+Page up/down static TextViewPosition GetUpDownCaretPosition(TextView textView, TextViewPosition caretPosition, CaretMovementType direction, VisualLine visualLine, TextLine textLine, bool enableVirtualSpace, ref float xPos) { // moving up/down happens using the desired visual X position if (float.IsNaN(xPos)) xPos = visualLine.GetTextLineVisualXPosition(textLine, caretPosition.VisualColumn); // now find the TextLine+VisualLine where the caret will end up in var targetVisualLine = visualLine; TextLine targetLine; var textLineIndex = visualLine.TextLines.IndexOf(textLine); switch (direction) { case CaretMovementType.LineUp: { // Move up: move to the previous TextLine in the same visual line // or move to the last TextLine of the previous visual line var prevLineNumber = visualLine.FirstDocumentLine.LineNumber - 1; if (textLineIndex > 0) { targetLine = visualLine.TextLines[textLineIndex - 1]; } else if (prevLineNumber >= 1) { var prevLine = textView.Document.GetLineByNumber(prevLineNumber); targetVisualLine = textView.GetOrConstructVisualLine(prevLine); targetLine = targetVisualLine.TextLines[targetVisualLine.TextLines.Count - 1]; } else { targetLine = null; } break; } case CaretMovementType.LineDown: { // Move down: move to the next TextLine in the same visual line // or move to the first TextLine of the next visual line var nextLineNumber = visualLine.LastDocumentLine.LineNumber + 1; if (textLineIndex < visualLine.TextLines.Count - 1) { targetLine = visualLine.TextLines[textLineIndex + 1]; } else if (nextLineNumber <= textView.Document.LineCount) { var nextLine = textView.Document.GetLineByNumber(nextLineNumber); targetVisualLine = textView.GetOrConstructVisualLine(nextLine); targetLine = targetVisualLine.TextLines[0]; } else { targetLine = null; } break; } case CaretMovementType.PageUp: case CaretMovementType.PageDown: { // Page up/down: find the target line using its visual position var yPos = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.LineMiddle); if (direction == CaretMovementType.PageUp) yPos -= textView.Bounds.height; else yPos += textView.Bounds.height; var newLine = textView.GetDocumentLineByVisualTop(yPos); targetVisualLine = textView.GetOrConstructVisualLine(newLine); targetLine = targetVisualLine.GetTextLineByVisualYPosition(yPos); break; } default: throw new NotSupportedException(direction.ToString()); } if (targetLine != null) { var yPos = targetVisualLine.GetTextLineVisualYPosition(targetLine, VisualYPosition.LineMiddle); var newVisualColumn = targetVisualLine.GetVisualColumn(new Vector2(xPos, yPos), enableVirtualSpace); // prevent wrapping to the next line; TODO: could 'IsAtEnd' help here? var targetLineStartCol = targetVisualLine.GetTextLineVisualStartColumn(targetLine); if (newVisualColumn >= targetLineStartCol + targetLine.Length) { if (newVisualColumn <= targetVisualLine.VisualLength) newVisualColumn = targetLineStartCol + targetLine.Length - 1; } return targetVisualLine.GetTextViewPosition(newVisualColumn); } else { return caretPosition; } } #endregion } }