|
@@ -1,10 +1,18 @@
|
|
#nullable enable
|
|
#nullable enable
|
|
|
|
|
|
using System.Diagnostics;
|
|
using System.Diagnostics;
|
|
|
|
+using System.Text;
|
|
|
|
|
|
namespace Terminal.Gui;
|
|
namespace Terminal.Gui;
|
|
class AnsiResponseParser
|
|
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
|
|
* ANSI Input Sequences
|
|
@@ -23,71 +31,97 @@ class AnsiResponseParser
|
|
* \x1B[0c // Device Attributes Response (e.g., terminal identification)
|
|
* \x1B[0c // Device Attributes Response (e.g., terminal identification)
|
|
*/
|
|
*/
|
|
|
|
|
|
- private bool inResponse = false;
|
|
|
|
-
|
|
|
|
- private StringBuilder held = new StringBuilder();
|
|
|
|
|
|
+ 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"));
|
|
|
|
+ }
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
- /// <para>
|
|
|
|
/// Processes input which may be a single character or multiple.
|
|
/// Processes input which may be a single character or multiple.
|
|
/// Returns what should be passed on to any downstream input processing
|
|
/// 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>
|
|
|
|
|
|
+ /// (i.e., removes expected ANSI responses from the input stream).
|
|
/// </summary>
|
|
/// </summary>
|
|
- /// <param name="input"></param>
|
|
|
|
- /// <returns></returns>
|
|
|
|
public string ProcessInput (string input)
|
|
public string ProcessInput (string input)
|
|
{
|
|
{
|
|
|
|
+ StringBuilder output = new StringBuilder (); // Holds characters that should pass through
|
|
|
|
+ int index = 0; // Tracks position in the input string
|
|
|
|
|
|
- if (inResponse)
|
|
|
|
|
|
+ while (index < input.Length)
|
|
{
|
|
{
|
|
- if (currentTerminator != null && input.StartsWith (currentTerminator))
|
|
|
|
- {
|
|
|
|
- // Consume terminator and release the event
|
|
|
|
- held.Append (currentTerminator);
|
|
|
|
- currentResponse?.Invoke (held.ToString());
|
|
|
|
|
|
+ char currentChar = input [index];
|
|
|
|
|
|
- // clear the state
|
|
|
|
- held.Clear ();
|
|
|
|
- currentResponse = null;
|
|
|
|
|
|
+ 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;
|
|
|
|
+ }
|
|
|
|
|
|
- // recurse
|
|
|
|
- return ProcessInput (input.Substring (currentTerminator.Length));
|
|
|
|
|
|
+ // 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;
|
|
}
|
|
}
|
|
|
|
|
|
- // we are in a response but have not reached terminator yet
|
|
|
|
- held.Append (input [0]);
|
|
|
|
- return ProcessInput (input.Substring (1));
|
|
|
|
|
|
+ // 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 ();
|
|
|
|
+ }
|
|
|
|
|
|
- // if character is escape
|
|
|
|
- if (input.StartsWith ('\x1B'))
|
|
|
|
|
|
+ /// <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))
|
|
{
|
|
{
|
|
- // We shouldn't get an escape in the middle of a response - TODO: figure out how to handle that
|
|
|
|
- Debug.Assert (!inResponse);
|
|
|
|
-
|
|
|
|
|
|
+ // If it matches the expected response, invoke the callback and return nothing for output
|
|
|
|
+ currentResponse?.Invoke (held.ToString ());
|
|
|
|
+ return string.Empty;
|
|
|
|
+ }
|
|
|
|
|
|
- // consume the escape
|
|
|
|
- held.Append (input [0]);
|
|
|
|
- inResponse = true;
|
|
|
|
- return ProcessInput (input.Substring (1));
|
|
|
|
|
|
+ // 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 ();
|
|
}
|
|
}
|
|
|
|
|
|
- return input[0] + ProcessInput (input.Substring (1));
|
|
|
|
|
|
+ // Add more cases here for other standard sequences (like arrow keys, function keys, etc.)
|
|
|
|
+
|
|
|
|
+ // If no match, continue accumulating characters
|
|
|
|
+ return string.Empty;
|
|
}
|
|
}
|
|
|
|
|
|
- private string? currentTerminator = null;
|
|
|
|
- private Action<string>? currentResponse = null;
|
|
|
|
|
|
|
|
|
|
+ /// <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)
|
|
public void ExpectResponse (string terminator, Action<string> response)
|
|
{
|
|
{
|
|
currentTerminator = terminator;
|
|
currentTerminator = terminator;
|
|
currentResponse = response;
|
|
currentResponse = response;
|
|
-
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|