Browse Source

Implemented SD-1853 - AvalonEdit: Distinguish between 'end of line' and 'start of next line' at word wrap positions

Also, the Home/End keys now apply to the current TextLine, not the whole VisualLine.
pull/41/head
Daniel Grunwald 13 years ago
parent
commit
00d4408a57
  1. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs
  2. 22
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretNavigationCommandHandler.cs
  3. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/ImeNativeWrapper.cs
  4. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/RectangleSelection.cs
  5. 31
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/SelectionMouseHandler.cs
  6. 12
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/SimpleSelection.cs
  7. 4
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/BackgroundGeometryBuilder.cs
  8. 8
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs
  9. 61
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs
  10. 22
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextViewPosition.cs

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs

@ -348,7 +348,7 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -348,7 +348,7 @@ namespace ICSharpCode.AvalonEdit.Editing
RevalidateVisualColumn(visualLine);
}
TextLine textLine = visualLine.GetTextLine(position.VisualColumn);
TextLine textLine = visualLine.GetTextLine(position.VisualColumn, position.IsAtEndOfLine);
double xPos = visualLine.GetTextLineVisualXPosition(textLine, position.VisualColumn);
double lineTop = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextTop);
double lineBottom = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextBottom);

22
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretNavigationCommandHandler.cs

@ -177,7 +177,7 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -177,7 +177,7 @@ namespace ICSharpCode.AvalonEdit.Editing
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);
TextLine textLine = visualLine.GetTextLine(caretPosition.VisualColumn, caretPosition.IsAtEndOfLine);
switch (direction) {
case CaretMovementType.CharLeft:
MoveCaretLeft(textArea, caretPosition, visualLine, CaretPositioningMode.Normal);
@ -204,10 +204,10 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -204,10 +204,10 @@ namespace ICSharpCode.AvalonEdit.Editing
SetCaretPosition(textArea, -1, textArea.Document.TextLength);
break;
case CaretMovementType.LineStart:
MoveCaretToStartOfLine(textArea, visualLine);
MoveCaretToStartOfLine(textArea, visualLine, textLine);
break;
case CaretMovementType.LineEnd:
MoveCaretToEndOfLine(textArea, visualLine);
MoveCaretToEndOfLine(textArea, visualLine, textLine);
break;
default:
throw new NotSupportedException(direction.ToString());
@ -216,9 +216,11 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -216,9 +216,11 @@ namespace ICSharpCode.AvalonEdit.Editing
#endregion
#region Home/End
static void MoveCaretToStartOfLine(TextArea textArea, VisualLine visualLine)
static void MoveCaretToStartOfLine(TextArea textArea, VisualLine visualLine, TextLine textLine)
{
int newVC = visualLine.GetNextCaretPosition(-1, LogicalDirection.Forward, CaretPositioningMode.WordStart, textArea.Selection.EnableVirtualSpace);
int newVC = visualLine.GetTextLineVisualStartColumn(textLine);
if (newVC == 0)
newVC = visualLine.GetNextCaretPosition(newVC - 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
@ -228,11 +230,11 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -228,11 +230,11 @@ namespace ICSharpCode.AvalonEdit.Editing
SetCaretPosition(textArea, newVC, offset);
}
static void MoveCaretToEndOfLine(TextArea textArea, VisualLine visualLine)
static void MoveCaretToEndOfLine(TextArea textArea, VisualLine visualLine, TextLine textLine)
{
int newVC = visualLine.VisualLength;
int newVC = visualLine.GetTextLineVisualStartColumn(textLine) + textLine.Length - textLine.TrailingWhitespaceLength;
int offset = visualLine.FirstDocumentLine.Offset + visualLine.GetRelativeOffset(newVC);
SetCaretPosition(textArea, newVC, offset);
SetCaretPosition(textArea, newVC, offset, isAtEndOfLine: true);
}
#endregion
@ -365,9 +367,9 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -365,9 +367,9 @@ namespace ICSharpCode.AvalonEdit.Editing
SetCaretPosition(textArea, newVisualColumn, newOffset);
}
static void SetCaretPosition(TextArea textArea, int newVisualColumn, int newOffset)
static void SetCaretPosition(TextArea textArea, int newVisualColumn, int newOffset, bool isAtEndOfLine = false)
{
textArea.Caret.Position = new TextViewPosition(textArea.Document.GetLocation(newOffset), newVisualColumn);
textArea.Caret.Position = new TextViewPosition(textArea.Document.GetLocation(newOffset), newVisualColumn) { IsAtEndOfLine = isAtEndOfLine };
textArea.Caret.DesiredXPos = double.NaN;
}
#endregion

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/ImeNativeWrapper.cs

@ -169,7 +169,7 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -169,7 +169,7 @@ namespace ICSharpCode.AvalonEdit.Editing
// in those cases. It should be refreshed immediately.
if (source.RootVisual == null || !source.RootVisual.IsAncestorOf(textView))
return EMPTY_RECT;
TextLine line = vl.GetTextLine(pos.VisualColumn);
TextLine line = vl.GetTextLine(pos.VisualColumn, pos.IsAtEndOfLine);
Rect displayRect;
// calculate the display rect for the current character
if (pos.VisualColumn < vl.VisualLengthWithEndOfLineMarker) {

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/RectangleSelection.cs

@ -148,7 +148,7 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -148,7 +148,7 @@ namespace ICSharpCode.AvalonEdit.Editing
DocumentLine documentLine = textArea.Document.GetLineByNumber(pos.Line);
VisualLine visualLine = textArea.TextView.GetOrConstructVisualLine(documentLine);
int vc = visualLine.ValidateVisualColumn(pos, true);
TextLine textLine = visualLine.GetTextLine(vc);
TextLine textLine = visualLine.GetTextLine(vc, pos.IsAtEndOfLine);
return visualLine.GetTextLineVisualXPosition(textLine, vc);
}

31
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/SelectionMouseHandler.cs

@ -166,9 +166,10 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -166,9 +166,10 @@ namespace ICSharpCode.AvalonEdit.Editing
if (e.Data.GetDataPresent(DataFormats.UnicodeText, true)) {
e.Handled = true;
int visualColumn;
int offset = GetOffsetFromMousePosition(e.GetPosition(textArea.TextView), out visualColumn);
bool isAtEndOfLine;
int offset = GetOffsetFromMousePosition(e.GetPosition(textArea.TextView), out visualColumn, out isAtEndOfLine);
if (offset >= 0) {
textArea.Caret.Position = new TextViewPosition(textArea.Document.GetLocation(offset), visualColumn);
textArea.Caret.Position = new TextViewPosition(textArea.Document.GetLocation(offset), visualColumn) { IsAtEndOfLine = isAtEndOfLine };
textArea.Caret.DesiredXPos = double.NaN;
if (textArea.ReadOnlySectionProvider.CanInsert(offset)) {
if ((e.AllowedEffects & DragDropEffects.Move) == DragDropEffects.Move
@ -359,7 +360,8 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -359,7 +360,8 @@ namespace ICSharpCode.AvalonEdit.Editing
Point p = e.GetPosition(textArea.TextView);
if (p.X >= 0 && p.Y >= 0 && p.X <= textArea.TextView.ActualWidth && p.Y <= textArea.TextView.ActualHeight) {
int visualColumn;
int offset = GetOffsetFromMousePosition(e, out visualColumn);
bool isAtEndOfLine;
int offset = GetOffsetFromMousePosition(e, out visualColumn, out isAtEndOfLine);
if (textArea.Selection.Contains(offset))
e.Cursor = Cursors.Arrow;
else
@ -380,7 +382,8 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -380,7 +382,8 @@ namespace ICSharpCode.AvalonEdit.Editing
bool shift = (modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
if (enableTextDragDrop && e.ClickCount == 1 && !shift) {
int visualColumn;
int offset = GetOffsetFromMousePosition(e, out visualColumn);
bool isAtEndOfLine;
int offset = GetOffsetFromMousePosition(e, out visualColumn, out isAtEndOfLine);
if (textArea.Selection.Contains(offset)) {
if (textArea.CaptureMouse()) {
mode = SelectionMode.PossibleDragStart;
@ -488,12 +491,12 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -488,12 +491,12 @@ namespace ICSharpCode.AvalonEdit.Editing
}
}
int GetOffsetFromMousePosition(MouseEventArgs e, out int visualColumn)
int GetOffsetFromMousePosition(MouseEventArgs e, out int visualColumn, out bool isAtEndOfLine)
{
return GetOffsetFromMousePosition(e.GetPosition(textArea.TextView), out visualColumn);
return GetOffsetFromMousePosition(e.GetPosition(textArea.TextView), out visualColumn, out isAtEndOfLine);
}
int GetOffsetFromMousePosition(Point positionRelativeToTextView, out int visualColumn)
int GetOffsetFromMousePosition(Point positionRelativeToTextView, out int visualColumn, out bool isAtEndOfLine)
{
visualColumn = 0;
TextView textView = textArea.TextView;
@ -507,9 +510,10 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -507,9 +510,10 @@ namespace ICSharpCode.AvalonEdit.Editing
pos.Y = textView.DocumentHeight - ExtensionMethods.Epsilon;
VisualLine line = textView.GetVisualLineFromVisualTop(pos.Y);
if (line != null) {
visualColumn = line.GetVisualColumn(pos, textArea.Selection.EnableVirtualSpace);
visualColumn = line.GetVisualColumn(pos, textArea.Selection.EnableVirtualSpace, out isAtEndOfLine);
return line.GetRelativeOffset(visualColumn) + line.FirstDocumentLine.Offset;
}
isAtEndOfLine = false;
return -1;
}
@ -568,16 +572,19 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -568,16 +572,19 @@ namespace ICSharpCode.AvalonEdit.Editing
void SetCaretOffsetToMousePosition(MouseEventArgs e, ISegment allowedSegment)
{
int visualColumn;
bool isAtEndOfLine;
int offset;
if (mode == SelectionMode.Rectangular)
if (mode == SelectionMode.Rectangular) {
offset = GetOffsetFromMousePositionFirstTextLineOnly(e.GetPosition(textArea.TextView), out visualColumn);
else
offset = GetOffsetFromMousePosition(e, out visualColumn);
isAtEndOfLine = true;
} else {
offset = GetOffsetFromMousePosition(e, out visualColumn, out isAtEndOfLine);
}
if (allowedSegment != null) {
offset = offset.CoerceValue(allowedSegment.Offset, allowedSegment.EndOffset);
}
if (offset >= 0) {
textArea.Caret.Position = new TextViewPosition(textArea.Document.GetLocation(offset), visualColumn);
textArea.Caret.Position = new TextViewPosition(textArea.Document.GetLocation(offset), visualColumn) { IsAtEndOfLine = isAtEndOfLine };
textArea.Caret.DesiredXPos = double.NaN;
}
}

12
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/SimpleSelection.cs

@ -84,11 +84,13 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -84,11 +84,13 @@ namespace ICSharpCode.AvalonEdit.Editing
{
if (e == null)
throw new ArgumentNullException("e");
return Selection.Create(
textArea,
new TextViewPosition(textArea.Document.GetLocation(e.GetNewOffset(startOffset, AnchorMovementType.Default)), start.VisualColumn),
new TextViewPosition(textArea.Document.GetLocation(e.GetNewOffset(endOffset, AnchorMovementType.Default)), end.VisualColumn)
);
TextViewPosition newStart = start;
TextViewPosition newEnd = end;
// by changing the existing TextViewPosition, we preserve the VisualColumn (though it might become invalid)
// and the IsAtEndOfLine property.
newStart.Location = textArea.Document.GetLocation(e.GetNewOffset(startOffset, AnchorMovementType.Default));
newEnd.Location = textArea.Document.GetLocation(e.GetNewOffset(endOffset, AnchorMovementType.Default));
return Selection.Create(textArea, newStart, newEnd);
}
/// <inheritdoc/>

4
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/BackgroundGeometryBuilder.cs

@ -125,8 +125,8 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -125,8 +125,8 @@ namespace ICSharpCode.AvalonEdit.Rendering
start = new TextViewPosition(textView.Document.GetLocation(sel.StartOffset), sel.StartVisualColumn);
end = new TextViewPosition(textView.Document.GetLocation(sel.EndOffset), sel.EndVisualColumn);
} else {
start = new TextViewPosition(textView.Document.GetLocation(segmentStart), -1);
end = new TextViewPosition(textView.Document.GetLocation(segmentEnd), -1);
start = new TextViewPosition(textView.Document.GetLocation(segmentStart));
end = new TextViewPosition(textView.Document.GetLocation(segmentEnd));
}
foreach (VisualLine vl in textView.VisualLines) {

8
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs

@ -1757,9 +1757,7 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -1757,9 +1757,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
VisualLine line = GetVisualLineFromVisualTop(visualPosition.Y);
if (line == null)
return null;
int visualColumn = line.GetVisualColumn(visualPosition);
int documentOffset = line.GetRelativeOffset(visualColumn) + line.FirstDocumentLine.Offset;
return new TextViewPosition(document.GetLocation(documentOffset), visualColumn);
return line.GetTextViewPosition(visualPosition, Options.EnableVirtualSpace);
}
/// <summary>
@ -1777,9 +1775,7 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -1777,9 +1775,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
VisualLine line = GetVisualLineFromVisualTop(visualPosition.Y);
if (line == null)
return null;
int visualColumn = line.GetVisualColumnFloor(visualPosition);
int documentOffset = line.GetRelativeOffset(visualColumn) + line.FirstDocumentLine.Offset;
return new TextViewPosition(document.GetLocation(documentOffset), visualColumn);
return line.GetTextViewPositionFloor(visualPosition, Options.EnableVirtualSpace);
}
#endregion

61
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs

@ -307,13 +307,21 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -307,13 +307,21 @@ namespace ICSharpCode.AvalonEdit.Rendering
/// Gets the text line containing the specified visual column.
/// </summary>
public TextLine GetTextLine(int visualColumn)
{
return GetTextLine(visualColumn, false);
}
/// <summary>
/// Gets the text line containing the specified visual column.
/// </summary>
public TextLine GetTextLine(int visualColumn, bool isAtEndOfLine)
{
if (visualColumn < 0)
throw new ArgumentOutOfRangeException("visualColumn");
if (visualColumn >= VisualLengthWithEndOfLineMarker)
return TextLines[TextLines.Count - 1];
foreach (TextLine line in TextLines) {
if (visualColumn < line.Length)
if (isAtEndOfLine ? visualColumn <= line.Length : visualColumn < line.Length)
return line;
else
visualColumn -= line.Length;
@ -437,6 +445,14 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -437,6 +445,14 @@ namespace ICSharpCode.AvalonEdit.Rendering
return GetVisualColumn(GetTextLineByVisualYPosition(point.Y), point.X, allowVirtualSpace);
}
internal int GetVisualColumn(Point point, bool allowVirtualSpace, out bool isAtEndOfLine)
{
var textLine = GetTextLineByVisualYPosition(point.Y);
int vc = GetVisualColumn(textLine, point.X, allowVirtualSpace);
isAtEndOfLine = (vc >= GetTextLineVisualStartColumn(textLine) + textLine.Length);
return vc;
}
/// <summary>
/// Gets the visual column from a document position (relative to top left of the document).
/// If the user clicks between two visual columns, rounds to the nearest column.
@ -497,9 +513,16 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -497,9 +513,16 @@ namespace ICSharpCode.AvalonEdit.Rendering
/// If the user clicks between two visual columns, returns the first of those columns.
/// </summary>
public int GetVisualColumnFloor(Point point, bool allowVirtualSpace)
{
bool tmp;
return GetVisualColumnFloor(point, allowVirtualSpace, out tmp);
}
internal int GetVisualColumnFloor(Point point, bool allowVirtualSpace, out bool isAtEndOfLine)
{
TextLine textLine = GetTextLineByVisualYPosition(point.Y);
if (point.X > textLine.WidthIncludingTrailingWhitespace) {
isAtEndOfLine = true;
if (allowVirtualSpace && textLine == TextLines[TextLines.Count - 1]) {
// clicking virtual space in the last line
int virtualX = (int)((point.X - textLine.WidthIncludingTrailingWhitespace) / textView.WideSpaceWidth);
@ -510,11 +533,47 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -510,11 +533,47 @@ namespace ICSharpCode.AvalonEdit.Rendering
// specially and return the line's end column instead.
return GetTextLineVisualStartColumn(textLine) + textLine.Length;
}
} else {
isAtEndOfLine = false;
}
CharacterHit ch = textLine.GetCharacterHitFromDistance(point.X);
return ch.FirstCharacterIndex;
}
/// <summary>
/// Gets the text view position from the specified visual position.
/// If the position is within a character, it is rounded to the next character boundary.
/// </summary>
/// <param name="visualPosition">The position in WPF device-independent pixels relative
/// to the top left corner of the document.</param>
/// <param name="allowVirtualSpace">Controls whether positions in virtual space may be returned.</param>
public TextViewPosition GetTextViewPosition(Point visualPosition, bool allowVirtualSpace)
{
bool isAtEndOfLine;
int visualColumn = GetVisualColumn(visualPosition, allowVirtualSpace, out isAtEndOfLine);
int documentOffset = GetRelativeOffset(visualColumn) + this.FirstDocumentLine.Offset;
TextViewPosition pos = new TextViewPosition(this.Document.GetLocation(documentOffset), visualColumn);
pos.IsAtEndOfLine = isAtEndOfLine;
return pos;
}
/// <summary>
/// Gets the text view position from the specified visual position.
/// If the position is inside a character, the position in front of the character is returned.
/// </summary>
/// <param name="visualPosition">The position in WPF device-independent pixels relative
/// to the top left corner of the document.</param>
/// <param name="allowVirtualSpace">Controls whether positions in virtual space may be returned.</param>
public TextViewPosition GetTextViewPositionFloor(Point visualPosition, bool allowVirtualSpace)
{
bool isAtEndOfLine;
int visualColumn = GetVisualColumnFloor(visualPosition, allowVirtualSpace, out isAtEndOfLine);
int documentOffset = GetRelativeOffset(visualColumn) + this.FirstDocumentLine.Offset;
TextViewPosition pos = new TextViewPosition(this.Document.GetLocation(documentOffset), visualColumn);
pos.IsAtEndOfLine = isAtEndOfLine;
return pos;
}
/// <summary>
/// Gets whether the visual line was disposed.
/// </summary>

22
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextViewPosition.cs

@ -13,6 +13,7 @@ namespace ICSharpCode.AvalonEdit @@ -13,6 +13,7 @@ namespace ICSharpCode.AvalonEdit
public struct TextViewPosition : IEquatable<TextViewPosition>
{
int line, column, visualColumn;
bool isAtEndOfLine;
/// <summary>
/// Gets/Sets Location.
@ -52,6 +53,21 @@ namespace ICSharpCode.AvalonEdit @@ -52,6 +53,21 @@ namespace ICSharpCode.AvalonEdit
set { visualColumn = value; }
}
/// <summary>
/// When word-wrap is enabled and a line is wrapped at a position where there is no space character;
/// then both the end of the first TextLine and the beginning of the second TextLine
/// refer to the same position in the document, and also have the same visual column.
/// In this case, the IsAtEndOfLine property is used to distinguish between the two cases:
/// the value <c>true</c> indicates that the position refers to the end of the previous TextLine;
/// the value <c>false</c> indicates that the position refers to the beginning of the next TextLine.
///
/// If this position is not at such a wrapping position, the value of this property has no effect.
/// </summary>
public bool IsAtEndOfLine {
get { return isAtEndOfLine; }
set { isAtEndOfLine = value; }
}
/// <summary>
/// Creates a new TextViewPosition instance.
/// </summary>
@ -60,6 +76,7 @@ namespace ICSharpCode.AvalonEdit @@ -60,6 +76,7 @@ namespace ICSharpCode.AvalonEdit
this.line = line;
this.column = column;
this.visualColumn = visualColumn;
this.isAtEndOfLine = false;
}
/// <summary>
@ -78,6 +95,7 @@ namespace ICSharpCode.AvalonEdit @@ -78,6 +95,7 @@ namespace ICSharpCode.AvalonEdit
this.line = location.Line;
this.column = location.Column;
this.visualColumn = visualColumn;
this.isAtEndOfLine = false;
}
/// <summary>
@ -92,8 +110,8 @@ namespace ICSharpCode.AvalonEdit @@ -92,8 +110,8 @@ namespace ICSharpCode.AvalonEdit
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture,
"[TextViewPosition Line={0} Column={1} VisualColumn={2}]",
this.line, this.column, this.visualColumn);
"[TextViewPosition Line={0} Column={1} VisualColumn={2} IsAtEndOfLine={3}]",
this.line, this.column, this.visualColumn, this.isAtEndOfLine);
}
/// <summary>

Loading…
Cancel
Save