File: Packaging\PackageInstallerServiceFactory_UndoRedo.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_yayxzhyh_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.
 
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Progress;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.OLE.Interop;
 
namespace Microsoft.VisualStudio.LanguageServices.Packaging;
 
internal partial class PackageInstallerService
{
    private async Task<bool> TryInstallAndAddUndoActionAsync(
        string? source, string packageName, string? version, bool includePrerelease,
        Guid projectGuid, EnvDTE.DTE dte, EnvDTE.Project dteProject, IOleUndoManager undoManager,
        IProgress<CodeAnalysisProgress> progressTracker, CancellationToken cancellationToken)
    {
        var installed = await TryInstallPackageAsync(
            source, packageName, version, includePrerelease,
            projectGuid, dte, dteProject, progressTracker, cancellationToken).ConfigureAwait(false);
        if (installed)
        {
            // if the install succeeded, then add an uninstall item to the undo manager.
            await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
            undoManager?.Add(new UninstallPackageUndoUnit(
                this, source, packageName,
                version, includePrerelease,
                projectGuid, dte, dteProject, undoManager));
        }
 
        return installed;
    }
 
    private async Task<bool> TryUninstallAndAddRedoActionAsync(
        string? source, string packageName, string? version, bool includePrerelease,
        Guid projectGuid, EnvDTE.DTE dte, EnvDTE.Project dteProject, IOleUndoManager undoManager,
        IProgress<CodeAnalysisProgress> progressTracker, CancellationToken cancellationToken)
    {
        var uninstalled = await TryUninstallPackageAsync(
            packageName, projectGuid, dte, dteProject, progressTracker, cancellationToken).ConfigureAwait(false);
        if (uninstalled)
        {
            // if the install succeeded, then add an uninstall item to the undo manager.
            await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
            undoManager?.Add(new InstallPackageUndoUnit(
                this, source, packageName,
                version, includePrerelease,
                projectGuid, dte, dteProject, undoManager));
        }
 
        return uninstalled;
    }
 
    private abstract class BaseUndoUnit : IOleUndoUnit
    {
        protected readonly Guid projectGuid;
        protected readonly EnvDTE.DTE dte;
        protected readonly EnvDTE.Project dteProject;
        protected readonly PackageInstallerService packageInstallerService;
        protected readonly string? source;
        protected readonly string packageName;
        protected readonly IOleUndoManager undoManager;
        protected readonly string? version;
        protected readonly bool includePrerelease;
 
        protected BaseUndoUnit(
            PackageInstallerService packageInstallerService,
            string? source,
            string packageName,
            string? version,
            bool includePrerelease,
            Guid projectGuid,
            EnvDTE.DTE dte,
            EnvDTE.Project dteProject,
            IOleUndoManager undoManager)
        {
            this.packageInstallerService = packageInstallerService;
            this.source = source;
            this.packageName = packageName;
            this.version = version;
            this.includePrerelease = includePrerelease;
            this.projectGuid = projectGuid;
            this.dte = dte;
            this.dteProject = dteProject;
            this.undoManager = undoManager;
        }
 
        protected abstract Task DoWorkerAsync(IOleUndoManager pUndoManager);
        public abstract void GetDescription(out string pBstr);
 
        public void Do(IOleUndoManager pUndoManager)
        {
            var token = this.packageInstallerService._listener.BeginAsyncOperation($"{GetType().Name}.{nameof(Do)}");
            DoAsync(pUndoManager).CompletesAsyncOperation(token);
        }
 
        private async Task DoAsync(IOleUndoManager pUndoManager)
        {
            try
            {
                await DoWorkerAsync(pUndoManager).ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
            }
            catch (Exception e) when (FatalError.ReportAndCatch(e))
            {
            }
        }
 
        public void GetUnitType(out Guid pClsid, out int plID)
            => throw new NotImplementedException();
 
        public void OnNextAdd()
        {
 
        }
    }
 
    private class UninstallPackageUndoUnit : BaseUndoUnit
    {
        public UninstallPackageUndoUnit(
            PackageInstallerService packageInstallerService,
            string? source,
            string packageName,
            string? version,
            bool includePrerelease,
            Guid projectGuid,
            EnvDTE.DTE dte,
            EnvDTE.Project dteProject,
            IOleUndoManager undoManager)
            : base(packageInstallerService, source, packageName,
                   version, includePrerelease,
                   projectGuid, dte, dteProject, undoManager)
        {
        }
 
        protected override async Task DoWorkerAsync(IOleUndoManager pUndoManager)
        {
            var description = string.Format(ServicesVSResources.Uninstalling_0, packageName);
            using var context = this.packageInstallerService._operationExecutor.BeginExecute(NugetTitle, description, allowCancellation: true, showProgress: false);
            using var scope = context.AddScope(allowCancellation: true, description);
 
            await packageInstallerService.TryUninstallAndAddRedoActionAsync(
                source, packageName, version, includePrerelease,
                projectGuid, dte, dteProject, undoManager,
                scope.GetCodeAnalysisProgress(),
                context.UserCancellationToken).ConfigureAwait(false);
        }
 
        public override void GetDescription(out string pBstr)
            => pBstr = string.Format(ServicesVSResources.Uninstall_0, packageName);
    }
 
    private class InstallPackageUndoUnit : BaseUndoUnit
    {
        public InstallPackageUndoUnit(
            PackageInstallerService packageInstallerService,
            string? source,
            string packageName,
            string? version,
            bool includePrerelease,
            Guid projectGuid,
            EnvDTE.DTE dte,
            EnvDTE.Project dteProject,
            IOleUndoManager undoManager)
            : base(packageInstallerService, source, packageName,
                   version, includePrerelease,
                   projectGuid, dte, dteProject, undoManager)
        {
        }
 
        public override void GetDescription(out string pBstr)
            => pBstr = string.Format(ServicesVSResources.Install_0, packageName);
 
        protected override async Task DoWorkerAsync(IOleUndoManager pUndoManager)
        {
            var description = string.Format(ServicesVSResources.Installing_0, packageName);
            using var context = this.packageInstallerService._operationExecutor.BeginExecute(NugetTitle, description, allowCancellation: true, showProgress: false);
            using var scope = context.AddScope(allowCancellation: true, description);
 
            await packageInstallerService.TryInstallAndAddUndoActionAsync(
                source, packageName, version, includePrerelease,
                projectGuid, dte, dteProject, undoManager,
                scope.GetCodeAnalysisProgress(),
                context.UserCancellationToken).ConfigureAwait(false);
        }
    }
}