SuggestedActionWithNestedFlavors.cs 7.3 KB
Newer Older
C
CyrusNajmabadi 已提交
1 2 3 4 5 6 7
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
C
CyrusNajmabadi 已提交
8
using Microsoft.CodeAnalysis.Diagnostics;
C
CyrusNajmabadi 已提交
9
using Microsoft.CodeAnalysis.Editor.Host;
10
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
C
CyrusNajmabadi 已提交
11
using Microsoft.CodeAnalysis.Extensions;
T
Tomas Matousek 已提交
12
using Microsoft.CodeAnalysis.PooledObjects;
13
using Microsoft.CodeAnalysis.Text;
C
CyrusNajmabadi 已提交
14 15 16 17 18 19
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions
{
20 21 22 23 24 25 26 27
    /// <summary>
    /// Base type for all SuggestedActions that have 'flavors'.  'Flavors' are child actions that
    /// are presented as simple links, not as menu-items, in the light-bulb.  Examples of 'flavors'
    /// include 'preview changes' (for refactorings and fixes) and 'fix all in document, project, solution'
    /// (for fixes).
    /// 
    /// Because all derivations support 'preview changes', we bake that logic into this base type.
    /// </summary>
28
    internal abstract partial class SuggestedActionWithNestedFlavors : SuggestedAction, ISuggestedActionWithFlavors
C
CyrusNajmabadi 已提交
29
    {
C
CyrusNajmabadi 已提交
30
        private readonly SuggestedActionSet _additionalFlavors;
31
        private ImmutableArray<SuggestedActionSet> _nestedFlavors;
C
CyrusNajmabadi 已提交
32

33
        public SuggestedActionWithNestedFlavors(
34 35 36 37 38
            SuggestedActionsSourceProvider sourceProvider,
            Workspace workspace, ITextBuffer subjectBuffer,
            object provider, CodeAction codeAction, 
            SuggestedActionSet additionalFlavors = null) 
            : base(sourceProvider, workspace, subjectBuffer, 
39
                   provider, codeAction)
C
CyrusNajmabadi 已提交
40
        {
C
CyrusNajmabadi 已提交
41
            _additionalFlavors = additionalFlavors;
C
CyrusNajmabadi 已提交
42 43
        }

44 45 46
        /// <summary>
        /// HasActionSets is always true because we always know we provide 'preview changes'.
        /// </summary>
47
        public sealed override bool HasActionSets => true;
C
CyrusNajmabadi 已提交
48 49 50 51 52 53 54 55

        public async sealed override Task<IEnumerable<SuggestedActionSet>> GetActionSetsAsync(CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            // Light bulb will always invoke this property on the UI thread.
            AssertIsForeground();

56
            if (_nestedFlavors.IsDefault)
C
CyrusNajmabadi 已提交
57 58 59
            {
                var extensionManager = this.Workspace.Services.GetService<IExtensionManager>();

C
CyrusNajmabadi 已提交
60
                // We use ConfigureAwait(true) to stay on the UI thread.
61
                _nestedFlavors = await extensionManager.PerformFunctionAsync(
C
CyrusNajmabadi 已提交
62
                    Provider, () => CreateAllFlavors(cancellationToken),
C
CyrusNajmabadi 已提交
63
                    defaultValue: ImmutableArray<SuggestedActionSet>.Empty).ConfigureAwait(true);
C
CyrusNajmabadi 已提交
64 65
            }

66 67
            Contract.ThrowIfTrue(_nestedFlavors.IsDefault);
            return _nestedFlavors;
C
CyrusNajmabadi 已提交
68 69
        }

C
CyrusNajmabadi 已提交
70
        private async Task<ImmutableArray<SuggestedActionSet>> CreateAllFlavors(CancellationToken cancellationToken)
C
CyrusNajmabadi 已提交
71 72 73 74 75 76 77 78 79 80
        {
            var builder = ArrayBuilder<SuggestedActionSet>.GetInstance();

            // We use ConfigureAwait(true) to stay on the UI thread.
            var previewChangesSuggestedActionSet = await GetPreviewChangesFlavor(cancellationToken).ConfigureAwait(true);
            if (previewChangesSuggestedActionSet != null)
            {
                builder.Add(previewChangesSuggestedActionSet);
            }

C
CyrusNajmabadi 已提交
81
            if (_additionalFlavors != null)
C
CyrusNajmabadi 已提交
82
            {
C
CyrusNajmabadi 已提交
83
                builder.Add(_additionalFlavors);
C
CyrusNajmabadi 已提交
84 85 86 87 88
            }

            return builder.ToImmutableAndFree();
        }

C
CyrusNajmabadi 已提交
89
        private async Task<SuggestedActionSet> GetPreviewChangesFlavor(CancellationToken cancellationToken)
C
CyrusNajmabadi 已提交
90
        {
C
CyrusNajmabadi 已提交
91
            // We use ConfigureAwait(true) to stay on the UI thread.
C
CyrusNajmabadi 已提交
92 93 94
            var previewChangesAction = await PreviewChangesSuggestedAction.CreateAsync(
                this, cancellationToken).ConfigureAwait(true);
            if (previewChangesAction == null)
C
CyrusNajmabadi 已提交
95 96 97 98
            {
                return null;
            }

C
CyrusNajmabadi 已提交
99
            return new SuggestedActionSet(ImmutableArray.Create(previewChangesAction));
C
CyrusNajmabadi 已提交
100
        }
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150

        // HasPreview is called synchronously on the UI thread. In order to avoid blocking the UI thread,
        // we need to provide a 'quick' answer here as opposed to the 'right' answer. Providing the 'right'
        // answer is expensive (because we will need to call CodeAction.GetPreviewOperationsAsync() for this
        // and this will involve computing the changed solution for the ApplyChangesOperation for the fix /
        // refactoring). So we always return 'true' here (so that platform will call GetActionSetsAsync()
        // below). Platform guarantees that nothing bad will happen if we return 'true' here and later return
        // 'null' / empty collection from within GetPreviewAsync().
        public override bool HasPreview => true;

        public override async Task<object> GetPreviewAsync(CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            // Light bulb will always invoke this function on the UI thread.
            AssertIsForeground();

            var previewPaneService = Workspace.Services.GetService<IPreviewPaneService>();
            if (previewPaneService == null)
            {
                return null;
            }

            // after this point, this method should only return at GetPreviewPane. otherwise, DifferenceViewer will leak
            // since there is no one to close the viewer
            var preferredDocumentId = Workspace.GetDocumentIdInCurrentContext(SubjectBuffer.AsTextContainer());
            var preferredProjectId = preferredDocumentId?.ProjectId;

            var extensionManager = this.Workspace.Services.GetService<IExtensionManager>();
            var previewContents = await extensionManager.PerformFunctionAsync(Provider, async () =>
            {
                // We need to stay on UI thread after GetPreviewResultAsync() so that TakeNextPreviewAsync()
                // below can execute on UI thread. We use ConfigureAwait(true) to stay on the UI thread.
                var previewResult = await GetPreviewResultAsync(cancellationToken).ConfigureAwait(true);
                if (previewResult == null)
                {
                    return null;
                }
                else
                {
                    // TakeNextPreviewAsync() needs to run on UI thread.
                    AssertIsForeground();
                    return await previewResult.GetPreviewsAsync(preferredDocumentId, preferredProjectId, cancellationToken).ConfigureAwait(true);
                }

                // GetPreviewPane() below needs to run on UI thread. We use ConfigureAwait(true) to stay on the UI thread.
            }, defaultValue: null).ConfigureAwait(true);

            // GetPreviewPane() needs to run on the UI thread.
            AssertIsForeground();
C
CyrusNajmabadi 已提交
151
            Workspace.GetLanguageAndProjectType(preferredProjectId, out var language, out var projectType);
152 153 154

            return previewPaneService.GetPreviewPane(GetDiagnostic(), language, projectType, previewContents);
        }
C
CyrusNajmabadi 已提交
155 156

        protected virtual DiagnosticData GetDiagnostic() => null;
C
CyrusNajmabadi 已提交
157
    }
T
Tomas Matousek 已提交
158
}