[11700] | 1 | // Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team |
---|
| 2 | // |
---|
| 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this |
---|
| 4 | // software and associated documentation files (the "Software"), to deal in the Software |
---|
| 5 | // without restriction, including without limitation the rights to use, copy, modify, merge, |
---|
| 6 | // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons |
---|
| 7 | // to whom the Software is furnished to do so, subject to the following conditions: |
---|
| 8 | // |
---|
| 9 | // The above copyright notice and this permission notice shall be included in all copies or |
---|
| 10 | // substantial portions of the Software. |
---|
| 11 | // |
---|
| 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
---|
| 13 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR |
---|
| 14 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE |
---|
| 15 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
---|
| 16 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
---|
| 17 | // DEALINGS IN THE SOFTWARE. |
---|
| 18 | |
---|
| 19 | using System; |
---|
| 20 | using System.Collections.Generic; |
---|
| 21 | using System.Diagnostics; |
---|
| 22 | using System.Linq; |
---|
| 23 | using System.Windows; |
---|
| 24 | using System.Windows.Documents; |
---|
| 25 | using System.Windows.Input; |
---|
| 26 | using System.Windows.Media.TextFormatting; |
---|
| 27 | using ICSharpCode.AvalonEdit.Document; |
---|
| 28 | using ICSharpCode.AvalonEdit.Rendering; |
---|
| 29 | using ICSharpCode.AvalonEdit.Utils; |
---|
| 30 | |
---|
| 31 | namespace ICSharpCode.AvalonEdit.Editing |
---|
| 32 | { |
---|
| 33 | enum CaretMovementType |
---|
| 34 | { |
---|
| 35 | None, |
---|
| 36 | CharLeft, |
---|
| 37 | CharRight, |
---|
| 38 | Backspace, |
---|
| 39 | WordLeft, |
---|
| 40 | WordRight, |
---|
| 41 | LineUp, |
---|
| 42 | LineDown, |
---|
| 43 | PageUp, |
---|
| 44 | PageDown, |
---|
| 45 | LineStart, |
---|
| 46 | LineEnd, |
---|
| 47 | DocumentStart, |
---|
| 48 | DocumentEnd |
---|
| 49 | } |
---|
| 50 | |
---|
| 51 | static class CaretNavigationCommandHandler |
---|
| 52 | { |
---|
| 53 | /// <summary> |
---|
| 54 | /// Creates a new <see cref="TextAreaInputHandler"/> for the text area. |
---|
| 55 | /// </summary> |
---|
| 56 | public static TextAreaInputHandler Create(TextArea textArea) |
---|
| 57 | { |
---|
| 58 | TextAreaInputHandler handler = new TextAreaInputHandler(textArea); |
---|
| 59 | handler.CommandBindings.AddRange(CommandBindings); |
---|
| 60 | handler.InputBindings.AddRange(InputBindings); |
---|
| 61 | return handler; |
---|
| 62 | } |
---|
| 63 | |
---|
| 64 | static readonly List<CommandBinding> CommandBindings = new List<CommandBinding>(); |
---|
| 65 | static readonly List<InputBinding> InputBindings = new List<InputBinding>(); |
---|
| 66 | |
---|
| 67 | static void AddBinding(ICommand command, ModifierKeys modifiers, Key key, ExecutedRoutedEventHandler handler) |
---|
| 68 | { |
---|
| 69 | CommandBindings.Add(new CommandBinding(command, handler)); |
---|
| 70 | InputBindings.Add(TextAreaDefaultInputHandler.CreateFrozenKeyBinding(command, modifiers, key)); |
---|
| 71 | } |
---|
| 72 | |
---|
| 73 | static CaretNavigationCommandHandler() |
---|
| 74 | { |
---|
| 75 | const ModifierKeys None = ModifierKeys.None; |
---|
| 76 | const ModifierKeys Ctrl = ModifierKeys.Control; |
---|
| 77 | const ModifierKeys Shift = ModifierKeys.Shift; |
---|
| 78 | const ModifierKeys Alt = ModifierKeys.Alt; |
---|
| 79 | |
---|
| 80 | AddBinding(EditingCommands.MoveLeftByCharacter, None, Key.Left, OnMoveCaret(CaretMovementType.CharLeft)); |
---|
| 81 | AddBinding(EditingCommands.SelectLeftByCharacter, Shift, Key.Left, OnMoveCaretExtendSelection(CaretMovementType.CharLeft)); |
---|
| 82 | AddBinding(RectangleSelection.BoxSelectLeftByCharacter, Alt | Shift, Key.Left, OnMoveCaretBoxSelection(CaretMovementType.CharLeft)); |
---|
| 83 | AddBinding(EditingCommands.MoveRightByCharacter, None, Key.Right, OnMoveCaret(CaretMovementType.CharRight)); |
---|
| 84 | AddBinding(EditingCommands.SelectRightByCharacter, Shift, Key.Right, OnMoveCaretExtendSelection(CaretMovementType.CharRight)); |
---|
| 85 | AddBinding(RectangleSelection.BoxSelectRightByCharacter, Alt | Shift, Key.Right, OnMoveCaretBoxSelection(CaretMovementType.CharRight)); |
---|
| 86 | |
---|
| 87 | AddBinding(EditingCommands.MoveLeftByWord, Ctrl, Key.Left, OnMoveCaret(CaretMovementType.WordLeft)); |
---|
| 88 | AddBinding(EditingCommands.SelectLeftByWord, Ctrl | Shift, Key.Left, OnMoveCaretExtendSelection(CaretMovementType.WordLeft)); |
---|
| 89 | AddBinding(RectangleSelection.BoxSelectLeftByWord, Ctrl | Alt | Shift, Key.Left, OnMoveCaretBoxSelection(CaretMovementType.WordLeft)); |
---|
| 90 | AddBinding(EditingCommands.MoveRightByWord, Ctrl, Key.Right, OnMoveCaret(CaretMovementType.WordRight)); |
---|
| 91 | AddBinding(EditingCommands.SelectRightByWord, Ctrl | Shift, Key.Right, OnMoveCaretExtendSelection(CaretMovementType.WordRight)); |
---|
| 92 | AddBinding(RectangleSelection.BoxSelectRightByWord, Ctrl | Alt | Shift, Key.Right, OnMoveCaretBoxSelection(CaretMovementType.WordRight)); |
---|
| 93 | |
---|
| 94 | AddBinding(EditingCommands.MoveUpByLine, None, Key.Up, OnMoveCaret(CaretMovementType.LineUp)); |
---|
| 95 | AddBinding(EditingCommands.SelectUpByLine, Shift, Key.Up, OnMoveCaretExtendSelection(CaretMovementType.LineUp)); |
---|
| 96 | AddBinding(RectangleSelection.BoxSelectUpByLine, Alt | Shift, Key.Up, OnMoveCaretBoxSelection(CaretMovementType.LineUp)); |
---|
| 97 | AddBinding(EditingCommands.MoveDownByLine, None, Key.Down, OnMoveCaret(CaretMovementType.LineDown)); |
---|
| 98 | AddBinding(EditingCommands.SelectDownByLine, Shift, Key.Down, OnMoveCaretExtendSelection(CaretMovementType.LineDown)); |
---|
| 99 | AddBinding(RectangleSelection.BoxSelectDownByLine, Alt | Shift, Key.Down, OnMoveCaretBoxSelection(CaretMovementType.LineDown)); |
---|
| 100 | |
---|
| 101 | AddBinding(EditingCommands.MoveDownByPage, None, Key.PageDown, OnMoveCaret(CaretMovementType.PageDown)); |
---|
| 102 | AddBinding(EditingCommands.SelectDownByPage, Shift, Key.PageDown, OnMoveCaretExtendSelection(CaretMovementType.PageDown)); |
---|
| 103 | AddBinding(EditingCommands.MoveUpByPage, None, Key.PageUp, OnMoveCaret(CaretMovementType.PageUp)); |
---|
| 104 | AddBinding(EditingCommands.SelectUpByPage, Shift, Key.PageUp, OnMoveCaretExtendSelection(CaretMovementType.PageUp)); |
---|
| 105 | |
---|
| 106 | AddBinding(EditingCommands.MoveToLineStart, None, Key.Home, OnMoveCaret(CaretMovementType.LineStart)); |
---|
| 107 | AddBinding(EditingCommands.SelectToLineStart, Shift, Key.Home, OnMoveCaretExtendSelection(CaretMovementType.LineStart)); |
---|
| 108 | AddBinding(RectangleSelection.BoxSelectToLineStart, Alt | Shift, Key.Home, OnMoveCaretBoxSelection(CaretMovementType.LineStart)); |
---|
| 109 | AddBinding(EditingCommands.MoveToLineEnd, None, Key.End, OnMoveCaret(CaretMovementType.LineEnd)); |
---|
| 110 | AddBinding(EditingCommands.SelectToLineEnd, Shift, Key.End, OnMoveCaretExtendSelection(CaretMovementType.LineEnd)); |
---|
| 111 | AddBinding(RectangleSelection.BoxSelectToLineEnd, Alt | Shift, Key.End, OnMoveCaretBoxSelection(CaretMovementType.LineEnd)); |
---|
| 112 | |
---|
| 113 | AddBinding(EditingCommands.MoveToDocumentStart, Ctrl, Key.Home, OnMoveCaret(CaretMovementType.DocumentStart)); |
---|
| 114 | AddBinding(EditingCommands.SelectToDocumentStart, Ctrl | Shift, Key.Home, OnMoveCaretExtendSelection(CaretMovementType.DocumentStart)); |
---|
| 115 | AddBinding(EditingCommands.MoveToDocumentEnd, Ctrl, Key.End, OnMoveCaret(CaretMovementType.DocumentEnd)); |
---|
| 116 | AddBinding(EditingCommands.SelectToDocumentEnd, Ctrl | Shift, Key.End, OnMoveCaretExtendSelection(CaretMovementType.DocumentEnd)); |
---|
| 117 | |
---|
| 118 | CommandBindings.Add(new CommandBinding(ApplicationCommands.SelectAll, OnSelectAll)); |
---|
| 119 | |
---|
| 120 | TextAreaDefaultInputHandler.WorkaroundWPFMemoryLeak(InputBindings); |
---|
| 121 | } |
---|
| 122 | |
---|
| 123 | static void OnSelectAll(object target, ExecutedRoutedEventArgs args) |
---|
| 124 | { |
---|
| 125 | TextArea textArea = GetTextArea(target); |
---|
| 126 | if (textArea != null && textArea.Document != null) { |
---|
| 127 | args.Handled = true; |
---|
| 128 | textArea.Caret.Offset = textArea.Document.TextLength; |
---|
| 129 | textArea.Selection = SimpleSelection.Create(textArea, 0, textArea.Document.TextLength); |
---|
| 130 | } |
---|
| 131 | } |
---|
| 132 | |
---|
| 133 | static TextArea GetTextArea(object target) |
---|
| 134 | { |
---|
| 135 | return target as TextArea; |
---|
| 136 | } |
---|
| 137 | |
---|
| 138 | static ExecutedRoutedEventHandler OnMoveCaret(CaretMovementType direction) |
---|
| 139 | { |
---|
| 140 | return (target, args) => { |
---|
| 141 | TextArea textArea = GetTextArea(target); |
---|
| 142 | if (textArea != null && textArea.Document != null) { |
---|
| 143 | args.Handled = true; |
---|
| 144 | textArea.ClearSelection(); |
---|
| 145 | MoveCaret(textArea, direction); |
---|
| 146 | textArea.Caret.BringCaretToView(); |
---|
| 147 | } |
---|
| 148 | }; |
---|
| 149 | } |
---|
| 150 | |
---|
| 151 | static ExecutedRoutedEventHandler OnMoveCaretExtendSelection(CaretMovementType direction) |
---|
| 152 | { |
---|
| 153 | return (target, args) => { |
---|
| 154 | TextArea textArea = GetTextArea(target); |
---|
| 155 | if (textArea != null && textArea.Document != null) { |
---|
| 156 | args.Handled = true; |
---|
| 157 | TextViewPosition oldPosition = textArea.Caret.Position; |
---|
| 158 | MoveCaret(textArea, direction); |
---|
| 159 | textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position); |
---|
| 160 | textArea.Caret.BringCaretToView(); |
---|
| 161 | } |
---|
| 162 | }; |
---|
| 163 | } |
---|
| 164 | |
---|
| 165 | static ExecutedRoutedEventHandler OnMoveCaretBoxSelection(CaretMovementType direction) |
---|
| 166 | { |
---|
| 167 | return (target, args) => { |
---|
| 168 | TextArea textArea = GetTextArea(target); |
---|
| 169 | if (textArea != null && textArea.Document != null) { |
---|
| 170 | args.Handled = true; |
---|
| 171 | // First, convert the selection into a rectangle selection |
---|
| 172 | // (this is required so that virtual space gets enabled for the caret movement) |
---|
| 173 | if (textArea.Options.EnableRectangularSelection && !(textArea.Selection is RectangleSelection)) { |
---|
| 174 | if (textArea.Selection.IsEmpty) { |
---|
| 175 | textArea.Selection = new RectangleSelection(textArea, textArea.Caret.Position, textArea.Caret.Position); |
---|
| 176 | } else { |
---|
| 177 | // Convert normal selection to rectangle selection |
---|
| 178 | textArea.Selection = new RectangleSelection(textArea, textArea.Selection.StartPosition, textArea.Caret.Position); |
---|
| 179 | } |
---|
| 180 | } |
---|
| 181 | // Now move the caret and extend the selection |
---|
| 182 | TextViewPosition oldPosition = textArea.Caret.Position; |
---|
| 183 | MoveCaret(textArea, direction); |
---|
| 184 | textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position); |
---|
| 185 | textArea.Caret.BringCaretToView(); |
---|
| 186 | } |
---|
| 187 | }; |
---|
| 188 | } |
---|
| 189 | |
---|
| 190 | #region Caret movement |
---|
| 191 | internal static void MoveCaret(TextArea textArea, CaretMovementType direction) |
---|
| 192 | { |
---|
| 193 | double desiredXPos = textArea.Caret.DesiredXPos; |
---|
| 194 | textArea.Caret.Position = GetNewCaretPosition(textArea.TextView, textArea.Caret.Position, direction, textArea.Selection.EnableVirtualSpace, ref desiredXPos); |
---|
| 195 | textArea.Caret.DesiredXPos = desiredXPos; |
---|
| 196 | } |
---|
| 197 | |
---|
| 198 | internal static TextViewPosition GetNewCaretPosition(TextView textView, TextViewPosition caretPosition, CaretMovementType direction, bool enableVirtualSpace, ref double desiredXPos) |
---|
| 199 | { |
---|
| 200 | switch (direction) { |
---|
| 201 | case CaretMovementType.None: |
---|
| 202 | return caretPosition; |
---|
| 203 | case CaretMovementType.DocumentStart: |
---|
| 204 | desiredXPos = double.NaN; |
---|
| 205 | return new TextViewPosition(0, 0); |
---|
| 206 | case CaretMovementType.DocumentEnd: |
---|
| 207 | desiredXPos = double.NaN; |
---|
| 208 | return new TextViewPosition(textView.Document.GetLocation(textView.Document.TextLength)); |
---|
| 209 | } |
---|
| 210 | DocumentLine caretLine = textView.Document.GetLineByNumber(caretPosition.Line); |
---|
| 211 | VisualLine visualLine = textView.GetOrConstructVisualLine(caretLine); |
---|
| 212 | TextLine textLine = visualLine.GetTextLine(caretPosition.VisualColumn, caretPosition.IsAtEndOfLine); |
---|
| 213 | switch (direction) { |
---|
| 214 | case CaretMovementType.CharLeft: |
---|
| 215 | desiredXPos = double.NaN; |
---|
| 216 | return GetPrevCaretPosition(textView, caretPosition, visualLine, CaretPositioningMode.Normal, enableVirtualSpace); |
---|
| 217 | case CaretMovementType.Backspace: |
---|
| 218 | desiredXPos = double.NaN; |
---|
| 219 | return GetPrevCaretPosition(textView, caretPosition, visualLine, CaretPositioningMode.EveryCodepoint, enableVirtualSpace); |
---|
| 220 | case CaretMovementType.CharRight: |
---|
| 221 | desiredXPos = double.NaN; |
---|
| 222 | return GetNextCaretPosition(textView, caretPosition, visualLine, CaretPositioningMode.Normal, enableVirtualSpace); |
---|
| 223 | case CaretMovementType.WordLeft: |
---|
| 224 | desiredXPos = double.NaN; |
---|
| 225 | return GetPrevCaretPosition(textView, caretPosition, visualLine, CaretPositioningMode.WordStart, enableVirtualSpace); |
---|
| 226 | case CaretMovementType.WordRight: |
---|
| 227 | desiredXPos = double.NaN; |
---|
| 228 | return GetNextCaretPosition(textView, caretPosition, visualLine, CaretPositioningMode.WordStart, enableVirtualSpace); |
---|
| 229 | case CaretMovementType.LineUp: |
---|
| 230 | case CaretMovementType.LineDown: |
---|
| 231 | case CaretMovementType.PageUp: |
---|
| 232 | case CaretMovementType.PageDown: |
---|
| 233 | return GetUpDownCaretPosition(textView, caretPosition, direction, visualLine, textLine, enableVirtualSpace, ref desiredXPos); |
---|
| 234 | case CaretMovementType.LineStart: |
---|
| 235 | desiredXPos = double.NaN; |
---|
| 236 | return GetStartOfLineCaretPosition(caretPosition.VisualColumn, visualLine, textLine, enableVirtualSpace); |
---|
| 237 | case CaretMovementType.LineEnd: |
---|
| 238 | desiredXPos = double.NaN; |
---|
| 239 | return GetEndOfLineCaretPosition(visualLine, textLine); |
---|
| 240 | default: |
---|
| 241 | throw new NotSupportedException(direction.ToString()); |
---|
| 242 | } |
---|
| 243 | } |
---|
| 244 | #endregion |
---|
| 245 | |
---|
| 246 | #region Home/End |
---|
| 247 | static TextViewPosition GetStartOfLineCaretPosition(int oldVC, VisualLine visualLine, TextLine textLine, bool enableVirtualSpace) |
---|
| 248 | { |
---|
| 249 | int newVC = visualLine.GetTextLineVisualStartColumn(textLine); |
---|
| 250 | if (newVC == 0) |
---|
| 251 | newVC = visualLine.GetNextCaretPosition(newVC - 1, LogicalDirection.Forward, CaretPositioningMode.WordStart, enableVirtualSpace); |
---|
| 252 | if (newVC < 0) |
---|
| 253 | throw ThrowUtil.NoValidCaretPosition(); |
---|
| 254 | // when the caret is already at the start of the text, jump to start before whitespace |
---|
| 255 | if (newVC == oldVC) |
---|
| 256 | newVC = 0; |
---|
| 257 | return visualLine.GetTextViewPosition(newVC); |
---|
| 258 | } |
---|
| 259 | |
---|
| 260 | static TextViewPosition GetEndOfLineCaretPosition(VisualLine visualLine, TextLine textLine) |
---|
| 261 | { |
---|
| 262 | int newVC = visualLine.GetTextLineVisualStartColumn(textLine) + textLine.Length - textLine.TrailingWhitespaceLength; |
---|
| 263 | TextViewPosition pos = visualLine.GetTextViewPosition(newVC); |
---|
| 264 | pos.IsAtEndOfLine = true; |
---|
| 265 | return pos; |
---|
| 266 | } |
---|
| 267 | #endregion |
---|
| 268 | |
---|
| 269 | #region By-character / By-word movement |
---|
| 270 | static TextViewPosition GetNextCaretPosition(TextView textView, TextViewPosition caretPosition, VisualLine visualLine, CaretPositioningMode mode, bool enableVirtualSpace) |
---|
| 271 | { |
---|
| 272 | int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Forward, mode, enableVirtualSpace); |
---|
| 273 | if (pos >= 0) { |
---|
| 274 | return visualLine.GetTextViewPosition(pos); |
---|
| 275 | } else { |
---|
| 276 | // move to start of next line |
---|
| 277 | DocumentLine nextDocumentLine = visualLine.LastDocumentLine.NextLine; |
---|
| 278 | if (nextDocumentLine != null) { |
---|
| 279 | VisualLine nextLine = textView.GetOrConstructVisualLine(nextDocumentLine); |
---|
| 280 | pos = nextLine.GetNextCaretPosition(-1, LogicalDirection.Forward, mode, enableVirtualSpace); |
---|
| 281 | if (pos < 0) |
---|
| 282 | throw ThrowUtil.NoValidCaretPosition(); |
---|
| 283 | return nextLine.GetTextViewPosition(pos); |
---|
| 284 | } else { |
---|
| 285 | // at end of document |
---|
| 286 | Debug.Assert(visualLine.LastDocumentLine.Offset + visualLine.LastDocumentLine.TotalLength == textView.Document.TextLength); |
---|
| 287 | return new TextViewPosition(textView.Document.GetLocation(textView.Document.TextLength)); |
---|
| 288 | } |
---|
| 289 | } |
---|
| 290 | } |
---|
| 291 | |
---|
| 292 | static TextViewPosition GetPrevCaretPosition(TextView textView, TextViewPosition caretPosition, VisualLine visualLine, CaretPositioningMode mode, bool enableVirtualSpace) |
---|
| 293 | { |
---|
| 294 | int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Backward, mode, enableVirtualSpace); |
---|
| 295 | if (pos >= 0) { |
---|
| 296 | return visualLine.GetTextViewPosition(pos); |
---|
| 297 | } else { |
---|
| 298 | // move to end of previous line |
---|
| 299 | DocumentLine previousDocumentLine = visualLine.FirstDocumentLine.PreviousLine; |
---|
| 300 | if (previousDocumentLine != null) { |
---|
| 301 | VisualLine previousLine = textView.GetOrConstructVisualLine(previousDocumentLine); |
---|
| 302 | pos = previousLine.GetNextCaretPosition(previousLine.VisualLength + 1, LogicalDirection.Backward, mode, enableVirtualSpace); |
---|
| 303 | if (pos < 0) |
---|
| 304 | throw ThrowUtil.NoValidCaretPosition(); |
---|
| 305 | return previousLine.GetTextViewPosition(pos); |
---|
| 306 | } else { |
---|
| 307 | // at start of document |
---|
| 308 | Debug.Assert(visualLine.FirstDocumentLine.Offset == 0); |
---|
| 309 | return new TextViewPosition(0, 0); |
---|
| 310 | } |
---|
| 311 | } |
---|
| 312 | } |
---|
| 313 | #endregion |
---|
| 314 | |
---|
| 315 | #region Line+Page up/down |
---|
| 316 | static TextViewPosition GetUpDownCaretPosition(TextView textView, TextViewPosition caretPosition, CaretMovementType direction, VisualLine visualLine, TextLine textLine, bool enableVirtualSpace, ref double xPos) |
---|
| 317 | { |
---|
| 318 | // moving up/down happens using the desired visual X position |
---|
| 319 | if (double.IsNaN(xPos)) |
---|
| 320 | xPos = visualLine.GetTextLineVisualXPosition(textLine, caretPosition.VisualColumn); |
---|
| 321 | // now find the TextLine+VisualLine where the caret will end up in |
---|
| 322 | VisualLine targetVisualLine = visualLine; |
---|
| 323 | TextLine targetLine; |
---|
| 324 | int textLineIndex = visualLine.TextLines.IndexOf(textLine); |
---|
| 325 | switch (direction) { |
---|
| 326 | case CaretMovementType.LineUp: |
---|
| 327 | { |
---|
| 328 | // Move up: move to the previous TextLine in the same visual line |
---|
| 329 | // or move to the last TextLine of the previous visual line |
---|
| 330 | int prevLineNumber = visualLine.FirstDocumentLine.LineNumber - 1; |
---|
| 331 | if (textLineIndex > 0) { |
---|
| 332 | targetLine = visualLine.TextLines[textLineIndex - 1]; |
---|
| 333 | } else if (prevLineNumber >= 1) { |
---|
| 334 | DocumentLine prevLine = textView.Document.GetLineByNumber(prevLineNumber); |
---|
| 335 | targetVisualLine = textView.GetOrConstructVisualLine(prevLine); |
---|
| 336 | targetLine = targetVisualLine.TextLines[targetVisualLine.TextLines.Count - 1]; |
---|
| 337 | } else { |
---|
| 338 | targetLine = null; |
---|
| 339 | } |
---|
| 340 | break; |
---|
| 341 | } |
---|
| 342 | case CaretMovementType.LineDown: |
---|
| 343 | { |
---|
| 344 | // Move down: move to the next TextLine in the same visual line |
---|
| 345 | // or move to the first TextLine of the next visual line |
---|
| 346 | int nextLineNumber = visualLine.LastDocumentLine.LineNumber + 1; |
---|
| 347 | if (textLineIndex < visualLine.TextLines.Count - 1) { |
---|
| 348 | targetLine = visualLine.TextLines[textLineIndex + 1]; |
---|
| 349 | } else if (nextLineNumber <= textView.Document.LineCount) { |
---|
| 350 | DocumentLine nextLine = textView.Document.GetLineByNumber(nextLineNumber); |
---|
| 351 | targetVisualLine = textView.GetOrConstructVisualLine(nextLine); |
---|
| 352 | targetLine = targetVisualLine.TextLines[0]; |
---|
| 353 | } else { |
---|
| 354 | targetLine = null; |
---|
| 355 | } |
---|
| 356 | break; |
---|
| 357 | } |
---|
| 358 | case CaretMovementType.PageUp: |
---|
| 359 | case CaretMovementType.PageDown: |
---|
| 360 | { |
---|
| 361 | // Page up/down: find the target line using its visual position |
---|
| 362 | double yPos = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.LineMiddle); |
---|
| 363 | if (direction == CaretMovementType.PageUp) |
---|
| 364 | yPos -= textView.RenderSize.Height; |
---|
| 365 | else |
---|
| 366 | yPos += textView.RenderSize.Height; |
---|
| 367 | DocumentLine newLine = textView.GetDocumentLineByVisualTop(yPos); |
---|
| 368 | targetVisualLine = textView.GetOrConstructVisualLine(newLine); |
---|
| 369 | targetLine = targetVisualLine.GetTextLineByVisualYPosition(yPos); |
---|
| 370 | break; |
---|
| 371 | } |
---|
| 372 | default: |
---|
| 373 | throw new NotSupportedException(direction.ToString()); |
---|
| 374 | } |
---|
| 375 | if (targetLine != null) { |
---|
| 376 | double yPos = targetVisualLine.GetTextLineVisualYPosition(targetLine, VisualYPosition.LineMiddle); |
---|
| 377 | int newVisualColumn = targetVisualLine.GetVisualColumn(new Point(xPos, yPos), enableVirtualSpace); |
---|
| 378 | |
---|
| 379 | // prevent wrapping to the next line; TODO: could 'IsAtEnd' help here? |
---|
| 380 | int targetLineStartCol = targetVisualLine.GetTextLineVisualStartColumn(targetLine); |
---|
| 381 | if (newVisualColumn >= targetLineStartCol + targetLine.Length) { |
---|
| 382 | if (newVisualColumn <= targetVisualLine.VisualLength) |
---|
| 383 | newVisualColumn = targetLineStartCol + targetLine.Length - 1; |
---|
| 384 | } |
---|
| 385 | return targetVisualLine.GetTextViewPosition(newVisualColumn); |
---|
| 386 | } else { |
---|
| 387 | return caretPosition; |
---|
| 388 | } |
---|
| 389 | } |
---|
| 390 | #endregion |
---|
| 391 | } |
---|
| 392 | } |
---|