| File: AddImport\CodeActions\InstallPackageAndAddImportCodeAction.cs | Web Access |
| Project: src\roslyn\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features) |
// 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.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.AddPackage; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.AddImport; internal sealed class InstallPackageAndAddImportCodeAction : AddImportCodeAction { public override string Title { get; } /// <summary> /// The operation that will actually install the nuget package. /// </summary> private readonly InstallPackageDirectlyCodeActionOperation _installOperation; /// <summary> /// This code action only works by installing a package. As such, it requires a non document change (and is /// thus restricted in which hosts it can run). /// </summary> public InstallPackageAndAddImportCodeAction( Document originalDocument, AddImportFixData fixData, string title, InstallPackageDirectlyCodeActionOperation installOperation) : base(originalDocument, fixData, RequiresNonDocumentChangeTags) { Contract.ThrowIfFalse(fixData.Kind == AddImportFixKind.PackageSymbol); Title = title; _installOperation = installOperation; } /// <summary> /// For preview purposes we return all the operations in a list. This way the /// preview system stiches things together in the UI to make a suitable display. /// i.e. if we have a SolutionChangedOperation and some other operation with a /// Title, then the UI will show that nicely to the user. /// </summary> protected override async Task<IEnumerable<CodeActionOperation>> ComputePreviewOperationsAsync(CancellationToken cancellationToken) { // Make a SolutionChangeAction. This way we can let it generate the diff // preview appropriately. var solutionChangeAction = SolutionChangeAction.Create("", GetUpdatedSolutionAsync, ""); using var _ = ArrayBuilder<CodeActionOperation>.GetInstance(out var result); result.AddRange(await solutionChangeAction.GetPreviewOperationsAsync( this.OriginalDocument.Project.Solution, cancellationToken).ConfigureAwait(false)); result.Add(_installOperation); return result.ToImmutableAndClear(); } private async Task<Solution> GetUpdatedSolutionAsync(CancellationToken cancellationToken) { var newDocument = await GetUpdatedDocumentAsync(cancellationToken).ConfigureAwait(false); var newRoot = await newDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); // Suppress diagnostics on the import we create. Because we only get here when we are // adding a nuget package, it is certainly the case that in the preview this will not // bind properly. It will look silly to show such an error, so we just suppress things. var updatedRoot = newRoot.WithAdditionalAnnotations(SuppressDiagnosticsAnnotation.Create()); var updatedDocument = newDocument.WithSyntaxRoot(updatedRoot); return updatedDocument.Project.Solution; } /// <summary> /// However, for application purposes, we end up returning a single operation /// that will then apply all our sub actions in order, stopping the moment /// one of them fails. /// </summary> protected override async Task<IEnumerable<CodeActionOperation>> ComputeOperationsAsync( CancellationToken cancellationToken) { var updatedDocument = await GetUpdatedDocumentAsync(cancellationToken).ConfigureAwait(false); var oldText = await OriginalDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); var newText = await updatedDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); return [new InstallPackageAndAddImportOperation( OriginalDocument.Id, oldText, newText, _installOperation)]; } private sealed class InstallPackageAndAddImportOperation( DocumentId changedDocumentId, SourceText oldText, SourceText newText, InstallPackageDirectlyCodeActionOperation item2) : CodeActionOperation { private readonly DocumentId _changedDocumentId = changedDocumentId; private readonly SourceText _oldText = oldText; private readonly SourceText _newText = newText; private readonly InstallPackageDirectlyCodeActionOperation _installPackageOperation = item2; internal override bool ApplyDuringTests => _installPackageOperation.ApplyDuringTests; public override string Title => _installPackageOperation.Title; internal override async Task<bool> TryApplyAsync( Workspace workspace, Solution originalSolution, IProgress<CodeAnalysisProgress> progressTracker, CancellationToken cancellationToken) { var newSolution = workspace.CurrentSolution.WithDocumentText( _changedDocumentId, _newText); // First make the changes to add the import to the document. if (workspace.TryApplyChanges(newSolution, progressTracker)) { if (await _installPackageOperation.TryApplyAsync(workspace, originalSolution, progressTracker, cancellationToken).ConfigureAwait(true)) { return true; } // Installing the nuget package failed. Roll back the workspace. var rolledBackSolution = workspace.CurrentSolution.WithDocumentText( _changedDocumentId, _oldText); workspace.TryApplyChanges(rolledBackSolution, progressTracker); } return false; } } }