Browse Source

Fix base class dropping T all over the place

tznind 10 months ago
parent
commit
16a787e196

+ 46 - 18
Terminal.Gui/ConsoleDrivers/AnsiResponseParser.cs

@@ -103,57 +103,70 @@ namespace Terminal.Gui;
 
 
         public abstract void ClearHeld ();
         public abstract void ClearHeld ();
         protected abstract string HeldToString ();
         protected abstract string HeldToString ();
-        protected abstract void AddToHeld (char c);
+        protected abstract IEnumerable<object> HeldToObjects ();
+        protected abstract void AddToHeld (object o);
 
 
         // Base method for processing input
         // Base method for processing input
-        public void ProcessInputBase (Func<int, char> getCharAtIndex, Action<char> appendOutput, int inputLength)
+        public void ProcessInputBase (
+            Func<int, char> getCharAtIndex,
+            Func<int, object> getObjectAtIndex,
+            Action<object> appendOutput,
+            int inputLength)
         {
         {
             var index = 0; // Tracks position in the input string
             var index = 0; // Tracks position in the input string
 
 
             while (index < inputLength)
             while (index < inputLength)
             {
             {
                 var currentChar = getCharAtIndex (index);
                 var currentChar = getCharAtIndex (index);
+                var currentObj = getObjectAtIndex (index);
+
+                bool isEscape = currentChar == '\x1B';
 
 
                 switch (State)
                 switch (State)
                 {
                 {
                     case ParserState.Normal:
                     case ParserState.Normal:
-                        if (currentChar == '\x1B')
+                        if (isEscape)
                         {
                         {
                             // Escape character detected, move to ExpectingBracket state
                             // Escape character detected, move to ExpectingBracket state
                             State = ParserState.ExpectingBracket;
                             State = ParserState.ExpectingBracket;
-                            AddToHeld (currentChar); // Hold the escape character
+                            AddToHeld (currentObj); // Hold the escape character
                         }
                         }
                         else
                         else
                         {
                         {
                             // Normal character, append to output
                             // Normal character, append to output
-                            appendOutput (currentChar);
+                            appendOutput (currentObj);
                         }
                         }
                         break;
                         break;
 
 
                     case ParserState.ExpectingBracket:
                     case ParserState.ExpectingBracket:
+                        if (isEscape)
+                        {
+                            // Second escape so we must release first
+                            ReleaseHeld (appendOutput, ParserState.ExpectingBracket);
+                            AddToHeld (currentObj); // Hold the new escape
+                        }
+                        else
                         if (currentChar == '[')
                         if (currentChar == '[')
                         {
                         {
                             // Detected '[', transition to InResponse state
                             // Detected '[', transition to InResponse state
                             State = ParserState.InResponse;
                             State = ParserState.InResponse;
-                            AddToHeld (currentChar); // Hold the '['
+                            AddToHeld (currentObj); // Hold the '['
                         }
                         }
                         else
                         else
                         {
                         {
                             // Invalid sequence, release held characters and reset to Normal
                             // Invalid sequence, release held characters and reset to Normal
                             ReleaseHeld (appendOutput);
                             ReleaseHeld (appendOutput);
-                            appendOutput (currentChar); // Add current character
-                            ResetState ();
+                            appendOutput (currentObj); // Add current character
                         }
                         }
                         break;
                         break;
 
 
                     case ParserState.InResponse:
                     case ParserState.InResponse:
-                        AddToHeld (currentChar);
+                        AddToHeld (currentObj);
 
 
                         // Check if the held content should be released
                         // Check if the held content should be released
                         if (ShouldReleaseHeldContent ())
                         if (ShouldReleaseHeldContent ())
                         {
                         {
                             ReleaseHeld (appendOutput);
                             ReleaseHeld (appendOutput);
-                            ResetState (); // Exit response mode and reset
                         }
                         }
                         break;
                         break;
                 }
                 }
@@ -162,13 +175,17 @@ namespace Terminal.Gui;
             }
             }
         }
         }
 
 
-        private void ReleaseHeld (Action<char> appendOutput)
+
+        private void ReleaseHeld (Action<object> appendOutput, ParserState newState = ParserState.Normal)
         {
         {
-            foreach (var c in HeldToString ())
+            foreach (var o in HeldToObjects ())
             {
             {
-                appendOutput (c);
+                appendOutput (o);
             }
             }
-        }
+
+            State = newState;
+            ClearHeld ();
+    }
 
 
         // Common response handler logic
         // Common response handler logic
         protected bool ShouldReleaseHeldContent ()
         protected bool ShouldReleaseHeldContent ()
@@ -214,7 +231,11 @@ namespace Terminal.Gui;
         public IEnumerable<Tuple<char, T>> ProcessInput (params Tuple<char, T> [] input)
         public IEnumerable<Tuple<char, T>> ProcessInput (params Tuple<char, T> [] input)
         {
         {
             var output = new List<Tuple<char, T>> ();
             var output = new List<Tuple<char, T>> ();
-            ProcessInputBase (i => input [i].Item1, c => output.Add (new Tuple<char, T> (c, input [0].Item2)), input.Length);
+            ProcessInputBase (
+                              i => input [i].Item1,
+                              i => input [i],
+                              c => output.Add ((Tuple<char, T>)c),
+                              input.Length);
             return output;
             return output;
         }
         }
 
 
@@ -231,7 +252,9 @@ namespace Terminal.Gui;
 
 
         protected override string HeldToString () => new string (held.Select (h => h.Item1).ToArray ());
         protected override string HeldToString () => new string (held.Select (h => h.Item1).ToArray ());
 
 
-        protected override void AddToHeld (char c) => held.Add (new Tuple<char, T> (c, default!));
+        protected override IEnumerable<object> HeldToObjects () => held;
+
+        protected override void AddToHeld (object o) => held.Add ((Tuple<char, T>)o);
 
 
 
 
 }
 }
@@ -243,7 +266,11 @@ namespace Terminal.Gui;
         public string ProcessInput (string input)
         public string ProcessInput (string input)
         {
         {
             var output = new StringBuilder ();
             var output = new StringBuilder ();
-            ProcessInputBase (i => input [i], c => output.Append (c), input.Length);
+            ProcessInputBase (
+                              i => input [i],
+                              i => input [i], // For string there is no T so object is same as char
+                              c => output.Append ((char)c),
+                              input.Length);
             return output.ToString ();
             return output.ToString ();
         }
         }
         public string Release ()
         public string Release ()
@@ -257,5 +284,6 @@ namespace Terminal.Gui;
 
 
         protected override string HeldToString () => held.ToString ();
         protected override string HeldToString () => held.ToString ();
 
 
-        protected override void AddToHeld (char c) => held.Append (c);
+        protected override IEnumerable<object> HeldToObjects () => held.ToString().Select(c => (object) c).ToArray ();
+        protected override void AddToHeld (object o) => held.Append ((char)o);
     }
     }

+ 70 - 3
UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs

@@ -8,6 +8,11 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
     AnsiResponseParser<int> _parser1 = new AnsiResponseParser<int> ();
     AnsiResponseParser<int> _parser1 = new AnsiResponseParser<int> ();
     AnsiResponseParser _parser2 = new AnsiResponseParser ();
     AnsiResponseParser _parser2 = new AnsiResponseParser ();
 
 
+    /// <summary>
+    /// Used for the T value in batches that are passed to the  AnsiResponseParser&lt;int&gt;  (parser1)
+    /// </summary>
+    private int tIndex = 0;
+
     [Fact]
     [Fact]
     public void TestInputProcessing ()
     public void TestInputProcessing ()
     {
     {
@@ -109,6 +114,7 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
 
 
         foreach (var batchSet in permutations)
         foreach (var batchSet in permutations)
         {
         {
+            tIndex = 0;
             string response1 = string.Empty;
             string response1 = string.Empty;
             string response2 = string.Empty;
             string response2 = string.Empty;
 
 
@@ -140,9 +146,47 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
         output.WriteLine ($"Tested {tests} in {swRunTest.ElapsedMilliseconds} ms (gen batches took {swGenBatches.ElapsedMilliseconds} ms)" );
         output.WriteLine ($"Tested {tests} in {swRunTest.ElapsedMilliseconds} ms (gen batches took {swGenBatches.ElapsedMilliseconds} ms)" );
     }
     }
 
 
+    [Fact]
+    public void ReleasesEscapeAfterTimeout ()
+    {
+        string input = "\x1B";
+        int i = 0;
+
+        // Esc on its own looks like it might be an esc sequence so should be consumed
+        AssertConsumed (input,ref i);
+
+        // We should know when the state changed
+        Assert.Equal (ParserState.ExpectingBracket, _parser1.State);
+        Assert.Equal (ParserState.ExpectingBracket, _parser2.State);
+
+        Assert.Equal (DateTime.Now.Date, _parser1.StateChangedAt.Date);
+        Assert.Equal (DateTime.Now.Date, _parser2.StateChangedAt.Date);
+
+        AssertManualReleaseIs (input);
+    }
+
+
+    [Fact]
+    public void TwoExcapesInARow ()
+    {
+        // Example user presses Esc key then a DAR comes in
+        string input = "\x1B\x1B";
+        int i = 0;
+
+        // First Esc gets grabbed
+        AssertConsumed (input, ref i);
+
+        // Upon getting the second Esc we should release the first
+        AssertReleased (input, ref i, "\x1B",0);
+
+        // Assume 50ms or something has passed, lets force release as no new content
+        // It should be the second escape that gets released (i.e. index 1)
+        AssertManualReleaseIs (input,1);
+    }
+
     private Tuple<char, int> [] StringToBatch (string batch)
     private Tuple<char, int> [] StringToBatch (string batch)
     {
     {
-        return batch.Select ((k, i) => Tuple.Create (k, i)).ToArray ();
+        return batch.Select ((k) => Tuple.Create (k, tIndex++)).ToArray ();
     }
     }
 
 
     public static IEnumerable<string []> GetBatchPermutations (string input, int maxDepth = 3)
     public static IEnumerable<string []> GetBatchPermutations (string input, int maxDepth = 3)
@@ -207,15 +251,21 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
         Assert.Empty (_parser1.ProcessInput(c1));
         Assert.Empty (_parser1.ProcessInput(c1));
         Assert.Empty (_parser2.ProcessInput (c2.ToString()));
         Assert.Empty (_parser2.ProcessInput (c2.ToString()));
     }
     }
-    private void AssertReleased (string ansiStream, ref int i, string expectedRelease)
+
+    private void AssertReleased (string ansiStream, ref int i, string expectedRelease, params int[] expectedTValues)
     {
     {
         var c2 = ansiStream [i];
         var c2 = ansiStream [i];
         var c1 = NextChar (ansiStream, ref i);
         var c1 = NextChar (ansiStream, ref i);
 
 
         // Parser realizes it has grabbed content that does not belong to an outstanding request
         // Parser realizes it has grabbed content that does not belong to an outstanding request
         // Parser returns false to indicate to continue
         // Parser returns false to indicate to continue
-        Assert.Equal(expectedRelease,BatchToString(_parser1.ProcessInput (c1)));
+        var released1 = _parser1.ProcessInput (c1).ToArray ();
+        Assert.Equal (expectedRelease, BatchToString (released1));
 
 
+        if (expectedTValues.Length > 0)
+        {
+            Assert.True (expectedTValues.SequenceEqual (released1.Select (kv=>kv.Item2)));
+        }
 
 
         Assert.Equal (expectedRelease, _parser2.ProcessInput (c2.ToString ()));
         Assert.Equal (expectedRelease, _parser2.ProcessInput (c2.ToString ()));
     }
     }
@@ -229,4 +279,21 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
     {
     {
         return  StringToBatch(ansiStream [i++].ToString());
         return  StringToBatch(ansiStream [i++].ToString());
     }
     }
+    private void AssertManualReleaseIs (string expectedRelease, params int [] expectedTValues)
+    {
+
+        // Consumer is responsible for determining this based on  e.g. after 50ms
+        var released1 = _parser1.Release ().ToArray ();
+        Assert.Equal (expectedRelease, BatchToString (released1));
+
+        if (expectedTValues.Length > 0)
+        {
+            Assert.True (expectedTValues.SequenceEqual (released1.Select (kv => kv.Item2)));
+        }
+
+        Assert.Equal (expectedRelease, _parser2.Release ());
+
+        Assert.Equal (ParserState.Normal, _parser1.State);
+        Assert.Equal (ParserState.Normal, _parser2.State);
+    }
 }
 }