Click here to Skip to main content
12,622,517 members (31,787 online)
Click here to Skip to main content

Stats

947.8K views
57.1K downloads
508 bookmarked
Posted

Using AvalonEdit (WPF Text Editor)

, 1 Apr 2013 LGPL3
AvalonEdit is an extensible Open-Source text editor with support for syntax highlighting and folding.
AvalonEdit.Sample.exe
ICSharpCode.AvalonEdit.dll
AvalonEdit.Sample
AvalonEdit
dependencies.png
folding.png
renderingPipeline.png
screenshot.png
snoop.png
CustomHighlighting.xshd
Images
Copy.png
Cut.png
Delete.png
Open.png
Paste.png
Redo.png
Save.png
Undo.png
WordWrap.png
Properties
RenderingPipeline.pptx
Documentation
Architecture.aml
Code Completion.aml
Coordinate Systems.aml
Folding.aml
ICSharpCode.AvalonEdit.content
ICSharpCode.AvalonEdit.shfbproj
Media
NamespaceDependencies.png
RenderingPipeline.png
VisualTree.png
WelcomeScreenshot.png
Sample Application.aml
Syntax Highlighting.aml
Text Rendering.aml
Welcome.aml
ICSharpCode.AvalonEdit.Tests
Document
Editing
Highlighting
Properties
Search
Utils
ICSharpCode.AvalonEdit
CodeCompletion
Document
Editing
Folding
Highlighting
Resources
ASPX.xshd
Boo.xshd
Coco-Mode.xshd
CPP-Mode.xshd
CSharp-Mode.xshd
CSS-Mode.xshd
HTML-Mode.xshd
Java-Mode.xshd
JavaScript-Mode.xshd
MarkDown-Mode.xshd
Patch-Mode.xshd
PHP-Mode.xshd
PowerShell.xshd
Tex-Mode.xshd
VBNET-Mode.xshd
XML-Mode.xshd
XmlDoc.xshd
Xshd
ICSharpCode.AvalonEdit.snk
Indentation
CSharp
Properties
Rendering
Search
next.png
prev.png
Snippets
themes
RightArrow.cur
Utils
Xml
dependencies.png
folding.png
renderingPipeline.png
screenshot.png
snoop.png
CustomHighlighting.xshd
Copy.png
Cut.png
Delete.png
Open.png
Paste.png
Redo.png
Save.png
Undo.png
WordWrap.png
RenderingPipeline.pptx
Architecture.aml
Code Completion.aml
Coordinate Systems.aml
Folding.aml
ICSharpCode.AvalonEdit.content
ICSharpCode.AvalonEdit.shfbproj
NamespaceDependencies.png
RenderingPipeline.png
VisualTree.png
WelcomeScreenshot.png
Sample Application.aml
Syntax Highlighting.aml
Text Rendering.aml
Welcome.aml
nunit.framework.dll
ASPX.xshd
Boo.xshd
Coco-Mode.xshd
CPP-Mode.xshd
CSharp-Mode.xshd
CSS-Mode.xshd
HTML-Mode.xshd
Java-Mode.xshd
JavaScript-Mode.xshd
MarkDown-Mode.xshd
Patch-Mode.xshd
PHP-Mode.xshd
PowerShell.xshd
Tex-Mode.xshd
VBNET-Mode.xshd
XML-Mode.xshd
XmlDoc.xshd
ICSharpCode.AvalonEdit.snk
next.png
prev.png
RightArrow.cur
AvalonEdit.Sample.exe
ICSharpCode.AvalonEdit.dll
AvalonEdit Documentation.chm
dependencies.png
folding.png
renderingPipeline.png
screenshot.png
snoop.png
CustomHighlighting.xshd
Copy.png
Cut.png
Delete.png
Open.png
Paste.png
Redo.png
Save.png
Undo.png
WordWrap.png
RenderingPipeline.pptx
Architecture.aml
Code Completion.aml
Coordinate Systems.aml
Folding.aml
Help
ICSharpCode.AvalonEdit.content
ICSharpCode.AvalonEdit.shfbproj
NamespaceDependencies.png
RenderingPipeline.png
VisualTree.png
WelcomeScreenshot.png
Sample Application.aml
Syntax Highlighting.aml
Text Rendering.aml
Welcome.aml
nunit.framework.dll
ASPX.xshd
Boo.xshd
Coco-Mode.xshd
CPP-Mode.xshd
CSharp-Mode.xshd
CSS-Mode.xshd
HTML-Mode.xshd
Java-Mode.xshd
JavaScript-Mode.xshd
Patch-Mode.xshd
PHP-Mode.xshd
PowerShell.xshd
Tex-Mode.xshd
VBNET-Mode.xshd
XML-Mode.xshd
XmlDoc.xshd
ICSharpCode.AvalonEdit.snk
next.png
prev.png
RightArrow.cur
// 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.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;

using ICSharpCode.AvalonEdit.Utils;

namespace ICSharpCode.AvalonEdit.Document
{
	/// <summary>
	/// Contains predefined offset change mapping types.
	/// </summary>
	public enum OffsetChangeMappingType
	{
		/// <summary>
		/// Normal replace.
		/// Anchors in front of the replaced region will stay in front, anchors after the replaced region will stay after.
		/// Anchors in the middle of the removed region will be deleted. If they survive deletion,
		/// they move depending on their AnchorMovementType.
		/// </summary>
		/// <remarks>
		/// This is the default implementation of DocumentChangeEventArgs when OffsetChangeMap is null,
		/// so using this option usually works without creating an OffsetChangeMap instance.
		/// This is equivalent to an OffsetChangeMap with a single entry describing the replace operation.
		/// </remarks>
		Normal,
		/// <summary>
		/// First the old text is removed, then the new text is inserted.
		/// Anchors immediately in front (or after) the replaced region may move to the other side of the insertion,
		/// depending on the AnchorMovementType.
		/// </summary>
		/// <remarks>
		/// This is implemented as an OffsetChangeMap with two entries: the removal, and the insertion.
		/// </remarks>
		RemoveAndInsert,
		/// <summary>
		/// The text is replaced character-by-character.
		/// Anchors keep their position inside the replaced text.
		/// Anchors after the replaced region will move accordingly if the replacement text has a different length than the replaced text.
		/// If the new text is shorter than the old text, anchors inside the old text that would end up behind the replacement text
		/// will be moved so that they point to the end of the replacement text.
		/// </summary>
		/// <remarks>
		/// On the OffsetChangeMap level, growing text is implemented by replacing the last character in the replaced text
		/// with itself and the additional text segment. A simple insertion of the additional text would have the undesired
		/// effect of moving anchors immediately after the replaced text into the replacement text if they used
		/// AnchorMovementStyle.BeforeInsertion.
		/// Shrinking text is implemented by removing the text segment that's too long; but in a special mode that
		/// 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,
		/// <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>
	/// Describes a series of offset changes.
	/// </summary>
	[Serializable]
	[SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix",
	                 Justification="It's a mapping old offsets -> new offsets")]
	public sealed class OffsetChangeMap : Collection<OffsetChangeMapEntry>
	{
		/// <summary>
		/// Immutable OffsetChangeMap that is empty.
		/// </summary>
		[SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes",
		                 Justification="The Empty instance is immutable")]
		public static readonly OffsetChangeMap Empty = new OffsetChangeMap(Empty<OffsetChangeMapEntry>.Array, true);
		
		/// <summary>
		/// Creates a new OffsetChangeMap with a single element.
		/// </summary>
		/// <param name="entry">The entry.</param>
		/// <returns>Returns a frozen OffsetChangeMap with a single entry.</returns>
		public static OffsetChangeMap FromSingleElement(OffsetChangeMapEntry entry)
		{
			return new OffsetChangeMap(new OffsetChangeMapEntry[] { entry }, true);
		}
		
		bool isFrozen;
		
		/// <summary>
		/// Creates a new OffsetChangeMap instance.
		/// </summary>
		public OffsetChangeMap()
		{
		}
		
		internal OffsetChangeMap(int capacity)
			: base(new List<OffsetChangeMapEntry>(capacity))
		{
		}
		
		private OffsetChangeMap(IList<OffsetChangeMapEntry> entries, bool isFrozen)
			: base(entries)
		{
			this.isFrozen = isFrozen;
		}
		
		/// <summary>
		/// Gets the new offset where the specified offset moves after this document change.
		/// </summary>
		public int GetNewOffset(int offset, AnchorMovementType movementType)
		{
			IList<OffsetChangeMapEntry> items = this.Items;
			int count = items.Count;
			for (int i = 0; i < count; i++) {
				offset = items[i].GetNewOffset(offset, movementType);
			}
			return offset;
		}
		
		/// <summary>
		/// Gets whether this OffsetChangeMap is a valid explanation for the specified document change.
		/// </summary>
		public bool IsValidForDocumentChange(int offset, int removalLength, int insertionLength)
		{
			int endOffset = offset + removalLength;
			foreach (OffsetChangeMapEntry entry in this) {
				// check that ChangeMapEntry is in valid range for this document change
				if (entry.Offset < offset || entry.Offset + entry.RemovalLength > endOffset)
					return false;
				endOffset += entry.InsertionLength - entry.RemovalLength;
			}
			// check that the total delta matches
			return endOffset == offset + insertionLength;
		}
		
		/// <summary>
		/// Calculates the inverted OffsetChangeMap (used for the undo operation).
		/// </summary>
		public OffsetChangeMap Invert()
		{
			if (this == Empty)
				return this;
			OffsetChangeMap newMap = new OffsetChangeMap(this.Count);
			for (int i = this.Count - 1; i >= 0; i--) {
				OffsetChangeMapEntry entry = this[i];
				// swap InsertionLength and RemovalLength
				newMap.Add(new OffsetChangeMapEntry(entry.Offset, entry.InsertionLength, entry.RemovalLength));
			}
			return newMap;
		}
		
		/// <inheritdoc/>
		protected override void ClearItems()
		{
			CheckFrozen();
			base.ClearItems();
		}
		
		/// <inheritdoc/>
		protected override void InsertItem(int index, OffsetChangeMapEntry item)
		{
			CheckFrozen();
			base.InsertItem(index, item);
		}
		
		/// <inheritdoc/>
		protected override void RemoveItem(int index)
		{
			CheckFrozen();
			base.RemoveItem(index);
		}
		
		/// <inheritdoc/>
		protected override void SetItem(int index, OffsetChangeMapEntry item)
		{
			CheckFrozen();
			base.SetItem(index, item);
		}
		
		void CheckFrozen()
		{
			if (isFrozen)
				throw new InvalidOperationException("This instance is frozen and cannot be modified.");
		}
		
		/// <summary>
		/// Gets if this instance is frozen. Frozen instances are immutable and thus thread-safe.
		/// </summary>
		public bool IsFrozen {
			get { return isFrozen; }
		}
		
		/// <summary>
		/// Freezes this instance.
		/// </summary>
		public void Freeze()
		{
			isFrozen = true;
		}
	}
	
	/// <summary>
	/// An entry in the OffsetChangeMap.
	/// This represents the offset of a document change (either insertion or removal, not both at once).
	/// </summary>
	[Serializable]
	public struct OffsetChangeMapEntry : IEquatable<OffsetChangeMapEntry>
	{
		readonly int offset;
		
		// MSB: DefaultAnchorMovementIsBeforeInsertion
		readonly uint insertionLengthWithMovementFlag;
		
		// MSB: RemovalNeverCausesAnchorDeletion; other 31 bits: RemovalLength
		readonly uint removalLengthWithDeletionFlag;
		
		/// <summary>
		/// The offset at which the change occurs.
		/// </summary>
		public int Offset {
			get { return offset; }
		}
		
		/// <summary>
		/// The number of characters inserted.
		/// Returns 0 if this entry represents a removal.
		/// </summary>
		public int InsertionLength {
			get { return (int)(insertionLengthWithMovementFlag & 0x7fffffff); }
		}
		
		/// <summary>
		/// The number of characters removed.
		/// Returns 0 if this entry represents an insertion.
		/// </summary>
		public int RemovalLength {
			get { return (int)(removalLengthWithDeletionFlag & 0x7fffffff); }
		}
		
		/// <summary>
		/// Gets whether the removal should not cause any anchor deletions.
		/// </summary>
		public bool RemovalNeverCausesAnchorDeletion {
			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)
				// if there's no removal and we insert at the offset
				// -> we'd need to disambiguate by movementType, which is handled after the if
				
				// offset is before start of change: no movement
				if (oldOffset <= offset)
					return oldOffset;
				// offset is after end of change: movement by normal delta
				if (oldOffset >= offset + removalLength)
					return oldOffset + insertionLength - removalLength;
			}
			// we reach this point if
			// a) the oldOffset is inside the deleted segment
			// b) there was no removal and we insert at the caret position
			if (movementType == AnchorMovementType.AfterInsertion)
				return offset + insertionLength;
			else if (movementType == AnchorMovementType.BeforeInsertion)
				return offset;
			else
				return this.DefaultAnchorMovementIsBeforeInsertion ? offset : offset + insertionLength;
		}
		
		/// <summary>
		/// Creates a new OffsetChangeMapEntry instance.
		/// </summary>
		public OffsetChangeMapEntry(int offset, int removalLength, int insertionLength)
		{
			ThrowUtil.CheckNotNegative(offset, "offset");
			ThrowUtil.CheckNotNegative(removalLength, "removalLength");
			ThrowUtil.CheckNotNegative(insertionLength, "insertionLength");
			
			this.offset = offset;
			this.removalLengthWithDeletionFlag = (uint)removalLength;
			this.insertionLengthWithMovementFlag = (uint)insertionLength;
		}
		
		/// <summary>
		/// Creates a new OffsetChangeMapEntry instance.
		/// </summary>
		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 * (int)insertionLengthWithMovementFlag + 3571 * (int)removalLengthWithDeletionFlag;
			}
		}
		
		/// <inheritdoc/>
		public override bool Equals(object obj)
		{
			return obj is OffsetChangeMapEntry && this.Equals((OffsetChangeMapEntry)obj);
		}
		
		/// <inheritdoc/>
		public bool Equals(OffsetChangeMapEntry other)
		{
			return offset == other.offset && insertionLengthWithMovementFlag == other.insertionLengthWithMovementFlag && removalLengthWithDeletionFlag == other.removalLengthWithDeletionFlag;
		}
		
		/// <summary>
		/// Tests the two entries for equality.
		/// </summary>
		public static bool operator ==(OffsetChangeMapEntry left, OffsetChangeMapEntry right)
		{
			return left.Equals(right);
		}
		
		/// <summary>
		/// Tests the two entries for inequality.
		/// </summary>
		public static bool operator !=(OffsetChangeMapEntry left, OffsetChangeMapEntry right)
		{
			return !left.Equals(right);
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

Share

About the Author

Daniel Grunwald
Germany Germany
I am the lead developer on the SharpDevelop open source project.

You may also be interested in...

Pro
Pro
| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.161128.1 | Last Updated 1 Apr 2013
Article Copyright 2009 by Daniel Grunwald
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid