internal void ApplyReplacementText(string replacementText, bool propagateEditImmediately) { AssertIsForeground(); VerifyNotDismissed(); this.ReplacementText = _renameInfo.GetFinalSymbolName(replacementText); var asyncToken = _asyncListener.BeginAsyncOperation(nameof(ApplyReplacementText)); Action propagateEditAction = delegate { AssertIsForeground(); if (_dismissed) { asyncToken.Dispose(); return; } _isApplyingEdit = true; using (Logger.LogBlock(FunctionId.Rename_ApplyReplacementText, replacementText, _cancellationTokenSource.Token)) { foreach (var openBuffer in _openTextBuffers.Values) { openBuffer.ApplyReplacementText(); } } _isApplyingEdit = false; // We already kicked off UpdateConflictResolutionTask below (outside the delegate). // Now that we are certain the replacement text has been propagated to all of the // open buffers, it is safe to actually apply the replacements it has calculated. // See https://devdiv.visualstudio.com/DevDiv/_workitems?_a=edit&id=227513 QueueApplyReplacements(); asyncToken.Dispose(); }; // Start the conflict resolution task but do not apply the results immediately. The // buffer changes performed in propagateEditAction can cause source control modal // dialogs to show. Those dialogs pump, and yield the UI thread to whatever work is // waiting to be done there, including our ApplyReplacements work. If ApplyReplacements // starts running on the UI thread while propagateEditAction is still updating buffers // on the UI thread, we crash because we try to enumerate the undo stack while an undo // transaction is still in process. Therefore, we defer QueueApplyReplacements until // after the buffers have been edited, and any modal dialogs have been completed. // In addition to avoiding the crash, this also ensures that the resolved conflict text // is applied after the simple text change is propagated. // See https://devdiv.visualstudio.com/DevDiv/_workitems?_a=edit&id=227513 UpdateConflictResolutionTask(); if (propagateEditImmediately) { propagateEditAction(); } else { // When responding to a text edit, we delay propagating the edit until the first transaction completes. ThreadingContext.JoinableTaskFactory.WithPriority(Dispatcher.CurrentDispatcher, DispatcherPriority.Send).RunAsync(async() => { await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true); propagateEditAction(); }); } }
private void CommitCore(IWaitContext waitContext, bool previewChanges) { using (Logger.LogBlock(previewChanges ? FunctionId.Rename_CommitCoreWithPreview : FunctionId.Rename_CommitCore, waitContext.CancellationToken)) { _conflictResolutionTask.Wait(waitContext.CancellationToken); waitContext.AllowCancel = false; Solution newSolution = _conflictResolutionTask.Result.NewSolution; if (previewChanges) { var previewService = _workspace.Services.GetService <IPreviewDialogService>(); newSolution = previewService.PreviewChanges( string.Format(EditorFeaturesResources.Preview_Changes_0, EditorFeaturesResources.Rename), "vs.csharp.refactoring.rename", string.Format(EditorFeaturesResources.Rename_0_to_1_colon, this.OriginalSymbolName, _renameInfo.GetFinalSymbolName(this.ReplacementText)), _renameInfo.FullDisplayName, _renameInfo.Glyph, _conflictResolutionTask.Result.NewSolution, _triggerDocument.Project.Solution); if (newSolution == null) { // User clicked cancel. return; } } // The user hasn't cancelled by now, so we're done waiting for them. Off to // rename! waitContext.Message = EditorFeaturesResources.Updating_files; Dismiss(rollbackTemporaryEdits: true); CancelAllOpenDocumentTrackingTasks(); ApplyRename(newSolution, waitContext); LogRenameSession(RenameLogMessage.UserActionOutcome.Committed, previewChanges); EndRenameSession(); } }