ansi8.go (1904B)
1 package graphemes 2 3 // ansiEscapeLength8Bit returns the byte length of a valid 8-bit C1 ANSI 4 // sequence at the start of data, or 0 if none. 5 // 6 // Recognized forms (ECMA-48 / ISO 6429): 7 // - C1 CSI (0x9B) body as parameter/intermediate/final bytes 8 // - C1 OSC (0x9D) body terminated by BEL, C1 ST, CAN, or SUB 9 // - C1 DCS/SOS/PM/APC (0x90/0x98/0x9E/0x9F) body terminated by C1 ST, CAN, or SUB 10 // - Standalone C1 controls (0x80..0x9F not listed above): single byte 11 func ansiEscapeLength8Bit[T ~string | ~[]byte](data T) int { 12 if len(data) == 0 { 13 return 0 14 } 15 16 switch data[0] { 17 case 0x9B: // C1 CSI 18 body := csiBodyLength(data[1:]) 19 if body == 0 { 20 return 0 21 } 22 return 1 + body 23 case 0x9D: // C1 OSC 24 body := oscLengthC1(data[1:]) 25 if body < 0 { 26 return 0 27 } 28 return 1 + body 29 case 0x90, 0x98, 0x9E, 0x9F: // C1 DCS, SOS, PM, APC 30 body := stSequenceLengthC1(data[1:]) 31 if body < 0 { 32 return 0 33 } 34 return 1 + body 35 default: 36 if data[0] >= 0x80 && data[0] <= 0x9F { 37 return 1 38 } 39 } 40 41 return 0 42 } 43 44 // oscLengthC1 returns the length of a C1 OSC body. 45 // data is the slice after the C1 OSC initiator (0x9D). 46 // 47 // Returns: 48 // - n >= 0: consumed body length (includes BEL/ST terminator when present) 49 // - -1: not terminated in the provided data 50 // 51 // Terminators: BEL (0x07) or C1 ST (0x9C). 52 // CAN (0x18) and SUB (0x1A) cancel the control string. 53 func oscLengthC1[T ~string | ~[]byte](data T) int { 54 for i := 0; i < len(data); i++ { 55 b := data[i] 56 if b == bel || b == st { 57 return i + 1 58 } 59 if b == can || b == sub { 60 return i 61 } 62 } 63 return -1 64 } 65 66 // stSequenceLengthC1 parses DCS/SOS/PM/APC bodies that terminate with C1 ST 67 // (0x9C), or are canceled by CAN/SUB. 68 func stSequenceLengthC1[T ~string | ~[]byte](data T) int { 69 for i := 0; i < len(data); i++ { 70 b := data[i] 71 if b == can || b == sub { 72 return i 73 } 74 if b == st { 75 return i + 1 76 } 77 } 78 return -1 79 }