File: SolutionEventMonitor.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.Collections.Generic;
using Microsoft.CodeAnalysis.Notification;
using Microsoft.VisualStudio.Shell;
using Roslyn.Utilities;
 
namespace Microsoft.VisualStudio.LanguageServices;
 
/// <summary>
/// Monitors Visual Studio's UIContext for Solution building/opening/closing and notifies the GlobalOperationService.
/// The intent is to suspend analysis of non-essential files for the duration of these solution operations.
/// </summary>
internal sealed class SolutionEventMonitor : IDisposable
{
    private const string SolutionBuilding = "Solution Building";
    private const string SolutionOpening = "Solution Opening";
    private const string SolutionClosing = "Solution Closing";
 
    private readonly UIContext _solutionClosingContext = UIContext.FromUIContextGuid(VSConstants.UICONTEXT.SolutionClosing_guid);
    private readonly IGlobalOperationNotificationService _notificationService;
    private readonly Dictionary<string, IDisposable> _operations = [];
 
    public SolutionEventMonitor(IGlobalOperationNotificationService notificationService)
    {
        Contract.ThrowIfNull(notificationService);
        _notificationService = notificationService;
 
        RegisterEventHandler(KnownUIContexts.SolutionBuildingContext, SolutionBuildingContextChanged);
        RegisterEventHandler(KnownUIContexts.SolutionOpeningContext, SolutionOpeningContextChanged);
        RegisterEventHandler(_solutionClosingContext, SolutionClosingContextChanged);
 
        static void RegisterEventHandler(UIContext context, EventHandler<UIContextChangedEventArgs> handler)
        {
            // make sure we set initial state correctly. otherwise, we can get into a race where we might miss the very first events
            if (context.IsActive)
                handler(sender: null, UIContextChangedEventArgs.From(activated: true));
 
            context.UIContextChanged += handler;
        }
    }
 
    public void Dispose()
    {
        foreach (var globalOperation in _operations.Values)
            globalOperation.Dispose();
 
        _operations.Clear();
 
        KnownUIContexts.SolutionBuildingContext.UIContextChanged -= SolutionBuildingContextChanged;
        KnownUIContexts.SolutionOpeningContext.UIContextChanged -= SolutionOpeningContextChanged;
        _solutionClosingContext.UIContextChanged -= SolutionClosingContextChanged;
    }
 
    private void SolutionBuildingContextChanged(object? sender, UIContextChangedEventArgs e)
        => ContextChanged(e.Activated, SolutionBuilding);
 
    private void SolutionOpeningContextChanged(object? sender, UIContextChangedEventArgs e)
        => ContextChanged(e.Activated, SolutionOpening);
 
    private void SolutionClosingContextChanged(object? sender, UIContextChangedEventArgs e)
        => ContextChanged(e.Activated, SolutionClosing);
 
    private void ContextChanged(bool active, string operation)
    {
        TryCancelPendingNotification(operation);
 
        if (active)
            _operations[operation] = _notificationService.Start(operation);
    }
 
    private void TryCancelPendingNotification(string operation)
    {
        if (_operations.TryGetValue(operation, out var globalOperation))
        {
            globalOperation.Dispose();
            _operations.Remove(operation);
        }
    }
}