File: RazorPackage.cs
Web Access
Project: src\src\Razor\src\Razor\src\Microsoft.VisualStudio.RazorExtension\Microsoft.VisualStudio.RazorExtension_r1ze3jzg_wpftmp.csproj (Microsoft.VisualStudio.RazorExtension)
// 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.ComponentModel.Design;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor;
using Microsoft.CodeAnalysis.Razor.NestedFiles;
using Microsoft.CodeAnalysis.Razor.Workspaces;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.Razor;
using Microsoft.VisualStudio.Razor.Debugging;
using Microsoft.VisualStudio.Razor.LanguageClient.Options;
using Microsoft.VisualStudio.Razor.Logging;
using Microsoft.VisualStudio.Razor.ProjectSystem;
using Microsoft.VisualStudio.Razor.Snippets;
using Microsoft.VisualStudio.RazorExtension.NestedFiles;
using Microsoft.VisualStudio.RazorExtension.Snippets;
using Microsoft.VisualStudio.RazorExtension.SyntaxVisualizer;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Threading;
using Microsoft.VisualStudio.Utilities;
using Task = System.Threading.Tasks.Task;
 
namespace Microsoft.VisualStudio.RazorExtension;
 
[Guid(PackageGuidString)]
internal sealed class RazorPackage : AsyncPackage
{
    public const string PackageGuidString = "13b72f58-279e-49e0-a56d-296be02f0805";
 
    internal const string GuidSyntaxVisualizerMenuCmdSetString = "a3a603a2-2b17-4ce2-bd21-cbb8ccc084ec";
    internal static readonly Guid GuidSyntaxVisualizerMenuCmdSet = new Guid(GuidSyntaxVisualizerMenuCmdSetString);
    internal const uint CmdIDRazorSyntaxVisualizer = 0x101;
 
    // Razor nested files command set
    internal const string GuidRazorNestedFilesCmdSetString = "8B2B3C5D-6E4A-4F9B-9C8D-1A2B3C4D5E6F";
    internal static readonly Guid GuidRazorNestedFilesCmdSet = new Guid(GuidRazorNestedFilesCmdSetString);
 
    internal const uint CmdIdAddOrViewNestedCsFile = 0x0100;
    internal const uint CmdIdAddOrViewNestedCssFile = 0x0101;
    internal const uint CmdIdAddOrViewNestedJsFile = 0x0102;
    internal const uint CmdIdViewPageEditor = 0x0203;
    internal const uint CmdIdAddNestedCsFileEditor = 0x0204;
 
    // UI Context for when a .razor or .cshtml file is selected
    internal const string GuidRazorFileContextString = "7C3F2F9E-8D4A-4B6C-9E1F-5A8D7C6B3E2D";
    internal static readonly Guid GuidRazorFileContext = new Guid(GuidRazorFileContextString);
 
    private OptionsStorage? _optionsStorage = null;
 
    protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
    {
        await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
 
        cancellationToken.ThrowIfCancellationRequested();
 
        var container = this as IServiceContainer;
        container.AddService(typeof(RazorLanguageService), (container, type) =>
        {
            var componentModel = (IComponentModel)GetGlobalService(typeof(SComponentModel));
            var breakpointResolver = componentModel.GetService<IRazorBreakpointResolver>();
            var proximityExpressionResolver = componentModel.GetService<IRazorProximityExpressionResolver>();
            var uiThreadOperationExecutor = componentModel.GetService<IUIThreadOperationExecutor>();
            var editorAdaptersFactory = componentModel.GetService<IVsEditorAdaptersFactoryService>();
            var lspServerActivationTracker = componentModel.GetService<ILspServerActivationTracker>();
            var joinableTaskContext = componentModel.GetService<JoinableTaskContext>();
 
            return new RazorLanguageService(breakpointResolver, proximityExpressionResolver, lspServerActivationTracker, uiThreadOperationExecutor, editorAdaptersFactory, joinableTaskContext.Factory);
        }, promote: true);
 
        // Add our command handlers for menu (commands must exist in the .vsct file).
        if (await GetServiceAsync(typeof(IMenuCommandService)) is OleMenuCommandService mcs)
        {
            // Create the command for the tool window.
            var toolwndCommandID = new CommandID(GuidSyntaxVisualizerMenuCmdSet, (int)CmdIDRazorSyntaxVisualizer);
            var menuToolWin = new MenuCommand(ShowToolWindow, toolwndCommandID);
            mcs.AddCommand(menuToolWin);
 
            // Register nested file commands
            RegisterNestedFileCommands(mcs);
        }
 
        var componentModel = (IComponentModel)GetGlobalService(typeof(SComponentModel));
        _optionsStorage = componentModel.GetService<OptionsStorage>();
        CreateSnippetService(componentModel);
 
        // LogHub can be initialized off the UI thread
        await TaskScheduler.Default;
 
        var traceProvider = componentModel.GetService<RazorLogHubTraceProvider>();
        await traceProvider.InitializeTraceAsync("Razor", 1, cancellationToken);
    }
 
    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);
        _optionsStorage?.Dispose();
        _optionsStorage = null;
    }
 
    private SnippetService CreateSnippetService(IComponentModel componentModel)
    {
        var joinableTaskContext = componentModel.GetService<JoinableTaskContext>();
        var cache = componentModel.GetService<SnippetCache>();
        return new SnippetService(joinableTaskContext.Factory, this, cache, _optionsStorage.AssumeNotNull());
    }
 
    /// <summary>
    /// This function is called when the user clicks the menu item that shows the
    /// tool window. See the Initialize method to see how the menu item is associated to
    /// this function using the OleMenuCommandService service and the MenuCommand class.
    /// </summary>
    private void ShowToolWindow(object sender, EventArgs e)
    {
        ThreadHelper.ThrowIfNotOnUIThread();
 
        // Get the instance number 0 of this tool window. This window is single instance so this instance
        // is actually the only one. The last flag is set to true so that if the tool window does not exist
        // it will be created.
        var window = (SyntaxVisualizerToolWindow)FindToolWindow(typeof(SyntaxVisualizerToolWindow), id: 0, create: true);
        if (window?.Frame is not IVsWindowFrame windowFrame)
        {
            throw new NotSupportedException("Can not create window");
        }
 
        // Initialize command handlers in the window
        if (!window.CommandHandlersInitialized)
        {
            var mcs = (IMenuCommandService?)GetService(typeof(IMenuCommandService));
            if (mcs is not null)
            {
                window.InitializeCommands(mcs, GuidSyntaxVisualizerMenuCmdSet);
            }
        }
 
        ErrorHandler.ThrowOnFailure(windowFrame.Show());
    }
 
    /// <summary>
    /// Registers the nested file commands (CSS, C#, Javascript) in the menu command service
    /// for both Solution Explorer and editor context menus.
    /// </summary>
    private void RegisterNestedFileCommands(OleMenuCommandService mcs)
    {
        ThreadHelper.ThrowIfNotOnUIThread();
 
        var componentModel = (IComponentModel)GetGlobalService(typeof(SComponentModel));
        var requestInvoker = new Lazy<LSPRequestInvokerWrapper>(() => componentModel.GetService<LSPRequestInvokerWrapper>());
 
        // Add nested file commands
        AddMenuNestedFileCommand(".cs", NestedFileKind.CSharp, (int)CmdIdAddOrViewNestedCsFile, GuidRazorNestedFilesCmdSet, allowExternalHandlers: false, hideWhenFileExists: false);
        AddMenuNestedFileCommand(".css", NestedFileKind.Css, (int)CmdIdAddOrViewNestedCssFile, GuidRazorNestedFilesCmdSet, allowExternalHandlers: false, hideWhenFileExists: false);
        AddMenuNestedFileCommand(".js", NestedFileKind.JavaScript, (int)CmdIdAddOrViewNestedJsFile, GuidRazorNestedFilesCmdSet, allowExternalHandlers: false, hideWhenFileExists: false);
 
        // .cs View Code (Editor) — override standard ViewCode (F7) command so VS displays the F7
        // keybinding annotation. Only shows when .cs file exists; yields when missing so the
        // cmdidAddNestedCsFileEditor command (without F7) handles the "Add" case instead.
        // When not in a Razor file, sets Supported = false to fall through to default ViewCode.
        AddMenuNestedFileCommand(".cs", NestedFileKind.CSharp, (int)VSConstants.VSStd97CmdID.ViewCode, VSConstants.GUID_VSStandardCommandSet97, allowExternalHandlers: true, hideWhenFileExists: false);
 
        // .cs Add Code (Editor) — shows "Add .cs file" only when the .cs doesn't exist yet.
        // Uses a separate command ID from ViewCode so it doesn't display the F7 keybinding.
        AddMenuNestedFileCommand(".cs", NestedFileKind.CSharp, (int)CmdIdAddNestedCsFileEditor, GuidRazorNestedFilesCmdSet, allowExternalHandlers: false, hideWhenFileExists: true);
 
        // View Page Command (Editor) — appears in nested file editors (.cshtml.cs, etc.)
        var viewPageHandler = new ViewPageCommandHandler(this);
        AddMenuCommand((int)CmdIdViewPageEditor, GuidRazorNestedFilesCmdSet, viewPageHandler.Execute, viewPageHandler.OnBeforeQueryStatus);
 
        return;
 
        void AddMenuCommand(int cmdId, Guid cmdSet, EventHandler executeHandler, EventHandler queryStatusHandler)
        {
            var cmdID = new CommandID(cmdSet, cmdId);
            var command = new OleMenuCommand(executeHandler, cmdID);
            command.BeforeQueryStatus += queryStatusHandler;
            mcs.AddCommand(command);
        }
 
        void AddMenuNestedFileCommand(string fileExtension, NestedFileKind fileKind, int cmdId, Guid cmdSet, bool allowExternalHandlers, bool hideWhenFileExists)
        {
            var handler = new NestedFileCommandHandler(this, fileExtension, fileKind, requestInvoker, allowExternalHandlers, hideWhenFileExists);
            AddMenuCommand(cmdId, cmdSet, handler.Execute, handler.OnBeforeQueryStatus);
        }
    }
}