File: Workspace\GlobalUndoServiceFactory.WorkspaceGlobalUndoTransaction.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_ozsccwvc_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices)
// 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.
 
#nullable disable
 
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Editor.Undo;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Text.Operations;
using Microsoft.VisualStudio.TextManager.Interop;
using Roslyn.Utilities;
 
namespace Microsoft.VisualStudio.LanguageServices.Implementation;
 
using Workspace = Microsoft.CodeAnalysis.Workspace;
 
internal partial class GlobalUndoServiceFactory
{
    private sealed class WorkspaceUndoTransaction : IWorkspaceGlobalUndoTransaction
    {
        private readonly IThreadingContext _threadingContext;
        private readonly ITextUndoHistoryRegistry _undoHistoryRegistry;
        private readonly IVsLinkedUndoTransactionManager _undoManager;
        private readonly Workspace _workspace;
        private readonly string _description;
        private readonly GlobalUndoService _service;
 
        // indicate whether undo transaction is currently active
        private bool _transactionAlive;
 
        public WorkspaceUndoTransaction(
            IThreadingContext threadingContext,
            ITextUndoHistoryRegistry undoHistoryRegistry,
            IVsLinkedUndoTransactionManager undoManager,
            Workspace workspace,
            string description,
            GlobalUndoService service)
        {
            _threadingContext = threadingContext;
            _undoHistoryRegistry = undoHistoryRegistry;
            _undoManager = undoManager;
            _workspace = workspace;
            _description = description;
            _service = service;
 
            _threadingContext.ThrowIfNotOnUIThread();
 
            Marshal.ThrowExceptionForHR(_undoManager.OpenLinkedUndo((uint)LinkedTransactionFlags2.mdtGlobal, _description));
            _transactionAlive = true;
        }
 
        public void AddDocument(DocumentId id)
        {
            var visualStudioWorkspace = (VisualStudioWorkspace)_workspace;
            Contract.ThrowIfNull(visualStudioWorkspace);
 
            var solution = visualStudioWorkspace.CurrentSolution;
            var document = solution.GetDocument(id);
            if (document == null)
            {
                // document is not part of the workspace (newly created document that is not applied to the workspace yet?)
                return;
            }
 
            if (visualStudioWorkspace.IsDocumentOpen(id))
            {
                var container = document.GetTextSynchronously(CancellationToken.None).Container;
                var textBuffer = container.TryGetTextBuffer();
                var undoHistory = _undoHistoryRegistry.RegisterHistory(textBuffer);
 
                using var undoTransaction = undoHistory.CreateTransaction(_description);
                undoTransaction.AddUndo(new NoOpUndoPrimitive());
                undoTransaction.Complete();
            }
            else
            {
                // open and close the document so that it is included in the global undo transaction
                using (visualStudioWorkspace.OpenInvisibleEditor(id))
                {
                    // empty
                }
            }
        }
 
        public void Commit()
        {
            _threadingContext.ThrowIfNotOnUIThread();
 
            // once either commit or disposed is called, don't do finalizer check
            GC.SuppressFinalize(this);
 
            if (_transactionAlive)
            {
                _service.ActiveTransactions--;
 
                var result = _undoManager.CloseLinkedUndo();
                if (result == VSConstants.UNDO_E_CLIENTABORT)
                {
                    Dispose();
                }
                else
                {
                    Marshal.ThrowExceptionForHR(result);
                    _transactionAlive = false;
                }
            }
        }
 
        public void Dispose()
        {
            _threadingContext.ThrowIfNotOnUIThread();
 
            // once either commit or disposed is called, don't do finalizer check
            GC.SuppressFinalize(this);
 
            if (_transactionAlive)
            {
                _service.ActiveTransactions--;
 
                Marshal.ThrowExceptionForHR(_undoManager.AbortLinkedUndo());
                _transactionAlive = false;
            }
        }
 
#if DEBUG
        ~WorkspaceUndoTransaction()
        {
            // make sure we closed it correctly
            Debug.Assert(!_transactionAlive);
        }
#endif
    }
}