Free cookie consent management tool by TermsFeed Policy Generator

source: branches/PersistenceOverhaul/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/Highlighting/HighlightedLine.cs @ 15016

Last change on this file since 15016 was 11700, checked in by jkarder, 10 years ago

#2077: created branch and added first version

File size: 11.6 KB
Line 
1// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4// software and associated documentation files (the "Software"), to deal in the Software
5// without restriction, including without limitation the rights to use, copy, modify, merge,
6// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7// to whom the Software is furnished to do so, subject to the following conditions:
8//
9// The above copyright notice and this permission notice shall be included in all copies or
10// substantial portions of the Software.
11//
12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17// DEALINGS IN THE SOFTWARE.
18
19using System;
20using System.Collections.Generic;
21using System.Globalization;
22using System.IO;
23using System.Linq;
24using ICSharpCode.AvalonEdit.Document;
25using ICSharpCode.AvalonEdit.Utils;
26using ICSharpCode.NRefactory.Editor;
27
28namespace ICSharpCode.AvalonEdit.Highlighting
29{
30  /// <summary>
31  /// Represents a highlighted document line.
32  /// </summary>
33  public class HighlightedLine
34  {
35    /// <summary>
36    /// Creates a new HighlightedLine instance.
37    /// </summary>
38    public HighlightedLine(IDocument document, IDocumentLine documentLine)
39    {
40      if (document == null)
41        throw new ArgumentNullException("document");
42      //if (!document.Lines.Contains(documentLine))
43      //  throw new ArgumentException("Line is null or not part of document");
44      this.Document = document;
45      this.DocumentLine = documentLine;
46      this.Sections = new NullSafeCollection<HighlightedSection>();
47    }
48   
49    /// <summary>
50    /// Gets the document associated with this HighlightedLine.
51    /// </summary>
52    public IDocument Document { get; private set; }
53   
54    /// <summary>
55    /// Gets the document line associated with this HighlightedLine.
56    /// </summary>
57    public IDocumentLine DocumentLine { get; private set; }
58   
59    /// <summary>
60    /// Gets the highlighted sections.
61    /// The sections are not overlapping, but they may be nested.
62    /// In that case, outer sections come in the list before inner sections.
63    /// The sections are sorted by start offset.
64    /// </summary>
65    public IList<HighlightedSection> Sections { get; private set; }
66   
67    /// <summary>
68    /// Validates that the sections are sorted correctly, and that they are not overlapping.
69    /// </summary>
70    /// <seealso cref="Sections"/>
71    public void ValidateInvariants()
72    {
73      var line = this;
74      int lineStartOffset = line.DocumentLine.Offset;
75      int lineEndOffset = line.DocumentLine.EndOffset;
76      for (int i = 0; i < line.Sections.Count; i++) {
77        HighlightedSection s1 = line.Sections[i];
78        if (s1.Offset < lineStartOffset || s1.Length < 0 || s1.Offset + s1.Length > lineEndOffset)
79          throw new InvalidOperationException("Section is outside line bounds");
80        for (int j = i + 1; j < line.Sections.Count; j++) {
81          HighlightedSection s2 = line.Sections[j];
82          if (s2.Offset >= s1.Offset + s1.Length) {
83            // s2 is after s1
84          } else if (s2.Offset >= s1.Offset && s2.Offset + s2.Length <= s1.Offset + s1.Length) {
85            // s2 is nested within s1
86          } else {
87            throw new InvalidOperationException("Sections are overlapping or incorrectly sorted.");
88          }
89        }
90      }
91    }
92   
93    #region Merge
94    /// <summary>
95    /// Merges the additional line into this line.
96    /// </summary>
97    public void MergeWith(HighlightedLine additionalLine)
98    {
99      if (additionalLine == null)
100        return;
101      #if DEBUG
102      ValidateInvariants();
103      additionalLine.ValidateInvariants();
104      #endif
105     
106      int pos = 0;
107      Stack<int> activeSectionEndOffsets = new Stack<int>();
108      int lineEndOffset = this.DocumentLine.EndOffset;
109      activeSectionEndOffsets.Push(lineEndOffset);
110      foreach (HighlightedSection newSection in additionalLine.Sections) {
111        int newSectionStart = newSection.Offset;
112        // Track the existing sections using the stack, up to the point where
113        // we need to insert the first part of the newSection
114        while (pos < this.Sections.Count) {
115          HighlightedSection s = this.Sections[pos];
116          if (newSection.Offset < s.Offset)
117            break;
118          while (s.Offset > activeSectionEndOffsets.Peek()) {
119            activeSectionEndOffsets.Pop();
120          }
121          activeSectionEndOffsets.Push(s.Offset + s.Length);
122          pos++;
123        }
124        // Now insert the new section
125        // Create a copy of the stack so that we can track the sections we traverse
126        // during the insertion process:
127        Stack<int> insertionStack = new Stack<int>(activeSectionEndOffsets.Reverse());
128        // The stack enumerator reverses the order of the elements, so we call Reverse() to restore
129        // the original order.
130        int i;
131        for (i = pos; i < this.Sections.Count; i++) {
132          HighlightedSection s = this.Sections[i];
133          if (newSection.Offset + newSection.Length <= s.Offset)
134            break;
135          // Insert a segment in front of s:
136          Insert(ref i, ref newSectionStart, s.Offset, newSection.Color, insertionStack);
137         
138          while (s.Offset > insertionStack.Peek()) {
139            insertionStack.Pop();
140          }
141          insertionStack.Push(s.Offset + s.Length);
142        }
143        Insert(ref i, ref newSectionStart, newSection.Offset + newSection.Length, newSection.Color, insertionStack);
144      }
145     
146      #if DEBUG
147      ValidateInvariants();
148      #endif
149    }
150   
151    void Insert(ref int pos, ref int newSectionStart, int insertionEndPos, HighlightingColor color, Stack<int> insertionStack)
152    {
153      if (newSectionStart >= insertionEndPos) {
154        // nothing to insert here
155        return;
156      }
157     
158      while (insertionStack.Peek() <= newSectionStart) {
159        insertionStack.Pop();
160      }
161      while (insertionStack.Peek() < insertionEndPos) {
162        int end = insertionStack.Pop();
163        // insert the portion from newSectionStart to end
164        if (end > newSectionStart) {
165          this.Sections.Insert(pos++, new HighlightedSection {
166                                Offset = newSectionStart,
167                                Length = end - newSectionStart,
168                                Color = color
169                               });
170          newSectionStart = end;
171        }
172      }
173      if (insertionEndPos > newSectionStart) {
174        this.Sections.Insert(pos++, new HighlightedSection {
175                              Offset = newSectionStart,
176                              Length = insertionEndPos - newSectionStart,
177                              Color = color
178                             });
179        newSectionStart = insertionEndPos;
180      }
181    }
182    #endregion
183   
184    #region WriteTo / ToHtml
185    sealed class HtmlElement : IComparable<HtmlElement>
186    {
187      internal readonly int Offset;
188      internal readonly int Nesting;
189      internal readonly bool IsEnd;
190      internal readonly HighlightingColor Color;
191     
192      public HtmlElement(int offset, int nesting, bool isEnd, HighlightingColor color)
193      {
194        this.Offset = offset;
195        this.Nesting = nesting;
196        this.IsEnd = isEnd;
197        this.Color = color;
198      }
199     
200      public int CompareTo(HtmlElement other)
201      {
202        int r = Offset.CompareTo(other.Offset);
203        if (r != 0)
204          return r;
205        if (IsEnd != other.IsEnd) {
206          if (IsEnd)
207            return -1;
208          else
209            return 1;
210        } else {
211          if (IsEnd)
212            return other.Nesting.CompareTo(Nesting);
213          else
214            return Nesting.CompareTo(other.Nesting);
215        }
216      }
217    }
218   
219    /// <summary>
220    /// Writes the highlighted line to the RichTextWriter.
221    /// </summary>
222    internal void WriteTo(RichTextWriter writer)
223    {
224      int startOffset = this.DocumentLine.Offset;
225      WriteTo(writer, startOffset, startOffset + this.DocumentLine.Length);
226    }
227   
228    /// <summary>
229    /// Writes a part of the highlighted line to the RichTextWriter.
230    /// </summary>
231    internal void WriteTo(RichTextWriter writer, int startOffset, int endOffset)
232    {
233      if (writer == null)
234        throw new ArgumentNullException("writer");
235      int documentLineStartOffset = this.DocumentLine.Offset;
236      int documentLineEndOffset = documentLineStartOffset + this.DocumentLine.Length;
237      if (startOffset < documentLineStartOffset || startOffset > documentLineEndOffset)
238        throw new ArgumentOutOfRangeException("startOffset", startOffset, "Value must be between " + documentLineStartOffset + " and " + documentLineEndOffset);
239      if (endOffset < startOffset || endOffset > documentLineEndOffset)
240        throw new ArgumentOutOfRangeException("endOffset", endOffset, "Value must be between startOffset and " + documentLineEndOffset);
241      ISegment requestedSegment = new SimpleSegment(startOffset, endOffset - startOffset);
242     
243      List<HtmlElement> elements = new List<HtmlElement>();
244      for (int i = 0; i < this.Sections.Count; i++) {
245        HighlightedSection s = this.Sections[i];
246        if (SimpleSegment.GetOverlap(s, requestedSegment).Length > 0) {
247          elements.Add(new HtmlElement(s.Offset, i, false, s.Color));
248          elements.Add(new HtmlElement(s.Offset + s.Length, i, true, s.Color));
249        }
250      }
251      elements.Sort();
252     
253      IDocument document = this.Document;
254      int textOffset = startOffset;
255      foreach (HtmlElement e in elements) {
256        int newOffset = Math.Min(e.Offset, endOffset);
257        if (newOffset > startOffset) {
258          document.WriteTextTo(writer, textOffset, newOffset - textOffset);
259        }
260        textOffset = Math.Max(textOffset, newOffset);
261        if (e.IsEnd)
262          writer.EndSpan();
263        else
264          writer.BeginSpan(e.Color);
265      }
266      document.WriteTextTo(writer, textOffset, endOffset - textOffset);
267    }
268   
269    /// <summary>
270    /// Produces HTML code for the line, with &lt;span class="colorName"&gt; tags.
271    /// </summary>
272    public string ToHtml(HtmlOptions options = null)
273    {
274      StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture);
275      using (var htmlWriter = new HtmlRichTextWriter(stringWriter, options)) {
276        WriteTo(htmlWriter);
277      }
278      return stringWriter.ToString();
279    }
280   
281    /// <summary>
282    /// Produces HTML code for a section of the line, with &lt;span class="colorName"&gt; tags.
283    /// </summary>
284    public string ToHtml(int startOffset, int endOffset, HtmlOptions options = null)
285    {
286      StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture);
287      using (var htmlWriter = new HtmlRichTextWriter(stringWriter, options)) {
288        WriteTo(htmlWriter, startOffset, endOffset);
289      }
290      return stringWriter.ToString();
291    }
292   
293    /// <inheritdoc/>
294    public override string ToString()
295    {
296      return "[" + GetType().Name + " " + ToHtml() + "]";
297    }
298    #endregion
299   
300    /// <summary>
301    /// Creates a <see cref="HighlightedInlineBuilder"/> that stores the text and highlighting of this line.
302    /// </summary>
303    [Obsolete("Use ToRichText() instead")]
304    public HighlightedInlineBuilder ToInlineBuilder()
305    {
306      HighlightedInlineBuilder builder = new HighlightedInlineBuilder(Document.GetText(DocumentLine));
307      int startOffset = DocumentLine.Offset;
308      foreach (HighlightedSection section in Sections) {
309        builder.SetHighlighting(section.Offset - startOffset, section.Length, section.Color);
310      }
311      return builder;
312    }
313   
314    /// <summary>
315    /// Creates a <see cref="RichTextModel"/> that stores the highlighting of this line.
316    /// </summary>
317    public RichTextModel ToRichTextModel()
318    {
319      var builder = new RichTextModel();
320      int startOffset = DocumentLine.Offset;
321      foreach (HighlightedSection section in Sections) {
322        builder.ApplyHighlighting(section.Offset - startOffset, section.Length, section.Color);
323      }
324      return builder;
325    }
326   
327    /// <summary>
328    /// Creates a <see cref="RichText"/> that stores the text and highlighting of this line.
329    /// </summary>
330    public RichText ToRichText()
331    {
332      return new RichText(Document.GetText(DocumentLine), ToRichTextModel());
333    }
334  }
335}
Note: See TracBrowser for help on using the repository browser.