//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
//
// This file contain implementations details that are subject to change without notice.
// Use at your own risk.
//
namespace Microsoft.VisualStudio.Text.Implementation
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
///
/// Implementation of ITrackingSpan for the UndoRedo and Backward TrackingFidelityMode.
/// Records noninvertible transition and consults these records as appropriate.
///
internal class HighFidelityTrackingSpan : TrackingSpan
{
#region State and Construction
private class VersionSpanHistory
{
private readonly ITextVersion version;
private readonly Span span;
private readonly List noninvertibleStartHistory;
private readonly List noninvertibleEndHistory;
public VersionSpanHistory(ITextVersion version,
Span span,
List noninvertibleStartHistory,
List noninvertibleEndHistory)
{
this.version = version;
this.span = span;
this.noninvertibleStartHistory = noninvertibleStartHistory;
this.noninvertibleEndHistory = noninvertibleEndHistory;
}
public ITextVersion Version { get { return this.version; } }
public Span Span { get { return this.span; } }
public List NoninvertibleStartHistory { get { return this.noninvertibleStartHistory; } }
public List NoninvertibleEndHistory { get { return this.noninvertibleEndHistory; } }
}
private VersionSpanHistory cachedSpan;
private TrackingFidelityMode fidelity;
internal HighFidelityTrackingSpan(ITextVersion version, Span span, SpanTrackingMode spanTrackingMode, TrackingFidelityMode fidelity)
: base(version, span, spanTrackingMode)
{
if (fidelity != TrackingFidelityMode.UndoRedo && fidelity != TrackingFidelityMode.Backward)
{
throw new ArgumentOutOfRangeException("fidelity");
}
List startHistory = null;
List endHistory = null;
if (fidelity == TrackingFidelityMode.UndoRedo && version.VersionNumber > 0)
{
// The system may perform undo operations that reach prior to the initial version; if any of
// those transitions are noninvertible, then redoing back to the initial version will give the
// wrong answer. Thus we save the state of the span for the initial version, unless
// the initial version happens to be version zero (in which case we could not undo past it).
startHistory = new List();
endHistory = new List();
if (version.VersionNumber != version.ReiteratedVersionNumber)
{
Debug.Assert(version.ReiteratedVersionNumber < version.VersionNumber);
// If the current version and reiterated version differ, also remember the position
// using the reiterated version number, since when undoing back to this point it
// will be the key that is used.
startHistory.Add(new VersionNumberPosition(version.ReiteratedVersionNumber, span.Start));
endHistory.Add(new VersionNumberPosition(version.ReiteratedVersionNumber, span.End));
}
startHistory.Add(new VersionNumberPosition(version.VersionNumber, span.Start));
endHistory.Add(new VersionNumberPosition(version.VersionNumber, span.End));
}
this.cachedSpan = new VersionSpanHistory(version, span, startHistory, endHistory);
this.fidelity = fidelity;
}
#endregion
#region Overrides
public override ITextBuffer TextBuffer
{
get { return this.cachedSpan.Version.TextBuffer; }
}
public override TrackingFidelityMode TrackingFidelity
{
get { return this.fidelity; }
}
protected override Span TrackSpan(ITextVersion targetVersion)
{
// Compute the span on the requested snapshot.
// This object caches the most recently requested version and the span in that version.
//
// We are relying on the atomicity of pointer copies (this.cachedSpan might change after we've
// fetched it below but we will always get a self-consistent VersionSpan). This ensures we
// have proper behavior when called from multiple threads (multiple threads may all track and update the
// cached value if called at inconvenient times, but they will return consistent results).
//
// In most cases, one can track backwards from the cached version to a previously computed
// version and get the same result, but this is not always the case: in particular, when one or both
// ends of the span lie in a deleted region, simulating reinsertion of that region will not cause
// the previous value of the span to be recovered. Such transitions are called noninvertible.
// This class explicitly tracks the positions of span endpoints for versions for which the subsequent
// transition is noninvertible; this allows the value to be computed properly when tracking backwards
// or in undo/redo situations.
VersionSpanHistory cached = this.cachedSpan; // must fetch just once
if (targetVersion == cached.Version)
{
// easy!
return cached.Span;
}
PointTrackingMode startMode =
(this.trackingMode == SpanTrackingMode.EdgeExclusive || this.trackingMode == SpanTrackingMode.EdgePositive)
? PointTrackingMode.Positive
: PointTrackingMode.Negative;
PointTrackingMode endMode =
(this.trackingMode == SpanTrackingMode.EdgeExclusive || this.trackingMode == SpanTrackingMode.EdgeNegative)
? PointTrackingMode.Negative
: PointTrackingMode.Positive;
List noninvertibleStartHistory = cached.NoninvertibleStartHistory;
List noninvertibleEndHistory = cached.NoninvertibleEndHistory;
Span targetSpan;
if (targetVersion.VersionNumber > cached.Version.VersionNumber)
{
// Compute the target span by going forward from the cached version
int start = HighFidelityTrackingPoint.TrackPositionForwardInTime
(startMode, this.fidelity, ref noninvertibleStartHistory, cached.Span.Start, cached.Version, targetVersion);
int end = HighFidelityTrackingPoint.TrackPositionForwardInTime
(endMode, this.fidelity, ref noninvertibleEndHistory, cached.Span.End, cached.Version, targetVersion);
targetSpan = Span.FromBounds(start, System.Math.Max(start, end));
// Cache the new span
this.cachedSpan = new VersionSpanHistory(targetVersion, targetSpan, noninvertibleStartHistory, noninvertibleEndHistory);
}
else
{
// we are looking for a version prior to the cached version.
int start = HighFidelityTrackingPoint.TrackPositionBackwardInTime
(startMode, this.fidelity == TrackingFidelityMode.Backward ? noninvertibleStartHistory : null, cached.Span.Start, cached.Version, targetVersion);
int end = HighFidelityTrackingPoint.TrackPositionBackwardInTime
(endMode, this.fidelity == TrackingFidelityMode.Backward ? noninvertibleEndHistory : null, cached.Span.End, cached.Version, targetVersion);
targetSpan = Span.FromBounds(start, System.Math.Max(start, end));
}
return targetSpan;
}
#endregion
#region Diagnostic Support
public override string ToString()
{
VersionSpanHistory c = this.cachedSpan;
System.Text.StringBuilder sb = new System.Text.StringBuilder("*");
sb.Append(ToString(c.Version, c.Span, this.trackingMode));
if (c.NoninvertibleStartHistory != null)
{
sb.Append("[Start");
foreach (VersionNumberPosition vp in c.NoninvertibleStartHistory)
{
sb.Append(string.Format(System.Globalization.CultureInfo.CurrentCulture, "V{0}@{1}", vp.VersionNumber, vp.Position));
}
sb.Append("]");
}
if (c.NoninvertibleEndHistory != null)
{
sb.Append("[End");
foreach (VersionNumberPosition vp in c.NoninvertibleEndHistory)
{
sb.Append(string.Format(System.Globalization.CultureInfo.CurrentCulture, "V{0}@{1}", vp.VersionNumber, vp.Position));
}
sb.Append("]");
}
return sb.ToString();
}
#endregion
}
}