Free cookie consent management tool by TermsFeed Policy Generator

source: branches/2893_BNLR/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/Highlighting/DocumentHighlighter.cs @ 18066

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

#2077: created branch and added first version

File size: 11.3 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 ICSharpCode.NRefactory.Editor;
24using ICSharpCode.AvalonEdit.Document;
25using ICSharpCode.AvalonEdit.Utils;
26using SpanStack = ICSharpCode.AvalonEdit.Utils.ImmutableStack<ICSharpCode.AvalonEdit.Highlighting.HighlightingSpan>;
27
28namespace ICSharpCode.AvalonEdit.Highlighting
29{
30  /// <summary>
31  /// This class can syntax-highlight a document.
32  /// It automatically manages invalidating the highlighting when the document changes.
33  /// </summary>
34  public class DocumentHighlighter : ILineTracker, IHighlighter
35  {
36    /// <summary>
37    /// Stores the span state at the end of each line.
38    /// storedSpanStacks[0] = state at beginning of document
39    /// storedSpanStacks[i] = state after line i
40    /// </summary>
41    readonly CompressingTreeList<SpanStack> storedSpanStacks = new CompressingTreeList<SpanStack>(object.ReferenceEquals);
42    readonly CompressingTreeList<bool> isValid = new CompressingTreeList<bool>((a, b) => a == b);
43    readonly IDocument document;
44    readonly IHighlightingDefinition definition;
45    readonly HighlightingEngine engine;
46    readonly WeakLineTracker weakLineTracker;
47    bool isHighlighting;
48    bool isInHighlightingGroup;
49    bool isDisposed;
50   
51    /// <summary>
52    /// Gets the document that this DocumentHighlighter is highlighting.
53    /// </summary>
54    public IDocument Document {
55      get { return document; }
56    }
57   
58    /// <summary>
59    /// Creates a new DocumentHighlighter instance.
60    /// </summary>
61    public DocumentHighlighter(TextDocument document, IHighlightingDefinition definition)
62    {
63      if (document == null)
64        throw new ArgumentNullException("document");
65      if (definition == null)
66        throw new ArgumentNullException("definition");
67      this.document = document;
68      this.definition = definition;
69      this.engine = new HighlightingEngine(definition.MainRuleSet);
70      document.VerifyAccess();
71      weakLineTracker = WeakLineTracker.Register(document, this);
72      InvalidateHighlighting();
73    }
74   
75    #if NREFACTORY
76    /// <summary>
77    /// Creates a new DocumentHighlighter instance.
78    /// </summary>
79    public DocumentHighlighter(ReadOnlyDocument document, IHighlightingDefinition definition)
80    {
81      if (document == null)
82        throw new ArgumentNullException("document");
83      if (definition == null)
84        throw new ArgumentNullException("definition");
85      this.document = document;
86      this.definition = definition;
87      this.engine = new HighlightingEngine(definition.MainRuleSet);
88      InvalidateHighlighting();
89    }
90    #endif
91   
92    /// <summary>
93    /// Disposes the document highlighter.
94    /// </summary>
95    public void Dispose()
96    {
97      if (weakLineTracker != null)
98        weakLineTracker.Deregister();
99      isDisposed = true;
100    }
101   
102    void ILineTracker.BeforeRemoveLine(DocumentLine line)
103    {
104      CheckIsHighlighting();
105      int number = line.LineNumber;
106      storedSpanStacks.RemoveAt(number);
107      isValid.RemoveAt(number);
108      if (number < isValid.Count) {
109        isValid[number] = false;
110        if (number < firstInvalidLine)
111          firstInvalidLine = number;
112      }
113    }
114   
115    void ILineTracker.SetLineLength(DocumentLine line, int newTotalLength)
116    {
117      CheckIsHighlighting();
118      int number = line.LineNumber;
119      isValid[number] = false;
120      if (number < firstInvalidLine)
121        firstInvalidLine = number;
122    }
123   
124    void ILineTracker.LineInserted(DocumentLine insertionPos, DocumentLine newLine)
125    {
126      CheckIsHighlighting();
127      Debug.Assert(insertionPos.LineNumber + 1 == newLine.LineNumber);
128      int lineNumber = newLine.LineNumber;
129      storedSpanStacks.Insert(lineNumber, null);
130      isValid.Insert(lineNumber, false);
131      if (lineNumber < firstInvalidLine)
132        firstInvalidLine = lineNumber;
133    }
134   
135    void ILineTracker.RebuildDocument()
136    {
137      InvalidateHighlighting();
138    }
139   
140    void ILineTracker.ChangeComplete(DocumentChangeEventArgs e)
141    {
142    }
143   
144    ImmutableStack<HighlightingSpan> initialSpanStack = SpanStack.Empty;
145   
146    /// <summary>
147    /// Gets/sets the the initial span stack of the document. Default value is <see cref="SpanStack.Empty" />.
148    /// </summary>
149    public ImmutableStack<HighlightingSpan> InitialSpanStack {
150      get { return initialSpanStack; }
151      set {
152        initialSpanStack = value ?? SpanStack.Empty;
153        InvalidateHighlighting();
154      }
155    }
156   
157    /// <summary>
158    /// Invalidates all stored highlighting info.
159    /// When the document changes, the highlighting is invalidated automatically, this method
160    /// needs to be called only when there are changes to the highlighting rule set.
161    /// </summary>
162    public void InvalidateHighlighting()
163    {
164      CheckIsHighlighting();
165      storedSpanStacks.Clear();
166      storedSpanStacks.Add(initialSpanStack);
167      storedSpanStacks.InsertRange(1, document.LineCount, null);
168      isValid.Clear();
169      isValid.Add(true);
170      isValid.InsertRange(1, document.LineCount, false);
171      firstInvalidLine = 1;
172    }
173   
174    int firstInvalidLine;
175   
176    /// <inheritdoc/>
177    public HighlightedLine HighlightLine(int lineNumber)
178    {
179      ThrowUtil.CheckInRangeInclusive(lineNumber, "lineNumber", 1, document.LineCount);
180      CheckIsHighlighting();
181      isHighlighting = true;
182      try {
183        HighlightUpTo(lineNumber - 1);
184        IDocumentLine line = document.GetLineByNumber(lineNumber);
185        HighlightedLine result = engine.HighlightLine(document, line);
186        UpdateTreeList(lineNumber);
187        return result;
188      } finally {
189        isHighlighting = false;
190      }
191    }
192   
193    /// <summary>
194    /// Gets the span stack at the end of the specified line.
195    /// -> GetSpanStack(1) returns the spans at the start of the second line.
196    /// </summary>
197    /// <remarks>
198    /// GetSpanStack(0) is valid and will return <see cref="InitialSpanStack"/>.
199    /// The elements are returned in inside-out order (first element of result enumerable is the color of the innermost span).
200    /// </remarks>
201    public SpanStack GetSpanStack(int lineNumber)
202    {
203      ThrowUtil.CheckInRangeInclusive(lineNumber, "lineNumber", 0, document.LineCount);
204      if (firstInvalidLine <= lineNumber) {
205        UpdateHighlightingState(lineNumber);
206      }
207      return storedSpanStacks[lineNumber];
208    }
209   
210    /// <inheritdoc/>
211    public IEnumerable<HighlightingColor> GetColorStack(int lineNumber)
212    {
213      return GetSpanStack(lineNumber).Select(s => s.SpanColor).Where(s => s != null);
214    }
215   
216    void CheckIsHighlighting()
217    {
218      if (isDisposed) {
219        throw new ObjectDisposedException("DocumentHighlighter");
220      }
221      if (isHighlighting) {
222        throw new InvalidOperationException("Invalid call - a highlighting operation is currently running.");
223      }
224    }
225   
226    /// <inheritdoc/>
227    public void UpdateHighlightingState(int lineNumber)
228    {
229      CheckIsHighlighting();
230      isHighlighting = true;
231      try {
232        HighlightUpTo(lineNumber);
233      } finally {
234        isHighlighting = false;
235      }
236    }
237   
238    /// <summary>
239    /// Sets the engine's CurrentSpanStack to the end of the target line.
240    /// Updates the span stack for all lines up to (and including) the target line, if necessary.
241    /// </summary>
242    void HighlightUpTo(int targetLineNumber)
243    {
244      for (int currentLine = 0; currentLine <= targetLineNumber; currentLine++) {
245        if (firstInvalidLine > currentLine) {
246          // (this branch is always taken on the first loop iteration, as firstInvalidLine > 0)
247         
248          if (firstInvalidLine <= targetLineNumber) {
249            // Skip valid lines to next invalid line:
250            engine.CurrentSpanStack = storedSpanStacks[firstInvalidLine - 1];
251            currentLine = firstInvalidLine;
252          } else {
253            // Skip valid lines to target line:
254            engine.CurrentSpanStack = storedSpanStacks[targetLineNumber];
255            break;
256          }
257        }
258        Debug.Assert(EqualSpanStacks(engine.CurrentSpanStack, storedSpanStacks[currentLine - 1]));
259        engine.ScanLine(document, document.GetLineByNumber(currentLine));
260        UpdateTreeList(currentLine);
261      }
262      Debug.Assert(EqualSpanStacks(engine.CurrentSpanStack, storedSpanStacks[targetLineNumber]));
263    }
264   
265    void UpdateTreeList(int lineNumber)
266    {
267      if (!EqualSpanStacks(engine.CurrentSpanStack, storedSpanStacks[lineNumber])) {
268        isValid[lineNumber] = true;
269        //Debug.WriteLine("Span stack in line " + lineNumber + " changed from " + storedSpanStacks[lineNumber] + " to " + spanStack);
270        storedSpanStacks[lineNumber] = engine.CurrentSpanStack;
271        if (lineNumber + 1 < isValid.Count) {
272          isValid[lineNumber + 1] = false;
273          firstInvalidLine = lineNumber + 1;
274        } else {
275          firstInvalidLine = int.MaxValue;
276        }
277        if (lineNumber + 1 < document.LineCount)
278          OnHighlightStateChanged(lineNumber + 1, lineNumber + 1);
279      } else if (firstInvalidLine == lineNumber) {
280        isValid[lineNumber] = true;
281        firstInvalidLine = isValid.IndexOf(false);
282        if (firstInvalidLine < 0)
283          firstInvalidLine = int.MaxValue;
284      }
285    }
286   
287    static bool EqualSpanStacks(SpanStack a, SpanStack b)
288    {
289      // We must use value equality between the stacks because HighlightingColorizer.OnHighlightStateChanged
290      // depends on the fact that equal input state + unchanged line contents produce equal output state.
291      if (a == b)
292        return true;
293      if (a == null || b == null)
294        return false;
295      while (!a.IsEmpty && !b.IsEmpty) {
296        if (a.Peek() != b.Peek())
297          return false;
298        a = a.Pop();
299        b = b.Pop();
300        if (a == b)
301          return true;
302      }
303      return a.IsEmpty && b.IsEmpty;
304    }
305   
306    /// <inheritdoc/>
307    public event HighlightingStateChangedEventHandler HighlightingStateChanged;
308   
309    /// <summary>
310    /// Is called when the highlighting state at the end of the specified line has changed.
311    /// </summary>
312    /// <remarks>This callback must not call HighlightLine or InvalidateHighlighting.
313    /// It may call GetSpanStack, but only for the changed line and lines above.
314    /// This method must not modify the document.</remarks>
315    protected virtual void OnHighlightStateChanged(int fromLineNumber, int toLineNumber)
316    {
317      if (HighlightingStateChanged != null)
318        HighlightingStateChanged(fromLineNumber, toLineNumber);
319    }
320   
321    /// <inheritdoc/>
322    public HighlightingColor DefaultTextColor {
323      get { return null; }
324    }
325   
326    /// <inheritdoc/>
327    public void BeginHighlighting()
328    {
329      if (isInHighlightingGroup)
330        throw new InvalidOperationException("Highlighting group is already open");
331      isInHighlightingGroup = true;
332    }
333   
334    /// <inheritdoc/>
335    public void EndHighlighting()
336    {
337      if (!isInHighlightingGroup)
338        throw new InvalidOperationException("Highlighting group is not open");
339      isInHighlightingGroup = false;
340    }
341   
342    /// <inheritdoc/>
343    public HighlightingColor GetNamedColor(string name)
344    {
345      return definition.GetNamedColor(name);
346    }
347  }
348}
Note: See TracBrowser for help on using the repository browser.