prepare to handle more rules

This commit is contained in:
tznind
2024-10-11 18:17:35 +01:00
parent 22e612e4ac
commit 75ec589073

View File

@@ -1,10 +1,18 @@
#nullable enable
using System.Diagnostics;
using System.Text;
namespace Terminal.Gui;
class AnsiResponseParser
{
private bool inResponse = false;
private StringBuilder held = new StringBuilder ();
private string? currentTerminator = null;
private Action<string>? currentResponse = null;
private List<Func<string, bool>> _ignorers = new ();
/*
* ANSI Input Sequences
@@ -23,71 +31,97 @@ class AnsiResponseParser
* \x1B[0c // Device Attributes Response (e.g., terminal identification)
*/
private bool inResponse = false;
private StringBuilder held = new StringBuilder();
/// <summary>
/// <para>
/// 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
/// </para>
/// <para>
/// 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.</para>
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public string ProcessInput (string input)
public AnsiResponseParser ()
{
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);
// consume the escape
held.Append (input [0]);
inResponse = true;
return ProcessInput (input.Substring (1));
}
return input[0] + ProcessInput (input.Substring (1));
// 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"));
}
private string? currentTerminator = null;
private Action<string>? currentResponse = null;
/// <summary>
/// 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).
/// </summary>
public string ProcessInput (string input)
{
StringBuilder output = new StringBuilder (); // Holds characters that should pass through
int index = 0; // Tracks position in the input string
while (index < input.Length)
{
char currentChar = input [index];
if (inResponse)
{
// 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;
}
// 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 ();
}
/// <summary>
/// Checks the current `held` content to decide whether it should be released, either as an expected or unexpected response.
/// </summary>
private string HandleHeldContent ()
{
// If we're expecting a specific terminator, check if the content matches
if (currentTerminator != null && held.ToString ().EndsWith (currentTerminator))
{
// If it matches the expected response, invoke the callback and return nothing for output
currentResponse?.Invoke (held.ToString ());
return string.Empty;
}
// Handle common ANSI sequences (such as mouse input or arrow keys)
if (_ignorers.Any(m=>m.Invoke (held.ToString())))
{
// Detected mouse input, release it without triggering the delegate
return held.ToString ();
}
// Add more cases here for other standard sequences (like arrow keys, function keys, etc.)
// If no match, continue accumulating characters
return string.Empty;
}
/// <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)
{
currentTerminator = terminator;
currentResponse = response;
}
}