diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index 264d82350b329919dcd23208364ebfc04568a299..4a17303417d98338174c85679ad601c0f3bd5330 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -301,9 +301,9 @@ static SuggestedActionSetPriority ConvertToSuggestedActionSetPriority(UnifiedSug // See https://github.com/dotnet/roslyn/issues/29589 const bool includeSuppressionFixes = true; - return UnifiedSuggestedActionsSource.GetFilterAndOrderCodeFixes_MustBeCalledFromUIThread( + return UnifiedSuggestedActionsSource.GetFilterAndOrderCodeFixesAsync( workspace, _owner._codeFixService, document, range.Span.ToTextSpan(), - includeSuppressionFixes, isBlocking: true, addOperationScope, cancellationToken); + includeSuppressionFixes, isBlocking: true, addOperationScope, cancellationToken).WaitAndGetResult(cancellationToken); } private static string GetFixCategory(DiagnosticSeverity severity) @@ -352,9 +352,9 @@ private static string GetFixCategory(DiagnosticSeverity severity) // then we want to filter out refactorings outside the selection span. var filterOutsideSelection = !requestedActionCategories.Contains(PredefinedSuggestedActionCategoryNames.Refactoring); - return UnifiedSuggestedActionsSource.GetFilterAndOrderCodeRefactorings_MustBeCalledFromUIThread( + return UnifiedSuggestedActionsSource.GetFilterAndOrderCodeRefactoringsAsync( workspace, _owner._codeRefactoringService, document, selection, isBlocking: true, - addOperationScope, filterOutsideSelection, cancellationToken); + addOperationScope, filterOutsideSelection, cancellationToken).WaitAndGetResult(cancellationToken); } public Task HasSuggestedActionsAsync( diff --git a/src/EditorFeatures/Core/Implementation/CodeFixes/CodeFixService.cs b/src/EditorFeatures/Core/Implementation/CodeFixes/CodeFixService.cs index 52502e9b15961f04ee421249ebe6a43e2fd55512..3ef2a847a0ffab424d50c5994eaacbf765550a46 100644 --- a/src/EditorFeatures/Core/Implementation/CodeFixes/CodeFixService.cs +++ b/src/EditorFeatures/Core/Implementation/CodeFixes/CodeFixService.cs @@ -743,26 +743,8 @@ private async Task> GetProjectDiagnosticsAsync(Project p foreach (var fixer in allFixers) { await extensionManager.PerformActionAsync(fixer, () => fixer.RegisterCodeFixesAsync(context) ?? Task.CompletedTask).ConfigureAwait(false); - foreach (var fix in fixes) - { - if (!fix.Action.PerformFinalApplicabilityCheck) - { - return true; - } - - // Have to see if this fix is still applicable. Jump to the foreground thread - // to make that check. - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); - - var applicable = fix.Action.IsApplicable(document.Project.Solution.Workspace); - - await TaskScheduler.Default; - - if (applicable) - { - return true; - } - } + if (fixes.Count > 0) + return true; } return false; diff --git a/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb b/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb index d3c4bf09d9ff58ed63d41d670a15a2593b3561f6..7422320e0d60568c8057ccf3e6fd2bf014a4441f 100644 --- a/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/AddImport/AddImportCrossLanguageTests.vb @@ -224,7 +224,7 @@ End Namespace Await TestAsync(input, expected, codeActionIndex:=1) End Function - + Public Async Function AddProjectReference_CSharpToCSharp_Test() As Task Dim input = @@ -305,7 +305,7 @@ namespace CSAssembly2 Await TestMissing(input) End Function - + Public Async Function TestAddProjectReference_CSharpToCSharp_WithProjectRenamed() As Task Dim input = @@ -358,7 +358,7 @@ namespace CSAssembly2 End Sub) End Function - + Public Async Function TestAddProjectReference_VBToVB() As Task Dim input = @@ -449,7 +449,7 @@ class C Await TestMissing(input) End Function - + Public Async Function TestAddProjectReference_EvenWithExistingUsing() As Task Dim input = diff --git a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs index be922cf94f499163e6f1247e781ad16672bdb43e..e4fc716f0175939d1e5149e47c949d5a6e232727 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs @@ -9,9 +9,11 @@ using System.Collections.Immutable; using System.Linq; using System.Threading; +using System.Threading.Tasks; using System.Windows.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.MetadataAsSource; @@ -707,6 +709,15 @@ public void ChangeSolution(Solution solution) public override bool CanApplyParseOptionChange(ParseOptions oldOptions, ParseOptions newOptions, Project project) => true; + internal override async Task CanAddProjectReferenceAsync(ProjectId referencingProject, ProjectId referencedProject, CancellationToken cancellationToken) + { + // VisualStudioWorkspace switches to the main thread for this call, so do the same thing here to catch tests + // that fail to account for this possibility. + var threadingContext = ExportProvider.GetExportedValue(); + await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); + return true; + } + internal ITextBuffer GetOrCreateBufferForPath(string? filePath, IContentType contentType, string languageName, string initialText) { // If we don't have a file path we'll just make something up for the purpose of this dictionary so all diff --git a/src/Features/Core/Portable/AddImport/CodeActions/AssemblyReferenceCodeAction.cs b/src/Features/Core/Portable/AddImport/CodeActions/AssemblyReferenceCodeAction.cs index 384fdca125ec87265b9dd674b05de1f0ba91f63a..d786ac4e6be1d1b0e80899ba0f9f346107f0f7b4 100644 --- a/src/Features/Core/Portable/AddImport/CodeActions/AssemblyReferenceCodeAction.cs +++ b/src/Features/Core/Portable/AddImport/CodeActions/AssemblyReferenceCodeAction.cs @@ -2,7 +2,8 @@ // 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; +#nullable enable + using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -16,47 +17,48 @@ internal abstract partial class AbstractAddImportFeatureService _lazyResolvedPath; - public AssemblyReferenceCodeAction( Document originalDocument, AddImportFixData fixData) : base(originalDocument, fixData) { Contract.ThrowIfFalse(fixData.Kind == AddImportFixKind.ReferenceAssemblySymbol); - _lazyResolvedPath = new Lazy(ResolvePath); } - private string ResolvePath() + private Task ResolvePathAsync(CancellationToken cancellationToken) { - var assemblyResolverService = OriginalDocument.Project.Solution.Workspace.Services.GetService(); + var assemblyResolverService = OriginalDocument.Project.Solution.Workspace.Services.GetRequiredService(); - var assemblyPath = assemblyResolverService?.ResolveAssemblyPath( + return assemblyResolverService.ResolveAssemblyPathAsync( OriginalDocument.Project.Id, FixData.AssemblyReferenceAssemblyName, - FixData.AssemblyReferenceFullyQualifiedTypeName); - - return assemblyPath; + FixData.AssemblyReferenceFullyQualifiedTypeName, + cancellationToken); } - internal override bool PerformFinalApplicabilityCheck - => true; + protected override Task> ComputePreviewOperationsAsync(CancellationToken cancellationToken) + => ComputeOperationsAsync(isPreview: true, cancellationToken); - internal override bool IsApplicable(Workspace workspace) - => !string.IsNullOrWhiteSpace(_lazyResolvedPath.Value); + protected override Task> ComputeOperationsAsync(CancellationToken cancellationToken) + => ComputeOperationsAsync(isPreview: false, cancellationToken); - protected override async Task> ComputeOperationsAsync(CancellationToken cancellationToken) + private async Task> ComputeOperationsAsync(bool isPreview, CancellationToken cancellationToken) { - var service = OriginalDocument.Project.Solution.Workspace.Services.GetService(); - var resolvedPath = _lazyResolvedPath.Value; - var reference = service.GetReference(resolvedPath, MetadataReferenceProperties.Assembly); - var newDocument = await GetUpdatedDocumentAsync(cancellationToken).ConfigureAwait(false); + var newProject = newDocument.Project; // Now add the actual assembly reference. - var newProject = newDocument.Project; - newProject = newProject.WithMetadataReferences( - newProject.MetadataReferences.Concat(reference)); + if (!isPreview) + { + var resolvedPath = await ResolvePathAsync(cancellationToken).ConfigureAwait(false); + if (!string.IsNullOrWhiteSpace(resolvedPath)) + { + var service = OriginalDocument.Project.Solution.Workspace.Services.GetRequiredService(); + var reference = service.GetReference(resolvedPath, MetadataReferenceProperties.Assembly); + newProject = newProject.WithMetadataReferences( + newProject.MetadataReferences.Concat(reference)); + } + } var operation = new ApplyChangesOperation(newProject.Solution); return SpecializedCollections.SingletonEnumerable(operation); diff --git a/src/Features/Core/Portable/AddImport/CodeActions/MetadataSymbolReferenceCodeAction.cs b/src/Features/Core/Portable/AddImport/CodeActions/MetadataSymbolReferenceCodeAction.cs index fb9742d01dec203b83f4e4853995f6320338e6e7..0b022a633b531de5fb6b4877705a921594ed8c7f 100644 --- a/src/Features/Core/Portable/AddImport/CodeActions/MetadataSymbolReferenceCodeAction.cs +++ b/src/Features/Core/Portable/AddImport/CodeActions/MetadataSymbolReferenceCodeAction.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.AddImport @@ -17,14 +19,14 @@ public MetadataSymbolReferenceCodeAction(Document originalDocument, AddImportFix Contract.ThrowIfFalse(fixData.Kind == AddImportFixKind.MetadataSymbol); } - protected override Project UpdateProject(Project project) + protected override Task UpdateProjectAsync(Project project, bool isPreview, CancellationToken cancellationToken) { var projectWithReference = project.Solution.GetProject(FixData.PortableExecutableReferenceProjectId); var reference = projectWithReference.MetadataReferences .OfType() .First(pe => pe.FilePath == FixData.PortableExecutableReferenceFilePathToAdd); - return project.AddMetadataReference(reference); + return Task.FromResult(project.AddMetadataReference(reference)); } } } diff --git a/src/Features/Core/Portable/AddImport/CodeActions/ProjectSymbolReferenceCodeAction.cs b/src/Features/Core/Portable/AddImport/CodeActions/ProjectSymbolReferenceCodeAction.cs index 6a447096bcb66dc0a26932ded9b14540f9949e16..82420a7d0ad3435cddba82f827e1e519cae8b0b7 100644 --- a/src/Features/Core/Portable/AddImport/CodeActions/ProjectSymbolReferenceCodeAction.cs +++ b/src/Features/Core/Portable/AddImport/CodeActions/ProjectSymbolReferenceCodeAction.cs @@ -2,6 +2,8 @@ // 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.Threading; +using System.Threading.Tasks; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.AddImport @@ -27,19 +29,22 @@ private class ProjectSymbolReferenceCodeAction : SymbolReferenceCodeAction private bool ShouldAddProjectReference() => FixData.ProjectReferenceToAdd != null && FixData.ProjectReferenceToAdd != OriginalDocument.Project.Id; - internal override bool PerformFinalApplicabilityCheck - => ShouldAddProjectReference(); + protected override async Task UpdateProjectAsync(Project project, bool isPreview, CancellationToken cancellationToken) + { + if (!ShouldAddProjectReference()) + { + return project; + } - internal override bool IsApplicable(Workspace workspace) - => ShouldAddProjectReference() && - workspace.CanAddProjectReference( - OriginalDocument.Project.Id, FixData.ProjectReferenceToAdd); + if (!isPreview) + { + if (!await project.Solution.Workspace.CanAddProjectReferenceAsync(OriginalDocument.Project.Id, FixData.ProjectReferenceToAdd, cancellationToken).ConfigureAwait(false)) + { + return project; + } + } - protected override Project UpdateProject(Project project) - { - return ShouldAddProjectReference() - ? project.AddProjectReference(new ProjectReference(FixData.ProjectReferenceToAdd)) - : project; + return project.AddProjectReference(new ProjectReference(FixData.ProjectReferenceToAdd)); } } } diff --git a/src/Features/Core/Portable/AddImport/CodeActions/SymbolReference.SymbolReferenceCodeAction.cs b/src/Features/Core/Portable/AddImport/CodeActions/SymbolReference.SymbolReferenceCodeAction.cs index 1ed296574b47debb59355955143608a21a9037fd..8e2fbe5d424e853e631477ec6c060a19024a772b 100644 --- a/src/Features/Core/Portable/AddImport/CodeActions/SymbolReference.SymbolReferenceCodeAction.cs +++ b/src/Features/Core/Portable/AddImport/CodeActions/SymbolReference.SymbolReferenceCodeAction.cs @@ -2,8 +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.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; namespace Microsoft.CodeAnalysis.AddImport { @@ -24,18 +27,34 @@ private abstract class SymbolReferenceCodeAction : AddImportCodeAction { } - protected override async Task GetChangedSolutionAsync(CancellationToken cancellationToken) + protected override async Task> ComputePreviewOperationsAsync(CancellationToken cancellationToken) + { + var changedSolution = await GetChangedSolutionAsync(isPreview: true, cancellationToken).ConfigureAwait(false); + if (changedSolution == null) + { + return Array.Empty(); + } + + return new CodeActionOperation[] { new ApplyChangesOperation(changedSolution) }; + } + + protected override Task GetChangedSolutionAsync(CancellationToken cancellationToken) + { + return GetChangedSolutionAsync(isPreview: false, cancellationToken); + } + + private async Task GetChangedSolutionAsync(bool isPreview, CancellationToken cancellationToken) { var updatedDocument = await GetUpdatedDocumentAsync(cancellationToken).ConfigureAwait(false); // Defer to subtype to add any p2p or metadata refs as appropriate. - var updatedProject = UpdateProject(updatedDocument.Project); + var updatedProject = await UpdateProjectAsync(updatedDocument.Project, isPreview, cancellationToken).ConfigureAwait(false); var updatedSolution = updatedProject.Solution; return updatedSolution; } - protected abstract Project UpdateProject(Project project); + protected abstract Task UpdateProjectAsync(Project project, bool isPreview, CancellationToken cancellationToken); } } } diff --git a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractSyncNamespaceCodeRefactoringProvider.MoveFileCodeAction.cs b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractSyncNamespaceCodeRefactoringProvider.MoveFileCodeAction.cs index c07b69edbfe03bba08814f5f125f00c3971efd44..e0dedbf5a8fdbc55550c0f7a05b9effe6154e095 100644 --- a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractSyncNamespaceCodeRefactoringProvider.MoveFileCodeAction.cs +++ b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractSyncNamespaceCodeRefactoringProvider.MoveFileCodeAction.cs @@ -38,8 +38,6 @@ public MoveFileCodeAction(State state, ImmutableArray newFolders) _newfolders = newFolders; } - internal override bool PerformFinalApplicabilityCheck => true; - protected override async Task> ComputeOperationsAsync(CancellationToken cancellationToken) { var id = _state.Document.Id; diff --git a/src/Features/Core/Portable/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs b/src/Features/Core/Portable/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs index f8910890a7a5e7d8046d33930daa78fc869d3c8d..7cce7fa06417b82cfa197cfec38e51475081cf6a 100644 --- a/src/Features/Core/Portable/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs +++ b/src/Features/Core/Portable/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs @@ -29,10 +29,9 @@ namespace Microsoft.CodeAnalysis.UnifiedSuggestions internal class UnifiedSuggestedActionsSource { /// - /// Gets, filters, and orders code fixes. Must be called from the UI thread - /// due to the eventual call to . + /// Gets, filters, and orders code fixes. /// - public static ImmutableArray GetFilterAndOrderCodeFixes_MustBeCalledFromUIThread( + public static async Task> GetFilterAndOrderCodeFixesAsync( Workspace workspace, ICodeFixService codeFixService, Document document, @@ -46,30 +45,25 @@ internal class UnifiedSuggestedActionsSource // it. However, it's deliberate. We want to make sure that the code runs on // the background so that no one takes an accidentally dependency on running on // the UI thread. - var fixes = Task.Run( + var fixes = await Task.Run( () => codeFixService.GetFixesAsync( document, selection, includeSuppressionFixes, isBlocking, - addOperationScope, cancellationToken), cancellationToken).WaitAndGetResult(cancellationToken); + addOperationScope, cancellationToken), cancellationToken).ConfigureAwait(false); - var filteredFixes = FilterOnUIThread(fixes, workspace); + var filteredFixes = FilterOnAnyThread(fixes); var organizedFixes = OrganizeFixes(workspace, filteredFixes, includeSuppressionFixes); return organizedFixes; } - private static ImmutableArray FilterOnUIThread( - ImmutableArray collections, - Workspace workspace) + private static ImmutableArray FilterOnAnyThread(ImmutableArray collections) { - return collections.Select( - c => FilterIndividuallyOnUIThread(c, workspace)).WhereNotNull().ToImmutableArray(); + return collections.Select(c => FilterIndividuallyOnAnyThread(c)).WhereNotNull().ToImmutableArray(); } - private static CodeFixCollection? FilterIndividuallyOnUIThread( - CodeFixCollection collection, - Workspace workspace) + private static CodeFixCollection? FilterIndividuallyOnAnyThread(CodeFixCollection collection) { - var applicableFixes = collection.Fixes.WhereAsArray(f => IsApplicable(f.Action, workspace)); + var applicableFixes = collection.Fixes; return applicableFixes.Length == 0 ? null : applicableFixes.Length == collection.Fixes.Length @@ -411,10 +405,9 @@ private static bool IsBulkConfigurationAction(CodeAction action) => (action as AbstractConfigurationActionWithNestedActions)?.IsBulkConfigurationAction == true; /// - /// Gets, filters, and orders code refactorings. Must be called from the UI thread - /// due to the eventual call to . + /// Gets, filters, and orders code refactorings. /// - public static ImmutableArray GetFilterAndOrderCodeRefactorings_MustBeCalledFromUIThread( + public static async Task> GetFilterAndOrderCodeRefactoringsAsync( Workspace workspace, ICodeRefactoringService codeRefactoringService, Document document, @@ -428,30 +421,27 @@ private static bool IsBulkConfigurationAction(CodeAction action) // it. However, it's deliberate. We want to make sure that the code runs on // the background so that no one takes an accidentally dependency on running on // the UI thread. - var refactorings = Task.Run( + var refactorings = await Task.Run( () => codeRefactoringService.GetRefactoringsAsync( document, selection, isBlocking, addOperationScope, - cancellationToken), cancellationToken).WaitAndGetResult(cancellationToken); + cancellationToken), cancellationToken).ConfigureAwait(false); - var filteredRefactorings = FilterOnUIThread(refactorings, selection, filterOutsideSelection, workspace); + var filteredRefactorings = FilterOnAnyThread(refactorings, selection, filterOutsideSelection); return filteredRefactorings.SelectAsArray( r => OrganizeRefactorings(workspace, r)); } - private static ImmutableArray FilterOnUIThread( + private static ImmutableArray FilterOnAnyThread( ImmutableArray refactorings, TextSpan selection, - bool filterOutsideSelection, - Workspace workspace) - => refactorings.Select(r => FilterOnUIThread( - r, selection, filterOutsideSelection, workspace)).WhereNotNull().ToImmutableArray(); + bool filterOutsideSelection) + => refactorings.Select(r => FilterOnAnyThread(r, selection, filterOutsideSelection)).WhereNotNull().ToImmutableArray(); - private static CodeRefactoring? FilterOnUIThread( + private static CodeRefactoring? FilterOnAnyThread( CodeRefactoring refactoring, TextSpan selection, - bool filterOutsideSelection, - Workspace workspace) + bool filterOutsideSelection) { var actions = refactoring.CodeActions.WhereAsArray(IsActionAndSpanApplicable); return actions.Length == 0 @@ -462,11 +452,6 @@ private static bool IsBulkConfigurationAction(CodeAction action) bool IsActionAndSpanApplicable((CodeAction action, TextSpan? applicableSpan) actionAndSpan) { - if (!IsApplicable(actionAndSpan.action, workspace)) - { - return false; - } - if (filterOutsideSelection) { // Filter out refactorings with applicable span outside the selection span. @@ -539,19 +524,6 @@ private static UnifiedSuggestedActionSet OrganizeRefactorings(Workspace workspac applicableToSpan: refactoring.CodeActions.FirstOrDefault().applicableToSpan); } - private static bool IsApplicable(CodeAction action, Workspace workspace) - { - if (!action.PerformFinalApplicabilityCheck) - { - // If we don't even need to perform the final applicability check, - // then the code action is applicable. - return true; - } - - // Otherwise, defer to the action to make the decision. - return action.IsApplicable(workspace); - } - private static UnifiedSuggestedActionSetPriority GetUnifiedSuggestedActionSetPriority(CodeActionPriority key) => key switch { @@ -564,8 +536,8 @@ private static UnifiedSuggestedActionSetPriority GetUnifiedSuggestedActionSetPri /// /// Filters and orders the code fix sets and code refactoring sets amongst each other. - /// Should be called with the results from - /// and . + /// Should be called with the results from + /// and . /// public static ImmutableArray? FilterAndOrderActionSets( ImmutableArray fixes, diff --git a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs index 18004366409dccf1f346c69f60d9f998a5af0ebd..10c71258e5197863cf03316a1d3be8d6ded34111 100644 --- a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeRefactorings; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.UnifiedSuggestions; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -35,12 +34,11 @@ internal static class CodeActionHelpers Document document, ICodeFixService codeFixService, ICodeRefactoringService codeRefactoringService, - IThreadingContext threadingContext, LSP.Range selection, CancellationToken cancellationToken) { var actionSets = await GetActionSetsAsync( - document, codeFixService, codeRefactoringService, threadingContext, selection, cancellationToken).ConfigureAwait(false); + document, codeFixService, codeRefactoringService, selection, cancellationToken).ConfigureAwait(false); if (!actionSets.HasValue) { return Array.Empty(); @@ -108,12 +106,11 @@ internal static class CodeActionHelpers Document document, ICodeFixService codeFixService, ICodeRefactoringService codeRefactoringService, - IThreadingContext threadingContext, LSP.Range selection, CancellationToken cancellationToken) { var actionSets = await GetActionSetsAsync( - document, codeFixService, codeRefactoringService, threadingContext, selection, cancellationToken).ConfigureAwait(false); + document, codeFixService, codeRefactoringService, selection, cancellationToken).ConfigureAwait(false); if (!actionSets.HasValue) { return ImmutableArray.Empty; @@ -165,23 +162,19 @@ private static CodeAction GetNestedActionsFromActionSet(IUnifiedSuggestedAction Document document, ICodeFixService codeFixService, ICodeRefactoringService codeRefactoringService, - IThreadingContext threadingContext, LSP.Range selection, CancellationToken cancellationToken) { var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var textSpan = ProtocolConversions.RangeToTextSpan(selection, text); - // The logic to filter code actions requires the UI thread - await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - - var codeFixes = UnifiedSuggestedActionsSource.GetFilterAndOrderCodeFixes_MustBeCalledFromUIThread( + var codeFixes = await UnifiedSuggestedActionsSource.GetFilterAndOrderCodeFixesAsync( document.Project.Solution.Workspace, codeFixService, document, textSpan, includeSuppressionFixes: true, - isBlocking: false, addOperationScope: _ => null, cancellationToken); + isBlocking: false, addOperationScope: _ => null, cancellationToken).ConfigureAwait(false); - var codeRefactorings = UnifiedSuggestedActionsSource.GetFilterAndOrderCodeRefactorings_MustBeCalledFromUIThread( + var codeRefactorings = await UnifiedSuggestedActionsSource.GetFilterAndOrderCodeRefactoringsAsync( document.Project.Solution.Workspace, codeRefactoringService, document, textSpan, isBlocking: false, - addOperationScope: _ => null, filterOutsideSelection: false, cancellationToken); + addOperationScope: _ => null, filterOutsideSelection: false, cancellationToken).ConfigureAwait(false); var actionSets = UnifiedSuggestedActionsSource.FilterAndOrderActionSets(codeFixes, codeRefactorings, textSpan); return actionSets; diff --git a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs index 793284414b98d99097b8768eab7da6a628bda681..d0600984f988fcea74ef0a0cef18c04d1e1f9b97 100644 --- a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Composition; using System.Linq; using System.Threading; @@ -14,7 +13,6 @@ 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.PooledObjects; @@ -37,20 +35,17 @@ internal class CodeActionResolveHandler : AbstractRequestHandler HandleRequestAsync(LSP.VSCodeAction codeAction, RequestContext context, CancellationToken cancellationToken) @@ -63,7 +58,6 @@ public override async Task HandleRequestAsync(LSP.VSCodeAction document, _codeFixService, _codeRefactoringService, - _threadingContext, data.Range, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionsHandler.cs index 9dc4288822f3a242cd1db6ebbbfcc9c957bc0a1c..7e8a42ba85b2ca78e8ab65b4be930394d721a916 100644 --- a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionsHandler.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; 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.VisualStudio.LanguageServer.Protocol; @@ -28,7 +27,6 @@ internal class CodeActionsHandler : AbstractRequestHandler HandleRequestAsync(LSP.CodeActionParams request, RequestContext context, CancellationToken cancellationToken) @@ -55,8 +51,8 @@ public override async Task HandleRequestAsync(LSP.CodeAction } var codeActions = await CodeActionHelpers.GetVSCodeActionsAsync( - request, document, _codeFixService, _codeRefactoringService, _threadingContext, - request.Range, cancellationToken).ConfigureAwait(false); + request, document, _codeFixService, _codeRefactoringService, request.Range, + cancellationToken).ConfigureAwait(false); return codeActions; } diff --git a/src/Features/LanguageServer/Protocol/Handler/CodeActions/RunCodeActionHandler.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/RunCodeActionHandler.cs index 4ec0eda81b033d37d1cc67a8bb9b0426d1e49285..5eab3520d7f417a91ae525e5f6caa38bd91d377d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/CodeActions/RunCodeActionHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/RunCodeActionHandler.cs @@ -55,7 +55,7 @@ public async Task HandleRequestAsync(LSP.ExecuteCommandParams request, R var document = _solutionProvider.GetDocument(runRequest.TextDocument); var codeActions = await CodeActionHelpers.GetCodeActionsAsync( document, _codeFixService, _codeRefactoringService, - _threadingContext, runRequest.Range, cancellationToken).ConfigureAwait(false); + runRequest.Range, cancellationToken).ConfigureAwait(false); var actionToRun = CodeActionHelpers.GetCodeActionToResolve(runRequest.UniqueIdentifier, codeActions); Contract.ThrowIfNull(actionToRun); diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/MetadataReferences/VisualStudioFrameworkAssemblyPathResolverFactory.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/MetadataReferences/VisualStudioFrameworkAssemblyPathResolverFactory.cs index f19d4155bb89ac8c2b5f3c81767c131578bee7e8..eb415adf93c4869ff347cc806a54e8c7b8898045 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/MetadataReferences/VisualStudioFrameworkAssemblyPathResolverFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/MetadataReferences/VisualStudioFrameworkAssemblyPathResolverFactory.cs @@ -2,11 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#nullable enable + using System; using System.Composition; using System.IO; using System.Reflection; using System.Runtime.Versioning; +using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host; @@ -46,12 +50,13 @@ public Service(IThreadingContext threadingContext, VisualStudioWorkspace workspa _serviceProvider = serviceProvider; } - public string ResolveAssemblyPath( + public async Task ResolveAssemblyPathAsync( ProjectId projectId, string assemblyName, - string fullyQualifiedTypeName = null) + string? fullyQualifiedTypeName, + CancellationToken cancellationToken) { - this.AssertIsForeground(); + await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var assembly = ResolveAssembly(projectId, assemblyName); if (assembly != null) diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs index 02414559cb16279fe4171cf3b770ad772013b1f5..0c87f578b4ceef1bc84c6d7c470d09814cf0da78 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs @@ -13,6 +13,7 @@ using System.Linq; using System.Text; using System.Threading; +using System.Threading.Tasks; using EnvDTE; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -1351,9 +1352,10 @@ public void EnsureEditableDocuments(IEnumerable documents) public void EnsureEditableDocuments(params DocumentId[] documents) => this.EnsureEditableDocuments((IEnumerable)documents); - internal override bool CanAddProjectReference(ProjectId referencingProject, ProjectId referencedProject) + internal override async Task CanAddProjectReferenceAsync(ProjectId referencingProject, ProjectId referencedProject, CancellationToken cancellationToken) { - _foregroundObject.AssertIsForeground(); + await _foregroundObject.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + if (!TryGetHierarchy(referencingProject, out var referencingHierarchy) || !TryGetHierarchy(referencedProject, out var referencedHierarchy)) { diff --git a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs index d83155832a3899e12657a109544007abb45bb4f1..45944bc8ff1981ed656755c8cf2a6ddb7e0b3176 100644 --- a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs +++ b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs @@ -288,17 +288,6 @@ protected virtual Task PostProcessChangesAsync(Document document, Canc return document; } - internal virtual bool PerformFinalApplicabilityCheck => false; - - /// - /// Called by the CodeActions on the UI thread to determine if the CodeAction is still - /// applicable and should be presented to the user. CodeActions can override this if they - /// need to do any final checking that must be performed on the UI thread (for example - /// accessing and querying the Visual Studio DTE). - /// - internal virtual bool IsApplicable(Workspace workspace) - => true; - #region Factories for standard code actions /// diff --git a/src/Workspaces/Core/Portable/Workspace/Host/Metadata/FrameworkAssemblyPathResolverFactory.cs b/src/Workspaces/Core/Portable/Workspace/Host/Metadata/FrameworkAssemblyPathResolverFactory.cs index 915b8d09b3ec05235fd1b271a70394fdf7c33c5e..70d100206cb6d06c3108dba401a96e08e66509f2 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/Metadata/FrameworkAssemblyPathResolverFactory.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/Metadata/FrameworkAssemblyPathResolverFactory.cs @@ -2,9 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#nullable enable + using System; using System.Composition; +using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Host { @@ -31,10 +36,10 @@ public Service() // return false; //} - public string ResolveAssemblyPath(ProjectId projectId, string assemblyName, string fullyQualifiedTypeName = null) + public Task ResolveAssemblyPathAsync(ProjectId projectId, string assemblyName, string? fullyQualifiedTypeName, CancellationToken cancellationToken) { // Assembly path resolution not supported at the default workspace level. - return null; + return SpecializedTasks.Null(); } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/Metadata/IFrameworkAssemblyPathResolver.cs b/src/Workspaces/Core/Portable/Workspace/Host/Metadata/IFrameworkAssemblyPathResolver.cs index 3c617a1ec667566826536da475ee1bbf8a51f4a9..0bdcbb3bf3907b33c148e1cb782de364ad5baa29 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/Metadata/IFrameworkAssemblyPathResolver.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/Metadata/IFrameworkAssemblyPathResolver.cs @@ -2,6 +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. +#nullable enable + +using System.Threading; +using System.Threading.Tasks; + namespace Microsoft.CodeAnalysis.Host { /// @@ -19,7 +24,7 @@ internal interface IFrameworkAssemblyPathResolver : IWorkspaceService /// exist in the assembly. /// The project context to search within. /// The name of the assembly to try to resolve. - string ResolveAssemblyPath(ProjectId projectId, string assemblyName, string fullyQualifiedName = null); + Task ResolveAssemblyPathAsync(ProjectId projectId, string assemblyName, string? fullyQualifiedName, CancellationToken cancellationToken); // bool CanResolveType(ProjectId projectId, string assemblyName, string fullyQualifiedTypeName); } diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index 05da0b6e6b289ff00dd69ce668c0a00786603c68..3961220f3c068786d3e0a628bff259ff7738e260 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -1154,8 +1154,8 @@ public virtual bool CanApplyChange(ApplyChangesKind feature) /// Returns if a reference to referencedProject can be added to /// referencingProject. otherwise. /// - internal virtual bool CanAddProjectReference(ProjectId referencingProject, ProjectId referencedProject) - => false; + internal virtual Task CanAddProjectReferenceAsync(ProjectId referencingProject, ProjectId referencedProject, CancellationToken cancellationToken) + => SpecializedTasks.False; /// /// Apply changes made to a solution back to the workspace.