706 lines
32 KiB
C#
706 lines
32 KiB
C#
// 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<KeyGesture, CaretNavigationCommand> _navigationCommands = new Dictionary<KeyGesture, CaretNavigationCommand>();
|
|
|
|
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
|
|
}
|
|
} |