ansi.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. package graphemes
  2. // ansiEscapeLength returns the byte length of a valid 7-bit ANSI escape
  3. // sequence at the start of data, or 0 if none.
  4. //
  5. // Recognized forms (ECMA-48 / ISO 6429):
  6. // - CSI: ESC [ then parameter bytes (0x30-0x3F), intermediate (0x20-0x2F), final (0x40-0x7E)
  7. // - OSC: ESC ] then payload until BEL (0x07), 7-bit ST (ESC \), CAN (0x18), or SUB (0x1A)
  8. // - DCS, SOS, PM, APC: ESC P/X/^/_ then payload until 7-bit ST (ESC \), CAN, or SUB
  9. // - Two-byte: ESC + Fe/Fs (0x40-0x7E excluding above), or Fp (0x30-0x3F), or nF (0x20-0x2F then final)
  10. func ansiEscapeLength[T ~string | ~[]byte](data T) int {
  11. n := len(data)
  12. if n < 2 || data[0] != esc {
  13. return 0
  14. }
  15. b1 := data[1]
  16. switch b1 {
  17. case '[': // CSI
  18. body := csiBodyLength(data[2:])
  19. if body == 0 {
  20. return 0
  21. }
  22. return 2 + body
  23. case ']': // OSC - allows BEL or 7-bit ST terminator
  24. body := oscLength(data[2:])
  25. if body < 0 {
  26. return 0
  27. }
  28. return 2 + body
  29. case 'P', 'X', '^', '_': // DCS, SOS, PM, APC
  30. body := stSequenceLength(data[2:])
  31. if body < 0 {
  32. return 0
  33. }
  34. return 2 + body
  35. }
  36. if b1 >= 0x40 && b1 <= 0x7E {
  37. // Fe/Fs two-byte; [ ] P X ^ _ handled above
  38. return 2
  39. }
  40. if b1 >= 0x30 && b1 <= 0x3F {
  41. // Fp (private) two-byte
  42. return 2
  43. }
  44. if b1 >= 0x20 && b1 <= 0x2F {
  45. // nF: intermediates then one final (0x30-0x7E)
  46. i := 2
  47. for i < n && data[i] >= 0x20 && data[i] <= 0x2F {
  48. i++
  49. }
  50. if i < n && data[i] >= 0x30 && data[i] <= 0x7E {
  51. return i + 1
  52. }
  53. return 0
  54. }
  55. return 0
  56. }
  57. // csiBodyLength returns the length of the CSI body (param/intermediate/final bytes).
  58. // data is the slice after "ESC [".
  59. // Per ECMA-48, the CSI body has the form:
  60. //
  61. // parameters (0x30–0x3F)*, intermediates (0x20–0x2F)*, final (0x40–0x7E)
  62. //
  63. // Once an intermediate byte is seen, subsequent parameter bytes are invalid.
  64. func csiBodyLength[T ~string | ~[]byte](data T) int {
  65. seenIntermediate := false
  66. for i := 0; i < len(data); i++ {
  67. b := data[i]
  68. if b >= 0x30 && b <= 0x3F {
  69. if seenIntermediate {
  70. return 0
  71. }
  72. continue
  73. }
  74. if b >= 0x20 && b <= 0x2F {
  75. seenIntermediate = true
  76. continue
  77. }
  78. if b >= 0x40 && b <= 0x7E {
  79. return i + 1
  80. }
  81. return 0
  82. }
  83. return 0
  84. }
  85. // oscLength returns the length of the OSC body.
  86. // data is the slice after "ESC ]".
  87. //
  88. // Returns:
  89. // - n >= 0: consumed body length (includes BEL/ST terminator when present)
  90. // - -1: not terminated in the provided data
  91. //
  92. // OSC accepts BEL (0x07) or 7-bit ST (ESC \) as terminators by widespread convention.
  93. // Per ECMA-48, CAN (0x18) and SUB (0x1A) cancel the control string; in that
  94. // case they are not part of the OSC sequence length.
  95. func oscLength[T ~string | ~[]byte](data T) int {
  96. for i := 0; i < len(data); i++ {
  97. b := data[i]
  98. if b == bel {
  99. return i + 1
  100. }
  101. if b == can || b == sub {
  102. return i
  103. }
  104. if b == esc && i+1 < len(data) && data[i+1] == '\\' {
  105. return i + 2
  106. }
  107. }
  108. return -1
  109. }
  110. // stSequenceLength returns the length of a control-string body.
  111. // data is the slice after "ESC x".
  112. //
  113. // Returns:
  114. // - n >= 0: consumed body length (includes ST terminator when present)
  115. // - -1: not terminated in the provided data
  116. //
  117. // Used for DCS, SOS, PM, and APC, which per ECMA-48 terminate with ST.
  118. // ST here is the 7-bit form (ESC \).
  119. // CAN (0x18) and SUB (0x1A) cancel the control string; in that case they are
  120. // not part of the sequence length.
  121. func stSequenceLength[T ~string | ~[]byte](data T) int {
  122. for i := 0; i < len(data); i++ {
  123. if data[i] == can || data[i] == sub {
  124. return i
  125. }
  126. if data[i] == esc && i+1 < len(data) && data[i+1] == '\\' {
  127. return i + 2
  128. }
  129. }
  130. return -1
  131. }