diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser.cs index 31aed18ee..3ac5b4d68 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser.cs @@ -1,5 +1,7 @@ #nullable enable +using System.Diagnostics; + namespace Terminal.Gui; class AnsiResponseParser { @@ -23,18 +25,69 @@ class AnsiResponseParser private bool inResponse = false; + private StringBuilder held = new StringBuilder(); - public bool ConsumeInput (char character, out string? released) + /// + /// + /// Processes input which may be a single character or multiple. + /// Returns what should be passed on to any downstream input processing + /// (i.e. removes expected Ansi responses from the input stream + /// + /// + /// 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. + /// + /// + /// + public string ProcessInput (string input) { + + if (inResponse) + { + if (currentTerminator != null && input.StartsWith (currentTerminator)) + { + // Consume terminator and release the event + held.Append (currentTerminator); + currentResponse?.Invoke (held.ToString()); + + // clear the state + held.Clear (); + currentResponse = null; + + // recurse + return ProcessInput (input.Substring (currentTerminator.Length)); + } + + // we are in a response but have not reached terminator yet + held.Append (input [0]); + return ProcessInput (input.Substring (1)); + } + + // if character is escape + if (input.StartsWith ('\x1B')) + { + // We shouldn't get an escape in the middle of a response - TODO: figure out how to handle that + Debug.Assert (!inResponse); - // start consuming till we see terminator - released = null; - return false; + // consume the escape + held.Append (input [0]); + inResponse = true; + return ProcessInput (input.Substring (1)); + } + + return input[0] + ProcessInput (input.Substring (1)); } - public void ExpectResponse (char terminator, Action response) + private string? currentTerminator = null; + private Action? currentResponse = null; + + public void ExpectResponse (string terminator, Action response) { + currentTerminator = terminator; + currentResponse = response; + } } diff --git a/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs b/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs index 79f0bbba4..ab72288b1 100644 --- a/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs +++ b/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs @@ -16,7 +16,7 @@ public class AnsiResponseParserTests int i = 0; // Imagine that we are expecting a DAR - _parser.ExpectResponse ('c',(s)=> response = s); + _parser.ExpectResponse ("c",(s)=> response = s); // First char is Escape which we must consume incase what follows is the DAR AssertConsumed (ansiStream, ref i); // Esc @@ -50,27 +50,27 @@ public class AnsiResponseParserTests private void AssertIgnored (string ansiStream, ref int i) { + var c = NextChar (ansiStream, ref i); + // Parser does not grab this key (i.e. driver can continue with regular operations) - Assert.False (_parser.ConsumeInput (NextChar (ansiStream, ref i), out var released)); - Assert.Null (released); + Assert.Equal ( c,_parser.ProcessInput (c)); } private void AssertConsumed (string ansiStream, ref int i) { // Parser grabs this key - Assert.True (_parser.ConsumeInput( NextChar (ansiStream, ref i), out var released)); - Assert.Null (released); + var c = NextChar (ansiStream, ref i); + Assert.Empty (_parser.ProcessInput(c)); } private void AssertReleased (string ansiStream, ref int i, string expectedRelease) { + var c = NextChar (ansiStream, ref i); + // Parser realizes it has grabbed content that does not belong to an outstanding request // Parser returns false to indicate to continue - Assert.False(_parser.ConsumeInput (NextChar (ansiStream,ref i), out var released)); - - // Parser releases all the grabbed content back to the driver - Assert.Equal ( released,expectedRelease); + Assert.Equal(expectedRelease,_parser.ProcessInput (c)); } - private char NextChar (string ansiStream, ref int i) + private string NextChar (string ansiStream, ref int i) { - return ansiStream [i++]; + return ansiStream [i++].ToString(); } }