Browse Source

tidyup and streamline naming

tznind 9 months ago
parent
commit
e0415e6584

+ 193 - 196
Terminal.Gui/ConsoleDrivers/AnsiResponseParser.cs

@@ -2,45 +2,31 @@
 
 namespace Terminal.Gui;
 
-    // Enum to manage the parser's state
-    public enum ParserState
-    {
-        Normal,
-        ExpectingBracket,
-        InResponse
-    }
-
-    public interface IAnsiResponseParser
-    {
-        void ExpectResponse (string terminator, Action<string> response);
-    }
+internal abstract class AnsiResponseParserBase : IAnsiResponseParser
+{
+    protected readonly List<(string terminator, Action<string> response)> expectedResponses = new ();
+    private AnsiResponseParserState _state = AnsiResponseParserState.Normal;
 
-    internal abstract class AnsiResponseParserBase : IAnsiResponseParser
+    // Current state of the parser
+    public AnsiResponseParserState State
     {
-        protected readonly List<(string terminator, Action<string> response)> expectedResponses = new ();
-        private ParserState _state = ParserState.Normal;
-
-        // Current state of the parser
-        public ParserState State
+        get => _state;
+        protected set
         {
-            get => _state;
-            protected set
-            {
-                StateChangedAt = DateTime.Now;
-                _state = value;
-            }
+            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 ();
+    /// <summary>
+    ///     When <see cref="State"/> was last changed.
+    /// </summary>
+    public DateTime StateChangedAt { get; private set; } = DateTime.Now;
 
-        public AnsiResponseParserBase ()
-        {
+    protected readonly HashSet<char> _knownTerminators = new ();
 
+    public AnsiResponseParserBase ()
+    {
         // 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
         _knownTerminators.Add ('@');
@@ -98,206 +84,217 @@ namespace Terminal.Gui;
         _knownTerminators.Add ('x');
         _knownTerminators.Add ('y');
         _knownTerminators.Add ('z');
-        }
+    }
 
-        protected void ResetState ()
-        {
-            State = ParserState.Normal;
-            ClearHeld ();
-        }
+    protected void ResetState ()
+    {
+        State = AnsiResponseParserState.Normal;
+        ClearHeld ();
+    }
 
-        public abstract void ClearHeld ();
-        protected abstract string HeldToString ();
-        protected abstract IEnumerable<object> HeldToObjects ();
-        protected abstract void AddToHeld (object o);
-
-        /// <summary>
-        /// Processes an input collection of objects <paramref name="inputLength"/> long.
-        /// You must provide the indexers to return the objects and the action to append
-        /// to output stream.
-        /// </summary>
-        /// <param name="getCharAtIndex">The character representation of element i of your input collection</param>
-        /// <param name="getObjectAtIndex">The actual element in the collection (e.g. char or Tuple&lt;char,T&gt;)</param>
-        /// <param name="appendOutput">Action to invoke when parser confirms an element of the current collection or a previous
-        /// call's collection should be appended to the current output (i.e. append to your output List/StringBuilder).</param>
-        /// <param name="inputLength">The total number of elements in your collection</param>
-        protected void ProcessInputBase (
-            Func<int, char> getCharAtIndex,
-            Func<int, object> getObjectAtIndex,
-            Action<object> appendOutput,
-            int inputLength)
-        {
-            var index = 0; // Tracks position in the input string
+    public abstract void ClearHeld ();
+    protected abstract string HeldToString ();
+    protected abstract IEnumerable<object> HeldToObjects ();
+    protected abstract void AddToHeld (object o);
+
+    /// <summary>
+    ///     Processes an input collection of objects <paramref name="inputLength"/> long.
+    ///     You must provide the indexers to return the objects and the action to append
+    ///     to output stream.
+    /// </summary>
+    /// <param name="getCharAtIndex">The character representation of element i of your input collection</param>
+    /// <param name="getObjectAtIndex">The actual element in the collection (e.g. char or Tuple&lt;char,T&gt;)</param>
+    /// <param name="appendOutput">
+    ///     Action to invoke when parser confirms an element of the current collection or a previous
+    ///     call's collection should be appended to the current output (i.e. append to your output List/StringBuilder).
+    /// </param>
+    /// <param name="inputLength">The total number of elements in your collection</param>
+    protected void ProcessInputBase (
+        Func<int, char> getCharAtIndex,
+        Func<int, object> getObjectAtIndex,
+        Action<object> appendOutput,
+        int inputLength
+    )
+    {
+        var index = 0; // Tracks position in the input string
 
-            while (index < inputLength)
-            {
-                var currentChar = getCharAtIndex (index);
-                var currentObj = getObjectAtIndex (index);
-
-                bool isEscape = currentChar == '\x1B';
-
-                switch (State)
-                {
-                    case ParserState.Normal:
-                        if (isEscape)
-                        {
-                            // Escape character detected, move to ExpectingBracket state
-                            State = ParserState.ExpectingBracket;
-                            AddToHeld (currentObj); // Hold the escape character
-                        }
-                        else
-                        {
-                            // Normal character, append to output
-                            appendOutput (currentObj);
-                        }
-                        break;
-
-                    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 == '[')
-                        {
-                            // Detected '[', transition to InResponse state
-                            State = ParserState.InResponse;
-                            AddToHeld (currentObj); // Hold the '['
-                        }
-                        else
-                        {
-                            // Invalid sequence, release held characters and reset to Normal
-                            ReleaseHeld (appendOutput);
-                            appendOutput (currentObj); // Add current character
-                        }
-                        break;
-
-                    case ParserState.InResponse:
-                        AddToHeld (currentObj);
-
-                        // Check if the held content should be released
-                        if (ShouldReleaseHeldContent ())
-                        {
-                            ReleaseHeld (appendOutput);
-                        }
-                        break;
-                }
-
-                index++;
-            }
-        }
+        while (index < inputLength)
+        {
+            char currentChar = getCharAtIndex (index);
+            object currentObj = getObjectAtIndex (index);
 
+            bool isEscape = currentChar == '\x1B';
 
-        private void ReleaseHeld (Action<object> appendOutput, ParserState newState = ParserState.Normal)
-        {
-            foreach (var o in HeldToObjects ())
+            switch (State)
             {
-                appendOutput (o);
+                case AnsiResponseParserState.Normal:
+                    if (isEscape)
+                    {
+                        // Escape character detected, move to ExpectingBracket state
+                        State = AnsiResponseParserState.ExpectingBracket;
+                        AddToHeld (currentObj); // Hold the escape character
+                    }
+                    else
+                    {
+                        // Normal character, append to output
+                        appendOutput (currentObj);
+                    }
+
+                    break;
+
+                case AnsiResponseParserState.ExpectingBracket:
+                    if (isEscape)
+                    {
+                        // Second escape so we must release first
+                        ReleaseHeld (appendOutput, AnsiResponseParserState.ExpectingBracket);
+                        AddToHeld (currentObj); // Hold the new escape
+                    }
+                    else if (currentChar == '[')
+                    {
+                        // Detected '[', transition to InResponse state
+                        State = AnsiResponseParserState.InResponse;
+                        AddToHeld (currentObj); // Hold the '['
+                    }
+                    else
+                    {
+                        // Invalid sequence, release held characters and reset to Normal
+                        ReleaseHeld (appendOutput);
+                        appendOutput (currentObj); // Add current character
+                    }
+
+                    break;
+
+                case AnsiResponseParserState.InResponse:
+                    AddToHeld (currentObj);
+
+                    // Check if the held content should be released
+                    if (ShouldReleaseHeldContent ())
+                    {
+                        ReleaseHeld (appendOutput);
+                    }
+
+                    break;
             }
 
-            State = newState;
-            ClearHeld ();
+            index++;
+        }
     }
 
-        // Common response handler logic
-        protected bool ShouldReleaseHeldContent ()
+    private void ReleaseHeld (Action<object> appendOutput, AnsiResponseParserState newState = AnsiResponseParserState.Normal)
+    {
+        foreach (object o in HeldToObjects ())
         {
-            string cur = HeldToString ();
+            appendOutput (o);
+        }
 
-            // Check for expected responses
-            (string terminator, Action<string> response) matchingResponse = expectedResponses.FirstOrDefault (r => cur.EndsWith (r.terminator));
+        State = newState;
+        ClearHeld ();
+    }
 
-            if (matchingResponse.response != null)
-            {
-                DispatchResponse (matchingResponse.response);
-                expectedResponses.Remove (matchingResponse);
-                return false;
-            }
+    // Common response handler logic
+    protected bool ShouldReleaseHeldContent ()
+    {
+        string cur = HeldToString ();
 
-            if (_knownTerminators.Contains (cur.Last ()) && cur.StartsWith (EscSeqUtils.CSI))
-            {
-                // Detected a response that was not expected
-                return true;
-            }
+        // Check for expected responses
+        (string terminator, Action<string> response) matchingResponse = expectedResponses.FirstOrDefault (r => cur.EndsWith (r.terminator));
 
-            return false; // Continue accumulating
-        }
+        if (matchingResponse.response != null)
+        {
+            DispatchResponse (matchingResponse.response);
+            expectedResponses.Remove (matchingResponse);
 
+            return false;
+        }
 
-        protected void DispatchResponse (Action<string> response)
+        if (_knownTerminators.Contains (cur.Last ()) && cur.StartsWith (EscSeqUtils.CSI))
         {
-            response?.Invoke (HeldToString ());
-            ResetState ();
+            // Detected a response that was not expected
+            return true;
         }
 
-        /// <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));
+        return false; // Continue accumulating
     }
 
-    internal class AnsiResponseParser<T> : AnsiResponseParserBase
+    protected void DispatchResponse (Action<string> response)
     {
-        private readonly List<Tuple<char, T>> held = new ();
+        response?.Invoke (HeldToString ());
+        ResetState ();
+    }
 
-        public IEnumerable<Tuple<char, T>> ProcessInput (params Tuple<char, T> [] input)
-        {
-            var output = new List<Tuple<char, T>> ();
-            ProcessInputBase (
-                              i => input [i].Item1,
-                              i => input [i],
-                              c => output.Add ((Tuple<char, T>)c),
-                              input.Length);
-            return output;
-        }
+    /// <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)); }
+}
 
-        public IEnumerable<Tuple<char, T>> Release ()
+internal class AnsiResponseParser<T> : AnsiResponseParserBase
+{
+    private readonly List<Tuple<char, T>> held = new ();
+
+    public IEnumerable<Tuple<char, T>> ProcessInput (params Tuple<char, T> [] input)
+    {
+        List<Tuple<char, T>> output = new List<Tuple<char, T>> ();
+
+        ProcessInputBase (
+                          i => input [i].Item1,
+                          i => input [i],
+                          c => output.Add ((Tuple<char, T>)c),
+                          input.Length);
+
+        return output;
+    }
+
+    public IEnumerable<Tuple<char, T>> Release ()
+    {
+        foreach (Tuple<char, T> h in held.ToArray ())
         {
-            foreach (var h in held.ToArray ())
-            {
-                yield return h;
-            }
-            ResetState ();
+            yield return h;
         }
 
-        public override void ClearHeld () => held.Clear ();
-
-        protected override string HeldToString () => new string (held.Select (h => h.Item1).ToArray ());
+        ResetState ();
+    }
 
-        protected override IEnumerable<object> HeldToObjects () => held;
+    public override void ClearHeld () { held.Clear (); }
 
-        protected override void AddToHeld (object o) => held.Add ((Tuple<char, T>)o);
+    protected override string HeldToString () { return new (held.Select (h => h.Item1).ToArray ()); }
 
+    protected override IEnumerable<object> HeldToObjects () { return held; }
 
+    protected override void AddToHeld (object o) { held.Add ((Tuple<char, T>)o); }
 }
 
-    internal class AnsiResponseParser : AnsiResponseParserBase
+internal class AnsiResponseParser : AnsiResponseParserBase
+{
+    private readonly StringBuilder held = new ();
+
+    public string ProcessInput (string input)
     {
-        private readonly StringBuilder held = new ();
+        var output = new StringBuilder ();
 
-        public string ProcessInput (string input)
-        {
-            var output = new StringBuilder ();
-            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 ();
-        }
-        public string Release ()
-        {
-            var output = held.ToString ();
-            ResetState ();
+        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;
-        }
-        public override void ClearHeld () => held.Clear ();
+        return output.ToString ();
+    }
+
+    public string Release ()
+    {
+        var output = held.ToString ();
+        ResetState ();
+
+        return output;
+    }
+
+    public override void ClearHeld () { held.Clear (); }
 
-        protected override string HeldToString () => held.ToString ();
+    protected override string HeldToString () { return held.ToString (); }
 
-        protected override IEnumerable<object> HeldToObjects () => held.ToString().Select(c => (object) c).ToArray ();
-        protected override void AddToHeld (object o) => held.Append ((char)o);
-    }
+    protected override IEnumerable<object> HeldToObjects () { return held.ToString ().Select (c => (object)c).ToArray (); }
+
+    protected override void AddToHeld (object o) { held.Append ((char)o); }
+}

+ 8 - 0
Terminal.Gui/ConsoleDrivers/IAnsiResponseParser.cs

@@ -0,0 +1,8 @@
+#nullable enable
+namespace Terminal.Gui;
+
+public interface IAnsiResponseParser
+{
+    AnsiResponseParserState State { get; }
+    void ExpectResponse (string terminator, Action<string> response);
+}

+ 24 - 0
Terminal.Gui/ConsoleDrivers/ParserState.cs

@@ -0,0 +1,24 @@
+namespace Terminal.Gui;
+
+/// <summary>
+///     Describes the current state of an <see cref="IAnsiResponseParser"/>
+/// </summary>
+public enum AnsiResponseParserState
+{
+    /// <summary>
+    ///     Parser is reading normal input e.g. keys typed by user.
+    /// </summary>
+    Normal,
+
+    /// <summary>
+    ///     Parser has encountered an Esc and is waiting to see if next
+    ///     key(s) continue to form an Ansi escape sequence
+    /// </summary>
+    ExpectingBracket,
+
+    /// <summary>
+    ///     Parser has encountered Esc[ and considers that it is in the process
+    ///     of reading an ANSI sequence.
+    /// </summary>
+    InResponse
+}

+ 1 - 1
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -1566,7 +1566,7 @@ internal class WindowsDriver : ConsoleDriver
     public IEnumerable<WindowsConsole.InputRecord> ShouldRelease ()
     {
 
-        if (Parser.State == ParserState.ExpectingBracket &&
+        if (Parser.State == AnsiResponseParserState.ExpectingBracket &&
             DateTime.Now - Parser.StateChangedAt > _escTimeout)
         {
             return Parser.Release ().Select (o => o.Item2);

+ 18 - 18
UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs

@@ -154,7 +154,7 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
             null,
             new []
             {
-                new StepExpectation ('\u001b',ParserState.ExpectingBracket,string.Empty)
+                new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingBracket,string.Empty)
             }
         ];
 
@@ -164,13 +164,13 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
             'c',
             new []
             {
-                new StepExpectation ('\u001b',ParserState.ExpectingBracket,string.Empty),
-                new StepExpectation ('H',ParserState.Normal,"\u001bH"), // H is known terminator and not expected one so here we release both chars
-                new StepExpectation ('\u001b',ParserState.ExpectingBracket,string.Empty),
-                new StepExpectation ('[',ParserState.InResponse,string.Empty),
-                new StepExpectation ('0',ParserState.InResponse,string.Empty),
-                new StepExpectation ('c',ParserState.Normal,string.Empty,"\u001b[0c"), // c is expected terminator so here we swallow input and populate expected response
-                new StepExpectation ('\u001b',ParserState.ExpectingBracket,string.Empty),
+                new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingBracket,string.Empty),
+                new StepExpectation ('H',AnsiResponseParserState.Normal,"\u001bH"), // H is known terminator and not expected one so here we release both chars
+                new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingBracket,string.Empty),
+                new StepExpectation ('[',AnsiResponseParserState.InResponse,string.Empty),
+                new StepExpectation ('0',AnsiResponseParserState.InResponse,string.Empty),
+                new StepExpectation ('c',AnsiResponseParserState.Normal,string.Empty,"\u001b[0c"), // c is expected terminator so here we swallow input and populate expected response
+                new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingBracket,string.Empty),
             }
         ];
     }
@@ -186,7 +186,7 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
         /// What should the state of the parser be after the <see cref="Input"/>
         /// is fed in.
         /// </summary>
-        public ParserState ExpectedStateAfterOperation { get; }
+        public AnsiResponseParserState ExpectedStateAfterOperation { get; }
 
         /// <summary>
         /// If this step should release one or more characters, put them here.
@@ -201,7 +201,7 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
 
         public StepExpectation (
             char input,
-            ParserState expectedStateAfterOperation,
+            AnsiResponseParserState expectedStateAfterOperation,
             string expectedRelease = "",
             string expectedAnsiResponse = "") : this ()
         {
@@ -261,8 +261,8 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
         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 (AnsiResponseParserState.ExpectingBracket, _parser1.State);
+        Assert.Equal (AnsiResponseParserState.ExpectingBracket, _parser2.State);
 
         Assert.Equal (DateTime.Now.Date, _parser1.StateChangedAt.Date);
         Assert.Equal (DateTime.Now.Date, _parser2.StateChangedAt.Date);
@@ -300,14 +300,14 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
 
         // First Esc gets grabbed
         AssertConsumed (input, ref i); // Esc
-        Assert.Equal (ParserState.ExpectingBracket,_parser1.State);
-        Assert.Equal (ParserState.ExpectingBracket, _parser2.State);
+        Assert.Equal (AnsiResponseParserState.ExpectingBracket,_parser1.State);
+        Assert.Equal (AnsiResponseParserState.ExpectingBracket, _parser2.State);
 
         // Because next char is 'f' we do not see a bracket so release both
         AssertReleased (input, ref i, "\u001bf", 0,1); // f
 
-        Assert.Equal (ParserState.Normal, _parser1.State);
-        Assert.Equal (ParserState.Normal, _parser2.State);
+        Assert.Equal (AnsiResponseParserState.Normal, _parser1.State);
+        Assert.Equal (AnsiResponseParserState.Normal, _parser2.State);
 
         AssertReleased (input, ref i,"i",2);
         AssertReleased (input, ref i, "s", 3);
@@ -428,7 +428,7 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
 
         Assert.Equal (expectedRelease, _parser2.Release ());
 
-        Assert.Equal (ParserState.Normal, _parser1.State);
-        Assert.Equal (ParserState.Normal, _parser2.State);
+        Assert.Equal (AnsiResponseParserState.Normal, _parser1.State);
+        Assert.Equal (AnsiResponseParserState.Normal, _parser2.State);
     }
 }