using System; using System.Collections.Generic; using Swan.Lite.Logging; using System.Globalization; using Swan.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(bool intercept, bool 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, bool preventEcho = true) { if (!IsConsolePresent) return default; lock (SyncLock) { if (prompt != null) { Write($"{GetNowFormatted()}{Settings.UserInputPrefix} << {prompt} ", ConsoleColor.White); } var input = ReadKey(true); var 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 int ReadNumber(string prompt, int defaultNumber) { if (!IsConsolePresent) return defaultNumber; lock (SyncLock) { Write($"{GetNowFormatted()}{Settings.UserInputPrefix} << {prompt} (default is {defaultNumber}): ", ConsoleColor.White); var input = ReadLine(); return int.TryParse(input, out var 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; var lineLength = Console.WindowWidth; var lineAlign = -(lineLength - 2); var textFormat = "{0," + lineAlign + "}"; // lock the output as an atomic operation lock (SyncLock) { { // Top border Table.TopLeft(); Table.Horizontal(-lineAlign); Table.TopRight(); } { // Title Table.Vertical(); var 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 (var 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(); } } var inputLeft = Settings.UserOptionText.Length + 3; SetCursorPosition(inputLeft, CursorTop - 1); var 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) + " ")}"; } }