|
// 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();
}
}
}
}
|