diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs
index 98a2e67e6e..9ecb8f5281 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs
@@ -289,7 +289,7 @@ namespace ICSharpCode.AvalonEdit.Editing
if (offsetFromVisualColumn != caretOffset) {
position.VisualColumn = visualLine.GetVisualColumn(caretOffset - firstDocumentLineOffset);
} else {
- if (position.VisualColumn > visualLine.VisualLength) {
+ if (position.VisualColumn > visualLine.VisualLength && !textArea.Options.EnableVirtualSpace) {
position.VisualColumn = visualLine.VisualLength;
}
}
@@ -347,7 +347,7 @@ namespace ICSharpCode.AvalonEdit.Editing
}
TextLine textLine = visualLine.GetTextLine(position.VisualColumn);
- double xPos = textLine.GetDistanceFromCharacterHit(new CharacterHit(position.VisualColumn, 0));
+ double xPos = visualLine.GetTextLineVisualXPosition(textLine, position.VisualColumn);
double lineTop = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextTop);
double lineBottom = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.LineBottom);
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretNavigationCommandHandler.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretNavigationCommandHandler.cs
index 216a1e249d..c39a350623 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretNavigationCommandHandler.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretNavigationCommandHandler.cs
@@ -4,10 +4,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media.TextFormatting;
-
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Utils;
@@ -250,7 +250,7 @@ namespace ICSharpCode.AvalonEdit.Editing
// moving up/down happens using the desired visual X position
double xPos = textArea.Caret.DesiredXPos;
if (double.IsNaN(xPos))
- xPos = textLine.GetDistanceFromCharacterHit(new CharacterHit(caretVisualColumn, 0));
+ xPos = visualLine.GetTextLineVisualXPosition(textLine, caretVisualColumn);
// now find the TextLine+VisualLine where the caret will end up in
VisualLine targetVisualLine = visualLine;
TextLine targetLine;
@@ -306,8 +306,9 @@ namespace ICSharpCode.AvalonEdit.Editing
throw new NotSupportedException(direction.ToString());
}
if (targetLine != null) {
- CharacterHit ch = targetLine.GetCharacterHitFromDistance(xPos);
- SetCaretPosition(textArea, targetVisualLine, targetLine, ch, false);
+ double yPos = targetVisualLine.GetTextLineVisualYPosition(targetLine, VisualYPosition.LineMiddle);
+ int newVisualColumn = targetVisualLine.GetVisualColumn(new Point(xPos, yPos));
+ SetCaretPosition(textArea, targetVisualLine, targetLine, newVisualColumn, false);
textArea.Caret.DesiredXPos = xPos;
}
}
@@ -315,12 +316,13 @@ namespace ICSharpCode.AvalonEdit.Editing
#region SetCaretPosition
static void SetCaretPosition(TextArea textArea, VisualLine targetVisualLine, TextLine targetLine,
- CharacterHit ch, bool allowWrapToNextLine)
+ int newVisualColumn, bool allowWrapToNextLine)
{
- int newVisualColumn = ch.FirstCharacterIndex + ch.TrailingLength;
int targetLineStartCol = targetVisualLine.GetTextLineVisualStartColumn(targetLine);
- if (!allowWrapToNextLine && newVisualColumn >= targetLineStartCol + targetLine.Length)
- newVisualColumn = targetLineStartCol + targetLine.Length - 1;
+ 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);
}
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/RectangleSelection.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/RectangleSelection.cs
index e66a34a42a..76eac2e3b7 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/RectangleSelection.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/RectangleSelection.cs
@@ -13,7 +13,7 @@ using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.Editing
{
///
- /// Rectangular selection.
+ /// Rectangular selection ("box selection").
///
public sealed class RectangleSelection : Selection
{
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/BackgroundGeometryBuilder.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/BackgroundGeometryBuilder.cs
index 4cd7d63c13..367b12340a 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/BackgroundGeometryBuilder.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/BackgroundGeometryBuilder.cs
@@ -129,7 +129,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
if (segmentStartVCInLine == segmentEndVCInLine) {
// GetTextBounds crashes for length=0, so we'll handle this case with GetDistanceFromCharacterHit
// We need to return a rectangle to ensure empty lines are still visible
- double pos = line.GetDistanceFromCharacterHit(new CharacterHit(segmentStartVCInLine, 0));
+ double pos = vl.GetTextLineVisualXPosition(line, segmentStartVCInLine);
pos -= scrollOffset.X;
// The following special cases are necessary to get rid of empty rectangles at the end of a TextLine if "Show Spaces" is active.
// If not excluded once, the same rectangle is calculated (and added) twice (since the offset could be mapped to two visual positions; end/start of line), if there is no trailing whitespace.
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs
index 58f1c059a1..b4ef080727 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs
@@ -1360,7 +1360,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
double wideSpaceWidth; // Width of an 'x'. Used as basis for the tab width, and for scrolling.
double defaultLineHeight; // Height of a line containing 'x'. Used for scrolling.
- double WideSpaceWidth {
+ internal double WideSpaceWidth {
get {
if (wideSpaceWidth == 0) {
MeasureWideSpaceWidthAndDefaultLineHeight();
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs
index 2fc7b960f9..22309b5b8d 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs
@@ -224,8 +224,9 @@ namespace ICSharpCode.AvalonEdit.Rendering
///
public TextLine GetTextLine(int visualColumn)
{
- ThrowUtil.CheckInRangeInclusive(visualColumn, "visualColumn", 0, VisualLength);
- if (visualColumn == VisualLength)
+ if (visualColumn < 0)
+ throw new ArgumentOutOfRangeException("visualColumn");
+ if (visualColumn >= VisualLength)
return TextLines[TextLines.Count - 1];
foreach (TextLine line in TextLines) {
if (visualColumn < line.Length)
@@ -307,18 +308,45 @@ namespace ICSharpCode.AvalonEdit.Rendering
public Point GetVisualPosition(int visualColumn, VisualYPosition yPositionMode)
{
TextLine textLine = GetTextLine(visualColumn);
- double xPos = textLine.GetDistanceFromCharacterHit(new CharacterHit(visualColumn, 0));
+ double xPos = GetTextLineVisualXPosition(textLine, visualColumn);
double yPos = GetTextLineVisualYPosition(textLine, yPositionMode);
return new Point(xPos, yPos);
}
+ ///
+ /// Gets the distance to the left border of the text area of the specified visual column.
+ /// The visual column must belong to the specified text line.
+ ///
+ public double GetTextLineVisualXPosition(TextLine textLine, int visualColumn)
+ {
+ if (textLine == null)
+ throw new ArgumentNullException("textLine");
+ double xPos = textLine.GetDistanceFromCharacterHit(
+ new CharacterHit(Math.Min(visualColumn, VisualLength), 0));
+ if (visualColumn > VisualLength) {
+ xPos += (visualColumn - VisualLength) * textView.WideSpaceWidth;
+ }
+ return xPos;
+ }
+
///
/// 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.
///
public int GetVisualColumn(Point point)
+ {
+ return GetVisualColumn(point, textView.Options.EnableVirtualSpace);
+ }
+
+ public int GetVisualColumn(Point point, bool allowVirtualSpace)
{
TextLine textLine = GetTextLineByVisualYPosition(point.Y);
+ if (point.X > textLine.WidthIncludingTrailingWhitespace) {
+ if (allowVirtualSpace && textLine == TextLines[TextLines.Count - 1]) {
+ int virtualX = (int)Math.Round((point.X - textLine.WidthIncludingTrailingWhitespace) / textView.WideSpaceWidth);
+ return VisualLength + virtualX;
+ }
+ }
CharacterHit ch = textLine.GetCharacterHitFromDistance(point.X);
return ch.FirstCharacterIndex + ch.TrailingLength;
}
@@ -328,13 +356,24 @@ namespace ICSharpCode.AvalonEdit.Rendering
/// If the user clicks between two visual columns, returns the first of those columns.
///
public int GetVisualColumnFloor(Point point)
+ {
+ return GetVisualColumnFloor(point, textView.Options.EnableVirtualSpace);
+ }
+
+ public int GetVisualColumnFloor(Point point, bool allowVirtualSpace)
{
TextLine textLine = GetTextLineByVisualYPosition(point.Y);
if (point.X > textLine.WidthIncludingTrailingWhitespace) {
- // GetCharacterHitFromDistance returns a hit with FirstCharacterIndex=last character inline
- // and TrailingLength=1 when clicking behind the line, so the floor function needs to handle this case
- // specially end return the line's end column instead.
- return GetTextLineVisualStartColumn(textLine) + textLine.Length;
+ if (allowVirtualSpace && textLine == TextLines[TextLines.Count - 1]) {
+ // clicking virtual space in the last line
+ int virtualX = (int)((point.X - textLine.WidthIncludingTrailingWhitespace) / textView.WideSpaceWidth);
+ return VisualLength + virtualX;
+ } else {
+ // GetCharacterHitFromDistance returns a hit with FirstCharacterIndex=last character in line
+ // and TrailingLength=1 when clicking behind the line, so the floor function needs to handle this case
+ // specially and return the line's end column instead.
+ return GetTextLineVisualStartColumn(textLine) + textLine.Length;
+ }
}
CharacterHit ch = textLine.GetCharacterHitFromDistance(point.X);
return ch.FirstCharacterIndex;
@@ -352,14 +391,23 @@ namespace ICSharpCode.AvalonEdit.Rendering
{
if (elements.Count == 0) {
// special handling for empty visual lines:
- // even though we don't have any elements,
- // there's a single caret stop at visualColumn 0
- if (visualColumn < 0 && direction == LogicalDirection.Forward)
- return 0;
- else if (visualColumn > 0 && direction == LogicalDirection.Backward)
- return 0;
- else
- return -1;
+ if (textView.Options.EnableVirtualSpace) {
+ if (direction == LogicalDirection.Forward)
+ return Math.Max(0, visualColumn + 1);
+ else if (visualColumn > 0)
+ return visualColumn - 1;
+ else
+ return -1;
+ } else {
+ // even though we don't have any elements,
+ // there's a single caret stop at visualColumn 0
+ if (visualColumn < 0 && direction == LogicalDirection.Forward)
+ return 0;
+ else if (visualColumn > 0 && direction == LogicalDirection.Backward)
+ return 0;
+ else
+ return -1;
+ }
}
int i;
@@ -368,7 +416,10 @@ namespace ICSharpCode.AvalonEdit.Rendering
// If the last element doesn't handle line borders, return the line end as caret stop
if (visualColumn > this.VisualLength && !elements[elements.Count-1].HandlesLineBorders && HasImplicitStopAtLineEnd(mode)) {
- return this.VisualLength;
+ if (textView.Options.EnableVirtualSpace)
+ return visualColumn - 1;
+ else
+ return this.VisualLength;
}
// skip elements that start after or at visualColumn
for (i = elements.Count - 1; i >= 0; i--) {
@@ -407,8 +458,12 @@ namespace ICSharpCode.AvalonEdit.Rendering
}
// if we've found nothing, and the last element doesn't handle line borders,
// return the line end as caret stop
- if (visualColumn < this.VisualLength && !elements[elements.Count-1].HandlesLineBorders && HasImplicitStopAtLineEnd(mode))
- return this.VisualLength;
+ if (!elements[elements.Count-1].HandlesLineBorders && HasImplicitStopAtLineEnd(mode)) {
+ if (visualColumn < this.VisualLength)
+ return this.VisualLength;
+ else if (textView.Options.EnableVirtualSpace)
+ return visualColumn + 1;
+ }
}
// we've found nothing, return -1 and let the caret search continue in the next line
return -1;
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextEditorOptions.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextEditorOptions.cs
index a2b708a1f6..35af3fba72 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextEditorOptions.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextEditorOptions.cs
@@ -362,5 +362,24 @@ namespace ICSharpCode.AvalonEdit
}
}
}
+
+ bool enableVirtualSpace;
+
+ ///
+ /// Gets/Sets whether the user can set the caret behind the line ending
+ /// (into "virtual space").
+ /// Note that virtual space is always used (independent from this setting)
+ /// when doing rectangle selections.
+ ///
+ [DefaultValue(false)]
+ public virtual bool EnableVirtualSpace {
+ get { return enableVirtualSpace; }
+ set {
+ if (enableVirtualSpace != value) {
+ enableVirtualSpace = value;
+ OnPropertyChanged("EnableVirtualSpace");
+ }
+ }
+ }
}
}