@ -15,6 +15,24 @@ using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.Editing
namespace ICSharpCode.AvalonEdit.Editing
{
{
enum CaretMovementType
{
None ,
CharLeft ,
CharRight ,
Backspace ,
WordLeft ,
WordRight ,
LineUp ,
LineDown ,
PageUp ,
PageDown ,
LineStart ,
LineEnd ,
DocumentStart ,
DocumentEnd
}
static class CaretNavigationCommandHandler
static class CaretNavigationCommandHandler
{
{
/// <summary>
/// <summary>
@ -102,22 +120,6 @@ namespace ICSharpCode.AvalonEdit.Editing
return target as TextArea ;
return target as TextArea ;
}
}
enum CaretMovementType
{
CharLeft ,
CharRight ,
WordLeft ,
WordRight ,
LineUp ,
LineDown ,
PageUp ,
PageDown ,
LineStart ,
LineEnd ,
DocumentStart ,
DocumentEnd
}
static ExecutedRoutedEventHandler OnMoveCaret ( CaretMovementType direction )
static ExecutedRoutedEventHandler OnMoveCaret ( CaretMovementType direction )
{
{
return ( target , args ) = > {
return ( target , args ) = > {
@ -140,8 +142,7 @@ namespace ICSharpCode.AvalonEdit.Editing
TextViewPosition oldPosition = textArea . Caret . Position ;
TextViewPosition oldPosition = textArea . Caret . Position ;
MoveCaret ( textArea , direction ) ;
MoveCaret ( textArea , direction ) ;
textArea . Selection = textArea . Selection . StartSelectionOrSetEndpoint ( oldPosition , textArea . Caret . Position ) ;
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 ( ) ;
textArea . Caret . BringCaretToView ( ) ;
}
}
} ;
} ;
}
}
@ -172,43 +173,55 @@ namespace ICSharpCode.AvalonEdit.Editing
}
}
#region Caret movement
#region Caret movement
static void MoveCaret ( TextArea textArea , CaretMovementType direction )
internal static void MoveCaret ( TextArea textArea , CaretMovementType direction )
{
{
DocumentLine caretLine = textArea . Document . GetLineByNumber ( textArea . Caret . Line ) ;
double desiredXPos = textArea . Caret . DesiredXPos ;
VisualLine visualLine = textArea . TextView . GetOrConstructVisualLine ( caretLine ) ;
textArea . Caret . Position = GetNewCaretPosition ( textArea . TextView , textArea . Caret . Position , direction , textArea . Selection . EnableVirtualSpace , ref desiredXPos ) ;
TextViewPosition caretPosition = textArea . Caret . Position ;
textArea . Caret . DesiredXPos = desiredXPos ;
}
internal static TextViewPosition GetNewCaretPosition ( TextView textView , TextViewPosition caretPosition , CaretMovementType direction , bool enableVirtualSpace , ref double desiredXPos )
{
switch ( direction ) {
case CaretMovementType . None :
return caretPosition ;
case CaretMovementType . DocumentStart :
desiredXPos = double . NaN ;
return new TextViewPosition ( 0 , 0 ) ;
case CaretMovementType . DocumentEnd :
desiredXPos = double . NaN ;
return new TextViewPosition ( textView . Document . GetLocation ( textView . Document . TextLength ) ) ;
}
DocumentLine caretLine = textView . Document . GetLineByNumber ( caretPosition . Line ) ;
VisualLine visualLine = textView . GetOrConstructVisualLine ( caretLine ) ;
TextLine textLine = visualLine . GetTextLine ( caretPosition . VisualColumn , caretPosition . IsAtEndOfLine ) ;
TextLine textLine = visualLine . GetTextLine ( caretPosition . VisualColumn , caretPosition . IsAtEndOfLine ) ;
switch ( direction ) {
switch ( direction ) {
case CaretMovementType . CharLeft :
case CaretMovementType . CharLeft :
MoveCaretLeft ( textArea , caretPosition , visualLine , CaretPositioningMode . Normal ) ;
desiredXPos = double . NaN ;
break ;
return GetPrevCaretPosition ( textView , caretPosition , visualLine , CaretPositioningMode . Normal , enableVirtualSpace ) ;
case CaretMovementType . Backspace :
desiredXPos = double . NaN ;
return GetPrevCaretPosition ( textView , caretPosition , visualLine , CaretPositioningMode . EveryCodepoint , enableVirtualSpace ) ;
case CaretMovementType . CharRight :
case CaretMovementType . CharRight :
MoveCaretRight ( textArea , caretPosition , visualLine , CaretPositioningMode . Normal ) ;
desiredXPos = double . NaN ;
break ;
return GetNextCaretPosition ( textView , caretPosition , visualLine , CaretPositioningMode . Normal , enableVirtualSpace ) ;
case CaretMovementType . WordLeft :
case CaretMovementType . WordLeft :
MoveCaretLeft ( textArea , caretPosition , visualLine , CaretPositioningMode . WordStart ) ;
desiredXPos = double . NaN ;
break ;
return GetPrevCaretPosition ( textView , caretPosition , visualLine , CaretPositioningMode . WordStart , enableVirtualSpace ) ;
case CaretMovementType . WordRight :
case CaretMovementType . WordRight :
MoveCaretRight ( textArea , caretPosition , visualLine , CaretPositioningMode . WordStart ) ;
desiredXPos = double . NaN ;
break ;
return GetNextCaretPosition ( textView , caretPosition , visualLine , CaretPositioningMode . WordStart , enableVirtualSpace ) ;
case CaretMovementType . LineUp :
case CaretMovementType . LineUp :
case CaretMovementType . LineDown :
case CaretMovementType . LineDown :
case CaretMovementType . PageUp :
case CaretMovementType . PageUp :
case CaretMovementType . PageDown :
case CaretMovementType . PageDown :
MoveCaretUpDown ( textArea , direction , visualLine , textLine , caretPosition . VisualColumn ) ;
return GetUpDownCaretPosition ( textView , caretPosition , direction , visualLine , textLine , enableVirtualSpace , ref desiredXPos ) ;
break ;
case CaretMovementType . DocumentStart :
SetCaretPosition ( textArea , 0 , 0 ) ;
break ;
case CaretMovementType . DocumentEnd :
SetCaretPosition ( textArea , - 1 , textArea . Document . TextLength ) ;
break ;
case CaretMovementType . LineStart :
case CaretMovementType . LineStart :
MoveCaretToStartOfLine ( textArea , visualLine , textLine ) ;
desiredXPos = double . NaN ;
break ;
return GetStartOfLineCaretPosition ( caretPosition . VisualColumn , visualLine , textLine , enableVirtualSpace ) ;
case CaretMovementType . LineEnd :
case CaretMovementType . LineEnd :
MoveCaretToEndOfLine ( textArea , visualLine , textLine ) ;
desiredXPos = double . NaN ;
break ;
return GetEndOfLineCaretPosition ( visualLine , textLine ) ;
default :
default :
throw new NotSupportedException ( direction . ToString ( ) ) ;
throw new NotSupportedException ( direction . ToString ( ) ) ;
}
}
@ -216,81 +229,80 @@ namespace ICSharpCode.AvalonEdit.Editing
#endregion
#endregion
#region Home/End
#region Home/End
static void MoveCaretToStartOfLine ( TextArea textArea , VisualLine visualLine , TextLine textLine )
static TextViewPosition GetStartOfLineCaretPosition ( int oldVC , VisualLine visualLine , TextLine textLine , bool enableVirtualSpac e )
{
{
int newVC = visualLine . GetTextLineVisualStartColumn ( textLine ) ;
int newVC = visualLine . GetTextLineVisualStartColumn ( textLine ) ;
if ( newVC = = 0 )
if ( newVC = = 0 )
newVC = visualLine . GetNextCaretPosition ( newVC - 1 , LogicalDirection . Forward , CaretPositioningMode . WordStart , t extArea . Selection . E nableVirtualSpace) ;
newVC = visualLine . GetNextCaretPosition ( newVC - 1 , LogicalDirection . Forward , CaretPositioningMode . WordStart , enableVirtualSpace ) ;
if ( newVC < 0 )
if ( newVC < 0 )
throw ThrowUtil . NoValidCaretPosition ( ) ;
throw ThrowUtil . NoValidCaretPosition ( ) ;
// when the caret is already at the start of the text, jump to start before whitespace
// when the caret is already at the start of the text, jump to start before whitespace
if ( newVC = = textArea . Caret . VisualColumn )
if ( newVC = = oldVC )
newVC = 0 ;
newVC = 0 ;
int offset = visualLine . FirstDocumentLine . Offset + visualLine . GetRelativeOffset ( newVC ) ;
return visualLine . GetTextViewPosition ( newVC ) ;
SetCaretPosition ( textArea , newVC , offset ) ;
}
}
static void MoveCaretToEndOfLine ( TextArea textArea , VisualLine visualLine , TextLine textLine )
static TextViewPosition GetEndOfLineCaretPosition ( VisualLine visualLine , TextLine textLine )
{
{
int newVC = visualLine . GetTextLineVisualStartColumn ( textLine ) + textLine . Length - textLine . TrailingWhitespaceLength ;
int newVC = visualLine . GetTextLineVisualStartColumn ( textLine ) + textLine . Length - textLine . TrailingWhitespaceLength ;
int offset = visualLine . FirstDocumentLine . Offset + visualLine . GetRelativeOffset ( newVC ) ;
TextViewPosition pos = visualLine . GetTextViewPosition ( newVC ) ;
SetCaretPosition ( textArea , newVC , offset , isAtEndOfLine : true ) ;
pos . IsAtEndOfLine = true ;
return pos ;
}
}
#endregion
#endregion
#region By-character / By-word movement
#region By-character / By-word movement
static void MoveCaretRight ( TextArea textArea , TextViewPosition caretPosition , VisualLine visualLine , CaretPositioningMode mode )
static TextViewPosition GetNextCaretPosition ( TextView textView , TextViewPosition caretPosition , VisualLine visualLine , CaretPositioningMode mode , bool enableVirtualSpac e )
{
{
int pos = visualLine . GetNextCaretPosition ( caretPosition . VisualColumn , LogicalDirection . Forward , mode , t extArea . Selection . E nableVirtualSpace) ;
int pos = visualLine . GetNextCaretPosition ( caretPosition . VisualColumn , LogicalDirection . Forward , mode , enableVirtualSpace ) ;
if ( pos > = 0 ) {
if ( pos > = 0 ) {
SetCaretPosition ( textArea , pos , visualLine . GetRelativeOffset ( pos ) + visualLine . FirstDocumentLine . Offset ) ;
return visualLine . GetTextViewPosition ( pos ) ;
} else {
} else {
// move to start of next line
// move to start of next line
DocumentLine nextDocumentLine = visualLine . LastDocumentLine . NextLine ;
DocumentLine nextDocumentLine = visualLine . LastDocumentLine . NextLine ;
if ( nextDocumentLine ! = null ) {
if ( nextDocumentLine ! = null ) {
VisualLine nextLine = textArea . Text View . GetOrConstructVisualLine ( nextDocumentLine ) ;
VisualLine nextLine = textView . GetOrConstructVisualLine ( nextDocumentLine ) ;
pos = nextLine . GetNextCaretPosition ( - 1 , LogicalDirection . Forward , mode , t extArea . Selection . E nableVirtualSpace) ;
pos = nextLine . GetNextCaretPosition ( - 1 , LogicalDirection . Forward , mode , enableVirtualSpace ) ;
if ( pos < 0 )
if ( pos < 0 )
throw ThrowUtil . NoValidCaretPosition ( ) ;
throw ThrowUtil . NoValidCaretPosition ( ) ;
SetCaretPosition ( textArea , pos , nextLine . GetRelativeOffset ( pos ) + nextLine . FirstDocumentLine . Offset ) ;
return nextLine . GetTextViewPosition ( pos ) ;
} else {
} else {
// at end of document
// at end of document
Debug . Assert ( visualLine . LastDocumentLine . Offset + visualLine . LastDocumentLine . TotalLength = = textArea . Document . TextLength ) ;
Debug . Assert ( visualLine . LastDocumentLine . Offset + visualLine . LastDocumentLine . TotalLength = = textView . Document . TextLength ) ;
SetCaretPosition ( textArea , - 1 , textArea . Document . TextLength ) ;
return new TextViewPosition ( textView . Document . GetLocation ( textView . Document . TextLength ) ) ;
}
}
}
}
}
}
static void MoveCaretLeft ( TextArea textArea , TextViewPosition caretPosition , VisualLine visualLine , CaretPositioningMode mode )
static TextViewPosition GetPrevCaretPosition ( TextView textView , TextViewPosition caretPosition , VisualLine visualLine , CaretPositioningMode mode , bool enableVirtualSpac e )
{
{
int pos = visualLine . GetNextCaretPosition ( caretPosition . VisualColumn , LogicalDirection . Backward , mode , t extArea . Selection . E nableVirtualSpace) ;
int pos = visualLine . GetNextCaretPosition ( caretPosition . VisualColumn , LogicalDirection . Backward , mode , enableVirtualSpace ) ;
if ( pos > = 0 ) {
if ( pos > = 0 ) {
SetCaretPosition ( textArea , pos , visualLine . GetRelativeOffset ( pos ) + visualLine . FirstDocumentLine . Offset ) ;
return visualLine . GetTextViewPosition ( pos ) ;
} else {
} else {
// move to end of previous line
// move to end of previous line
DocumentLine previousDocumentLine = visualLine . FirstDocumentLine . PreviousLine ;
DocumentLine previousDocumentLine = visualLine . FirstDocumentLine . PreviousLine ;
if ( previousDocumentLine ! = null ) {
if ( previousDocumentLine ! = null ) {
VisualLine previousLine = textArea . Text View . GetOrConstructVisualLine ( previousDocumentLine ) ;
VisualLine previousLine = textView . GetOrConstructVisualLine ( previousDocumentLine ) ;
pos = previousLine . GetNextCaretPosition ( previousLine . VisualLength + 1 , LogicalDirection . Backward , mode , t extArea . Selection . E nableVirtualSpace) ;
pos = previousLine . GetNextCaretPosition ( previousLine . VisualLength + 1 , LogicalDirection . Backward , mode , enableVirtualSpace ) ;
if ( pos < 0 )
if ( pos < 0 )
throw ThrowUtil . NoValidCaretPosition ( ) ;
throw ThrowUtil . NoValidCaretPosition ( ) ;
SetCaretPosition ( textArea , pos , previousLine . GetRelativeOffset ( pos ) + previousLine . FirstDocumentLine . Offset ) ;
return previousLine . GetTextViewPosition ( pos ) ;
} else {
} else {
// at start of document
// at start of document
Debug . Assert ( visualLine . FirstDocumentLine . Offset = = 0 ) ;
Debug . Assert ( visualLine . FirstDocumentLine . Offset = = 0 ) ;
SetCaretPosition ( textArea , 0 , 0 ) ;
return new TextViewPosition ( 0 , 0 ) ;
}
}
}
}
}
}
#endregion
#endregion
#region Line+Page up/down
#region Line+Page up/down
static void MoveCaretUpDown ( TextArea textArea , CaretMovementType direction , VisualLine visualLine , TextLine textLine , int caretVisualColumn )
static TextViewPosition GetUpDownCaretPosition ( TextView textView , TextViewPosition caretPosition , CaretMovementType direction , VisualLine visualLine , TextLine textLine , bool enableVirtualSpace , ref double xPos )
{
{
// moving up/down happens using the desired visual X position
// moving up/down happens using the desired visual X position
double xPos = textArea . Caret . DesiredXPos ;
if ( double . IsNaN ( xPos ) )
if ( double . IsNaN ( xPos ) )
xPos = visualLine . GetTextLineVisualXPosition ( textLine , caretVisualColumn ) ;
xPos = visualLine . GetTextLineVisualXPosition ( textLine , caretPosition . VisualColumn ) ;
// now find the TextLine+VisualLine where the caret will end up in
// now find the TextLine+VisualLine where the caret will end up in
VisualLine targetVisualLine = visualLine ;
VisualLine targetVisualLine = visualLine ;
TextLine targetLine ;
TextLine targetLine ;
@ -304,8 +316,8 @@ namespace ICSharpCode.AvalonEdit.Editing
if ( textLineIndex > 0 ) {
if ( textLineIndex > 0 ) {
targetLine = visualLine . TextLines [ textLineIndex - 1 ] ;
targetLine = visualLine . TextLines [ textLineIndex - 1 ] ;
} else if ( prevLineNumber > = 1 ) {
} else if ( prevLineNumber > = 1 ) {
DocumentLine prevLine = textArea . Document . GetLineByNumber ( prevLineNumber ) ;
DocumentLine prevLine = textView . Document . GetLineByNumber ( prevLineNumber ) ;
targetVisualLine = textArea . Text View . GetOrConstructVisualLine ( prevLine ) ;
targetVisualLine = textView . GetOrConstructVisualLine ( prevLine ) ;
targetLine = targetVisualLine . TextLines [ targetVisualLine . TextLines . Count - 1 ] ;
targetLine = targetVisualLine . TextLines [ targetVisualLine . TextLines . Count - 1 ] ;
} else {
} else {
targetLine = null ;
targetLine = null ;
@ -319,9 +331,9 @@ namespace ICSharpCode.AvalonEdit.Editing
int nextLineNumber = visualLine . LastDocumentLine . LineNumber + 1 ;
int nextLineNumber = visualLine . LastDocumentLine . LineNumber + 1 ;
if ( textLineIndex < visualLine . TextLines . Count - 1 ) {
if ( textLineIndex < visualLine . TextLines . Count - 1 ) {
targetLine = visualLine . TextLines [ textLineIndex + 1 ] ;
targetLine = visualLine . TextLines [ textLineIndex + 1 ] ;
} else if ( nextLineNumber < = textArea . Document . LineCount ) {
} else if ( nextLineNumber < = textView . Document . LineCount ) {
DocumentLine nextLine = textArea . Document . GetLineByNumber ( nextLineNumber ) ;
DocumentLine nextLine = textView . Document . GetLineByNumber ( nextLineNumber ) ;
targetVisualLine = textArea . Text View . GetOrConstructVisualLine ( nextLine ) ;
targetVisualLine = textView . GetOrConstructVisualLine ( nextLine ) ;
targetLine = targetVisualLine . TextLines [ 0 ] ;
targetLine = targetVisualLine . TextLines [ 0 ] ;
} else {
} else {
targetLine = null ;
targetLine = null ;
@ -334,11 +346,11 @@ namespace ICSharpCode.AvalonEdit.Editing
// Page up/down: find the target line using its visual position
// Page up/down: find the target line using its visual position
double yPos = visualLine . GetTextLineVisualYPosition ( textLine , VisualYPosition . LineMiddle ) ;
double yPos = visualLine . GetTextLineVisualYPosition ( textLine , VisualYPosition . LineMiddle ) ;
if ( direction = = CaretMovementType . PageUp )
if ( direction = = CaretMovementType . PageUp )
yPos - = textArea . Text View . RenderSize . Height ;
yPos - = textView . RenderSize . Height ;
else
else
yPos + = textArea . Text View . RenderSize . Height ;
yPos + = textView . RenderSize . Height ;
DocumentLine newLine = textArea . Text View . GetDocumentLineByVisualTop ( yPos ) ;
DocumentLine newLine = textView . GetDocumentLineByVisualTop ( yPos ) ;
targetVisualLine = textArea . Text View . GetOrConstructVisualLine ( newLine ) ;
targetVisualLine = textView . GetOrConstructVisualLine ( newLine ) ;
targetLine = targetVisualLine . GetTextLineByVisualYPosition ( yPos ) ;
targetLine = targetVisualLine . GetTextLineByVisualYPosition ( yPos ) ;
break ;
break ;
}
}
@ -347,30 +359,18 @@ namespace ICSharpCode.AvalonEdit.Editing
}
}
if ( targetLine ! = null ) {
if ( targetLine ! = null ) {
double yPos = targetVisualLine . GetTextLineVisualYPosition ( targetLine , VisualYPosition . LineMiddle ) ;
double yPos = targetVisualLine . GetTextLineVisualYPosition ( targetLine , VisualYPosition . LineMiddle ) ;
int newVisualColumn = targetVisualLine . GetVisualColumn ( new Point ( xPos , yPos ) , textArea . Selection . EnableVirtualSpace ) ;
int newVisualColumn = targetVisualLine . GetVisualColumn ( new Point ( xPos , yPos ) , enableVirtualSpace ) ;
SetCaretPosition ( textArea , targetVisualLine , targetLine , newVisualColumn , false ) ;
textArea . Caret . DesiredXPos = xPos ;
}
}
#endregion
#region SetCaretPosition
// prevent wrapping to the next line; TODO: could 'IsAtEnd' help here?
static void SetCaretPosition ( TextArea textArea , VisualLine targetVisualLine , TextLine targetLine ,
int targetLineStartCol = targetVisualLine . GetTextLineVisualStartColumn ( targetLine ) ;
int newVisualColumn , bool allowWrapToNextLine )
if ( newVisualColumn > = targetLineStartCol + targetLine . Length ) {
{
if ( newVisualColumn < = targetVisualLine . VisualLength )
int targetLineStartCol = targetVisualLine . GetTextLineVisualStartColumn ( targetLine ) ;
newVisualColumn = targetLineStartCol + targetLine . Length - 1 ;
if ( ! allowWrapToNextLine & & newVisualColumn > = targetLineStartCol + targetLine . Length ) {
}
if ( newVisualColumn < = targetVisualLine . VisualLength )
return targetVisualLine . GetTextViewPosition ( newVisualColumn ) ;
newVisualColumn = targetLineStartCol + targetLine . Length - 1 ;
} else {
return caretPosition ;
}
}
int newOffset = targetVisualLine . GetRelativeOffset ( newVisualColumn ) + targetVisualLine . FirstDocumentLine . Offset ;
SetCaretPosition ( textArea , newVisualColumn , newOffset ) ;
}
static void SetCaretPosition ( TextArea textArea , int newVisualColumn , int newOffset , bool isAtEndOfLine = false )
{
textArea . Caret . Position = new TextViewPosition ( textArea . Document . GetLocation ( newOffset ) , newVisualColumn ) { IsAtEndOfLine = isAtEndOfLine } ;
textArea . Caret . DesiredXPos = double . NaN ;
}
}
#endregion
#endregion
}
}