SuggestedAction.cs 9.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// 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;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Shared.Options;
using Microsoft.CodeAnalysis.Extensions;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Text;
using Roslyn.Utilities;
17
using Microsoft.CodeAnalysis.Notification;
18 19 20 21 22 23 24 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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 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 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255

namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions
{
    /// <summary>
    /// Base class for all Roslyn light bulb menu items.
    /// </summary>
    internal partial class SuggestedAction : ISuggestedAction, IEquatable<ISuggestedAction>
    {
        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;
        }

        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;

                    // NOTE: We want to avoid computing the operations on the UI thread, so we will kick off a task to GetOperations.
                    // However, for CodeActionWithOptions, GetOptions might involve spinning up a dialog to compute the options and must be done on the UI thread.
                    // Hence we need the below if-else statement instead of just invoking CodeAction.GetOperationsAsync()
                    var actionWithOptions = this.CodeAction as CodeActionWithOptions;
                    if (actionWithOptions != null)
                    {
                        var options = actionWithOptions.GetOptions(cancellationToken);
                        if (options != null)
                        {
                            operations = Task.Run(
                                async () => await actionWithOptions.GetOperationsAsync(options, cancellationToken).ConfigureAwait(false), cancellationToken).WaitAndGetResult(cancellationToken);
                        }
                    }
                    else
                    {
                        operations = Task.Run(
                            async () => await this.CodeAction.GetOperationsAsync(cancellationToken).ConfigureAwait(false), cancellationToken).WaitAndGetResult(cancellationToken);
                    }

                    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>();
                var text = extensionManager.PerformFunction(Provider, () => CodeAction.Title, string.Empty);
                return text.Replace("_", "__");
            }
        }

        private IEnumerable<CodeActionOperation> _operations;
        protected SolutionPreviewResult GetPreviewResult(CancellationToken cancellationToken)
        {
            if (_operations == null)
            {
                _operations = Task.Run(
                    async () => await this.CodeAction.GetPreviewOperationsAsync(cancellationToken).ConfigureAwait(false), cancellationToken).WaitAndGetResult(cancellationToken);
            }

            return EditHandler.GetPreviews(Workspace, _operations, cancellationToken);
        }

        public virtual object GetPreview(CancellationToken cancellationToken)
        {
            var extensionManager = this.Workspace.Services.GetService<IExtensionManager>();
            var previewContent = extensionManager.PerformFunction(Provider, () =>
            {
                var previewResult = GetPreviewResult(cancellationToken);
                if (previewResult == null)
                {
                    return null;
                }
                else
                {
                    var preferredDocumentId = Workspace.GetDocumentIdInCurrentContext(SubjectBuffer.AsTextContainer());
                    var preferredProjectid = preferredDocumentId == null ? null : preferredDocumentId.ProjectId;

                    return previewResult.TakeNextPreview(preferredDocumentId, preferredProjectid);
                }
            });

            var optionService = Workspace.Services.GetService<IOptionService>();
            if (optionService == null || !optionService.GetOption(InternalFeatureOnOffOptions.EnhancedPreviewPane))
            {
                return previewContent;
            }

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

            return previewPaneService.GetPreviewPane(GetDiagnostic(), previewContent);
        }

        protected virtual Diagnostic GetDiagnostic()
        {
            return null;
        }

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

        public virtual IEnumerable<SuggestedActionSet> ActionSets
        {
            get
            {
                return null;
            }
        }

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

        ImageSource ISuggestedAction.IconSource
        {
            get
            {
                // no icon support
                return null;
            }
        }

        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
    }
}