Free cookie consent management tool by TermsFeed Policy Generator

source: branches/Async/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/Highlighting/HighlightingEngine.cs @ 13508

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

#2077: created branch and added first version

File size: 10.4 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.Diagnostics;
22using System.Linq;
23using System.Text.RegularExpressions;
24#if NREFACTORY
25using ICSharpCode.NRefactory.Editor;
26#else
27using ICSharpCode.AvalonEdit.Document;
28#endif
29using ICSharpCode.AvalonEdit.Utils;
30using SpanStack = ICSharpCode.AvalonEdit.Utils.ImmutableStack<ICSharpCode.AvalonEdit.Highlighting.HighlightingSpan>;
31
32namespace ICSharpCode.AvalonEdit.Highlighting
33{
34  /// <summary>
35  /// Regex-based highlighting engine.
36  /// </summary>
37  public class HighlightingEngine
38  {
39    readonly HighlightingRuleSet mainRuleSet;
40    SpanStack spanStack = SpanStack.Empty;
41   
42    /// <summary>
43    /// Creates a new HighlightingEngine instance.
44    /// </summary>
45    public HighlightingEngine(HighlightingRuleSet mainRuleSet)
46    {
47      if (mainRuleSet == null)
48        throw new ArgumentNullException("mainRuleSet");
49      this.mainRuleSet = mainRuleSet;
50    }
51   
52    /// <summary>
53    /// Gets/sets the current span stack.
54    /// </summary>
55    public SpanStack CurrentSpanStack {
56      get { return spanStack; }
57      set {
58        spanStack = value ?? SpanStack.Empty;
59      }
60    }
61   
62    #region Highlighting Engine
63   
64    // local variables from HighlightLineInternal (are member because they are accessed by HighlighLine helper methods)
65    string lineText;
66    int lineStartOffset;
67    int position;
68   
69    /// <summary>
70    /// the HighlightedLine where highlighting output is being written to.
71    /// if this variable is null, nothing is highlighted and only the span state is updated
72    /// </summary>
73    HighlightedLine highlightedLine;
74   
75    /// <summary>
76    /// Highlights the specified line in the specified document.
77    ///
78    /// Before calling this method, <see cref="CurrentSpanStack"/> must be set to the proper
79    /// state for the beginning of this line. After highlighting has completed,
80    /// <see cref="CurrentSpanStack"/> will be updated to represent the state after the line.
81    /// </summary>
82    public HighlightedLine HighlightLine(IDocument document, IDocumentLine line)
83    {
84      this.lineStartOffset = line.Offset;
85      this.lineText = document.GetText(line);
86      try {
87        this.highlightedLine = new HighlightedLine(document, line);
88        HighlightLineInternal();
89        return this.highlightedLine;
90      } finally {
91        this.highlightedLine = null;
92        this.lineText = null;
93        this.lineStartOffset = 0;
94      }
95    }
96   
97    /// <summary>
98    /// Updates <see cref="CurrentSpanStack"/> for the specified line in the specified document.
99    ///
100    /// Before calling this method, <see cref="CurrentSpanStack"/> must be set to the proper
101    /// state for the beginning of this line. After highlighting has completed,
102    /// <see cref="CurrentSpanStack"/> will be updated to represent the state after the line.
103    /// </summary>
104    public void ScanLine(IDocument document, IDocumentLine line)
105    {
106      //this.lineStartOffset = line.Offset; not necessary for scanning
107      this.lineText = document.GetText(line);
108      try {
109        Debug.Assert(highlightedLine == null);
110        HighlightLineInternal();
111      } finally {
112        this.lineText = null;
113      }
114    }
115   
116    void HighlightLineInternal()
117    {
118      position = 0;
119      ResetColorStack();
120      HighlightingRuleSet currentRuleSet = this.CurrentRuleSet;
121      Stack<Match[]> storedMatchArrays = new Stack<Match[]>();
122      Match[] matches = AllocateMatchArray(currentRuleSet.Spans.Count);
123      Match endSpanMatch = null;
124     
125      while (true) {
126        for (int i = 0; i < matches.Length; i++) {
127          if (matches[i] == null || (matches[i].Success && matches[i].Index < position))
128            matches[i] = currentRuleSet.Spans[i].StartExpression.Match(lineText, position);
129        }
130        if (endSpanMatch == null && !spanStack.IsEmpty)
131          endSpanMatch = spanStack.Peek().EndExpression.Match(lineText, position);
132       
133        Match firstMatch = Minimum(matches, endSpanMatch);
134        if (firstMatch == null)
135          break;
136       
137        HighlightNonSpans(firstMatch.Index);
138       
139        Debug.Assert(position == firstMatch.Index);
140       
141        if (firstMatch == endSpanMatch) {
142          HighlightingSpan poppedSpan = spanStack.Peek();
143          if (!poppedSpan.SpanColorIncludesEnd)
144            PopColor(); // pop SpanColor
145          PushColor(poppedSpan.EndColor);
146          position = firstMatch.Index + firstMatch.Length;
147          PopColor(); // pop EndColor
148          if (poppedSpan.SpanColorIncludesEnd)
149            PopColor(); // pop SpanColor
150          spanStack = spanStack.Pop();
151          currentRuleSet = this.CurrentRuleSet;
152          //FreeMatchArray(matches);
153          if (storedMatchArrays.Count > 0) {
154            matches = storedMatchArrays.Pop();
155            int index = currentRuleSet.Spans.IndexOf(poppedSpan);
156            Debug.Assert(index >= 0 && index < matches.Length);
157            if (matches[index].Index == position) {
158              throw new InvalidOperationException(
159                "A highlighting span matched 0 characters, which would cause an endless loop.\n" +
160                "Change the highlighting definition so that either the start or the end regex matches at least one character.\n" +
161                "Start regex: " + poppedSpan.StartExpression + "\n" +
162                "End regex: " + poppedSpan.EndExpression);
163            }
164          } else {
165            matches = AllocateMatchArray(currentRuleSet.Spans.Count);
166          }
167        } else {
168          int index = Array.IndexOf(matches, firstMatch);
169          Debug.Assert(index >= 0);
170          HighlightingSpan newSpan = currentRuleSet.Spans[index];
171          spanStack = spanStack.Push(newSpan);
172          currentRuleSet = this.CurrentRuleSet;
173          storedMatchArrays.Push(matches);
174          matches = AllocateMatchArray(currentRuleSet.Spans.Count);
175          if (newSpan.SpanColorIncludesStart)
176            PushColor(newSpan.SpanColor);
177          PushColor(newSpan.StartColor);
178          position = firstMatch.Index + firstMatch.Length;
179          PopColor();
180          if (!newSpan.SpanColorIncludesStart)
181            PushColor(newSpan.SpanColor);
182        }
183        endSpanMatch = null;
184      }
185      HighlightNonSpans(lineText.Length);
186     
187      PopAllColors();
188    }
189   
190    void HighlightNonSpans(int until)
191    {
192      Debug.Assert(position <= until);
193      if (position == until)
194        return;
195      if (highlightedLine != null) {
196        IList<HighlightingRule> rules = CurrentRuleSet.Rules;
197        Match[] matches = AllocateMatchArray(rules.Count);
198        while (true) {
199          for (int i = 0; i < matches.Length; i++) {
200            if (matches[i] == null || (matches[i].Success && matches[i].Index < position))
201              matches[i] = rules[i].Regex.Match(lineText, position, until - position);
202          }
203          Match firstMatch = Minimum(matches, null);
204          if (firstMatch == null)
205            break;
206         
207          position = firstMatch.Index;
208          int ruleIndex = Array.IndexOf(matches, firstMatch);
209          if (firstMatch.Length == 0) {
210            throw new InvalidOperationException(
211              "A highlighting rule matched 0 characters, which would cause an endless loop.\n" +
212              "Change the highlighting definition so that the rule matches at least one character.\n" +
213              "Regex: " + rules[ruleIndex].Regex);
214          }
215          PushColor(rules[ruleIndex].Color);
216          position = firstMatch.Index + firstMatch.Length;
217          PopColor();
218        }
219        //FreeMatchArray(matches);
220      }
221      position = until;
222    }
223   
224    static readonly HighlightingRuleSet emptyRuleSet = new HighlightingRuleSet() { Name = "EmptyRuleSet" };
225   
226    HighlightingRuleSet CurrentRuleSet {
227      get {
228        if (spanStack.IsEmpty)
229          return mainRuleSet;
230        else
231          return spanStack.Peek().RuleSet ?? emptyRuleSet;
232      }
233    }
234    #endregion
235   
236    #region Color Stack Management
237    Stack<HighlightedSection> highlightedSectionStack;
238    HighlightedSection lastPoppedSection;
239   
240    void ResetColorStack()
241    {
242      Debug.Assert(position == 0);
243      lastPoppedSection = null;
244      if (highlightedLine == null) {
245        highlightedSectionStack = null;
246      } else {
247        highlightedSectionStack = new Stack<HighlightedSection>();
248        foreach (HighlightingSpan span in spanStack.Reverse()) {
249          PushColor(span.SpanColor);
250        }
251      }
252    }
253   
254    void PushColor(HighlightingColor color)
255    {
256      if (highlightedLine == null)
257        return;
258      if (color == null) {
259        highlightedSectionStack.Push(null);
260      } else if (lastPoppedSection != null && lastPoppedSection.Color == color
261                 && lastPoppedSection.Offset + lastPoppedSection.Length == position + lineStartOffset)
262      {
263        highlightedSectionStack.Push(lastPoppedSection);
264        lastPoppedSection = null;
265      } else {
266        HighlightedSection hs = new HighlightedSection {
267          Offset = position + lineStartOffset,
268          Color = color
269        };
270        highlightedLine.Sections.Add(hs);
271        highlightedSectionStack.Push(hs);
272        lastPoppedSection = null;
273      }
274    }
275   
276    void PopColor()
277    {
278      if (highlightedLine == null)
279        return;
280      HighlightedSection s = highlightedSectionStack.Pop();
281      if (s != null) {
282        s.Length = (position + lineStartOffset) - s.Offset;
283        if (s.Length == 0)
284          highlightedLine.Sections.Remove(s);
285        else
286          lastPoppedSection = s;
287      }
288    }
289   
290    void PopAllColors()
291    {
292      if (highlightedSectionStack != null) {
293        while (highlightedSectionStack.Count > 0)
294          PopColor();
295      }
296    }
297    #endregion
298   
299    #region Match helpers
300    /// <summary>
301    /// Returns the first match from the array or endSpanMatch.
302    /// </summary>
303    static Match Minimum(Match[] arr, Match endSpanMatch)
304    {
305      Match min = null;
306      foreach (Match v in arr) {
307        if (v.Success && (min == null || v.Index < min.Index))
308          min = v;
309      }
310      if (endSpanMatch != null && endSpanMatch.Success && (min == null || endSpanMatch.Index < min.Index))
311        return endSpanMatch;
312      else
313        return min;
314    }
315   
316    static Match[] AllocateMatchArray(int count)
317    {
318      if (count == 0)
319        return Empty<Match>.Array;
320      else
321        return new Match[count];
322    }
323    #endregion
324  }
325}
Note: See TracBrowser for help on using the repository browser.