File: InlineHints\InlineHintsKeyProcessorProvider.cs
Web Access
Project: src\src\EditorFeatures\Core\Microsoft.CodeAnalysis.EditorFeatures_3hxu4syd_wpftmp.csproj (Microsoft.CodeAnalysis.EditorFeatures)
// 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.Windows.Input;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Utilities;
 
namespace Microsoft.CodeAnalysis.Editor.InlineHints;
 
/// <summary>
/// Key processor that allows us to toggle inline hints when a user hits Alt+F1
/// </summary>
[Export(typeof(IKeyProcessorProvider))]
[Export(typeof(IInlineHintKeyProcessor))]
[TextViewRole(PredefinedTextViewRoles.Interactive)]
[ContentType(ContentTypeNames.RoslynContentType)]
[Name(nameof(InlineHintsKeyProcessorProvider))]
internal sealed class InlineHintsKeyProcessorProvider : IKeyProcessorProvider, IInlineHintKeyProcessor
{
    private readonly IGlobalOptionService _globalOptions;
    private readonly IThreadingContext _threadingContext;
    private int _state;
 
    [ImportingConstructor]
    [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
    public InlineHintsKeyProcessorProvider(
        IGlobalOptionService globalOptions,
        IThreadingContext threadingContext)
    {
        _globalOptions = globalOptions;
        _threadingContext = threadingContext;
    }
 
    public bool State
    {
        get
        {
            // Can be read on any thread.
            return Volatile.Read(ref _state) == 1;
        }
 
        private set
        {
            _threadingContext.ThrowIfNotOnUIThread();
            Volatile.Write(ref _state, value ? 1 : 0);
        }
    }
 
    public event Action? StateChanged;
 
    public KeyProcessor GetAssociatedProcessor(IWpfTextView wpfTextView)
        => new InlineHintsKeyProcessor(this, wpfTextView);
 
    private sealed class InlineHintsKeyProcessor : KeyProcessor
    {
        private readonly InlineHintsKeyProcessorProvider _processorProvider;
        private readonly IWpfTextView _view;
 
        public InlineHintsKeyProcessor(InlineHintsKeyProcessorProvider processorProvider, IWpfTextView view)
        {
            _processorProvider = processorProvider;
            _view = view;
            _view.Closed += OnViewClosed;
            _view.LostAggregateFocus += OnLostFocus;
        }
 
        private static bool IsAlt(KeyEventArgs args)
            => IsKey(args, Key.LeftAlt) || IsKey(args, Key.RightAlt);
 
        private static bool IsF1(KeyEventArgs args)
            => IsKey(args, Key.F1);
 
        private static bool IsKey(KeyEventArgs args, Key key)
            => args.SystemKey == key || args.Key == key;
 
        private void OnViewClosed(object sender, EventArgs e)
        {
            // Disconnect our callbacks.
            _view.Closed -= OnViewClosed;
            _view.LostAggregateFocus -= OnLostFocus;
 
            // Go back to off-mode just so we don't somehow get stuck in on-mode if the option was on when the view closed.
            ToggleOff();
        }
 
        private void OnLostFocus(object sender, EventArgs e)
        {
            // if focus is lost then go back to normal inline-hint processing.
            ToggleOff();
        }
 
        public override void KeyDown(KeyEventArgs args)
        {
            base.KeyDown(args);
 
            // If the user is now holding down F1, see if they're also holding down 'alt'.  If so, toggle the inline hints on.
            if (IsF1(args) &&
                args.KeyboardDevice.Modifiers == ModifierKeys.Alt)
            {
                ToggleOn();
            }
            else
            {
                // Otherwise, on any other keypress toggle off.  Note that this will normally be non-expensive as we
                // will see the option is already off and immediately exit..
                ToggleOff();
            }
        }
 
        public override void KeyUp(KeyEventArgs args)
        {
            base.KeyUp(args);
 
            // If we've lifted a key up from either character of our alt-F1 chord, then turn off the inline hints.
            if (IsAlt(args) || IsF1(args))
                ToggleOff();
        }
 
        private void ToggleOn()
            => Toggle(on: true);
 
        private void ToggleOff()
            => Toggle(on: false);
 
        private void Toggle(bool on)
        {
            // No need to do anything if we're already in the requested state
            if (_processorProvider.State == on)
                return;
 
            // We can only enter the on-state if the user has the chord feature enabled.  We can always enter the
            // off state though.
            on = on && _processorProvider._globalOptions.GetOption(InlineHintsViewOptionsStorage.DisplayAllHintsWhilePressingAltF1);
            if (_processorProvider.State == on)
                return;
 
            _processorProvider.State = on;
            _processorProvider.StateChanged?.Invoke();
        }
    }
}