|
// 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;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Differencing;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Projection;
using Microsoft.VisualStudio.Threading;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Editor.Implementation.Preview
{
internal static class ICocoaDifferenceViewerExtensions
{
private class SizeToFitHelper
{
private readonly IThreadingContext _threadingContext;
private readonly ICocoaDifferenceViewer _diffViewer;
private readonly double _minWidth;
private double _width;
private double _height;
public SizeToFitHelper(IThreadingContext threadingContext, ICocoaDifferenceViewer diffViewer, double minWidth)
{
_threadingContext = threadingContext;
_diffViewer = diffViewer;
_minWidth = minWidth;
}
public async Task SizeToFitAsync(CancellationToken cancellationToken)
{
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
await CalculateSizeAsync(cancellationToken).ConfigureAwait(true);
// We have the height and width required to display the inline diff snapshot now.
// Set the height and width of the ICocoaDifferenceViewer accordingly.
_diffViewer.VisualElement.SetFrameSize(new CoreGraphics.CGSize(_width, _height));
_diffViewer.VisualElement.Subviews[0].SetFrameSize(new CoreGraphics.CGSize(_width, _height));
}
private async Task<IProjectionSnapshot> GetInlineBufferSnapshotAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (_diffViewer.DifferenceBuffer.CurrentInlineBufferSnapshot is { } snapshot)
{
return snapshot;
}
var completionSource = new TaskCompletionSource<IProjectionSnapshot>(TaskCreationOptions.RunContinuationsAsynchronously);
_diffViewer.DifferenceBuffer.SnapshotDifferenceChanged += HandleSnapshotDifferenceChanged;
// Handle cases where the snapshot was set between the previous check and the event registration
if (_diffViewer.DifferenceBuffer.CurrentInlineBufferSnapshot is { } snapshot2)
completionSource.SetResult(snapshot2);
try
{
return await completionSource.Task.WithCancellation(cancellationToken).ConfigureAwaitRunInline();
}
finally
{
_diffViewer.DifferenceBuffer.SnapshotDifferenceChanged -= HandleSnapshotDifferenceChanged;
}
// Local function
void HandleSnapshotDifferenceChanged(object sender, SnapshotDifferenceChangeEventArgs e)
{
// This event handler will only be called when the inline diff snapshot computation is complete.
Contract.ThrowIfNull(_diffViewer.DifferenceBuffer.CurrentInlineBufferSnapshot);
completionSource.SetResult(_diffViewer.DifferenceBuffer.CurrentInlineBufferSnapshot);
}
}
private async Task CalculateSizeAsync(CancellationToken cancellationToken)
{
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
ICocoaTextView textView;
ITextSnapshot snapshot;
if (_diffViewer.ViewMode == DifferenceViewMode.RightViewOnly)
{
textView = _diffViewer.RightView;
snapshot = _diffViewer.DifferenceBuffer.RightBuffer.CurrentSnapshot;
}
else if (_diffViewer.ViewMode == DifferenceViewMode.LeftViewOnly)
{
textView = _diffViewer.LeftView;
snapshot = _diffViewer.DifferenceBuffer.LeftBuffer.CurrentSnapshot;
}
else
{
textView = _diffViewer.InlineView;
snapshot = await GetInlineBufferSnapshotAsync(cancellationToken).ConfigureAwait(true);
}
// Perform a layout without actually rendering the content on the screen so that
// we can calculate the exact height and width required to render the content on
// the screen before actually rendering it. This helps us avoiding the flickering
// effect that would be caused otherwise when the UI is rendered twice with
// different sizes.
textView.DisplayTextLineContainingBufferPosition(
new SnapshotPoint(snapshot, 0),
0.0, ViewRelativePosition.Top, double.MaxValue, double.MaxValue);
_width = Math.Max(textView.MaxTextRightCoordinate * (textView.ZoomLevel / 100), _minWidth); // Width of the widest line.
Contract.ThrowIfFalse(IsNormal(_width));
_height = textView.LineHeight * (textView.ZoomLevel / 100) * // Height of each line.
snapshot.LineCount; // Number of lines.
Contract.ThrowIfFalse(IsNormal(_height));
}
private static bool IsNormal(double value)
{
return !double.IsNaN(value) && !double.IsInfinity(value) && value > 0.0;
}
}
public static Task SizeToFitAsync(this ICocoaDifferenceViewer diffViewer, IThreadingContext threadingContext, double minWidth = 400.0, CancellationToken cancellationToken = default)
{
var helper = new SizeToFitHelper(threadingContext, diffViewer, minWidth);
return helper.SizeToFitAsync(cancellationToken);
}
}
}
|