Browse Source

Update handling to have state

tznind 10 months ago
parent
commit
a0c3363210

+ 84 - 33
Terminal.Gui/ConsoleDrivers/AnsiResponseParser.cs

@@ -14,6 +14,17 @@ class AnsiResponseParser
 
 
     private List<Func<string, bool>> _ignorers = new ();
     private List<Func<string, bool>> _ignorers = new ();
 
 
+    // Enum to manage the parser's state
+    private enum ParserState
+    {
+        Normal,
+        ExpectingBracket,
+        InResponse
+    }
+
+    // Current state of the parser
+    private ParserState currentState = ParserState.Normal;
+
     /*
     /*
      * ANSI Input Sequences
      * ANSI Input Sequences
      *
      *
@@ -33,10 +44,20 @@ class AnsiResponseParser
 
 
     public AnsiResponseParser ()
     public AnsiResponseParser ()
     {
     {
-        // How to spot when you have entered and left an AnsiResponse but not the one we are looking for
-        _ignorers.Add (s=>s.StartsWith ("\x1B[<") && s.EndsWith ("M"));
+        // Add more common ANSI sequences to be ignored
+        _ignorers.Add (s => s.StartsWith ("\x1B[<") && s.EndsWith ("M"));  // Mouse event
+        _ignorers.Add (s => s.StartsWith ("\x1B[") && s.EndsWith ("A"));   // Up arrow
+        _ignorers.Add (s => s.StartsWith ("\x1B[") && s.EndsWith ("B"));   // Down arrow
+        _ignorers.Add (s => s.StartsWith ("\x1B[") && s.EndsWith ("C"));   // Right arrow
+        _ignorers.Add (s => s.StartsWith ("\x1B[") && s.EndsWith ("D"));   // Left arrow
+        _ignorers.Add (s => s.StartsWith ("\x1B[3~"));                     // Delete
+        _ignorers.Add (s => s.StartsWith ("\x1B[5~"));                     // Page Up
+        _ignorers.Add (s => s.StartsWith ("\x1B[6~"));                     // Page Down
+        _ignorers.Add (s => s.StartsWith ("\x1B[2~"));                     // Insert
+        // Add more if necessary
     }
     }
 
 
+
     /// <summary>
     /// <summary>
     /// Processes input which may be a single character or multiple.
     /// Processes input which may be a single character or multiple.
     /// Returns what should be passed on to any downstream input processing
     /// Returns what should be passed on to any downstream input processing
@@ -51,42 +72,71 @@ class AnsiResponseParser
         {
         {
             char currentChar = input [index];
             char currentChar = input [index];
 
 
-            if (inResponse)
+            switch (currentState)
             {
             {
-                // If we are in a response, accumulate characters in `held`
-                held.Append (currentChar);
-
-                // Handle the current content in `held`
-                var handled = HandleHeldContent ();
-                if (!string.IsNullOrEmpty (handled))
-                {
-                    // If content is ready to be released, append it to output and reset state
-                    output.Append (handled);
-                    inResponse = false;
-                    held.Clear ();
-                }
-
-                index++;
-                continue;
+                case ParserState.Normal:
+                    if (currentChar == '\x1B')
+                    {
+                        // Escape character detected, move to ExpectingBracket state
+                        currentState = ParserState.ExpectingBracket;
+                        held.Append (currentChar);  // Hold the escape character
+                        index++;
+                    }
+                    else
+                    {
+                        // Normal character, append to output
+                        output.Append (currentChar);
+                        index++;
+                    }
+                    break;
+
+                case ParserState.ExpectingBracket:
+                    if (currentChar == '[' || currentChar == ']')
+                    {
+                        // Detected '[' or ']', transition to InResponse state
+                        currentState = ParserState.InResponse;
+                        held.Append (currentChar);  // Hold the '[' or ']'
+                        index++;
+                    }
+                    else
+                    {
+                        // Invalid sequence, release held characters and reset to Normal
+                        output.Append (held.ToString ());
+                        output.Append (currentChar);  // Add current character
+                        ResetState ();
+                        index++;
+                    }
+                    break;
+
+                case ParserState.InResponse:
+                    held.Append (currentChar);
+
+                    // Check if the held content should be released
+                    var handled = HandleHeldContent ();
+                    if (!string.IsNullOrEmpty (handled))
+                    {
+                        output.Append (handled);
+                        ResetState ();  // Exit response mode and reset
+                    }
+
+                    index++;
+                    break;
             }
             }
+        }
 
 
-            // If character is the start of an escape sequence
-            if (currentChar == '\x1B')
-            {
-                // Start capturing the ANSI response sequence
-                inResponse = true;
-                held.Append (currentChar);
-                index++;
-                continue;
-            }
+        return output.ToString ();  // Return all characters that passed through
+    }
 
 
-            // If not in an ANSI response, pass the character through as regular input
-            output.Append (currentChar);
-            index++;
-        }
 
 
-        // Return characters that should pass through as regular input
-        return output.ToString ();
+    /// <summary>
+    /// Resets the parser's state when a response is handled or finished.
+    /// </summary>
+    private void ResetState ()
+    {
+        currentState = ParserState.Normal;
+        held.Clear ();
+        currentTerminator = null;
+        currentResponse = null;
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -124,4 +174,5 @@ class AnsiResponseParser
         currentTerminator = terminator;
         currentTerminator = terminator;
         currentResponse = response;
         currentResponse = response;
     }
     }
+
 }
 }

+ 4 - 3
UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs

@@ -32,11 +32,11 @@ public class AnsiResponseParserTests
         // Regular user typing
         // Regular user typing
         for (int c = 0; c < "Hello".Length; c++)
         for (int c = 0; c < "Hello".Length; c++)
         {
         {
-            AssertIgnored (ansiStream, ref i);
+            AssertIgnored (ansiStream,"Hello"[c], ref i);
         }
         }
 
 
         // Now we have entered the actual DAR we should be consuming these
         // Now we have entered the actual DAR we should be consuming these
-        for (int c = 0; c < "\x1B [0".Length; c++)
+        for (int c = 0; c < "\x1B[0".Length; c++)
         {
         {
             AssertConsumed (ansiStream, ref i);
             AssertConsumed (ansiStream, ref i);
         }
         }
@@ -48,12 +48,13 @@ public class AnsiResponseParserTests
         Assert.Equal ("\u001b[0c", response);
         Assert.Equal ("\u001b[0c", response);
     }
     }
 
 
-    private void AssertIgnored (string ansiStream, ref int i)
+    private void AssertIgnored (string ansiStream,char expected, ref int i)
     {
     {
         var c = NextChar (ansiStream, ref i);
         var c = NextChar (ansiStream, ref i);
 
 
         // Parser does not grab this key (i.e. driver can continue with regular operations)
         // Parser does not grab this key (i.e. driver can continue with regular operations)
         Assert.Equal ( c,_parser.ProcessInput (c));
         Assert.Equal ( c,_parser.ProcessInput (c));
+        Assert.Equal (expected,c.Single());
     }
     }
     private void AssertConsumed (string ansiStream, ref int i)
     private void AssertConsumed (string ansiStream, ref int i)
     {
     {