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