提交 e59c7c2c 编写于 作者: B Balaji Krishnan

Offer one codeaction for rename tracking and show preview popup, use the flavored action to

show view preview option. We also now start the rename task during preview and
use its result during apply. Also update unit tests.
上级 7e345e79
......@@ -610,6 +610,7 @@
<Compile Include="Implementation\RenameTracking\RenameTrackingTaggerProvider.cs" />
<Compile Include="Implementation\RenameTracking\RenameTrackingTaggerProvider.RenameTrackingCodeAction.cs" />
<Compile Include="Implementation\RenameTracking\RenameTrackingTaggerProvider.RenameTrackingCommitter.cs" />
<Compile Include="Implementation\RenameTracking\RenameTrackingTaggerProvider.RenameTrackingSolutionSet.cs" />
<Compile Include="Implementation\RenameTracking\RenameTrackingTaggerProvider.StateMachine.cs" />
<Compile Include="Implementation\RenameTracking\RenameTrackingTaggerProvider.Tagger.cs" />
<Compile Include="Implementation\RenameTracking\RenameTrackingTaggerProvider.TrackingSession.cs" />
......
......@@ -1600,15 +1600,6 @@ internal class EditorFeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Rename &apos;{0}&apos; to &apos;{1}&apos; with preview....
/// </summary>
internal static string RenameToWithPreview {
get {
return ResourceManager.GetString("RenameToWithPreview", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Renaming anonymous type members is not yet supported..
/// </summary>
......
......@@ -618,9 +618,6 @@
<data name="PreviewCodeChanges" xml:space="preserve">
<value>Preview Code Changes:</value>
</data>
<data name="RenameToWithPreview" xml:space="preserve">
<value>Rename '{0}' to '{1}' with preview...</value>
</data>
<data name="PreviewChanges" xml:space="preserve">
<value>Preview Changes</value>
</data>
......
......@@ -43,11 +43,8 @@ public sealed override Task RegisterCodeFixesAsync(CodeFixContext context)
// any fixes.
if (RenameTrackingTaggerProvider.CanInvokeRename(document))
{
var action1 = RenameTrackingTaggerProvider.CreateCodeAction(document, diagnostic, _waitIndicator, _refactorNotifyServices, _undoHistoryRegistry, showPreview: false);
var action1 = RenameTrackingTaggerProvider.CreateCodeAction(document, diagnostic, _waitIndicator, _refactorNotifyServices, _undoHistoryRegistry);
context.RegisterCodeFix(action1, diagnostic);
var action2 = RenameTrackingTaggerProvider.CreateCodeAction(document, diagnostic, _waitIndicator, _refactorNotifyServices, _undoHistoryRegistry, showPreview: true);
context.RegisterCodeFix(action2, diagnostic);
}
return SpecializedTasks.EmptyTask;
......
......@@ -21,62 +21,81 @@ private class RenameTrackingCodeAction : CodeAction
private readonly string _title;
private readonly Document _document;
private readonly IEnumerable<IRefactorNotifyService> _refactorNotifyServices;
private readonly bool _showPreview;
private readonly ITextUndoHistoryRegistry _undoHistoryRegistry;
private RenameTrackingCommitter _renameTrackingCommitter;
public RenameTrackingCodeAction(Document document, string title, IEnumerable<IRefactorNotifyService> refactorNotifyServices, ITextUndoHistoryRegistry undoHistoryRegistry, bool showPreview)
public RenameTrackingCodeAction(Document document, string title, IEnumerable<IRefactorNotifyService> refactorNotifyServices, ITextUndoHistoryRegistry undoHistoryRegistry)
{
_document = document;
_title = title;
_refactorNotifyServices = refactorNotifyServices;
_undoHistoryRegistry = undoHistoryRegistry;
_showPreview = showPreview;
}
public override string Title { get { return _title; } }
protected override Task<IEnumerable<CodeActionOperation>> ComputeOperationsAsync(CancellationToken cancellationToken)
{
// Invoked directly without previewing.
if (_renameTrackingCommitter == null)
{
if (!TryInitializeRenameTrackingCommitter(cancellationToken))
{
return SpecializedTasks.EmptyEnumerable<CodeActionOperation>();
}
////_renameTrackingCommitter.RenameSymbolAsync(cancellationToken).WaitAndGetResult(cancellationToken);
}
var committerOperation = new RenameTrackingCommitterOperation(_renameTrackingCommitter);
return Task.FromResult(SpecializedCollections.SingletonEnumerable(committerOperation as CodeActionOperation));
}
protected override async Task<IEnumerable<CodeActionOperation>> ComputePreviewOperationsAsync(CancellationToken cancellationToken)
{
if (!TryInitializeRenameTrackingCommitter(cancellationToken))
{
return await SpecializedTasks.EmptyEnumerable<CodeActionOperation>().ConfigureAwait(false);
}
var solutionSet = await _renameTrackingCommitter
.RenameSymbolAsync(isPreview: true, cancellationToken: cancellationToken)
.ConfigureAwait(false);
return SpecializedCollections.SingletonEnumerable(
(CodeActionOperation)new ApplyChangesOperation(solutionSet.RenamedSolution));
}
private bool TryInitializeRenameTrackingCommitter(CancellationToken cancellationToken)
{
SourceText text;
StateMachine stateMachine;
ITextBuffer textBuffer;
if (_document.TryGetText(out text))
{
textBuffer = text.Container.TryGetTextBuffer();
if (textBuffer == null)
{
Environment.FailFast(string.Format("document with name {0} is open but textBuffer is null. Textcontainer is of type {1}. SourceText is: {2}",
_document.Name, text.Container.GetType().FullName, text.ToString()));
}
var textBuffer = text.Container.GetTextBuffer();
if (textBuffer.Properties.TryGetProperty(typeof(StateMachine), out stateMachine))
{
TrackingSession trackingSession;
if (stateMachine.CanInvokeRename(out trackingSession, cancellationToken: cancellationToken))
if (!stateMachine.CanInvokeRename(out trackingSession, cancellationToken: cancellationToken))
{
var snapshotSpan = stateMachine.TrackingSession.TrackingSpan.GetSpan(stateMachine.Buffer.CurrentSnapshot);
var str = string.Format(EditorFeaturesResources.RenameTo, stateMachine.TrackingSession.OriginalName, snapshotSpan.GetText());
var committerOperation = new RenameTrackingCommitterOperation(new RenameTrackingCommitter(stateMachine, snapshotSpan, _refactorNotifyServices, _undoHistoryRegistry, str, showPreview: _showPreview));
return Task.FromResult(SpecializedCollections.SingletonEnumerable(committerOperation as CodeActionOperation));
// The rename tracking could be dismissed while a codefix is still cached
// in the lightbulb. If this happens, do not perform the rename requested
// and instead let the user know their fix will not be applied.
_document.Project.Solution.Workspace.Services.GetService<INotificationService>()
?.SendNotification(EditorFeaturesResources.TheRenameTrackingSessionWasCancelledAndIsNoLongerAvailable, severity: NotificationSeverity.Error);
return false;
}
// The rename tracking could be dismissed while a codefix is still cached
// in the lightbulb. If this happens, do not perform the rename requested
// and instead let the user know their fix will not be applied.
_document.Project.Solution.Workspace.Services.GetService<INotificationService>()
?.SendNotification(EditorFeaturesResources.TheRenameTrackingSessionWasCancelledAndIsNoLongerAvailable, severity: NotificationSeverity.Error);
return SpecializedTasks.EmptyEnumerable<CodeActionOperation>();
var snapshotSpan = stateMachine.TrackingSession.TrackingSpan.GetSpan(stateMachine.Buffer.CurrentSnapshot);
var newName = snapshotSpan.GetText();
var displayText = string.Format(EditorFeaturesResources.RenameTo, stateMachine.TrackingSession.OriginalName, newName);
_renameTrackingCommitter = new RenameTrackingCommitter(stateMachine, snapshotSpan, _refactorNotifyServices, _undoHistoryRegistry, displayText);
return true;
}
}
Debug.Assert(false, "RenameTracking codefix invoked on a document for which the text or StateMachine is not available.");
return SpecializedTasks.EmptyEnumerable<CodeActionOperation>();
}
protected override Task<IEnumerable<CodeActionOperation>> ComputePreviewOperationsAsync(CancellationToken cancellationToken)
{
return SpecializedTasks.EmptyEnumerable<CodeActionOperation>();
return false;
}
private sealed class RenameTrackingCommitterOperation : CodeActionOperation
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
......@@ -27,30 +29,34 @@ private class RenameTrackingCommitter : ForegroundThreadAffinitizedObject
private readonly IEnumerable<IRefactorNotifyService> _refactorNotifyServices;
private readonly ITextUndoHistoryRegistry _undoHistoryRegistry;
private readonly string _displayText;
private readonly bool _showPreview;
// This task performs the actual rename on solution. We kick this off during preview to be able to show the diff preview
// and if it was successfully completed without being cancelled, we reuse its result during the actual commit operation as well.
private Task<RenameTrackingSolutionSet> _renameSymbolTask;
// Since the renameSymbolTask is used in both Preview and Commit, we need to be able to cancel it cleanly in both phases.
// We use this CTS to cancel the task kicked off during preview during the commit phase.
private readonly CancellationTokenSource _previewTaskCancellationTokenSource = new CancellationTokenSource();
public RenameTrackingCommitter(
StateMachine stateMachine,
SnapshotSpan snapshotSpan,
IEnumerable<IRefactorNotifyService> refactorNotifyServices,
ITextUndoHistoryRegistry undoHistoryRegistry,
string displayText,
bool showPreview)
string displayText)
{
_stateMachine = stateMachine;
_snapshotSpan = snapshotSpan;
_refactorNotifyServices = refactorNotifyServices;
_undoHistoryRegistry = undoHistoryRegistry;
_displayText = displayText;
_showPreview = showPreview;
}
public void Commit(CancellationToken cancellationToken)
{
AssertIsForeground();
bool clearTrackingSession = false;
RenameSymbolAndApplyChanges(cancellationToken, out clearTrackingSession);
bool clearTrackingSession = ApplyChangesToWorkspace(cancellationToken);
// Clear the state machine so that future updates to the same token work,
// and any text changes caused by this update are not interpreted as
......@@ -61,47 +67,48 @@ public void Commit(CancellationToken cancellationToken)
}
}
private void RenameSymbolAndApplyChanges(CancellationToken cancellationToken, out bool clearTrackingSession)
public Task<RenameTrackingSolutionSet> RenameSymbolAsync(bool isPreview, CancellationToken cancellationToken)
{
AssertIsForeground();
// start rename task and use our own cancellation token if it is a Preview operation.
_renameSymbolTask = Task.Factory.SafeStartNewFromAsync(
() => RenameSymbolWorkerAsync(isPreview ? _previewTaskCancellationTokenSource.Token : cancellationToken),
isPreview ? _previewTaskCancellationTokenSource.Token : cancellationToken,
TaskScheduler.Default);
// Undo must backtrack to the state with the original identifier before the state
// with the user-edited identifier. For example,
//
// 1. Original: void M() { M(); }
// 2. User types: void Method() { M(); }
// 3. Invoke rename: void Method() { Method(); }
//
// The undo process should be as follows
// 1. Back to original name everywhere: void M() { M(); } // No tracking session
// 2. Back to state 2 above: void Method() { M(); } // Resume tracking session
// 3. Finally, start undoing typing: void M() { M(); }
//
// As far as the user can see, undo state 1 never actually existed so we must insert
// a state here to facilitate the undo. Do the work to obtain the intermediate and
// final solution without updating the workspace, and then finally disallow
// cancellation and update the workspace twice.
if (isPreview)
{
// register for a callback on the cancellation token we are handed and perform the actual cancellation upon callback.
var cancellationTokenRegistration = cancellationToken.Register(() => _previewTaskCancellationTokenSource.Cancel());
// deregister the callback after the task completes.
_renameSymbolTask.ContinueWith(_ => cancellationTokenRegistration.Dispose(),
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
}
return _renameSymbolTask;
}
private async Task<RenameTrackingSolutionSet> RenameSymbolWorkerAsync(CancellationToken cancellationToken)
{
var document = _snapshotSpan.Snapshot.GetOpenDocumentInCurrentContextWithChanges();
var newName = _snapshotSpan.GetText();
Document document = _snapshotSpan.Snapshot.GetOpenDocumentInCurrentContextWithChanges();
if (document == null)
{
Contract.Fail("Invoked rename tracking smart tag but cannot find the document for the snapshot span.");
}
// Get copy of solution with the original name in the place of the renamed name
var solutionWithOriginalName = CreateSolutionWithOriginalName(document, cancellationToken);
// Get the symbol for the identifier we're renaming (which has now been reverted to
// its original name) and invoke the rename service.
ISymbol symbol;
if (!TryGetSymbol(solutionWithOriginalName, document.Id, cancellationToken, out symbol))
var symbol = await TryGetSymbolAsync(solutionWithOriginalName, document.Id, cancellationToken).ConfigureAwait(false);
if (symbol == null)
{
Contract.Fail("Invoked rename tracking smart tag but cannot find the symbol");
Contract.Fail("Invoked rename tracking smart tag but cannot find the symbol.");
}
var newName = _snapshotSpan.GetText();
var optionSet = document.Project.Solution.Workspace.Options;
if (_stateMachine.TrackingSession.ForceRenameOverloads)
......@@ -109,37 +116,60 @@ private void RenameSymbolAndApplyChanges(CancellationToken cancellationToken, ou
optionSet = optionSet.WithChangedOption(RenameOptions.RenameOverloads, true);
}
var renamedSolution = Renamer.RenameSymbolAsync(solutionWithOriginalName, symbol, newName, optionSet, cancellationToken).WaitAndGetResult(cancellationToken);
var renamedSolution = await Renamer.RenameSymbolAsync(solutionWithOriginalName, symbol, newName, optionSet, cancellationToken).ConfigureAwait(false);
return new RenameTrackingSolutionSet(symbol, solutionWithOriginalName, renamedSolution);
}
private bool ApplyChangesToWorkspace(CancellationToken cancellationToken)
{
AssertIsForeground();
// Now that the necessary work has been done to create the intermediate and final
// solutions, check one more time for cancellation before making all of the
// solutions during PreparePreview, check one more time for cancellation before making all of the
// workspace changes.
cancellationToken.ThrowIfCancellationRequested();
if (_showPreview)
// Undo must backtrack to the state with the original identifier before the state
// with the user-edited identifier. For example,
//
// 1. Original: void M() { M(); }
// 2. User types: void Method() { M(); }
// 3. Invoke rename: void Method() { Method(); }
//
// The undo process should be as follows
// 1. Back to original name everywhere: void M() { M(); } // No tracking session
// 2. Back to state 2 above: void Method() { M(); } // Resume tracking session
// 3. Finally, start undoing typing: void M() { M(); }
//
// As far as the user can see, undo state 1 never actually existed so we must insert
// a state here to facilitate the undo. Do the work to obtain the intermediate and
// final solution without updating the workspace, and then finally disallow
// cancellation and update the workspace twice.
RenameTrackingSolutionSet renameTrackingSolutionSet;
try
{
var previewService = renamedSolution.Workspace.Services.GetService<IPreviewDialogService>();
renamedSolution = previewService.PreviewChanges(
string.Format(EditorFeaturesResources.PreviewChangesOf, EditorFeaturesResources.Rename),
"vs.csharp.refactoring.rename",
string.Format(
EditorFeaturesResources.RenameToTitle,
_stateMachine.TrackingSession.OriginalName,
newName),
symbol.ToDisplayString(),
symbol.GetGlyph(),
renamedSolution,
solutionWithOriginalName);
if (renamedSolution == null)
// If the task was kicked off and had successfully completed to show preview, re-use its result.
if (_renameSymbolTask != null && _renameSymbolTask.Status == TaskStatus.RanToCompletion)
{
renameTrackingSolutionSet = _renameSymbolTask.Result;
}
else
{
// User clicked cancel.
clearTrackingSession = false;
return;
// If the task isn't complete yet, cancel it anyway, because lightbulb will cancel the preview
// operation once commit is entered, because the preview has technically been dismissed.
// We kick off another rename task using Commit's cancellation here.
_previewTaskCancellationTokenSource.Cancel();
renameTrackingSolutionSet = RenameSymbolAsync(isPreview: false, cancellationToken: cancellationToken).WaitAndGetResult(cancellationToken);
}
}
finally
{
_previewTaskCancellationTokenSource.Dispose();
}
var document = _snapshotSpan.Snapshot.GetOpenDocumentInCurrentContextWithChanges();
var newName = _snapshotSpan.GetText();
var workspace = document.Project.Solution.Workspace;
......@@ -147,15 +177,15 @@ private void RenameSymbolAndApplyChanges(CancellationToken cancellationToken, ou
// text changes caused by undo and redo actions as potential renames, so carefully
// update the state machine after undo/redo actions.
var changedDocuments = renamedSolution.GetChangedDocuments(solutionWithOriginalName);
var changedDocuments = renameTrackingSolutionSet.RenamedSolution.GetChangedDocuments(renameTrackingSolutionSet.OriginalSolution);
// When this action is undone (the user has undone twice), restore the state
// machine to so that they can continue their original rename tracking session.
UpdateWorkspaceForResetOfTypedIdentifier(workspace, solutionWithOriginalName);
UpdateWorkspaceForResetOfTypedIdentifier(workspace, renameTrackingSolutionSet.OriginalSolution);
// Now that the solution is back in its original state, notify third parties about
// the coming rename operation.
if (!_refactorNotifyServices.TryOnBeforeGlobalSymbolRenamed(workspace, changedDocuments, symbol, newName, throwOnFailure: false))
if (!_refactorNotifyServices.TryOnBeforeGlobalSymbolRenamed(workspace, changedDocuments, renameTrackingSolutionSet.Symbol, newName, throwOnFailure: false))
{
var notificationService = workspace.Services.GetService<INotificationService>();
notificationService.SendNotification(
......@@ -163,8 +193,7 @@ private void RenameSymbolAndApplyChanges(CancellationToken cancellationToken, ou
EditorFeaturesResources.RenameSymbol,
NotificationSeverity.Error);
clearTrackingSession = true;
return;
return true;
}
// move all changes to final solution based on the workspace's current solution, since the current solution
......@@ -174,15 +203,15 @@ private void RenameSymbolAndApplyChanges(CancellationToken cancellationToken, ou
{
// because changes have already been made to the workspace (UpdateWorkspaceForResetOfTypedIdentifier() above),
// these calls can't be cancelled and must be allowed to complete.
var root = renamedSolution.GetDocument(docId).GetSyntaxRootAsync(CancellationToken.None).WaitAndGetResult(CancellationToken.None);
var root = renameTrackingSolutionSet.RenamedSolution.GetDocument(docId).GetSyntaxRootAsync(CancellationToken.None).WaitAndGetResult(CancellationToken.None);
finalSolution = finalSolution.WithDocumentSyntaxRoot(docId, root);
}
// Undo/redo on this action must always clear the state machine
UpdateWorkspaceForGlobalIdentifierRename(workspace, finalSolution, workspace.CurrentSolution, _displayText, changedDocuments, symbol, newName);
UpdateWorkspaceForGlobalIdentifierRename(workspace, finalSolution, workspace.CurrentSolution, _displayText, changedDocuments, renameTrackingSolutionSet.Symbol, newName);
RenameTrackingDismisser.DismissRenameTracking(workspace, changedDocuments);
clearTrackingSession = true;
return true;
}
private Solution CreateSolutionWithOriginalName(Document document, CancellationToken cancellationToken)
......@@ -209,20 +238,19 @@ private Solution CreateSolutionWithOriginalName(Document document, CancellationT
return solution;
}
private bool TryGetSymbol(Solution solutionWithOriginalName, DocumentId documentId, CancellationToken cancellationToken, out ISymbol symbol)
private async Task<ISymbol> TryGetSymbolAsync(Solution solutionWithOriginalName, DocumentId documentId, CancellationToken cancellationToken)
{
var documentWithOriginalName = solutionWithOriginalName.GetDocument(documentId);
var syntaxTreeWithOriginalName = documentWithOriginalName.GetSyntaxTreeAsync(cancellationToken).WaitAndGetResult(cancellationToken);
var syntaxTreeWithOriginalName = await documentWithOriginalName.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
var syntaxFacts = documentWithOriginalName.GetLanguageService<ISyntaxFactsService>();
var semanticFacts = documentWithOriginalName.GetLanguageService<ISemanticFactsService>();
var semanticModel = documentWithOriginalName.GetSemanticModelAsync(cancellationToken).WaitAndGetResult(cancellationToken);
var semanticModel = await documentWithOriginalName.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var token = syntaxTreeWithOriginalName.GetTouchingWord(_snapshotSpan.Start, syntaxFacts, cancellationToken);
var tokenRenameInfo = RenameUtilities.GetTokenRenameInfo(semanticFacts, semanticModel, token, cancellationToken);
symbol = tokenRenameInfo.HasSymbols ? tokenRenameInfo.Symbols.First() : null;
return symbol != null;
return tokenRenameInfo.HasSymbols ? tokenRenameInfo.Symbols.First() : null;
}
private void UpdateWorkspaceForResetOfTypedIdentifier(Workspace workspace, Solution newSolution)
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.CodeAnalysis.Editor.Implementation.RenameTracking
{
internal sealed partial class RenameTrackingTaggerProvider
{
/// <summary>
/// Tracks the solution before and after rename.
/// </summary>
private class RenameTrackingSolutionSet
{
public ISymbol Symbol { get; }
public Solution OriginalSolution { get; }
public Solution RenamedSolution { get; }
public RenameTrackingSolutionSet(
ISymbol symbolToRename,
Solution originalSolution,
Solution renamedSolution)
{
Symbol = symbolToRename;
OriginalSolution = originalSolution;
RenamedSolution = renamedSolution;
}
}
}
}
......@@ -130,25 +130,23 @@ internal static async Task<IEnumerable<Diagnostic>> GetDiagnosticsAsync(SyntaxTr
{
throw ExceptionUtilities.Unreachable;
}
}
}
internal static CodeAction CreateCodeAction(
Document document,
Diagnostic diagnostic,
IWaitIndicator waitIndicator,
IEnumerable<IRefactorNotifyService> refactorNotifyServices,
ITextUndoHistoryRegistry undoHistoryRegistry,
bool showPreview)
ITextUndoHistoryRegistry undoHistoryRegistry)
{
// This can run on a background thread.
var renameToResourceString = showPreview ? EditorFeaturesResources.RenameToWithPreview : EditorFeaturesResources.RenameTo;
var message = string.Format(
renameToResourceString,
diagnostic.Properties[RenameTrackingDiagnosticAnalyzer.RenameFromPropertyKey],
EditorFeaturesResources.RenameTo,
diagnostic.Properties[RenameTrackingDiagnosticAnalyzer.RenameFromPropertyKey],
diagnostic.Properties[RenameTrackingDiagnosticAnalyzer.RenameToPropertyKey]);
return new RenameTrackingCodeAction(document, message, refactorNotifyServices, undoHistoryRegistry, showPreview);
return new RenameTrackingCodeAction(document, message, refactorNotifyServices, undoHistoryRegistry);
}
internal static bool IsRenamableIdentifier(Task<TriggerIdentifierKind> isRenamableIdentifierTask, bool waitForResult, CancellationToken cancellationToken)
......@@ -201,6 +199,6 @@ internal static bool CanInvokeRename(Document document)
return textBuffer != null &&
textBuffer.Properties.TryGetProperty(typeof(StateMachine), out stateMachine) &&
stateMachine.CanInvokeRename(out unused);
}
}
}
}
......@@ -835,56 +835,6 @@ Enum E
}
}
[Fact, WorkItem(978099)]
[Trait(Traits.Feature, Traits.Features.RenameTracking)]
public void RenameTrackingPreviewChanges()
{
var code = @"
class C$$
{
}";
using (var state = new RenameTrackingTestState(code, LanguageNames.CSharp))
{
var mockPreview = state.Workspace.Services.GetService<IPreviewDialogService>() as MockPreviewDialogService;
mockPreview.Called = false;
mockPreview.ReturnsNull = false;
state.EditorOperations.InsertText("at");
state.AssertTag("C", "Cat", invokeAction: true, actionIndex: 1);
Assert.True(mockPreview.Called);
Assert.Equal(string.Format(EditorFeaturesResources.RenameToTitle, "C", "Cat"), mockPreview.Description);
Assert.Equal(string.Format(EditorFeaturesResources.PreviewChangesOf, EditorFeaturesResources.Rename), mockPreview.Title);
Assert.Equal("C", mockPreview.TopLevelName);
Assert.Equal(Glyph.ClassInternal, mockPreview.TopLevelGlyph);
}
}
[Fact]
[Trait(Traits.Feature, Traits.Features.RenameTracking)]
public void RenameTrackingCancelPreviewChanges()
{
var code = @"
class C$$
{
}";
var expected = @"
class Cat
{
}";
using (var state = new RenameTrackingTestState(code, LanguageNames.CSharp))
{
var mockPreview = state.Workspace.Services.GetService<IPreviewDialogService>() as MockPreviewDialogService;
mockPreview.Called = false;
mockPreview.ReturnsNull = true;
state.EditorOperations.InsertText("at");
state.AssertTag("C", "Cat", invokeAction: true, actionIndex: 1);
Assert.True(mockPreview.Called);
Assert.Equal(expected, state.HostDocument.TextBuffer.CurrentSnapshot.GetText());
state.AssertTag("C", "Cat");
}
}
[Fact, WorkItem(1028072)]
[Trait(Traits.Feature, Traits.Features.RenameTracking)]
public void RenameTrackingDoesNotThrowAggregateException()
......
......@@ -154,7 +154,7 @@ public IList<Diagnostic> GetDocumentDiagnostics(Document document = null)
return DiagnosticProviderTestUtilities.GetDocumentDiagnostics(analyzer, document, document.GetSyntaxRootAsync(CancellationToken.None).Result.FullSpan).ToList();
}
public void AssertTag(string expectedFromName, string expectedToName, bool invokeAction = false, int actionIndex = 0)
public void AssertTag(string expectedFromName, string expectedToName, bool invokeAction = false)
{
WaitForAsyncOperations();
......@@ -176,15 +176,14 @@ public void AssertTag(string expectedFromName, string expectedToName, bool invok
var context = new CodeFixContext(document, diagnostics[0], (a, d) => actions.Add(a), CancellationToken.None);
_codeFixProvider.RegisterCodeFixesAsync(context).Wait();
// There should be two actions
Assert.Equal(2, actions.Count);
// There should only be one code action
Assert.Equal(1, actions.Count);
Assert.Equal(string.Format(EditorFeaturesResources.RenameTo, expectedFromName, expectedToName), actions[0].Title);
Assert.Equal(string.Format(EditorFeaturesResources.RenameToWithPreview, expectedFromName, expectedToName), actions[1].Title);
if (invokeAction)
{
var operations = actions[actionIndex]
var operations = actions[0]
.GetOperationsAsync(CancellationToken.None)
.WaitAndGetResult(CancellationToken.None)
.ToArray();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册