diff --git a/src/Analyzers/Core/CodeFixes/NamingStyle/NamingStyleCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/NamingStyle/NamingStyleCodeFixProvider.cs index e42d5924318ee..95e261d279c36 100644 --- a/src/Analyzers/Core/CodeFixes/NamingStyle/NamingStyleCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/NamingStyle/NamingStyleCodeFixProvider.cs @@ -121,6 +121,13 @@ private class FixNameCodeAction : CodeAction private readonly Func> _createChangedSolutionAsync; private readonly string _equivalenceKey; + /// + /// This code action does produce non-text-edit operations (like notifying 3rd parties about a rename). But + /// it doesn't require this. As such, we can allow it to run in hosts that only allow document edits. Those + /// hosts will simply ignore the operations they don't understand. + /// + public override ImmutableArray Tags => ImmutableArray.Empty; + public FixNameCodeAction( #if !CODE_STYLE Solution startingSolution, @@ -159,7 +166,7 @@ protected override async Task> ComputeOperation { codeAction, factory.CreateSymbolRenamedOperation(_symbol, _newName, _startingSolution, newSolution) - }.AsEnumerable(); + }; #endif } diff --git a/src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionResolveHandler.cs b/src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionResolveHandler.cs deleted file mode 100644 index b58aa7a27fc03..0000000000000 --- a/src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionResolveHandler.cs +++ /dev/null @@ -1,219 +0,0 @@ -// 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.Collections.Generic; -using System.Composition; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CodeRefactorings; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.CodeActions; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.LanguageServer.Protocol; -using Newtonsoft.Json.Linq; -using Roslyn.Utilities; -using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; - -namespace Microsoft.CodeAnalysis.LanguageServer.Handler -{ - /// - /// Resolves a code action by filling out its Edit and/or Command property. - /// The handler is triggered only when a user hovers over a code action. This - /// system allows the basic code action data to be computed quickly, and the - /// complex data, such as edits and commands, to be computed only when necessary - /// (i.e. when hovering/previewing a code action). - /// - /// TODO - This must be moved to the MS.CA.LanguageServer.Protocol project once the - /// EditorFeatures references in are removed. - /// See https://github.com/dotnet/roslyn/issues/55142 - /// - [ExportCSharpVisualBasicStatelessLspService(typeof(CodeActionResolveHandler)), Shared] - [Method(LSP.Methods.CodeActionResolveName)] - internal class CodeActionResolveHandler : ILspServiceDocumentRequestHandler - { - private readonly ICodeFixService _codeFixService; - private readonly ICodeRefactoringService _codeRefactoringService; - private readonly IGlobalOptionService _globalOptions; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CodeActionResolveHandler( - ICodeFixService codeFixService, - ICodeRefactoringService codeRefactoringService, - IGlobalOptionService globalOptions) - { - _codeFixService = codeFixService; - _codeRefactoringService = codeRefactoringService; - _globalOptions = globalOptions; - } - - public bool MutatesSolutionState => false; - public bool RequiresLSPSolution => true; - - public TextDocumentIdentifier GetTextDocumentIdentifier(LSP.CodeAction request) - => ((JToken)request.Data!).ToObject()!.TextDocument; - - public async Task HandleRequestAsync(LSP.CodeAction codeAction, RequestContext context, CancellationToken cancellationToken) - { - var document = context.GetRequiredDocument(); - - var data = ((JToken)codeAction.Data!).ToObject(); - Assumes.Present(data); - - var options = _globalOptions.GetCodeActionOptionsProvider(); - - var codeActionsCache = context.GetRequiredLspService(); - var codeActions = await CodeActionHelpers.GetCodeActionsAsync( - codeActionsCache, - document, - data.Range, - options, - _codeFixService, - _codeRefactoringService, - cancellationToken).ConfigureAwait(false); - - var codeActionToResolve = CodeActionHelpers.GetCodeActionToResolve( - data.UniqueIdentifier, codeActions); - Contract.ThrowIfNull(codeActionToResolve); - - var operations = await codeActionToResolve.GetOperationsAsync(cancellationToken).ConfigureAwait(false); - if (operations.IsEmpty) - { - return codeAction; - } - - // If we have all non-ApplyChangesOperations, set up to run as command on the server - // instead of using WorkspaceEdits. - if (operations.All(operation => operation is not ApplyChangesOperation)) - { - codeAction.Command = SetCommand(codeAction.Title, data); - return codeAction; - } - - // TO-DO: We currently must execute code actions which add new documents on the server as commands, - // since there is no LSP support for adding documents yet. In the future, we should move these actions - // to execute on the client. - // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1147293/ - - // Add workspace edits - var applyChangesOperations = operations.OfType(); - if (applyChangesOperations.Any()) - { - var solution = document.Project.Solution; - var textDiffService = solution.Services.GetService(); - - using var _ = ArrayBuilder.GetInstance(out var textDocumentEdits); - foreach (var applyChangesOperation in applyChangesOperations) - { - var changes = applyChangesOperation.ChangedSolution.GetChanges(solution); - var projectChanges = changes.GetProjectChanges(); - - // TO-DO: If the change involves adding or removing a document, execute via command instead of WorkspaceEdit - // until adding/removing documents is supported in LSP: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1147293/ - // After support is added, remove the below if-statement and add code to support adding/removing documents. - var addedDocuments = projectChanges.SelectMany( - pc => pc.GetAddedDocuments().Concat(pc.GetAddedAdditionalDocuments().Concat(pc.GetAddedAnalyzerConfigDocuments()))); - var removedDocuments = projectChanges.SelectMany( - pc => pc.GetRemovedDocuments().Concat(pc.GetRemovedAdditionalDocuments().Concat(pc.GetRemovedAnalyzerConfigDocuments()))); - if (addedDocuments.Any() || removedDocuments.Any()) - { - codeAction.Command = SetCommand(codeAction.Title, data); - return codeAction; - } - - // TO-DO: If the change involves adding or removing a project reference, execute via command instead of - // WorkspaceEdit until adding/removing project references is supported in LSP: - // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1166040 - var projectReferences = projectChanges.SelectMany( - pc => pc.GetAddedProjectReferences().Concat(pc.GetRemovedProjectReferences())); - if (projectReferences.Any()) - { - codeAction.Command = SetCommand(codeAction.Title, data); - return codeAction; - } - - var changedDocuments = projectChanges.SelectMany(pc => pc.GetChangedDocuments()); - var changedAnalyzerConfigDocuments = projectChanges.SelectMany(pc => pc.GetChangedAnalyzerConfigDocuments()); - var changedAdditionalDocuments = projectChanges.SelectMany(pc => pc.GetChangedAdditionalDocuments()); - - // Changed documents - await AddTextDocumentEditsAsync( - textDocumentEdits, changedDocuments, - applyChangesOperation.ChangedSolution.GetDocument, solution.GetDocument, textDiffService, - cancellationToken).ConfigureAwait(false); - - // Changed analyzer config documents - await AddTextDocumentEditsAsync( - textDocumentEdits, changedAnalyzerConfigDocuments, - applyChangesOperation.ChangedSolution.GetAnalyzerConfigDocument, solution.GetAnalyzerConfigDocument, - textDiffService: null, cancellationToken).ConfigureAwait(false); - - // Changed additional documents - await AddTextDocumentEditsAsync( - textDocumentEdits, changedAdditionalDocuments, - applyChangesOperation.ChangedSolution.GetAdditionalDocument, solution.GetAdditionalDocument, - textDiffService: null, cancellationToken).ConfigureAwait(false); - } - - codeAction.Edit = new LSP.WorkspaceEdit { DocumentChanges = textDocumentEdits.ToArray() }; - } - - return codeAction; - - // Local functions - static LSP.Command SetCommand(string title, CodeActionResolveData data) => new LSP.Command - { - CommandIdentifier = CodeActionsHandler.RunCodeActionCommandName, - Title = title, - Arguments = new object[] { data } - }; - - static async Task AddTextDocumentEditsAsync( - ArrayBuilder textDocumentEdits, - IEnumerable changedDocuments, - Func getNewDocumentFunc, - Func getOldDocumentFunc, - IDocumentTextDifferencingService? textDiffService, - CancellationToken cancellationToken) - where T : TextDocument - { - foreach (var docId in changedDocuments) - { - var newTextDoc = getNewDocumentFunc(docId); - var oldTextDoc = getOldDocumentFunc(docId); - - Contract.ThrowIfNull(oldTextDoc); - Contract.ThrowIfNull(newTextDoc); - - var oldText = await oldTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); - - IEnumerable textChanges; - - // Normal documents have a unique service for calculating minimal text edits. If we used the standard 'GetTextChanges' - // method instead, we would get a change that spans the entire document, which we ideally want to avoid. - if (newTextDoc is Document newDoc && oldTextDoc is Document oldDoc) - { - Contract.ThrowIfNull(textDiffService); - textChanges = await textDiffService.GetTextChangesAsync(oldDoc, newDoc, cancellationToken).ConfigureAwait(false); - } - else - { - var newText = await newTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); - textChanges = newText.GetTextChanges(oldText); - } - - var edits = textChanges.Select(tc => ProtocolConversions.TextChangeToTextEdit(tc, oldText)).ToArray(); - var documentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = newTextDoc.GetURI() }; - textDocumentEdits.Add(new TextDocumentEdit { TextDocument = documentIdentifier, Edits = edits.ToArray() }); - } - } - } - } -} diff --git a/src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/RunCodeActionHandler.cs b/src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/RunCodeActionHandler.cs deleted file mode 100644 index 9fb862ff58220..0000000000000 --- a/src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/RunCodeActionHandler.cs +++ /dev/null @@ -1,103 +0,0 @@ -// 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.Composition; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CodeRefactorings; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.CodeActions; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Commands; -using Microsoft.CodeAnalysis.Options; -using Newtonsoft.Json.Linq; -using Roslyn.Utilities; -using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; - -namespace Microsoft.CodeAnalysis.LanguageServer.Handler -{ - /// - /// Runs a code action as a command on the server. - /// This is done when a code action cannot be applied as a WorkspaceEdit on the LSP client. - /// For example, all non-ApplyChangesOperations must be applied as a command. - /// TO-DO: Currently, any ApplyChangesOperation that adds or removes a document must also be - /// applied as a command due to an LSP bug (see https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1147293/). - /// Commands must be applied from the UI thread in VS. - /// - /// TODO - This must be moved to the MS.CA.LanguageServer.Protocol project once the - /// UI thread dependencies are resolved and references are removed. - /// See https://github.com/dotnet/roslyn/issues/55142 - /// - [ExportCSharpVisualBasicStatelessLspService(typeof(RunCodeActionHandler)), Shared] - [Command(CodeActionsHandler.RunCodeActionCommandName)] - internal class RunCodeActionHandler : AbstractExecuteWorkspaceCommandHandler - { - private readonly ICodeFixService _codeFixService; - private readonly ICodeRefactoringService _codeRefactoringService; - private readonly IGlobalOptionService _globalOptions; - private readonly IThreadingContext _threadingContext; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public RunCodeActionHandler( - ICodeFixService codeFixService, - ICodeRefactoringService codeRefactoringService, - IGlobalOptionService globalOptions, - IThreadingContext threadingContext) - { - _codeFixService = codeFixService; - _codeRefactoringService = codeRefactoringService; - _globalOptions = globalOptions; - _threadingContext = threadingContext; - } - - public override string Command => CodeActionsHandler.RunCodeActionCommandName; - - public override bool MutatesSolutionState => true; - public override bool RequiresLSPSolution => true; - - public override LSP.TextDocumentIdentifier GetTextDocumentIdentifier(LSP.ExecuteCommandParams request) - { - var runRequest = ((JToken)request.Arguments.Single()).ToObject(); - Assumes.Present(runRequest); - - return runRequest.TextDocument; - } - - public override async Task HandleRequestAsync(LSP.ExecuteCommandParams request, RequestContext context, CancellationToken cancellationToken) - { - var document = context.Document; - Contract.ThrowIfNull(document); - - var runRequest = ((JToken)request.Arguments.Single()).ToObject(); - Assumes.Present(runRequest); - - var options = _globalOptions.GetCodeActionOptionsProvider(); - - var codeActionsCache = context.GetRequiredLspService(); - var codeActions = await CodeActionHelpers.GetCodeActionsAsync( - codeActionsCache, document, runRequest.Range, options, _codeFixService, _codeRefactoringService, cancellationToken).ConfigureAwait(false); - - var actionToRun = CodeActionHelpers.GetCodeActionToResolve(runRequest.UniqueIdentifier, codeActions); - Contract.ThrowIfNull(actionToRun); - - var operations = await actionToRun.GetOperationsAsync(cancellationToken).ConfigureAwait(false); - - // TODO - This UI thread dependency should be removed. - // https://github.com/dotnet/roslyn/projects/45#card-20619668 - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - - foreach (var operation in operations) - { - operation.Apply(document.Project.Solution.Workspace, cancellationToken); - } - - return true; - } - } -} diff --git a/src/EditorFeatures/Test2/Diagnostics/AbstractCrossLanguageUserDiagnosticTest.vb b/src/EditorFeatures/Test2/Diagnostics/AbstractCrossLanguageUserDiagnosticTest.vb index beda37d80fb1d..62aab7c8b8d39 100644 --- a/src/EditorFeatures/Test2/Diagnostics/AbstractCrossLanguageUserDiagnosticTest.vb +++ b/src/EditorFeatures/Test2/Diagnostics/AbstractCrossLanguageUserDiagnosticTest.vb @@ -77,7 +77,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics Dim codeAction = codeActions(codeActionIndex) If Not glyphTags.IsDefault Then - Assert.Equal(glyphTags, codeAction.Tags) + AssertEx.SetEqual(glyphTags, codeAction.Tags) End If Dim oldSolution = workspace.CurrentSolution diff --git a/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb b/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb index 348eb6832d533..2f21f5d7e6895 100644 --- a/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb @@ -13,6 +13,7 @@ Imports Microsoft.CodeAnalysis.SolutionCrawler Imports Microsoft.CodeAnalysis.Tags Imports Microsoft.CodeAnalysis.VisualBasic.AddImport Imports Roslyn.Utilities +Imports Microsoft.CodeAnalysis.CodeActions Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics.AddImport @@ -266,7 +267,8 @@ namespace CSAssembly2 Await TestAsync( input, expected, codeActionIndex:=0, addedReference:="CSAssembly1", - glyphTags:=WellKnownTagArrays.CSharpProject, onAfterWorkspaceCreated:=AddressOf WaitForSymbolTreeInfoCache) + glyphTags:=WellKnownTagArrays.CSharpProject.Add(CodeAction.RequiresNonDocumentChange), + onAfterWorkspaceCreated:=AddressOf WaitForSymbolTreeInfoCache) End Function @@ -353,7 +355,8 @@ namespace CSAssembly2 Await TestAsync( input, expected, codeActionIndex:=0, addedReference:="CSAssembly1", - glyphTags:=WellKnownTagArrays.CSharpProject, onAfterWorkspaceCreated:=AddressOf WaitForSymbolTreeInfoCache) + glyphTags:=WellKnownTagArrays.CSharpProject.Add(CodeAction.RequiresNonDocumentChange), + onAfterWorkspaceCreated:=AddressOf WaitForSymbolTreeInfoCache) End Function @@ -399,7 +402,7 @@ namespace CSAssembly2 .Value.Trim() Await TestAsync(input, expected, codeActionIndex:=0, addedReference:="NewName", - glyphTags:=WellKnownTagArrays.CSharpProject, + glyphTags:=WellKnownTagArrays.CSharpProject.Add(CodeAction.RequiresNonDocumentChange), onAfterWorkspaceCreated:= Async Function(workspace As TestWorkspace) Dim project = workspace.CurrentSolution.Projects.Single(Function(p) p.AssemblyName = "CSAssembly1") @@ -445,7 +448,8 @@ End Namespace Await TestAsync( input, expected, codeActionIndex:=0, addedReference:="VBAssembly1", - glyphTags:=WellKnownTagArrays.VisualBasicProject, onAfterWorkspaceCreated:=AddressOf WaitForSymbolTreeInfoCache) + glyphTags:=WellKnownTagArrays.VisualBasicProject.Add(CodeAction.RequiresNonDocumentChange), + onAfterWorkspaceCreated:=AddressOf WaitForSymbolTreeInfoCache) End Function Private Async Function WaitForSymbolTreeInfoCache(workspace As TestWorkspace) As Task @@ -522,7 +526,8 @@ namespace A - Await TestAsync(input, addedReference:="CSAssembly2", glyphTags:=WellKnownTagArrays.CSharpProject, + Await TestAsync(input, addedReference:="CSAssembly2", + glyphTags:=WellKnownTagArrays.CSharpProject.Add(CodeAction.RequiresNonDocumentChange), onAfterWorkspaceCreated:=AddressOf WaitForSymbolTreeInfoCache) End Function diff --git a/src/Features/Core/Portable/AddImport/CodeActions/AddImportCodeAction.cs b/src/Features/Core/Portable/AddImport/CodeActions/AddImportCodeAction.cs index b7d1135d61447..05234f52caad7 100644 --- a/src/Features/Core/Portable/AddImport/CodeActions/AddImportCodeAction.cs +++ b/src/Features/Core/Portable/AddImport/CodeActions/AddImportCodeAction.cs @@ -43,13 +43,14 @@ private abstract class AddImportCodeAction : CodeAction protected AddImportCodeAction( Document originalDocument, - AddImportFixData fixData) + AddImportFixData fixData, + ImmutableArray additionalTags) { OriginalDocument = originalDocument; FixData = fixData; Title = fixData.Title; - Tags = fixData.Tags.ToImmutableArrayOrEmpty(); + Tags = fixData.Tags.ToImmutableArrayOrEmpty().AddRange(additionalTags); Priority = fixData.Priority; _textChanges = fixData.TextChanges.ToImmutableArrayOrEmpty(); } diff --git a/src/Features/Core/Portable/AddImport/CodeActions/AssemblyReferenceCodeAction.cs b/src/Features/Core/Portable/AddImport/CodeActions/AssemblyReferenceCodeAction.cs index ca2b0f94a064c..a43865cbd7310 100644 --- a/src/Features/Core/Portable/AddImport/CodeActions/AssemblyReferenceCodeAction.cs +++ b/src/Features/Core/Portable/AddImport/CodeActions/AssemblyReferenceCodeAction.cs @@ -16,10 +16,14 @@ internal abstract partial class AbstractAddImportFeatureService + /// This code action only works by adding a reference. As such, it requires a non document change (and is + /// thus restricted in which hosts it can run). + /// public AssemblyReferenceCodeAction( Document originalDocument, AddImportFixData fixData) - : base(originalDocument, fixData) + : base(originalDocument, fixData, RequiresNonDocumentChangeTags) { Contract.ThrowIfFalse(fixData.Kind == AddImportFixKind.ReferenceAssemblySymbol); } diff --git a/src/Features/Core/Portable/AddImport/CodeActions/InstallPackageAndAddImportCodeAction.cs b/src/Features/Core/Portable/AddImport/CodeActions/InstallPackageAndAddImportCodeAction.cs index 4ba21c98e321a..4be859f664114 100644 --- a/src/Features/Core/Portable/AddImport/CodeActions/InstallPackageAndAddImportCodeAction.cs +++ b/src/Features/Core/Portable/AddImport/CodeActions/InstallPackageAndAddImportCodeAction.cs @@ -28,12 +28,16 @@ private class InstallPackageAndAddImportCodeAction : AddImportCodeAction /// private readonly InstallPackageDirectlyCodeActionOperation _installOperation; + /// + /// 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). + /// public InstallPackageAndAddImportCodeAction( Document originalDocument, AddImportFixData fixData, string title, InstallPackageDirectlyCodeActionOperation installOperation) - : base(originalDocument, fixData) + : base(originalDocument, fixData, RequiresNonDocumentChangeTags) { Contract.ThrowIfFalse(fixData.Kind == AddImportFixKind.PackageSymbol); Title = title; diff --git a/src/Features/Core/Portable/AddImport/CodeActions/MetadataSymbolReferenceCodeAction.cs b/src/Features/Core/Portable/AddImport/CodeActions/MetadataSymbolReferenceCodeAction.cs index 713dc90adaa54..2b7848a5d5f39 100644 --- a/src/Features/Core/Portable/AddImport/CodeActions/MetadataSymbolReferenceCodeAction.cs +++ b/src/Features/Core/Portable/AddImport/CodeActions/MetadataSymbolReferenceCodeAction.cs @@ -15,8 +15,12 @@ internal abstract partial class AbstractAddImportFeatureService + /// This code action only works by adding a reference to a metadata dll. As such, it requires a non + /// document change (and is thus restricted in which hosts it can run). + /// public MetadataSymbolReferenceCodeAction(Document originalDocument, AddImportFixData fixData) - : base(originalDocument, fixData) + : base(originalDocument, fixData, RequiresNonDocumentChangeTags) { Contract.ThrowIfFalse(fixData.Kind == AddImportFixKind.MetadataSymbol); } diff --git a/src/Features/Core/Portable/AddImport/CodeActions/ParentInstallPackageCodeAction.cs b/src/Features/Core/Portable/AddImport/CodeActions/ParentInstallPackageCodeAction.cs index 4884081c4131e..66380dc60d477 100644 --- a/src/Features/Core/Portable/AddImport/CodeActions/ParentInstallPackageCodeAction.cs +++ b/src/Features/Core/Portable/AddImport/CodeActions/ParentInstallPackageCodeAction.cs @@ -10,7 +10,6 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Packaging; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Tags; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.AddImport @@ -24,7 +23,11 @@ internal abstract partial class AbstractAddImportFeatureService private class ParentInstallPackageCodeAction : CodeAction.CodeActionWithNestedActions { - public override ImmutableArray Tags => WellKnownTagArrays.NuGet; + /// + /// 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). + /// + public override ImmutableArray Tags => RequiresNonDocumentChangeTags; /// /// Even though we have child actions, we mark ourselves as explicitly non-inlinable. diff --git a/src/Features/Core/Portable/AddImport/CodeActions/ProjectSymbolReferenceCodeAction.cs b/src/Features/Core/Portable/AddImport/CodeActions/ProjectSymbolReferenceCodeAction.cs index 05f98f38e3083..06b9606da80a1 100644 --- a/src/Features/Core/Portable/AddImport/CodeActions/ProjectSymbolReferenceCodeAction.cs +++ b/src/Features/Core/Portable/AddImport/CodeActions/ProjectSymbolReferenceCodeAction.cs @@ -2,6 +2,7 @@ // 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.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; @@ -20,23 +21,27 @@ internal abstract partial class AbstractAddImportFeatureService private class ProjectSymbolReferenceCodeAction : SymbolReferenceCodeAction { + /// + /// This code action may or may not add a project reference. If it does, it requires a non document change + /// (and is thus restricted in which hosts it can run). If it doesn't, it can run anywhere. + /// public ProjectSymbolReferenceCodeAction( Document originalDocument, AddImportFixData fixData) - : base(originalDocument, fixData) + : base(originalDocument, + fixData, + additionalTags: ShouldAddProjectReference(originalDocument, fixData) ? RequiresNonDocumentChangeTags : ImmutableArray.Empty) { Contract.ThrowIfFalse(fixData.Kind == AddImportFixKind.ProjectSymbol); } - private bool ShouldAddProjectReference() - => FixData.ProjectReferenceToAdd != null && FixData.ProjectReferenceToAdd != OriginalDocument.Project.Id; + private static bool ShouldAddProjectReference(Document originalDocument, AddImportFixData fixData) + => fixData.ProjectReferenceToAdd != null && fixData.ProjectReferenceToAdd != originalDocument.Project.Id; protected override Task UpdateProjectAsync(Project project, bool isPreview, CancellationToken cancellationToken) { - if (!ShouldAddProjectReference()) - { + if (!ShouldAddProjectReference(this.OriginalDocument, this.FixData)) return SpecializedTasks.Null(); - } var projectWithAddedReference = project.AddProjectReference(new ProjectReference(FixData.ProjectReferenceToAdd)); var applyOperation = new ApplyChangesOperation(projectWithAddedReference.Solution); diff --git a/src/Features/Core/Portable/AddImport/CodeActions/SymbolReference.SymbolReferenceCodeAction.cs b/src/Features/Core/Portable/AddImport/CodeActions/SymbolReference.SymbolReferenceCodeAction.cs index 882aabe48023f..5e5a0fcb7a898 100644 --- a/src/Features/Core/Portable/AddImport/CodeActions/SymbolReference.SymbolReferenceCodeAction.cs +++ b/src/Features/Core/Portable/AddImport/CodeActions/SymbolReference.SymbolReferenceCodeAction.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; @@ -23,8 +24,9 @@ private abstract class SymbolReferenceCodeAction : AddImportCodeAction { protected SymbolReferenceCodeAction( Document originalDocument, - AddImportFixData fixData) - : base(originalDocument, fixData) + AddImportFixData fixData, + ImmutableArray additionalTags) + : base(originalDocument, fixData, additionalTags) { } diff --git a/src/Features/Core/Portable/AddMissingReference/AddMissingReferenceCodeAction.cs b/src/Features/Core/Portable/AddMissingReference/AddMissingReferenceCodeAction.cs index c5c56dc004e12..fdeeb1df1095a 100644 --- a/src/Features/Core/Portable/AddMissingReference/AddMissingReferenceCodeAction.cs +++ b/src/Features/Core/Portable/AddMissingReference/AddMissingReferenceCodeAction.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; @@ -21,6 +22,12 @@ internal class AddMissingReferenceCodeAction : CodeAction public override string Title { get; } + /// + /// This code action only works by adding references. As such, it requires a non document change (and is + /// thus restricted in which hosts it can run). + /// + public override ImmutableArray Tags => RequiresNonDocumentChangeTags; + public AddMissingReferenceCodeAction(Project project, string title, ProjectReference? projectReferenceToAdd, AssemblyIdentity missingAssemblyIdentity) { _project = project; diff --git a/src/Features/Core/Portable/AddPackage/InstallPackageParentCodeAction.cs b/src/Features/Core/Portable/AddPackage/InstallPackageParentCodeAction.cs index 912d8989569c8..933b628839994 100644 --- a/src/Features/Core/Portable/AddPackage/InstallPackageParentCodeAction.cs +++ b/src/Features/Core/Portable/AddPackage/InstallPackageParentCodeAction.cs @@ -20,7 +20,11 @@ namespace Microsoft.CodeAnalysis.AddPackage /// internal class InstallPackageParentCodeAction : CodeAction.CodeActionWithNestedActions { - public override ImmutableArray Tags => WellKnownTagArrays.NuGet; + /// + /// 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). + /// + public override ImmutableArray Tags => RequiresNonDocumentChangeTags; /// /// Even though we have child actions, we mark ourselves as explicitly non-inlinable. diff --git a/src/Features/Core/Portable/ChangeSignature/ChangeSignatureCodeAction.cs b/src/Features/Core/Portable/ChangeSignature/ChangeSignatureCodeAction.cs index ed277bad0ab9f..9fe93598816dc 100644 --- a/src/Features/Core/Portable/ChangeSignature/ChangeSignatureCodeAction.cs +++ b/src/Features/Core/Portable/ChangeSignature/ChangeSignatureCodeAction.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; @@ -18,6 +19,12 @@ internal class ChangeSignatureCodeAction : CodeActionWithOptions private readonly AbstractChangeSignatureService _changeSignatureService; private readonly ChangeSignatureAnalysisSucceededContext _context; + /// + /// This code action currently pops up a confirmation dialog to the user. As such, it does more than make + /// document changes (and is thus restricted in which hosts it can run). + /// + public override ImmutableArray Tags => RequiresNonDocumentChangeTags; + public ChangeSignatureCodeAction(AbstractChangeSignatureService changeSignatureService, ChangeSignatureAnalysisSucceededContext context) { _changeSignatureService = changeSignatureService; diff --git a/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceCodeAction.cs b/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceCodeAction.cs index 6d3cccd76b5fd..189dd13e602c3 100644 --- a/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceCodeAction.cs +++ b/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceCodeAction.cs @@ -21,13 +21,23 @@ internal abstract partial class AbstractMoveToNamespaceCodeAction : CodeActionWi private readonly MoveToNamespaceAnalysisResult _moveToNamespaceAnalysisResult; private readonly CodeCleanupOptionsProvider _cleanupOptions; - public AbstractMoveToNamespaceCodeAction(IMoveToNamespaceService moveToNamespaceService, MoveToNamespaceAnalysisResult analysisResult, CodeCleanupOptionsProvider cleanupOptions) + public AbstractMoveToNamespaceCodeAction( + IMoveToNamespaceService moveToNamespaceService, + MoveToNamespaceAnalysisResult analysisResult, + CodeCleanupOptionsProvider cleanupOptions) { _moveToNamespaceService = moveToNamespaceService; _moveToNamespaceAnalysisResult = analysisResult; _cleanupOptions = cleanupOptions; } + /// + /// This code action does notify clients about the rename it performs. However, this is an optional part of + /// this work, that happens after the move has happened. As such, this does not require non document changes + /// and can run in all our hosts. + /// + public override ImmutableArray Tags => ImmutableArray.Empty; + public override object GetOptions(CancellationToken cancellationToken) { return _moveToNamespaceService.GetChangeNamespaceOptions( @@ -67,10 +77,9 @@ private static ImmutableArray CreateRenameOperations(MoveTo var symbolRenameCodeActionOperationFactory = moveToNamespaceResult.UpdatedSolution.Services.GetService(); - // It's possible we're not in a host context providing this service, in which case - // just provide a code action that won't notify of the symbol rename. - // Without the symbol rename operation, code generators (like WPF) may not - // know to regenerate code correctly. + // It's possible we're not in a host context providing this service, in which case just provide a code + // action that won't notify of the symbol rename. Without the symbol rename operation, code generators (like + // WPF) may not know to regenerate code correctly. if (symbolRenameCodeActionOperationFactory != null) { foreach (var (newName, symbol) in moveToNamespaceResult.NewNameOriginalSymbolMapping) diff --git a/src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionHelpers.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs similarity index 97% rename from src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionHelpers.cs rename to src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs index 3a957e6848bea..19534571877ee 100644 --- a/src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionHelpers.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs @@ -16,9 +16,7 @@ using Microsoft.CodeAnalysis.UnifiedSuggestions; using Microsoft.VisualStudio.LanguageServer.Protocol; using Roslyn.Utilities; -using static Microsoft.CodeAnalysis.CodeActions.CodeAction; using CodeAction = Microsoft.CodeAnalysis.CodeActions.CodeAction; -using CodeActionOptions = Microsoft.CodeAnalysis.CodeActions.CodeActionOptions; using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.CodeActions @@ -59,16 +57,12 @@ public static async Task GetVSCodeActionsAsync( { // Filter out code actions with options since they'll show dialogs and we can't remote the UI and the options. if (suggestedAction.OriginalCodeAction is CodeActionWithOptions) - { continue; - } - // TO-DO: Re-enable code actions involving package manager once supported by LSP. + // Skip code actions that requires non-document changes. We can't apply them in LSP currently. // https://github.com/dotnet/roslyn/issues/48698 - if (suggestedAction.OriginalCodeAction.Tags.Equals(WellKnownTagArrays.NuGet)) - { + if (suggestedAction.OriginalCodeAction.Tags.Contains(CodeAction.RequiresNonDocumentChange)) continue; - } codeActions.Add(GenerateVSCodeAction( request, documentText, @@ -236,7 +230,7 @@ private static CodeAction GetNestedActionsFromActionSet(IUnifiedSuggestedAction } } - return CodeActionWithNestedActions.Create( + return CodeAction.CodeActionWithNestedActions.Create( codeAction.Title, nestedActions.ToImmutable(), codeAction.IsInlinable, codeAction.Priority); } diff --git a/src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionResolveData.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveData.cs similarity index 100% rename from src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionResolveData.cs rename to src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveData.cs diff --git a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs new file mode 100644 index 0000000000000..97ce814f8f0ce --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs @@ -0,0 +1,208 @@ +// 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.Collections.Generic; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.CodeActions; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.LanguageServer.Protocol; +using Newtonsoft.Json.Linq; +using Roslyn.Utilities; +using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler +{ + /// + /// Resolves a code action by filling out its Edit property. The handler is triggered only when a user hovers over a + /// code action. This system allows the basic code action data to be computed quickly, and the complex data, to be + /// computed only when necessary (i.e. when hovering/previewing a code action). + /// + /// This system only supports text edits to documents. In the future, supporting complex edits (including changes to + /// project files) would be desirable. + /// + /// + [ExportCSharpVisualBasicStatelessLspService(typeof(CodeActionResolveHandler)), Shared] + [Method(LSP.Methods.CodeActionResolveName)] + internal class CodeActionResolveHandler : ILspServiceDocumentRequestHandler + { + private readonly ICodeFixService _codeFixService; + private readonly ICodeRefactoringService _codeRefactoringService; + private readonly IGlobalOptionService _globalOptions; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public CodeActionResolveHandler( + ICodeFixService codeFixService, + ICodeRefactoringService codeRefactoringService, + IGlobalOptionService globalOptions) + { + _codeFixService = codeFixService; + _codeRefactoringService = codeRefactoringService; + _globalOptions = globalOptions; + } + + public bool MutatesSolutionState => false; + public bool RequiresLSPSolution => true; + + public TextDocumentIdentifier GetTextDocumentIdentifier(LSP.CodeAction request) + => ((JToken)request.Data!).ToObject()!.TextDocument; + + public async Task HandleRequestAsync(LSP.CodeAction codeAction, RequestContext context, CancellationToken cancellationToken) + { + var document = context.GetRequiredDocument(); + var solution = document.Project.Solution; + + var data = ((JToken)codeAction.Data!).ToObject(); + Assumes.Present(data); + + var options = _globalOptions.GetCodeActionOptionsProvider(); + + var codeActionsCache = context.GetRequiredLspService(); + var codeActions = await CodeActionHelpers.GetCodeActionsAsync( + codeActionsCache, + document, + data.Range, + options, + _codeFixService, + _codeRefactoringService, + cancellationToken).ConfigureAwait(false); + + var codeActionToResolve = CodeActionHelpers.GetCodeActionToResolve(data.UniqueIdentifier, codeActions); + Contract.ThrowIfNull(codeActionToResolve); + + var operations = await codeActionToResolve.GetOperationsAsync(cancellationToken).ConfigureAwait(false); + + // TO-DO: We currently must execute code actions which add new documents on the server as commands, + // since there is no LSP support for adding documents yet. In the future, we should move these actions + // to execute on the client. + // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1147293/ + + var textDiffService = solution.Services.GetService(); + + using var _ = ArrayBuilder.GetInstance(out var textDocumentEdits); + + foreach (var option in operations) + { + // We only support making solution-updating operations in LSP. And only ones that modify documents. 1st + // class code actions that do more than this are supposed to add the CodeAction.MakesNonDocumentChange + // in their Tags so we can filter them out before returning them to the client. + // + // However, we cannot enforce this as 3rd party fixers can still run. So we filter their results to + // only apply the portions of their work that updates documents, and nothing else. + if (option is not ApplyChangesOperation applyChangesOperation) + { + context.TraceInformation($"Skipping code action operation for '{data.UniqueIdentifier}'. It was a '{option.GetType().FullName}'"); + continue; + } + + var changes = applyChangesOperation.ChangedSolution.GetChanges(solution); + var projectChanges = changes.GetProjectChanges(); + + // Ignore any non-document changes for now. Note though that LSP does support additional functionality + // (like create/rename/delete file). Once VS updates their LSP client impl to support this, we should + // add that support here. + // + // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspaceEdit + // + // Tracked with: https://github.com/dotnet/roslyn/issues/65303 +#if false + + // TO-DO: If the change involves adding or removing a document, execute via command instead of WorkspaceEdit + // until adding/removing documents is supported in LSP: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1147293/ + // After support is added, remove the below if-statement and add code to support adding/removing documents. + var addedDocuments = projectChanges.SelectMany( + pc => pc.GetAddedDocuments().Concat(pc.GetAddedAdditionalDocuments().Concat(pc.GetAddedAnalyzerConfigDocuments()))); + var removedDocuments = projectChanges.SelectMany( + pc => pc.GetRemovedDocuments().Concat(pc.GetRemovedAdditionalDocuments().Concat(pc.GetRemovedAnalyzerConfigDocuments()))); + if (addedDocuments.Any() || removedDocuments.Any()) + { + codeAction.Command = SetCommand(codeAction.Title, data); + return codeAction; + } + + // TO-DO: If the change involves adding or removing a project reference, execute via command instead of + // WorkspaceEdit until adding/removing project references is supported in LSP: + // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1166040 + var projectReferences = projectChanges.SelectMany( + pc => pc.GetAddedProjectReferences().Concat(pc.GetRemovedProjectReferences())); + if (projectReferences.Any()) + { + codeAction.Command = SetCommand(codeAction.Title, data); + return codeAction; + } + +#endif + + // Changed documents + await AddTextDocumentEditsAsync( + projectChanges.SelectMany(pc => pc.GetChangedDocuments()), + applyChangesOperation.ChangedSolution.GetDocument, + solution.GetDocument).ConfigureAwait(false); + + // Changed analyzer config documents + await AddTextDocumentEditsAsync( + projectChanges.SelectMany(pc => pc.GetChangedAnalyzerConfigDocuments()), + applyChangesOperation.ChangedSolution.GetAnalyzerConfigDocument, + solution.GetAnalyzerConfigDocument).ConfigureAwait(false); + + // Changed additional documents + await AddTextDocumentEditsAsync( + projectChanges.SelectMany(pc => pc.GetChangedAdditionalDocuments()), + applyChangesOperation.ChangedSolution.GetAdditionalDocument, + solution.GetAdditionalDocument).ConfigureAwait(false); + } + + codeAction.Edit = new LSP.WorkspaceEdit { DocumentChanges = textDocumentEdits.ToArray() }; + + return codeAction; + + async Task AddTextDocumentEditsAsync( + IEnumerable changedDocuments, + Func getNewDocument, + Func getOldDocument) + where TTextDocument : TextDocument + { + foreach (var docId in changedDocuments) + { + var newTextDoc = getNewDocument(docId); + var oldTextDoc = getOldDocument(docId); + + Contract.ThrowIfNull(oldTextDoc); + Contract.ThrowIfNull(newTextDoc); + + var oldText = await oldTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); + + IEnumerable textChanges; + + // Normal documents have a unique service for calculating minimal text edits. If we used the standard 'GetTextChanges' + // method instead, we would get a change that spans the entire document, which we ideally want to avoid. + if (newTextDoc is Document newDoc && oldTextDoc is Document oldDoc) + { + Contract.ThrowIfNull(textDiffService); + textChanges = await textDiffService.GetTextChangesAsync(oldDoc, newDoc, cancellationToken).ConfigureAwait(false); + } + else + { + var newText = await newTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); + textChanges = newText.GetTextChanges(oldText); + } + + var edits = textChanges.Select(tc => ProtocolConversions.TextChangeToTextEdit(tc, oldText)).ToArray(); + var documentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = newTextDoc.GetURI() }; + textDocumentEdits.Add(new TextDocumentEdit { TextDocument = documentIdentifier, Edits = edits }); + } + } + } + } +} diff --git a/src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionsCache.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionsCache.cs similarity index 97% rename from src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionsCache.cs rename to src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionsCache.cs index bd4bac23fac21..453a5e29cad2d 100644 --- a/src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionsCache.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionsCache.cs @@ -2,15 +2,11 @@ // 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.Collections.Generic; using System.Collections.Immutable; -using System.Composition; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Completion; using Microsoft.CodeAnalysis.UnifiedSuggestions; using Roslyn.Utilities; using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; diff --git a/src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionsCacheFactory.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionsCacheFactory.cs similarity index 100% rename from src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionsCacheFactory.cs rename to src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionsCacheFactory.cs diff --git a/src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionsHandler.cs similarity index 86% rename from src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionsHandler.cs rename to src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionsHandler.cs index fb35d5dbf39fe..99f63c14f4ccf 100644 --- a/src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionsHandler.cs @@ -19,13 +19,9 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler { /// - /// Handles the initial request for code actions. Leaves the Edit and Command properties - /// of the returned VSCodeActions blank, as these properties should be populated by the - /// CodeActionsResolveHandler only when the user requests them. - /// - /// TODO - This must be moved to the MS.CA.LanguageServer.Protocol project once the - /// EditorFeatures references in are removed. - /// See https://github.com/dotnet/roslyn/issues/55142 + /// Handles the initial request for code actions. Leaves the Edit and Command properties of the returned + /// VSCodeActions blank, as these properties should be populated by the CodeActionsResolveHandler only when the user + /// requests them. /// [ExportCSharpVisualBasicStatelessLspService(typeof(CodeActionsHandler)), Shared] [Method(LSP.Methods.TextDocumentCodeActionName)] diff --git a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/RunCodeActionsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/RunCodeActionsTests.cs index d7d753843da1c..33aedb466d380 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/RunCodeActionsTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/RunCodeActionsTests.cs @@ -24,7 +24,7 @@ public RunCodeActionsTests(ITestOutputHelper testOutputHelper) : base(testOutput { } - [WpfFact] + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/65303")] public async Task TestRunCodeActions() { var markup = diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioAddMetadataReferenceCodeActionOperationFactoryWorkspaceService.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioAddMetadataReferenceCodeActionOperationFactoryWorkspaceService.cs index 3c215fe548bbd..c379352837f72 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioAddMetadataReferenceCodeActionOperationFactoryWorkspaceService.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioAddMetadataReferenceCodeActionOperationFactoryWorkspaceService.cs @@ -42,7 +42,7 @@ public CodeActionOperation CreateAddMetadataReferenceOperation(ProjectId project return new AddMetadataReferenceOperation(projectId, assemblyIdentity); } - private class AddMetadataReferenceOperation : Microsoft.CodeAnalysis.CodeActions.CodeActionOperation + private class AddMetadataReferenceOperation : CodeActionOperation { private readonly AssemblyIdentity _assemblyIdentity; private readonly ProjectId _projectId; @@ -53,7 +53,7 @@ public AddMetadataReferenceOperation(ProjectId projectId, AssemblyIdentity assem _assemblyIdentity = assemblyIdentity; } - public override void Apply(Microsoft.CodeAnalysis.Workspace workspace, CancellationToken cancellationToken = default) + public override void Apply(Workspace workspace, CancellationToken cancellationToken = default) { var visualStudioWorkspace = (VisualStudioWorkspaceImpl)workspace; if (!visualStudioWorkspace.TryAddReferenceToProject(_projectId, "*" + _assemblyIdentity.GetDisplayName())) diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/CodeActions/CodeActionsHandlerProvider.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/CodeActions/CodeActionsHandlerProvider.cs index 16128bb031d60..4c041562a38be 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/CodeActions/CodeActionsHandlerProvider.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/CodeActions/CodeActionsHandlerProvider.cs @@ -44,18 +44,4 @@ public XamlCodeActionResolveHandler( { } } - - [ExportStatelessXamlLspService(typeof(RunCodeActionHandler)), Shared] - internal class XamlRunCodeActionHandler : RunCodeActionHandler - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public XamlRunCodeActionHandler( - ICodeFixService codeFixService, - ICodeRefactoringService codeRefactoringService, - IGlobalOptionService globalOptions, - IThreadingContext threadingContext) : base(codeFixService, codeRefactoringService, globalOptions, threadingContext) - { - } - } } diff --git a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs index e3c3c6e1402e8..31b2bc798e1dc 100644 --- a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs +++ b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs @@ -33,6 +33,23 @@ namespace Microsoft.CodeAnalysis.CodeActions /// public abstract class CodeAction { + /// + /// Tag we use to convey that this code action should only be shown if it's in a host that allows for + /// non-document changes. For example if it needs to make project changes, or if will show host-specific UI. + /// + /// Note: if the bulk of code action is just document changes, and it does some optional things beyond that + /// (like navigating the user somewhere) this should not be set. Such a code action is still usable in all + /// hosts and should be shown to the user. It's only if the code action can truly not function should this + /// tag be provided. + /// + /// + /// Currently, this also means that we presume that all 3rd party code actions do not require non-document + /// changes and we will show them all in all hosts. + /// + /// + internal const string RequiresNonDocumentChange = nameof(RequiresNonDocumentChange); + private protected static ImmutableArray RequiresNonDocumentChangeTags = ImmutableArray.Create(RequiresNonDocumentChange); + /// /// A short title describing the action that may appear in a menu. /// diff --git a/src/Workspaces/Core/Portable/Tags/WellKnownTags.cs b/src/Workspaces/Core/Portable/Tags/WellKnownTags.cs index d17c2078e94da..b0e2e3957198b 100644 --- a/src/Workspaces/Core/Portable/Tags/WellKnownTags.cs +++ b/src/Workspaces/Core/Portable/Tags/WellKnownTags.cs @@ -130,7 +130,6 @@ internal static class WellKnownTagArrays internal static readonly ImmutableArray StatusInformation = ImmutableArray.Create(WellKnownTags.StatusInformation); internal static readonly ImmutableArray AddReference = ImmutableArray.Create(WellKnownTags.AddReference); - internal static readonly ImmutableArray NuGet = ImmutableArray.Create(WellKnownTags.NuGet); internal static readonly ImmutableArray TargetTypeMatch = ImmutableArray.Create(WellKnownTags.TargetTypeMatch); internal static readonly ImmutableArray CSharpFile = ImmutableArray.Create(WellKnownTags.File, LanguageNames.CSharp);