File: StackTraceExplorer\StackTraceExplorerCommandHandler.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices.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.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.StackTraceExplorer;
using Microsoft.VisualStudio.LanguageServices.Setup;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Roslyn.Utilities;
 
namespace Microsoft.VisualStudio.LanguageServices.StackTraceExplorer;
 
[Export(typeof(StackTraceExplorerCommandHandler))]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class StackTraceExplorerCommandHandler(
    IThreadingContext threadingContext,
    IGlobalOptionService globalOptions,
    [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) : IVsBroadcastMessageEvents, IDisposable
{
    private readonly IThreadingContext _threadingContext = threadingContext;
    private readonly IGlobalOptionService _globalOptions = globalOptions;
    private readonly IServiceProvider _serviceProvider = serviceProvider;
    private uint _vsShellBroadcastCookie;
    private bool _initialized;
 
    /// <summary>
    /// Called during solution load to ensure the handler is created and subscribes to broadcast
    /// messages if the "open on focus" option is enabled.
    /// </summary>
    internal async Task EnsureInitializedAsync(CancellationToken cancellationToken)
    {
        if (_initialized)
            return;
 
        _initialized = true;
 
        _globalOptions.AddOptionChangedHandler(this, GlobalOptionChanged);
 
        var enabled = _globalOptions.GetOption(StackTraceExplorerOptionsStorage.OpenOnFocus);
        if (enabled)
        {
            await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
            AdviseBroadcastMessages();
        }
    }
 
    public int OnBroadcastMessage(uint msg, IntPtr wParam, IntPtr lParam)
    {
        if (msg != 0x001C) // WM_ACTIVATEAPP
        {
            return VSConstants.S_OK;
        }
 
        // wParam contains a value indicated if the window was activated.
        // See https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-activateapp 
        // 1 = activate
        // 0 = deactivated
        if (wParam == IntPtr.Zero)
        {
            return VSConstants.S_OK;
        }
 
        _threadingContext.JoinableTaskFactory.RunAsync(async () =>
        {
            var window = await GetOrInitializeWindowAsync().ConfigureAwait(true);
            var shouldActivate = await window.ShouldShowOnActivatedAsync(default).ConfigureAwait(true);
 
            if (shouldActivate)
            {
                await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync();
                var windowFrame = (IVsWindowFrame)window.Frame;
                ErrorHandler.ThrowOnFailure(windowFrame.Show());
                Logger.Log(FunctionId.StackTraceToolWindow_ShowOnActivated, logLevel: LogLevel.Information);
            }
        });
 
        return VSConstants.S_OK;
    }
 
    public void Dispose()
    {
        UnadviseBroadcastMessages();
 
        if (_initialized)
            _globalOptions.RemoveOptionChangedHandler(this, GlobalOptionChanged);
    }
 
    private void AdviseBroadcastMessages()
    {
        if (_vsShellBroadcastCookie != 0)
        {
            return;
        }
 
        var vsShell = _serviceProvider.GetService(typeof(SVsShell)) as IVsShell;
        vsShell?.AdviseBroadcastMessages(this, out _vsShellBroadcastCookie);
    }
 
    private void UnadviseBroadcastMessages()
    {
        if (_vsShellBroadcastCookie != 0)
        {
            var vsShell = _serviceProvider.GetService(typeof(SVsShell)) as IVsShell;
            if (vsShell is not null)
            {
                vsShell.UnadviseBroadcastMessages(_vsShellBroadcastCookie);
                _vsShellBroadcastCookie = 0;
            }
        }
    }
 
    private void GlobalOptionChanged(object sender, object target, OptionChangedEventArgs e)
    {
        bool? enabled = null;
        foreach (var (key, newValue) in e.ChangedOptions)
        {
            if (key.Option.Equals(StackTraceExplorerOptionsStorage.OpenOnFocus))
            {
                Contract.ThrowIfNull(newValue);
                enabled = (bool)newValue;
            }
        }
 
        if (enabled.HasValue)
        {
            if (enabled.Value)
            {
                AdviseBroadcastMessages();
            }
            else
            {
                UnadviseBroadcastMessages();
            }
        }
    }
 
    internal void OnExecute(object sender, EventArgs e)
    {
        ThreadHelper.ThrowIfNotOnUIThread();
 
        var window = _threadingContext.JoinableTaskFactory.Run(() => GetOrInitializeWindowAsync());
 
        var windowFrame = (IVsWindowFrame)window.Frame;
        ErrorHandler.ThrowOnFailure(windowFrame.Show());
 
        // Paste current clipboard contents on showing
        // the window
        window.Root?.ViewModel.DoPasteSynchronously(default);
    }
 
    private async Task<StackTraceExplorerToolWindow> GetOrInitializeWindowAsync()
    {
        var package = await RoslynPackage.GetOrLoadAsync(_threadingContext, (IAsyncServiceProvider)_serviceProvider, _threadingContext.DisposalToken).ConfigureAwait(true);
        Contract.ThrowIfNull(package);
 
        // 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 exists it will be created.
        var window = package.FindToolWindow(typeof(StackTraceExplorerToolWindow), 0, true) as StackTraceExplorerToolWindow;
        if (window is not { Frame: not null })
        {
            throw new NotSupportedException("Cannot create tool window");
        }
 
        window.InitializeIfNeeded(package);
 
        return window;
    }
 
    internal void OnPaste(object sender, EventArgs e)
    {
        var window = _threadingContext.JoinableTaskFactory.Run(() => GetOrInitializeWindowAsync());
        window.Root?.ViewModel?.DoPasteSynchronously(default);
    }
 
    internal void OnClear(object sender, EventArgs e)
    {
        var window = _threadingContext.JoinableTaskFactory.Run(() => GetOrInitializeWindowAsync());
        window.Root?.OnClear();
    }
}