AnsiResponseParser.cs 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. #nullable enable
  2. using System.Diagnostics;
  3. namespace Terminal.Gui;
  4. class AnsiResponseParser
  5. {
  6. /*
  7. * ANSI Input Sequences
  8. *
  9. * \x1B[A // Up Arrow key pressed
  10. * \x1B[B // Down Arrow key pressed
  11. * \x1B[C // Right Arrow key pressed
  12. * \x1B[D // Left Arrow key pressed
  13. * \x1B[3~ // Delete key pressed
  14. * \x1B[2~ // Insert key pressed
  15. * \x1B[5~ // Page Up key pressed
  16. * \x1B[6~ // Page Down key pressed
  17. * \x1B[1;5D // Ctrl + Left Arrow
  18. * \x1B[1;5C // Ctrl + Right Arrow
  19. * \x1B[0;10;20M // Mouse button pressed at position (10, 20)
  20. * \x1B[0c // Device Attributes Response (e.g., terminal identification)
  21. */
  22. private bool inResponse = false;
  23. private StringBuilder held = new StringBuilder();
  24. /// <summary>
  25. /// <para>
  26. /// Processes input which may be a single character or multiple.
  27. /// Returns what should be passed on to any downstream input processing
  28. /// (i.e. removes expected Ansi responses from the input stream
  29. /// </para>
  30. /// <para>
  31. /// This method is designed to be called iteratively and as such may
  32. /// return more characters than were passed in depending on previous
  33. /// calls (e.g. if it was in the middle of an unrelated ANSI response.</para>
  34. /// </summary>
  35. /// <param name="input"></param>
  36. /// <returns></returns>
  37. public string ProcessInput (string input)
  38. {
  39. if (inResponse)
  40. {
  41. if (currentTerminator != null && input.StartsWith (currentTerminator))
  42. {
  43. // Consume terminator and release the event
  44. held.Append (currentTerminator);
  45. currentResponse?.Invoke (held.ToString());
  46. // clear the state
  47. held.Clear ();
  48. currentResponse = null;
  49. // recurse
  50. return ProcessInput (input.Substring (currentTerminator.Length));
  51. }
  52. // we are in a response but have not reached terminator yet
  53. held.Append (input [0]);
  54. return ProcessInput (input.Substring (1));
  55. }
  56. // if character is escape
  57. if (input.StartsWith ('\x1B'))
  58. {
  59. // We shouldn't get an escape in the middle of a response - TODO: figure out how to handle that
  60. Debug.Assert (!inResponse);
  61. // consume the escape
  62. held.Append (input [0]);
  63. inResponse = true;
  64. return ProcessInput (input.Substring (1));
  65. }
  66. return input[0] + ProcessInput (input.Substring (1));
  67. }
  68. private string? currentTerminator = null;
  69. private Action<string>? currentResponse = null;
  70. public void ExpectResponse (string terminator, Action<string> response)
  71. {
  72. currentTerminator = terminator;
  73. currentResponse = response;
  74. }
  75. }