File: RoslynPackage.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.Immutable;
using System.ComponentModel.Design;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ColorSchemes;
using Microsoft.CodeAnalysis.Common;
using Microsoft.CodeAnalysis.EditAndContinue;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Notification;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Remote.ProjectSystem;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.LanguageServices.EditorConfigSettings;
using Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics;
using Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.RuleSets;
using Microsoft.VisualStudio.LanguageServices.Implementation.Suppression;
using Microsoft.VisualStudio.LanguageServices.Implementation.SyncNamespaces;
using Microsoft.VisualStudio.LanguageServices.Implementation.TableDataSource;
using Microsoft.VisualStudio.LanguageServices.Implementation.UnusedReferences;
using Microsoft.VisualStudio.LanguageServices.InheritanceMargin;
using Microsoft.VisualStudio.LanguageServices.Options;
using Microsoft.VisualStudio.LanguageServices.ProjectSystem;
using Microsoft.VisualStudio.LanguageServices.ProjectSystem.BrokeredService;
using Microsoft.VisualStudio.LanguageServices.StackTraceExplorer;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell.ServiceBroker;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Threading;
using Roslyn.Utilities;
using Task = System.Threading.Tasks.Task;
 
namespace Microsoft.VisualStudio.LanguageServices.Setup;
 
[Guid(Guids.RoslynPackageIdString)]
 
// The option page configuration is duplicated in PackageRegistration.pkgdef
[ProvideToolWindow(typeof(ValueTracking.ValueTrackingToolWindow))]
[ProvideToolWindow(typeof(StackTraceExplorerToolWindow))]
internal sealed class RoslynPackage : AbstractPackage
{
    // The randomly-generated key name is used for serializing the Background Analysis Scope preference to the .SUO
    // file. It doesn't have any semantic meaning, but is intended to not conflict with any other extension that
    // might be saving an "AnalysisScope" named stream to the same file.
    // note: must be <= 31 characters long
    private const string BackgroundAnalysisScopeOptionKey = "AnalysisScope-DCE33A29A768";
    private const byte BackgroundAnalysisScopeOptionVersion = 1;
 
    private static RoslynPackage? _lazyInstance;
 
    private RuleSetEventHandler? _ruleSetEventHandler;
    private ColorSchemeApplier? _colorSchemeApplier;
    private IDisposable? _solutionEventMonitor;
 
    private BackgroundAnalysisScope? _analysisScope;
 
    public RoslynPackage()
    {
        // We need to register an option in order for OnLoadOptions/OnSaveOptions to be called
        AddOptionKey(BackgroundAnalysisScopeOptionKey);
    }
 
    public BackgroundAnalysisScope? AnalysisScope
    {
        get
        {
            return _analysisScope;
        }
 
        set
        {
            if (_analysisScope == value)
                return;
 
            _analysisScope = value;
            AnalysisScopeChanged?.Invoke(this, EventArgs.Empty);
        }
    }
 
    public event EventHandler? AnalysisScopeChanged;
 
    internal static async ValueTask<RoslynPackage?> GetOrLoadAsync(IThreadingContext threadingContext, IAsyncServiceProvider serviceProvider, CancellationToken cancellationToken)
    {
        if (_lazyInstance is null)
        {
            await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
 
            var shell = (IVsShell7?)await serviceProvider.GetServiceAsync(typeof(SVsShell)).ConfigureAwait(true);
            Assumes.Present(shell);
            await shell.LoadPackageAsync(typeof(RoslynPackage).GUID);
 
            if (ErrorHandler.Succeeded(((IVsShell)shell).IsPackageLoaded(typeof(RoslynPackage).GUID, out var package)))
            {
                _lazyInstance = (RoslynPackage)package;
            }
        }
 
        return _lazyInstance;
    }
 
    protected override void OnLoadOptions(string key, Stream stream)
    {
        if (key == BackgroundAnalysisScopeOptionKey)
        {
            if (stream.ReadByte() == BackgroundAnalysisScopeOptionVersion)
            {
                var hasValue = stream.ReadByte() == 1;
                AnalysisScope = hasValue ? (BackgroundAnalysisScope)stream.ReadByte() : null;
            }
            else
            {
                AnalysisScope = null;
            }
        }
 
        base.OnLoadOptions(key, stream);
    }
 
    protected override void OnSaveOptions(string key, Stream stream)
    {
        if (key == BackgroundAnalysisScopeOptionKey)
        {
            stream.WriteByte(BackgroundAnalysisScopeOptionVersion);
            stream.WriteByte(AnalysisScope.HasValue ? (byte)1 : (byte)0);
            stream.WriteByte((byte)AnalysisScope.GetValueOrDefault());
        }
 
        base.OnSaveOptions(key, stream);
    }
 
    protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
    {
        await base.InitializeAsync(cancellationToken, progress).ConfigureAwait(true);
 
        await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
 
        cancellationToken.ThrowIfCancellationRequested();
 
        // Ensure the options persisters are loaded since we have to fetch options from the shell
        LoadOptionPersistersAsync(this.ComponentModel, cancellationToken).Forget();
 
        await InitializeColorsAsync(cancellationToken).ConfigureAwait(true);
 
        // load some services that have to be loaded in UI thread
        LoadComponentsInUIContextOnceSolutionFullyLoadedAsync(cancellationToken).Forget();
 
        // We are at the VS layer, so we know we must be able to get the IGlobalOperationNotificationService here.
        var globalNotificationService = this.ComponentModel.GetService<IGlobalOperationNotificationService>();
        Assumes.Present(globalNotificationService);
 
        _solutionEventMonitor = new SolutionEventMonitor(globalNotificationService);
        TrackBulkFileOperations(globalNotificationService);
 
        var settingsEditorFactory = this.ComponentModel.GetService<SettingsEditorFactory>();
        RegisterEditorFactory(settingsEditorFactory);
 
        // Misc workspace has to be up and running by the time our package is usable so that it can track running
        // doc events and appropriately map files to/from it and other relevant workspaces (like the
        // metadata-as-source workspace).
        await this.ComponentModel.GetService<MiscellaneousFilesWorkspace>().InitializeAsync().ConfigureAwait(false);
 
        // Proffer in-process service broker services
        var serviceBrokerContainer = await this.GetServiceAsync<SVsBrokeredServiceContainer, IBrokeredServiceContainer>(this.JoinableTaskFactory).ConfigureAwait(false);
 
        serviceBrokerContainer.Proffer(
            WorkspaceProjectFactoryServiceDescriptor.ServiceDescriptor,
            (_, _, _, _) => ValueTaskFactory.FromResult<object?>(new WorkspaceProjectFactoryService(this.ComponentModel.GetService<IWorkspaceProjectContextFactory>())));
 
        // Must be profferred before any C#/VB projects are loaded and the corresponding UI context activated.
        serviceBrokerContainer.Proffer(
            ManagedHotReloadLanguageServiceDescriptor.Descriptor,
            (_, _, _, _) => ValueTaskFactory.FromResult<object?>(new ManagedEditAndContinueLanguageServiceBridge(this.ComponentModel.GetService<EditAndContinueLanguageService>())));
    }
 
    private async Task LoadOptionPersistersAsync(IComponentModel componentModel, CancellationToken cancellationToken)
    {
        var listenerProvider = componentModel.GetService<IAsynchronousOperationListenerProvider>();
        using var token = listenerProvider.GetListener(FeatureAttribute.Workspace).BeginAsyncOperation(nameof(LoadOptionPersistersAsync));
 
        // Switch to a background thread to ensure assembly loads don't show up as UI delays attributed to
        // InitializeAsync.
        await TaskScheduler.Default;
 
        var persisterProviders = componentModel.GetExtensions<IOptionPersisterProvider>().ToImmutableArray();
 
        foreach (var provider in persisterProviders)
        {
            var persister = await provider.GetOrCreatePersisterAsync(cancellationToken).ConfigureAwait(true);
 
            // Initialize the PackageSettingsPersister to allow it to listen to analysis scope changed
            // events from this package.
            if (persister is PackageSettingsPersister packageSettingsPersister)
                packageSettingsPersister.Initialize(this);
        }
    }
 
    private async Task InitializeColorsAsync(CancellationToken cancellationToken)
    {
        await TaskScheduler.Default;
        _colorSchemeApplier = ComponentModel.GetService<ColorSchemeApplier>();
        await _colorSchemeApplier.InitializeAsync(cancellationToken).ConfigureAwait(false);
    }
 
    protected override async Task LoadComponentsAsync(CancellationToken cancellationToken)
    {
        await TaskScheduler.Default;
 
        await GetServiceAsync(typeof(SVsErrorList)).ConfigureAwait(false);
        await GetServiceAsync(typeof(SVsSolution)).ConfigureAwait(false);
        await GetServiceAsync(typeof(SVsShell)).ConfigureAwait(false);
        await GetServiceAsync(typeof(SVsRunningDocumentTable)).ConfigureAwait(false);
        await GetServiceAsync(typeof(SVsTextManager)).ConfigureAwait(false);
 
        // we need to load it as early as possible since we can have errors from
        // package from each language very early
        await this.ComponentModel.GetService<VisualStudioSuppressionFixService>().InitializeAsync(this).ConfigureAwait(false);
        await this.ComponentModel.GetService<VisualStudioDiagnosticListSuppressionStateService>().InitializeAsync(this, cancellationToken).ConfigureAwait(false);
 
        await this.ComponentModel.GetService<IVisualStudioDiagnosticAnalyzerService>().InitializeAsync(this, cancellationToken).ConfigureAwait(false);
        await this.ComponentModel.GetService<RemoveUnusedReferencesCommandHandler>().InitializeAsync(this, cancellationToken).ConfigureAwait(false);
        await this.ComponentModel.GetService<SyncNamespacesCommandHandler>().InitializeAsync(this, cancellationToken).ConfigureAwait(false);
 
        await LoadAnalyzerNodeComponentsAsync(cancellationToken).ConfigureAwait(false);
 
        LoadComponentsBackgroundAsync(cancellationToken).ReportNonFatalErrorUnlessCancelledAsync(cancellationToken).Forget();
    }
 
    // Overrides for VSSDK003 fix 
    // See https://github.com/Microsoft/VSSDK-Analyzers/blob/main/doc/VSSDK003.md
    public override IVsAsyncToolWindowFactory GetAsyncToolWindowFactory(Guid toolWindowType)
    {
        if (toolWindowType == typeof(ValueTracking.ValueTrackingToolWindow).GUID)
        {
            return this;
        }
 
        if (toolWindowType == typeof(StackTraceExplorerToolWindow).GUID)
        {
            return this;
        }
 
        return base.GetAsyncToolWindowFactory(toolWindowType);
    }
 
    protected override string GetToolWindowTitle(Type toolWindowType, int id)
            => base.GetToolWindowTitle(toolWindowType, id);
 
    protected override Task<object?> InitializeToolWindowAsync(Type toolWindowType, int id, CancellationToken cancellationToken)
        => Task.FromResult((object?)null);
 
    private async Task LoadComponentsBackgroundAsync(CancellationToken cancellationToken)
    {
        await TaskScheduler.Default;
 
        await LoadStackTraceExplorerMenusAsync(cancellationToken).ConfigureAwait(true);
 
        // Initialize keybinding reset detector
        await ComponentModel.DefaultExportProvider.GetExportedValue<KeybindingReset.KeybindingResetDetector>().InitializeAsync(cancellationToken).ConfigureAwait(true);
    }
 
    private async Task LoadStackTraceExplorerMenusAsync(CancellationToken cancellationToken)
    {
        // Obtain services and QueryInterface from the main thread
        await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
 
        var menuCommandService = (OleMenuCommandService?)await GetServiceAsync(typeof(IMenuCommandService)).ConfigureAwait(true);
        Assumes.Present(menuCommandService);
        StackTraceExplorerCommandHandler.Initialize(menuCommandService, this);
    }
 
    protected override void Dispose(bool disposing)
    {
        UnregisterAnalyzerTracker();
        UnregisterRuleSetEventHandler();
 
        ReportSessionWideTelemetry();
 
        if (_solutionEventMonitor != null)
        {
            _solutionEventMonitor.Dispose();
            _solutionEventMonitor = null;
        }
 
        base.Dispose(disposing);
    }
 
    private void ReportSessionWideTelemetry()
    {
        AsyncCompletionLogger.ReportTelemetry();
        InheritanceMarginLogger.ReportTelemetry();
        FeaturesSessionTelemetry.Report();
        ComponentModel.GetService<VisualStudioSourceGeneratorTelemetryCollectorWorkspaceServiceFactory>().ReportOtherWorkspaceTelemetry();
    }
 
    private async Task LoadAnalyzerNodeComponentsAsync(CancellationToken cancellationToken)
    {
        await this.ComponentModel.GetService<IAnalyzerNodeSetup>().InitializeAsync(this, cancellationToken).ConfigureAwait(false);
 
        _ruleSetEventHandler = this.ComponentModel.GetService<RuleSetEventHandler>();
        if (_ruleSetEventHandler != null)
            await _ruleSetEventHandler.RegisterAsync(this, cancellationToken).ConfigureAwait(false);
    }
 
    private void UnregisterAnalyzerTracker()
        => this.ComponentModel.GetService<IAnalyzerNodeSetup>().Unregister();
 
    private void UnregisterRuleSetEventHandler()
    {
        if (_ruleSetEventHandler != null)
        {
            _ruleSetEventHandler.Unregister();
            _ruleSetEventHandler = null;
        }
    }
 
    private static void TrackBulkFileOperations(IGlobalOperationNotificationService globalNotificationService)
    {
        // we will pause whatever ambient work loads we have that are tied to IGlobalOperationNotificationService
        // such as solution crawler, preemptive remote host synchronization and etc. any background work users
        // didn't explicitly asked for.
        //
        // this should give all resources to BulkFileOperation. we do same for things like build, debugging, wait
        // dialog and etc. BulkFileOperation is used for things like git branch switching and etc.
        Contract.ThrowIfNull(globalNotificationService);
 
        // BulkFileOperation can't have nested events. there will be ever only 1 events (Begin/End)
        // so we only need simple tracking.
        var gate = new object();
        IDisposable? localRegistration = null;
 
        BulkFileOperation.Begin += (s, a) => StartBulkFileOperationNotification();
        BulkFileOperation.End += (s, a) => StopBulkFileOperationNotification();
 
        return;
 
        void StartBulkFileOperationNotification()
        {
            Contract.ThrowIfNull(gate);
            Contract.ThrowIfNull(globalNotificationService);
 
            lock (gate)
            {
                // this shouldn't happen, but we are using external component
                // so guarding us from them
                if (localRegistration != null)
                {
                    FatalError.ReportAndCatch(new InvalidOperationException("BulkFileOperation already exist"), ErrorSeverity.General);
                    return;
                }
 
                localRegistration = globalNotificationService.Start("BulkFileOperation");
            }
        }
 
        void StopBulkFileOperationNotification()
        {
            Contract.ThrowIfNull(gate);
            Contract.ThrowIfNull(globalNotificationService);
 
            lock (gate)
            {
                // localRegistration may be null if BulkFileOperation was already in the middle of running.  So we
                // explicitly do not assert that is is non-null here.
                localRegistration?.Dispose();
                localRegistration = null;
            }
        }
    }
}