|
@@ -2,7 +2,7 @@
|
|
|
|
|
|
namespace Terminal.Gui;
|
|
|
|
|
|
- // Enum to manage the parser's state
|
|
|
+ // Enum to manage the parser's state
|
|
|
internal enum ParserState
|
|
|
{
|
|
|
Normal,
|
|
@@ -10,31 +10,31 @@ namespace Terminal.Gui;
|
|
|
InResponse
|
|
|
}
|
|
|
|
|
|
-internal abstract class AnsiResponseParserBase
|
|
|
-{
|
|
|
- protected readonly List<(string terminator, Action<string> response)> expectedResponses = new ();
|
|
|
-
|
|
|
- // Current state of the parser
|
|
|
- private ParserState _state = ParserState.Normal;
|
|
|
- public ParserState State
|
|
|
+ internal abstract class AnsiResponseParserBase
|
|
|
{
|
|
|
- get => _state;
|
|
|
- protected set
|
|
|
+ protected readonly List<(string terminator, Action<string> response)> expectedResponses = new ();
|
|
|
+ private ParserState _state = ParserState.Normal;
|
|
|
+
|
|
|
+ // Current state of the parser
|
|
|
+ public ParserState State
|
|
|
{
|
|
|
- StateChangedAt = DateTime.Now;
|
|
|
- _state = value;
|
|
|
+ get => _state;
|
|
|
+ protected set
|
|
|
+ {
|
|
|
+ StateChangedAt = DateTime.Now;
|
|
|
+ _state = value;
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// When <see cref="State"/> was last changed.
|
|
|
- /// </summary>
|
|
|
- public DateTime StateChangedAt { get; private set; } = DateTime.Now;
|
|
|
+ /// <summary>
|
|
|
+ /// When <see cref="State"/> was last changed.
|
|
|
+ /// </summary>
|
|
|
+ public DateTime StateChangedAt { get; private set; } = DateTime.Now;
|
|
|
|
|
|
- protected readonly HashSet<char> _knownTerminators = new ();
|
|
|
+ protected readonly HashSet<char> _knownTerminators = new ();
|
|
|
|
|
|
- public AnsiResponseParserBase ()
|
|
|
- {
|
|
|
+ public AnsiResponseParserBase ()
|
|
|
+ {
|
|
|
|
|
|
// These all are valid terminators on ansi responses,
|
|
|
// see CSI in https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s
|
|
@@ -93,248 +93,152 @@ internal abstract class AnsiResponseParserBase
|
|
|
_knownTerminators.Add ('x');
|
|
|
_knownTerminators.Add ('y');
|
|
|
_knownTerminators.Add ('z');
|
|
|
- }
|
|
|
-
|
|
|
- // Reset the parser's state
|
|
|
- protected void ResetState ()
|
|
|
- {
|
|
|
- State = ParserState.Normal;
|
|
|
- ClearHeld ();
|
|
|
- }
|
|
|
-
|
|
|
- public abstract void ClearHeld ();
|
|
|
-
|
|
|
- protected abstract string HeldToString ();
|
|
|
-
|
|
|
- protected void DispatchResponse (Action<string> response)
|
|
|
- {
|
|
|
- response?.Invoke (HeldToString ());
|
|
|
- ResetState ();
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
- // Common response handler logic
|
|
|
- protected bool ShouldReleaseHeldContent ()
|
|
|
- {
|
|
|
- string cur = HeldToString ();
|
|
|
+ protected void ResetState ()
|
|
|
+ {
|
|
|
+ State = ParserState.Normal;
|
|
|
+ ClearHeld ();
|
|
|
+ }
|
|
|
|
|
|
- // Check for expected responses
|
|
|
- (string terminator, Action<string> response) matchingResponse = expectedResponses.FirstOrDefault (r => cur.EndsWith (r.terminator));
|
|
|
+ public abstract void ClearHeld ();
|
|
|
+ protected abstract string HeldToString ();
|
|
|
+ protected abstract void AddToHeld (char c);
|
|
|
|
|
|
- if (matchingResponse.response != null)
|
|
|
+ // Base method for processing input
|
|
|
+ public void ProcessInputBase (Func<int, char> getCharAtIndex, Action<char> appendOutput, int inputLength)
|
|
|
{
|
|
|
- DispatchResponse (matchingResponse.response);
|
|
|
- expectedResponses.Remove (matchingResponse);
|
|
|
- return false;
|
|
|
+ var index = 0; // Tracks position in the input string
|
|
|
+
|
|
|
+ while (index < inputLength)
|
|
|
+ {
|
|
|
+ var currentChar = getCharAtIndex (index);
|
|
|
+
|
|
|
+ switch (State)
|
|
|
+ {
|
|
|
+ case ParserState.Normal:
|
|
|
+ if (currentChar == '\x1B')
|
|
|
+ {
|
|
|
+ // Escape character detected, move to ExpectingBracket state
|
|
|
+ State = ParserState.ExpectingBracket;
|
|
|
+ AddToHeld (currentChar); // Hold the escape character
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Normal character, append to output
|
|
|
+ appendOutput (currentChar);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case ParserState.ExpectingBracket:
|
|
|
+ if (currentChar == '[')
|
|
|
+ {
|
|
|
+ // Detected '[', transition to InResponse state
|
|
|
+ State = ParserState.InResponse;
|
|
|
+ AddToHeld (currentChar); // Hold the '['
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Invalid sequence, release held characters and reset to Normal
|
|
|
+ ReleaseHeld (appendOutput);
|
|
|
+ appendOutput (currentChar); // Add current character
|
|
|
+ ResetState ();
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case ParserState.InResponse:
|
|
|
+ AddToHeld (currentChar);
|
|
|
+
|
|
|
+ // Check if the held content should be released
|
|
|
+ if (ShouldReleaseHeldContent ())
|
|
|
+ {
|
|
|
+ ReleaseHeld (appendOutput);
|
|
|
+ ResetState (); // Exit response mode and reset
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ index++;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- if (_knownTerminators.Contains (cur.Last ()) && cur.StartsWith (EscSeqUtils.CSI))
|
|
|
+ private void ReleaseHeld (Action<char> appendOutput)
|
|
|
{
|
|
|
- // Detected a response that was not expected
|
|
|
- return true;
|
|
|
+ foreach (var c in HeldToString ())
|
|
|
+ {
|
|
|
+ appendOutput (c);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- return false; // Continue accumulating
|
|
|
- }
|
|
|
-
|
|
|
- /// <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) { expectedResponses.Add ((terminator, response)); }
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-internal class AnsiResponseParser<T> : AnsiResponseParserBase
|
|
|
-{
|
|
|
- private readonly List<Tuple<char,T>> held = new ();
|
|
|
+ // Common response handler logic
|
|
|
+ protected bool ShouldReleaseHeldContent ()
|
|
|
+ {
|
|
|
+ string cur = HeldToString ();
|
|
|
|
|
|
- /// <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 IEnumerable<Tuple<char,T>> ProcessInput (params Tuple<char,T>[] input)
|
|
|
- {
|
|
|
- var output = new List<Tuple<char, T>> (); // Holds characters that should pass through
|
|
|
- var index = 0; // Tracks position in the input string
|
|
|
+ // Check for expected responses
|
|
|
+ (string terminator, Action<string> response) matchingResponse = expectedResponses.FirstOrDefault (r => cur.EndsWith (r.terminator));
|
|
|
|
|
|
- while (index < input.Length)
|
|
|
- {
|
|
|
- var currentChar = input [index];
|
|
|
+ if (matchingResponse.response != null)
|
|
|
+ {
|
|
|
+ DispatchResponse (matchingResponse.response);
|
|
|
+ expectedResponses.Remove (matchingResponse);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
|
|
|
- switch (State)
|
|
|
+ if (_knownTerminators.Contains (cur.Last ()) && cur.StartsWith (EscSeqUtils.CSI))
|
|
|
{
|
|
|
- case ParserState.Normal:
|
|
|
- if (currentChar.Item1 == '\x1B')
|
|
|
- {
|
|
|
- // Escape character detected, move to ExpectingBracket state
|
|
|
- State = ParserState.ExpectingBracket;
|
|
|
- held.Add (currentChar); // Hold the escape character
|
|
|
- index++;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // Normal character, append to output
|
|
|
- output.Add (currentChar);
|
|
|
- index++;
|
|
|
- }
|
|
|
-
|
|
|
- break;
|
|
|
-
|
|
|
- case ParserState.ExpectingBracket:
|
|
|
- if (currentChar.Item1 == '[')
|
|
|
- {
|
|
|
- // Detected '[' , transition to InResponse state
|
|
|
- State = ParserState.InResponse;
|
|
|
- held.Add (currentChar); // Hold the '['
|
|
|
- index++;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // Invalid sequence, release held characters and reset to Normal
|
|
|
- output.AddRange (held);
|
|
|
- output.Add (currentChar); // Add current character
|
|
|
- ResetState ();
|
|
|
- index++;
|
|
|
- }
|
|
|
-
|
|
|
- break;
|
|
|
-
|
|
|
- case ParserState.InResponse:
|
|
|
- held.Add (currentChar);
|
|
|
-
|
|
|
- // Check if the held content should be released
|
|
|
- if (ShouldReleaseHeldContent ())
|
|
|
- {
|
|
|
- output.AddRange (held);
|
|
|
- ResetState (); // Exit response mode and reset
|
|
|
- }
|
|
|
-
|
|
|
- index++;
|
|
|
-
|
|
|
- break;
|
|
|
+ // Detected a response that was not expected
|
|
|
+ return true;
|
|
|
}
|
|
|
+
|
|
|
+ return false; // Continue accumulating
|
|
|
}
|
|
|
|
|
|
- return output; // Return all characters that passed through
|
|
|
- }
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Resets the parser's state when a response is handled or finished.
|
|
|
- /// </summary>
|
|
|
- private void ResetState ()
|
|
|
- {
|
|
|
- State = ParserState.Normal;
|
|
|
- held.Clear ();
|
|
|
- }
|
|
|
+ protected void DispatchResponse (Action<string> response)
|
|
|
+ {
|
|
|
+ response?.Invoke (HeldToString ());
|
|
|
+ ResetState ();
|
|
|
+ }
|
|
|
|
|
|
- /// <inheritdoc />
|
|
|
- public override void ClearHeld ()
|
|
|
- {
|
|
|
- held.Clear ();
|
|
|
+ /// <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) => expectedResponses.Add ((terminator, response));
|
|
|
}
|
|
|
|
|
|
- protected override string HeldToString ()
|
|
|
+ internal class AnsiResponseParser<T> : AnsiResponseParserBase
|
|
|
{
|
|
|
- return new string (held.Select (h => h.Item1).ToArray ());
|
|
|
- }
|
|
|
-}
|
|
|
+ private readonly List<Tuple<char, T>> held = new ();
|
|
|
|
|
|
+ public IEnumerable<Tuple<char, T>> ProcessInput (params Tuple<char, T> [] input)
|
|
|
+ {
|
|
|
+ var output = new List<Tuple<char, T>> ();
|
|
|
+ ProcessInputBase (i => input [i].Item1, c => output.Add (new Tuple<char, T> (c, input [0].Item2)), input.Length);
|
|
|
+ return output;
|
|
|
+ }
|
|
|
|
|
|
+ public override void ClearHeld () => held.Clear ();
|
|
|
|
|
|
+ protected override string HeldToString () => new string (held.Select (h => h.Item1).ToArray ());
|
|
|
|
|
|
-internal class AnsiResponseParser : AnsiResponseParserBase
|
|
|
-{
|
|
|
- private readonly StringBuilder held = new ();
|
|
|
+ protected override void AddToHeld (char c) => held.Add (new Tuple<char, T> (c, default!));
|
|
|
+ }
|
|
|
|
|
|
- /// <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)
|
|
|
+ internal class AnsiResponseParser : AnsiResponseParserBase
|
|
|
{
|
|
|
- var output = new StringBuilder (); // Holds characters that should pass through
|
|
|
- var index = 0; // Tracks position in the input string
|
|
|
+ private readonly StringBuilder held = new ();
|
|
|
|
|
|
- while (index < input.Length)
|
|
|
+ public string ProcessInput (string input)
|
|
|
{
|
|
|
- var currentChar = input [index];
|
|
|
-
|
|
|
- switch (State)
|
|
|
- {
|
|
|
- case ParserState.Normal:
|
|
|
- if (currentChar == '\x1B')
|
|
|
- {
|
|
|
- // Escape character detected, move to ExpectingBracket state
|
|
|
- State = 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 == '[')
|
|
|
- {
|
|
|
- // Detected '[' , transition to InResponse state
|
|
|
- State = ParserState.InResponse;
|
|
|
- held.Append (currentChar); // Hold the '['
|
|
|
- index++;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // Invalid sequence, release held characters and reset to Normal
|
|
|
- output.Append (held);
|
|
|
- output.Append (currentChar); // Add current character
|
|
|
- ResetState ();
|
|
|
- index++;
|
|
|
- }
|
|
|
-
|
|
|
- break;
|
|
|
-
|
|
|
- case ParserState.InResponse:
|
|
|
- held.Append (currentChar);
|
|
|
-
|
|
|
- // Check if the held content should be released
|
|
|
- if (ShouldReleaseHeldContent ())
|
|
|
- {
|
|
|
- output.Append (held);
|
|
|
- ResetState (); // Exit response mode and reset
|
|
|
- }
|
|
|
-
|
|
|
- index++;
|
|
|
-
|
|
|
- break;
|
|
|
- }
|
|
|
+ var output = new StringBuilder ();
|
|
|
+ ProcessInputBase (i => input [i], c => output.Append (c), input.Length);
|
|
|
+ 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 ()
|
|
|
- {
|
|
|
- State = ParserState.Normal;
|
|
|
- held.Clear ();
|
|
|
- }
|
|
|
+ public override void ClearHeld () => held.Clear ();
|
|
|
|
|
|
- /// <inheritdoc />
|
|
|
- public override void ClearHeld ()
|
|
|
- {
|
|
|
- held.Clear ();
|
|
|
- }
|
|
|
+ protected override string HeldToString () => held.ToString ();
|
|
|
|
|
|
- protected override string HeldToString ()
|
|
|
- {
|
|
|
- return held.ToString ();
|
|
|
- }
|
|
|
-}
|
|
|
+ protected override void AddToHeld (char c) => held.Append (c);
|
|
|
+ }
|