src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

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 }