diff --git a/src/EditorFeatures/TestUtilities/ExtractInterface/TestExtractInterfaceOptions.cs b/src/EditorFeatures/TestUtilities/ExtractInterface/TestExtractInterfaceOptions.cs index b2b8c730ec0e7..846c12ca6ba8b 100644 --- a/src/EditorFeatures/TestUtilities/ExtractInterface/TestExtractInterfaceOptions.cs +++ b/src/EditorFeatures/TestUtilities/ExtractInterface/TestExtractInterfaceOptions.cs @@ -10,8 +10,6 @@ using System.Threading; using Microsoft.CodeAnalysis.ExtractInterface; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.Notification; namespace Microsoft.CodeAnalysis.Editor.UnitTests.ExtractInterface; @@ -33,14 +31,12 @@ internal sealed class TestExtractInterfaceOptionsService() : IExtractInterfaceOp public bool SameFile { get; set; } public ExtractInterfaceOptionsResult GetExtractInterfaceOptions( - ISyntaxFactsService syntaxFactsService, - INotificationService notificationService, + Document document, List extractableMembers, string defaultInterfaceName, List conflictingTypeNames, string defaultNamespace, string generatedNameTypeParameterSuffix, - string languageName, CancellationToken cancellationToken) { this.AllExtractableMembers = extractableMembers; diff --git a/src/Features/CSharpTest/ExtractClass/ExtractClassTests.cs b/src/Features/CSharpTest/ExtractClass/ExtractClassTests.cs index ef3a60743848e..f6288267e8aa7 100644 --- a/src/Features/CSharpTest/ExtractClass/ExtractClassTests.cs +++ b/src/Features/CSharpTest/ExtractClass/ExtractClassTests.cs @@ -38,7 +38,7 @@ private class Test : CSharpCodeRefactoringVerifier GetCodeRefactoringProviders() { - var service = new TestExtractClassOptionsService(DialogSelection, SameFile, IsClassDeclarationSelection) + var service = new TestExtractClassOptionsService(DialogSelection, WorkspaceKind != CodeAnalysis.WorkspaceKind.MiscellaneousFiles ? SameFile : true, IsClassDeclarationSelection) { FileName = FileName }; @@ -131,10 +131,24 @@ class Test } } """; + var expected = """ + internal class MyBase + { + int Method() + { + return 1 + 1; + } + } + + class Test : MyBase + { + } + """; await new Test { TestCode = input, + FixedCode = expected, WorkspaceKind = WorkspaceKind.MiscellaneousFiles }.RunAsync(); } diff --git a/src/Features/Core/Portable/ExtractClass/AbstractExtractClassRefactoringProvider.cs b/src/Features/Core/Portable/ExtractClass/AbstractExtractClassRefactoringProvider.cs index 95c0909b2a7ee..0e2c11c772cbc 100644 --- a/src/Features/Core/Portable/ExtractClass/AbstractExtractClassRefactoringProvider.cs +++ b/src/Features/Core/Portable/ExtractClass/AbstractExtractClassRefactoringProvider.cs @@ -24,15 +24,7 @@ internal abstract class AbstractExtractClassRefactoringProvider(IExtractClassOpt public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { - // For simplicity if we can't add a document the don't supply this refactoring. Not checking this results in known - // cases that won't work because the refactoring may try to add a document. There's non-trivial - // work to support a user interaction that makes sense for those cases. - // See: https://github.com/dotnet/roslyn/issues/50868 var solution = context.Document.Project.Solution; - if (!solution.CanApplyChange(ApplyChangesKind.AddDocument)) - { - return; - } var optionsService = _optionsService ?? solution.Services.GetService(); if (optionsService is null) diff --git a/src/Features/Core/Portable/ExtractInterface/AbstractExtractInterfaceService.cs b/src/Features/Core/Portable/ExtractInterface/AbstractExtractInterfaceService.cs index 19b40d749aa24..14a5aa1e13e79 100644 --- a/src/Features/Core/Portable/ExtractInterface/AbstractExtractInterfaceService.cs +++ b/src/Features/Core/Portable/ExtractInterface/AbstractExtractInterfaceService.cs @@ -264,20 +264,16 @@ internal static ExtractInterfaceOptionsResult GetExtractInterfaceOptions( var conflictingTypeNames = type.ContainingNamespace.GetAllTypes(cancellationToken).Select(t => t.Name); var candidateInterfaceName = type.TypeKind == TypeKind.Interface ? type.Name : "I" + type.Name; var defaultInterfaceName = NameGenerator.GenerateUniqueName(candidateInterfaceName, name => !conflictingTypeNames.Contains(name)); - var syntaxFactsService = document.GetRequiredLanguageService(); - var notificationService = document.Project.Solution.Services.GetRequiredService(); var generatedNameTypeParameterSuffix = ExtractTypeHelpers.GetTypeParameterSuffix(document, formattingOptions, type, extractableMembers, cancellationToken); var service = document.Project.Solution.Services.GetRequiredService(); return service.GetExtractInterfaceOptions( - syntaxFactsService, - notificationService, + document, [.. extractableMembers], defaultInterfaceName, [.. conflictingTypeNames], containingNamespace, generatedNameTypeParameterSuffix, - document.Project.Language, cancellationToken); } diff --git a/src/Features/Core/Portable/ExtractInterface/IExtractInterfaceOptionsService.cs b/src/Features/Core/Portable/ExtractInterface/IExtractInterfaceOptionsService.cs index cdc723464c54d..6281c68a90e76 100644 --- a/src/Features/Core/Portable/ExtractInterface/IExtractInterfaceOptionsService.cs +++ b/src/Features/Core/Portable/ExtractInterface/IExtractInterfaceOptionsService.cs @@ -5,21 +5,17 @@ using System.Collections.Generic; using System.Threading; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.Notification; namespace Microsoft.CodeAnalysis.ExtractInterface; internal interface IExtractInterfaceOptionsService : IWorkspaceService { ExtractInterfaceOptionsResult GetExtractInterfaceOptions( - ISyntaxFactsService syntaxFactsService, - INotificationService notificationService, + Document document, List extractableMembers, string defaultInterfaceName, List conflictingTypeNames, string defaultNamespace, string generatedNameTypeParameterSuffix, - string languageName, CancellationToken cancellationToken); } diff --git a/src/Features/ExternalAccess/OmniSharp/Internal/ExtractInterface/OmniSharpExtractInterfaceOptionsService.cs b/src/Features/ExternalAccess/OmniSharp/Internal/ExtractInterface/OmniSharpExtractInterfaceOptionsService.cs index 3b713575f0d9f..4b7bdbb8eb102 100644 --- a/src/Features/ExternalAccess/OmniSharp/Internal/ExtractInterface/OmniSharpExtractInterfaceOptionsService.cs +++ b/src/Features/ExternalAccess/OmniSharp/Internal/ExtractInterface/OmniSharpExtractInterfaceOptionsService.cs @@ -9,9 +9,6 @@ using Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.ExtractInterface; using Microsoft.CodeAnalysis.ExtractInterface; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.Notification; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Internal.ExtractInterface; @@ -24,14 +21,12 @@ internal sealed class OmniSharpExtractInterfaceOptionsService( private readonly IOmniSharpExtractInterfaceOptionsService _omniSharpExtractInterfaceOptionsService = omniSharpExtractInterfaceOptionsService; public ExtractInterfaceOptionsResult GetExtractInterfaceOptions( - ISyntaxFactsService syntaxFactsService, - INotificationService notificationService, + Document document, List extractableMembers, string defaultInterfaceName, List conflictingTypeNames, string defaultNamespace, string generatedNameTypeParameterSuffix, - string languageName, CancellationToken cancellationToken) { var result = _omniSharpExtractInterfaceOptionsService.GetExtractInterfaceOptions(extractableMembers, defaultInterfaceName); diff --git a/src/Features/ExternalAccess/OmniSharp/Internal/Notification/OmniSharpNotificationService.cs b/src/Features/ExternalAccess/OmniSharp/Internal/Notification/OmniSharpNotificationService.cs deleted file mode 100644 index ed048ccb32445..0000000000000 --- a/src/Features/ExternalAccess/OmniSharp/Internal/Notification/OmniSharpNotificationService.cs +++ /dev/null @@ -1,28 +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 Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Notification; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Notification; - -namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Internal.Notification; - -[ExportWorkspaceService(typeof(INotificationService)), Shared] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class OmniSharpNotificationService( - IOmniSharpNotificationService omniSharpNotificationService) : INotificationService -{ - public bool ConfirmMessageBox(string message, string? title = null, NotificationSeverity severity = NotificationSeverity.Warning) - { - return omniSharpNotificationService.ConfirmMessageBox(message, title, (OmniSharpNotificationSeverity)severity); - } - - public void SendNotification(string message, string? title = null, NotificationSeverity severity = NotificationSeverity.Warning) - { - omniSharpNotificationService.SendNotification(message, title, (OmniSharpNotificationSeverity)severity); - } -} diff --git a/src/VisualStudio/Core/Def/CommonControls/NewTypeDestinationSelection.xaml b/src/VisualStudio/Core/Def/CommonControls/NewTypeDestinationSelection.xaml index 5edd73d3569d1..340a480d00f2d 100644 --- a/src/VisualStudio/Core/Def/CommonControls/NewTypeDestinationSelection.xaml +++ b/src/VisualStudio/Core/Def/CommonControls/NewTypeDestinationSelection.xaml @@ -87,6 +87,7 @@ Grid.Column="0" Content="{Binding ElementName=control, Path=SelectNewFileAsDestination}" IsChecked="{Binding Destination, Converter={StaticResource enumBoolConverter}, ConverterParameter={x:Static local:NewTypeDestination.NewFile}}" + IsEnabled="{Binding CanAddDocument}" Padding="{StaticResource ResourceKey=radioButtonPadding}" VerticalAlignment="Center" /> diff --git a/src/VisualStudio/Core/Def/CommonControls/NewTypeDestinationSelectionViewModel.cs b/src/VisualStudio/Core/Def/CommonControls/NewTypeDestinationSelectionViewModel.cs index 800a30c34321a..e7c47a027c737 100644 --- a/src/VisualStudio/Core/Def/CommonControls/NewTypeDestinationSelectionViewModel.cs +++ b/src/VisualStudio/Core/Def/CommonControls/NewTypeDestinationSelectionViewModel.cs @@ -15,12 +15,13 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.CommonControls; internal class NewTypeDestinationSelectionViewModel : AbstractNotifyPropertyChanged { public static NewTypeDestinationSelectionViewModel Default = new( - string.Empty, - LanguageNames.CSharp, - string.Empty, - string.Empty, - [], - null + defaultName: string.Empty, + languageName: LanguageNames.CSharp, + defaultNamespace: string.Empty, + generatedNameTypeParameterSuffix: string.Empty, + conflictingNames: [], + syntaxFactsService: null, + canAddDocument: false ); private readonly string _fileExtension; @@ -37,7 +38,8 @@ public NewTypeDestinationSelectionViewModel( string defaultNamespace, string generatedNameTypeParameterSuffix, ImmutableArray conflictingNames, - ISyntaxFactsService? syntaxFactsService) + ISyntaxFactsService? syntaxFactsService, + bool canAddDocument) { _defaultName = defaultName; _fileExtension = languageName == LanguageNames.CSharp ? ".cs" : ".vb"; @@ -48,8 +50,12 @@ public NewTypeDestinationSelectionViewModel( _typeName = _defaultName; _syntaxFactsService = syntaxFactsService; _fileName = $"{defaultName}{_fileExtension}"; + CanAddDocument = canAddDocument; + _destination = canAddDocument ? NewTypeDestination.NewFile : NewTypeDestination.CurrentFile; } + public bool CanAddDocument { get; } + private string _typeName; public string TypeName { @@ -87,7 +93,7 @@ public string FileName set { SetProperty(ref _fileName, value); } } - private NewTypeDestination _destination = NewTypeDestination.NewFile; + private NewTypeDestination _destination; public NewTypeDestination Destination { get { return _destination; } diff --git a/src/VisualStudio/Core/Def/ExtractClass/ExtractClassViewModel.cs b/src/VisualStudio/Core/Def/ExtractClass/ExtractClassViewModel.cs index de4123c5ee503..bd43f08cc4bb5 100644 --- a/src/VisualStudio/Core/Def/ExtractClass/ExtractClassViewModel.cs +++ b/src/VisualStudio/Core/Def/ExtractClass/ExtractClassViewModel.cs @@ -29,7 +29,8 @@ public ExtractClassViewModel( string languageName, string typeParameterSuffix, ImmutableArray conflictingNames, - ISyntaxFactsService syntaxFactsService) + ISyntaxFactsService syntaxFactsService, + bool canAddDocument) { _notificationService = notificationService; _selectedType = selectedType; @@ -46,7 +47,8 @@ public ExtractClassViewModel( defaultNamespace, typeParameterSuffix, conflictingNames, - syntaxFactsService); + syntaxFactsService, + canAddDocument); } internal bool TrySubmit() diff --git a/src/VisualStudio/Core/Def/ExtractClass/VisualStudioExtractClassOptionsService.cs b/src/VisualStudio/Core/Def/ExtractClass/VisualStudioExtractClassOptionsService.cs index e85ebfe6af304..d013ea59852b5 100644 --- a/src/VisualStudio/Core/Def/ExtractClass/VisualStudioExtractClassOptionsService.cs +++ b/src/VisualStudio/Core/Def/ExtractClass/VisualStudioExtractClassOptionsService.cs @@ -46,7 +46,10 @@ internal sealed class VisualStudioExtractClassOptionsService( CancellationToken cancellationToken) { _threadingContext.ThrowIfNotOnUIThread(); - var notificationService = document.Project.Solution.Services.GetRequiredService(); + + var solution = document.Project.Solution; + var canAddDocument = solution.CanApplyChange(ApplyChangesKind.AddDocument); + var notificationService = solution.Services.GetRequiredService(); var membersInType = selectedType.GetMembers(). WhereAsArray(MemberAndDestinationValidator.IsMemberValid); @@ -85,7 +88,8 @@ internal sealed class VisualStudioExtractClassOptionsService( document.Project.Language, generatedNameTypeParameterSuffix, [.. conflictingTypeNames], - document.GetRequiredLanguageService()); + document.GetRequiredLanguageService(), + canAddDocument); var dialog = new ExtractClassDialog(viewModel); diff --git a/src/VisualStudio/Core/Def/ExtractInterface/ExtractInterfaceDialogViewModel.cs b/src/VisualStudio/Core/Def/ExtractInterface/ExtractInterfaceDialogViewModel.cs index 91381cfbbead3..3e9883fa74a87 100644 --- a/src/VisualStudio/Core/Def/ExtractInterface/ExtractInterfaceDialogViewModel.cs +++ b/src/VisualStudio/Core/Def/ExtractInterface/ExtractInterfaceDialogViewModel.cs @@ -31,7 +31,8 @@ internal ExtractInterfaceDialogViewModel( ImmutableArray memberViewModels, string defaultNamespace, string generatedNameTypeParameterSuffix, - string languageName) + string languageName, + bool canAddDocument) { _notificationService = notificationService; @@ -49,7 +50,8 @@ internal ExtractInterfaceDialogViewModel( defaultNamespace, generatedNameTypeParameterSuffix, [.. conflictingTypeNames], - syntaxFactsService); + syntaxFactsService, + canAddDocument); } internal bool TrySubmit() diff --git a/src/VisualStudio/Core/Def/ExtractInterface/VisualStudioExtractInterfaceOptionsService.cs b/src/VisualStudio/Core/Def/ExtractInterface/VisualStudioExtractInterfaceOptionsService.cs index 5af92736f82e8..a5520a85ea979 100644 --- a/src/VisualStudio/Core/Def/ExtractInterface/VisualStudioExtractInterfaceOptionsService.cs +++ b/src/VisualStudio/Core/Def/ExtractInterface/VisualStudioExtractInterfaceOptionsService.cs @@ -2,22 +2,19 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Composition; using System.Linq; using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ExtractInterface; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Notification; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.LanguageServices.Implementation.CommonControls; using Microsoft.VisualStudio.LanguageServices.Utilities; @@ -36,19 +33,18 @@ internal sealed class VisualStudioExtractInterfaceOptionsService(IGlyphService g private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor = uiThreadOperationExecutor; public ExtractInterfaceOptionsResult GetExtractInterfaceOptions( - ISyntaxFactsService syntaxFactsService, - INotificationService notificationService, + Document document, List extractableMembers, string defaultInterfaceName, List allTypeNames, string defaultNamespace, string generatedNameTypeParameterSuffix, - string languageName, CancellationToken cancellationToken) { _threadingContext.ThrowIfNotOnUIThread(); - - using var cancellationTokenSource = new CancellationTokenSource(); + var solution = document.Project.Solution; + var canAddDocument = solution.CanApplyChange(ApplyChangesKind.AddDocument); + var notificationService = solution.Services.GetRequiredService(); var memberViewModels = extractableMembers .SelectAsArray(member => @@ -61,7 +57,7 @@ public ExtractInterfaceOptionsResult GetExtractInterfaceOptions( }); var viewModel = new ExtractInterfaceDialogViewModel( - syntaxFactsService, + document.GetRequiredLanguageService(), _uiThreadOperationExecutor, notificationService, defaultInterfaceName, @@ -69,7 +65,8 @@ public ExtractInterfaceOptionsResult GetExtractInterfaceOptions( memberViewModels, defaultNamespace, generatedNameTypeParameterSuffix, - languageName); + document.Project.Language, + canAddDocument); var dialog = new ExtractInterfaceDialog(viewModel); var result = dialog.ShowModal(); diff --git a/src/VisualStudio/Core/Test/CommonControls/NewTypeDestinationSelectionViewModelTests.vb b/src/VisualStudio/Core/Test/CommonControls/NewTypeDestinationSelectionViewModelTests.vb index 75cb7a4fbc635..f454824384377 100644 --- a/src/VisualStudio/Core/Test/CommonControls/NewTypeDestinationSelectionViewModelTests.vb +++ b/src/VisualStudio/Core/Test/CommonControls/NewTypeDestinationSelectionViewModelTests.vb @@ -243,7 +243,8 @@ class $$MyClass languageName:=languageName, generatedNameTypeParameterSuffix:=generatedNameTypeParameterSuffix, conflictingNames:=symbol.ContainingNamespace.GetAllTypes(CancellationToken.None).SelectAsArray(Function(t) t.Name), - syntaxFactsService:=workspaceDoc.GetRequiredLanguageService(Of ISyntaxFactsService)) + syntaxFactsService:=workspaceDoc.GetRequiredLanguageService(Of ISyntaxFactsService), + canAddDocument:=True) End Using End Function End Class diff --git a/src/VisualStudio/Core/Test/ExtractInterface/ExtractInterfaceViewModelTests.vb b/src/VisualStudio/Core/Test/ExtractInterface/ExtractInterfaceViewModelTests.vb index 77844efadc7b6..02da64ac88ed5 100644 --- a/src/VisualStudio/Core/Test/ExtractInterface/ExtractInterfaceViewModelTests.vb +++ b/src/VisualStudio/Core/Test/ExtractInterface/ExtractInterfaceViewModelTests.vb @@ -313,7 +313,8 @@ public class $$MyClass memberViewModels:=memberViewModels.ToImmutableArray(), defaultNamespace:=defaultNamespace, generatedNameTypeParameterSuffix:=generatedNameTypeParameterSuffix, - languageName:=doc.Project.Language) + languageName:=doc.Project.Language, + canAddDocument:=True) End Using End Function End Class