AnsiResponseParser.cs 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. #nullable enable
  2. using System.Diagnostics;
  3. using System.Text;
  4. namespace Terminal.Gui;
  5. class AnsiResponseParser
  6. {
  7. private bool inResponse = false;
  8. private StringBuilder held = new StringBuilder ();
  9. private string? currentTerminator = null;
  10. private Action<string>? currentResponse = null;
  11. private List<Func<string, bool>> _ignorers = new ();
  12. /*
  13. * ANSI Input Sequences
  14. *
  15. * \x1B[A // Up Arrow key pressed
  16. * \x1B[B // Down Arrow key pressed
  17. * \x1B[C // Right Arrow key pressed
  18. * \x1B[D // Left Arrow key pressed
  19. * \x1B[3~ // Delete key pressed
  20. * \x1B[2~ // Insert key pressed
  21. * \x1B[5~ // Page Up key pressed
  22. * \x1B[6~ // Page Down key pressed
  23. * \x1B[1;5D // Ctrl + Left Arrow
  24. * \x1B[1;5C // Ctrl + Right Arrow
  25. * \x1B[0;10;20M // Mouse button pressed at position (10, 20)
  26. * \x1B[0c // Device Attributes Response (e.g., terminal identification)
  27. */
  28. public AnsiResponseParser ()
  29. {
  30. // How to spot when you have entered and left an AnsiResponse but not the one we are looking for
  31. _ignorers.Add (s=>s.StartsWith ("\x1B[<") && s.EndsWith ("M"));
  32. }
  33. /// <summary>
  34. /// Processes input which may be a single character or multiple.
  35. /// Returns what should be passed on to any downstream input processing
  36. /// (i.e., removes expected ANSI responses from the input stream).
  37. /// </summary>
  38. public string ProcessInput (string input)
  39. {
  40. StringBuilder output = new StringBuilder (); // Holds characters that should pass through
  41. int index = 0; // Tracks position in the input string
  42. while (index < input.Length)
  43. {
  44. char currentChar = input [index];
  45. if (inResponse)
  46. {
  47. // If we are in a response, accumulate characters in `held`
  48. held.Append (currentChar);
  49. // Handle the current content in `held`
  50. var handled = HandleHeldContent ();
  51. if (!string.IsNullOrEmpty (handled))
  52. {
  53. // If content is ready to be released, append it to output and reset state
  54. output.Append (handled);
  55. inResponse = false;
  56. held.Clear ();
  57. }
  58. index++;
  59. continue;
  60. }
  61. // If character is the start of an escape sequence
  62. if (currentChar == '\x1B')
  63. {
  64. // Start capturing the ANSI response sequence
  65. inResponse = true;
  66. held.Append (currentChar);
  67. index++;
  68. continue;
  69. }
  70. // If not in an ANSI response, pass the character through as regular input
  71. output.Append (currentChar);
  72. index++;
  73. }
  74. // Return characters that should pass through as regular input
  75. return output.ToString ();
  76. }
  77. /// <summary>
  78. /// Checks the current `held` content to decide whether it should be released, either as an expected or unexpected response.
  79. /// </summary>
  80. private string HandleHeldContent ()
  81. {
  82. // If we're expecting a specific terminator, check if the content matches
  83. if (currentTerminator != null && held.ToString ().EndsWith (currentTerminator))
  84. {
  85. // If it matches the expected response, invoke the callback and return nothing for output
  86. currentResponse?.Invoke (held.ToString ());
  87. return string.Empty;
  88. }
  89. // Handle common ANSI sequences (such as mouse input or arrow keys)
  90. if (_ignorers.Any(m=>m.Invoke (held.ToString())))
  91. {
  92. // Detected mouse input, release it without triggering the delegate
  93. return held.ToString ();
  94. }
  95. // Add more cases here for other standard sequences (like arrow keys, function keys, etc.)
  96. // If no match, continue accumulating characters
  97. return string.Empty;
  98. }
  99. /// <summary>
  100. /// Registers a new expected ANSI response with a specific terminator and a callback for when the response is completed.
  101. /// </summary>
  102. public void ExpectResponse (string terminator, Action<string> response)
  103. {
  104. currentTerminator = terminator;
  105. currentResponse = response;
  106. }
  107. }