File: OrganizeUsingsCommand.cs
Web Access
Project: src\src\Razor\src\Razor\src\Microsoft.VisualStudio.LanguageServices.Razor\Microsoft.VisualStudio.LanguageServices.Razor.csproj (Microsoft.VisualStudio.LanguageServices.Razor)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor.Remote;
using Microsoft.CodeAnalysis.Text;
using ExternalHandlers = Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers;
 
namespace Microsoft.VisualStudio.Razor;
 
[Export(typeof(IInterceptedCommand))]
[method: ImportingConstructor]
internal sealed class OrganizeUsingsCommand(IRemoteServiceInvoker remoteServiceInvoker) : IInterceptedCommand
{
    // Roslyn C# command group (guidCSharpGrpId) — used for "Remove and Sort Usings"
    private static readonly Guid s_cSharpGroupGuid = new("5d7e7f65-a63f-46ee-84f1-990b2cab23f9");
    private const uint SortUsingsCommandId = 0x1922;                  // cmdidCSharpOrganizeSortUsings (edit menu)
    private const uint RemoveAndSortUsingsCommandId = 0x1923;         // cmdidCSharpOrganizeRemoveAndSort (edit menu)
    private const uint ContextRemoveAndSortUsingsCommandId = 0x1913;  // cmdidContextOrganizeRemoveAndSort (context menu)
 
    private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker;
 
    public bool QueryStatus(Guid pguidCmdGroup, uint nCmdID)
    {
        return pguidCmdGroup == s_cSharpGroupGuid &&
            (nCmdID == RemoveAndSortUsingsCommandId ||
             nCmdID == ContextRemoveAndSortUsingsCommandId ||
             nCmdID == SortUsingsCommandId);
    }
 
    public Task<ImmutableArray<TextChange>> ExecuteAsync(
        Solution solution,
        DocumentId documentId,
        uint nCmdID,
        CancellationToken cancellationToken)
    {
        // Sort usings is a bit simpler, so we can just execute it directly
        if (nCmdID == SortUsingsCommandId)
        {
            return _remoteServiceInvoker.TryInvokeAsync<IRemoteRemoveAndSortUsingsService, ImmutableArray<TextChange>>(
                    solution,
                    (service, solutionInfo, ct) => service.GetSortUsingsEditsAsync(solutionInfo, documentId, ct),
                    cancellationToken).AsTask();
        }
 
        return ExecuteRemoveAndSortUsingsAsync(solution, documentId, cancellationToken);
    }
 
    private async Task<ImmutableArray<TextChange>> ExecuteRemoveAndSortUsingsAsync(Solution solution, DocumentId documentId, CancellationToken cancellationToken)
    {
        // To ensure our unused usings cache is populated, we first ask Roslyn for diagnostics, to get IDE005_gen diagnostics,
        // then we request our own diagnostics, to have it filter Roslyn's diagnostics, and make sure our cache is up to date.
        var razorDocument = solution.GetAdditionalDocument(documentId);
        if (razorDocument is null)
        {
            return [];
        }
 
        var generatedDocument = await razorDocument.Project.TryGetSourceGeneratedDocumentForRazorDocumentAsync(razorDocument, cancellationToken).ConfigureAwait(false);
        if (generatedDocument is null)
        {
            return [];
        }
 
        // C# diagnostics
        var csharpDiagnostics = await ExternalHandlers.Diagnostics.GetDocumentDiagnosticsAsync(generatedDocument, supportsVisualStudioExtensions: true, cancellationToken).ConfigureAwait(false);
 
        // Razor diagnostics (to filter and hydrate cache)
        await _remoteServiceInvoker.TryInvokeAsync<IRemoteDiagnosticsService, ImmutableArray<LspDiagnostic>>(
            razorDocument.Project.Solution,
            (service, solutionInfo, cancellationToken) => service.GetDiagnosticsAsync(solutionInfo, razorDocument.Id, [.. csharpDiagnostics], htmlDiagnostics: [], cancellationToken),
            cancellationToken).ConfigureAwait(false);
 
        // Now do the remove and sort
        return await _remoteServiceInvoker.TryInvokeAsync<IRemoteRemoveAndSortUsingsService, ImmutableArray<TextChange>>(
            solution,
            (service, solutionInfo, ct) => service.GetRemoveAndSortUsingsEditsAsync(solutionInfo, documentId, ct),
            cancellationToken).ConfigureAwait(false);
    }
 
    internal TestAccessor GetTestAccessor() => new(this);
 
    internal class TestAccessor(OrganizeUsingsCommand command)
    {
        public bool QueryRemoveAndSortUsings()
            => command.QueryStatus(s_cSharpGroupGuid, RemoveAndSortUsingsCommandId);
 
        public bool QuerySortUsings()
            => command.QueryStatus(s_cSharpGroupGuid, SortUsingsCommandId);
 
        public Task<ImmutableArray<TextChange>> ExecuteRemoveAndSortUsingsAsync(Solution solution, DocumentId documentId, CancellationToken cancellationToken)
            => command.ExecuteAsync(solution, documentId, RemoveAndSortUsingsCommandId, cancellationToken);
 
        public Task<ImmutableArray<TextChange>> ExecuteSortUsingsAsync(Solution solution, DocumentId documentId, CancellationToken cancellationToken)
            => command.ExecuteAsync(solution, documentId, SortUsingsCommandId, cancellationToken);
    }
}