#nullable enable using System; using System.Collections.Generic; using System.Globalization; using Swan.Lite.Logging; namespace Swan { /// /// A console terminal helper to create nicer output and receive input from the user /// This class is thread-safe :). /// public static partial class Terminal { #region ReadKey /// /// Reads a key from the Terminal. This is the closest equivalent to Console.ReadKey. /// /// if set to true the pressed key will not be rendered to the output. /// if set to true the output will continue to be shown. /// This is useful for services and daemons that are running as console applications and wait for a key to exit the program. /// The console key information. public static ConsoleKeyInfo ReadKey(Boolean intercept, Boolean disableLocking = false) { if(!IsConsolePresent) { return default; } if(disableLocking) { return Console.ReadKey(intercept); } lock(SyncLock) { Flush(); InputDone.Reset(); try { Console.CursorVisible = true; return Console.ReadKey(intercept); } finally { Console.CursorVisible = false; InputDone.Set(); } } } /// /// Reads a key from the Terminal. /// /// The prompt. /// if set to true [prevent echo]. /// The console key information. public static ConsoleKeyInfo ReadKey(String prompt, Boolean preventEcho = true) { if(!IsConsolePresent) { return default; } lock(SyncLock) { if(prompt != null) { Write($"{GetNowFormatted()}{Settings.UserInputPrefix} << {prompt} ", ConsoleColor.White); } ConsoleKeyInfo input = ReadKey(true); String echo = preventEcho ? String.Empty : input.Key.ToString(); WriteLine(echo); return input; } } #endregion #region Other Terminal Read Methods /// /// Clears the screen. /// public static void Clear() { Flush(); Console.Clear(); } /// /// Reads a line of text from the console. /// /// The read line. public static String? ReadLine() { if(IsConsolePresent == false) { return default; } lock(SyncLock) { Flush(); InputDone.Reset(); try { Console.CursorVisible = true; return Console.ReadLine(); } finally { Console.CursorVisible = false; InputDone.Set(); } } } /// /// Reads a line from the input. /// /// The prompt. /// The read line. public static String? ReadLine(String prompt) { if(!IsConsolePresent) { return null; } lock(SyncLock) { Write($"{GetNowFormatted()}{Settings.UserInputPrefix} << {prompt}: ", ConsoleColor.White); return ReadLine(); } } /// /// Reads a number from the input. If unable to parse, it returns the default number. /// /// The prompt. /// The default number. /// /// Conversions of string representation of a number to its 32-bit signed integer equivalent. /// public static Int32 ReadNumber(String prompt, Int32 defaultNumber) { if(!IsConsolePresent) { return defaultNumber; } lock(SyncLock) { Write($"{GetNowFormatted()}{Settings.UserInputPrefix} << {prompt} (default is {defaultNumber}): ", ConsoleColor.White); String? input = ReadLine(); return Int32.TryParse(input, out Int32 parsedInt) ? parsedInt : defaultNumber; } } /// /// Creates a table prompt where the user can enter an option based on the options dictionary provided. /// /// The title. /// The options. /// Any key option. /// /// A value that identifies the console key that was pressed. /// /// options. public static ConsoleKeyInfo ReadPrompt(String title, IDictionary options, String anyKeyOption) { if(!IsConsolePresent) { return default; } if(options == null) { throw new ArgumentNullException(nameof(options)); } const ConsoleColor textColor = ConsoleColor.White; Int32 lineLength = Console.WindowWidth; Int32 lineAlign = -(lineLength - 2); String textFormat = "{0," + lineAlign + "}"; // lock the output as an atomic operation lock(SyncLock) { { // Top border Table.TopLeft(); Table.Horizontal(-lineAlign); Table.TopRight(); } { // Title Table.Vertical(); String titleText = String.Format(CultureInfo.CurrentCulture, textFormat, String.IsNullOrWhiteSpace(title) ? " Select an option from the list below." : $" {title}"); Write(titleText, textColor); Table.Vertical(); } { // Title Bottom Table.LeftTee(); Table.Horizontal(lineLength - 2); Table.RightTee(); } // Options foreach(KeyValuePair kvp in options) { Table.Vertical(); Write(String.Format(CultureInfo.CurrentCulture, textFormat, $" {"[ " + kvp.Key + " ]",-10} {kvp.Value}"), textColor); Table.Vertical(); } // Any Key Options if(String.IsNullOrWhiteSpace(anyKeyOption) == false) { Table.Vertical(); Write(String.Format(CultureInfo.CurrentCulture, textFormat, " "), ConsoleColor.Gray); Table.Vertical(); Table.Vertical(); Write(String.Format(CultureInfo.CurrentCulture, textFormat, $" {" ",-10} {anyKeyOption}"), ConsoleColor.Gray); Table.Vertical(); } { // Input section Table.LeftTee(); Table.Horizontal(lineLength - 2); Table.RightTee(); Table.Vertical(); Write(String.Format(CultureInfo.CurrentCulture, textFormat, Settings.UserOptionText), ConsoleColor.Green); Table.Vertical(); Table.BottomLeft(); Table.Horizontal(lineLength - 2); Table.BottomRight(); } } Int32 inputLeft = Settings.UserOptionText.Length + 3; SetCursorPosition(inputLeft, CursorTop - 1); ConsoleKeyInfo userInput = ReadKey(true); Write(userInput.Key.ToString(), ConsoleColor.Gray); SetCursorPosition(0, CursorTop + 2); return userInput; } #endregion private static String GetNowFormatted() => $" {(String.IsNullOrWhiteSpace(TextLogger.LoggingTimeFormat) ? String.Empty : DateTime.Now.ToString(TextLogger.LoggingTimeFormat) + " ")}"; } }