Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/microsoft/vs-editor-api.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/Text/Impl/TextBufferUndoManager/TextBufferUndoManager.cs')
-rw-r--r--src/Text/Impl/TextBufferUndoManager/TextBufferUndoManager.cs245
1 files changed, 135 insertions, 110 deletions
diff --git a/src/Text/Impl/TextBufferUndoManager/TextBufferUndoManager.cs b/src/Text/Impl/TextBufferUndoManager/TextBufferUndoManager.cs
index 77f526c..646a47e 100644
--- a/src/Text/Impl/TextBufferUndoManager/TextBufferUndoManager.cs
+++ b/src/Text/Impl/TextBufferUndoManager/TextBufferUndoManager.cs
@@ -8,159 +8,165 @@
namespace Microsoft.VisualStudio.Text.BufferUndoManager.Implementation
{
using System;
- using System.Collections.Generic;
- using System.Text;
+ using System.Diagnostics;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Operations;
- using System.Diagnostics;
- class TextBufferUndoManager : ITextBufferUndoManager, IDisposable
+ internal sealed class TextBufferUndoManager : ITextBufferUndoManager, IDisposable
{
#region Private Members
- ITextBuffer _textBuffer;
- ITextUndoHistoryRegistry _undoHistoryRegistry;
- ITextUndoHistory _undoHistory;
- Queue<ITextVersion> _editVersionList = new Queue<ITextVersion>();
- bool _inPostChanged;
+ private ITextBuffer _textBuffer;
+ private readonly ITextUndoHistoryRegistry _undoHistoryRegistry;
+ private ITextUndoHistory _undoHistory;
+
+ // The plan had been to add the IUndoMetadataEditTag to allow people to create simple edits
+ // that would restore carets. That is being pushed back to 16.0 (maybe) but I didn't want to
+ // abandon the work in progress.
+#if false
+ private readonly IEditorOperationsFactoryService _editorOperationsFactoryService;
- #endregion
+ IEditorOperations _initiatingOperations = null;
+#endif
+ ITextUndoTransaction _createdTransaction = null;
+#endregion
public TextBufferUndoManager(ITextBuffer textBuffer, ITextUndoHistoryRegistry undoHistoryRegistry)
{
if (textBuffer == null)
{
- throw new ArgumentNullException("textBuffer");
+ throw new ArgumentNullException(nameof(textBuffer));
}
if (undoHistoryRegistry == null)
{
- throw new ArgumentNullException("undoHistoryRegistry");
+ throw new ArgumentNullException(nameof(undoHistoryRegistry));
}
_textBuffer = textBuffer;
-
_undoHistoryRegistry = undoHistoryRegistry;
+#if false
+ if (editorOperationsFactoryService == null)
+ {
+ throw new ArgumentNullException(nameof(editorOperationsFactoryService));
+ }
+
+ _editorOperationsFactoryService = editorOperationsFactoryService;
+#endif
+
// Register the undo history
- _undoHistory = _undoHistoryRegistry.RegisterHistory(_textBuffer);
+ this.EnsureTextBufferUndoHistory();
// Listen for the buffer changed events so that we can make them undo/redo-able
+ _textBuffer.Changing += TextBufferChanging;
_textBuffer.Changed += TextBufferChanged;
_textBuffer.PostChanged += TextBufferPostChanged;
- _textBuffer.Changing += TextBufferChanging;
}
- #region Private Methods
+#region Private Methods
private void TextBufferChanged(object sender, TextContentChangedEventArgs e)
{
- Debug.Assert((e.EditTag as Type) != typeof(TextBufferChangeUndoPrimitive) ||
- (_undoHistory.State != TextUndoHistoryState.Idle),
- "We are undoing/redoing a change while UndoHistory.State is Idle. Something is wrong with the state.");
-
- // If this change didn't originate from undo, add a TextBufferChangeUndoPrimitive to our history.
- if (_undoHistory.State == TextUndoHistoryState.Idle &&
- (e.EditTag as Type) != typeof(TextBufferChangeUndoPrimitive))
+ if (!(e.EditTag is IUndoEditTag))
{
- // With projection, we sometimes get Changed events with no changes, or for "" -> "".
- // We don't want to create undo actions for these.
- bool nonNullChange = false;
- foreach (ITextChange c in e.BeforeVersion.Changes)
+ if (this.TextBufferUndoHistory.State != TextUndoHistoryState.Idle)
{
- if (c.OldLength != 0 || c.NewLength != 0)
+ Debug.Fail("We are doing a normal edit in a non-idle undo state. This is explicitly prohibited as it would corrupt the undo stack! Please fix your code.");
+ }
+ else
+ {
+ // With projection, we sometimes get Changed events with no changes, or for "" -> "".
+ // We don't want to create undo actions for these.
+ bool nonNullChange = false;
+ foreach (ITextChange c in e.BeforeVersion.Changes)
{
- nonNullChange = true;
- break;
+ if (c.OldLength != 0 || c.NewLength != 0)
+ {
+ nonNullChange = true;
+ break;
+ }
}
- }
- if (nonNullChange)
- {
- // Queue the edit, and actually add an undo primitive later (see comment on PostChanged).
- _editVersionList.Enqueue(e.BeforeVersion);
+ if (nonNullChange)
+ {
+ // If there's an open undo transaction, add our edit (turned into a primitive) to it. Otherwise, create and undo transaction.
+ var currentTransaction = _undoHistory.CurrentTransaction;
+ if (currentTransaction == null)
+ {
+ // TODO remove this
+ // Hack to allow Cascade's local undo to light up if using v15.7 but behave using the old -- non-local -- undo before if running on 15.6.
+ // Cascade should really be marking its edits with IInvisibleEditTag (and will once it can take a hard requirement of VS 15.7).
+ if ((e.EditTag is IInvisibleEditTag) || ((e.EditTag != null) && (string.Equals(e.EditTag.ToString(), "CascadeRemoteEdit", StringComparison.Ordinal))))
+ {
+ _createdTransaction = ((ITextUndoHistory2)_undoHistory).CreateInvisibleTransaction("<invisible>");
+ }
+#if false
+ else if (e.EditTag is IUndoMetadataEditTag metadata)
+ {
+ _createdTransaction = _undoHistory.CreateTransaction(metadata.Description);
+ if (_initiatingOperations == null)
+ {
+ var view = metadata.InitiatingView;
+ if (view != null)
+ {
+ _initiatingOperations = _editorOperationsFactoryService.GetEditorOperations(view);
+ _initiatingOperations.AddBeforeTextBufferChangePrimitive();
+ }
+ }
+ }
+#endif
+ else
+ {
+ _createdTransaction = _undoHistory.CreateTransaction(Strings.TextBufferChanged);
+ }
+
+ currentTransaction = _createdTransaction;
+ }
+
+ currentTransaction.AddUndo(new TextBufferChangeUndoPrimitive(_undoHistory, e.BeforeVersion));
+ }
}
}
}
- /// <remarks>
- /// Edits are queued up by our TextBufferChanged handler and then we finally add them to the
- /// undo stack here in response to PostChanged. The reason and history behind why we do this
- /// is as follows:
- ///
- /// Originally this was done for VB commit, which uses undo events (i.e. TransactionCompleted) to
- /// trigger commit. Their commit logic relies on the buffer being in a state such that applying
- /// an edit synchronously raises a Changed event (which is always the case for PostChanged, but
- /// not for Changed if there are nested edits).
- ///
- /// JaredPar made a change (CS 1182244) that allowed VB to detect that UndoTransactionCompleted
- /// was being fired from a nested edit, and therefore delay the actual commit until the following
- /// PostChanged event.
- ///
- /// So this allowed us to move TextBufferUndoManager back to adding undo actions directly
- /// from the TextBufferChanged handler (CS 1285117). This is preferable, as otherwise there's a
- /// "delay" between when the edit happens and when we record the edit on the undo stack,
- /// allowing other people to stick something on the undo stack (i.e. from
- /// their ITextBuffer.Changed handler) in between. The result is actions being "out-of-order"
- /// on the undo stack.
- ///
- /// Unfortunately, it turns out VB snippets actually rely on this "out-of-order" behavior
- /// (see Dev10 834740) and so we are forced to revert CS 1285117) and return to the model
- /// where we queue up edits and delay adding them to the undo stack until PostChanged.
- ///
- /// It would be good to revisit this at again, but we would need to work with VB
- /// to fix their snippets / undo behavior, and verify that VB commit is also unaffected.
- /// </remarks>
- private void TextBufferPostChanged(object sender, EventArgs e)
+ void TextBufferChanging(object sender, TextContentChangingEventArgs e)
{
- // Only process a top level PostChanged event. Nested events will continue to process TextChange events
- // which are added to the queue and will be processed below
- if ( _inPostChanged )
- {
- return;
- }
-
- _inPostChanged = true;
- try
+ // Note that VB explicitly forces undo edits to happen while the history is idle so we need to allow this here
+ // by always doing nothing for undo edits). This may be a bug in our code (e.g. not properly cleaning up when
+ // an undo transaction is cancelled in mid-flight) but changing that will require coordination with Roslyn.
+ if (!(e.EditTag is IUndoEditTag))
{
- // Do not do a foreach loop here. It's perfectly possible, and in fact expected, that the Complete
- // method below can trigger a series of events which leads to a nested edit and another
- // ITextBuffer::Changed. That event will add to the _editVersionList queue and hence break a
- // foreach loop
- while ( _editVersionList.Count > 0 )
+ if (this.TextBufferUndoHistory.State != TextUndoHistoryState.Idle)
{
- var cur = _editVersionList.Dequeue();
- using (ITextUndoTransaction undoTransaction = _undoHistory.CreateTransaction(Strings.TextBufferChanged))
- {
- TextBufferChangeUndoPrimitive undoPrimitive = new TextBufferChangeUndoPrimitive(_undoHistory, cur);
- undoTransaction.AddUndo(undoPrimitive);
-
- undoTransaction.Complete();
- }
+ Debug.Fail("We are doing a normal edit in a non-idle undo state. This is explicitly prohibited as it would corrupt the undo stack! Please fix your code.");
+ e.Cancel();
}
}
- finally
- {
- _editVersionList.Clear(); // Ensure we cleanup state in the face of an exception
- _inPostChanged = false;
- }
}
- void TextBufferChanging(object sender, TextContentChangingEventArgs e)
+ private void TextBufferPostChanged(object sender, EventArgs e)
{
- // See if somebody (other than us) is trying to edit the buffer during undo/redo.
- if (_undoHistory.State != TextUndoHistoryState.Idle &&
- (e.EditTag as Type) != typeof(TextBufferChangeUndoPrimitive))
+ if (_createdTransaction != null)
{
- Debug.Fail("Attempt to edit the buffer during undo/redo has been denied. This is explicitly prohibited as it would corrupt the undo stack! Please fix your code.");
- e.Cancel();
+#if false
+ if (_initiatingOperations != null)
+ {
+ _initiatingOperations.AddAfterTextBufferChangePrimitive();
+ }
+
+ _initiatingOperations = null;
+#endif
+
+ _createdTransaction.Complete();
+ _createdTransaction.Dispose();
+ _createdTransaction = null;
}
}
+#endregion
- #endregion
-
- #region ITextBufferUndoManager Members
+#region ITextBufferUndoManager Members
public ITextBuffer TextBuffer
{
@@ -175,31 +181,50 @@ namespace Microsoft.VisualStudio.Text.BufferUndoManager.Implementation
// we are robust, always register the undo history.
get
{
- _undoHistory = _undoHistoryRegistry.RegisterHistory(_textBuffer);
- return _undoHistory;
+ this.EnsureTextBufferUndoHistory();
+ return _undoHistory;
}
}
public void UnregisterUndoHistory()
{
// Unregister the undo history
- _undoHistoryRegistry.RemoveHistory(_undoHistory);
+ if (_undoHistory != null)
+ {
+ _undoHistoryRegistry.RemoveHistory(_undoHistory);
+ _undoHistory = null;
+ }
}
- #endregion
+#endregion
+
+ private void EnsureTextBufferUndoHistory()
+ {
+ if (_textBuffer == null)
+ throw new ObjectDisposedException("TextBufferUndoManager");
+
+ // Note, right now, there is no way for us to know if an ITextUndoHistory
+ // has been unregistered (ie it can be unregistered by a third party)
+ // An issue has been logged with the Undo team, but in the mean time, to ensure that
+ // we are robust, always register the undo history.
+ _undoHistory = _undoHistoryRegistry.RegisterHistory(_textBuffer);
+ }
- #region IDisposable Members
+#region IDisposable Members
public void Dispose()
{
- UnregisterUndoHistory();
- _textBuffer.Changed -= TextBufferChanged;
- _textBuffer.PostChanged -= TextBufferPostChanged;
- _textBuffer.Changing -= TextBufferChanging;
+ if (_textBuffer != null)
+ {
+ _textBuffer.PostChanged -= TextBufferPostChanged;
+ _textBuffer.Changed -= TextBufferChanged;
+ _textBuffer.Changing -= TextBufferChanging;
+ _textBuffer = null;
+ }
GC.SuppressFinalize(this);
}
- #endregion
+#endregion
}
}