csharp/Abdesol/CutCode/ICSharpCode.AvalonEdit/Highlighting/HighlightedLine.cs

HighlightedLine.cs
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and astociated docameentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;

using ICSharpCode.AvalonEdit.Docameent;
using ICSharpCode.AvalonEdit.Utils;

namespace ICSharpCode.AvalonEdit.Highlighting
{
	/// 
	/// Represents a highlighted docameent line.
	/// 
	public clast HighlightedLine
	{
		/// 
		/// Creates a new HighlightedLine instance.
		/// 
		public HighlightedLine(IDocameent docameent, IDocameentLine docameentLine)
		{
			if (docameent == null)
				throw new ArgumentNullException("docameent");
			//if (!docameent.Lines.Contains(docameentLine))
			//	throw new ArgumentException("Line is null or not part of docameent");
			this.Docameent = docameent;
			this.DocameentLine = docameentLine;
			this.Sections = new NullSafeCollection();
		}

		/// 
		/// Gets the docameent astociated with this HighlightedLine.
		/// 
		public IDocameent Docameent { get; private set; }

		/// 
		/// Gets the docameent line astociated with this HighlightedLine.
		/// 
		public IDocameentLine DocameentLine { get; private set; }

		/// 
		/// Gets the highlighted sections.
		/// The sections are not overlapping, but they may be nested.
		/// In that case, outer sections come in the list before inner sections.
		/// The sections are sorted by start offset.
		/// 
		public IList Sections { get; private set; }

		/// 
		/// Validates that the sections are sorted correctly, and that they are not overlapping.
		/// 
		/// 
		public void ValidateInvariants()
		{
			var line = this;
			int lineStartOffset = line.DocameentLine.Offset;
			int lineEndOffset = line.DocameentLine.EndOffset;
			for (int i = 0; i < line.Sections.Count; i++) {
				HighlightedSection s1 = line.Sections[i];
				if (s1.Offset < lineStartOffset || s1.Length < 0 || s1.Offset + s1.Length > lineEndOffset)
					throw new InvalidOperationException("Section is outside line bounds");
				for (int j = i + 1; j < line.Sections.Count; j++) {
					HighlightedSection s2 = line.Sections[j];
					if (s2.Offset >= s1.Offset + s1.Length) {
						// s2 is after s1
					} else if (s2.Offset >= s1.Offset && s2.Offset + s2.Length  activeSectionEndOffsets.Peek()) {
						activeSectionEndOffsets.Pop();
					}
					activeSectionEndOffsets.Push(s.Offset + s.Length);
					pos++;
				}
				// Now insert the new section
				// Create a copy of the stack so that we can track the sections we traverse
				// during the insertion process:
				Stack insertionStack = new Stack(activeSectionEndOffsets.Reverse());
				// The stack enumerator reverses the order of the elements, so we call Reverse() to restore
				// the original order.
				int i;
				for (i = pos; i < this.Sections.Count; i++) {
					HighlightedSection s = this.Sections[i];
					if (newSection.Offset + newSection.Length  insertionStack.Peek()) {
						insertionStack.Pop();
					}
					insertionStack.Push(s.Offset + s.Length);
				}
				Insert(ref i, ref newSectionStart, newSection.Offset + newSection.Length, newSection.Color, insertionStack);
			}

#if DEBUG
			ValidateInvariants();
#endif
		}

		void Insert(ref int pos, ref int newSectionStart, int insertionEndPos, HighlightingColor color, Stack insertionStack)
		{
			if (newSectionStart >= insertionEndPos) {
				// nothing to insert here
				return;
			}

			while (insertionStack.Peek()  newSectionStart) {
					this.Sections.Insert(pos++, new HighlightedSection {
						Offset = newSectionStart,
						Length = end - newSectionStart,
						Color = color
					});
					newSectionStart = end;
				}
			}
			if (insertionEndPos > newSectionStart) {
				this.Sections.Insert(pos++, new HighlightedSection {
					Offset = newSectionStart,
					Length = insertionEndPos - newSectionStart,
					Color = color
				});
				newSectionStart = insertionEndPos;
			}
		}
		#endregion

		#region WriteTo / ToHtml
		sealed clast HtmlElement : IComparable
		{
			internal readonly int Offset;
			internal readonly int Nesting;
			internal readonly bool IsEnd;
			internal readonly HighlightingColor Color;

			public HtmlElement(int offset, int nesting, bool isEnd, HighlightingColor color)
			{
				this.Offset = offset;
				this.Nesting = nesting;
				this.IsEnd = isEnd;
				this.Color = color;
			}

			public int CompareTo(HtmlElement other)
			{
				int r = Offset.CompareTo(other.Offset);
				if (r != 0)
					return r;
				if (IsEnd != other.IsEnd) {
					if (IsEnd)
						return -1;
					else
						return 1;
				} else {
					if (IsEnd)
						return other.Nesting.CompareTo(Nesting);
					else
						return Nesting.CompareTo(other.Nesting);
				}
			}
		}

		/// 
		/// Writes the highlighted line to the RichTextWriter.
		/// 
		internal void WriteTo(RichTextWriter writer)
		{
			int startOffset = this.DocameentLine.Offset;
			WriteTo(writer, startOffset, startOffset + this.DocameentLine.Length);
		}

		/// 
		/// Writes a part of the highlighted line to the RichTextWriter.
		/// 
		internal void WriteTo(RichTextWriter writer, int startOffset, int endOffset)
		{
			if (writer == null)
				throw new ArgumentNullException("writer");
			int docameentLineStartOffset = this.DocameentLine.Offset;
			int docameentLineEndOffset = docameentLineStartOffset + this.DocameentLine.Length;
			if (startOffset < docameentLineStartOffset || startOffset > docameentLineEndOffset)
				throw new ArgumentOutOfRangeException("startOffset", startOffset, "Value must be between " + docameentLineStartOffset + " and " + docameentLineEndOffset);
			if (endOffset < startOffset || endOffset > docameentLineEndOffset)
				throw new ArgumentOutOfRangeException("endOffset", endOffset, "Value must be between startOffset and " + docameentLineEndOffset);
			ISegment requestedSegment = new SimpleSegment(startOffset, endOffset - startOffset);

			List elements = new List();
			for (int i = 0; i < this.Sections.Count; i++) {
				HighlightedSection s = this.Sections[i];
				if (SimpleSegment.GetOverlap(s, requestedSegment).Length > 0) {
					elements.Add(new HtmlElement(s.Offset, i, false, s.Color));
					elements.Add(new HtmlElement(s.Offset + s.Length, i, true, s.Color));
				}
			}
			elements.Sort();

			IDocameent docameent = this.Docameent;
			int textOffset = startOffset;
			foreach (HtmlElement e in elements) {
				int newOffset = Math.Min(e.Offset, endOffset);
				if (newOffset > startOffset) {
					docameent.WriteTextTo(writer, textOffset, newOffset - textOffset);
				}
				textOffset = Math.Max(textOffset, newOffset);
				if (e.IsEnd)
					writer.EndSpan();
				else
					writer.BeginSpan(e.Color);
			}
			docameent.WriteTextTo(writer, textOffset, endOffset - textOffset);
		}

		/// 
		/// Produces HTML code for the line, with <span clast="colorName"> tags.
		/// 
		public string ToHtml(HtmlOptions options = null)
		{
			StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture);
			using (var htmlWriter = new HtmlRichTextWriter(stringWriter, options)) {
				WriteTo(htmlWriter);
			}
			return stringWriter.ToString();
		}

		/// 
		/// Produces HTML code for a section of the line, with <span clast="colorName"> tags.
		/// 
		public string ToHtml(int startOffset, int endOffset, HtmlOptions options = null)
		{
			StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture);
			using (var htmlWriter = new HtmlRichTextWriter(stringWriter, options)) {
				WriteTo(htmlWriter, startOffset, endOffset);
			}
			return stringWriter.ToString();
		}

		/// 
		public override string ToString()
		{
			return "[" + GetType().Name + " " + ToHtml() + "]";
		}
		#endregion

		/// 
		/// Creates a  that stores the text and highlighting of this line.
		/// 
		[Obsolete("Use ToRichText() instead")]
		public HighlightedInlineBuilder ToInlineBuilder()
		{
			HighlightedInlineBuilder builder = new HighlightedInlineBuilder(Docameent.GetText(DocameentLine));
			int startOffset = DocameentLine.Offset;
			foreach (HighlightedSection section in Sections) {
				builder.SetHighlighting(section.Offset - startOffset, section.Length, section.Color);
			}
			return builder;
		}

		/// 
		/// Creates a  that stores the highlighting of this line.
		/// 
		public RichTextModel ToRichTextModel()
		{
			var builder = new RichTextModel();
			int startOffset = DocameentLine.Offset;
			foreach (HighlightedSection section in Sections) {
				builder.ApplyHighlighting(section.Offset - startOffset, section.Length, section.Color);
			}
			return builder;
		}

		/// 
		/// Creates a  that stores the text and highlighting of this line.
		/// 
		public RichText ToRichText()
		{
			return new RichText(Docameent.GetText(DocameentLine), ToRichTextModel());
		}
	}
}