mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2026-01-01 08:50:25 +01:00
prepare to handle more rules
This commit is contained in:
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user