File: SyncNamespaces\SyncNamespacesCommandHandler.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_pxr0p0dn_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices)
// 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.Immutable;
using System.ComponentModel.Design;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Progress;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.SyncNamespaces;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities;
using Microsoft.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Utilities;
using Roslyn.Utilities;
 
namespace Microsoft.VisualStudio.LanguageServices.Implementation.SyncNamespaces;
 
[Export(typeof(SyncNamespacesCommandHandler)), Shared]
internal sealed class SyncNamespacesCommandHandler
{
    private readonly VisualStudioWorkspace _workspace;
    private readonly IUIThreadOperationExecutor _threadOperationExecutor;
    private readonly IThreadingContext _threadingContext;
    private IServiceProvider? _serviceProvider;
 
    [ImportingConstructor]
    [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
    public SyncNamespacesCommandHandler(
        IUIThreadOperationExecutor threadOperationExecutor,
        VisualStudioWorkspace workspace,
        IThreadingContext threadingContext)
    {
        _threadOperationExecutor = threadOperationExecutor;
        _workspace = workspace;
        _threadingContext = threadingContext;
    }
 
    public async Task InitializeAsync(IAsyncServiceProvider serviceProvider, CancellationToken cancellationToken)
    {
        Contract.ThrowIfNull(serviceProvider);
 
        _serviceProvider = (IServiceProvider)serviceProvider;
 
        // Hook up the "Remove Unused References" menu command for CPS based managed projects.
        var menuCommandService = await serviceProvider.GetServiceAsync<IMenuCommandService, IMenuCommandService>(_threadingContext.JoinableTaskFactory, throwOnFailure: false).ConfigureAwait(false);
        if (menuCommandService != null)
        {
            await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
            VisualStudioCommandHandlerHelpers.AddCommand(menuCommandService, ID.RoslynCommands.SyncNamespaces, Guids.RoslynGroupId, OnSyncNamespacesForSelectedProject, OnSyncNamespacesForSelectedProjectStatus);
        }
    }
 
    private void OnSyncNamespacesForSelectedProjectStatus(object sender, EventArgs e)
    {
        var command = (OleMenuCommand)sender;
 
        var visible = false;
 
        if (VisualStudioCommandHandlerHelpers.TryGetSelectedProjectHierarchy(_serviceProvider, out var projectHierarchy))
        {
            // Is a project node. Are we C# project node?
            visible = projectHierarchy.IsCapabilityMatch(".NET & CSharp");
        }
        else
        {
            // Is a solution node. Do we contain any C# projects?
            visible = _workspace.CurrentSolution.Projects
                .Any(project => project.Language.Equals(LanguageNames.CSharp, StringComparison.OrdinalIgnoreCase));
        }
 
        var enabled = visible && !VisualStudioCommandHandlerHelpers.IsBuildActive();
 
        if (command.Visible != visible)
        {
            command.Visible = visible;
        }
 
        if (command.Enabled != enabled)
        {
            command.Enabled = enabled;
        }
    }
 
    private void OnSyncNamespacesForSelectedProject(object sender, EventArgs args)
    {
        if (VisualStudioCommandHandlerHelpers.TryGetSelectedProjectHierarchy(_serviceProvider, out var projectHierarchy))
        {
            // The project node is selected, so get projects that this node represents.
            var projects = GetProjectsForHierarchy(projectHierarchy);
 
            SyncNamespaces(projects);
        }
        else
        {
            // The solution node is selected, so collect all the C# projects for update.
            var projects = _workspace.CurrentSolution.Projects
                .Where(project => project.Language.Equals(LanguageNames.CSharp, StringComparison.OrdinalIgnoreCase))
                .ToImmutableArray();
 
            SyncNamespaces(projects);
        }
    }
 
    private ImmutableArray<Project> GetProjectsForHierarchy(Shell.Interop.IVsHierarchy projectHierarchy)
    {
        var projectFilePath = projectHierarchy.TryGetProjectFilePath();
 
        var solution = _workspace.CurrentSolution;
        return solution.Projects
            .Where(project => project.FilePath?.Equals(projectFilePath, StringComparison.OrdinalIgnoreCase) == true)
            .ToImmutableArrayOrEmpty();
    }
 
    private void SyncNamespaces(ImmutableArray<Project> projects)
    {
        if (projects.IsEmpty)
        {
            return;
        }
 
        var syncService = projects[0].GetRequiredLanguageService<ISyncNamespacesService>();
 
        Solution? solution = null;
        var status = _threadOperationExecutor.Execute(
            ServicesVSResources.Sync_Namespaces, ServicesVSResources.Updating_namspaces, allowCancellation: true, showProgress: true,
            operationContext =>
            {
                solution = _threadingContext.JoinableTaskFactory.Run(
                    () => syncService.SyncNamespacesAsync(projects, operationContext.GetCodeAnalysisProgress(), operationContext.UserCancellationToken));
            });
 
        if (status != UIThreadOperationStatus.Canceled && solution is not null)
        {
            if (_workspace.CurrentSolution.GetChanges(solution).GetProjectChanges().Any())
            {
                var previewChangeService = _workspace.Services.GetRequiredService<IPreviewDialogService>();
                var newSolution = previewChangeService.PreviewChanges(
                    title: EditorFeaturesResources.Preview_Changes,
                    helpString: "vs.csharp.refactoring.preview",
                    description: ServicesVSResources.Sync_Namespaces,
                    topLevelName: ServicesVSResources.Sync_namespaces_changes,
                    topLevelGlyph: Glyph.OpenFolder,
                    newSolution: solution,
                    oldSolution: _workspace.CurrentSolution,
                    showCheckBoxes: false);
 
                // If user clicks cancel, this would be null
                if (newSolution != null)
                {
                    _workspace.TryApplyChanges(newSolution);
                }
            }
            else
            {
                MessageDialog.Show(ServicesVSResources.Sync_Namespaces, ServicesVSResources.No_namespaces_needed_updating, MessageDialogCommandSet.Ok);
            }
        }
    }
}