Skip to content
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

Update 'Encapsulte Field' to use the new background work indicator. #61531

Merged
merged 7 commits into from
May 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#nullable disable

using System.Linq;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.CodeAnalysis.Editor.CSharp.EncapsulateField;
using Microsoft.CodeAnalysis.Editor.UnitTests;
Expand All @@ -21,7 +22,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.EncapsulateField
public class EncapsulateFieldCommandHandlerTests
{
[WpfFact, Trait(Traits.Feature, Traits.Features.EncapsulateField)]
public void EncapsulatePrivateField()
public async Task EncapsulatePrivateField()
{
var text = @"
class C
Expand Down Expand Up @@ -58,11 +59,11 @@ private void goo()
}";

using var state = EncapsulateFieldTestState.Create(text);
state.AssertEncapsulateAs(expected);
await state.AssertEncapsulateAsAsync(expected);
}

[WpfFact, Trait(Traits.Feature, Traits.Features.EncapsulateField)]
public void EncapsulateNonPrivateField()
public async Task EncapsulateNonPrivateField()
{
var text = @"
class C
Expand Down Expand Up @@ -99,11 +100,11 @@ private void goo()
}";

using var state = EncapsulateFieldTestState.Create(text);
state.AssertEncapsulateAs(expected);
await state.AssertEncapsulateAsAsync(expected);
}

[WpfFact, Trait(Traits.Feature, Traits.Features.EncapsulateField)]
public void DialogShownIfNotFieldsFound()
public async Task DialogShownIfNotFieldsFound()
{
var text = @"
class$$ C
Expand All @@ -117,12 +118,12 @@ private void goo()
}";

using var state = EncapsulateFieldTestState.Create(text);
state.AssertError();
await state.AssertErrorAsync();
}

[WorkItem(1086632, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1086632")]
[WpfFact, Trait(Traits.Feature, Traits.Features.EncapsulateField)]
public void EncapsulateTwoFields()
public async Task EncapsulateTwoFields()
{
var text = @"
class Program
Expand Down Expand Up @@ -178,7 +179,7 @@ static void Main(string[] args)
";

using var state = EncapsulateFieldTestState.Create(text);
state.AssertEncapsulateAs(expected);
await state.AssertEncapsulateAsAsync(expected);
}

[WpfFact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@

using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.Editor.CSharp.EncapsulateField;
using Microsoft.CodeAnalysis.Editor.UnitTests;
using Microsoft.CodeAnalysis.Editor.UnitTests.Extensions;
using Microsoft.CodeAnalysis.Editor.UnitTests.Utilities;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Notification;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
using Xunit;

Expand Down Expand Up @@ -47,30 +49,28 @@ public static EncapsulateFieldTestState Create(string markup)
return new EncapsulateFieldTestState(workspace);
}

public void Encapsulate()
public async Task EncapsulateAsync()
{
var args = new EncapsulateFieldCommandArgs(_testDocument.GetTextView(), _testDocument.GetTextBuffer());
var commandHandler = Workspace.ExportProvider.GetCommandHandler<EncapsulateFieldCommandHandler>(PredefinedCommandHandlerNames.EncapsulateField, ContentTypeNames.CSharpContentType);
var provider = Workspace.ExportProvider.GetExportedValue<IAsynchronousOperationListenerProvider>();
var waiter = (IAsynchronousOperationWaiter)provider.GetListener(FeatureAttribute.EncapsulateField);
commandHandler.ExecuteCommand(args, TestCommandExecutionContext.Create());
await waiter.ExpeditedWaitAsync();
}

public void Dispose()
{
if (Workspace != null)
{
Workspace.Dispose();
}
}
=> Workspace?.Dispose();

public void AssertEncapsulateAs(string expected)
public async Task AssertEncapsulateAsAsync(string expected)
{
Encapsulate();
await EncapsulateAsync();
Assert.Equal(expected, _testDocument.GetTextBuffer().CurrentSnapshot.GetText().ToString());
}

public void AssertError()
public async Task AssertErrorAsync()
{
Encapsulate();
await EncapsulateAsync();
Assert.NotNull(NotificationMessage);
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/EditorFeatures/Core/EditorFeaturesResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,9 @@ Do you want to proceed?</value>
<value>{0} (Esc to cancel)</value>
<comment>Placeholder represents an operating that is happening. Parenthesized expression indicates how you can cancel it.</comment>
</data>
<data name="Computing_Encapsulate_Field_information" xml:space="preserve">
<value>Computing 'Encapsulate Field' information</value>
</data>
<data name="Rename_operation_could_not_complete_due_to_external_change_to_workspace" xml:space="preserve">
<value>Rename operation could not complete due to external change to workspace</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@
// 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.Linq;
using Microsoft.CodeAnalysis.CodeCleanup;
using Microsoft.CodeAnalysis.CodeGeneration;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.BackgroundWorkIndicator;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Notification;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Commanding;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
using Microsoft.VisualStudio.Text.Operations;
using Microsoft.VisualStudio.Utilities;
Expand Down Expand Up @@ -44,88 +44,109 @@ public AbstractEncapsulateFieldCommandHandler(
_listener = listenerProvider.GetListener(FeatureAttribute.EncapsulateField);
}

public CommandState GetCommandState(EncapsulateFieldCommandArgs args)
=> args.SubjectBuffer.SupportsRefactorings() ? CommandState.Available : CommandState.Unspecified;

public bool ExecuteCommand(EncapsulateFieldCommandArgs args, CommandExecutionContext context)
{
if (!args.SubjectBuffer.SupportsRefactorings())
{
var textBuffer = args.SubjectBuffer;
if (!textBuffer.SupportsRefactorings())
return false;
}

using var waitScope = context.OperationContext.AddScope(allowCancellation: true, EditorFeaturesResources.Applying_Encapsulate_Field_refactoring);
var spans = args.TextView.Selection.GetSnapshotSpansOnBuffer(textBuffer);
if (spans.Count != 1)
return false;

return Execute(args, waitScope);
var document = args.SubjectBuffer.AsTextContainer().GetOpenDocumentInCurrentContext();
if (document == null)
return false;

// Fire and forget
var token = _listener.BeginAsyncOperation(FeatureAttribute.EncapsulateField);
_ = ExecuteAsync(args, document, spans.Single()).CompletesAsyncOperation(token);
return true;
}

private bool Execute(EncapsulateFieldCommandArgs args, IUIThreadOperationScope waitScope)
private async Task ExecuteAsync(
EncapsulateFieldCommandArgs args,
Document initialDocument,
SnapshotSpan span)
{
using var token = _listener.BeginAsyncOperation("EncapsulateField");
_threadingContext.ThrowIfNotOnUIThread();

var cancellationToken = waitScope.Context.UserCancellationToken;
var document = args.SubjectBuffer.CurrentSnapshot.GetFullyLoadedOpenDocumentInCurrentContextWithChanges(
waitScope.Context, _threadingContext);
if (document == null)
{
return false;
}
var subjectBuffer = args.SubjectBuffer;
var workspace = initialDocument.Project.Solution.Workspace;

var spans = args.TextView.Selection.GetSnapshotSpansOnBuffer(args.SubjectBuffer);
var indicatorFactory = workspace.Services.GetRequiredService<IBackgroundWorkIndicatorFactory>();
using var context = indicatorFactory.Create(
args.TextView, span, EditorFeaturesResources.Computing_Encapsulate_Field_information,
cancelOnEdit: true, cancelOnFocusLost: true);

var service = document.GetLanguageService<AbstractEncapsulateFieldService>();
var cancellationToken = context.UserCancellationToken;
var document = await subjectBuffer.CurrentSnapshot.GetFullyLoadedOpenDocumentInCurrentContextWithChangesAsync(context).ConfigureAwait(false);
Contract.ThrowIfNull(document);

var result = service.EncapsulateFieldsInSpanAsync(document, spans.First().Span.ToTextSpan(), _globalOptions.CreateProvider(), useDefaultBehavior: true, cancellationToken).WaitAndGetResult(cancellationToken);
var service = document.GetRequiredLanguageService<AbstractEncapsulateFieldService>();

// We are about to show a modal UI dialog so we should take over the command execution
// wait context. That means the command system won't attempt to show its own wait dialog
// and also will take it into consideration when measuring command handling duration.
waitScope.Context.TakeOwnership();
var result = await service.EncapsulateFieldsInSpanAsync(
document, span.Span.ToTextSpan(), _globalOptions.CreateProvider(), useDefaultBehavior: true, cancellationToken).ConfigureAwait(false);

var workspace = document.Project.Solution.Workspace;
if (result == null)
{
var notificationService = workspace.Services.GetService<INotificationService>();
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

// We are about to show a modal UI dialog so we should take over the command execution
// wait context. That means the command system won't attempt to show its own wait dialog
// and also will take it into consideration when measuring command handling duration.
context.TakeOwnership();

var notificationService = workspace.Services.GetRequiredService<INotificationService>();
notificationService.SendNotification(EditorFeaturesResources.Please_select_the_definition_of_the_field_to_encapsulate, severity: NotificationSeverity.Error);
return false;
return;
}

waitScope.AllowCancellation = false;
cancellationToken = waitScope.Context.UserCancellationToken;
await ApplyChangeAsync(subjectBuffer, document, result, cancellationToken).ConfigureAwait(false);
}

var finalSolution = result.GetSolutionAsync(cancellationToken).WaitAndGetResult(cancellationToken);
private async Task ApplyChangeAsync(
ITextBuffer subjectBuffer,
Document document,
EncapsulateFieldResult result, CancellationToken cancellationToken)
{
var finalSolution = await result.GetSolutionAsync(cancellationToken).ConfigureAwait(false);

var solution = document.Project.Solution;
var workspace = solution.Workspace;
var previewService = workspace.Services.GetService<IPreviewDialogService>();
if (previewService != null)
{
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
finalSolution = previewService.PreviewChanges(
string.Format(EditorFeaturesResources.Preview_Changes_0, EditorFeaturesResources.Encapsulate_Field),
"vs.csharp.refactoring.preview",
EditorFeaturesResources.Encapsulate_Field_colon,
result.Name,
result.Glyph,
finalSolution,
document.Project.Solution);
solution);
}

if (finalSolution == null)
{
// User clicked cancel.
return true;
return;
}

using (var undoTransaction = _undoManager.GetTextBufferUndoManager(args.SubjectBuffer).TextBufferUndoHistory.CreateTransaction(EditorFeaturesResources.Encapsulate_Field))
{
if (!workspace.TryApplyChanges(finalSolution))
{
undoTransaction.Cancel();
return false;
}
using var undoTransaction = _undoManager.GetTextBufferUndoManager(subjectBuffer).TextBufferUndoHistory.CreateTransaction(EditorFeaturesResources.Encapsulate_Field);

if (workspace.TryApplyChanges(finalSolution))
{
undoTransaction.Complete();
}

return true;
else
{
undoTransaction.Cancel();
}
}

public CommandState GetCommandState(EncapsulateFieldCommandArgs args)
=> args.SubjectBuffer.SupportsRefactorings() ? CommandState.Available : CommandState.Unspecified;
}
}
5 changes: 5 additions & 0 deletions src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading