finished implementation of virtual space

This commit is contained in:
Siegfried Pammer 2011-11-20 22:22:01 +01:00
Родитель 3b5da7ab5f
Коммит 387d37066e
18 изменённых файлов: 240 добавлений и 231 удалений

Просмотреть файл

@ -281,14 +281,14 @@ namespace ICSharpCode.AvalonEdit.Editing
int caretOffset = textView.Document.GetOffset(position);
int firstDocumentLineOffset = visualLine.FirstDocumentLine.Offset;
position.VisualColumn = visualLine.ValidateVisualColumn(position, textArea.Options.EnableVirtualSpace);
position.VisualColumn = visualLine.ValidateVisualColumn(position, textArea.Selection.EnableVirtualSpace);
// search possible caret positions
int newVisualColumnForwards = visualLine.GetNextCaretPosition(position.VisualColumn - 1, LogicalDirection.Forward, CaretPositioningMode.Normal);
int newVisualColumnForwards = visualLine.GetNextCaretPosition(position.VisualColumn - 1, LogicalDirection.Forward, CaretPositioningMode.Normal, textArea.Selection.EnableVirtualSpace);
// If position.VisualColumn was valid, we're done with validation.
if (newVisualColumnForwards != position.VisualColumn) {
// also search backwards so that we can pick the better match
int newVisualColumnBackwards = visualLine.GetNextCaretPosition(position.VisualColumn + 1, LogicalDirection.Backward, CaretPositioningMode.Normal);
int newVisualColumnBackwards = visualLine.GetNextCaretPosition(position.VisualColumn + 1, LogicalDirection.Backward, CaretPositioningMode.Normal, textArea.Selection.EnableVirtualSpace);
if (newVisualColumnForwards < 0 && newVisualColumnBackwards < 0)
throw ThrowUtil.NoValidCaretPosition();

Просмотреть файл

@ -180,7 +180,7 @@ namespace ICSharpCode.AvalonEdit.Editing
#region Home/End
static void MoveCaretToStartOfLine(TextArea textArea, VisualLine visualLine)
{
int newVC = visualLine.GetNextCaretPosition(-1, LogicalDirection.Forward, CaretPositioningMode.WordStart);
int newVC = visualLine.GetNextCaretPosition(-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
@ -201,7 +201,7 @@ namespace ICSharpCode.AvalonEdit.Editing
#region By-character / By-word movement
static void MoveCaretRight(TextArea textArea, TextViewPosition caretPosition, VisualLine visualLine, CaretPositioningMode mode)
{
int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Forward, mode);
int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Forward, mode, textArea.Selection.EnableVirtualSpace);
if (pos >= 0) {
SetCaretPosition(textArea, pos, visualLine.GetRelativeOffset(pos) + visualLine.FirstDocumentLine.Offset);
} else {
@ -209,7 +209,7 @@ namespace ICSharpCode.AvalonEdit.Editing
DocumentLine nextDocumentLine = visualLine.LastDocumentLine.NextLine;
if (nextDocumentLine != null) {
VisualLine nextLine = textArea.TextView.GetOrConstructVisualLine(nextDocumentLine);
pos = nextLine.GetNextCaretPosition(-1, LogicalDirection.Forward, mode);
pos = nextLine.GetNextCaretPosition(-1, LogicalDirection.Forward, mode, textArea.Selection.EnableVirtualSpace);
if (pos < 0)
throw ThrowUtil.NoValidCaretPosition();
SetCaretPosition(textArea, pos, nextLine.GetRelativeOffset(pos) + nextLine.FirstDocumentLine.Offset);
@ -223,7 +223,7 @@ namespace ICSharpCode.AvalonEdit.Editing
static void MoveCaretLeft(TextArea textArea, TextViewPosition caretPosition, VisualLine visualLine, CaretPositioningMode mode)
{
int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Backward, mode);
int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Backward, mode, textArea.Selection.EnableVirtualSpace);
if (pos >= 0) {
SetCaretPosition(textArea, pos, visualLine.GetRelativeOffset(pos) + visualLine.FirstDocumentLine.Offset);
} else {
@ -231,7 +231,7 @@ namespace ICSharpCode.AvalonEdit.Editing
DocumentLine previousDocumentLine = visualLine.FirstDocumentLine.PreviousLine;
if (previousDocumentLine != null) {
VisualLine previousLine = textArea.TextView.GetOrConstructVisualLine(previousDocumentLine);
pos = previousLine.GetNextCaretPosition(previousLine.VisualLength + 1, LogicalDirection.Backward, mode);
pos = previousLine.GetNextCaretPosition(previousLine.VisualLength + 1, LogicalDirection.Backward, mode, textArea.Selection.EnableVirtualSpace);
if (pos < 0)
throw ThrowUtil.NoValidCaretPosition();
SetCaretPosition(textArea, pos, previousLine.GetRelativeOffset(pos) + previousLine.FirstDocumentLine.Offset);
@ -307,7 +307,7 @@ namespace ICSharpCode.AvalonEdit.Editing
}
if (targetLine != null) {
double yPos = targetVisualLine.GetTextLineVisualYPosition(targetLine, VisualYPosition.LineMiddle);
int newVisualColumn = targetVisualLine.GetVisualColumn(new Point(xPos, yPos));
int newVisualColumn = targetVisualLine.GetVisualColumn(new Point(xPos, yPos), textArea.Selection.EnableVirtualSpace);
SetCaretPosition(textArea, targetVisualLine, targetLine, newVisualColumn, false);
textArea.Caret.DesiredXPos = xPos;
}

Просмотреть файл

@ -398,7 +398,7 @@ namespace ICSharpCode.AvalonEdit.Editing
if (textArea.ReadOnlySectionProvider.CanInsert(currentLine.Offset)) {
textArea.Document.Insert(currentLine.Offset, text);
}
} else if (rectangular && textArea.Selection.IsEmpty) {
} else if (rectangular && textArea.Selection.IsEmpty && !(textArea.Selection is RectangleSelection)) {
if (!RectangleSelection.PerformRectangularPaste(textArea, textArea.Caret.Position, text, false))
textArea.ReplaceSelectionWithText(text);
} else {

Просмотреть файл

@ -8,6 +8,7 @@ using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media.TextFormatting;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Utils;
@ -42,8 +43,8 @@ namespace ICSharpCode.AvalonEdit.Editing
InitDocument();
this.startLine = start.Line;
this.endLine = end.Line;
this.startXPos = GetXPos(start);
this.endXPos = GetXPos(end);
this.startXPos = GetXPos(textArea, start);
this.endXPos = GetXPos(textArea, end);
this.startOffset = document.GetOffset(start);
this.endOffset = document.GetOffset(end);
CalculateSegments();
@ -56,7 +57,7 @@ namespace ICSharpCode.AvalonEdit.Editing
this.startLine = startLine;
this.endLine = end.Line;
this.startXPos = startXPos;
this.endXPos = GetXPos(end);
this.endXPos = GetXPos(textArea, end);
this.startOffset = startOffset;
this.endOffset = document.GetOffset(end);
CalculateSegments();
@ -68,19 +69,26 @@ namespace ICSharpCode.AvalonEdit.Editing
InitDocument();
this.startLine = start.Line;
this.endLine = endLine;
this.startXPos = GetXPos(start);
this.startXPos = GetXPos(textArea, start);
this.endXPos = endXPos;
this.startOffset = document.GetOffset(start);
this.endOffset = endOffset;
CalculateSegments();
}
double GetXPos(TextViewPosition pos)
static double GetXPos(TextArea textArea, TextViewPosition pos)
{
DocumentLine documentLine = document.GetLineByNumber(pos.Line);
DocumentLine documentLine = textArea.Document.GetLineByNumber(pos.Line);
VisualLine visualLine = textArea.TextView.GetOrConstructVisualLine(documentLine);
TextLine textLine = visualLine.GetTextLine(pos.VisualColumn);
return visualLine.GetTextLineVisualXPosition(textLine, pos.VisualColumn);
int vc = visualLine.ValidateVisualColumn(pos, true);
TextLine textLine = visualLine.GetTextLine(vc);
return visualLine.GetTextLineVisualXPosition(textLine, vc);
}
int GetVisualColumnFromXPos(int line, double xPos)
{
var vl = textArea.TextView.GetOrConstructVisualLine(textArea.Document.GetLineByNumber(line));
return vl.GetVisualColumn(new Point(xPos, 0), true);
}
/// <inheritdoc/>
@ -108,6 +116,11 @@ namespace ICSharpCode.AvalonEdit.Editing
}
}
/// <inheritdoc/>
public override bool EnableVirtualSpace {
get { return true; }
}
/// <inheritdoc/>
public override ISegment SurroundingSegment {
get {
@ -120,6 +133,7 @@ namespace ICSharpCode.AvalonEdit.Editing
get { return segments; }
}
void CalculateSegments()
{
DocumentLine nextLine = document.GetLineByNumber(Math.Min(startLine, endLine));
@ -131,10 +145,10 @@ namespace ICSharpCode.AvalonEdit.Editing
int baseOffset = vl.FirstDocumentLine.Offset;
int startOffset = baseOffset + vl.GetRelativeOffset(startVC);
int endOffset = baseOffset + vl.GetRelativeOffset(endVC);
segments.Add(new SelectionSegment(startOffset, startVC, endOffset, endVC, true));
segments.Add(new SelectionSegment(startOffset, startVC, endOffset, endVC));
nextLine = vl.LastDocumentLine.NextLine;
} while (nextLine.LineNumber <= Math.Max(startLine, endLine));
} while (nextLine != null && nextLine.LineNumber <= Math.Max(startLine, endLine));
}
/// <inheritdoc/>
@ -156,97 +170,81 @@ namespace ICSharpCode.AvalonEdit.Editing
/// <inheritdoc/>
public override Selection SetEndpoint(TextViewPosition endPosition)
{
return new RectangleSelection(textArea, startLine, startXPos, startOffset, endPosition);
var r = new RectangleSelection(textArea, startLine, startXPos, startOffset, endPosition);
return r;
}
/// <inheritdoc/>
public override Selection UpdateOnDocumentChange(DocumentChangeEventArgs e)
{
throw new NotImplementedException();
// return new RectangleSelection(textArea,
// e.GetNewOffset(StartOffset, AnchorMovementType.AfterInsertion),
// e.GetNewOffset(EndOffset, AnchorMovementType.BeforeInsertion));
TextLocation newStartLocation = textArea.Document.GetLocation(e.GetNewOffset(startOffset, AnchorMovementType.AfterInsertion));
TextLocation newEndLocation = textArea.Document.GetLocation(e.GetNewOffset(endOffset, AnchorMovementType.BeforeInsertion));
return new RectangleSelection(textArea,
new TextViewPosition(newStartLocation, GetVisualColumnFromXPos(newStartLocation.Line, startXPos)),
new TextViewPosition(newEndLocation, GetVisualColumnFromXPos(newEndLocation.Line, endXPos)));
}
/// <inheritdoc/>
public override void ReplaceSelectionWithText(string newText)
{
throw new NotImplementedException(); /*
if (newText == null)
throw new ArgumentNullException("newText");
using (textArea.Document.RunUpdate()) {
TextLocation start = document.GetLocation(StartOffset);
TextLocation end = document.GetLocation(EndOffset);
int editColumn = Math.Min(start.Column, end.Column);
TextViewPosition start = new TextViewPosition(document.GetLocation(startOffset), GetVisualColumnFromXPos(startLine, startXPos));
TextViewPosition end = new TextViewPosition(document.GetLocation(endOffset), GetVisualColumnFromXPos(endLine, endXPos));
int insertionLength;
int totalInsertionLength = 0;
int firstInsertionLength = 0;
int editOffset = Math.Min(startOffset, endOffset);
if (NewLineFinder.NextNewLine(newText, 0) == SimpleSegment.Invalid) {
// insert same text into every line
foreach (ISegment lineSegment in this.Segments.Reverse()) {
ReplaceSingleLineText(textArea, lineSegment, newText);
foreach (SelectionSegment lineSegment in this.Segments.Reverse()) {
ReplaceSingleLineText(textArea, lineSegment, newText, out insertionLength);
totalInsertionLength += insertionLength;
firstInsertionLength = insertionLength;
}
TextLocation newStart = new TextLocation(start.Line, editColumn + newText.Length);
TextLocation newEnd = new TextLocation(end.Line, editColumn + newText.Length);
textArea.Caret.Location = newEnd;
textArea.Selection = new RectangleSelection(document, document.GetOffset(newStart), document.GetOffset(newEnd));
int newEndOffset = editOffset + totalInsertionLength;
TextViewPosition pos = new TextViewPosition(document.GetLocation(editOffset + firstInsertionLength));
textArea.Selection = new RectangleSelection(textArea, pos, Math.Max(startLine, endLine), GetXPos(textArea, pos), newEndOffset);
textArea.Caret.Position = textArea.TextView.GetPosition(new Point(GetXPos(textArea, pos), textArea.TextView.GetVisualTopByDocumentLine(Math.Max(startLine, endLine)))).GetValueOrDefault();
} else {
// convert all segment start/ends to anchors
var segments = this.Segments.Select(s => new AnchorSegment(this.document, s)).ToList();
SimpleSegment ds = NewLineFinder.NextNewLine(newText, 0);
// we'll check whether all lines have the same length. If so, we can continue using a rectangular selection.
int commonLength = -1;
// now insert lines into rectangular selection
int lastDelimiterEnd = 0;
bool isAtEnd = false;
int i;
for (i = 0; i < segments.Count; i++) {
string lineText;
if (ds == SimpleSegment.Invalid || (i == segments.Count - 1)) {
lineText = newText.Substring(lastDelimiterEnd);
isAtEnd = true;
// if we have more lines to insert than this selection is long, we cannot continue using a rectangular selection
if (ds != SimpleSegment.Invalid)
commonLength = -1;
} else {
lineText = newText.Substring(lastDelimiterEnd, ds.Offset - lastDelimiterEnd);
}
if (i == 0) {
commonLength = lineText.Length;
} else if (commonLength != lineText.Length) {
commonLength = -1;
}
ReplaceSingleLineText(textArea, segments[i], lineText);
if (isAtEnd)
break;
lastDelimiterEnd = ds.EndOffset;
ds = NewLineFinder.NextNewLine(newText, lastDelimiterEnd);
}
if (commonLength >= 0) {
TextLocation newStart = new TextLocation(start.Line, editColumn + commonLength);
TextLocation newEnd = new TextLocation(start.Line + i, editColumn + commonLength);
textArea.Selection = new RectangleSelection(document, document.GetOffset(newStart), document.GetOffset(newEnd));
} else {
textArea.Selection = Selection.Empty;
string[] lines = newText.Split(new[] { "\r\n", "\r", "\n" }, segments.Count, StringSplitOptions.None);
int line = Math.Min(startLine, endLine);
for (int i = lines.Length - 1; i >= 0; i--) {
ReplaceSingleLineText(textArea, segments[i], lines[i], out insertionLength);
firstInsertionLength = insertionLength;
}
TextViewPosition pos = new TextViewPosition(document.GetLocation(editOffset + firstInsertionLength));
textArea.ClearSelection();
textArea.Caret.Position = textArea.TextView.GetPosition(new Point(GetXPos(textArea, pos), textArea.TextView.GetVisualTopByDocumentLine(Math.Max(startLine, endLine)))).GetValueOrDefault();
}
}*/
}
}
static void ReplaceSingleLineText(TextArea textArea, ISegment lineSegment, string newText)
void ReplaceSingleLineText(TextArea textArea, SelectionSegment lineSegment, string newText, out int insertionLength)
{
if (lineSegment.Length == 0) {
if (newText.Length > 0 && textArea.ReadOnlySectionProvider.CanInsert(lineSegment.Offset)) {
textArea.Document.Insert(lineSegment.Offset, newText);
if (newText.Length > 0 && textArea.ReadOnlySectionProvider.CanInsert(lineSegment.StartOffset)) {
newText = AddSpacesIfRequired(newText, new TextViewPosition(document.GetLocation(lineSegment.StartOffset), lineSegment.StartVisualColumn));
textArea.Document.Insert(lineSegment.StartOffset, newText);
}
} else {
ISegment[] segmentsToDelete = textArea.GetDeletableSegments(lineSegment);
for (int i = segmentsToDelete.Length - 1; i >= 0; i--) {
if (i == segmentsToDelete.Length - 1) {
if (segmentsToDelete[i].Offset == SurroundingSegment.Offset && segmentsToDelete[i].Length == SurroundingSegment.Length) {
newText = AddSpacesIfRequired(newText, new TextViewPosition(document.GetLocation(lineSegment.StartOffset), lineSegment.StartVisualColumn));
}
textArea.Document.Replace(segmentsToDelete[i], newText);
} else {
textArea.Document.Remove(segmentsToDelete[i]);
}
}
}
insertionLength = newText.Length;
}
/// <summary>
@ -258,12 +256,12 @@ namespace ICSharpCode.AvalonEdit.Editing
throw new ArgumentNullException("textArea");
if (text == null)
throw new ArgumentNullException("text");
int newLineCount = text.Count(c => c == '\n');
int newLineCount = text.Count(c => c == '\n'); // TODO might not work in all cases, but single \r line endings are really rare today.
TextLocation endLocation = new TextLocation(startPosition.Line + newLineCount, startPosition.Column);
if (endLocation.Line <= textArea.Document.LineCount) {
int endOffset = textArea.Document.GetOffset(endLocation);
if (textArea.Document.GetLocation(endOffset) == endLocation) {
RectangleSelection rsel = new RectangleSelection(textArea, startPosition, new TextViewPosition(endLocation));
if (textArea.Selection.EnableVirtualSpace || textArea.Document.GetLocation(endOffset) == endLocation) {
RectangleSelection rsel = new RectangleSelection(textArea, startPosition, endLocation.Line, GetXPos(textArea, startPosition), endOffset);
rsel.ReplaceSelectionWithText(text);
if (selectInsertedText && textArea.Selection is RectangleSelection) {
RectangleSelection sel = (RectangleSelection)textArea.Selection;
@ -296,7 +294,7 @@ namespace ICSharpCode.AvalonEdit.Editing
{
// It's possible that ToString() gets called on old (invalid) selections, e.g. for "change from... to..." debug message
// make sure we don't crash even when the desired locations don't exist anymore.
return "[RectangleSelection " + startOffset + " to " + endOffset + "]";
return string.Format("[RectangleSelection {0} {1} {2} to {3} {4} {5}]", startLine, startOffset, startXPos, endLine, endOffset, endXPos);
}
}
}

Просмотреть файл

@ -81,7 +81,7 @@ namespace ICSharpCode.AvalonEdit.Editing
internal string AddSpacesIfRequired(string newText, TextViewPosition pos)
{
if (textArea.Options.EnableVirtualSpace && !string.IsNullOrEmpty(newText)) {
if (EnableVirtualSpace && !string.IsNullOrEmpty(newText) && newText != "\r\n" && newText != "\n" && newText != "\r") {
var line = textArea.Document.GetLineByNumber(pos.Line);
string lineText = textArea.Document.GetText(line);
var vLine = textArea.TextView.GetOrConstructVisualLine(line);
@ -112,6 +112,10 @@ namespace ICSharpCode.AvalonEdit.Editing
get { return Length == 0; }
}
public virtual bool EnableVirtualSpace {
get { return textArea.Options.EnableVirtualSpace; }
}
/// <summary>
/// Gets the selection length.
/// </summary>

Просмотреть файл

@ -28,19 +28,24 @@ namespace ICSharpCode.AvalonEdit.Editing
int lineStartOffset = context.VisualLine.FirstDocumentLine.Offset;
int lineEndOffset = context.VisualLine.LastDocumentLine.Offset + context.VisualLine.LastDocumentLine.TotalLength;
foreach (var segment in textArea.Selection.Segments) {
foreach (SelectionSegment segment in textArea.Selection.Segments) {
int segmentStart = segment.StartOffset;
int segmentEnd = segment.EndOffset;
if (segmentEnd <= lineStartOffset)
continue;
if (segmentStart >= lineEndOffset)
continue;
int startColumn = segment.StartVisualColumn;
int endColumn = segment.EndVisualColumn;
if (startColumn < 0)
startColumn = context.VisualLine.GetVisualColumn(Math.Max(0, segmentStart - lineStartOffset));
if (endColumn < 0)
endColumn = context.VisualLine.GetVisualColumn(segmentEnd - lineStartOffset);
int startColumn;
if (segmentStart < lineStartOffset)
startColumn = 0;
else
startColumn = context.VisualLine.ValidateVisualColumn(segment.StartOffset, segment.StartVisualColumn, textArea.Selection.EnableVirtualSpace);
int endColumn;
if (segmentEnd > lineEndOffset)
endColumn = textArea.Selection.EnableVirtualSpace ? int.MaxValue : context.VisualLine.VisualLengthWithEndOfLineMarker;
else
endColumn = context.VisualLine.ValidateVisualColumn(segment.EndOffset, segment.EndVisualColumn, textArea.Selection.EnableVirtualSpace);
ChangeVisualElements(
startColumn, endColumn,

Просмотреть файл

@ -39,7 +39,7 @@ namespace ICSharpCode.AvalonEdit.Editing
BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder();
geoBuilder.AlignToMiddleOfPixels = true;
geoBuilder.ExtendToFullWidthAtLineEnd = textArea.Options.EnableVirtualSpace;
geoBuilder.ExtendToFullWidthAtLineEnd = textArea.Selection.EnableVirtualSpace;
geoBuilder.CornerRadius = textArea.SelectionCornerRadius;
foreach (var segment in textArea.Selection.Segments) {
geoBuilder.AddSegment(textView, segment);

Просмотреть файл

@ -9,8 +9,8 @@ using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media.TextFormatting;
using System.Windows.Threading;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Utils;
@ -454,11 +454,11 @@ namespace ICSharpCode.AvalonEdit.Editing
pos += textView.ScrollOffset;
VisualLine line = textView.GetVisualLineFromVisualTop(pos.Y);
if (line != null) {
int visualColumn = line.GetVisualColumn(pos);
int wordStartVC = line.GetNextCaretPosition(visualColumn + 1, LogicalDirection.Backward, CaretPositioningMode.WordStartOrSymbol);
int visualColumn = line.GetVisualColumn(pos, textArea.Selection.EnableVirtualSpace);
int wordStartVC = line.GetNextCaretPosition(visualColumn + 1, LogicalDirection.Backward, CaretPositioningMode.WordStartOrSymbol, textArea.Selection.EnableVirtualSpace);
if (wordStartVC == -1)
wordStartVC = 0;
int wordEndVC = line.GetNextCaretPosition(wordStartVC, LogicalDirection.Forward, CaretPositioningMode.WordBorderOrSymbol);
int wordEndVC = line.GetNextCaretPosition(wordStartVC, LogicalDirection.Forward, CaretPositioningMode.WordBorderOrSymbol, textArea.Selection.EnableVirtualSpace);
if (wordEndVC == -1)
wordEndVC = line.VisualLength;
int relOffset = line.FirstDocumentLine.Offset;
@ -507,7 +507,27 @@ namespace ICSharpCode.AvalonEdit.Editing
pos.Y = textView.DocumentHeight - ExtensionMethods.Epsilon;
VisualLine line = textView.GetVisualLineFromVisualTop(pos.Y);
if (line != null) {
visualColumn = line.GetVisualColumn(pos);
visualColumn = line.GetVisualColumn(pos, textArea.Selection.EnableVirtualSpace);
return line.GetRelativeOffset(visualColumn) + line.FirstDocumentLine.Offset;
}
return -1;
}
int GetOffsetFromMousePositionFirstTextLineOnly(Point positionRelativeToTextView, out int visualColumn)
{
visualColumn = 0;
TextView textView = textArea.TextView;
Point pos = positionRelativeToTextView;
if (pos.Y < 0)
pos.Y = 0;
if (pos.Y > textView.ActualHeight)
pos.Y = textView.ActualHeight;
pos += textView.ScrollOffset;
if (pos.Y > textView.DocumentHeight)
pos.Y = textView.DocumentHeight - ExtensionMethods.Epsilon;
VisualLine line = textView.GetVisualLineFromVisualTop(pos.Y);
if (line != null) {
visualColumn = line.GetVisualColumn(line.TextLines.First(), positionRelativeToTextView.X, textArea.Selection.EnableVirtualSpace);
return line.GetRelativeOffset(visualColumn) + line.FirstDocumentLine.Offset;
}
return -1;
@ -548,7 +568,11 @@ namespace ICSharpCode.AvalonEdit.Editing
void SetCaretOffsetToMousePosition(MouseEventArgs e, ISegment allowedSegment)
{
int visualColumn;
int offset = GetOffsetFromMousePosition(e, out visualColumn);
int offset;
if (mode == SelectionMode.Rectangular)
offset = GetOffsetFromMousePositionFirstTextLineOnly(e.GetPosition(textArea.TextView), out visualColumn);
else
offset = GetOffsetFromMousePosition(e, out visualColumn);
if (allowedSegment != null) {
offset = offset.CoerceValue(allowedSegment.Offset, allowedSegment.EndOffset);
}

Просмотреть файл

@ -14,8 +14,9 @@ namespace ICSharpCode.AvalonEdit.Editing
readonly int startOffset, endOffset;
readonly int startVC, endVC;
public bool AllowVirtualSpace { get; private set; }
/// <summary>
/// Creates a SelectionSegment from two offsets.
/// </summary>
public SelectionSegment(int startOffset, int endOffset)
{
this.startOffset = Math.Min(startOffset, endOffset);
@ -23,9 +24,12 @@ namespace ICSharpCode.AvalonEdit.Editing
this.startVC = this.endVC = -1;
}
public SelectionSegment(int startOffset, int startVC, int endOffset, int endVC, bool allowVirtualSpace)
/// <summary>
/// Creates a SelectionSegment from two offsets and visual columns.
/// </summary>
public SelectionSegment(int startOffset, int startVC, int endOffset, int endVC)
{
if (startOffset <= endOffset) {
if (startOffset < endOffset || (startOffset == endOffset && startVC <= endVC)) {
this.startOffset = startOffset;
this.startVC = startVC;
this.endOffset = endOffset;
@ -36,25 +40,37 @@ namespace ICSharpCode.AvalonEdit.Editing
this.endOffset = startOffset;
this.endVC = startVC;
}
this.AllowVirtualSpace = allowVirtualSpace;
}
/// <summary>
/// Gets the start offset.
/// </summary>
public int StartOffset {
get { return startOffset; }
}
/// <summary>
/// Gets the end offset.
/// </summary>
public int EndOffset {
get { return endOffset; }
}
/// <summary>
/// Gets the start visual column.
/// </summary>
public int StartVisualColumn {
get { return startVC; }
}
/// <summary>
/// Gets the end visual column.
/// </summary>
public int EndVisualColumn {
get { return endVC; }
}
/// <inheritdoc/>
int ISegment.Offset {
get { return startOffset; }
}
@ -64,6 +80,7 @@ namespace ICSharpCode.AvalonEdit.Editing
get { return endOffset - startOffset; }
}
/// <inheritdoc/>
public override string ToString()
{
return string.Format("[SelectionSegment StartOffset={0}, EndOffset={1}, StartVC={2}, EndVC={3}]", startOffset, endOffset, startVC, endVC);

Просмотреть файл

@ -33,7 +33,7 @@ namespace ICSharpCode.AvalonEdit.Editing
/// <inheritdoc/>
public override IEnumerable<SelectionSegment> Segments {
get {
return ExtensionMethods.Sequence<SelectionSegment>(new SelectionSegment(startOffset, start.VisualColumn, endOffset, end.VisualColumn, textArea.Options.EnableVirtualSpace));
return ExtensionMethods.Sequence<SelectionSegment>(new SelectionSegment(startOffset, start.VisualColumn, endOffset, end.VisualColumn));
}
}

Просмотреть файл

@ -378,6 +378,7 @@ namespace ICSharpCode.AvalonEdit.Editing
/// <summary>
/// Gets/Sets the selection in this text area.
/// </summary>
public Selection Selection {
get { return selection; }
set {
@ -390,7 +391,7 @@ namespace ICSharpCode.AvalonEdit.Editing
if (textView != null) {
ISegment oldSegment = selection.SurroundingSegment;
ISegment newSegment = value.SurroundingSegment;
if (!Options.EnableVirtualSpace && (selection is SimpleSelection && value is SimpleSelection && oldSegment != null && newSegment != null)) {
if (!Selection.EnableVirtualSpace && (selection is SimpleSelection && value is SimpleSelection && oldSegment != null && newSegment != null)) {
// perf optimization:
// When a simple selection changes, don't redraw the whole selection, but only the changed parts.
int oldSegmentOffset = oldSegment.Offset;
@ -870,19 +871,19 @@ namespace ICSharpCode.AvalonEdit.Editing
}
}
internal void RemoveSelectedText()
{
if (this.Document == null)
throw ThrowUtil.NoDocumentAssigned();
selection.ReplaceSelectionWithText(string.Empty);
#if DEBUG
if (!selection.IsEmpty) {
foreach (ISegment s in selection.Segments) {
Debug.Assert(this.ReadOnlySectionProvider.GetDeletableSegments(s).Count() == 0);
}
}
#endif
internal void RemoveSelectedText()
{
if (this.Document == null)
throw ThrowUtil.NoDocumentAssigned();
selection.ReplaceSelectionWithText(string.Empty);
#if DEBUG
if (!selection.IsEmpty) {
foreach (ISegment s in selection.Segments) {
Debug.Assert(this.ReadOnlySectionProvider.GetDeletableSegments(s).Count() == 0);
}
}
#endif
}
internal void ReplaceSelectionWithText(string newText)
{

Просмотреть файл

@ -262,7 +262,6 @@
</Compile>
<Compile Include="Rendering\LinkElementGenerator.cs" />
<Compile Include="Rendering\MouseHoverLogic.cs" />
<Compile Include="Rendering\NewLineElementGenerator.cs" />
<Compile Include="Rendering\SimpleTextSource.cs">
<DependentUpon>FormattedTextElement.cs</DependentUpon>
</Compile>
@ -340,7 +339,9 @@
</Compile>
<Compile Include="TextViewPosition.cs" />
<Compile Include="Utils\Boxes.cs" />
<Compile Include="Utils\BusyManager.cs" />
<Compile Include="Utils\BusyManager.cs">
<DependentUpon>ObserveAddRemoveCollection.cs</DependentUpon>
</Compile>
<Compile Include="Utils\CharRope.cs" />
<Compile Include="Utils\CompressingTreeList.cs" />
<Compile Include="Utils\Constants.cs" />

Просмотреть файл

@ -124,7 +124,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
int segmentEndVC;
if (segmentEnd > vlEndOffset)
segmentEndVC = extendToFullWidthAtLineEnd ? int.MaxValue : vl.VisualLength;
segmentEndVC = extendToFullWidthAtLineEnd ? int.MaxValue : vl.VisualLengthWithEndOfLineMarker;
else
segmentEndVC = vl.ValidateVisualColumn(end, extendToFullWidthAtLineEnd);
@ -170,8 +170,8 @@ namespace ICSharpCode.AvalonEdit.Rendering
lastRect = new Rect(Math.Min(left, right), y, Math.Abs(right - left), line.Height);
}
}
if (segmentEndVC >= vl.VisualLength) {
double left = (segmentStartVC > vl.VisualLength ? vl.GetTextLineVisualXPosition(lastTextLine, segmentStartVC) : line.Width) - scrollOffset.X;
if (segmentEndVC >= vl.VisualLengthWithEndOfLineMarker) {
double left = (segmentStartVC > vl.VisualLengthWithEndOfLineMarker ? vl.GetTextLineVisualXPosition(lastTextLine, segmentStartVC) : line.Width) - scrollOffset.X;
double right = ((segmentEndVC == int.MaxValue || line != lastTextLine) ? Math.Max(((IScrollInfo)textView).ExtentWidth, ((IScrollInfo)textView).ViewportWidth) : vl.GetTextLineVisualXPosition(lastTextLine, segmentEndVC)) - scrollOffset.X;
Rect extendSelection = new Rect(Math.Min(left, right), y, Math.Abs(right - left), line.Height);
if (!lastRect.IsEmpty) {

Просмотреть файл

@ -1,88 +0,0 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.TextFormatting;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.Rendering
{
// This class is internal because it does not need to be accessed by the user - it can be configured using TextEditorOptions.
/// <summary>
/// Elements generator that displays "¶" at the end of lines.
/// </summary>
/// <remarks>
/// This element generator can be easily enabled and configured using the
/// <see cref="TextEditorOptions"/>.
/// </remarks>
sealed class NewLineElementGenerator : VisualLineElementGenerator, IBuiltinElementGenerator
{
void IBuiltinElementGenerator.FetchOptions(TextEditorOptions options)
{
}
public override int GetFirstInterestedOffset(int startOffset)
{
DocumentLine lastDocumentLine = CurrentContext.VisualLine.LastDocumentLine;
if (lastDocumentLine.DelimiterLength > 0)
return lastDocumentLine.Offset + lastDocumentLine.Length;
else
return -1;
}
public override VisualLineElement ConstructElement(int offset)
{
string newlineText;
DocumentLine lastDocumentLine = CurrentContext.VisualLine.LastDocumentLine;
if (lastDocumentLine.DelimiterLength == 2) {
newlineText = "\u00B6";
} else if (lastDocumentLine.DelimiterLength == 1) {
char newlineChar = CurrentContext.Document.GetCharAt(lastDocumentLine.Offset + lastDocumentLine.Length);
if (newlineChar == '\r')
newlineText = "\\r";
else if (newlineChar == '\n')
newlineText = "\\n";
else
newlineText = "?";
} else {
return null;
}
return new NewLineTextElement(CurrentContext.TextView.cachedElements.GetTextForNonPrintableCharacter(newlineText, CurrentContext));
}
sealed class NewLineTextElement : FormattedTextElement
{
public NewLineTextElement(TextLine text) : base(text, 0)
{
BreakBefore = LineBreakCondition.BreakPossible;
BreakAfter = LineBreakCondition.BreakRestrained;
}
public override int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode)
{
// only place a caret stop before the newline, no caret stop after it
if (visualColumn > this.VisualColumn && direction == LogicalDirection.Backward ||
visualColumn < this.VisualColumn && direction == LogicalDirection.Forward)
{
return this.VisualColumn;
} else {
return -1;
}
}
public override bool IsWhitespace(int visualColumn)
{
return true;
}
public override bool HandlesLineBorders {
get { return true; }
}
}
}
}

Просмотреть файл

@ -263,7 +263,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
#endregion
#region Builtin ElementGenerators
NewLineElementGenerator newLineElementGenerator;
// NewLineElementGenerator newLineElementGenerator;
SingleCharacterElementGenerator singleCharacterElementGenerator;
LinkElementGenerator linkElementGenerator;
MailLinkElementGenerator mailLinkElementGenerator;
@ -272,7 +272,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
{
TextEditorOptions options = this.Options;
AddRemoveDefaultElementGeneratorOnDemand(ref newLineElementGenerator, options.ShowEndOfLine);
// AddRemoveDefaultElementGeneratorOnDemand(ref newLineElementGenerator, options.ShowEndOfLine);
AddRemoveDefaultElementGeneratorOnDemand(ref singleCharacterElementGenerator, options.ShowBoxForControlCharacters || options.ShowSpaces || options.ShowTabs);
AddRemoveDefaultElementGeneratorOnDemand(ref linkElementGenerator, options.EnableHyperlinks);
AddRemoveDefaultElementGeneratorOnDemand(ref mailLinkElementGenerator, options.EnableEmailHyperlinks);
@ -1013,7 +1013,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
var textLines = new List<TextLine>();
paragraphProperties.indent = 0;
paragraphProperties.firstLineInParagraph = true;
while (textOffset <= visualLine.VisualLength) {
while (textOffset <= visualLine.VisualLengthWithEndOfLineMarker) {
TextLine textLine = formatter.FormatLine(
textSource,
textOffset,
@ -1025,7 +1025,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
textOffset += textLine.Length;
// exit loop so that we don't do the indentation calculation if there's only a single line
if (textOffset >= visualLine.VisualLength)
if (textOffset >= visualLine.VisualLengthWithEndOfLineMarker)
break;
if (paragraphProperties.firstLineInParagraph) {

Просмотреть файл

@ -65,6 +65,14 @@ namespace ICSharpCode.AvalonEdit.Rendering
/// </summary>
public int VisualLength { get; private set; }
public int VisualLengthWithEndOfLineMarker {
get {
int length = VisualLength;
if (textView.Options.ShowEndOfLine && LastDocumentLine.NextLine != null) length++;
return length;
}
}
/// <summary>
/// Gets the height of the visual line in device-independent pixels.
/// </summary>
@ -226,7 +234,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
{
if (visualColumn < 0)
throw new ArgumentOutOfRangeException("visualColumn");
if (visualColumn >= VisualLength)
if (visualColumn >= VisualLengthWithEndOfLineMarker)
return TextLines[TextLines.Count - 1];
foreach (TextLine line in TextLines) {
if (visualColumn < line.Length)
@ -322,9 +330,9 @@ namespace ICSharpCode.AvalonEdit.Rendering
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;
new CharacterHit(Math.Min(visualColumn, VisualLengthWithEndOfLineMarker), 0));
if (visualColumn > VisualLengthWithEndOfLineMarker) {
xPos += (visualColumn - VisualLengthWithEndOfLineMarker) * textView.WideSpaceWidth;
}
return xPos;
}
@ -340,35 +348,43 @@ namespace ICSharpCode.AvalonEdit.Rendering
public int GetVisualColumn(Point point, bool allowVirtualSpace)
{
TextLine textLine = GetTextLineByVisualYPosition(point.Y);
if (point.X > textLine.WidthIncludingTrailingWhitespace) {
return GetVisualColumn(GetTextLineByVisualYPosition(point.Y), point.X, allowVirtualSpace);
}
public int GetVisualColumn(TextLine textLine, double xPos, bool allowVirtualSpace)
{
if (xPos > textLine.WidthIncludingTrailingWhitespace) {
if (allowVirtualSpace && textLine == TextLines[TextLines.Count - 1]) {
int virtualX = (int)Math.Round((point.X - textLine.WidthIncludingTrailingWhitespace) / textView.WideSpaceWidth);
return VisualLength + virtualX;
int virtualX = (int)Math.Round((xPos - textLine.WidthIncludingTrailingWhitespace) / textView.WideSpaceWidth);
return VisualLengthWithEndOfLineMarker + virtualX;
}
}
CharacterHit ch = textLine.GetCharacterHitFromDistance(point.X);
CharacterHit ch = textLine.GetCharacterHitFromDistance(xPos);
return ch.FirstCharacterIndex + ch.TrailingLength;
}
public int ValidateVisualColumn(TextViewPosition position, bool allowVirtualSpace)
{
int offset = Document.GetOffset(position);
return ValidateVisualColumn(Document.GetOffset(position), position.VisualColumn, allowVirtualSpace);
}
public int ValidateVisualColumn(int offset, int visualColumn, bool allowVirtualSpace)
{
int firstDocumentLineOffset = this.FirstDocumentLine.Offset;
if (position.VisualColumn < 0) {
if (visualColumn < 0) {
return GetVisualColumn(offset - firstDocumentLineOffset);
} else {
int offsetFromVisualColumn = GetRelativeOffset(position.VisualColumn);
int offsetFromVisualColumn = GetRelativeOffset(visualColumn);
offsetFromVisualColumn += firstDocumentLineOffset;
if (offsetFromVisualColumn != offset) {
return GetVisualColumn(offset - firstDocumentLineOffset);
} else {
if (position.VisualColumn > VisualLength && !allowVirtualSpace) {
if (visualColumn > VisualLength && !allowVirtualSpace) {
return VisualLength;
}
}
}
return position.VisualColumn;
return visualColumn;
}
/// <summary>
@ -387,7 +403,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
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;
return VisualLengthWithEndOfLineMarker + 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
@ -407,11 +423,11 @@ namespace ICSharpCode.AvalonEdit.Rendering
/// <summary>
/// Gets the next possible caret position after visualColumn, or -1 if there is no caret position.
/// </summary>
public int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode)
public int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode, bool allowVirtualSpace)
{
if (elements.Count == 0) {
// special handling for empty visual lines:
if (textView.Options.EnableVirtualSpace) {
if (allowVirtualSpace) {
if (direction == LogicalDirection.Forward)
return Math.Max(0, visualColumn + 1);
else if (visualColumn > 0)
@ -436,7 +452,7 @@ 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)) {
if (textView.Options.EnableVirtualSpace)
if (allowVirtualSpace)
return visualColumn - 1;
else
return this.VisualLength;
@ -478,10 +494,10 @@ 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 (!elements[elements.Count-1].HandlesLineBorders && HasImplicitStopAtLineEnd(mode)) {
if ((allowVirtualSpace || !elements[elements.Count-1].HandlesLineBorders) && HasImplicitStopAtLineEnd(mode)) {
if (visualColumn < this.VisualLength)
return this.VisualLength;
else if (textView.Options.EnableVirtualSpace)
else if (allowVirtualSpace)
return visualColumn + 1;
}
}

Просмотреть файл

@ -49,12 +49,33 @@ namespace ICSharpCode.AvalonEdit.Rendering
return run;
}
}
if (TextView.Options.ShowEndOfLine && textSourceCharacterIndex == VisualLine.VisualLength) {
return CreateTextRunForNewLine();
}
return new TextEndOfParagraph(1);
} catch (Exception ex) {
Debug.WriteLine(ex.ToString());
throw;
}
}
TextRun CreateTextRunForNewLine()
{
string newlineText = "";
DocumentLine lastDocumentLine = VisualLine.LastDocumentLine;
if (lastDocumentLine.DelimiterLength == 2) {
newlineText = "¶";
} else if (lastDocumentLine.DelimiterLength == 1) {
char newlineChar = Document.GetCharAt(lastDocumentLine.Offset + lastDocumentLine.Length);
if (newlineChar == '\r')
newlineText = "\\r";
else if (newlineChar == '\n')
newlineText = "\\n";
else
newlineText = "?";
}
return new FormattedTextRun(new FormattedTextElement(TextView.cachedElements.GetTextForNonPrintableCharacter(newlineText, this), 0), GlobalTextRunProperties);
}
public override TextSpan<CultureSpecificCharacterBufferRange> GetPrecedingText(int textSourceCharacterIndexLimit)
{

Просмотреть файл

@ -192,5 +192,15 @@ namespace ICSharpCode.AvalonEdit.Utils
if (f != null && !f.IsFrozen)
Debug.WriteLine("Performance warning: Not frozen: " + f.ToString());
}
[Conditional("DEBUG")]
public static void Log(bool condition, string format, params object[] args)
{
if (condition) {
string output = DateTime.Now.ToString("hh:MM:ss") + ": " + string.Format(format, args) + Environment.NewLine + Environment.StackTrace;
Console.WriteLine(output);
Debug.WriteLine(output);
}
}
}
}