From a0c33632104cf96cb0a242a57c3f77f80ce62ca2 Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 11 Oct 2024 18:39:31 +0100 Subject: [PATCH] Update handling to have state --- .../ConsoleDrivers/AnsiResponseParser.cs | 115 +++++++++++++----- .../ConsoleDrivers/AnsiResponseParserTests.cs | 7 +- 2 files changed, 87 insertions(+), 35 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser.cs index 721913d2a..83133c624 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser.cs @@ -14,6 +14,17 @@ class AnsiResponseParser private List> _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 * @@ -33,10 +44,20 @@ class 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 } + /// /// Processes input which may be a single character or multiple. /// Returns what should be passed on to any downstream input processing @@ -51,42 +72,71 @@ class AnsiResponseParser { char currentChar = input [index]; - if (inResponse) + switch (currentState) { - // If we are in a response, accumulate characters in `held` - held.Append (currentChar); + 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; - // 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 (); - } + 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; - index++; - continue; + 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; - } - - // 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 (); + return output.ToString (); // Return all characters that passed through + } + + + /// + /// Resets the parser's state when a response is handled or finished. + /// + private void ResetState () + { + currentState = ParserState.Normal; + held.Clear (); + currentTerminator = null; + currentResponse = null; } /// @@ -124,4 +174,5 @@ class AnsiResponseParser currentTerminator = terminator; currentResponse = response; } + } diff --git a/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs b/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs index ab72288b1..bd538d01e 100644 --- a/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs +++ b/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs @@ -32,11 +32,11 @@ public class AnsiResponseParserTests // Regular user typing 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 - for (int c = 0; c < "\x1B [0".Length; c++) + for (int c = 0; c < "\x1B[0".Length; c++) { AssertConsumed (ansiStream, ref i); } @@ -48,12 +48,13 @@ public class AnsiResponseParserTests 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); // Parser does not grab this key (i.e. driver can continue with regular operations) Assert.Equal ( c,_parser.ProcessInput (c)); + Assert.Equal (expected,c.Single()); } private void AssertConsumed (string ansiStream, ref int i) {