mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2026-01-02 01:03:29 +01:00
Code Cleanup (resharper)
This commit is contained in:
@@ -43,12 +43,9 @@ public class AnsiEscapeSequenceRequest
|
||||
public required string Terminator { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Sends the <see cref="Request"/> to the raw output stream of the current <see cref="ConsoleDriver"/>.
|
||||
/// Only call this method from the main UI thread. You should use <see cref="AnsiRequestScheduler"/> if
|
||||
/// sending many requests.
|
||||
/// Sends the <see cref="Request"/> to the raw output stream of the current <see cref="ConsoleDriver"/>.
|
||||
/// Only call this method from the main UI thread. You should use <see cref="AnsiRequestScheduler"/> if
|
||||
/// sending many requests.
|
||||
/// </summary>
|
||||
public void Send ()
|
||||
{
|
||||
Application.Driver?.RawWrite (Request);
|
||||
}
|
||||
public void Send () { Application.Driver?.RawWrite (Request); }
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ using System.Collections.Concurrent;
|
||||
namespace Terminal.Gui;
|
||||
|
||||
/// <summary>
|
||||
/// Manages <see cref="AnsiEscapeSequenceRequest"/> made to an <see cref="IAnsiResponseParser"/>.
|
||||
/// Ensures there are not 2+ outstanding requests with the same terminator, throttles request sends
|
||||
/// to prevent console becoming unresponsive and handles evicting ignored requests (no reply from
|
||||
/// terminal).
|
||||
/// Manages <see cref="AnsiEscapeSequenceRequest"/> made to an <see cref="IAnsiResponseParser"/>.
|
||||
/// Ensures there are not 2+ outstanding requests with the same terminator, throttles request sends
|
||||
/// to prevent console becoming unresponsive and handles evicting ignored requests (no reply from
|
||||
/// terminal).
|
||||
/// </summary>
|
||||
internal class AnsiRequestScheduler
|
||||
{
|
||||
@@ -17,53 +17,55 @@ internal class AnsiRequestScheduler
|
||||
private readonly List<Tuple<AnsiEscapeSequenceRequest, DateTime>> _requests = new ();
|
||||
|
||||
/// <summary>
|
||||
///<para>
|
||||
/// Dictionary where key is ansi request terminator and value is when we last sent a request for
|
||||
/// this terminator. Combined with <see cref="_throttle"/> this prevents hammering the console
|
||||
/// with too many requests in sequence which can cause console to freeze as there is no space for
|
||||
/// regular screen drawing / mouse events etc to come in.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When user exceeds the throttle, new requests accumulate in <see cref="_requests"/> (i.e. remain
|
||||
/// queued).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Dictionary where key is ansi request terminator and value is when we last sent a request for
|
||||
/// this terminator. Combined with <see cref="_throttle"/> this prevents hammering the console
|
||||
/// with too many requests in sequence which can cause console to freeze as there is no space for
|
||||
/// regular screen drawing / mouse events etc to come in.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When user exceeds the throttle, new requests accumulate in <see cref="_requests"/> (i.e. remain
|
||||
/// queued).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, DateTime> _lastSend = new ();
|
||||
|
||||
/// <summary>
|
||||
/// Number of milliseconds after sending a request that we allow
|
||||
/// another request to go out.
|
||||
/// Number of milliseconds after sending a request that we allow
|
||||
/// another request to go out.
|
||||
/// </summary>
|
||||
private TimeSpan _throttle = TimeSpan.FromMilliseconds (100);
|
||||
private TimeSpan _runScheduleThrottle = TimeSpan.FromMilliseconds (100);
|
||||
private readonly TimeSpan _throttle = TimeSpan.FromMilliseconds (100);
|
||||
|
||||
private readonly TimeSpan _runScheduleThrottle = TimeSpan.FromMilliseconds (100);
|
||||
|
||||
/// <summary>
|
||||
/// If console has not responded to a request after this period of time, we assume that it is never going
|
||||
/// to respond. Only affects when we try to send a new request with the same terminator - at which point
|
||||
/// we tell the parser to stop expecting the old request and start expecting the new request.
|
||||
/// If console has not responded to a request after this period of time, we assume that it is never going
|
||||
/// to respond. Only affects when we try to send a new request with the same terminator - at which point
|
||||
/// we tell the parser to stop expecting the old request and start expecting the new request.
|
||||
/// </summary>
|
||||
private TimeSpan _staleTimeout = TimeSpan.FromSeconds (5);
|
||||
private readonly TimeSpan _staleTimeout = TimeSpan.FromSeconds (5);
|
||||
|
||||
private readonly DateTime _lastRun;
|
||||
|
||||
private DateTime _lastRun;
|
||||
public AnsiRequestScheduler (IAnsiResponseParser parser, Func<DateTime>? now = null)
|
||||
{
|
||||
_parser = parser;
|
||||
_now = now ?? (() => DateTime.Now);
|
||||
_lastRun = _now ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the <paramref name="request"/> immediately or queues it if there is already
|
||||
/// an outstanding request for the given <see cref="AnsiEscapeSequenceRequest.Terminator"/>.
|
||||
/// Sends the <paramref name="request"/> immediately or queues it if there is already
|
||||
/// an outstanding request for the given <see cref="AnsiEscapeSequenceRequest.Terminator"/>.
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns><see langword="true"/> if request was sent immediately. <see langword="false"/> if it was queued.</returns>
|
||||
public bool SendOrSchedule (AnsiEscapeSequenceRequest request)
|
||||
{
|
||||
|
||||
if (CanSend (request, out var reason))
|
||||
if (CanSend (request, out ReasonCannotSend reason))
|
||||
{
|
||||
Send (request);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -75,26 +77,28 @@ internal class AnsiRequestScheduler
|
||||
if (CanSend (request, out _))
|
||||
{
|
||||
Send (request);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
_requests.Add (Tuple.Create (request, _now()));
|
||||
_requests.Add (Tuple.Create (request, _now ()));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks to see if the last time we sent <paramref name="withTerminator"/>
|
||||
/// is a long time ago. If so we assume that we will never get a response and
|
||||
/// can proceed with a new request for this terminator (returning <see langword="true"/>).
|
||||
/// Looks to see if the last time we sent <paramref name="withTerminator"/>
|
||||
/// is a long time ago. If so we assume that we will never get a response and
|
||||
/// can proceed with a new request for this terminator (returning <see langword="true"/>).
|
||||
/// </summary>
|
||||
/// <param name="withTerminator"></param>
|
||||
/// <returns></returns>
|
||||
private bool EvictStaleRequests (string withTerminator)
|
||||
{
|
||||
if (_lastSend.TryGetValue (withTerminator, out var dt))
|
||||
if (_lastSend.TryGetValue (withTerminator, out DateTime dt))
|
||||
{
|
||||
if (_now() - dt > _staleTimeout)
|
||||
if (_now () - dt > _staleTimeout)
|
||||
{
|
||||
_parser.StopExpecting (withTerminator, false);
|
||||
|
||||
@@ -105,23 +109,26 @@ internal class AnsiRequestScheduler
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Identifies and runs any <see cref="_requests"/> that can be sent based on the
|
||||
/// current outstanding requests of the parser.
|
||||
/// Identifies and runs any <see cref="_requests"/> that can be sent based on the
|
||||
/// current outstanding requests of the parser.
|
||||
/// </summary>
|
||||
/// <param name="force">Repeated requests to run the schedule over short period of time will be ignored.
|
||||
/// Pass <see langword="true"/> to override this behaviour and force evaluation of outstanding requests.</param>
|
||||
/// <returns><see langword="true"/> if a request was found and run. <see langword="false"/>
|
||||
/// if no outstanding requests or all have existing outstanding requests underway in parser.</returns>
|
||||
/// <param name="force">
|
||||
/// Repeated requests to run the schedule over short period of time will be ignored.
|
||||
/// Pass <see langword="true"/> to override this behaviour and force evaluation of outstanding requests.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if a request was found and run. <see langword="false"/>
|
||||
/// if no outstanding requests or all have existing outstanding requests underway in parser.
|
||||
/// </returns>
|
||||
public bool RunSchedule (bool force = false)
|
||||
{
|
||||
if (!force && _now() - _lastRun < _runScheduleThrottle)
|
||||
if (!force && _now () - _lastRun < _runScheduleThrottle)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var opportunity = _requests.FirstOrDefault (r => CanSend (r.Item1, out _));
|
||||
Tuple<AnsiEscapeSequenceRequest, DateTime>? opportunity = _requests.FirstOrDefault (r => CanSend (r.Item1, out _));
|
||||
|
||||
if (opportunity != null)
|
||||
{
|
||||
@@ -136,7 +143,7 @@ internal class AnsiRequestScheduler
|
||||
|
||||
private void Send (AnsiEscapeSequenceRequest r)
|
||||
{
|
||||
_lastSend.AddOrUpdate (r.Terminator, _=>_now(), (_, _) => _now());
|
||||
_lastSend.AddOrUpdate (r.Terminator, _ => _now (), (_, _) => _now ());
|
||||
_parser.ExpectResponse (r.Terminator, r.ResponseReceived, false);
|
||||
r.Send ();
|
||||
}
|
||||
@@ -146,16 +153,19 @@ internal class AnsiRequestScheduler
|
||||
if (ShouldThrottle (r))
|
||||
{
|
||||
reason = ReasonCannotSend.TooManyRequests;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_parser.IsExpecting (r.Terminator))
|
||||
{
|
||||
reason = ReasonCannotSend.OutstandingRequest;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
reason = default;
|
||||
reason = default (ReasonCannotSend);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -163,7 +173,7 @@ internal class AnsiRequestScheduler
|
||||
{
|
||||
if (_lastSend.TryGetValue (r.Terminator, out DateTime value))
|
||||
{
|
||||
return _now() - value < _throttle;
|
||||
return _now () - value < _throttle;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -173,18 +183,18 @@ internal class AnsiRequestScheduler
|
||||
internal enum ReasonCannotSend
|
||||
{
|
||||
/// <summary>
|
||||
/// No reason given.
|
||||
/// No reason given.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The parser is already waiting for a request to complete with the given terminator.
|
||||
/// The parser is already waiting for a request to complete with the given terminator.
|
||||
/// </summary>
|
||||
OutstandingRequest,
|
||||
|
||||
/// <summary>
|
||||
/// There have been too many requests sent recently, new requests will be put into
|
||||
/// queue to prevent console becoming unresponsive.
|
||||
/// There have been too many requests sent recently, new requests will be put into
|
||||
/// queue to prevent console becoming unresponsive.
|
||||
/// </summary>
|
||||
TooManyRequests
|
||||
}
|
||||
|
||||
@@ -3,8 +3,5 @@ namespace Terminal.Gui;
|
||||
|
||||
internal record AnsiResponseExpectation (string Terminator, Action<IHeld> Response)
|
||||
{
|
||||
public bool Matches (string cur)
|
||||
{
|
||||
return cur.EndsWith (Terminator);
|
||||
}
|
||||
}
|
||||
public bool Matches (string cur) { return cur.EndsWith (Terminator); }
|
||||
}
|
||||
|
||||
@@ -2,25 +2,25 @@
|
||||
|
||||
namespace Terminal.Gui;
|
||||
|
||||
|
||||
internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
{
|
||||
protected object lockExpectedResponses = new object();
|
||||
protected object lockExpectedResponses = new ();
|
||||
|
||||
protected object lockState = new ();
|
||||
|
||||
protected object lockState = new object ();
|
||||
/// <summary>
|
||||
/// Responses we are expecting to come in.
|
||||
/// Responses we are expecting to come in.
|
||||
/// </summary>
|
||||
protected readonly List<AnsiResponseExpectation> expectedResponses = new ();
|
||||
|
||||
/// <summary>
|
||||
/// Collection of responses that we <see cref="StopExpecting"/>.
|
||||
/// Collection of responses that we <see cref="StopExpecting"/>.
|
||||
/// </summary>
|
||||
protected readonly List<AnsiResponseExpectation> lateResponses = new ();
|
||||
|
||||
/// <summary>
|
||||
/// Responses that you want to look out for that will come in continuously e.g. mouse events.
|
||||
/// Key is the terminator.
|
||||
/// Responses that you want to look out for that will come in continuously e.g. mouse events.
|
||||
/// Key is the terminator.
|
||||
/// </summary>
|
||||
protected readonly List<AnsiResponseExpectation> persistentExpectations = new ();
|
||||
|
||||
@@ -47,21 +47,20 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
// 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
|
||||
// No - N or O
|
||||
protected readonly HashSet<char> _knownTerminators = new (new []
|
||||
{
|
||||
'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
||||
// No - N or O
|
||||
'P', 'Q', 'R', 'S', 'T', 'W', 'X', 'Z',
|
||||
'^', '`', '~',
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
|
||||
'l', 'm', 'n',
|
||||
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
|
||||
});
|
||||
protected readonly HashSet<char> _knownTerminators = new (
|
||||
new []
|
||||
{
|
||||
'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
||||
|
||||
protected AnsiResponseParserBase (IHeld heldContent)
|
||||
{
|
||||
this.heldContent = heldContent;
|
||||
}
|
||||
// No - N or O
|
||||
'P', 'Q', 'R', 'S', 'T', 'W', 'X', 'Z',
|
||||
'^', '`', '~',
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
|
||||
'l', 'm', 'n',
|
||||
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
|
||||
});
|
||||
|
||||
protected AnsiResponseParserBase (IHeld heldContent) { this.heldContent = heldContent; }
|
||||
|
||||
protected void ResetState ()
|
||||
{
|
||||
@@ -187,8 +186,8 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
if (MatchResponse (
|
||||
cur,
|
||||
expectedResponses,
|
||||
invokeCallback: true,
|
||||
removeExpectation: true))
|
||||
true,
|
||||
true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -197,8 +196,8 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
if (MatchResponse (
|
||||
cur,
|
||||
lateResponses,
|
||||
invokeCallback: false,
|
||||
removeExpectation: true))
|
||||
false,
|
||||
true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -207,8 +206,8 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
if (MatchResponse (
|
||||
cur,
|
||||
persistentExpectations,
|
||||
invokeCallback: true,
|
||||
removeExpectation: false))
|
||||
true,
|
||||
false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -222,11 +221,12 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
State = AnsiResponseParserState.Normal;
|
||||
|
||||
// Maybe swallow anyway if user has custom delegate
|
||||
var swallow = ShouldSwallowUnexpectedResponse ();
|
||||
bool swallow = ShouldSwallowUnexpectedResponse ();
|
||||
|
||||
if (swallow)
|
||||
{
|
||||
heldContent.ClearHeld ();
|
||||
|
||||
// Do not send back to input stream
|
||||
return false;
|
||||
}
|
||||
@@ -239,14 +239,15 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// When overriden in a derived class, indicates whether the unexpected response
|
||||
/// currently in <see cref="heldContent"/> should be released or swallowed.
|
||||
/// Use this to enable default event for escape codes.
|
||||
/// </para>
|
||||
///
|
||||
/// <remarks>Note this is only called for complete responses.
|
||||
/// Based on <see cref="_knownTerminators"/></remarks>
|
||||
/// <para>
|
||||
/// When overriden in a derived class, indicates whether the unexpected response
|
||||
/// currently in <see cref="heldContent"/> should be released or swallowed.
|
||||
/// Use this to enable default event for escape codes.
|
||||
/// </para>
|
||||
/// <remarks>
|
||||
/// Note this is only called for complete responses.
|
||||
/// Based on <see cref="_knownTerminators"/>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected abstract bool ShouldSwallowUnexpectedResponse ();
|
||||
@@ -254,7 +255,7 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
private bool MatchResponse (string cur, List<AnsiResponseExpectation> collection, bool invokeCallback, bool removeExpectation)
|
||||
{
|
||||
// Check for expected responses
|
||||
var matchingResponse = collection.FirstOrDefault (r => r.Matches (cur));
|
||||
AnsiResponseExpectation? matchingResponse = collection.FirstOrDefault (r => r.Matches (cur));
|
||||
|
||||
if (matchingResponse?.Response != null)
|
||||
{
|
||||
@@ -262,6 +263,7 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
{
|
||||
matchingResponse.Response.Invoke (heldContent);
|
||||
}
|
||||
|
||||
ResetState ();
|
||||
|
||||
if (removeExpectation)
|
||||
@@ -275,23 +277,23 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <inheritdoc/>
|
||||
public void ExpectResponse (string terminator, Action<string> response, bool persistent)
|
||||
{
|
||||
lock (lockExpectedResponses)
|
||||
{
|
||||
if (persistent)
|
||||
{
|
||||
persistentExpectations.Add (new (terminator, (h) => response.Invoke (h.HeldToString ())));
|
||||
persistentExpectations.Add (new (terminator, h => response.Invoke (h.HeldToString ())));
|
||||
}
|
||||
else
|
||||
{
|
||||
expectedResponses.Add (new (terminator, (h) => response.Invoke (h.HeldToString ())));
|
||||
expectedResponses.Add (new (terminator, h => response.Invoke (h.HeldToString ())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <inheritdoc/>
|
||||
public bool IsExpecting (string terminator)
|
||||
{
|
||||
lock (lockExpectedResponses)
|
||||
@@ -301,10 +303,9 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <inheritdoc/>
|
||||
public void StopExpecting (string terminator, bool persistent)
|
||||
{
|
||||
|
||||
lock (lockExpectedResponses)
|
||||
{
|
||||
if (persistent)
|
||||
@@ -313,9 +314,9 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
}
|
||||
else
|
||||
{
|
||||
var removed = expectedResponses.Where (r => r.Terminator == terminator).ToArray ();
|
||||
AnsiResponseExpectation [] removed = expectedResponses.Where (r => r.Terminator == terminator).ToArray ();
|
||||
|
||||
foreach (var r in removed)
|
||||
foreach (AnsiResponseExpectation r in removed)
|
||||
{
|
||||
expectedResponses.Remove (r);
|
||||
lateResponses.Add (r);
|
||||
@@ -329,14 +330,12 @@ internal class AnsiResponseParser<T> : AnsiResponseParserBase
|
||||
{
|
||||
public AnsiResponseParser () : base (new GenericHeld<T> ()) { }
|
||||
|
||||
|
||||
/// <inheritdoc cref="AnsiResponseParser.UnknownResponseHandler"/>
|
||||
public Func<IEnumerable<Tuple<char, T>>, bool> UnexpectedResponseHandler { get; set; } = (_) => false;
|
||||
|
||||
public Func<IEnumerable<Tuple<char, T>>, bool> UnexpectedResponseHandler { get; set; } = _ => false;
|
||||
|
||||
public IEnumerable<Tuple<char, T>> ProcessInput (params Tuple<char, T> [] input)
|
||||
{
|
||||
List<Tuple<char, T>> output = new List<Tuple<char, T>> ();
|
||||
List<Tuple<char, T>> output = new ();
|
||||
|
||||
ProcessInputBase (
|
||||
i => input [i].Item1,
|
||||
@@ -352,64 +351,57 @@ internal class AnsiResponseParser<T> : AnsiResponseParserBase
|
||||
// Lock in case Release is called from different Thread from parse
|
||||
lock (lockState)
|
||||
{
|
||||
foreach (Tuple<char, T> h in HeldToEnumerable())
|
||||
foreach (Tuple<char, T> h in HeldToEnumerable ())
|
||||
{
|
||||
yield return h;
|
||||
}
|
||||
|
||||
ResetState ();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private IEnumerable<Tuple<char, T>> HeldToEnumerable ()
|
||||
{
|
||||
return (IEnumerable<Tuple<char, T>>)heldContent.HeldToObjects ();
|
||||
}
|
||||
private IEnumerable<Tuple<char, T>> HeldToEnumerable () { return (IEnumerable<Tuple<char, T>>)heldContent.HeldToObjects (); }
|
||||
|
||||
/// <summary>
|
||||
/// 'Overload' for specifying an expectation that requires the metadata as well as characters. Has
|
||||
/// a unique name because otherwise most lamdas will give ambiguous overload errors.
|
||||
/// 'Overload' for specifying an expectation that requires the metadata as well as characters. Has
|
||||
/// a unique name because otherwise most lamdas will give ambiguous overload errors.
|
||||
/// </summary>
|
||||
/// <param name="terminator"></param>
|
||||
/// <param name="response"></param>
|
||||
/// <param name="persistent"></param>
|
||||
public void ExpectResponseT (string terminator, Action<IEnumerable<Tuple<char,T>>> response, bool persistent)
|
||||
public void ExpectResponseT (string terminator, Action<IEnumerable<Tuple<char, T>>> response, bool persistent)
|
||||
{
|
||||
lock (lockExpectedResponses)
|
||||
{
|
||||
if (persistent)
|
||||
{
|
||||
persistentExpectations.Add (new (terminator, (h) => response.Invoke (HeldToEnumerable ())));
|
||||
persistentExpectations.Add (new (terminator, h => response.Invoke (HeldToEnumerable ())));
|
||||
}
|
||||
else
|
||||
{
|
||||
expectedResponses.Add (new (terminator, (h) => response.Invoke (HeldToEnumerable ())));
|
||||
expectedResponses.Add (new (terminator, h => response.Invoke (HeldToEnumerable ())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool ShouldSwallowUnexpectedResponse ()
|
||||
{
|
||||
return UnexpectedResponseHandler.Invoke (HeldToEnumerable ());
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
protected override bool ShouldSwallowUnexpectedResponse () { return UnexpectedResponseHandler.Invoke (HeldToEnumerable ()); }
|
||||
}
|
||||
|
||||
internal class AnsiResponseParser : AnsiResponseParserBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Delegate for handling unrecognized escape codes. Default behaviour
|
||||
/// is to return <see langword="false"/> which simply releases the
|
||||
/// characters back to input stream for downstream processing.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Implement a method to handle if you want and return <see langword="true"/> if you want the
|
||||
/// keystrokes 'swallowed' (i.e. not returned to input stream).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Delegate for handling unrecognized escape codes. Default behaviour
|
||||
/// is to return <see langword="false"/> which simply releases the
|
||||
/// characters back to input stream for downstream processing.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Implement a method to handle if you want and return <see langword="true"/> if you want the
|
||||
/// keystrokes 'swallowed' (i.e. not returned to input stream).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public Func<string, bool> UnknownResponseHandler { get; set; } = (_) => false;
|
||||
public Func<string, bool> UnknownResponseHandler { get; set; } = _ => false;
|
||||
|
||||
public AnsiResponseParser () : base (new StringHeld ()) { }
|
||||
|
||||
@@ -430,16 +422,13 @@ internal class AnsiResponseParser : AnsiResponseParserBase
|
||||
{
|
||||
lock (lockState)
|
||||
{
|
||||
var output = heldContent.HeldToString ();
|
||||
string output = heldContent.HeldToString ();
|
||||
ResetState ();
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool ShouldSwallowUnexpectedResponse ()
|
||||
{
|
||||
return UnknownResponseHandler.Invoke (heldContent.HeldToString ());
|
||||
}
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
protected override bool ShouldSwallowUnexpectedResponse () { return UnknownResponseHandler.Invoke (heldContent.HeldToString ()); }
|
||||
}
|
||||
|
||||
@@ -2,15 +2,18 @@
|
||||
namespace Terminal.Gui;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IHeld"/> for <see cref="AnsiResponseParser{T}"/>
|
||||
/// Implementation of <see cref="IHeld"/> for <see cref="AnsiResponseParser{T}"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal class GenericHeld<T> : IHeld
|
||||
{
|
||||
private readonly List<Tuple<char, T>> held = new ();
|
||||
|
||||
public void ClearHeld () => held.Clear ();
|
||||
public string HeldToString () => new (held.Select (h => h.Item1).ToArray ());
|
||||
public IEnumerable<object> HeldToObjects () => held;
|
||||
public void AddToHeld (object o) => held.Add ((Tuple<char, T>)o);
|
||||
public void ClearHeld () { held.Clear (); }
|
||||
|
||||
public string HeldToString () { return new (held.Select (h => h.Item1).ToArray ()); }
|
||||
|
||||
public IEnumerable<object> HeldToObjects () { return held; }
|
||||
|
||||
public void AddToHeld (object o) { held.Add ((Tuple<char, T>)o); }
|
||||
}
|
||||
|
||||
@@ -2,46 +2,52 @@
|
||||
namespace Terminal.Gui;
|
||||
|
||||
/// <summary>
|
||||
/// When implemented in a derived class, allows watching an input stream of characters
|
||||
/// (i.e. console input) for ANSI response sequences.
|
||||
/// When implemented in a derived class, allows watching an input stream of characters
|
||||
/// (i.e. console input) for ANSI response sequences.
|
||||
/// </summary>
|
||||
public interface IAnsiResponseParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Current state of the parser based on what sequence of characters it has
|
||||
/// read from the console input stream.
|
||||
/// Current state of the parser based on what sequence of characters it has
|
||||
/// read from the console input stream.
|
||||
/// </summary>
|
||||
AnsiResponseParserState State { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Notifies the parser that you are expecting a response to come in
|
||||
/// with the given <paramref name="terminator"/> (i.e. because you have
|
||||
/// sent an ANSI request out).
|
||||
/// Notifies the parser that you are expecting a response to come in
|
||||
/// with the given <paramref name="terminator"/> (i.e. because you have
|
||||
/// sent an ANSI request out).
|
||||
/// </summary>
|
||||
/// <param name="terminator">The terminator you expect to see on response.</param>
|
||||
/// <param name="persistent"><see langword="true"/> if you want this to persist permanently
|
||||
/// and be raised for every event matching the <paramref name="terminator"/>.</param>
|
||||
/// <param name="persistent">
|
||||
/// <see langword="true"/> if you want this to persist permanently
|
||||
/// and be raised for every event matching the <paramref name="terminator"/>.
|
||||
/// </param>
|
||||
/// <param name="response">Callback to invoke when the response is seen in console input.</param>
|
||||
/// <exception cref="ArgumentException">If trying to register a persistent request for a terminator
|
||||
/// that already has one.
|
||||
/// exists.</exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// If trying to register a persistent request for a terminator
|
||||
/// that already has one.
|
||||
/// exists.
|
||||
/// </exception>
|
||||
void ExpectResponse (string terminator, Action<string> response, bool persistent);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if there is an existing expectation (i.e. we are waiting a response
|
||||
/// from console) for the given <paramref name="terminator"/>.
|
||||
/// Returns true if there is an existing expectation (i.e. we are waiting a response
|
||||
/// from console) for the given <paramref name="terminator"/>.
|
||||
/// </summary>
|
||||
/// <param name="terminator"></param>
|
||||
/// <returns></returns>
|
||||
bool IsExpecting (string terminator);
|
||||
|
||||
/// <summary>
|
||||
/// Removes callback and expectation that we will get a response for the
|
||||
/// given <pararef name="requestTerminator"/>. Use to give up on very old
|
||||
/// requests e.g. if you want to send a different one with the same terminator.
|
||||
/// Removes callback and expectation that we will get a response for the
|
||||
/// given <pararef name="requestTerminator"/>. Use to give up on very old
|
||||
/// requests e.g. if you want to send a different one with the same terminator.
|
||||
/// </summary>
|
||||
/// <param name="requestTerminator"></param>
|
||||
/// <param name="persistent"><see langword="true"/> if you want to remove a persistent
|
||||
/// request listener.</param>
|
||||
/// <param name="persistent">
|
||||
/// <see langword="true"/> if you want to remove a persistent
|
||||
/// request listener.
|
||||
/// </param>
|
||||
void StopExpecting (string requestTerminator, bool persistent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,31 +2,31 @@
|
||||
namespace Terminal.Gui;
|
||||
|
||||
/// <summary>
|
||||
/// Describes a sequence of chars (and optionally T metadata) accumulated
|
||||
/// by an <see cref="IAnsiResponseParser"/>
|
||||
/// Describes a sequence of chars (and optionally T metadata) accumulated
|
||||
/// by an <see cref="IAnsiResponseParser"/>
|
||||
/// </summary>
|
||||
internal interface IHeld
|
||||
{
|
||||
/// <summary>
|
||||
/// Clears all held objects
|
||||
/// Clears all held objects
|
||||
/// </summary>
|
||||
void ClearHeld ();
|
||||
|
||||
/// <summary>
|
||||
/// Returns string representation of the held objects
|
||||
/// Returns string representation of the held objects
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string HeldToString ();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the collection objects directly e.g. <see langword="char"/>
|
||||
/// or <see cref="Tuple"/> <see langword="char"/> + metadata T
|
||||
/// Returns the collection objects directly e.g. <see langword="char"/>
|
||||
/// or <see cref="Tuple"/> <see langword="char"/> + metadata T
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IEnumerable<object> HeldToObjects ();
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given object to the collection.
|
||||
/// Adds the given object to the collection.
|
||||
/// </summary>
|
||||
/// <param name="o"></param>
|
||||
void AddToHeld (object o);
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
namespace Terminal.Gui;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IHeld"/> for <see cref="AnsiResponseParser"/>
|
||||
/// Implementation of <see cref="IHeld"/> for <see cref="AnsiResponseParser"/>
|
||||
/// </summary>
|
||||
internal class StringHeld : IHeld
|
||||
{
|
||||
private readonly StringBuilder held = new ();
|
||||
|
||||
public void ClearHeld () => held.Clear ();
|
||||
public string HeldToString () => held.ToString ();
|
||||
public IEnumerable<object> HeldToObjects () => held.ToString ().Select (c => (object)c);
|
||||
public void AddToHeld (object o) => held.Append ((char)o);
|
||||
public void ClearHeld () { held.Clear (); }
|
||||
|
||||
public string HeldToString () { return held.ToString (); }
|
||||
|
||||
public IEnumerable<object> HeldToObjects () { return held.ToString ().Select (c => (object)c); }
|
||||
|
||||
public void AddToHeld (object o) { held.Append ((char)o); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user