File: StackTraceExplorer\StackTraceExplorerToolWindow.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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.EmbeddedLanguages.StackFrame;
using Microsoft.CodeAnalysis.StackTraceExplorer;
using Microsoft.VisualStudio.LanguageServices.Setup;
using Microsoft.VisualStudio.LanguageServices.Utilities;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Utilities;
using Roslyn.Utilities;
using static Microsoft.VisualStudio.VSConstants;
 
namespace Microsoft.VisualStudio.LanguageServices.StackTraceExplorer;
 
[Guid(Guids.StackTraceExplorerToolWindowIdString)]
internal class StackTraceExplorerToolWindow : ToolWindowPane, IOleCommandTarget
{
    private bool _initialized;
 
    [MemberNotNullWhen(true, nameof(_initialized))]
    public StackTraceExplorerRoot? Root { get; private set; }
 
    public StackTraceExplorerToolWindow() : base(null)
    {
        Caption = ServicesVSResources.Stack_Trace_Explorer;
        var dockPanel = new DockPanel
        {
            LastChildFill = true
        };
 
        dockPanel.CommandBindings.Add(new CommandBinding(ApplicationCommands.Paste, (s, e) =>
        {
            Root?.ViewModel.DoPasteAsync(default).FileAndForget("StackTraceExplorerPaste");
        }));
 
        Content = dockPanel;
    }
 
    /// <summary>
    /// Checks the contents of the clipboard for a valid stack trace and 
    /// opens stack trace explorer if anything parses correctly
    /// </summary>
    public async Task<bool> ShouldShowOnActivatedAsync(CancellationToken cancellationToken)
    {
        if (Root is null)
        {
            return false;
        }
 
        var text = ClipboardHelpers.GetTextNoRetry();
        if (RoslynString.IsNullOrEmpty(text))
        {
            return false;
        }
 
        if (Root.ViewModel.ContainsTab(text))
        {
            return false;
        }
 
        var result = await StackTraceAnalyzer.AnalyzeAsync(text, cancellationToken).ConfigureAwait(false);
        if (result.ParsedFrames.Any(static frame => FrameTriggersActivate(frame)))
        {
            await Root.ViewModel.AddNewTabAsync(result, text, cancellationToken).ConfigureAwait(false);
            return true;
        }
 
        return false;
    }
 
    private static bool FrameTriggersActivate(ParsedFrame frame)
    {
        if (frame is not ParsedStackFrame parsedFrame)
        {
            return false;
        }
 
        var methodDeclaration = parsedFrame.Root.MethodDeclaration;
 
        // Find the first token
        var firstNodeOrToken = methodDeclaration.ChildAt(0);
        while (firstNodeOrToken.IsNode)
        {
            firstNodeOrToken = firstNodeOrToken.Node.ChildAt(0);
        }
 
        if (firstNodeOrToken.Token.LeadingTrivia.IsDefault)
        {
            return false;
        }
 
        // If the stack frame starts with "at" we consider it a well formed stack frame and 
        // want to automatically open the window. This helps avoids some false positive cases 
        // where the window shows on code that parses as a stack frame but may not be. The explorer
        // should still handle those cases if explicitly pasted in, but can lead to false positives 
        // when automatically opening.
        return firstNodeOrToken.Token.LeadingTrivia.Any(static t => t.Kind == StackFrameKind.AtTrivia);
    }
 
    public void InitializeIfNeeded(RoslynPackage roslynPackage)
    {
        if (_initialized)
        {
            return;
        }
 
        var workspace = roslynPackage.ComponentModel.GetService<VisualStudioWorkspace>();
        var formatMapService = roslynPackage.ComponentModel.GetService<IClassificationFormatMapService>();
        var formatMap = formatMapService.GetClassificationFormatMap(StandardContentTypeNames.Text);
        var typeMap = roslynPackage.ComponentModel.GetService<ClassificationTypeMap>();
        var threadingContext = roslynPackage.ComponentModel.GetService<IThreadingContext>();
        var themingService = roslynPackage.ComponentModel.GetService<IWpfThemeService>();
 
        Root = new StackTraceExplorerRoot(new StackTraceExplorerRootViewModel(threadingContext, workspace, formatMap, typeMap))
        {
            HorizontalAlignment = HorizontalAlignment.Stretch,
            VerticalAlignment = VerticalAlignment.Stretch
        };
 
        var contentRoot = (DockPanel)Content;
        themingService?.ApplyThemeToElement(contentRoot);
        contentRoot.Children.Add(Root);
 
        contentRoot.MouseRightButtonUp += (s, e) =>
        {
            var uiShell = roslynPackage.GetServiceOnMainThread<SVsUIShell, IVsUIShell>();
            var relativePoint = e.GetPosition(contentRoot);
            var screenPosition = contentRoot.PointToScreen(relativePoint);
 
            var points = new[] {
                new POINTS()
                {
                    x = (short)screenPosition.X,
                    y = (short)screenPosition.Y
                }
            };
 
            var refCommandId = new Guid(Guids.StackTraceExplorerCommandIdString);
            var result = uiShell.ShowContextMenu(0, ref refCommandId, 0x0300, points, null);
            Debug.Assert(result == S_OK);
        };
 
        _initialized = true;
    }
 
    public override void OnToolWindowCreated()
    {
        // Hide the frame by default when VS starts
        if (Frame is IVsWindowFrame windowFrame)
        {
            windowFrame.CloseFrame((uint)__FRAMECLOSE.FRAMECLOSE_NoSave);
        }
    }
 
    int IOleCommandTarget.Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
    {
        if (pguidCmdGroup == GUID_VSStandardCommandSet97)
        {
            var command = (VSStd97CmdID)nCmdID;
            switch (command)
            {
                case VSStd97CmdID.Paste:
                    Root?.ViewModel.DoPasteSynchronously(default);
                    return S_OK;
            }
        }
 
        // Return OLECMDERR_E_UNKNOWNGROUP if we don't handle the command
        // see https://docs.microsoft.com/en-us/windows/win32/api/docobj/nf-docobj-iolecommandtarget-exec#return-value
        return -2147221244;
    }
}