Skip to content

Commit

Permalink
Update handling to have state
Browse files Browse the repository at this point in the history
  • Loading branch information
tznind committed Oct 11, 2024
1 parent 75ec589 commit a0c3363
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 36 deletions.
117 changes: 84 additions & 33 deletions Terminal.Gui/ConsoleDrivers/AnsiResponseParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand All @@ -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
Expand All @@ -51,42 +72,71 @@ public string ProcessInput (string input)
{
char currentChar = input [index];

if (inResponse)
switch (currentState)
{
// If we are in a response, accumulate characters in `held`
held.Append (currentChar);

// 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 ();
}

index++;
continue;
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;

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;

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;
}
return output.ToString (); // Return all characters that passed through
}

// 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 ();
/// <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>
Expand Down Expand Up @@ -124,4 +174,5 @@ public void ExpectResponse (string terminator, Action<string> response)
currentTerminator = terminator;
currentResponse = response;
}

}
7 changes: 4 additions & 3 deletions UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ public void TestInputProcessing ()
// 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);
}
Expand All @@ -48,12 +48,13 @@ public void TestInputProcessing ()
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)
{
Expand Down

0 comments on commit a0c3363

Please sign in to comment.