Browse Source

prepare to handle more rules

tznind 10 months ago
parent
commit
75ec589073
1 changed files with 73 additions and 39 deletions
  1. 73 39
      Terminal.Gui/ConsoleDrivers/AnsiResponseParser.cs

+ 73 - 39
Terminal.Gui/ConsoleDrivers/AnsiResponseParser.cs

@@ -1,10 +1,18 @@
 #nullable enable
 #nullable enable
 
 
 using System.Diagnostics;
 using System.Diagnostics;
+using System.Text;
 
 
 namespace Terminal.Gui;
 namespace Terminal.Gui;
 class AnsiResponseParser
 class AnsiResponseParser
 {
 {
+    private bool inResponse = false;
+    private StringBuilder held = new StringBuilder ();
+    private string? currentTerminator = null;
+    private Action<string>? currentResponse = null;
+
+
+    private List<Func<string, bool>> _ignorers = new ();
 
 
     /*
     /*
      * ANSI Input Sequences
      * ANSI Input Sequences
@@ -23,71 +31,97 @@ class AnsiResponseParser
      * \x1B[0c  // Device Attributes Response (e.g., terminal identification)
      * \x1B[0c  // Device Attributes Response (e.g., terminal identification)
      */
      */
 
 
-    private bool inResponse = false;
-
-    private StringBuilder held = new StringBuilder();
+    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"));
+    }
 
 
     /// <summary>
     /// <summary>
-    /// <para>
     /// 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
-    /// (i.e. removes expected Ansi responses from the input stream
-    /// </para>
-    /// <para>
-    /// This method is designed to be called iteratively and as such may
-    /// return more characters than were passed in depending on previous
-    /// calls (e.g. if it was in the middle of an unrelated ANSI response.</para>
+    /// (i.e., removes expected ANSI responses from the input stream).
     /// </summary>
     /// </summary>
-    /// <param name="input"></param>
-    /// <returns></returns>
     public string ProcessInput (string input)
     public string ProcessInput (string input)
     {
     {
+        StringBuilder output = new StringBuilder ();  // Holds characters that should pass through
+        int index = 0;  // Tracks position in the input string
 
 
-        if (inResponse)
+        while (index < input.Length)
         {
         {
-            if (currentTerminator != null && input.StartsWith (currentTerminator))
-            {
-                // Consume terminator and release the event
-                held.Append (currentTerminator);
-                currentResponse?.Invoke (held.ToString());
+            char currentChar = input [index];
 
 
-                // clear the state
-                held.Clear ();
-                currentResponse = null;
+            if (inResponse)
+            {
+                // 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;
+            }
 
 
-                // recurse
-                return ProcessInput (input.Substring (currentTerminator.Length));
+            // 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;
             }
             }
 
 
-            // we are in a response but have not reached terminator yet
-            held.Append (input [0]);
-            return ProcessInput (input.Substring (1));
+            // 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 ();
+    }
 
 
-        // if character is escape
-        if (input.StartsWith ('\x1B'))
+    /// <summary>
+    /// Checks the current `held` content to decide whether it should be released, either as an expected or unexpected response.
+    /// </summary>
+    private string HandleHeldContent ()
+    {
+        // If we're expecting a specific terminator, check if the content matches
+        if (currentTerminator != null && held.ToString ().EndsWith (currentTerminator))
         {
         {
-            // We shouldn't get an escape in the middle of a response - TODO: figure out how to handle that
-            Debug.Assert (!inResponse);
-
+            // If it matches the expected response, invoke the callback and return nothing for output
+            currentResponse?.Invoke (held.ToString ());
+            return string.Empty;
+        }
 
 
-            // consume the escape
-            held.Append (input [0]);
-            inResponse = true;
-            return ProcessInput (input.Substring (1));
+        // Handle common ANSI sequences (such as mouse input or arrow keys)
+        if (_ignorers.Any(m=>m.Invoke (held.ToString())))
+        {
+            // Detected mouse input, release it without triggering the delegate
+            return held.ToString ();
         }
         }
 
 
-        return input[0] + ProcessInput (input.Substring (1));
+        // Add more cases here for other standard sequences (like arrow keys, function keys, etc.)
+
+        // If no match, continue accumulating characters
+        return string.Empty;
     }
     }
 
 
-    private string? currentTerminator = null;
-    private Action<string>? currentResponse = null;
 
 
+    /// <summary>
+    /// Registers a new expected ANSI response with a specific terminator and a callback for when the response is completed.
+    /// </summary>
     public void ExpectResponse (string terminator, Action<string> response)
     public void ExpectResponse (string terminator, Action<string> response)
     {
     {
         currentTerminator = terminator;
         currentTerminator = terminator;
         currentResponse = response;
         currentResponse = response;
-        
     }
     }
 }
 }