diff --git a/src/LanguageServer/Protocol/Handler/RelatedDocuments/RelatedDocumentsHandler.cs b/src/LanguageServer/Protocol/Handler/RelatedDocuments/RelatedDocumentsHandler.cs index 85261644b8d3b..f30fe7ad72219 100644 --- a/src/LanguageServer/Protocol/Handler/RelatedDocuments/RelatedDocumentsHandler.cs +++ b/src/LanguageServer/Protocol/Handler/RelatedDocuments/RelatedDocumentsHandler.cs @@ -3,15 +3,12 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; -using System.Collections.Immutable; using System.Composition; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.RelatedDocuments; -using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.CommonLanguageServerProtocol.Framework; @@ -34,44 +31,17 @@ internal sealed class RelatedDocumentsHandler : ILspServiceRequestHandler, ITextDocumentIdentifierHandler { - /// - /// Cache where we store the data produced by prior requests so that they can be returned if nothing of significance - /// changed. The version key is produced by combining the checksums for project options and - /// - private readonly VersionedPullCache<(Checksum parseOptionsChecksum, Checksum textChecksum)?> _versionedCache = new(nameof(RelatedDocumentsHandler)); public bool MutatesSolutionState => false; public bool RequiresLSPSolution => true; - private static async Task<(Checksum parseOptionsChecksum, Checksum textChecksum)> ComputeChecksumsAsync(Document document, CancellationToken cancellationToken) - { - var project = document.Project; - var parseOptionsChecksum = project.State.GetParseOptionsChecksum(); - - var documentChecksumState = await document.State.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - var textChecksum = documentChecksumState.Text; - - return (parseOptionsChecksum, textChecksum); - } - public TextDocumentIdentifier GetTextDocumentIdentifier(VSInternalRelatedDocumentParams requestParams) => requestParams.TextDocument; - /// - /// Retrieve the previous results we reported. Used so we can avoid resending data for unchanged files. - /// - private static ImmutableArray? GetPreviousResults(VSInternalRelatedDocumentParams requestParams) - => requestParams.PreviousResultId != null && requestParams.TextDocument != null - ? [new PreviousPullResult(requestParams.PreviousResultId, requestParams.TextDocument)] - // The client didn't provide us with a previous result to look for, so we can't lookup anything. - : null; - public async Task HandleRequestAsync( VSInternalRelatedDocumentParams requestParams, RequestContext context, CancellationToken cancellationToken) { context.TraceInformation($"{this.GetType()} started getting related documents"); - context.TraceInformation($"PreviousResultId={requestParams.PreviousResultId}"); var solution = context.Solution; var document = context.Document; @@ -90,52 +60,28 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(VSInternalRelatedDocumen // The progress object we will stream reports to. using var progress = BufferedProgress.Create(requestParams.PartialResultToken); - var documentToPreviousParams = new Dictionary(); - if (requestParams.PreviousResultId != null) - documentToPreviousParams.Add(document, new PreviousPullResult(requestParams.PreviousResultId, requestParams.TextDocument)); - - var newResultId = await _versionedCache.GetNewResultIdAsync( - documentToPreviousParams, - document, - computeVersionAsync: async () => await ComputeChecksumsAsync(document, cancellationToken).ConfigureAwait(false), - cancellationToken).ConfigureAwait(false); - if (newResultId != null) - { - context.TraceInformation($"Version was changed for document: {document.FilePath}"); - - var linePosition = requestParams.Position is null - ? new LinePosition(0, 0) - : ProtocolConversions.PositionToLinePosition(requestParams.Position); + var linePosition = requestParams.Position is null + ? new LinePosition(0, 0) + : ProtocolConversions.PositionToLinePosition(requestParams.Position); - var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - var position = text.Lines.GetPosition(linePosition); + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var position = text.Lines.GetPosition(linePosition); - await relatedDocumentsService.GetRelatedDocumentIdsAsync( - document, - position, - (relatedDocumentIds, cancellationToken) => + await relatedDocumentsService.GetRelatedDocumentIdsAsync( + document, + position, + (relatedDocumentIds, cancellationToken) => + { + // As the related docs services reports document ids to us, stream those immediately through our + // progress reporter. + progress.Report(new VSInternalRelatedDocumentReport { - // As the related docs services reports document ids to us, stream those immediately through our - // progress reporter. - progress.Report(new VSInternalRelatedDocumentReport - { - ResultId = newResultId, - FilePaths = relatedDocumentIds.Select(id => solution.GetRequiredDocument(id).FilePath).WhereNotNull().ToArray(), - }); - - return ValueTaskFactory.CompletedTask; - }, - cancellationToken).ConfigureAwait(false); - } - else - { - context.TraceInformation($"Version was unchanged for document: {document.FilePath}"); + FilePaths = relatedDocumentIds.Select(id => solution.GetRequiredDocument(id).FilePath).WhereNotNull().ToArray(), + }); - // Nothing changed between the last request and this one. Report a (null-file-paths, same-result-id) - // response to the client as that means they should just preserve the current related file paths they - // have for this file. - progress.Report(new VSInternalRelatedDocumentReport { ResultId = requestParams.PreviousResultId }); - } + return ValueTaskFactory.CompletedTask; + }, + cancellationToken).ConfigureAwait(false); // If we had a progress object, then we will have been reporting to that. Otherwise, take what we've been // collecting and return that. diff --git a/src/LanguageServer/Protocol/Protocol/Internal/VSInternalRelatedDocumentParams.cs b/src/LanguageServer/Protocol/Protocol/Internal/VSInternalRelatedDocumentParams.cs index e52be3d89dcf5..c2c4014f2da96 100644 --- a/src/LanguageServer/Protocol/Protocol/Internal/VSInternalRelatedDocumentParams.cs +++ b/src/LanguageServer/Protocol/Protocol/Internal/VSInternalRelatedDocumentParams.cs @@ -10,8 +10,15 @@ namespace Roslyn.LanguageServer.Protocol /// /// Parameter for copilot/_related_documents. /// - internal sealed class VSInternalRelatedDocumentParams : VSInternalStreamingParams, IPartialResultParams + internal sealed class VSInternalRelatedDocumentParams : IPartialResultParams { + /// + /// Gets or sets the document for which the feature is being requested for. + /// + [JsonPropertyName("_vs_textDocument")] + [JsonRequired] + public TextDocumentIdentifier TextDocument { get; set; } + /// /// Gets or sets the value which indicates the position within the document. /// @@ -27,16 +34,6 @@ internal sealed class VSInternalRelatedDocumentParams : VSInternalStreamingParam internal sealed class VSInternalRelatedDocumentReport { - /// - /// Gets or sets the server-generated version number for the related documents result. This is treated as a - /// black box by the client: it is stored on the client for each textDocument and sent back to the server when - /// requesting related documents. The server can use this result ID to avoid resending results - /// that had previously been sent. - /// - [JsonPropertyName("_vs_resultId")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? ResultId { get; set; } - [JsonPropertyName("_vs_file_paths")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string[]? FilePaths { get; set; } diff --git a/src/LanguageServer/ProtocolUnitTests/RelatedDocuments/RelatedDocumentsTests.cs b/src/LanguageServer/ProtocolUnitTests/RelatedDocuments/RelatedDocumentsTests.cs index 02502fef59e14..2cc292339e553 100644 --- a/src/LanguageServer/ProtocolUnitTests/RelatedDocuments/RelatedDocumentsTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/RelatedDocuments/RelatedDocumentsTests.cs @@ -21,7 +21,6 @@ public sealed class RelatedDocumentsTests(ITestOutputHelper testOutputHelper) private static async Task RunGetRelatedDocumentsAsync( TestLspServer testLspServer, Uri uri, - string? previousResultId = null, bool useProgress = false) { BufferedProgress? progress = useProgress ? BufferedProgress.Create(null) : null; @@ -30,7 +29,6 @@ private static async Task RunGetRelatedDocume new VSInternalRelatedDocumentParams { TextDocument = new TextDocumentIdentifier { Uri = uri }, - PreviousResultId = previousResultId, PartialResultToken = progress, }, CancellationToken.None).ConfigureAwait(false); @@ -135,7 +133,7 @@ class Z } [Theory, CombinatorialData] - public async Task TestResultIds(bool mutatingLspWorkspace, bool useProgress) + public async Task TestRepeatInvocations(bool mutatingLspWorkspace, bool useProgress) { var markup1 = """ class X @@ -158,29 +156,22 @@ class Y project.Documents.First().GetURI(), useProgress: useProgress); - AssertJsonEquals(results1, new VSInternalRelatedDocumentReport[] + var expectedResult = new VSInternalRelatedDocumentReport[] { new() { - ResultId = "RelatedDocumentsHandler:0", FilePaths = [project.Documents.Last().FilePath!], } - }); + }; + + AssertJsonEquals(results1, expectedResult); // Calling again, without a change, should return the old result id and no filepaths. var results2 = await RunGetRelatedDocumentsAsync( testLspServer, project.Documents.First().GetURI(), - previousResultId: results1.Single().ResultId, useProgress: useProgress); - AssertJsonEquals(results2, new VSInternalRelatedDocumentReport[] - { - new() - { - ResultId = "RelatedDocumentsHandler:0", - FilePaths = null, - } - }); + AssertJsonEquals(results2, expectedResult); } }