diff --git a/Consoul.Test/Program.cs b/Consoul.Test/Program.cs
index 78fa1e8..dc070c0 100644
--- a/Consoul.Test/Program.cs
+++ b/Consoul.Test/Program.cs
@@ -62,8 +62,8 @@ static void Main(string[] args)
Routines.InitializeRoutine(args);
//Routines.UseDelays = true; // Showcases the usecase of reusing input delays to simulate user response
- var tableTest = new Test.Views.TableView();
- tableTest.Run();
+ var cancelReadTest = new Test.Views.CancellabelReadView();
+ cancelReadTest.Run();
var view1 = new Welcome();
diff --git a/Consoul.Test/Views/CancellabelReadView.cs b/Consoul.Test/Views/CancellabelReadView.cs
new file mode 100644
index 0000000..f8bf5ea
--- /dev/null
+++ b/Consoul.Test/Views/CancellabelReadView.cs
@@ -0,0 +1,43 @@
+using ConsoulLibrary.Views;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ConsoulLibrary.Test.Views
+{
+ public class CancellabelReadView : StaticView
+ {
+ public CancellabelReadView()
+ {
+ Title = (new BannerEntry("Testing the Read(CancellationToken) method")).Message;
+ }
+
+ [ViewOption("Test Read()")]
+ public void Test()
+ {
+ Consoul.Write("Input some text:", ConsoleColor.Yellow);
+ string input = Consoul.Read();
+ Consoul.Write("Read the following input:");
+ Consoul.Write(input, ConsoleColor.Gray);
+
+ Consoul.Wait();
+ }
+
+ [ViewOption("Test Read(CancellationToken)")]
+ public void TestCancellable()
+ {
+ const int TIMEOUT_SECONDS = 5;
+ string input = string.Empty;
+ Consoul.Write("Input some text:", ConsoleColor.Yellow);
+ using (var cancelSource = new CancellationTokenSource(TimeSpan.FromSeconds(TIMEOUT_SECONDS)))
+ {
+ cancelSource.Token.Register(() => Consoul.Write("Consoul.Read(CancellationToken) Timed Out!", ConsoleColor.Red));
+ input = Consoul.Read(cancelSource.Token);
+ }
+ Consoul.Write("Read the following input:");
+ Consoul.Write(input, ConsoleColor.Gray);
+
+ Consoul.Wait();
+ }
+ }
+}
diff --git a/Consoul/Consoul.cs b/Consoul/Consoul.cs
index fc5a2f2..ec786be 100644
--- a/Consoul/Consoul.cs
+++ b/Consoul/Consoul.cs
@@ -1,8 +1,7 @@
using System;
using System.IO;
using System.Linq;
-using System.Reflection;
-using System.Text;
+using System.Threading;
namespace ConsoulLibrary {
public static class Consoul
@@ -13,11 +12,10 @@ public static class Consoul
/// Waits for the user to press "Enter". Performs Console.ReadLine()
/// Flags whether or not to show continue message.
///
- public static void Wait(bool silent = false)
+ public static void Wait(bool silent = false, CancellationToken cancellationToken = default)
{
- if (!silent)
- Consoul._write(RenderOptions.ContinueMessage, RenderOptions.SubnoteColor);
- Read();
+ if (!silent) Consoul._write(RenderOptions.ContinueMessage, RenderOptions.SubnoteColor);
+ Read(cancellationToken);
}
///
@@ -27,14 +25,14 @@ public static void Wait(bool silent = false)
/// Override the Prompt Message color
/// Specifies whether the user can provide an empty response. Can result in string.Empty
/// User response (string)
- public static string Input(string message, ConsoleColor? color = null, bool allowEmpty = false)
+ public static string Input(string message, ConsoleColor? color = null, bool allowEmpty = false, CancellationToken cancellationToken = default)
{
string output = string.Empty;
bool valid = false;
do
{
Consoul._write(message, RenderOptions.GetColor(color));
- output = Read();
+ output = Read(cancellationToken);
if (allowEmpty)
{
valid = true;
@@ -88,12 +86,33 @@ public static void Write(string message, ConsoleColor? color = null, bool writeL
break;
}
}
-
+
+ ///
+ /// Reads user input from the console.
+ ///
+ /// Response from the user.
+ public static string Read() => Read("\r\n");
+
///
/// Waits for user input and reads the user response.
///
+ /// Reference to the string that indicates the end of stream.
/// Value from the user
- public static string Read()
+ public static string Read(string exitCode = "\r\n")
+ {
+ using (var cancelSource = new CancellationTokenSource())
+ {
+ return Read(cancelSource.Token, exitCode);
+ }
+ }
+
+ ///
+ /// Asynchronously reads any input from the user and allows the operation to be cancelled at any time.
+ ///
+ /// Reference to the cancellation token to stop the read operation.
+ /// Reference to the string that indicates the end of stream.
+ /// Response from the user.
+ public static string Read(CancellationToken cancellationToken = default, string exitCode = "\r\n")
{
bool keyControl = false, keyAlt = false, keyShift = false;
RoutineInput input = new RoutineInput();
@@ -115,7 +134,37 @@ public static string Read()
else
{
string userInput = string.Empty;
- input.Value = Console.ReadLine();
+ using (var stream = Console.OpenStandardInput())
+ {
+ byte[] data = new byte[1];
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ using (var readCanceller = new CancellationTokenSource(TimeSpan.FromMilliseconds(500)))
+ {
+ try
+ {
+ stream.ReadAsync(data, 0, data.Length, readCanceller.Token).Wait(cancellationToken);
+ }
+ catch (OperationCanceledException cancelled)
+ {
+ break;
+ }
+
+ if (data.Length > 0 && data[0] >= 0)
+ {
+ userInput += Console.InputEncoding.GetString(data);
+ }
+
+ if (userInput.EndsWith(exitCode))
+ {
+ userInput = userInput.Substring(0, userInput.Length - exitCode.Length);
+ break;
+ }
+ }
+ }
+ stream.Close();
+ }
+ input.Value = userInput;
}
if (Routines.PromptRegistry.Any())
@@ -145,7 +194,6 @@ public static void Center(string message, int maxWidth, ConsoleColor? color = nu
{
string text = message.Length > maxWidth ? message.Substring(0, maxWidth - 3) + "..." : message;
-
int remainder = maxWidth - text.Length - 1;
int left, right;
right = remainder / 2;
@@ -167,7 +215,7 @@ public static void Center(string message, int maxWidth, ConsoleColor? color = nu
/// Specifies whether the user can provide an empty response. Default is typically the 'No', but can be overriden
/// Specifies whether the default entry should be 'No'. This only applies if 'allowEmpty' is true.
/// Boolean of users response relative to 'Yes' or 'No'
- public static bool Ask(string message, bool clear = false, bool allowEmpty = false, bool defaultIsNo = true)
+ public static bool Ask(string message, bool clear = false, bool allowEmpty = false, bool defaultIsNo = true, CancellationToken cancellationToken = default)
{
string input = "";
string orEmpty = $" or Press Enter";
@@ -185,7 +233,7 @@ public static bool Ask(string message, bool clear = false, bool allowEmpty = fal
}
Consoul._write(message, RenderOptions.PromptColor);
Consoul._write(optionMessage, RenderOptions.SubnoteColor);
- input = Read();// Console.ReadLine();
+ input = Read(cancellationToken);
if (input.ToLower() != "y" && input.ToLower() != "n" && !string.IsNullOrEmpty(input))
{
Consoul._write("Invalid input!", RenderOptions.InvalidColor);
@@ -202,10 +250,22 @@ public static bool Ask(string message, bool clear = false, bool allowEmpty = fal
///
/// Indicates whether or not to clear the console window.
/// Simple list of options.
- ///
+ /// Index of the option that was chosen. Returns -1 if selection was invalid.
public static int Prompt(string message, bool clear = false, params string[] options)
{
- return (new Prompt(message, clear, options)).Run();
+ return Prompt(message, clear, CancellationToken.None, options);
+ }
+
+ ///
+ /// Prompts the user with a simple list of choices.
+ ///
+ ///
+ /// Indicates whether or not to clear the console window.
+ /// Simple list of options.
+ /// Index of the option that was chosen. Returns -1 if selection was invalid.
+ public static int Prompt(string message, bool clear = false, CancellationToken cancellationToken = default, params string[] options)
+ {
+ return (new Prompt(message, clear, options)).Run(cancellationToken);
}
///
@@ -227,12 +287,12 @@ public static int Prompt(string message, PromptOption[] options, bool clear = fa
/// Indicates whether to check the file exists before allowing the user exit the loop.
///
///
- public static string PromptForFilepath(string message, bool checkExists, ConsoleColor? color = null) {
+ public static string PromptForFilepath(string message, bool checkExists, ConsoleColor? color = null, CancellationToken cancellationToken = default) {
string path;
do
{
- Consoul.Write(message, color);
- path = Consoul.Read();
+ Write(message, color);
+ path = Read(cancellationToken);
} while (string.IsNullOrEmpty(path) && (checkExists ? !File.Exists(path) : true));
if (path.StartsWith("\"") && path.EndsWith("\"")) path = path.Substring(1, path.Length - 2);
return path;
@@ -246,10 +306,10 @@ public static string PromptForFilepath(string message, bool checkExists, Console
///
///
///
- public static string PromptForFilepath(string defaultPath, string message, bool checkExists, ConsoleColor? color = null) {
+ public static string PromptForFilepath(string defaultPath, string message, bool checkExists, ConsoleColor? color = null, CancellationToken cancellationToken = default) {
string path = defaultPath;
- if (!File.Exists(path) || !Consoul.Ask($"Would you like to use the default path:\r\n{path}", defaultIsNo: false)) {
- path = PromptForFilepath(message, checkExists, color);
+ if (!File.Exists(path) || !Ask($"Would you like to use the default path:\r\n{path}", defaultIsNo: false)) {
+ path = PromptForFilepath(message, checkExists, color, cancellationToken);
}
return path;
}
@@ -270,8 +330,8 @@ public static void Ding()
/// Specifies whether to use Console.WriteLine() or Console.Write()
public static void Alert(string message, ConsoleColor? color = null, bool writeLine = true)
{
- Consoul.Write(message, color, writeLine);
- Consoul.Ding();
+ Write(message, color, writeLine);
+ Ding();
}
}
}
diff --git a/Consoul/Consoul.csproj b/Consoul/Consoul.csproj
index 69126fc..870a119 100644
--- a/Consoul/Consoul.csproj
+++ b/Consoul/Consoul.csproj
@@ -9,7 +9,7 @@
Console
tbm0115
- 1.6.1
+ 1.6.2
Added Ding and Alert methods
1.5.14.0
1.5.14.0
diff --git a/Consoul/Prompt/Prompt.cs b/Consoul/Prompt/Prompt.cs
index 866bb67..58d5520 100644
--- a/Consoul/Prompt/Prompt.cs
+++ b/Consoul/Prompt/Prompt.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading;
using Options = ConsoulLibrary.RenderOptions;
namespace ConsoulLibrary {
public delegate void PromptChoiceCallback(TTarget choice);
@@ -67,7 +68,7 @@ public void Clear()
/// Displays the options for this prompt. Loops until the user "selects" the appropriate option.
///
/// Zero-based index of the selected option.
- public int Run()
+ public int Run(CancellationToken cancellationToken = default)
{
string[] escapePhrases = new string[]
{
@@ -98,7 +99,7 @@ public int Run()
i++;
}
Console.ForegroundColor = RenderOptions.DefaultColor;
- input = Consoul.Read();// Console.ReadLine();
+ input = Consoul.Read(cancellationToken);
if (string.IsNullOrEmpty(input) && defaultOption != null)
{
selection = defaultOption.Index;
diff --git a/Consoul/Table/TableView.cs b/Consoul/Table/TableView.cs
index 3471367..ca27628 100644
--- a/Consoul/Table/TableView.cs
+++ b/Consoul/Table/TableView.cs
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
-using System.Text;
+using System.Threading;
namespace ConsoulLibrary.Table
{
@@ -110,7 +110,7 @@ public void Write(bool clearConsole = true){
}
}
- public int Prompt(string message = "", ConsoleColor? color = null, bool allowEmpty = false, bool clearConsole = true)
+ public int Prompt(string message = "", ConsoleColor? color = null, bool allowEmpty = false, bool clearConsole = true, CancellationToken cancellationToken = default)
{
if (Contents?.Any() == false)
return -1;
@@ -132,7 +132,7 @@ public int Prompt(string message = "", ConsoleColor? color = null, bool allowEmp
Consoul.Write("Press Enter to continue", ConsoulLibrary.RenderOptions.SubnoteColor);
}
- string input = Consoul.Read();
+ string input = Consoul.Read(cancellationToken);
if (string.IsNullOrEmpty(input) && allowEmpty)
{
selection = Contents.Count + 1;