Click here to Skip to main content
15,886,362 members
Articles / Desktop Programming / WPF

Using AvalonEdit (WPF Text Editor)

Rate me:
Please Sign up or sign in to vote.
4.97/5 (271 votes)
1 Apr 2013LGPL313 min read 1.8M   72.3K   534  
AvalonEdit is an extensible Open-Source text editor with support for syntax highlighting and folding.
// 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.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.TextFormatting;

using ICSharpCode.AvalonEdit.Document;

namespace ICSharpCode.AvalonEdit.Rendering
{
	/// <summary>
	/// Represents a visual element in the document.
	/// </summary>
	public abstract class VisualLineElement
	{
		/// <summary>
		/// Creates a new VisualLineElement.
		/// </summary>
		/// <param name="visualLength">The length of the element in VisualLine coordinates. Must be positive.</param>
		/// <param name="documentLength">The length of the element in the document. Must be non-negative.</param>
		protected VisualLineElement(int visualLength, int documentLength)
		{
			if (visualLength < 1)
				throw new ArgumentOutOfRangeException("visualLength", visualLength, "Value must be at least 1");
			if (documentLength < 0)
				throw new ArgumentOutOfRangeException("documentLength", documentLength, "Value must be at least 0");
			this.VisualLength = visualLength;
			this.DocumentLength = documentLength;
		}
		
		/// <summary>
		/// Gets the length of this element in visual columns.
		/// </summary>
		public int VisualLength { get; private set; }
		
		/// <summary>
		/// Gets the length of this element in the text document.
		/// </summary>
		public int DocumentLength { get; private set; }
		
		/// <summary>
		/// Gets the visual column where this element starts.
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods",
		                                                 Justification = "This property holds the start visual column, use GetVisualColumn to get inner visual columns.")]
		public int VisualColumn { get; internal set; }
		
		/// <summary>
		/// Gets the text offset where this element starts, relative to the start text offset of the visual line.
		/// </summary>
		public int RelativeTextOffset { get; internal set; }
		
		/// <summary>
		/// Gets the text run properties.
		/// A unique <see cref="VisualLineElementTextRunProperties"/> instance is used for each
		/// <see cref="VisualLineElement"/>; colorizing code may assume that modifying the
		/// <see cref="VisualLineElementTextRunProperties"/> will affect only this
		/// <see cref="VisualLineElement"/>.
		/// </summary>
		public VisualLineElementTextRunProperties TextRunProperties { get; private set; }
		
		/// <summary>
		/// Gets/sets the brush used for the background of this <see cref="VisualLineElement" />.
		/// </summary>
		public Brush BackgroundBrush { get; set; }
		
		internal void SetTextRunProperties(VisualLineElementTextRunProperties p)
		{
			this.TextRunProperties = p;
		}
		
		/// <summary>
		/// Creates the TextRun for this line element.
		/// </summary>
		/// <param name="startVisualColumn">
		/// The visual column from which the run should be constructed.
		/// Normally the same value as the <see cref="VisualColumn"/> property is used to construct the full run;
		/// but when word-wrapping is active, partial runs might be created.
		/// </param>
		/// <param name="context">
		/// Context object that contains information relevant for text run creation.
		/// </param>
		public abstract TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context);
		
		/// <summary>
		/// Retrieves the text span immediately before the visual column.
		/// </summary>
		/// <remarks>This method is used for word-wrapping in bidirectional text.</remarks>
		public virtual TextSpan<CultureSpecificCharacterBufferRange> GetPrecedingText(int visualColumnLimit, ITextRunConstructionContext context)
		{
			return null;
		}
		
		/// <summary>
		/// Gets if this VisualLineElement can be split.
		/// </summary>
		public virtual bool CanSplit {
			get { return false; }
		}
		
		/// <summary>
		/// Splits the element.
		/// </summary>
		/// <param name="splitVisualColumn">Position inside this element at which it should be broken</param>
		/// <param name="elements">The collection of line elements</param>
		/// <param name="elementIndex">The index at which this element is in the elements list.</param>
		public virtual void Split(int splitVisualColumn, IList<VisualLineElement> elements, int elementIndex)
		{
			throw new NotSupportedException();
		}
		
		/// <summary>
		/// Helper method for splitting this line element into two, correctly updating the
		/// <see cref="VisualLength"/>, <see cref="DocumentLength"/>, <see cref="VisualColumn"/>
		/// and <see cref="RelativeTextOffset"/> properties.
		/// </summary>
		/// <param name="firstPart">The element before the split position.</param>
		/// <param name="secondPart">The element after the split position.</param>
		/// <param name="splitVisualColumn">The split position as visual column.</param>
		/// <param name="splitRelativeTextOffset">The split position as text offset.</param>
		protected void SplitHelper(VisualLineElement firstPart, VisualLineElement secondPart, int splitVisualColumn, int splitRelativeTextOffset)
		{
			if (firstPart == null)
				throw new ArgumentNullException("firstPart");
			if (secondPart == null)
				throw new ArgumentNullException("secondPart");
			int relativeSplitVisualColumn = splitVisualColumn - VisualColumn;
			int relativeSplitRelativeTextOffset = splitRelativeTextOffset - RelativeTextOffset;
			
			if (relativeSplitVisualColumn <= 0 || relativeSplitVisualColumn >= VisualLength)
				throw new ArgumentOutOfRangeException("splitVisualColumn", splitVisualColumn, "Value must be between " + (VisualColumn + 1) + " and " + (VisualColumn + VisualLength - 1));
			if (relativeSplitRelativeTextOffset < 0 || relativeSplitRelativeTextOffset > DocumentLength)
				throw new ArgumentOutOfRangeException("splitRelativeTextOffset", splitRelativeTextOffset, "Value must be between " + (RelativeTextOffset) + " and " + (RelativeTextOffset + DocumentLength));
			int oldVisualLength = VisualLength;
			int oldDocumentLength = DocumentLength;
			int oldVisualColumn = VisualColumn;
			int oldRelativeTextOffset = RelativeTextOffset;
			firstPart.VisualColumn = oldVisualColumn;
			secondPart.VisualColumn = oldVisualColumn + relativeSplitVisualColumn;
			firstPart.RelativeTextOffset = oldRelativeTextOffset;
			secondPart.RelativeTextOffset = oldRelativeTextOffset + relativeSplitRelativeTextOffset;
			firstPart.VisualLength = relativeSplitVisualColumn;
			secondPart.VisualLength = oldVisualLength - relativeSplitVisualColumn;
			firstPart.DocumentLength = relativeSplitRelativeTextOffset;
			secondPart.DocumentLength = oldDocumentLength - relativeSplitRelativeTextOffset;
			if (firstPart.TextRunProperties == null)
				firstPart.TextRunProperties = TextRunProperties.Clone();
			if (secondPart.TextRunProperties == null)
				secondPart.TextRunProperties = TextRunProperties.Clone();
		}
		
		/// <summary>
		/// Gets the visual column of a text location inside this element.
		/// The text offset is given relative to the visual line start.
		/// </summary>
		public virtual int GetVisualColumn(int relativeTextOffset)
		{
			if (relativeTextOffset >= this.RelativeTextOffset + DocumentLength)
				return VisualColumn + VisualLength;
			else
				return VisualColumn;
		}
		
		/// <summary>
		/// Gets the text offset of a visual column inside this element.
		/// </summary>
		/// <returns>A text offset relative to the visual line start.</returns>
		public virtual int GetRelativeOffset(int visualColumn)
		{
			if (visualColumn >= this.VisualColumn + VisualLength)
				return RelativeTextOffset + DocumentLength;
			else
				return RelativeTextOffset;
		}
		
		/// <summary>
		/// Gets the next caret position inside this element.
		/// </summary>
		/// <param name="visualColumn">The visual column from which the search should be started.</param>
		/// <param name="direction">The search direction (forwards or backwards).</param>
		/// <param name="mode">Whether to stop only at word borders.</param>
		/// <returns>The visual column of the next caret position, or -1 if there is no next caret position.</returns>
		/// <remarks>
		/// In the space between two line elements, it is sufficient that one of them contains a caret position;
		/// though in many cases, both of them contain one.
		/// </remarks>
		public virtual int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode)
		{
			int stop1 = this.VisualColumn;
			int stop2 = this.VisualColumn + this.VisualLength;
			if (direction == LogicalDirection.Backward) {
				if (visualColumn > stop2 && mode != CaretPositioningMode.WordStart && mode != CaretPositioningMode.WordStartOrSymbol)
					return stop2;
				else if (visualColumn > stop1)
					return stop1;
			} else {
				if (visualColumn < stop1)
					return stop1;
				else if (visualColumn < stop2 && mode != CaretPositioningMode.WordStart && mode != CaretPositioningMode.WordStartOrSymbol)
					return stop2;
			}
			return -1;
		}
		
		/// <summary>
		/// Gets whether the specified offset in this element is considered whitespace.
		/// </summary>
		public virtual bool IsWhitespace(int visualColumn)
		{
			return false;
		}
		
		/// <summary>
		/// Gets whether the <see cref="GetNextCaretPosition"/> implementation handles line borders.
		/// If this property returns false, the caller of GetNextCaretPosition should handle the line
		/// borders (i.e. place caret stops at the start and end of the line).
		/// This property has an effect only for VisualLineElements that are at the start or end of a
		/// <see cref="VisualLine"/>.
		/// </summary>
		public virtual bool HandlesLineBorders {
			get { return false; }
		}
		
		/// <summary>
		/// Queries the cursor over the visual line element.
		/// </summary>
		protected internal virtual void OnQueryCursor(QueryCursorEventArgs e)
		{
		}
		
		/// <summary>
		/// Allows the visual line element to handle a mouse event.
		/// </summary>
		protected internal virtual void OnMouseDown(MouseButtonEventArgs e)
		{
		}
		
		/// <summary>
		/// Allows the visual line element to handle a mouse event.
		/// </summary>
		protected internal virtual void OnMouseUp(MouseButtonEventArgs e)
		{
		}
	}
}

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)


Written By
Germany Germany
I am the lead developer on the SharpDevelop open source project.

Comments and Discussions