#nullable enable
namespace Terminal.Gui;
///
/// Describes an ongoing ANSI request sent to the console.
/// Use to handle the response
/// when console answers the request.
///
public class AnsiEscapeSequenceRequest
{
///
/// Request to send e.g. see
///
/// EscSeqUtils.CSI_SendDeviceAttributes.Request
///
///
public required string Request { get; init; }
///
/// Invoked when the console responds with an ANSI response code that matches the
///
///
public event EventHandler? ResponseReceived;
///
///
/// The terminator that uniquely identifies the type of response as responded
/// by the console. e.g. for
///
/// EscSeqUtils.CSI_SendDeviceAttributes.Request
///
/// the terminator is
///
/// EscSeqUtils.CSI_SendDeviceAttributes.Terminator
///
/// .
///
///
/// After sending a request, the first response with matching terminator will be matched
/// to the oldest outstanding request.
///
///
public required string Terminator { get; init; }
///
/// Execute an ANSI escape sequence escape which may return a response or error.
///
/// The ANSI escape sequence to request.
///
/// When this method returns , an object containing the response with an empty
/// error.
///
/// A with the response, error, terminator and value.
public static bool TryExecuteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest, out AnsiEscapeSequenceResponse result)
{
var response = new StringBuilder ();
var error = new StringBuilder ();
var savedIsReportingMouseMoves = false;
NetDriver? netDriver = null;
try
{
switch (Application.Driver)
{
case NetDriver:
netDriver = Application.Driver as NetDriver;
savedIsReportingMouseMoves = netDriver!.IsReportingMouseMoves;
if (savedIsReportingMouseMoves)
{
netDriver.StopReportingMouseMoves ();
}
while (Console.KeyAvailable)
{
netDriver._mainLoopDriver._netEvents._waitForStart.Set ();
netDriver._mainLoopDriver._netEvents._waitForStart.Reset ();
netDriver._mainLoopDriver._netEvents._forceRead = true;
}
netDriver._mainLoopDriver._netEvents._forceRead = false;
break;
case CursesDriver cursesDriver:
savedIsReportingMouseMoves = cursesDriver.IsReportingMouseMoves;
if (savedIsReportingMouseMoves)
{
cursesDriver.StopReportingMouseMoves ();
}
break;
}
if (netDriver is { })
{
NetEvents._suspendRead = true;
}
else
{
Thread.Sleep (100); // Allow time for mouse stopping and to flush the input buffer
// Flush the input buffer to avoid reading stale input
while (Console.KeyAvailable)
{
Console.ReadKey (true);
}
}
// Send the ANSI escape sequence
Console.Write (ansiRequest.Request);
Console.Out.Flush (); // Ensure the request is sent
// Read the response from stdin (response should come back as input)
Thread.Sleep (100); // Allow time for the terminal to respond
// Read input until no more characters are available or the terminator is encountered
while (Console.KeyAvailable)
{
// Peek the next key
ConsoleKeyInfo keyInfo = Console.ReadKey (true); // true to not display on the console
// Append the current key to the response
response.Append (keyInfo.KeyChar);
if (keyInfo.KeyChar == ansiRequest.Terminator [^1]) // Check if the key is terminator (ANSI escape sequence ends)
{
// Break out of the loop when terminator is found
break;
}
}
if (!response.ToString ().EndsWith (ansiRequest.Terminator [^1]))
{
throw new InvalidOperationException ($"Terminator doesn't ends with: '{ansiRequest.Terminator [^1]}'");
}
}
catch (Exception ex)
{
error.AppendLine ($"Error executing ANSI request: {ex.Message}");
}
finally
{
if (savedIsReportingMouseMoves)
{
switch (Application.Driver)
{
case NetDriver:
NetEvents._suspendRead = false;
netDriver!.StartReportingMouseMoves ();
break;
case CursesDriver cursesDriver:
cursesDriver.StartReportingMouseMoves ();
break;
}
}
}
var values = new string? [] { null };
if (string.IsNullOrEmpty (error.ToString ()))
{
(string? c1Control, string? code, values, string? terminator) = EscSeqUtils.GetEscapeResult (response.ToString ().ToCharArray ());
}
AnsiEscapeSequenceResponse ansiResponse = new ()
{
Response = response.ToString (), Error = error.ToString (),
Terminator = string.IsNullOrEmpty (response.ToString ()) ? "" : response.ToString () [^1].ToString (), Value = values [0]
};
// Invoke the event if it's subscribed
ansiRequest.ResponseReceived?.Invoke (ansiRequest, ansiResponse);
result = ansiResponse;
return string.IsNullOrWhiteSpace (result.Error) && !string.IsNullOrWhiteSpace (result.Response);
}
///
/// The value expected in the response e.g.
///
/// EscSeqUtils.CSI_ReportTerminalSizeInChars.Value
///
/// which will have a 't' as terminator but also other different request may return the same terminator with a
/// different value.
///
public string? Value { get; init; }
}