File: Options\VisualStudioOptionPersisterProvider.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 System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options;
using Microsoft.Internal.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Settings;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Roslyn.Utilities;
 
namespace Microsoft.VisualStudio.LanguageServices.Options;
 
[Export(typeof(IOptionPersisterProvider))]
[Export(typeof(VisualStudioOptionPersisterProvider))]
internal sealed class VisualStudioOptionPersisterProvider : IOptionPersisterProvider
{
    private readonly IAsyncServiceProvider _serviceProvider;
    private readonly ILegacyGlobalOptionService _legacyGlobalOptions;
 
    // maps config name to a read fallback:
    private readonly ImmutableDictionary<string, Lazy<IVisualStudioStorageReadFallback, OptionNameMetadata>> _readFallbacks;
 
    // Use vs-threading's JTF-aware AsyncLazy<T>. Ensure only one persister instance is created (even in the face of
    // parallel requests for the value) because the constructor registers global event handler callbacks.
    private readonly Threading.AsyncLazy<IOptionPersister> _lazyPersister;
 
    [ImportingConstructor]
    [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
    public VisualStudioOptionPersisterProvider(
        [Import(typeof(SAsyncServiceProvider))] IAsyncServiceProvider serviceProvider,
        [ImportMany] IEnumerable<Lazy<IVisualStudioStorageReadFallback, OptionNameMetadata>> readFallbacks,
        IThreadingContext threadingContext,
        ILegacyGlobalOptionService legacyGlobalOptions)
    {
        _serviceProvider = serviceProvider;
        _legacyGlobalOptions = legacyGlobalOptions;
        _readFallbacks = readFallbacks.ToImmutableDictionary(item => item.Metadata.ConfigName, item => item);
        _lazyPersister = new Threading.AsyncLazy<IOptionPersister>(() => CreatePersisterAsync(threadingContext.DisposalToken), threadingContext.JoinableTaskFactory);
    }
 
    public ValueTask<IOptionPersister> GetOrCreatePersisterAsync(CancellationToken cancellationToken)
        => new(_lazyPersister.GetValueAsync(cancellationToken));
 
    private async Task<IOptionPersister> CreatePersisterAsync(CancellationToken cancellationToken)
    {
        // Obtain services before creating instances. This avoids state corruption in the event cancellation is
        // requested (some of the constructors register event handlers that could leak if cancellation occurred
        // in the middle of construction).
        var settingsManager = await GetFreeThreadedServiceAsync<SVsSettingsPersistenceManager, ISettingsManager>().ConfigureAwait(false);
        Assumes.Present(settingsManager);
        var localRegistry = await GetFreeThreadedServiceAsync<SLocalRegistry, ILocalRegistry4>().ConfigureAwait(false);
        Assumes.Present(localRegistry);
        var featureFlags = await GetFreeThreadedServiceAsync<SVsFeatureFlags, IVsFeatureFlags>().ConfigureAwait(false);
 
        // Cancellation is not allowed after this point
        cancellationToken = CancellationToken.None;
 
        return new VisualStudioOptionPersister(
            new VisualStudioSettingsOptionPersister(RefreshOption, _readFallbacks, settingsManager),
            LocalUserRegistryOptionPersister.Create(localRegistry),
            new FeatureFlagPersister(featureFlags));
    }
 
    private void RefreshOption(OptionKey2 optionKey, object? newValue)
    {
        if (_legacyGlobalOptions.GlobalOptions.RefreshOption(optionKey, newValue))
        {
            // We may be updating the values of internally defined public options.
            // Update solution snapshots of all workspaces to reflect the new values.
            _legacyGlobalOptions.UpdateRegisteredWorkspaces();
        }
    }
 
    /// <summary>
    /// Returns a service without doing a transition to the UI thread to cast the service to the interface type. This should only be called for services that are
    /// well-understood to be castable off the UI thread, either because they are managed or free-threaded COM.
    /// </summary>
    private async ValueTask<I?> GetFreeThreadedServiceAsync<T, I>() where I : class
    {
        try
        {
            return (I?)await _serviceProvider.GetServiceAsync(typeof(T)).ConfigureAwait(false);
        }
        catch (Exception e) when (FatalError.ReportAndPropagate(e))
        {
            throw ExceptionUtilities.Unreachable();
        }
    }
}