Browse Source

Refactor so there is an abstract base parser

tznind 10 months ago
parent
commit
9f8fbc0698
1 changed files with 88 additions and 78 deletions
  1. 88 78
      Terminal.Gui/ConsoleDrivers/AnsiResponseParser.cs

+ 88 - 78
Terminal.Gui/ConsoleDrivers/AnsiResponseParser.cs

@@ -2,42 +2,40 @@
 
 
 namespace Terminal.Gui;
 namespace Terminal.Gui;
 
 
-internal class AnsiResponseParser<T>
-{
-    private readonly List<Tuple<char,T>> held = new ();
-    private readonly List<(string terminator, Action<string> response)> expectedResponses = new ();
-
-    // Enum to manage the parser's state
-    private enum ParserState
+  // Enum to manage the parser's state
+    internal enum ParserState
     {
     {
         Normal,
         Normal,
         ExpectingBracket,
         ExpectingBracket,
         InResponse
         InResponse
     }
     }
 
 
+internal abstract class AnsiResponseParserBase
+{
+    protected readonly List<(string terminator, Action<string> response)> expectedResponses = new ();
+
     // Current state of the parser
     // Current state of the parser
-    private ParserState currentState = ParserState.Normal;
-    private readonly HashSet<char> _knownTerminators = new ();
-
-    /*
-     * ANSI Input Sequences
-     *
-     * \x1B[A   // Up Arrow key pressed
-     * \x1B[B   // Down Arrow key pressed
-     * \x1B[C   // Right Arrow key pressed
-     * \x1B[D   // Left Arrow key pressed
-     * \x1B[3~  // Delete key pressed
-     * \x1B[2~  // Insert key pressed
-     * \x1B[5~  // Page Up key pressed
-     * \x1B[6~  // Page Down key pressed
-     * \x1B[1;5D // Ctrl + Left Arrow
-     * \x1B[1;5C // Ctrl + Right Arrow
-     * \x1B[0;10;20M // Mouse button pressed at position (10, 20)
-     * \x1B[0c  // Device Attributes Response (e.g., terminal identification)
-     */
-
-    public AnsiResponseParser ()
+    private ParserState _state = ParserState.Normal;
+    public ParserState State
+    {
+        get => _state;
+        protected set
+        {
+            StateChangedAt = DateTime.Now;
+            _state = value;
+        }
+    }
+
+    /// <summary>
+    /// When <see cref="State"/> was last changed.
+    /// </summary>
+    public DateTime StateChangedAt { get; private set; } = DateTime.Now;
+
+    protected readonly HashSet<char> _knownTerminators = new ();
+
+    public AnsiResponseParserBase ()
     {
     {
+
         // These all are valid terminators on ansi responses,
         // These all are valid terminators on ansi responses,
         // see CSI in https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s
         // see CSI in https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s
         _knownTerminators.Add ('@');
         _knownTerminators.Add ('@');
@@ -95,10 +93,61 @@ internal class AnsiResponseParser<T>
         _knownTerminators.Add ('x');
         _knownTerminators.Add ('x');
         _knownTerminators.Add ('y');
         _knownTerminators.Add ('y');
         _knownTerminators.Add ('z');
         _knownTerminators.Add ('z');
+    }
 
 
-        // Add more if necessary
+    // Reset the parser's state
+    protected void ResetState ()
+    {
+        State = ParserState.Normal;
+        ClearHeld ();
+    }
+
+    public abstract void ClearHeld ();
+
+    protected abstract string HeldToString ();
+
+    protected void DispatchResponse (Action<string> response)
+    {
+        response?.Invoke (HeldToString ());
+        ResetState ();
+    }
+
+    // Common response handler logic
+    protected bool ShouldReleaseHeldContent ()
+    {
+        string cur = HeldToString ();
+
+        // Check for expected responses
+        (string terminator, Action<string> response) matchingResponse = expectedResponses.FirstOrDefault (r => cur.EndsWith (r.terminator));
+
+        if (matchingResponse.response != null)
+        {
+            DispatchResponse (matchingResponse.response);
+            expectedResponses.Remove (matchingResponse);
+            return false;
+        }
+
+        if (_knownTerminators.Contains (cur.Last ()) && cur.StartsWith (EscSeqUtils.CSI))
+        {
+            // Detected a response that was not expected
+            return true;
+        }
+
+        return false; // Continue accumulating
     }
     }
 
 
+    /// <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) { expectedResponses.Add ((terminator, response)); }
+}
+
+
+internal class AnsiResponseParser<T> : AnsiResponseParserBase
+{
+    private readonly List<Tuple<char,T>> held = new ();
+
     /// <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
@@ -113,13 +162,13 @@ internal class AnsiResponseParser<T>
         {
         {
             var currentChar = input [index];
             var currentChar = input [index];
 
 
-            switch (currentState)
+            switch (State)
             {
             {
                 case ParserState.Normal:
                 case ParserState.Normal:
                     if (currentChar.Item1 == '\x1B')
                     if (currentChar.Item1 == '\x1B')
                     {
                     {
                         // Escape character detected, move to ExpectingBracket state
                         // Escape character detected, move to ExpectingBracket state
-                        currentState = ParserState.ExpectingBracket;
+                        State = ParserState.ExpectingBracket;
                         held.Add (currentChar); // Hold the escape character
                         held.Add (currentChar); // Hold the escape character
                         index++;
                         index++;
                     }
                     }
@@ -136,7 +185,7 @@ internal class AnsiResponseParser<T>
                     if (currentChar.Item1 == '[')
                     if (currentChar.Item1 == '[')
                     {
                     {
                         // Detected '[' , transition to InResponse state
                         // Detected '[' , transition to InResponse state
-                        currentState = ParserState.InResponse;
+                        State = ParserState.InResponse;
                         held.Add (currentChar); // Hold the '['
                         held.Add (currentChar); // Hold the '['
                         index++;
                         index++;
                     }
                     }
@@ -155,11 +204,10 @@ internal class AnsiResponseParser<T>
                     held.Add (currentChar);
                     held.Add (currentChar);
 
 
                     // Check if the held content should be released
                     // Check if the held content should be released
-                    var handled = HandleHeldContent ();
 
 
-                    if (handled != null)
+                    if (ShouldReleaseHeldContent ())
                     {
                     {
-                        output.AddRange (handled);
+                        output.AddRange (held);
                         ResetState (); // Exit response mode and reset
                         ResetState (); // Exit response mode and reset
                     }
                     }
 
 
@@ -177,56 +225,18 @@ internal class AnsiResponseParser<T>
     /// </summary>
     /// </summary>
     private void ResetState ()
     private void ResetState ()
     {
     {
-        currentState = ParserState.Normal;
+        State = ParserState.Normal;
         held.Clear ();
         held.Clear ();
     }
     }
 
 
-    /// <summary>
-    ///     Checks the current `held` content to decide whether it should be released, either as an expected or unexpected
-    ///     response.
-    /// </summary>
-    private IEnumerable<Tuple<char,T>>? HandleHeldContent ()
+    /// <inheritdoc />
+    public override void ClearHeld ()
     {
     {
-        string cur = HeldToString ();
-
-        // Check for expected responses
-        (string terminator, Action<string> response) matchingResponse = expectedResponses.FirstOrDefault (r => cur.EndsWith (r.terminator));
-
-        if (matchingResponse.response != null)
-        {
-            DispatchResponse (matchingResponse.response);
-            expectedResponses.Remove (matchingResponse);
-
-            return null;
-        }
-
-        if (_knownTerminators.Contains (cur.Last ()) && cur.StartsWith (EscSeqUtils.CSI))
-        {
-            // Detected a response that we were not expecting
-            return held;
-        }
-
-        // Add more cases here for other standard sequences (like arrow keys, function keys, etc.)
-
-        // If no match, continue accumulating characters
-        return null;
+        held.Clear ();
     }
     }
 
 
-    private string HeldToString ()
+    protected override string HeldToString ()
     {
     {
         return new string (held.Select (h => h.Item1).ToArray ());
         return new string (held.Select (h => h.Item1).ToArray ());
     }
     }
-
-    private void DispatchResponse (Action<string> response)
-    {
-        // If it matches the expected response, invoke the callback and return nothing for output
-        response?.Invoke (HeldToString ());
-        ResetState ();
-    }
-
-    /// <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) { expectedResponses.Add ((terminator, response)); }
 }
 }