From 5ddfd6143bcc5adb41b35e21485818edc72b4034 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Thu, 23 Apr 2020 11:53:28 -0700 Subject: [PATCH] Use merge linked file api properly instead of manually de-duping. --- .../Extensions/TextEditEqualityComparer.cs | 26 --------- .../Protocol/Handler/Rename/RenameHandler.cs | 56 +++++++++---------- 2 files changed, 25 insertions(+), 57 deletions(-) delete mode 100644 src/Features/LanguageServer/Protocol/Extensions/TextEditEqualityComparer.cs diff --git a/src/Features/LanguageServer/Protocol/Extensions/TextEditEqualityComparer.cs b/src/Features/LanguageServer/Protocol/Extensions/TextEditEqualityComparer.cs deleted file mode 100644 index 8b9b2e3ba2f..00000000000 --- a/src/Features/LanguageServer/Protocol/Extensions/TextEditEqualityComparer.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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. - -using System.Collections.Generic; -using Microsoft.VisualStudio.LanguageServer.Protocol; - -namespace Microsoft.CodeAnalysis.LanguageServer -{ - internal class TextEditEqualityComparer : IEqualityComparer - { - public bool Equals(TextEdit x, TextEdit y) - { - return EqualityComparer.Default.Equals(x.Range, y.Range) && - x.NewText == y.NewText; - } - - public int GetHashCode(TextEdit obj) - { - var hashCode = -1114201889; - hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(obj.Range); - hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(obj.NewText); - return hashCode; - } - } -} diff --git a/src/Features/LanguageServer/Protocol/Handler/Rename/RenameHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Rename/RenameHandler.cs index cc3ed1a2088..18599d99521 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Rename/RenameHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Rename/RenameHandler.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.VisualStudio.LanguageServer.Protocol; using Roslyn.Utilities; @@ -22,8 +23,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler [ExportLspMethod(LSP.Methods.TextDocumentRenameName), Shared] internal class RenameHandler : IRequestHandler { - private static TextEditEqualityComparer s_textEditEqualityComparer = new TextEditEqualityComparer(); - [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public RenameHandler() @@ -48,43 +47,38 @@ public RenameHandler() var renameLocationSet = await renameInfo.FindRenameLocationsAsync(solution.Workspace.Options, cancellationToken).ConfigureAwait(false); var renameReplacementInfo = await renameLocationSet.GetReplacementsAsync(request.NewName, solution.Workspace.Options, cancellationToken).ConfigureAwait(false); - var newSolution = renameReplacementInfo.NewSolution; - var solutionChanges = newSolution.GetChanges(solution); + var renamedSolution = renameReplacementInfo.NewSolution; + var solutionChanges = renamedSolution.GetChanges(solution); - var solutionWithLinkedFileChangesMerged = newSolution.WithMergedLinkedFileChangesAsync(solution, solutionChanges, cancellationToken: cancellationToken).Result; - var changedDocuments = solutionWithLinkedFileChangesMerged.GetChanges(solution).GetProjectChanges().SelectMany(p => p.GetChangedDocuments(onlyGetDocumentsWithTextChanges: true)); + // Linked files can correspond to multiple roslyn documents each with changes. W merge the changes in the linked files so that all linked documents have the same text. + // Then we can just take the text changes from the first document to avoid returning duplicate edits. + var solutionWithLinkedFileChangesMerged = await renamedSolution.WithMergedLinkedFileChangesAsync(solution, solutionChanges, cancellationToken: cancellationToken).ConfigureAwait(false); + solutionChanges = solutionWithLinkedFileChangesMerged.GetChanges(solution); + var changedDocuments = solutionChanges + .GetProjectChanges() + .SelectMany(p => p.GetChangedDocuments(onlyGetDocumentsWithTextChanges: true)) + .GroupBy(docId => solutionWithLinkedFileChangesMerged.GetRequiredDocument(docId).FilePath, StringComparer.OrdinalIgnoreCase).Select(group => group.First()); - // Linked files will create multiple documents with duplicate edits for the same actual file. - // So take only the unique edits per URI to avoid returning duplicate edits. - var textEdits = new Dictionary>(); - foreach (var changedDocument in changedDocuments) + using var _ = ArrayBuilder.GetInstance(out var documentEdits); + foreach (var docId in changedDocuments) { - var documentUri = newSolution.GetRequiredDocument(changedDocument).GetURI(); - var textChangesForUri = textEdits.GetOrValue(documentUri, new List()); - textChangesForUri.AddRange(await GetTextChangesAsync(changedDocument, newSolution, solution, cancellationToken).ConfigureAwait(false)); - textEdits[documentUri] = textChangesForUri.Distinct(s_textEditEqualityComparer).ToList(); - } + var oldDoc = solution.GetRequiredDocument(docId); + var newDoc = solutionWithLinkedFileChangesMerged.GetRequiredDocument(docId); - var documentEdits = textEdits.Select(kvp => new TextDocumentEdit - { - TextDocument = new VersionedTextDocumentIdentifier { Uri = kvp.Key }, - Edits = kvp.Value.ToArray() - }).ToArray(); + var textChanges = await newDoc.GetTextChangesAsync(oldDoc, cancellationToken).ConfigureAwait(false); + var oldText = await oldDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); + var textDocumentEdit = new TextDocumentEdit + { + TextDocument = new VersionedTextDocumentIdentifier { Uri = newDoc.GetURI() }, + Edits = textChanges.Select(tc => ProtocolConversions.TextChangeToTextEdit(tc, oldText)).ToArray() + }; + documentEdits.Add(textDocumentEdit); + } - workspaceEdit = new WorkspaceEdit { DocumentChanges = documentEdits }; + workspaceEdit = new WorkspaceEdit { DocumentChanges = documentEdits.ToArray() }; } return workspaceEdit; - - static async Task> GetTextChangesAsync(DocumentId documentId, Solution newSolution, Solution oldSolution, CancellationToken cancellationToken) - { - var oldDoc = oldSolution.GetRequiredDocument(documentId); - var newDoc = newSolution.GetRequiredDocument(documentId); - - var textChanges = await newDoc.GetTextChangesAsync(oldDoc, cancellationToken).ConfigureAwait(false); - var oldText = await oldDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); - return textChanges.Select(textChange => ProtocolConversions.TextChangeToTextEdit(textChange, oldText)); - } } } } -- GitLab