-
Notifications
You must be signed in to change notification settings - Fork 4.2k
WIP: Initial work to support renaming files to match types inside in bulk #80246
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
21c2de6
ed02a19
14bc1fa
27761ee
de9322f
d2f3558
b0b6f89
a4b0ca6
03c4342
d51da0e
3e485cc
2f9ff31
91494e9
7851ad4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis.CodeActions; | ||
using Microsoft.CodeAnalysis.EditAndContinue; | ||
using Microsoft.CodeAnalysis.Editor.Implementation.TextDiffing; | ||
using Microsoft.CodeAnalysis.Editor.Shared.Extensions; | ||
using Microsoft.CodeAnalysis.Editor.Shared.Preview; | ||
|
@@ -90,13 +91,34 @@ internal abstract class AbstractPreviewFactoryService<TDifferenceViewer>( | |
var newProject = projectChanges.NewProject; | ||
|
||
// Exclude changes to unchangeable documents if they will be ignored when applied to workspace. | ||
foreach (var documentId in projectChanges.GetChangedDocuments(onlyGetDocumentsWithTextChanges: true, ignoreUnchangeableDocuments)) | ||
var allChangedDocuments = projectChanges.GetChangedDocuments(onlyGetDocumentsWithTextChanges: false, ignoreUnchangeableDocuments: false).ToHashSet(); | ||
var textChangedDocuments = projectChanges.GetChangedDocuments(onlyGetDocumentsWithTextChanges: true, ignoreUnchangeableDocuments).ToHashSet(); | ||
|
||
foreach (var documentId in textChangedDocuments) | ||
{ | ||
cancellationToken.ThrowIfCancellationRequested(); | ||
previewItems.Add(new SolutionPreviewItem(documentId.ProjectId, documentId, async c => | ||
await CreateChangedDocumentPreviewViewAsync(oldSolution.GetRequiredDocument(documentId), newSolution.GetRequiredDocument(documentId), zoomLevel, c).ConfigureAwaitRunInline())); | ||
} | ||
|
||
foreach (var documentId in allChangedDocuments) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this was needed as otherwise we woudln't show the preview window if only renaming a file. which means there was no place to invoke 'fix all in ...' There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please document this scenario. Also, are there any other changes to a document that should be considered other than name? |
||
{ | ||
cancellationToken.ThrowIfCancellationRequested(); | ||
|
||
if (textChangedDocuments.Contains(documentId)) | ||
continue; | ||
|
||
var oldDocument = oldProject.GetRequiredDocument(documentId); | ||
var newDocument = newProject.GetRequiredDocument(documentId); | ||
|
||
if (oldDocument.Name != newDocument.Name) | ||
{ | ||
previewItems.Add(new SolutionPreviewItem(documentId.ProjectId, documentId, | ||
string.Format(WorkspacesResources.Rename_0_to_1, oldDocument.Name, newDocument.Name))); | ||
break; | ||
} | ||
} | ||
|
||
foreach (var documentId in projectChanges.GetAddedDocuments()) | ||
{ | ||
cancellationToken.ThrowIfCancellationRequested(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// 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 System.Linq; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis.CodeActions; | ||
using Microsoft.CodeAnalysis.Shared.Extensions; | ||
using Microsoft.CodeAnalysis.Threading; | ||
using Roslyn.Utilities; | ||
|
||
namespace Microsoft.CodeAnalysis.CodeRefactorings.MoveType; | ||
|
||
internal sealed partial class MoveTypeCodeRefactoringProvider | ||
{ | ||
private sealed class MoveTypeFixAllProvider : RefactorAllProvider | ||
{ | ||
public override IEnumerable<RefactorAllScope> GetSupportedRefactorAllScopes() | ||
=> [RefactorAllScope.Project]; | ||
|
||
/// <summary> | ||
/// Preview changes dialog can't represent file renames currently. So this ends up just showing the user that | ||
/// nothing changed (which is obviously misleading). So we just suppress the dialog entirely and just make the | ||
/// change. | ||
/// </summary> | ||
internal override bool ShowPreviewChangesDialog | ||
=> false; | ||
|
||
public override Task<CodeAction?> GetRefactoringAsync(RefactorAllContext refactorAllContext) | ||
{ | ||
if (refactorAllContext.CodeActionEquivalenceKey != MoveTypeOperationKind.RenameFile.ToString()) | ||
return SpecializedTasks.Null<CodeAction>(); | ||
|
||
var codeAction = CodeAction.Create( | ||
FeaturesResources.Rename_files_to_match_types_within, | ||
async cancellationToken => | ||
{ | ||
var documentsToCheck = await refactorAllContext.GetRefactorAllSpansAsync(cancellationToken).ConfigureAwait(false); | ||
|
||
var documentIdsAndNames = await ProducerConsumer<(DocumentId documentId, string newFileName)>.RunParallelAsync( | ||
documentsToCheck.Keys, | ||
produceItems: static async (document, callback, args, cancellationToken) => | ||
{ | ||
var service = document.GetRequiredLanguageService<IMoveTypeService>(); | ||
var suggestedNames = await service.TryGetSuggestedFileRenamesAsync(document, cancellationToken).ConfigureAwait(false); | ||
if (suggestedNames.IsEmpty) | ||
return; | ||
|
||
foreach (var name in suggestedNames) | ||
{ | ||
// Ensure the new name isn't one that will conflict with an existing document. | ||
if (CollidesWithExistingDocument(document.Project.State, document.State, name)) | ||
continue; | ||
|
||
callback((document.Id, name)); | ||
} | ||
}, | ||
args: default(VoidResult), | ||
cancellationToken).ConfigureAwait(false); | ||
|
||
var currentSolution = refactorAllContext.Solution; | ||
foreach (var (documentId, newFileName) in documentIdsAndNames) | ||
{ | ||
var projectState = currentSolution.GetRequiredProjectState(documentId.ProjectId); | ||
var documentState = projectState.DocumentStates.GetRequiredState(documentId); | ||
if (CollidesWithExistingDocument(projectState, documentState, newFileName)) | ||
continue; | ||
|
||
currentSolution = currentSolution.WithDocumentName(documentId, newFileName); | ||
} | ||
|
||
return currentSolution; | ||
}); | ||
|
||
return Task.FromResult<CodeAction?>(codeAction); | ||
} | ||
|
||
private static bool CollidesWithExistingDocument(ProjectState projectState, TextDocumentState document, string newName) | ||
{ | ||
return projectState.DocumentStates.States.Any( | ||
static (kvp, args) => kvp.Value.Name == args.newName && kvp.Value.Folders.SequenceEqual(args.document.Folders), | ||
arg: (document, newName)); | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would we not use
ignoreUnchangeableDocuments
here? Can we update filename even if the document is unchangeable?