|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.Text.Operations;
namespace Microsoft.CodeAnalysis.Editor.Shared.Utilities;
/// <summary>
/// An implementation of <see cref="ITextUndoTransaction" /> that wraps another
/// <see cref="ITextUndoTransaction" />. Some undo implementations (notably the VS implementation)
/// violate the specified contract for Cancel(), which states that cancelling an active transaction
/// should undo the primitives that we already added. This works around that problem; calling Cancel()
/// on this forwards the cancellation to the inner transaction, and if it failed to roll back we
/// do it ourselves.
/// </summary>
internal sealed class HACK_TextUndoTransactionThatRollsBackProperly(ITextUndoTransaction innerTransaction) : ITextUndoTransaction
{
private readonly ITextUndoTransaction _innerTransaction = innerTransaction;
private readonly RollbackDetectingUndoPrimitive _undoPrimitive = new();
private bool _transactionOpen = true;
public bool CanRedo => _innerTransaction.CanRedo;
public bool CanUndo => _innerTransaction.CanUndo;
public string Description
{
get
{
return _innerTransaction.Description;
}
set
{
_innerTransaction.Description = value;
}
}
public ITextUndoHistory History => _innerTransaction.History;
public IMergeTextUndoTransactionPolicy MergePolicy
{
get
{
return _innerTransaction.MergePolicy;
}
set
{
_innerTransaction.MergePolicy = value;
}
}
public ITextUndoTransaction Parent => _innerTransaction.Parent;
public UndoTransactionState State => _innerTransaction.State;
public IList<ITextUndoPrimitive> UndoPrimitives => _innerTransaction.UndoPrimitives;
public void AddUndo(ITextUndoPrimitive undo)
=> _innerTransaction.AddUndo(undo);
public void Cancel()
{
var transactionWasOpen = _transactionOpen;
_transactionOpen = false;
// First, add an undo primitive so we can detect whether or not undo gets called
if (transactionWasOpen)
{
_innerTransaction.AddUndo(_undoPrimitive);
}
_innerTransaction.Cancel();
if (transactionWasOpen && !_undoPrimitive.UndoCalled)
{
// Undo each of the primitives in reverse order to clean up. This is slimy.
foreach (var primitive in _innerTransaction.UndoPrimitives.Reverse())
{
primitive.Undo();
}
}
}
public void Complete()
{
_transactionOpen = false;
_innerTransaction.Complete();
}
public void Dispose()
{
if (_transactionOpen)
{
// Call our cancel method first to ensure we handle it properly
Cancel();
}
_innerTransaction.Dispose();
}
public void Do()
=> _innerTransaction.Do();
public void Undo()
=> _innerTransaction.Undo();
private sealed class RollbackDetectingUndoPrimitive : ITextUndoPrimitive
{
internal bool UndoCalled = false;
public bool CanRedo => true;
public bool CanUndo => true;
public ITextUndoTransaction? Parent { get; set; }
public bool CanMerge(ITextUndoPrimitive older)
=> false;
public void Do()
{
}
public ITextUndoPrimitive Merge(ITextUndoPrimitive older)
=> throw new NotSupportedException();
public void Undo()
=> UndoCalled = true;
}
}
|