File: UI\ConsoleInputReader.cs
Web Access
Project: ..\..\..\src\BuiltInTools\dotnet-watch\dotnet-watch.csproj (dotnet-watch)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
namespace Microsoft.DotNet.Watch
{
    internal sealed class ConsoleInputReader(IConsole console, bool quiet, bool suppressEmojis)
    {
        private readonly object _writeLock = new();
 
        public async Task<ConsoleKey> GetKeyAsync(string prompt, Func<ConsoleKeyInfo, bool> validateInput, CancellationToken cancellationToken)
        {
            if (quiet)
            {
                return ConsoleKey.Escape;
            }
 
            var questionMark = suppressEmojis ? "?" : "❔";
            while (true)
            {
                // Start listening to the key before printing the prompt to avoid race condition
                // in tests that wait for the prompt before sending the key press.
                var tcs = new TaskCompletionSource<ConsoleKey>(TaskCreationOptions.RunContinuationsAsynchronously);
                console.KeyPressed += KeyPressed;
 
                WriteLine($"  {questionMark} {prompt}");
 
                lock (_writeLock)
                {
                    console.ForegroundColor = ConsoleColor.DarkGray;
                    console.Out.Write($"  {questionMark} ");
                    console.ResetColor();
                }
 
                try
                {
                    return await tcs.Task.WaitAsync(cancellationToken);
                }
                catch (ArgumentException)
                {
                    // Prompt again for valid input
                }
                finally
                {
                    console.KeyPressed -= KeyPressed;
                }
 
                void KeyPressed(ConsoleKeyInfo key)
                {
                    var keyDisplay = GetDisplayString(key);
                    if (validateInput(key))
                    {
                        WriteLine(keyDisplay);
                        tcs.TrySetResult(key.Key);
                    }
                    else
                    {
                        WriteLine(keyDisplay, ConsoleColor.DarkRed);
                        tcs.TrySetException(new ArgumentException($"Invalid key '{keyDisplay}' entered."));
                    }
                }
 
                static string GetDisplayString(ConsoleKeyInfo key)
                {
                    var keyDisplay = (key.Modifiers == ConsoleModifiers.None) ? key.KeyChar.ToString() : key.Key.ToString();
 
                    if (key.Modifiers.HasFlag(ConsoleModifiers.Alt))
                    {
                        keyDisplay = "Alt+" + keyDisplay;
                    }
 
                    if (key.Modifiers.HasFlag(ConsoleModifiers.Shift))
                    {
                        keyDisplay = "Shift+" + keyDisplay;
                    }
 
                    if (key.Modifiers.HasFlag(ConsoleModifiers.Control))
                    {
                        keyDisplay = "Ctrl+" + keyDisplay;
                    }
 
                    return keyDisplay;
                }
            }
 
            void WriteLine(string message, ConsoleColor color = ConsoleColor.DarkGray)
            {
                lock (_writeLock)
                {
                    console.ForegroundColor = color;
                    console.Out.WriteLine(message);
                    console.ResetColor();
                }
            }
        }
    }
}