|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Projection;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense;
/// <summary>
/// Helper class to use type-safety to enforce we're using TextSpans from the
/// TextView's buffer. Intellisense primarily uses spans from the SubjectBuffer
/// which need to be mapped to ViewTextSpans before comparing to view positions
/// such as the current caret location.
/// </summary>
internal readonly struct ViewTextSpan(TextSpan textSpan)
{
public readonly TextSpan TextSpan = textSpan;
}
internal readonly struct DisconnectedBufferGraph(ITextBuffer subjectBuffer, ITextBuffer viewBuffer)
{
// The subject buffer's snapshot at the point of the initial model's creation
public readonly ITextSnapshot SubjectBufferSnapshot = subjectBuffer.CurrentSnapshot;
// The TextView's snapshot at the point of the initial model's creation
public readonly ITextSnapshot ViewSnapshot = viewBuffer.CurrentSnapshot;
// The relation of the subject buffer to the TextView's top buffer. This information
// is used with the subjectBufferSnapshot and viewSnapshot to map spans even when
// the buffers might be temporarily disconnected during Razor/Venus remappings.
public readonly BufferMapDirection SubjectBufferToTextViewDirection = IBufferGraphExtensions.ClassifyBufferMapDirection(
subjectBuffer,
viewBuffer);
// Normally, we could just use a BufferGraph to do the mapping, but our subjectBuffer may be
// disconnected from the view when we are asked to do this mapping.
public ViewTextSpan GetSubjectBufferTextSpanInViewBuffer(TextSpan textSpan)
{
switch (SubjectBufferToTextViewDirection)
{
// The view and subject buffer are the same
case BufferMapDirection.Identity:
return new ViewTextSpan(textSpan);
// The subject buffer contains the view buffer. This happens in debugger intellisense.
case BufferMapDirection.Down:
{
var projection = SubjectBufferSnapshot as IProjectionSnapshot;
var span = MapDownToSnapshot(textSpan.ToSpan(), projection, ViewSnapshot);
return new ViewTextSpan(span.ToTextSpan());
}
// The view buffer contains the subject buffer. This is the typical Razor setup.
case BufferMapDirection.Up:
{
var projection = ViewSnapshot as IProjectionSnapshot;
var span = MapUpToSnapshot(textSpan.ToSpan(), SubjectBufferSnapshot, projection);
return new ViewTextSpan(span.ToTextSpan());
}
default:
throw ExceptionUtilities.Unreachable();
}
}
private static Span MapUpToSnapshot(Span span, ITextSnapshot start, IProjectionSnapshot target)
{
var spans = MapUpToSnapshotRecursive(new SnapshotSpan(start, span), target);
return spans.First();
}
// Do a depth first search through the projection graph to find the first mapping
private static IEnumerable<Span> MapUpToSnapshotRecursive(SnapshotSpan start, IProjectionSnapshot target)
{
foreach (var source in target.SourceSnapshots)
{
if (source == start.Snapshot)
{
foreach (var result in target.MapFromSourceSnapshot(start))
{
yield return result;
}
}
else if (source is IProjectionSnapshot sourceProjection)
{
foreach (var span in MapUpToSnapshotRecursive(start, sourceProjection))
{
foreach (var result in target.MapFromSourceSnapshot(new SnapshotSpan(source, span)))
{
yield return result;
}
}
}
}
yield break;
}
private static Span MapDownToSnapshot(Span span, IProjectionSnapshot start, ITextSnapshot target)
{
var sourceSpans = new Queue<SnapshotSpan>(start.MapToSourceSnapshots(span));
while (true)
{
var sourceSpan = sourceSpans.Dequeue();
if (sourceSpan.Snapshot == target)
{
return sourceSpan.Span;
}
else if (sourceSpan.Snapshot is IProjectionSnapshot)
{
foreach (var s in (sourceSpan.Snapshot as IProjectionSnapshot).MapToSourceSnapshots(sourceSpan.Span))
{
sourceSpans.Enqueue(s);
}
}
}
}
}
|