File: SolutionExplorer\SymbolTree\SymbolItemContextMenuController.cs
Web Access
Project: src\src\VisualStudio\Core\Impl\Microsoft.VisualStudio.LanguageServices.Implementation.csproj (Microsoft.VisualStudio.LanguageServices.Implementation)
// 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.Generic;
using System.Linq;
using System.Windows;
using Microsoft.CodeAnalysis;
using Microsoft.Internal.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.ProjectSystem.VS;
using Microsoft.VisualStudio.Shell.Interop;
 
namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer;
 
internal sealed partial class RootSymbolTreeItemSourceProvider
{
    private sealed class SymbolItemContextMenuController(
        RootSymbolTreeItemSourceProvider rootProvider) : IContextMenuController
    {
        public bool ShowContextMenu(IEnumerable<object> items, Point location)
        {
            if (items.FirstOrDefault() is not SymbolTreeItem item)
                return false;
 
            var guidContextMenu = Guids.RoslynGroupId;
            if (Shell.Package.GetGlobalService(typeof(SVsUIShell)) is not IVsUIShell shell)
                return false;
 
            var result = shell.ShowContextMenu(
                dwCompRole: 0,
                ref guidContextMenu,
                //0x400,
                ID.RoslynCommands.SolutionExplorerSymbolItemContextMenu,
                [new() { x = (short)location.X, y = (short)location.Y }],
                pCmdTrgtActive: new SymbolTreeItemOleCommandTarget(rootProvider, item));
            return ErrorHandler.Succeeded(result);
        }
 
        private sealed class SymbolTreeItemOleCommandTarget(
            RootSymbolTreeItemSourceProvider rootProvider,
            SymbolTreeItem item) : IOleCommandTarget
        {
            public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
            {
                // All commands are available on all items currently.  Note: we could refine this if we want to.
                // For example "go to base/derived" doesn't really make sense on fields.  For now though, we just
                // keep things simple.  This is similar to how you always get all these nav options in the editor
                // when you right click on any location in a C# file.
                return HResult.OK;
            }
 
            public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
            {
                var navigationService = nCmdID switch
                {
                    ID.RoslynCommands.SolutionExplorerSymbolItemGoToBase => rootProvider._goToBaseNavigationService,
                    ID.RoslynCommands.SolutionExplorerSymbolItemGoToImplementation => rootProvider._goToImplementationNavigationService,
                    ID.RoslynCommands.SolutionExplorerSymbolItemFindAllReferences => rootProvider._findReferencesNavigationService,
                    _ => null,
                };
 
                if (navigationService != null)
                {
                    var document = rootProvider._workspace.CurrentSolution.GetDocument(item.ItemKey.DocumentId);
                    if (document != null)
                    {
                        navigationService.ExecuteCommand(
                            document, item.ItemSyntax.NavigationToken.SpanStart,
                            // May be calling this on stale data.  Allow the position to be invalid
                            allowInvalidPosition: true);
                    }
                }
 
                return HResult.OK;
            }
        }
    }
}