SuggestedAction.cs 12.0 KB
Newer Older
1 2 3 4
// 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;
using System.Collections.Generic;
5
using System.Collections.Immutable;
6 7 8 9
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Editor.Host;
10
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
11
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
12 13
using Microsoft.CodeAnalysis.Extensions;
using Microsoft.CodeAnalysis.Text;
14
using Microsoft.VisualStudio.Imaging.Interop;
15 16 17 18 19 20 21 22 23
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions
{
    /// <summary>
    /// Base class for all Roslyn light bulb menu items.
    /// </summary>
24
    internal partial class SuggestedAction : ForegroundThreadAffinitizedObject, ISuggestedAction, IEquatable<ISuggestedAction>
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
    {
        protected readonly Workspace Workspace;
        protected readonly ITextBuffer SubjectBuffer;
        protected readonly ICodeActionEditHandlerService EditHandler;

        protected readonly object Provider;
        protected readonly CodeAction CodeAction;

        protected SuggestedAction(
            Workspace workspace,
            ITextBuffer subjectBuffer,
            ICodeActionEditHandlerService editHandler,
            CodeAction codeAction,
            object provider)
        {
            Contract.ThrowIfTrue(provider == null);

            this.Workspace = workspace;
            this.SubjectBuffer = subjectBuffer;
            this.CodeAction = codeAction;
            this.EditHandler = editHandler;
            this.Provider = provider;
        }

        public bool TryGetTelemetryId(out Guid telemetryId)
        {
            // TODO: this is temporary. Diagnostic team needs to figure out how to provide unique id per a fix.
            // for now, we will use type of CodeAction, but there are some predefined code actions that are used by multiple fixes
            // and this will not distinguish those

            // AssemblyQualifiedName will change across version numbers, FullName won't
            var type = CodeAction.GetType();
            type = type.IsConstructedGenericType ? type.GetGenericTypeDefinition() : type;

            telemetryId = new Guid(type.FullName.GetHashCode(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
            return true;
        }

63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
        // NOTE: We want to avoid computing the operations on the UI thread. So we use Task.Run() to do this work on the background thread.
        protected Task<ImmutableArray<CodeActionOperation>> GetOperationsAsync(CancellationToken cancellationToken)
        {
            return Task.Run(
                async () => await CodeAction.GetOperationsAsync(cancellationToken).ConfigureAwait(false), cancellationToken);
        }

        protected Task<IEnumerable<CodeActionOperation>> GetOperationsAsync(CodeActionWithOptions actionWithOptions, object options, CancellationToken cancellationToken)
        {
            return Task.Run(
                async () => await actionWithOptions.GetOperationsAsync(options, cancellationToken).ConfigureAwait(false), cancellationToken);
        }

        protected Task<ImmutableArray<CodeActionOperation>> GetPreviewOperationsAsync(CancellationToken cancellationToken)
        {
            return Task.Run(
                async () => await CodeAction.GetPreviewOperationsAsync(cancellationToken).ConfigureAwait(false), cancellationToken);
        }

82 83 84 85 86 87 88 89 90 91 92
        public virtual void Invoke(CancellationToken cancellationToken)
        {
            var snapshot = this.SubjectBuffer.CurrentSnapshot;

            using (new CaretPositionRestorer(this.SubjectBuffer, this.EditHandler.AssociatedViewService))
            {
                var extensionManager = this.Workspace.Services.GetService<IExtensionManager>();
                extensionManager.PerformAction(Provider, () =>
                {
                    IEnumerable<CodeActionOperation> operations = null;

93 94 95
                    // NOTE: As mentoned above, we want to avoid computing the operations on the UI thread.
                    // However, for CodeActionWithOptions, GetOptions() might involve spinning up a dialog
                    // to compute the options and must be done on the UI thread.
96 97 98 99 100 101
                    var actionWithOptions = this.CodeAction as CodeActionWithOptions;
                    if (actionWithOptions != null)
                    {
                        var options = actionWithOptions.GetOptions(cancellationToken);
                        if (options != null)
                        {
102
                            operations = GetOperationsAsync(actionWithOptions, options, cancellationToken).WaitAndGetResult(cancellationToken);
103 104 105 106
                        }
                    }
                    else
                    {
107
                        operations = GetOperationsAsync(cancellationToken).WaitAndGetResult(cancellationToken);
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
                    }

                    if (operations != null)
                    {
                        var document = this.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
                        EditHandler.Apply(Workspace, document, operations, CodeAction.Title, cancellationToken);
                    }
                });
            }
        }

        public string DisplayText
        {
            get
            {
                // Underscores will become an accelerator in the VS smart tag.  So we double all
                // underscores so they actually get represented as an underscore in the UI.
                var extensionManager = this.Workspace.Services.GetService<IExtensionManager>();
S
Shyam N 已提交
126
                var text = extensionManager.PerformFunction(Provider, () => CodeAction.Title, defaultValue: string.Empty);
127 128 129 130
                return text.Replace("_", "__");
            }
        }

131
        protected async Task<SolutionPreviewResult> GetPreviewResultAsync(CancellationToken cancellationToken)
132
        {
133
            cancellationToken.ThrowIfCancellationRequested();
134

135 136 137 138 139 140 141 142
            // We will always invoke this from the UI thread.
            AssertIsForeground();

            // We use ConfigureAwait(true) to stay on the UI thread.
            var operations = await GetPreviewOperationsAsync(cancellationToken).ConfigureAwait(true);

            return EditHandler.GetPreviews(Workspace, operations, cancellationToken);
        }
143

144 145 146 147 148 149 150 151 152 153 154 155 156
        public virtual bool HasPreview
        {
            get
            {
                // 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.GetPreivewOperationsAsync() 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().

                return true;
S
Shyam N 已提交
157
            }
158 159
        }

160
        public virtual async Task<object> GetPreviewAsync(CancellationToken cancellationToken)
161
        {
162 163 164 165 166
            cancellationToken.ThrowIfCancellationRequested();

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

167 168 169
            var preferredDocumentId = Workspace.GetDocumentIdInCurrentContext(SubjectBuffer.AsTextContainer());
            var preferredProjectid = preferredDocumentId == null ? null : preferredDocumentId.ProjectId;

170
            var extensionManager = this.Workspace.Services.GetService<IExtensionManager>();
171
            var previewContent = await extensionManager.PerformFunctionAsync(Provider, async () =>
172
            {
173 174 175
                // 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);
176 177 178 179 180 181
                if (previewResult == null)
                {
                    return null;
                }
                else
                {
182 183 184
                    // TakeNextPreviewAsync() needs to run on UI thread.
                    AssertIsForeground();
                    return await previewResult.TakeNextPreviewAsync(preferredDocumentId, preferredProjectid, cancellationToken).ConfigureAwait(true);
185 186
                }

187
                // GetPreviewPane() below needs to run on UI thread. We use ConfigureAwait(true) to stay on the UI thread.
S
Shyam N 已提交
188
            }, defaultValue: null).ConfigureAwait(true);
189 190 191 192

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

196 197 198 199
            cancellationToken.ThrowIfCancellationRequested();

            // GetPreviewPane() needs to run on the UI thread.
            AssertIsForeground();
200 201 202 203 204 205

            string language;
            string projectType;
            Workspace.GetLanguageAndProjectType(preferredProjectid, out language, out projectType);

            return previewPaneService.GetPreviewPane(GetDiagnostic(), language, projectType, previewContent);
206 207 208 209 210 211 212 213 214 215 216 217 218
        }

        protected virtual Diagnostic GetDiagnostic()
        {
            return null;
        }

        #region not supported
        void IDisposable.Dispose()
        {
            // do nothing
        }

219
        public virtual bool HasActionSets
220 221 222
        {
            get
            {
223
                return false;
224
            }
S
Shyam N 已提交
225
        }
226 227 228 229

        public virtual Task<IEnumerable<SuggestedActionSet>> GetActionSetsAsync(CancellationToken cancellationToken)
        {
            return SpecializedTasks.Default<IEnumerable<SuggestedActionSet>>();
230 231 232 233 234 235 236 237 238 239 240
        }

        string ISuggestedAction.IconAutomationText
        {
            get
            {
                // same as display text
                return DisplayText;
            }
        }

241
        ImageMoniker ISuggestedAction.IconMoniker
242 243 244 245
        {
            get
            {
                // no icon support
246
                return default(ImageMoniker);
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
            }
        }

        string ISuggestedAction.InputGestureText
        {
            get
            {
                // no shortcut support
                return null;
            }
        }
        #endregion

        #region IEquatable<ISuggestedAction>
        public bool Equals(ISuggestedAction other)
        {
            return Equals(other as SuggestedAction);
        }

        public override bool Equals(object obj)
        {
            return Equals(obj as SuggestedAction);
        }

        public bool Equals(SuggestedAction otherSuggestedAction)
        {
            if (otherSuggestedAction == null)
            {
                return false;
            }

            if (ReferenceEquals(this, otherSuggestedAction))
            {
                return true;
            }

            if (!ReferenceEquals(Provider, otherSuggestedAction.Provider))
            {
                return false;
            }

            var otherCodeAction = otherSuggestedAction.CodeAction;
            if (CodeAction.EquivalenceKey == null || otherCodeAction.EquivalenceKey == null)
            {
                return false;
            }

            return CodeAction.EquivalenceKey == otherCodeAction.EquivalenceKey;
        }

        public override int GetHashCode()
        {
            if (CodeAction.EquivalenceKey == null)
            {
                return base.GetHashCode();
            }

            return Hash.Combine(Provider.GetHashCode(), CodeAction.EquivalenceKey.GetHashCode());
        }
        #endregion
    }
}