AnsiEscapeSequenceRequest.cs 4.3 KB

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