File: InProcess\EditorInProcess_Commands.cs
Web Access
Project: src\src\Razor\src\Razor\test\Microsoft.VisualStudio.Razor.IntegrationTests\Microsoft.VisualStudio.Razor.IntegrationTests.csproj (Microsoft.VisualStudio.Razor.IntegrationTests)
// 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.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Threading;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Razor.IntegrationTests.InProcess;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Threading;
using static Microsoft.VisualStudio.VSConstants;
 
namespace Microsoft.VisualStudio.Extensibility.Testing;
 
internal partial class EditorInProcess
{
    public async Task InvokeDeleteLineAsync(CancellationToken cancellationToken)
    {
        var commandGuid = typeof(VSStd2KCmdID).GUID;
        var commandId = VSStd2KCmdID.DELETELINE;
        await ExecuteCommandAsync(commandGuid, (uint)commandId, cancellationToken);
    }
 
    public async Task InvokeFormatDocumentAsync(CancellationToken cancellationToken)
    {
        var commandGuid = typeof(VSStd2KCmdID).GUID;
        var commandId = VSStd2KCmdID.FORMATDOCUMENT;
        await ExecuteCommandAsync(commandGuid, (uint)commandId, cancellationToken);
    }
 
    public async Task InvokeGoToDefinitionAsync(CancellationToken cancellationToken)
    {
        var commandGuid = typeof(VSStd97CmdID).GUID;
        var commandId = VSStd97CmdID.GotoDefn;
        await ExecuteCommandAsync(commandGuid, (uint)commandId, cancellationToken);
    }
 
    public async Task InvokeFindAllReferencesAsync(CancellationToken cancellationToken)
    {
        var commandGuid = typeof(VSStd97CmdID).GUID;
        var commandId = VSStd97CmdID.FindReferences;
        await ExecuteCommandAsync(commandGuid, (uint)commandId, cancellationToken);
    }
 
    public async Task InvokeGoToImplementationAsync(CancellationToken cancellationToken)
    {
        var commandGuid = typeof(VSStd97CmdID).GUID;
        var commandId = VSStd97CmdID.GotoDecl;
        await ExecuteCommandAsync(commandGuid, (uint)commandId, cancellationToken);
    }
 
    public async Task InvokeRenameAsync(CancellationToken cancellationToken)
    {
        var commandGuid = typeof(VSStd2KCmdID).GUID;
        var commandId = VSStd2KCmdID.RENAME;
        await ExecuteCommandAsync(commandGuid, (uint)commandId, cancellationToken);
    }
 
    public async Task CloseCodeFileAsync(string projectName, string relativeFilePath, bool saveFile, CancellationToken cancellationToken)
    {
        await CloseFileAsync(projectName, relativeFilePath, VSConstants.LOGVIEWID.Code_guid, saveFile, cancellationToken);
    }
 
    public async Task CloseCurrentlyFocusedWindowAsync(CancellationToken cancellationToken, bool save = false)
    {
        await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
 
        var monitorSelection = await GetRequiredGlobalServiceAsync<SVsShellMonitorSelection, IVsMonitorSelection>(cancellationToken);
        ErrorHandler.ThrowOnFailure(monitorSelection.GetCurrentElementValue((uint)VSSELELEMID.SEID_WindowFrame, out var windowFrameObj));
        var windowFrame = (IVsWindowFrame)windowFrameObj;
 
        var closeFlags = save
            ? __FRAMECLOSE.FRAMECLOSE_SaveIfDirty
            : __FRAMECLOSE.FRAMECLOSE_NoSave;
        ErrorHandler.ThrowOnFailure(windowFrame.CloseFrame((uint)closeFlags));
    }
 
    private async Task ExecuteCommandAsync(Guid commandGuid, uint commandId, CancellationToken cancellationToken)
    {
        await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
 
        var dispatcher = await TestServices.Shell.GetRequiredGlobalServiceAsync<SUIHostCommandDispatcher, IOleCommandTarget>(cancellationToken);
 
        // Before we execute the command, lets wait until it's enabled and available. Unfortunately this is an annoying COM pattern.
 
        // Set up the data for the API to fill in. We set command id, it sets the status in "cmdf"
        var cmds = new OLECMD[1];
        cmds[0].cmdID = commandId;
        cmds[0].cmdf = 0;
 
        await Helper.RetryAsync(ct =>
        {
            // The return value here is just whether the QueryStatus call worked, not whether the command is enabled.
            ErrorHandler.ThrowOnFailure(dispatcher.QueryStatus(ref commandGuid, 1, cmds, IntPtr.Zero));
 
            // Now check the status flags that were filled in for the command we asked about.
            var status = (OLECMDF)cmds[0].cmdf;
            if (status.HasFlag(OLECMDF.OLECMDF_ENABLED) &&
                status.HasFlag(OLECMDF.OLECMDF_SUPPORTED))
            {
                // Returning non-default from RetryAsync stops the retry loop.
                return SpecializedTasks.True;
            }
 
            // Returning default means it will try again.
            return SpecializedTasks.False;
        }, TimeSpan.FromMilliseconds(100), cancellationToken);
 
        // Now we can be reasonably sure the command is available, so execute it.
        ErrorHandler.ThrowOnFailure(dispatcher.Exec(commandGuid, commandId, (uint)OLECMDEXECOPT.OLECMDEXECOPT_DODEFAULT, IntPtr.Zero, IntPtr.Zero));
    }
 
    private async Task CloseFileAsync(string projectName, string relativeFilePath, Guid logicalView, bool saveFile, CancellationToken cancellationToken)
    {
        await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
 
        var filePath = await GetAbsolutePathForProjectRelativeFilePathAsync(projectName, relativeFilePath, cancellationToken);
        if (!VsShellUtilities.IsDocumentOpen(ServiceProvider.GlobalProvider, filePath, logicalView, out _, out _, out var windowFrame))
        {
            throw new InvalidOperationException($"File '{filePath}' is not open in logical view '{logicalView}'");
        }
 
        var frameClose = saveFile ? __FRAMECLOSE.FRAMECLOSE_SaveIfDirty : __FRAMECLOSE.FRAMECLOSE_NoSave;
        ErrorHandler.ThrowOnFailure(windowFrame.CloseFrame((uint)frameClose));
    }
 
    private async Task<string> GetAbsolutePathForProjectRelativeFilePathAsync(string projectName, string relativeFilePath, CancellationToken cancellationToken)
    {
        await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
 
        var dte = await GetRequiredGlobalServiceAsync<SDTE, EnvDTE.DTE>(cancellationToken);
        var solution = dte.Solution;
        Assumes.Present(solution);
 
        var project = solution.Projects.Cast<EnvDTE.Project>().First(x => x.Name == projectName);
        var projectPath = Path.GetDirectoryName(project.FullName);
        return Path.Combine(projectPath, relativeFilePath);
    }
}