File: Options\CSharpVisualStudioCopilotOptionsService.cs
Web Access
Project: src\src\VisualStudio\CSharp\Impl\Microsoft.VisualStudio.LanguageServices.CSharp_sivdbxd3_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices.CSharp)
// 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.Composition;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Copilot;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.Internal.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Settings;
using Microsoft.VisualStudio.Shell;
 
namespace Microsoft.VisualStudio.LanguageServices.CSharp.Options;
 
[ExportLanguageService(typeof(ICopilotOptionsService), LanguageNames.CSharp), Shared]
internal sealed class CSharpVisualStudioCopilotOptionsService : ICopilotOptionsService
{
    /// <summary>
    /// Guid for UI context that is set from Copilot when the package is initialized
    /// </summary>
    private const string CopilotHasLoadedGuid = "871c3e1c-e58c-4ce9-b6a7-26600555739a";
 
    /// <summary>
    /// Guid for UI Context that is set from Copilot when sign in related UI contexts have been set properly. Used to determine when UI context status is final
    /// for a set of operations. When this UI context is not active, the signed in and entitled contexts values may not be correct.
    /// </summary>
    private const string GitHubAccountStatusDetermined = "3049be7e-71ee-4045-a510-f8ee1a967723";
 
    /// <summary>
    /// Guid for UI context that is set from Copilot when we detect a GitHub account is signed in.
    /// </summary>
    private const string GitHubAccountStatusSignedIn = "ef3ebbb7-511d-472c-ae4b-6af1bb44f378";
 
    /// <summary>
    /// Guid for UI context that is set from VS Identity Service when we detect that a signed in GitHub account is entitled to access Copilot.
    /// </summary>
    private const string GitHubAccountStatusIsCopilotEntitled = "3DE3FA6E-91B2-46C1-9E9E-DD04975BB890";
 
    private const string CopilotOptionNamePrefix = "Microsoft.VisualStudio.Conversations";
 
    // Default value must reflect their default values in ConversationsOptions in Copilot repo.
    private readonly CopilotOption _copilotCodeAnalysisOption = new("EnableCSharpCodeAnalysis", false);
    private readonly CopilotOption _copilotRefineOption = new("EnableCSharpRefineQuickActionSuggestion", false);
    private readonly CopilotOption _copilotOnTheFlyDocsOption = new("EnableOnTheFlyDocs", true);
 
    private static readonly UIContext s_copilotHasLoadedUIContext = UIContext.FromUIContextGuid(new Guid(CopilotHasLoadedGuid));
    private static readonly UIContext s_gitHubAccountStatusDeterminedContext = UIContext.FromUIContextGuid(new Guid(GitHubAccountStatusDetermined));
    private static readonly UIContext s_gitHubAccountStatusIsCopilotEntitledUIContext = UIContext.FromUIContextGuid(new Guid(GitHubAccountStatusIsCopilotEntitled));
    private static readonly UIContext s_gitHubAccountStatusSignedInUIContext = UIContext.FromUIContextGuid(new Guid(GitHubAccountStatusSignedIn));
 
    private readonly Task<ISettingsManager> _settingsManagerTask;
 
    /// <summary>
    /// Determines if Copilot is active and the user is signed in and entitled to use Copilot.
    /// </summary>
    private static bool IsGithubCopilotLoadedAndSignedIn
        => s_copilotHasLoadedUIContext.IsActive
        && s_gitHubAccountStatusDeterminedContext.IsActive
        && s_gitHubAccountStatusSignedInUIContext.IsActive
        && s_gitHubAccountStatusIsCopilotEntitledUIContext.IsActive;
 
    [method: ImportingConstructor]
    [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
    public CSharpVisualStudioCopilotOptionsService(
        IVsService<SVsSettingsPersistenceManager, ISettingsManager> settingsManagerService,
        IThreadingContext threadingContext)
    {
        _settingsManagerTask = settingsManagerService.GetValueAsync(threadingContext.DisposalToken);
    }
 
    private async Task<bool> IsCopilotOptionEnabledAsync(CopilotOption option)
    {
        if (!IsGithubCopilotLoadedAndSignedIn)
            return false;
 
        var settingManager = await _settingsManagerTask.ConfigureAwait(false);
        // The bool setting is persisted as 0=None, 1=True, 2=False, so it needs to be retrieved as an int.
        // If isEnabled is 0 or the value is not persisted, we should return the default value for the option.
        var isEnabled = settingManager.GetValueOrDefault($"{CopilotOptionNamePrefix}.{option.Name}", 0);
        return isEnabled == 1 || (isEnabled == 0 && option.DefaultValue);
    }
 
    public Task<bool> IsCodeAnalysisOptionEnabledAsync()
        => IsCopilotOptionEnabledAsync(_copilotCodeAnalysisOption);
 
    public Task<bool> IsRefineOptionEnabledAsync()
        => IsCopilotOptionEnabledAsync(_copilotRefineOption);
 
    public Task<bool> IsOnTheFlyDocsOptionEnabledAsync()
        => IsCopilotOptionEnabledAsync(_copilotOnTheFlyDocsOption);
 
    private record struct CopilotOption(string Name, bool DefaultValue);
}