diff options
Diffstat (limited to 'Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretNavigationCommandHandler.cs')
| -rw-r--r-- | Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretNavigationCommandHandler.cs | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretNavigationCommandHandler.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretNavigationCommandHandler.cs new file mode 100644 index 000000000..d1b812b66 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretNavigationCommandHandler.cs @@ -0,0 +1,375 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Windows; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media.TextFormatting; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Rendering; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Editing +{ + static class CaretNavigationCommandHandler + { + /// <summary> + /// Creates a new <see cref="TextAreaInputHandler"/> for the text area. + /// </summary> + public static TextAreaInputHandler Create(TextArea textArea) + { + TextAreaInputHandler handler = new TextAreaInputHandler(textArea); + handler.CommandBindings.AddRange(CommandBindings); + handler.InputBindings.AddRange(InputBindings); + return handler; + } + + static readonly List<CommandBinding> CommandBindings = new List<CommandBinding>(); + static readonly List<InputBinding> InputBindings = new List<InputBinding>(); + + static void AddBinding(ICommand command, ModifierKeys modifiers, Key key, ExecutedRoutedEventHandler handler) + { + CommandBindings.Add(new CommandBinding(command, handler)); + InputBindings.Add(TextAreaDefaultInputHandler.CreateFrozenKeyBinding(command, modifiers, key)); + } + + static CaretNavigationCommandHandler() + { + const ModifierKeys None = ModifierKeys.None; + const ModifierKeys Ctrl = ModifierKeys.Control; + const ModifierKeys Shift = ModifierKeys.Shift; + const ModifierKeys Alt = ModifierKeys.Alt; + + AddBinding(EditingCommands.MoveLeftByCharacter, None, Key.Left, OnMoveCaret(CaretMovementType.CharLeft)); + AddBinding(EditingCommands.SelectLeftByCharacter, Shift, Key.Left, OnMoveCaretExtendSelection(CaretMovementType.CharLeft)); + AddBinding(RectangleSelection.BoxSelectLeftByCharacter, Alt | Shift, Key.Left, OnMoveCaretBoxSelection(CaretMovementType.CharLeft)); + AddBinding(EditingCommands.MoveRightByCharacter, None, Key.Right, OnMoveCaret(CaretMovementType.CharRight)); + AddBinding(EditingCommands.SelectRightByCharacter, Shift, Key.Right, OnMoveCaretExtendSelection(CaretMovementType.CharRight)); + AddBinding(RectangleSelection.BoxSelectRightByCharacter, Alt | Shift, Key.Right, OnMoveCaretBoxSelection(CaretMovementType.CharRight)); + + AddBinding(EditingCommands.MoveLeftByWord, Ctrl, Key.Left, OnMoveCaret(CaretMovementType.WordLeft)); + AddBinding(EditingCommands.SelectLeftByWord, Ctrl | Shift, Key.Left, OnMoveCaretExtendSelection(CaretMovementType.WordLeft)); + AddBinding(RectangleSelection.BoxSelectLeftByWord, Ctrl | Alt | Shift, Key.Left, OnMoveCaretBoxSelection(CaretMovementType.WordLeft)); + AddBinding(EditingCommands.MoveRightByWord, Ctrl, Key.Right, OnMoveCaret(CaretMovementType.WordRight)); + AddBinding(EditingCommands.SelectRightByWord, Ctrl | Shift, Key.Right, OnMoveCaretExtendSelection(CaretMovementType.WordRight)); + AddBinding(RectangleSelection.BoxSelectRightByWord, Ctrl | Alt | Shift, Key.Right, OnMoveCaretBoxSelection(CaretMovementType.WordRight)); + + AddBinding(EditingCommands.MoveUpByLine, None, Key.Up, OnMoveCaret(CaretMovementType.LineUp)); + AddBinding(EditingCommands.SelectUpByLine, Shift, Key.Up, OnMoveCaretExtendSelection(CaretMovementType.LineUp)); + AddBinding(RectangleSelection.BoxSelectUpByLine, Alt | Shift, Key.Up, OnMoveCaretBoxSelection(CaretMovementType.LineUp)); + AddBinding(EditingCommands.MoveDownByLine, None, Key.Down, OnMoveCaret(CaretMovementType.LineDown)); + AddBinding(EditingCommands.SelectDownByLine, Shift, Key.Down, OnMoveCaretExtendSelection(CaretMovementType.LineDown)); + AddBinding(RectangleSelection.BoxSelectDownByLine, Alt | Shift, Key.Down, OnMoveCaretBoxSelection(CaretMovementType.LineDown)); + + AddBinding(EditingCommands.MoveDownByPage, None, Key.PageDown, OnMoveCaret(CaretMovementType.PageDown)); + AddBinding(EditingCommands.SelectDownByPage, Shift, Key.PageDown, OnMoveCaretExtendSelection(CaretMovementType.PageDown)); + AddBinding(EditingCommands.MoveUpByPage, None, Key.PageUp, OnMoveCaret(CaretMovementType.PageUp)); + AddBinding(EditingCommands.SelectUpByPage, Shift, Key.PageUp, OnMoveCaretExtendSelection(CaretMovementType.PageUp)); + + AddBinding(EditingCommands.MoveToLineStart, None, Key.Home, OnMoveCaret(CaretMovementType.LineStart)); + AddBinding(EditingCommands.SelectToLineStart, Shift, Key.Home, OnMoveCaretExtendSelection(CaretMovementType.LineStart)); + AddBinding(RectangleSelection.BoxSelectToLineStart, Alt | Shift, Key.Home, OnMoveCaretBoxSelection(CaretMovementType.LineStart)); + AddBinding(EditingCommands.MoveToLineEnd, None, Key.End, OnMoveCaret(CaretMovementType.LineEnd)); + AddBinding(EditingCommands.SelectToLineEnd, Shift, Key.End, OnMoveCaretExtendSelection(CaretMovementType.LineEnd)); + AddBinding(RectangleSelection.BoxSelectToLineEnd, Alt | Shift, Key.End, OnMoveCaretBoxSelection(CaretMovementType.LineEnd)); + + AddBinding(EditingCommands.MoveToDocumentStart, Ctrl, Key.Home, OnMoveCaret(CaretMovementType.DocumentStart)); + AddBinding(EditingCommands.SelectToDocumentStart, Ctrl | Shift, Key.Home, OnMoveCaretExtendSelection(CaretMovementType.DocumentStart)); + AddBinding(EditingCommands.MoveToDocumentEnd, Ctrl, Key.End, OnMoveCaret(CaretMovementType.DocumentEnd)); + AddBinding(EditingCommands.SelectToDocumentEnd, Ctrl | Shift, Key.End, OnMoveCaretExtendSelection(CaretMovementType.DocumentEnd)); + + CommandBindings.Add(new CommandBinding(ApplicationCommands.SelectAll, OnSelectAll)); + + TextAreaDefaultInputHandler.WorkaroundWPFMemoryLeak(InputBindings); + } + + static void OnSelectAll(object target, ExecutedRoutedEventArgs args) + { + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.Document != null) { + args.Handled = true; + textArea.Caret.Offset = textArea.Document.TextLength; + textArea.Selection = SimpleSelection.Create(textArea, 0, textArea.Document.TextLength); + } + } + + static TextArea GetTextArea(object target) + { + return target as TextArea; + } + + enum CaretMovementType + { + CharLeft, + CharRight, + WordLeft, + WordRight, + LineUp, + LineDown, + PageUp, + PageDown, + LineStart, + LineEnd, + DocumentStart, + DocumentEnd + } + + static ExecutedRoutedEventHandler OnMoveCaret(CaretMovementType direction) + { + return (target, args) => { + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.Document != null) { + args.Handled = true; + textArea.ClearSelection(); + MoveCaret(textArea, direction); + textArea.Caret.BringCaretToView(); + } + }; + } + + static ExecutedRoutedEventHandler OnMoveCaretExtendSelection(CaretMovementType direction) + { + return (target, args) => { + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.Document != null) { + args.Handled = true; + TextViewPosition oldPosition = textArea.Caret.Position; + MoveCaret(textArea, direction); + textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position); + if (!textArea.Document.IsInUpdate) // if we're inside a larger update (e.g. called by EditingCommandHandler.OnDelete()), avoid calculating the caret rectangle now + textArea.Caret.BringCaretToView(); + } + }; + } + + static ExecutedRoutedEventHandler OnMoveCaretBoxSelection(CaretMovementType direction) + { + return (target, args) => { + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.Document != null) { + args.Handled = true; + // 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)) { + if (textArea.Selection.IsEmpty) { + textArea.Selection = new RectangleSelection(textArea, textArea.Caret.Position, textArea.Caret.Position); + } else { + // Convert normal selection to rectangle selection + textArea.Selection = new RectangleSelection(textArea, textArea.Selection.StartPosition, textArea.Caret.Position); + } + } + // Now move the caret and extend the selection + TextViewPosition oldPosition = textArea.Caret.Position; + MoveCaret(textArea, direction); + textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position); + textArea.Caret.BringCaretToView(); + } + }; + } + + #region Caret movement + static void MoveCaret(TextArea textArea, CaretMovementType direction) + { + DocumentLine caretLine = textArea.Document.GetLineByNumber(textArea.Caret.Line); + VisualLine visualLine = textArea.TextView.GetOrConstructVisualLine(caretLine); + TextViewPosition caretPosition = textArea.Caret.Position; + TextLine textLine = visualLine.GetTextLine(caretPosition.VisualColumn); + switch (direction) { + case CaretMovementType.CharLeft: + MoveCaretLeft(textArea, caretPosition, visualLine, CaretPositioningMode.Normal); + break; + case CaretMovementType.CharRight: + MoveCaretRight(textArea, caretPosition, visualLine, CaretPositioningMode.Normal); + break; + case CaretMovementType.WordLeft: + MoveCaretLeft(textArea, caretPosition, visualLine, CaretPositioningMode.WordStart); + break; + case CaretMovementType.WordRight: + MoveCaretRight(textArea, caretPosition, visualLine, CaretPositioningMode.WordStart); + break; + case CaretMovementType.LineUp: + case CaretMovementType.LineDown: + case CaretMovementType.PageUp: + case CaretMovementType.PageDown: + MoveCaretUpDown(textArea, direction, visualLine, textLine, caretPosition.VisualColumn); + break; + case CaretMovementType.DocumentStart: + SetCaretPosition(textArea, 0, 0); + break; + case CaretMovementType.DocumentEnd: + SetCaretPosition(textArea, -1, textArea.Document.TextLength); + break; + case CaretMovementType.LineStart: + MoveCaretToStartOfLine(textArea, visualLine); + break; + case CaretMovementType.LineEnd: + MoveCaretToEndOfLine(textArea, visualLine); + break; + default: + throw new NotSupportedException(direction.ToString()); + } + } + #endregion + + #region Home/End + static void MoveCaretToStartOfLine(TextArea textArea, VisualLine visualLine) + { + int newVC = visualLine.GetNextCaretPosition(-1, LogicalDirection.Forward, CaretPositioningMode.WordStart, textArea.Selection.EnableVirtualSpace); + if (newVC < 0) + throw ThrowUtil.NoValidCaretPosition(); + // when the caret is already at the start of the text, jump to start before whitespace + if (newVC == textArea.Caret.VisualColumn) + newVC = 0; + int offset = visualLine.FirstDocumentLine.Offset + visualLine.GetRelativeOffset(newVC); + SetCaretPosition(textArea, newVC, offset); + } + + static void MoveCaretToEndOfLine(TextArea textArea, VisualLine visualLine) + { + int newVC = visualLine.VisualLength; + int offset = visualLine.FirstDocumentLine.Offset + visualLine.GetRelativeOffset(newVC); + SetCaretPosition(textArea, newVC, offset); + } + #endregion + + #region By-character / By-word movement + static void MoveCaretRight(TextArea textArea, TextViewPosition caretPosition, VisualLine visualLine, CaretPositioningMode mode) + { + int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Forward, mode, textArea.Selection.EnableVirtualSpace); + if (pos >= 0) { + SetCaretPosition(textArea, pos, visualLine.GetRelativeOffset(pos) + visualLine.FirstDocumentLine.Offset); + } else { + // move to start of next line + DocumentLine nextDocumentLine = visualLine.LastDocumentLine.NextLine; + if (nextDocumentLine != null) { + VisualLine nextLine = textArea.TextView.GetOrConstructVisualLine(nextDocumentLine); + pos = nextLine.GetNextCaretPosition(-1, LogicalDirection.Forward, mode, textArea.Selection.EnableVirtualSpace); + if (pos < 0) + throw ThrowUtil.NoValidCaretPosition(); + SetCaretPosition(textArea, pos, nextLine.GetRelativeOffset(pos) + nextLine.FirstDocumentLine.Offset); + } else { + // at end of document + Debug.Assert(visualLine.LastDocumentLine.Offset + visualLine.LastDocumentLine.TotalLength == textArea.Document.TextLength); + SetCaretPosition(textArea, -1, textArea.Document.TextLength); + } + } + } + + static void MoveCaretLeft(TextArea textArea, TextViewPosition caretPosition, VisualLine visualLine, CaretPositioningMode mode) + { + int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Backward, mode, textArea.Selection.EnableVirtualSpace); + if (pos >= 0) { + SetCaretPosition(textArea, pos, visualLine.GetRelativeOffset(pos) + visualLine.FirstDocumentLine.Offset); + } else { + // move to end of previous line + DocumentLine previousDocumentLine = visualLine.FirstDocumentLine.PreviousLine; + if (previousDocumentLine != null) { + VisualLine previousLine = textArea.TextView.GetOrConstructVisualLine(previousDocumentLine); + pos = previousLine.GetNextCaretPosition(previousLine.VisualLength + 1, LogicalDirection.Backward, mode, textArea.Selection.EnableVirtualSpace); + if (pos < 0) + throw ThrowUtil.NoValidCaretPosition(); + SetCaretPosition(textArea, pos, previousLine.GetRelativeOffset(pos) + previousLine.FirstDocumentLine.Offset); + } else { + // at start of document + Debug.Assert(visualLine.FirstDocumentLine.Offset == 0); + SetCaretPosition(textArea, 0, 0); + } + } + } + #endregion + + #region Line+Page up/down + static void MoveCaretUpDown(TextArea textArea, CaretMovementType direction, VisualLine visualLine, TextLine textLine, int caretVisualColumn) + { + // moving up/down happens using the desired visual X position + double xPos = textArea.Caret.DesiredXPos; + if (double.IsNaN(xPos)) + xPos = visualLine.GetTextLineVisualXPosition(textLine, caretVisualColumn); + // now find the TextLine+VisualLine where the caret will end up in + VisualLine targetVisualLine = visualLine; + TextLine targetLine; + int 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 + int prevLineNumber = visualLine.FirstDocumentLine.LineNumber - 1; + if (textLineIndex > 0) { + targetLine = visualLine.TextLines[textLineIndex - 1]; + } else if (prevLineNumber >= 1) { + DocumentLine prevLine = textArea.Document.GetLineByNumber(prevLineNumber); + targetVisualLine = textArea.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 + int nextLineNumber = visualLine.LastDocumentLine.LineNumber + 1; + if (textLineIndex < visualLine.TextLines.Count - 1) { + targetLine = visualLine.TextLines[textLineIndex + 1]; + } else if (nextLineNumber <= textArea.Document.LineCount) { + DocumentLine nextLine = textArea.Document.GetLineByNumber(nextLineNumber); + targetVisualLine = textArea.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 + double yPos = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.LineMiddle); + if (direction == CaretMovementType.PageUp) + yPos -= textArea.TextView.RenderSize.Height; + else + yPos += textArea.TextView.RenderSize.Height; + DocumentLine newLine = textArea.TextView.GetDocumentLineByVisualTop(yPos); + targetVisualLine = textArea.TextView.GetOrConstructVisualLine(newLine); + targetLine = targetVisualLine.GetTextLineByVisualYPosition(yPos); + break; + } + default: + throw new NotSupportedException(direction.ToString()); + } + if (targetLine != null) { + double yPos = targetVisualLine.GetTextLineVisualYPosition(targetLine, VisualYPosition.LineMiddle); + int newVisualColumn = targetVisualLine.GetVisualColumn(new Point(xPos, yPos), textArea.Selection.EnableVirtualSpace); + SetCaretPosition(textArea, targetVisualLine, targetLine, newVisualColumn, false); + textArea.Caret.DesiredXPos = xPos; + } + } + #endregion + + #region SetCaretPosition + static void SetCaretPosition(TextArea textArea, VisualLine targetVisualLine, TextLine targetLine, + int newVisualColumn, bool allowWrapToNextLine) + { + int targetLineStartCol = targetVisualLine.GetTextLineVisualStartColumn(targetLine); + if (!allowWrapToNextLine && newVisualColumn >= targetLineStartCol + targetLine.Length) { + if (newVisualColumn <= targetVisualLine.VisualLength) + newVisualColumn = targetLineStartCol + targetLine.Length - 1; + } + int newOffset = targetVisualLine.GetRelativeOffset(newVisualColumn) + targetVisualLine.FirstDocumentLine.Offset; + SetCaretPosition(textArea, newVisualColumn, newOffset); + } + + static void SetCaretPosition(TextArea textArea, int newVisualColumn, int newOffset) + { + textArea.Caret.Position = new TextViewPosition(textArea.Document.GetLocation(newOffset), newVisualColumn); + textArea.Caret.DesiredXPos = double.NaN; + } + #endregion + } +} |
