// // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // // This file contain implementations details that are subject to change without notice. // Use at your own risk. // namespace Microsoft.VisualStudio.Text.Operations.Implementation { using System; using System.Diagnostics; using Microsoft.VisualStudio.Text.Editor; /// /// The UndoPrimitive to take place on the Undo stack before a text buffer change. This is the simpler /// version of the primitive that handles most common cases. /// internal class BeforeTextBufferChangeUndoPrimitive : TextUndoPrimitive { // Think twice before adding any fields here! These objects are long-lived and consume considerable space. // Unusual cases should be handled by the GeneralAfterTextBufferChangedUndoPrimitive class below. private readonly ITextUndoHistory _undoHistory; public readonly SelectionState State; private bool _canUndo; /// /// Constructs a BeforeTextBufferChangeUndoPrimitive. /// /// /// The text view that was responsible for causing this change. /// /// /// The this primitive will be added to. /// /// is null. /// is null. public static BeforeTextBufferChangeUndoPrimitive Create(ITextView textView, ITextUndoHistory undoHistory) { if (textView == null) { throw new ArgumentNullException(nameof(textView)); } if (undoHistory == null) { throw new ArgumentNullException(nameof(undoHistory)); } return new BeforeTextBufferChangeUndoPrimitive(textView, undoHistory); } private BeforeTextBufferChangeUndoPrimitive(ITextView textView, ITextUndoHistory undoHistory) { _undoHistory = undoHistory; this.State = new SelectionState(textView); _canUndo = true; } // Internal empty constructor for unit testing. internal BeforeTextBufferChangeUndoPrimitive() { } /// /// The that this is bound to. /// internal ITextView GetTextView() { ITextView view = null; _undoHistory.Properties.TryGetProperty(typeof(ITextView), out view); return view; } #region UndoPrimitive Members /// /// Returns true if operation can be undone, false otherwise. /// public override bool CanUndo { get { return _canUndo; } } /// /// Returns true if operation can be redone, false otherwise. /// public override bool CanRedo { get { return !_canUndo; } } /// /// Do the action. /// /// Operation cannot be redone. public override void Do() { // Validate, we shouldn't be allowed to undo if (!CanRedo) { throw new InvalidOperationException(Strings.CannotRedo); } // Currently, no action is done on the redo. To set the caret and selection for after a TextBuffer change redo, there is the AfterTextBufferChangeUndoPrimitive. // This Redo should not do anything with the caret and selection, because we only want to reset them after the TextBuffer change has occurred. // Therefore, we need to add this UndoPrimitive to the undo stack before the UndoPrimitive for the TextBuffer change. On an undo, the TextBuffer changed UndoPrimitive // will fire it's Undo first, and than the Undo for this UndoPrimitive will fire. // However, on a redo, the Redo for this UndoPrimitive will be fired, and then the Redo for the TextBuffer change UndoPrimitive. If we had set any caret placement/selection here (ie the new caret placement/selection), // we may crash because the TextBuffer change has not occurred yet (ie you try to set the caret to be at CharacterIndex 1 when the TextBuffer is still empty). _canUndo = true; } /// /// Undo the action. /// /// Operation cannot be undone. public override void Undo() { // Validate that we can undo this change if (!CanUndo) { throw new InvalidOperationException(Strings.CannotUndo); } // Restore the old caret position and active selection var view = this.GetTextView(); Debug.Assert(view == null || !view.IsClosed, "Attempt to undo/redo on a closed view? This shouldn't happen."); if (view != null && !view.IsClosed) { this.State.Restore(view); view.Caret.EnsureVisible(); } _canUndo = false; } public override bool CanMerge(ITextUndoPrimitive older) { if (older == null) { throw new ArgumentNullException(nameof(older)); } AfterTextBufferChangeUndoPrimitive olderPrimitive = older as AfterTextBufferChangeUndoPrimitive; // We can only merge with IUndoPrimitives of AfterTextBufferChangeUndoPrimitive type if (olderPrimitive == null) { return false; } return olderPrimitive.State.Matches(this.State); } #endregion } }