AnsiEscapeSequenceRequest.cs 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. #nullable enable
  2. namespace Terminal.Gui;
  3. /// <summary>
  4. /// Describes an ongoing ANSI request sent to the console.
  5. /// Use <see cref="ResponseReceived"/> to handle the response
  6. /// when the console answers the request.
  7. /// </summary>
  8. public class AnsiEscapeSequenceRequest
  9. {
  10. internal readonly object _responseLock = new (); // Per-instance lock
  11. /// <summary>
  12. /// Gets the response received from the request.
  13. /// </summary>
  14. public AnsiEscapeSequenceResponse? AnsiEscapeSequenceResponse { get; internal set; }
  15. /// <summary>
  16. /// The value expected in the response after the CSI e.g.
  17. /// <see>
  18. /// <cref>EscSeqUtils.CSI_ReportTerminalSizeInChars.Value</cref>
  19. /// </see>
  20. /// should result in a response of the form <c>ESC [ 8 ; height ; width t</c>. In this case,
  21. /// <see cref="ExpectedResponseValue"/>
  22. /// will be <c>"8"</c>.
  23. /// </summary>
  24. public string? ExpectedResponseValue { get; init; }
  25. /// <summary>
  26. /// Gets the request string to send e.g. see
  27. /// <see>
  28. /// <cref>EscSeqUtils.CSI_SendDeviceAttributes.Request</cref>
  29. /// </see>
  30. /// </summary>
  31. public required string Request { get; init; }
  32. /// <summary>
  33. /// <para>
  34. /// Gets the terminator that uniquely identifies the response received from
  35. /// the console. e.g. for
  36. /// <see>
  37. /// <cref>EscSeqUtils.CSI_SendDeviceAttributes.Request</cref>
  38. /// </see>
  39. /// the terminator is
  40. /// <see>
  41. /// <cref>EscSeqUtils.CSI_SendDeviceAttributes.Terminator</cref>
  42. /// </see>
  43. /// .
  44. /// </para>
  45. /// <para>
  46. /// After sending a request, the first response with matching terminator will be matched
  47. /// to the oldest outstanding request.
  48. /// </para>
  49. /// </summary>
  50. public required string Terminator { get; init; }
  51. internal void RaiseResponseFromInput (string? response)
  52. {
  53. ProcessResponse (response);
  54. ResponseFromInput?.Invoke (this, AnsiEscapeSequenceResponse);
  55. }
  56. /// <summary>
  57. /// Raised with the response object and validation.
  58. /// </summary>
  59. internal event EventHandler<AnsiEscapeSequenceResponse?>? ResponseFromInput;
  60. /// <summary>
  61. /// Process the <see cref="AnsiEscapeSequenceResponse"/> of an ANSI escape sequence request.
  62. /// </summary>
  63. /// <param name="response">The response.</param>
  64. private void ProcessResponse (string? response)
  65. {
  66. var error = new StringBuilder ();
  67. var values = new string? [] { null };
  68. try
  69. {
  70. if (!string.IsNullOrEmpty (response) && !response.StartsWith (AnsiEscapeSequenceRequestUtils.KeyEsc))
  71. {
  72. throw new InvalidOperationException ($"Invalid Response: {response}");
  73. }
  74. if (string.IsNullOrEmpty (Terminator))
  75. {
  76. throw new InvalidOperationException ("Terminator request is empty.");
  77. }
  78. if (string.IsNullOrEmpty (response))
  79. {
  80. throw new InvalidOperationException ("Response request is null.");
  81. }
  82. if (!string.IsNullOrEmpty (response) && !response.EndsWith (Terminator [^1]))
  83. {
  84. string resp = string.IsNullOrEmpty (response) ? "" : response.Last ().ToString ();
  85. throw new InvalidOperationException ($"Terminator ends with '{resp}'\nand doesn't end with: '{Terminator [^1]}'");
  86. }
  87. }
  88. catch (Exception ex)
  89. {
  90. error.AppendLine ($"Error executing ANSI request:\n{ex.Message}");
  91. }
  92. finally
  93. {
  94. if (string.IsNullOrEmpty (error.ToString ()))
  95. {
  96. (string? _, string? _, values, string? _) = AnsiEscapeSequenceRequestUtils.GetEscapeResult (response?.ToCharArray ());
  97. }
  98. }
  99. AnsiEscapeSequenceResponse = new ()
  100. {
  101. Response = response, Error = error.ToString (),
  102. Terminator = string.IsNullOrEmpty (response) ? "" : response [^1].ToString (),
  103. ExpectedResponseValue = values [0],
  104. Valid = string.IsNullOrWhiteSpace (error.ToString ()) && !string.IsNullOrWhiteSpace (response)
  105. };
  106. }
  107. }