// // 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.Projection.Implementation { using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using Microsoft.VisualStudio.Text.Implementation; using Microsoft.VisualStudio.Text.Utilities; using Strings = Microsoft.VisualStudio.Text.Implementation.Strings; internal class ElisionSnapshot : BaseProjectionSnapshot, IProjectionSnapshot, IElisionSnapshot { #region State and Construction private readonly ElisionBuffer elisionBuffer; private readonly ITextSnapshot sourceSnapshot; private readonly ReadOnlyCollection sourceSnapshots; private readonly ElisionMap content; private readonly bool fillInMappingMode; public ElisionSnapshot(ElisionBuffer elisionBuffer, ITextSnapshot sourceSnapshot, ITextVersion2 version, StringRebuilder builder, ElisionMap content, bool fillInMappingMode) : base(version, builder) { this.elisionBuffer = elisionBuffer; this.sourceSnapshot = sourceSnapshot; // The SourceSnapshots property is used heavily, so cache a handy copy. this.sourceSnapshots = new ReadOnlyCollection(new FrugalList() { sourceSnapshot }); this.totalLength = content.Length; this.content = content; this.totalLineCount = content.LineCount; this.fillInMappingMode = fillInMappingMode; Debug.Assert(this.totalLength == version.Length, string.Format(System.Globalization.CultureInfo.CurrentCulture, "Elision Snapshot Inconsistency. Content: {0}, Previous + delta: {1}", this.totalLength, version.Length)); if (this.totalLength != version.Length) { throw new InvalidOperationException(Strings.InvalidLengthCalculation); } } #endregion #region Buffers and Spans public override IProjectionBufferBase TextBuffer { get { return this.elisionBuffer; } } IElisionBuffer IElisionSnapshot.TextBuffer { get { return this.elisionBuffer; } } protected override ITextBuffer TextBufferHelper { get { return this.elisionBuffer; } } public override int SpanCount { get { return this.content.SpanCount; } } public override ReadOnlyCollection SourceSnapshots { get { return this.sourceSnapshots; } } public ITextSnapshot SourceSnapshot { get { return this.sourceSnapshot; } } public SnapshotPoint MapFromSourceSnapshotToNearest(SnapshotPoint point) { return this.content.MapFromSourceSnapshotToNearest(this, point.Position); } public override ITextSnapshot GetMatchingSnapshot(ITextBuffer textBuffer) { if (textBuffer == null) { throw new ArgumentNullException(nameof(textBuffer)); } return this.sourceSnapshot.TextBuffer == textBuffer ? this.sourceSnapshot : null; } public override ITextSnapshot GetMatchingSnapshotInClosure(ITextBuffer textBuffer) { if (textBuffer == null) { throw new ArgumentNullException(nameof(textBuffer)); } if (this.sourceSnapshot.TextBuffer == textBuffer) { return this.sourceSnapshot; } IProjectionSnapshot2 projSnap = this.sourceSnapshot as IProjectionSnapshot2; if (projSnap != null) { return projSnap.GetMatchingSnapshotInClosure(textBuffer); } return null; } public override ITextSnapshot GetMatchingSnapshotInClosure(Predicate match) { if (match == null) { throw new ArgumentNullException(nameof(match)); } if (match(this.sourceSnapshot.TextBuffer)) { return this.sourceSnapshot; } IProjectionSnapshot2 projSnap = this.sourceSnapshot as IProjectionSnapshot2; if (projSnap != null) { return projSnap.GetMatchingSnapshotInClosure(match); } return null; } public override ReadOnlyCollection GetSourceSpans(int startSpanIndex, int count) { if (startSpanIndex < 0) { throw new ArgumentOutOfRangeException(nameof(startSpanIndex)); } if (count < 0 || startSpanIndex + count > SpanCount) { throw new ArgumentOutOfRangeException(nameof(count)); } return new ReadOnlyCollection(this.content.GetSourceSpans(this.sourceSnapshot, startSpanIndex, count)); } public override ReadOnlyCollection GetSourceSpans() { return GetSourceSpans(0, this.content.SpanCount); } #endregion #region Mapping public override SnapshotPoint MapToSourceSnapshot(int position) { if (position < 0 || position > this.totalLength) { throw new ArgumentOutOfRangeException(nameof(position)); } FrugalList points = this.content.MapInsertionPointToSourceSnapshots(this, position); if (points.Count == 1) { return points[0]; } else if (this.elisionBuffer.resolver == null) { return points[points.Count - 1]; } else { return points[this.elisionBuffer.resolver.GetTypicalInsertionPosition(new SnapshotPoint(this, position), new ReadOnlyCollection(points))]; } } public override SnapshotPoint MapToSourceSnapshot(int position, PositionAffinity affinity) { if (position < 0 || position > this.totalLength) { throw new ArgumentOutOfRangeException(nameof(position)); } if (affinity < PositionAffinity.Predecessor || affinity > PositionAffinity.Successor) { throw new ArgumentOutOfRangeException(nameof(affinity)); } return this.content.MapToSourceSnapshot(this.sourceSnapshot, position, affinity); } public override SnapshotPoint? MapFromSourceSnapshot(SnapshotPoint point, PositionAffinity affinity) { if (point.Snapshot != this.sourceSnapshot) { throw new ArgumentException("The point does not belong to a source snapshot of the projection snapshot"); } if (affinity < PositionAffinity.Predecessor || affinity > PositionAffinity.Successor) { throw new ArgumentOutOfRangeException(nameof(affinity)); } return this.content.MapFromSourceSnapshot(this, point.Position); } private ReadOnlyCollection MapToSourceSnapshots(Span span, bool fillIn) { if (span.End > this.totalLength) { throw new ArgumentOutOfRangeException(nameof(span)); } FrugalList result = new FrugalList(); if (fillIn) { this.content.MapToSourceSnapshotsInFillInMode(this.sourceSnapshot, span, result); } else { this.content.MapToSourceSnapshots(this, span, result); } return new ReadOnlyCollection(result); } public override ReadOnlyCollection MapToSourceSnapshots(Span span) { return MapToSourceSnapshots(span, this.fillInMappingMode); } public override ReadOnlyCollection MapToSourceSnapshotsForRead(Span span) { return MapToSourceSnapshots(span, false); } public override ReadOnlyCollection MapFromSourceSnapshot(SnapshotSpan span) { if (span.Snapshot != this.sourceSnapshot) { throw new ArgumentException("The span does not belong to a source snapshot of the projection snapshot"); } FrugalList result = new FrugalList(); this.content.MapFromSourceSnapshot(span, result); return new ReadOnlyCollection(result); } internal override ReadOnlyCollection MapInsertionPointToSourceSnapshots(int position, ITextBuffer excludedBuffer) { return new ReadOnlyCollection(this.content.MapInsertionPointToSourceSnapshots(this, position)); } internal override ReadOnlyCollection MapReplacementSpanToSourceSnapshots(Span replacementSpan, ITextBuffer excludedBuffer) { // this implementation won't return zero-length spans on the edges as it // should, but that's OK because it is not called in Beta1 (we never edit // elision buffers directly). Third parties might do so, so we need a non-throwing // implementation here. Zero-length spans will be added for Beta2. return MapToSourceSnapshots(replacementSpan, false); } #endregion } }