Update handling to have state

This commit is contained in:
tznind
2024-10-11 18:39:31 +01:00
parent 75ec589073
commit a0c3363210
2 changed files with 87 additions and 35 deletions

View File

@@ -14,6 +14,17 @@ class AnsiResponseParser
private List<Func<string, bool>> _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
}
/// <summary>
/// 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
}
/// <summary>
/// Resets the parser's state when a response is handled or finished.
/// </summary>
private void ResetState ()
{
currentState = ParserState.Normal;
held.Clear ();
currentTerminator = null;
currentResponse = null;
}
/// <summary>
@@ -124,4 +174,5 @@ class AnsiResponseParser
currentTerminator = terminator;
currentResponse = response;
}
}

View File

@@ -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)
{