File: LanguageClient\GeneratedVirtualDocument.cs
Web Access
Project: src\src\Razor\src\Razor\src\Microsoft.VisualStudio.LanguageServices.Razor\Microsoft.VisualStudio.LanguageServices.Razor.csproj (Microsoft.VisualStudio.LanguageServices.Razor)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.CodeAnalysis.Razor.Telemetry;
using Microsoft.VisualStudio.LanguageServer.ContainedLanguage;
using Microsoft.VisualStudio.Text;
 
namespace Microsoft.VisualStudio.Razor.LanguageClient;
 
internal abstract class GeneratedVirtualDocument<T>(Uri uri, ITextBuffer textBuffer, ITelemetryReporter telemetryReporter) : VirtualDocumentBase<T>(uri, textBuffer) where T : VirtualDocumentSnapshot
{
    private readonly ITelemetryReporter _telemetryReporter = telemetryReporter;
 
    public override VirtualDocumentSnapshot Update(IReadOnlyList<ITextChange> changes, int hostDocumentVersion, object? state)
    {
        var currentSnapshotLength = CurrentSnapshot.Snapshot.Length;
        if (state is bool previousWasEmpty &&
            previousWasEmpty != (currentSnapshotLength == 0))
        {
            Debug.Fail($"The language server is sending us changes for what it/we thought was an empty file, but their/our copy is not empty. Generated C# file may have corrupted file contents after this update.");
 
            var recoverable = false;
            if (previousWasEmpty && changes is [{ OldPosition: 0, OldEnd: 0 } change])
            {
                recoverable = true;
                // The LSP server thought the file was empty, but we have some contents. That's not good, but we can recover
                // by adjusting the range for the change (which would be (0,0)-(0,0) from the LSP server point of view) to
                // cover the whole buffer, essentially just taking the LSP server as the source of truth.
                changes = new[] { new VisualStudioTextChange(0, currentSnapshotLength, change.NewText) };
            }
 
            _telemetryReporter.ReportEvent(
                "sync", Severity.High,
                new("version", hostDocumentVersion),
                new("type", typeof(T).Name),
                new("recoverable", recoverable));
        }
 
        return base.Update(changes, hostDocumentVersion, state);
    }
}