Code Cleanup (resharper)

This commit is contained in:
tznind
2024-10-30 20:28:31 +00:00
parent fdfd339dd2
commit fbbda69ba0
8 changed files with 185 additions and 180 deletions

View File

@@ -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); }
}

View File

@@ -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
}

View File

@@ -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); }
}

View File

@@ -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 ()); }
}

View File

@@ -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); }
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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); }
}