Browse Source

Add TextAnchorMovementMode.Default where the movement direction depends on the type of insertion.

pull/12/head
Daniel Grunwald 15 years ago
parent
commit
93f924b120
  1. 4
      src/AddIns/BackendBindings/CSharpBinding/Project/Src/FormattingStrategy/CSharpFormattingStrategy.cs
  2. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/ISegment.cs
  3. 35
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/OffsetChangeMap.cs
  4. 8
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextAnchor.cs
  5. 21
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextAnchorTree.cs
  6. 8
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs
  7. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs
  8. 13
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs
  9. 4
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/SimpleSelection.cs
  10. 20
      src/Main/Base/Project/Src/Editor/AvalonEdit/AvalonEditDocumentAdapter.cs
  11. 10
      src/Main/Base/Project/Src/Editor/ITextAnchor.cs

4
src/AddIns/BackendBindings/CSharpBinding/Project/Src/FormattingStrategy/CSharpFormattingStrategy.cs

@ -332,7 +332,9 @@ namespace CSharpBinding.FormattingStrategy @@ -332,7 +332,9 @@ namespace CSharpBinding.FormattingStrategy
tag += ">";
}
if (!tag.StartsWith("/")) {
textArea.Document.Insert(textArea.Caret.Offset, "</" + tag.Substring(1));
int caretOffset = textArea.Caret.Offset;
textArea.Document.Insert(caretOffset, "</" + tag.Substring(1));
textArea.Caret.Offset = caretOffset;
}
}
}

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/ISegment.cs

@ -208,7 +208,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -208,7 +208,7 @@ namespace ICSharpCode.AvalonEdit.Document
this.start.MovementType = AnchorMovementType.AfterInsertion;
this.end = document.CreateAnchor(offset + length);
this.end.SurviveDeletion = true;
this.start.MovementType = AnchorMovementType.BeforeInsertion;
this.end.MovementType = AnchorMovementType.BeforeInsertion;
}
/// <inheritdoc/>

35
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/OffsetChangeMap.cs

@ -52,7 +52,12 @@ namespace ICSharpCode.AvalonEdit.Document @@ -52,7 +52,12 @@ namespace ICSharpCode.AvalonEdit.Document
/// causes anchors to always survive irrespective of their <see cref="TextAnchor.SurviveDeletion"/> setting.
/// If the text keeps its old size, this is implemented as OffsetChangeMap.Empty.
/// </remarks>
CharacterReplace
CharacterReplace,
/// <summary>
/// Like 'Normal', but anchors with <see cref="TextAnchor.MovementType"/> = Default will stay in front of the
/// insertion instead of being moved behind it.
/// </summary>
KeepAnchorBeforeInsertion
}
/// <summary>
@ -201,7 +206,9 @@ namespace ICSharpCode.AvalonEdit.Document @@ -201,7 +206,9 @@ namespace ICSharpCode.AvalonEdit.Document
public struct OffsetChangeMapEntry : IEquatable<OffsetChangeMapEntry>
{
readonly int offset;
readonly int insertionLength;
// MSB: DefaultAnchorMovementIsBeforeInsertion
readonly uint insertionLengthWithMovementFlag;
// MSB: RemovalNeverCausesAnchorDeletion; other 31 bits: RemovalLength
readonly uint removalLengthWithDeletionFlag;
@ -218,7 +225,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -218,7 +225,7 @@ namespace ICSharpCode.AvalonEdit.Document
/// Returns 0 if this entry represents a removal.
/// </summary>
public int InsertionLength {
get { return insertionLength; }
get { return (int)(insertionLengthWithMovementFlag & 0x7fffffff); }
}
/// <summary>
@ -236,11 +243,19 @@ namespace ICSharpCode.AvalonEdit.Document @@ -236,11 +243,19 @@ namespace ICSharpCode.AvalonEdit.Document
get { return (removalLengthWithDeletionFlag & 0x80000000) != 0; }
}
/// <summary>
/// Gets whether default anchor movement causes the anchor to stay in front of the caret.
/// </summary>
public bool DefaultAnchorMovementIsBeforeInsertion {
get { return (insertionLengthWithMovementFlag & 0x80000000) != 0; }
}
/// <summary>
/// Gets the new offset where the specified offset moves after this document change.
/// </summary>
public int GetNewOffset(int oldOffset, AnchorMovementType movementType)
{
int insertionLength = this.InsertionLength;
int removalLength = this.RemovalLength;
if (!(removalLength == 0 && oldOffset == offset)) {
// we're getting trouble (both if statements in here would apply)
@ -259,8 +274,10 @@ namespace ICSharpCode.AvalonEdit.Document @@ -259,8 +274,10 @@ namespace ICSharpCode.AvalonEdit.Document
// b) there was no removal and we insert at the caret position
if (movementType == AnchorMovementType.AfterInsertion)
return offset + insertionLength;
else
else if (movementType == AnchorMovementType.BeforeInsertion)
return offset;
else
return this.DefaultAnchorMovementIsBeforeInsertion ? offset : offset + insertionLength;
}
/// <summary>
@ -274,24 +291,26 @@ namespace ICSharpCode.AvalonEdit.Document @@ -274,24 +291,26 @@ namespace ICSharpCode.AvalonEdit.Document
this.offset = offset;
this.removalLengthWithDeletionFlag = (uint)removalLength;
this.insertionLength = insertionLength;
this.insertionLengthWithMovementFlag = (uint)insertionLength;
}
/// <summary>
/// Creates a new OffsetChangeMapEntry instance.
/// </summary>
public OffsetChangeMapEntry(int offset, int removalLength, int insertionLength, bool removalNeverCausesAnchorDeletion)
public OffsetChangeMapEntry(int offset, int removalLength, int insertionLength, bool removalNeverCausesAnchorDeletion, bool defaultAnchorMovementIsBeforeInsertion)
: this(offset, removalLength, insertionLength)
{
if (removalNeverCausesAnchorDeletion)
this.removalLengthWithDeletionFlag |= 0x80000000;
if (defaultAnchorMovementIsBeforeInsertion)
this.insertionLengthWithMovementFlag |= 0x80000000;
}
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked {
return offset + 3559 * insertionLength + 3571 * (int)removalLengthWithDeletionFlag;
return offset + 3559 * (int)insertionLengthWithMovementFlag + 3571 * (int)removalLengthWithDeletionFlag;
}
}
@ -304,7 +323,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -304,7 +323,7 @@ namespace ICSharpCode.AvalonEdit.Document
/// <inheritdoc/>
public bool Equals(OffsetChangeMapEntry other)
{
return offset == other.offset && insertionLength == other.insertionLength && removalLengthWithDeletionFlag == other.removalLengthWithDeletionFlag;
return offset == other.offset && insertionLengthWithMovementFlag == other.insertionLengthWithMovementFlag && removalLengthWithDeletionFlag == other.removalLengthWithDeletionFlag;
}
/// <summary>

8
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextAnchor.cs

@ -56,7 +56,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -56,7 +56,7 @@ namespace ICSharpCode.AvalonEdit.Document
/// <remarks>Anchor movement is ambiguous if text is inserted exactly at the anchor's location.
/// Does the anchor stay before the inserted text, or does it move after it?
/// The property <see cref="MovementType"/> will be used to determine which of these two options the anchor will choose.
/// The default value is <see cref="AnchorMovementType.BeforeInsertion"/>.</remarks>
/// The default value is <see cref="AnchorMovementType.Default"/>.</remarks>
public AnchorMovementType MovementType { get; set; }
/// <summary>
@ -174,6 +174,12 @@ namespace ICSharpCode.AvalonEdit.Document @@ -174,6 +174,12 @@ namespace ICSharpCode.AvalonEdit.Document
/// </summary>
public enum AnchorMovementType
{
/// <summary>
/// When text is inserted at the anchor position, the type of the insertion
/// determines where the caret moves to. For normal insertions, the anchor will stay
/// behind the inserted text.
/// </summary>
Default,
/// <summary>
/// When text is inserted at the anchor position, the anchor will stay
/// before the inserted text.

21
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextAnchorTree.cs

@ -61,7 +61,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -61,7 +61,7 @@ namespace ICSharpCode.AvalonEdit.Document
}
#region Insert Text
void InsertText(int offset, int length)
void InsertText(int offset, int length, bool defaultAnchorMovementIsBeforeInsertion)
{
if (length == 0 || root == null || offset > root.totalLength)
return;
@ -69,7 +69,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -69,7 +69,7 @@ namespace ICSharpCode.AvalonEdit.Document
// find the range of nodes that are placed exactly at offset
// beginNode is inclusive, endNode is exclusive
if (offset == root.totalLength) {
PerformInsertText(FindActualBeginNode(root.RightMost), null, length);
PerformInsertText(FindActualBeginNode(root.RightMost), null, length, defaultAnchorMovementIsBeforeInsertion);
} else {
TextAnchorNode endNode = FindNode(ref offset);
Debug.Assert(endNode.length > 0);
@ -79,7 +79,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -79,7 +79,7 @@ namespace ICSharpCode.AvalonEdit.Document
endNode.length += length;
UpdateAugmentedData(endNode);
} else {
PerformInsertText(FindActualBeginNode(endNode.Predecessor), endNode, length);
PerformInsertText(FindActualBeginNode(endNode.Predecessor), endNode, length, defaultAnchorMovementIsBeforeInsertion);
}
}
DeleteMarkedNodes();
@ -99,7 +99,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -99,7 +99,7 @@ namespace ICSharpCode.AvalonEdit.Document
// Sorts the nodes in the range [beginNode, endNode) by MovementType
// and inserts the length between the BeforeInsertion and the AfterInsertion nodes.
void PerformInsertText(TextAnchorNode beginNode, TextAnchorNode endNode, int length)
void PerformInsertText(TextAnchorNode beginNode, TextAnchorNode endNode, int length, bool defaultAnchorMovementIsBeforeInsertion)
{
Debug.Assert(beginNode != null);
// endNode may be null at the end of the anchor tree
@ -114,10 +114,13 @@ namespace ICSharpCode.AvalonEdit.Document @@ -114,10 +114,13 @@ namespace ICSharpCode.AvalonEdit.Document
if (anchor == null) {
// afterInsert.Add(temp);
MarkNodeForDelete(temp);
} else if (anchor.MovementType == AnchorMovementType.AfterInsertion) {
// afterInsert.Add(temp);
} else {
} else if (defaultAnchorMovementIsBeforeInsertion
? anchor.MovementType != AnchorMovementType.AfterInsertion
: anchor.MovementType == AnchorMovementType.BeforeInsertion)
{
beforeInsert.Add(temp);
// } else {
// afterInsert.Add(temp);
}
temp = temp.Successor;
}
@ -182,7 +185,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -182,7 +185,7 @@ namespace ICSharpCode.AvalonEdit.Document
// Thus, we handle this case on a separate code path
// (the code below looks like it does something similar, but it can only split
// the set of deletion survivors, not all nodes at an offset)
InsertText(entry.Offset, entry.InsertionLength);
InsertText(entry.Offset, entry.InsertionLength, entry.DefaultAnchorMovementIsBeforeInsertion);
return;
}
// When handling a replacing text change, we need to:
@ -244,7 +247,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -244,7 +247,7 @@ namespace ICSharpCode.AvalonEdit.Document
// survivors - from firstDeletionSurvivor (inclusive) to node (exclusive).
// This ensures that nodes immediately before or after the replaced segment
// stay where they are (independent from their MovementType)
PerformInsertText(firstDeletionSurvivor, node, entry.InsertionLength);
PerformInsertText(firstDeletionSurvivor, node, entry.InsertionLength, entry.DefaultAnchorMovementIsBeforeInsertion);
} else if (node != null) {
// No deletion survivors:
// just perform the insertion

8
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs

@ -7,8 +7,8 @@ using System.Collections.ObjectModel; @@ -7,8 +7,8 @@ using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Globalization;
using System.Linq.Expressions;
using System.Threading;
using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.Document
@ -490,6 +490,10 @@ namespace ICSharpCode.AvalonEdit.Document @@ -490,6 +490,10 @@ namespace ICSharpCode.AvalonEdit.Document
case OffsetChangeMappingType.Normal:
Replace(offset, length, text, null);
break;
case OffsetChangeMappingType.KeepAnchorBeforeInsertion:
Replace(offset, length, text, OffsetChangeMap.FromSingleElement(
new OffsetChangeMapEntry(offset, length, text.Length, false, true)));
break;
case OffsetChangeMappingType.RemoveAndInsert:
if (length == 0 || text.Length == 0) {
// only insertion or only removal?
@ -514,7 +518,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -514,7 +518,7 @@ namespace ICSharpCode.AvalonEdit.Document
OffsetChangeMapEntry entry = new OffsetChangeMapEntry(offset + length - 1, 1, 1 + text.Length - length);
Replace(offset, length, text, OffsetChangeMap.FromSingleElement(entry));
} else if (text.Length < length) {
OffsetChangeMapEntry entry = new OffsetChangeMapEntry(offset + text.Length, length - text.Length, 0, true);
OffsetChangeMapEntry entry = new OffsetChangeMapEntry(offset + text.Length, length - text.Length, 0, true, false);
Replace(offset, length, text, OffsetChangeMap.FromSingleElement(entry));
} else {
Replace(offset, length, text, OffsetChangeMap.Empty);

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

@ -145,7 +145,7 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -145,7 +145,7 @@ namespace ICSharpCode.AvalonEdit.Editing
{
InvalidateVisualColumn();
if (storedCaretOffset >= 0) {
int newCaretOffset = e.GetNewOffset(storedCaretOffset, AnchorMovementType.AfterInsertion);
int newCaretOffset = e.GetNewOffset(storedCaretOffset, AnchorMovementType.Default);
TextDocument document = textArea.Document;
if (document != null) {
// keep visual column

13
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs

@ -183,8 +183,6 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -183,8 +183,6 @@ namespace ICSharpCode.AvalonEdit.Editing
using (textArea.Document.RunUpdate()) {
if (textArea.Selection.IsMultiline(textArea.Document)) {
var segment = textArea.Selection.SurroundingSegment;
int oldSelectionStart = segment.Offset;
int oldCaretPos = textArea.Caret.Offset;
DocumentLine start = textArea.Document.GetLineByOffset(segment.Offset);
DocumentLine end = textArea.Document.GetLineByOffset(segment.EndOffset);
// don't include the last line if no characters on it are selected
@ -194,20 +192,11 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -194,20 +192,11 @@ namespace ICSharpCode.AvalonEdit.Editing
while (true) {
int offset = current.Offset;
if (textArea.ReadOnlySectionProvider.CanInsert(offset))
textArea.Document.Insert(offset, textArea.Options.IndentationString);
textArea.Document.Replace(offset, 0, textArea.Options.IndentationString, OffsetChangeMappingType.KeepAnchorBeforeInsertion);
if (current == end)
break;
current = current.NextLine;
}
// Fix for http://community.sharpdevelop.net/forums/t/11909.aspx:
// The insertion at the first line will move the start of selection, so we change the selection
// to keep the old start offset if it started at the beginning of the line.
if (textArea.Selection.Segments.Count() == 1 && oldSelectionStart == start.Offset) {
textArea.Selection = new SimpleSelection(oldSelectionStart, textArea.Selection.SurroundingSegment.EndOffset);
}
// do the same to the caret:
if (oldCaretPos == start.Offset)
textArea.Caret.Offset = oldCaretPos;
} else {
string indentationString = textArea.Options.GetIndentationString(textArea.Caret.Column);
textArea.ReplaceSelectionWithText(indentationString);

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

@ -109,8 +109,8 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -109,8 +109,8 @@ namespace ICSharpCode.AvalonEdit.Editing
if (e == null)
throw new ArgumentNullException("e");
return new SimpleSelection(
e.GetNewOffset(startOffset, AnchorMovementType.AfterInsertion),
e.GetNewOffset(endOffset, AnchorMovementType.AfterInsertion)
e.GetNewOffset(startOffset, AnchorMovementType.Default),
e.GetNewOffset(endOffset, AnchorMovementType.Default)
);
}

20
src/Main/Base/Project/Src/Editor/AvalonEdit/AvalonEditDocumentAdapter.cs

@ -330,26 +330,10 @@ namespace ICSharpCode.SharpDevelop.Editor.AvalonEdit @@ -330,26 +330,10 @@ namespace ICSharpCode.SharpDevelop.Editor.AvalonEdit
public ICSharpCode.SharpDevelop.Editor.AnchorMovementType MovementType {
get {
switch (anchor.MovementType) {
case ICSharpCode.AvalonEdit.Document.AnchorMovementType.AfterInsertion:
return ICSharpCode.SharpDevelop.Editor.AnchorMovementType.AfterInsertion;
case ICSharpCode.AvalonEdit.Document.AnchorMovementType.BeforeInsertion:
return ICSharpCode.SharpDevelop.Editor.AnchorMovementType.BeforeInsertion;
default:
throw new NotSupportedException();
}
return (ICSharpCode.SharpDevelop.Editor.AnchorMovementType)anchor.MovementType;
}
set {
switch (value) {
case ICSharpCode.SharpDevelop.Editor.AnchorMovementType.AfterInsertion:
anchor.MovementType = ICSharpCode.AvalonEdit.Document.AnchorMovementType.AfterInsertion;
break;
case ICSharpCode.SharpDevelop.Editor.AnchorMovementType.BeforeInsertion:
anchor.MovementType = ICSharpCode.AvalonEdit.Document.AnchorMovementType.BeforeInsertion;
break;
default:
throw new NotSupportedException();
}
anchor.MovementType = (ICSharpCode.AvalonEdit.Document.AnchorMovementType)value;
}
}

10
src/Main/Base/Project/Src/Editor/ITextAnchor.cs

@ -63,15 +63,21 @@ namespace ICSharpCode.SharpDevelop.Editor @@ -63,15 +63,21 @@ namespace ICSharpCode.SharpDevelop.Editor
/// </summary>
public enum AnchorMovementType
{
/// <summary>
/// When text is inserted at the anchor position, the type of the insertion
/// determines where the caret moves to. For normal insertions, the anchor will stay
/// behind the inserted text.
/// </summary>
Default = ICSharpCode.AvalonEdit.Document.AnchorMovementType.Default,
/// <summary>
/// Behaves like a start marker - when text is inserted at the anchor position, the anchor will stay
/// before the inserted text.
/// </summary>
BeforeInsertion,
BeforeInsertion = ICSharpCode.AvalonEdit.Document.AnchorMovementType.BeforeInsertion,
/// <summary>
/// Behave like an end marker - when text is insered at the anchor position, the anchor will move
/// after the inserted text.
/// </summary>
AfterInsertion
AfterInsertion = ICSharpCode.AvalonEdit.Document.AnchorMovementType.AfterInsertion
}
}

Loading…
Cancel
Save