From a18a15c238e29f11bf9eb5aa2bdca6ea4401c6c2 Mon Sep 17 00:00:00 2001 From: Andrew Plotkin Date: Wed, 26 Jul 2017 18:28:36 -0400 Subject: [PATCH 01/11] Added timer loop (which currently has no effect). Also an initialInput argument, which can be used to preload the buffer. --- src/ReadLine/KeyHandler.cs | 8 +++++++- src/ReadLine/ReadLine.cs | 30 ++++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/ReadLine/KeyHandler.cs b/src/ReadLine/KeyHandler.cs index 539f810..4e8bd94 100644 --- a/src/ReadLine/KeyHandler.cs +++ b/src/ReadLine/KeyHandler.cs @@ -204,13 +204,19 @@ public string Text } } - public KeyHandler(IConsole console, List history, Func autoCompleteHandler) + public KeyHandler(IConsole console, string initialInput, List history, Func autoCompleteHandler) { Console2 = console; _historyIndex = history.Count; _history = history; _text = new StringBuilder(); + + if (initialInput.Length > 0) + { + WriteNewString(initialInput); + } + _keyActions = new Dictionary(); _keyActions["LeftArrow"] = MoveCursorLeft; diff --git a/src/ReadLine/ReadLine.cs b/src/ReadLine/ReadLine.cs index 9e518bf..f505166 100755 --- a/src/ReadLine/ReadLine.cs +++ b/src/ReadLine/ReadLine.cs @@ -1,6 +1,7 @@ using Internal.ReadLine; using Internal.ReadLine.Abstractions; +using System.Threading; using System.Collections.Generic; namespace System @@ -21,17 +22,30 @@ static ReadLine() public static Func AutoCompletionHandler { private get; set; } public static bool PasswordMode { private get; set; } - public static string Read(string prompt = "", string defaultInput = "") + public static string Read(string prompt = "", string defaultInput = "", string initialInput = "") { Console.Write(prompt); - _keyHandler = new KeyHandler(new Console2() { PasswordMode = PasswordMode }, _history, AutoCompletionHandler); - ConsoleKeyInfo keyInfo = Console.ReadKey(true); - - while (keyInfo.Key != ConsoleKey.Enter) - { - _keyHandler.Handle(keyInfo); - keyInfo = Console.ReadKey(true); + _keyHandler = new KeyHandler(new Console2() { PasswordMode = PasswordMode }, initialInput, _history, AutoCompletionHandler); + + bool done = false; + + while (!done) { + while (Console.KeyAvailable) + { + ConsoleKeyInfo keyInfo = Console.ReadKey(true); + if (keyInfo.Key == ConsoleKey.Enter) + { + done = true; + break; + } + _keyHandler.Handle(keyInfo); + } + + if (!done) + { + Thread.Sleep(50); + } } Console.WriteLine(); From 9f29417a784254fbed434ade1b1d69d8ffe694cd Mon Sep 17 00:00:00 2001 From: Andrew Plotkin Date: Wed, 26 Jul 2017 18:29:13 -0400 Subject: [PATCH 02/11] Demo with timer input. --- src/ReadLine.Demo/TimerDemo.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/ReadLine.Demo/TimerDemo.cs diff --git a/src/ReadLine.Demo/TimerDemo.cs b/src/ReadLine.Demo/TimerDemo.cs new file mode 100644 index 0000000..772054a --- /dev/null +++ b/src/ReadLine.Demo/TimerDemo.cs @@ -0,0 +1,23 @@ +using System; + +namespace ConsoleApplication +{ + public class Program + { + public static void Main(string[] args) + { + Console.WriteLine("ReadLine Library Timer Demo"); + Console.WriteLine(); + + while (true) + { + string input = ReadLine.Read("(prompt)> "); + if (input.Length == 0) + { + continue; + } + Console.WriteLine("You typed: \"" + input + "\""); + } + } + } +} From c420c14e77c051bd814d9cbec952f9b013b63b6a Mon Sep 17 00:00:00 2001 From: Andrew Plotkin Date: Thu, 27 Jul 2017 11:47:01 -0400 Subject: [PATCH 03/11] A CheckInterrupt global which can stop the readline. --- src/ReadLine.Demo/TimerDemo.cs | 3 +++ src/ReadLine/ReadLine.cs | 38 ++++++++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/ReadLine.Demo/TimerDemo.cs b/src/ReadLine.Demo/TimerDemo.cs index 772054a..95907b2 100644 --- a/src/ReadLine.Demo/TimerDemo.cs +++ b/src/ReadLine.Demo/TimerDemo.cs @@ -9,6 +9,9 @@ public static void Main(string[] args) Console.WriteLine("ReadLine Library Timer Demo"); Console.WriteLine(); + ReadLine.InterruptInterval = 1000; + ReadLine.CheckInterrupt = () => true; + while (true) { string input = ReadLine.Read("(prompt)> "); diff --git a/src/ReadLine/ReadLine.cs b/src/ReadLine/ReadLine.cs index f505166..00fa8d3 100755 --- a/src/ReadLine/ReadLine.cs +++ b/src/ReadLine/ReadLine.cs @@ -1,6 +1,7 @@ using Internal.ReadLine; using Internal.ReadLine.Abstractions; +using System.Diagnostics; using System.Threading; using System.Collections.Generic; @@ -21,6 +22,8 @@ static ReadLine() public static void ClearHistory() => _history = new List(); public static Func AutoCompletionHandler { private get; set; } public static bool PasswordMode { private get; set; } + public static int InterruptInterval = 5; + public static Func CheckInterrupt; public static string Read(string prompt = "", string defaultInput = "", string initialInput = "") { @@ -30,8 +33,20 @@ public static string Read(string prompt = "", string defaultInput = "", string i bool done = false; + Stopwatch stopwatch = null; + int sleeptime = InterruptInterval; + if (sleeptime > 5) + { + sleeptime = 5; + if (CheckInterrupt != null) + { + stopwatch = new Stopwatch(); + stopwatch.Start(); + } + } + while (!done) { - while (Console.KeyAvailable) + while (!done && Console.KeyAvailable) { ConsoleKeyInfo keyInfo = Console.ReadKey(true); if (keyInfo.Key == ConsoleKey.Enter) @@ -42,9 +57,28 @@ public static string Read(string prompt = "", string defaultInput = "", string i _keyHandler.Handle(keyInfo); } + if (!done && CheckInterrupt != null) + { + if (stopwatch == null) + { + // Check every tick. + done = CheckInterrupt(); + } + else + { + // Check if enough time has elapsed. + var elapsed = stopwatch.ElapsedMilliseconds; + if (elapsed >= InterruptInterval) + { + stopwatch.Reset(); + done = CheckInterrupt(); + } + } + } + if (!done) { - Thread.Sleep(50); + Thread.Sleep(sleeptime); } } From be9c913ecf136570505070d42aa31b5e5fc4a5fb Mon Sep 17 00:00:00 2001 From: Andrew Plotkin Date: Thu, 27 Jul 2017 13:08:20 -0400 Subject: [PATCH 04/11] Provide a flag for whether we're in a read. --- src/ReadLine/ReadLine.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ReadLine/ReadLine.cs b/src/ReadLine/ReadLine.cs index 00fa8d3..edbdb8a 100755 --- a/src/ReadLine/ReadLine.cs +++ b/src/ReadLine/ReadLine.cs @@ -11,6 +11,7 @@ public static class ReadLine { private static KeyHandler _keyHandler; private static List _history; + private static bool _active; static ReadLine() { @@ -24,9 +25,11 @@ static ReadLine() public static bool PasswordMode { private get; set; } public static int InterruptInterval = 5; public static Func CheckInterrupt; + public static bool IsReading => _active; public static string Read(string prompt = "", string defaultInput = "", string initialInput = "") { + _active = true; Console.Write(prompt); _keyHandler = new KeyHandler(new Console2() { PasswordMode = PasswordMode }, initialInput, _history, AutoCompletionHandler); @@ -90,6 +93,7 @@ public static string Read(string prompt = "", string defaultInput = "", string i else _history.Add(text); + _active = false; return text; } } From 4af20f6082a322d7bc702ec9e98aa67a8325f601 Mon Sep 17 00:00:00 2001 From: Andrew Plotkin Date: Mon, 31 Jul 2017 14:23:54 -0400 Subject: [PATCH 05/11] Call stopwatch.restart, not stopwatch.reset, you idiot. --- src/ReadLine/ReadLine.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ReadLine/ReadLine.cs b/src/ReadLine/ReadLine.cs index edbdb8a..4d83d04 100755 --- a/src/ReadLine/ReadLine.cs +++ b/src/ReadLine/ReadLine.cs @@ -58,6 +58,7 @@ public static string Read(string prompt = "", string defaultInput = "", string i break; } _keyHandler.Handle(keyInfo); + Thread.Sleep(0); } if (!done && CheckInterrupt != null) @@ -73,7 +74,7 @@ public static string Read(string prompt = "", string defaultInput = "", string i var elapsed = stopwatch.ElapsedMilliseconds; if (elapsed >= InterruptInterval) { - stopwatch.Reset(); + stopwatch.Restart(); done = CheckInterrupt(); } } From b0a0bf99d3192e9e4dc39d0418a7e06ecd30bb28 Mon Sep 17 00:00:00 2001 From: Andrew Plotkin Date: Tue, 1 Aug 2017 10:54:05 -0400 Subject: [PATCH 06/11] Static class initializer is an unnecessary complication. --- src/ReadLine/ReadLine.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/ReadLine/ReadLine.cs b/src/ReadLine/ReadLine.cs index 4d83d04..a580cee 100755 --- a/src/ReadLine/ReadLine.cs +++ b/src/ReadLine/ReadLine.cs @@ -10,14 +10,9 @@ namespace System public static class ReadLine { private static KeyHandler _keyHandler; - private static List _history; + private static List _history = new List(); private static bool _active; - static ReadLine() - { - _history = new List(); - } - public static void AddHistory(params string[] text) => _history.AddRange(text); public static List GetHistory() => _history; public static void ClearHistory() => _history = new List(); From 84273f3c8ada37e3193599d2ba127b6f25da3f06 Mon Sep 17 00:00:00 2001 From: Andrew Plotkin Date: Tue, 1 Aug 2017 11:04:35 -0400 Subject: [PATCH 07/11] Interruption flag. --- src/ReadLine.Demo/TimerDemo.cs | 18 ++++++++++++++---- src/ReadLine/ReadLine.cs | 26 +++++++++++++++++++++++--- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/ReadLine.Demo/TimerDemo.cs b/src/ReadLine.Demo/TimerDemo.cs index 95907b2..d0aa9a2 100644 --- a/src/ReadLine.Demo/TimerDemo.cs +++ b/src/ReadLine.Demo/TimerDemo.cs @@ -12,14 +12,24 @@ public static void Main(string[] args) ReadLine.InterruptInterval = 1000; ReadLine.CheckInterrupt = () => true; + string initial = ""; + while (true) { - string input = ReadLine.Read("(prompt)> "); - if (input.Length == 0) + ReadLine.ReadLineResult info = ReadLine.ReadExt("(prompt)> ", "", initial); + if (info.Interrupted) + { + initial = info.Result; + Console.WriteLine("Tick..."); + } + else { - continue; + initial = ""; + if (info.Result.Length != 0) + { + Console.WriteLine("You typed: \"" + info.Result + "\""); + } } - Console.WriteLine("You typed: \"" + input + "\""); } } } diff --git a/src/ReadLine/ReadLine.cs b/src/ReadLine/ReadLine.cs index a580cee..75d7763 100755 --- a/src/ReadLine/ReadLine.cs +++ b/src/ReadLine/ReadLine.cs @@ -9,6 +9,12 @@ namespace System { public static class ReadLine { + public struct ReadLineResult + { + public string Result; + public bool Interrupted; + } + private static KeyHandler _keyHandler; private static List _history = new List(); private static bool _active; @@ -23,6 +29,12 @@ public static class ReadLine public static bool IsReading => _active; public static string Read(string prompt = "", string defaultInput = "", string initialInput = "") + { + ReadLineResult res = ReadExt(prompt, defaultInput, initialInput); + return res.Result; + } + + public static ReadLineResult ReadExt(string prompt = "", string defaultInput = "", string initialInput = "") { _active = true; Console.Write(prompt); @@ -30,6 +42,7 @@ public static string Read(string prompt = "", string defaultInput = "", string i _keyHandler = new KeyHandler(new Console2() { PasswordMode = PasswordMode }, initialInput, _history, AutoCompletionHandler); bool done = false; + bool interrupted = false; Stopwatch stopwatch = null; int sleeptime = InterruptInterval; @@ -61,7 +74,7 @@ public static string Read(string prompt = "", string defaultInput = "", string i if (stopwatch == null) { // Check every tick. - done = CheckInterrupt(); + interrupted = CheckInterrupt(); } else { @@ -70,9 +83,13 @@ public static string Read(string prompt = "", string defaultInput = "", string i if (elapsed >= InterruptInterval) { stopwatch.Restart(); - done = CheckInterrupt(); + interrupted = CheckInterrupt(); } } + if (interrupted) + { + done = true; + } } if (!done) @@ -90,7 +107,10 @@ public static string Read(string prompt = "", string defaultInput = "", string i _history.Add(text); _active = false; - return text; + return new ReadLineResult() { + Result = text, + Interrupted = interrupted + }; } } } From 7e4193c23a6e6fcaa8e3ac71d0c70ac890fbb049 Mon Sep 17 00:00:00 2001 From: Andrew Plotkin Date: Tue, 1 Aug 2017 11:18:15 -0400 Subject: [PATCH 08/11] README notes. --- README.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/README.md b/README.md index c455942..53d9283 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,39 @@ ReadLine.AutoCompletionHandler = (t, s) => _Note: If no "AutoCompletionHandler" is set, tab autocompletion will be disabled_ +### Initial Buffer + +```csharp +string input = ReadLine.Read("(prompt)> ", "", "initial"); +``` + +The editing session will begin with "initial" in the readline buffer. + +### Background Processing + +```csharp +ReadLine.CheckInterrupt = () => +{ + bool interrupt = /* whether to interrupt the readline */ + return interrupt; +} +ReadLine.InterruptInterval = 1000; /* milliseconds */ +``` + +Every `InterruptInterval` milliseconds, the library will silently run the `CheckInterrupt()` function. If this returns true, the read operation will stop immediately and return what the user has typed so far. + +The `CheckInterrupt()` function should not print anything, as this will be mixed in with the editing line. Do your printing after the read operation stops. + +If you want to know whether the operation was interrupted: + +```csharp +ReadLine.ReadLineResult info = ReadLine.ReadExt("(prompt)> "); +``` + +`info.Result` is the string result. `info.Interrupted` is a bool indicating whether the operation was stopped by the interrupt routine or by the user hitting Enter. + +By combining background processing and the initial buffer option in a loop, you can achieve the effect of an interruption which prints something and then resumes editing. See the [TimerDemo.cs](src/ReadLine.Demo/TimerDemo.cs) demo. + ## Contributing Contributions are highly welcome. If you have found a bug or if you have a feature request, please report them at this repository issues section. From 6947270877044754cb4bcaba5cd6590a313ac56c Mon Sep 17 00:00:00 2001 From: Andrew Plotkin Date: Tue, 1 Aug 2017 11:26:30 -0400 Subject: [PATCH 09/11] Comments. --- src/ReadLine/ReadLine.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ReadLine/ReadLine.cs b/src/ReadLine/ReadLine.cs index 75d7763..4c2176b 100755 --- a/src/ReadLine/ReadLine.cs +++ b/src/ReadLine/ReadLine.cs @@ -28,6 +28,7 @@ public struct ReadLineResult public static Func CheckInterrupt; public static bool IsReading => _active; + // Wrapper for users who don't care about ReadLineResult. public static string Read(string prompt = "", string defaultInput = "", string initialInput = "") { ReadLineResult res = ReadExt(prompt, defaultInput, initialInput); @@ -44,6 +45,9 @@ public static ReadLineResult ReadExt(string prompt = "", string defaultInput = " bool done = false; bool interrupted = false; + /* We need to poll KeyAvailable very frequently so that typing doesn't feel laggy. So we cap the sleep interval at a maximum of 5 ms; if the InterruptInterval is longer than that, we set a Stopwatch to know when to call CheckInterrupt. + (We should be calling Console.ReadKey with a timeout, but C# doesn't support that, sigh.) + */ Stopwatch stopwatch = null; int sleeptime = InterruptInterval; if (sleeptime > 5) @@ -57,6 +61,7 @@ public static ReadLineResult ReadExt(string prompt = "", string defaultInput = " } while (!done) { + // Handle all keys that have come in. while (!done && Console.KeyAvailable) { ConsoleKeyInfo keyInfo = Console.ReadKey(true); @@ -69,11 +74,12 @@ public static ReadLineResult ReadExt(string prompt = "", string defaultInput = " Thread.Sleep(0); } + // Handle the timer, if there is one. if (!done && CheckInterrupt != null) { if (stopwatch == null) { - // Check every tick. + // Check every 5ms tick. interrupted = CheckInterrupt(); } else From dcffa641cde784a1974389411ab2d020a19a1a27 Mon Sep 17 00:00:00 2001 From: Andrew Plotkin Date: Tue, 1 Aug 2017 11:28:37 -0400 Subject: [PATCH 10/11] The timer demo is clearer if we break after input. --- src/ReadLine.Demo/TimerDemo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ReadLine.Demo/TimerDemo.cs b/src/ReadLine.Demo/TimerDemo.cs index d0aa9a2..b4d9345 100644 --- a/src/ReadLine.Demo/TimerDemo.cs +++ b/src/ReadLine.Demo/TimerDemo.cs @@ -28,6 +28,7 @@ public static void Main(string[] args) if (info.Result.Length != 0) { Console.WriteLine("You typed: \"" + info.Result + "\""); + break; } } } From d3f2203e29ae7e1162ea650d07dd4d718172cd0c Mon Sep 17 00:00:00 2001 From: Andrew Plotkin Date: Thu, 24 Aug 2017 17:49:31 -0400 Subject: [PATCH 11/11] Improve the history list. Blank lines and duplicate lines are no longer put into history. (That is, a line is not added if it is blank or matches the most recent history entry.) A partial (interrupted) input is also not added. --- src/ReadLine/ReadLine.cs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/ReadLine/ReadLine.cs b/src/ReadLine/ReadLine.cs index 4c2176b..9bd16d1 100755 --- a/src/ReadLine/ReadLine.cs +++ b/src/ReadLine/ReadLine.cs @@ -107,10 +107,24 @@ public static ReadLineResult ReadExt(string prompt = "", string defaultInput = " Console.WriteLine(); string text = _keyHandler.Text; - if (String.IsNullOrWhiteSpace(text) && !String.IsNullOrWhiteSpace(defaultInput)) - text = defaultInput; + + /* If the entry was blank, we substitute the default value (if there was one). If the entry was non-blank, we add it to history. */ + if (String.IsNullOrWhiteSpace(text)) + { + if ((!interrupted) && (!String.IsNullOrWhiteSpace(defaultInput))) + { + text = defaultInput; + } + } else - _history.Add(text); + { + /* We never add blank or default values to the history list. We also don't add value that duplicates the most recent value. */ + var len = _history.Count; + if (len == 0 || text != _history[len-1]) + { + _history.Add(text); + } + } _active = false; return new ReadLineResult() {