Skip to content
This repository has been archived by the owner on Oct 16, 2020. It is now read-only.

Commit

Permalink
AvalonEdit: Initial implementation of virtual space.
Browse files Browse the repository at this point in the history
  • Loading branch information
dgrunwald authored and siegfriedpammer committed Nov 20, 2011
1 parent 5f77498 commit 600ba51
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 31 deletions.
Expand Up @@ -289,7 +289,7 @@ void RevalidateVisualColumn(VisualLine visualLine)
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;
}
}
Expand Down Expand Up @@ -347,7 +347,7 @@ Rect CalcCaretRectangle(VisualLine visualLine)
}

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);

Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -250,7 +250,7 @@ static void MoveCaretUpDown(TextArea textArea, CaretMovementType direction, Visu
// 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;
Expand Down Expand Up @@ -306,21 +306,23 @@ static void MoveCaretUpDown(TextArea textArea, CaretMovementType direction, Visu
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;
}
}
#endregion

#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);
}
Expand Down
Expand Up @@ -13,7 +13,7 @@
namespace ICSharpCode.AvalonEdit.Editing
{
/// <summary>
/// Rectangular selection.
/// Rectangular selection ("box selection").
/// </summary>
public sealed class RectangleSelection : Selection
{
Expand Down
Expand Up @@ -129,7 +129,7 @@ static IEnumerable<Rect> GetRectsForSegmentImpl(TextView textView, ISegment segm
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.
Expand Down
Expand Up @@ -1360,7 +1360,7 @@ void IScrollInfo.MouseWheelRight()
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();
Expand Down
Expand Up @@ -224,8 +224,9 @@ public int GetRelativeOffset(int visualColumn)
/// </summary>
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)
Expand Down Expand Up @@ -307,18 +308,45 @@ public TextLine GetTextLineByVisualYPosition(double visualTop)
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);
}

/// <summary>
/// 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.
/// </summary>
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;
}

/// <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.
/// </summary>
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;
}
Expand All @@ -328,13 +356,24 @@ public int GetVisualColumn(Point point)
/// If the user clicks between two visual columns, returns the first of those columns.
/// </summary>
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;
Expand All @@ -352,14 +391,23 @@ public int GetNextCaretPosition(int visualColumn, LogicalDirection direction, Ca
{
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;
Expand All @@ -368,7 +416,10 @@ public int GetNextCaretPosition(int visualColumn, LogicalDirection direction, Ca
// 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--) {
Expand Down Expand Up @@ -407,8 +458,12 @@ public int GetNextCaretPosition(int visualColumn, LogicalDirection direction, Ca
}
// 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;
Expand Down
Expand Up @@ -362,5 +362,24 @@ public virtual string GetIndentationString(int column)
}
}
}

bool enableVirtualSpace;

/// <summary>
/// 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.
/// </summary>
[DefaultValue(false)]
public virtual bool EnableVirtualSpace {
get { return enableVirtualSpace; }
set {
if (enableVirtualSpace != value) {
enableVirtualSpace = value;
OnPropertyChanged("EnableVirtualSpace");
}
}
}
}
}

0 comments on commit 600ba51

Please sign in to comment.