Browse Source

Move regex-based highlighting engine from DocumentHighlighter into its own new class (HighlightingEngine).

DocumentHighlighter now only is only responsible for maintaining the highlighting state (span stacks at line boundaries).
pull/32/merge
Daniel Grunwald 13 years ago
parent
commit
76fed2b8d0
  1. 6
      TODOnewNR.txt
  2. 274
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/DocumentHighlighter.cs
  3. 303
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingEngine.cs
  4. 1
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj
  5. 12
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/CompressingTreeList.cs

6
TODOnewNR.txt

@ -142,6 +142,12 @@ Functionality changes:
The IUnresolvedFile is stored permanently (both in ParserService and in the IProjectContents). The IUnresolvedFile is stored permanently (both in ParserService and in the IProjectContents).
Solution model:
The class 'Solution' has been replaced with the interface 'ISolution'.
The static events that report changes to the solution (e.g. project added) no longer exist on IProjectService;
instead the ISolution.Projects collection itself has a changed event.
Text editor and document services: Text editor and document services:
In SharpDevelop 4.x it was possible to use IDocument.GetService(typeof(ITextEditor)) to find the In SharpDevelop 4.x it was possible to use IDocument.GetService(typeof(ITextEditor)) to find the
editor that presents the document. editor that presents the document.

274
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/DocumentHighlighter.cs

@ -29,6 +29,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
readonly CompressingTreeList<bool> isValid = new CompressingTreeList<bool>((a, b) => a == b); readonly CompressingTreeList<bool> isValid = new CompressingTreeList<bool>((a, b) => a == b);
readonly IDocument document; readonly IDocument document;
readonly IHighlightingDefinition definition; readonly IHighlightingDefinition definition;
readonly HighlightingEngine engine;
readonly WeakLineTracker weakLineTracker; readonly WeakLineTracker weakLineTracker;
bool isHighlighting; bool isHighlighting;
bool isInHighlightingGroup; bool isInHighlightingGroup;
@ -52,6 +53,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
throw new ArgumentNullException("definition"); throw new ArgumentNullException("definition");
this.document = document; this.document = document;
this.definition = definition; this.definition = definition;
this.engine = new HighlightingEngine(definition.MainRuleSet);
document.VerifyAccess(); document.VerifyAccess();
weakLineTracker = WeakLineTracker.Register(document, this); weakLineTracker = WeakLineTracker.Register(document, this);
InvalidateHighlighting(); InvalidateHighlighting();
@ -68,6 +70,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
throw new ArgumentNullException("definition"); throw new ArgumentNullException("definition");
this.document = document; this.document = document;
this.definition = definition; this.definition = definition;
this.engine = new HighlightingEngine(definition.MainRuleSet);
InvalidateHighlighting(); InvalidateHighlighting();
} }
@ -131,10 +134,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
public ImmutableStack<HighlightingSpan> InitialSpanStack { public ImmutableStack<HighlightingSpan> InitialSpanStack {
get { return initialSpanStack; } get { return initialSpanStack; }
set { set {
if (value == null) initialSpanStack = value ?? SpanStack.Empty;
initialSpanStack = SpanStack.Empty;
else
initialSpanStack = value;
InvalidateHighlighting(); InvalidateHighlighting();
} }
} }
@ -167,11 +167,10 @@ namespace ICSharpCode.AvalonEdit.Highlighting
try { try {
HighlightUpTo(lineNumber - 1); HighlightUpTo(lineNumber - 1);
IDocumentLine line = document.GetLineByNumber(lineNumber); IDocumentLine line = document.GetLineByNumber(lineNumber);
highlightedLine = new HighlightedLine(document, line); HighlightedLine result = engine.HighlightLine(document, line);
HighlightLineAndUpdateTreeList(line, lineNumber); UpdateTreeList(lineNumber);
return highlightedLine; return result;
} finally { } finally {
highlightedLine = null;
isHighlighting = false; isHighlighting = false;
} }
} }
@ -221,23 +220,39 @@ namespace ICSharpCode.AvalonEdit.Highlighting
} }
} }
/// <summary>
/// Sets the engine's CurrentSpanStack to the end of the target line.
/// Updates the span stack for all lines up to (and including) the target line, if necessary.
/// </summary>
void HighlightUpTo(int targetLineNumber) void HighlightUpTo(int targetLineNumber)
{ {
Debug.Assert(highlightedLine == null); // ensure this method is only outside the actual highlighting logic for (int currentLine = 0; currentLine <= targetLineNumber; currentLine++) {
while (firstInvalidLine <= targetLineNumber) { if (firstInvalidLine > currentLine) {
HighlightLineAndUpdateTreeList(document.GetLineByNumber(firstInvalidLine), firstInvalidLine); // (this branch is always taken on the first loop iteration, as firstInvalidLine > 0)
if (firstInvalidLine <= targetLineNumber) {
// Skip valid lines to next invalid line:
engine.CurrentSpanStack = storedSpanStacks[firstInvalidLine - 1];
currentLine = firstInvalidLine;
} else {
// Skip valid lines to target line:
engine.CurrentSpanStack = storedSpanStacks[targetLineNumber];
break;
} }
} }
Debug.Assert(EqualSpanStacks(engine.CurrentSpanStack, storedSpanStacks[currentLine - 1]));
engine.ScanLine(document, document.GetLineByNumber(currentLine));
UpdateTreeList(currentLine);
}
Debug.Assert(EqualSpanStacks(engine.CurrentSpanStack, storedSpanStacks[targetLineNumber]));
}
void HighlightLineAndUpdateTreeList(IDocumentLine line, int lineNumber) void UpdateTreeList(int lineNumber)
{ {
//Debug.WriteLine("Highlight line " + lineNumber + (highlightedLine != null ? "" : " (span stack only)")); if (!EqualSpanStacks(engine.CurrentSpanStack, storedSpanStacks[lineNumber])) {
spanStack = storedSpanStacks[lineNumber - 1];
HighlightLineInternal(line);
if (!EqualSpanStacks(spanStack, storedSpanStacks[lineNumber])) {
isValid[lineNumber] = true; isValid[lineNumber] = true;
//Debug.WriteLine("Span stack in line " + lineNumber + " changed from " + storedSpanStacks[lineNumber] + " to " + spanStack); //Debug.WriteLine("Span stack in line " + lineNumber + " changed from " + storedSpanStacks[lineNumber] + " to " + spanStack);
storedSpanStacks[lineNumber] = spanStack; storedSpanStacks[lineNumber] = engine.CurrentSpanStack;
if (lineNumber + 1 < isValid.Count) { if (lineNumber + 1 < isValid.Count) {
isValid[lineNumber + 1] = false; isValid[lineNumber + 1] = false;
firstInvalidLine = lineNumber + 1; firstInvalidLine = lineNumber + 1;
@ -288,231 +303,6 @@ namespace ICSharpCode.AvalonEdit.Highlighting
HighlightingStateChanged(fromLineNumber, toLineNumber); HighlightingStateChanged(fromLineNumber, toLineNumber);
} }
#region Highlighting Engine
SpanStack spanStack;
// local variables from HighlightLineInternal (are member because they are accessed by HighlighLine helper methods)
string lineText;
int lineStartOffset;
int position;
/// <summary>
/// the HighlightedLine where highlighting output is being written to.
/// if this variable is null, nothing is highlighted and only the span state is updated
/// </summary>
HighlightedLine highlightedLine;
void HighlightLineInternal(IDocumentLine line)
{
lineStartOffset = line.Offset;
lineText = document.GetText(lineStartOffset, line.Length);
position = 0;
ResetColorStack();
HighlightingRuleSet currentRuleSet = this.CurrentRuleSet;
Stack<Match[]> storedMatchArrays = new Stack<Match[]>();
Match[] matches = AllocateMatchArray(currentRuleSet.Spans.Count);
Match endSpanMatch = null;
while (true) {
for (int i = 0; i < matches.Length; i++) {
if (matches[i] == null || (matches[i].Success && matches[i].Index < position))
matches[i] = currentRuleSet.Spans[i].StartExpression.Match(lineText, position);
}
if (endSpanMatch == null && !spanStack.IsEmpty)
endSpanMatch = spanStack.Peek().EndExpression.Match(lineText, position);
Match firstMatch = Minimum(matches, endSpanMatch);
if (firstMatch == null)
break;
HighlightNonSpans(firstMatch.Index);
Debug.Assert(position == firstMatch.Index);
if (firstMatch == endSpanMatch) {
HighlightingSpan poppedSpan = spanStack.Peek();
if (!poppedSpan.SpanColorIncludesEnd)
PopColor(); // pop SpanColor
PushColor(poppedSpan.EndColor);
position = firstMatch.Index + firstMatch.Length;
PopColor(); // pop EndColor
if (poppedSpan.SpanColorIncludesEnd)
PopColor(); // pop SpanColor
spanStack = spanStack.Pop();
currentRuleSet = this.CurrentRuleSet;
//FreeMatchArray(matches);
if (storedMatchArrays.Count > 0) {
matches = storedMatchArrays.Pop();
int index = currentRuleSet.Spans.IndexOf(poppedSpan);
Debug.Assert(index >= 0 && index < matches.Length);
if (matches[index].Index == position) {
throw new InvalidOperationException(
"A highlighting span matched 0 characters, which would cause an endless loop.\n" +
"Change the highlighting definition so that either the start or the end regex matches at least one character.\n" +
"Start regex: " + poppedSpan.StartExpression + "\n" +
"End regex: " + poppedSpan.EndExpression);
}
} else {
matches = AllocateMatchArray(currentRuleSet.Spans.Count);
}
} else {
int index = Array.IndexOf(matches, firstMatch);
Debug.Assert(index >= 0);
HighlightingSpan newSpan = currentRuleSet.Spans[index];
spanStack = spanStack.Push(newSpan);
currentRuleSet = this.CurrentRuleSet;
storedMatchArrays.Push(matches);
matches = AllocateMatchArray(currentRuleSet.Spans.Count);
if (newSpan.SpanColorIncludesStart)
PushColor(newSpan.SpanColor);
PushColor(newSpan.StartColor);
position = firstMatch.Index + firstMatch.Length;
PopColor();
if (!newSpan.SpanColorIncludesStart)
PushColor(newSpan.SpanColor);
}
endSpanMatch = null;
}
HighlightNonSpans(line.Length);
PopAllColors();
}
void HighlightNonSpans(int until)
{
Debug.Assert(position <= until);
if (position == until)
return;
if (highlightedLine != null) {
IList<HighlightingRule> rules = CurrentRuleSet.Rules;
Match[] matches = AllocateMatchArray(rules.Count);
while (true) {
for (int i = 0; i < matches.Length; i++) {
if (matches[i] == null || (matches[i].Success && matches[i].Index < position))
matches[i] = rules[i].Regex.Match(lineText, position, until - position);
}
Match firstMatch = Minimum(matches, null);
if (firstMatch == null)
break;
position = firstMatch.Index;
int ruleIndex = Array.IndexOf(matches, firstMatch);
if (firstMatch.Length == 0) {
throw new InvalidOperationException(
"A highlighting rule matched 0 characters, which would cause an endless loop.\n" +
"Change the highlighting definition so that the rule matches at least one character.\n" +
"Regex: " + rules[ruleIndex].Regex);
}
PushColor(rules[ruleIndex].Color);
position = firstMatch.Index + firstMatch.Length;
PopColor();
}
//FreeMatchArray(matches);
}
position = until;
}
static readonly HighlightingRuleSet emptyRuleSet = new HighlightingRuleSet() { Name = "EmptyRuleSet" };
HighlightingRuleSet CurrentRuleSet {
get {
if (spanStack.IsEmpty)
return definition.MainRuleSet;
else
return spanStack.Peek().RuleSet ?? emptyRuleSet;
}
}
#endregion
#region Color Stack Management
Stack<HighlightedSection> highlightedSectionStack;
HighlightedSection lastPoppedSection;
void ResetColorStack()
{
Debug.Assert(position == 0);
lastPoppedSection = null;
if (highlightedLine == null) {
highlightedSectionStack = null;
} else {
highlightedSectionStack = new Stack<HighlightedSection>();
foreach (HighlightingSpan span in spanStack.Reverse()) {
PushColor(span.SpanColor);
}
}
}
void PushColor(HighlightingColor color)
{
if (highlightedLine == null)
return;
if (color == null) {
highlightedSectionStack.Push(null);
} else if (lastPoppedSection != null && lastPoppedSection.Color == color
&& lastPoppedSection.Offset + lastPoppedSection.Length == position + lineStartOffset)
{
highlightedSectionStack.Push(lastPoppedSection);
lastPoppedSection = null;
} else {
HighlightedSection hs = new HighlightedSection {
Offset = position + lineStartOffset,
Color = color
};
highlightedLine.Sections.Add(hs);
highlightedSectionStack.Push(hs);
lastPoppedSection = null;
}
}
void PopColor()
{
if (highlightedLine == null)
return;
HighlightedSection s = highlightedSectionStack.Pop();
if (s != null) {
s.Length = (position + lineStartOffset) - s.Offset;
if (s.Length == 0)
highlightedLine.Sections.Remove(s);
else
lastPoppedSection = s;
}
}
void PopAllColors()
{
if (highlightedSectionStack != null) {
while (highlightedSectionStack.Count > 0)
PopColor();
}
}
#endregion
#region Match helpers
/// <summary>
/// Returns the first match from the array or endSpanMatch.
/// </summary>
static Match Minimum(Match[] arr, Match endSpanMatch)
{
Match min = null;
foreach (Match v in arr) {
if (v.Success && (min == null || v.Index < min.Index))
min = v;
}
if (endSpanMatch != null && endSpanMatch.Success && (min == null || endSpanMatch.Index < min.Index))
return endSpanMatch;
else
return min;
}
static Match[] AllocateMatchArray(int count)
{
if (count == 0)
return Empty<Match>.Array;
else
return new Match[count];
}
#endregion
/// <inheritdoc/> /// <inheritdoc/>
public HighlightingColor DefaultTextColor { public HighlightingColor DefaultTextColor {
get { return null; } get { return null; }

303
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingEngine.cs

@ -0,0 +1,303 @@
// 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.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using ICSharpCode.NRefactory.Editor;
using ICSharpCode.AvalonEdit.Utils;
using SpanStack = ICSharpCode.NRefactory.Utils.ImmutableStack<ICSharpCode.AvalonEdit.Highlighting.HighlightingSpan>;
namespace ICSharpCode.AvalonEdit.Highlighting
{
/// <summary>
/// Regex-based highlighting engine.
/// </summary>
public class HighlightingEngine
{
readonly HighlightingRuleSet mainRuleSet;
SpanStack spanStack = SpanStack.Empty;
public HighlightingEngine(HighlightingRuleSet mainRuleSet)
{
if (mainRuleSet == null)
throw new ArgumentNullException("mainRuleSet");
this.mainRuleSet = mainRuleSet;
}
/// <summary>
/// Gets/sets the current span stack.
/// </summary>
public SpanStack CurrentSpanStack {
get { return spanStack; }
set {
spanStack = value ?? SpanStack.Empty;
}
}
#region Highlighting Engine
// local variables from HighlightLineInternal (are member because they are accessed by HighlighLine helper methods)
string lineText;
int lineStartOffset;
int position;
/// <summary>
/// the HighlightedLine where highlighting output is being written to.
/// if this variable is null, nothing is highlighted and only the span state is updated
/// </summary>
HighlightedLine highlightedLine;
/// <summary>
/// Highlights the specified line in the specified document.
///
/// Before calling this method, <see cref="CurrentSpanStack"/> must be set to the proper
/// state for the beginning of this line. After highlighting has completed,
/// <see cref="CurrentSpanStack"/> will be updated to represent the state after the line.
/// </summary>
public HighlightedLine HighlightLine(IDocument document, IDocumentLine line)
{
this.lineStartOffset = line.Offset;
this.lineText = document.GetText(line);
try {
this.highlightedLine = new HighlightedLine(document, line);
HighlightLineInternal();
return this.highlightedLine;
} finally {
this.highlightedLine = null;
this.lineText = null;
this.lineStartOffset = 0;
}
}
/// <summary>
/// Updates <see cref="CurrentSpanStack"/> for the specified line in the specified document.
///
/// Before calling this method, <see cref="CurrentSpanStack"/> must be set to the proper
/// state for the beginning of this line. After highlighting has completed,
/// <see cref="CurrentSpanStack"/> will be updated to represent the state after the line.
/// </summary>
public void ScanLine(IDocument document, IDocumentLine line)
{
//this.lineStartOffset = line.Offset; not necessary for scanning
this.lineText = document.GetText(line);
try {
Debug.Assert(highlightedLine == null);
HighlightLineInternal();
} finally {
this.lineText = null;
}
}
void HighlightLineInternal()
{
position = 0;
ResetColorStack();
HighlightingRuleSet currentRuleSet = this.CurrentRuleSet;
Stack<Match[]> storedMatchArrays = new Stack<Match[]>();
Match[] matches = AllocateMatchArray(currentRuleSet.Spans.Count);
Match endSpanMatch = null;
while (true) {
for (int i = 0; i < matches.Length; i++) {
if (matches[i] == null || (matches[i].Success && matches[i].Index < position))
matches[i] = currentRuleSet.Spans[i].StartExpression.Match(lineText, position);
}
if (endSpanMatch == null && !spanStack.IsEmpty)
endSpanMatch = spanStack.Peek().EndExpression.Match(lineText, position);
Match firstMatch = Minimum(matches, endSpanMatch);
if (firstMatch == null)
break;
HighlightNonSpans(firstMatch.Index);
Debug.Assert(position == firstMatch.Index);
if (firstMatch == endSpanMatch) {
HighlightingSpan poppedSpan = spanStack.Peek();
if (!poppedSpan.SpanColorIncludesEnd)
PopColor(); // pop SpanColor
PushColor(poppedSpan.EndColor);
position = firstMatch.Index + firstMatch.Length;
PopColor(); // pop EndColor
if (poppedSpan.SpanColorIncludesEnd)
PopColor(); // pop SpanColor
spanStack = spanStack.Pop();
currentRuleSet = this.CurrentRuleSet;
//FreeMatchArray(matches);
if (storedMatchArrays.Count > 0) {
matches = storedMatchArrays.Pop();
int index = currentRuleSet.Spans.IndexOf(poppedSpan);
Debug.Assert(index >= 0 && index < matches.Length);
if (matches[index].Index == position) {
throw new InvalidOperationException(
"A highlighting span matched 0 characters, which would cause an endless loop.\n" +
"Change the highlighting definition so that either the start or the end regex matches at least one character.\n" +
"Start regex: " + poppedSpan.StartExpression + "\n" +
"End regex: " + poppedSpan.EndExpression);
}
} else {
matches = AllocateMatchArray(currentRuleSet.Spans.Count);
}
} else {
int index = Array.IndexOf(matches, firstMatch);
Debug.Assert(index >= 0);
HighlightingSpan newSpan = currentRuleSet.Spans[index];
spanStack = spanStack.Push(newSpan);
currentRuleSet = this.CurrentRuleSet;
storedMatchArrays.Push(matches);
matches = AllocateMatchArray(currentRuleSet.Spans.Count);
if (newSpan.SpanColorIncludesStart)
PushColor(newSpan.SpanColor);
PushColor(newSpan.StartColor);
position = firstMatch.Index + firstMatch.Length;
PopColor();
if (!newSpan.SpanColorIncludesStart)
PushColor(newSpan.SpanColor);
}
endSpanMatch = null;
}
HighlightNonSpans(lineText.Length);
PopAllColors();
}
void HighlightNonSpans(int until)
{
Debug.Assert(position <= until);
if (position == until)
return;
if (highlightedLine != null) {
IList<HighlightingRule> rules = CurrentRuleSet.Rules;
Match[] matches = AllocateMatchArray(rules.Count);
while (true) {
for (int i = 0; i < matches.Length; i++) {
if (matches[i] == null || (matches[i].Success && matches[i].Index < position))
matches[i] = rules[i].Regex.Match(lineText, position, until - position);
}
Match firstMatch = Minimum(matches, null);
if (firstMatch == null)
break;
position = firstMatch.Index;
int ruleIndex = Array.IndexOf(matches, firstMatch);
if (firstMatch.Length == 0) {
throw new InvalidOperationException(
"A highlighting rule matched 0 characters, which would cause an endless loop.\n" +
"Change the highlighting definition so that the rule matches at least one character.\n" +
"Regex: " + rules[ruleIndex].Regex);
}
PushColor(rules[ruleIndex].Color);
position = firstMatch.Index + firstMatch.Length;
PopColor();
}
//FreeMatchArray(matches);
}
position = until;
}
static readonly HighlightingRuleSet emptyRuleSet = new HighlightingRuleSet() { Name = "EmptyRuleSet" };
HighlightingRuleSet CurrentRuleSet {
get {
if (spanStack.IsEmpty)
return mainRuleSet;
else
return spanStack.Peek().RuleSet ?? emptyRuleSet;
}
}
#endregion
#region Color Stack Management
Stack<HighlightedSection> highlightedSectionStack;
HighlightedSection lastPoppedSection;
void ResetColorStack()
{
Debug.Assert(position == 0);
lastPoppedSection = null;
if (highlightedLine == null) {
highlightedSectionStack = null;
} else {
highlightedSectionStack = new Stack<HighlightedSection>();
foreach (HighlightingSpan span in spanStack.Reverse()) {
PushColor(span.SpanColor);
}
}
}
void PushColor(HighlightingColor color)
{
if (highlightedLine == null)
return;
if (color == null) {
highlightedSectionStack.Push(null);
} else if (lastPoppedSection != null && lastPoppedSection.Color == color
&& lastPoppedSection.Offset + lastPoppedSection.Length == position + lineStartOffset)
{
highlightedSectionStack.Push(lastPoppedSection);
lastPoppedSection = null;
} else {
HighlightedSection hs = new HighlightedSection {
Offset = position + lineStartOffset,
Color = color
};
highlightedLine.Sections.Add(hs);
highlightedSectionStack.Push(hs);
lastPoppedSection = null;
}
}
void PopColor()
{
if (highlightedLine == null)
return;
HighlightedSection s = highlightedSectionStack.Pop();
if (s != null) {
s.Length = (position + lineStartOffset) - s.Offset;
if (s.Length == 0)
highlightedLine.Sections.Remove(s);
else
lastPoppedSection = s;
}
}
void PopAllColors()
{
if (highlightedSectionStack != null) {
while (highlightedSectionStack.Count > 0)
PopColor();
}
}
#endregion
#region Match helpers
/// <summary>
/// Returns the first match from the array or endSpanMatch.
/// </summary>
static Match Minimum(Match[] arr, Match endSpanMatch)
{
Match min = null;
foreach (Match v in arr) {
if (v.Success && (min == null || v.Index < min.Index))
min = v;
}
if (endSpanMatch != null && endSpanMatch.Success && (min == null || endSpanMatch.Index < min.Index))
return endSpanMatch;
else
return min;
}
static Match[] AllocateMatchArray(int count)
{
if (count == 0)
return Empty<Match>.Array;
else
return new Match[count];
}
#endregion
}
}

1
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj

@ -193,6 +193,7 @@
<Compile Include="Highlighting\HighlightingColorizer.cs" /> <Compile Include="Highlighting\HighlightingColorizer.cs" />
<Compile Include="Highlighting\HighlightingDefinitionInvalidException.cs" /> <Compile Include="Highlighting\HighlightingDefinitionInvalidException.cs" />
<Compile Include="Highlighting\HighlightingDefinitionTypeConverter.cs" /> <Compile Include="Highlighting\HighlightingDefinitionTypeConverter.cs" />
<Compile Include="Highlighting\HighlightingEngine.cs" />
<Compile Include="Highlighting\HighlightingManager.cs" /> <Compile Include="Highlighting\HighlightingManager.cs" />
<Compile Include="Highlighting\HtmlClipboard.cs" /> <Compile Include="Highlighting\HtmlClipboard.cs" />
<Compile Include="Highlighting\IHighlighter.cs" /> <Compile Include="Highlighting\IHighlighter.cs" />

12
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/CompressingTreeList.cs

@ -110,6 +110,18 @@ namespace ICSharpCode.AvalonEdit.Utils
readonly Func<T, T, bool> comparisonFunc; readonly Func<T, T, bool> comparisonFunc;
Node root; Node root;
/// <summary>
/// Creates a new CompressingTreeList instance.
/// </summary>
/// <param name="comparisonFunc">A function that checks two values for equality. If this
/// function returns true, a single node may be used to store the two values.</param>
public CompressingTreeList(IEqualityComparer<T> equalityComparer)
{
if (equalityComparer == null)
throw new ArgumentNullException("equalityComparer");
this.comparisonFunc = equalityComparer.Equals;
}
/// <summary> /// <summary>
/// Creates a new CompressingTreeList instance. /// Creates a new CompressingTreeList instance.
/// </summary> /// </summary>

Loading…
Cancel
Save