in src/Editor/Text/Impl/TextModel/ReadOnlySpanCollection.cs [250:383]
private static IList<ReadOnlySpan> NormalizeSpans(TextVersion version, IEnumerable<IReadOnlyRegion> regions)
{
List<IReadOnlyRegion> sorted = new List<IReadOnlyRegion>(regions.Where(region => region.QueryCallback == null));
if (sorted.Count == 0)
{
return new FrugalList<ReadOnlySpan>();
}
else if (sorted.Count == 1)
{
return new FrugalList<ReadOnlySpan>() {new ReadOnlySpan(version, sorted[0])};
}
else
{
sorted.Sort((s1, s2) => s1.Span.GetSpan(version).Start.CompareTo(s2.Span.GetSpan(version).Start));
List<ReadOnlySpan> normalized = new List<ReadOnlySpan>(sorted.Count);
int oldStart = sorted[0].Span.GetSpan(version).Start;
int oldEnd = sorted[0].Span.GetSpan(version).End;
EdgeInsertionMode oldStartEdgeInsertionMode = sorted[0].EdgeInsertionMode;
EdgeInsertionMode oldEndEdgeInsertionMode = sorted[0].EdgeInsertionMode;
SpanTrackingMode oldSpanTrackingMode = sorted[0].Span.TrackingMode;
for (int i = 1; (i < sorted.Count); ++i)
{
int newStart = sorted[i].Span.GetSpan(version).Start;
int newEnd = sorted[i].Span.GetSpan(version).End;
// Since the new span's start occurs after the old span's end, we can just add the old span directly.
if (oldEnd < newStart)
{
normalized.Add(new ReadOnlySpan(version, new Span(oldStart, oldEnd - oldStart), oldSpanTrackingMode, oldStartEdgeInsertionMode, oldEndEdgeInsertionMode));
oldStart = newStart;
oldEnd = newEnd;
oldStartEdgeInsertionMode = sorted[i].EdgeInsertionMode;
oldEndEdgeInsertionMode = sorted[i].EdgeInsertionMode;
oldSpanTrackingMode = sorted[i].Span.TrackingMode;
}
else
{
// The two read only regions start at the same position
if (newStart == oldStart)
{
// If one read only region denies edge insertions, combined they do as well
if (sorted[i].EdgeInsertionMode == EdgeInsertionMode.Deny)
{
oldStartEdgeInsertionMode = EdgeInsertionMode.Deny;
}
// This is tricky. We want one span that will be inclusive tracking, and one that won't.
if (oldSpanTrackingMode != sorted[i].Span.TrackingMode)
{
// Since the read only regions cover the same exact span, the combined one will be edge inclusive tracking
if (oldEnd == newEnd)
{
oldSpanTrackingMode = SpanTrackingMode.EdgeInclusive;
}
else if (oldEnd < newEnd)
{
// Since the old span and new span don't have the same span tracking mode and don't end in the same position, we need to create a new span that is edge inclusive
// and deny inserts between it and the next span.
normalized.Add(new ReadOnlySpan(version, new Span(oldStart, oldEnd - oldStart), SpanTrackingMode.EdgeInclusive, oldStartEdgeInsertionMode, EdgeInsertionMode.Deny));
oldStart = oldEnd; // Explicitly use the old end here since we want these spans to be adjacent
oldEnd = newEnd;
oldStartEdgeInsertionMode = sorted[i].EdgeInsertionMode;
oldEndEdgeInsertionMode = sorted[i].EdgeInsertionMode;
oldSpanTrackingMode = sorted[i].Span.TrackingMode;
}
else
{
// Since the new span ends first, create a span that is edge inclusive tracking that ends at the the new span's end.
normalized.Add(new ReadOnlySpan(version, new Span(newStart, newEnd - newStart), SpanTrackingMode.EdgeInclusive, oldStartEdgeInsertionMode, EdgeInsertionMode.Deny));
oldStart = newEnd; // Explicitly use the new end here since we want these spans to be adjacent
}
}
}
if (oldEnd < newEnd)
{
// If the tracking modes are different then we need to create a new span
// with the old tracking mode, and start a new span with the new span tracking mode.
// Also, if the old end and the new start are identical and both edge insertion mode's
// are allow, then we need to create a new span.
if (((oldEnd == newStart)
&&
((oldEndEdgeInsertionMode == EdgeInsertionMode.Allow) && (sorted[i].EdgeInsertionMode == EdgeInsertionMode.Allow)))
||
(oldSpanTrackingMode != sorted[i].Span.TrackingMode))
{
normalized.Add(new ReadOnlySpan(version, new Span(oldStart, oldEnd - oldStart), oldSpanTrackingMode, oldStartEdgeInsertionMode, oldEndEdgeInsertionMode));
oldStart = oldEnd; // Explicitly use the old end here since we want these spans to be adjacent.
oldEnd = newEnd;
// If we are splitting up the spans because of a change in tracking mode, then explicitly deny inserting between them
if (oldSpanTrackingMode != sorted[i].Span.TrackingMode)
{
oldStartEdgeInsertionMode = EdgeInsertionMode.Deny; // Explicitly use deny here since we don't want to allow insertions between these spans
}
else
{
oldStartEdgeInsertionMode = EdgeInsertionMode.Allow;
}
oldEndEdgeInsertionMode = sorted[i].EdgeInsertionMode;
oldSpanTrackingMode = sorted[i].Span.TrackingMode;
}
else
{
oldEnd = newEnd;
oldEndEdgeInsertionMode = sorted[i].EdgeInsertionMode;
}
}
else if (oldEnd == newEnd)
{
if (sorted[i].EdgeInsertionMode == EdgeInsertionMode.Deny)
{
oldEndEdgeInsertionMode = EdgeInsertionMode.Deny;
}
if (oldSpanTrackingMode != sorted[i].Span.TrackingMode)
{
normalized.Add(new ReadOnlySpan(version, new Span(oldStart, oldEnd - oldStart), oldSpanTrackingMode, oldStartEdgeInsertionMode, oldEndEdgeInsertionMode));
oldStart = newEnd;
oldEnd = newEnd;
oldStartEdgeInsertionMode = sorted[i].EdgeInsertionMode;
oldEndEdgeInsertionMode = sorted[i].EdgeInsertionMode;
oldSpanTrackingMode = sorted[i].Span.TrackingMode;
}
}
}
}
normalized.Add(new ReadOnlySpan(version, new Span(oldStart, oldEnd - oldStart), oldSpanTrackingMode, oldStartEdgeInsertionMode, oldEndEdgeInsertionMode));
return normalized;
}
}