| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- package graphemes
- // ansiEscapeLength returns the byte length of a valid 7-bit ANSI escape
- // sequence at the start of data, or 0 if none.
- //
- // Recognized forms (ECMA-48 / ISO 6429):
- // - CSI: ESC [ then parameter bytes (0x30-0x3F), intermediate (0x20-0x2F), final (0x40-0x7E)
- // - OSC: ESC ] then payload until BEL (0x07), 7-bit ST (ESC \), CAN (0x18), or SUB (0x1A)
- // - DCS, SOS, PM, APC: ESC P/X/^/_ then payload until 7-bit ST (ESC \), CAN, or SUB
- // - Two-byte: ESC + Fe/Fs (0x40-0x7E excluding above), or Fp (0x30-0x3F), or nF (0x20-0x2F then final)
- func ansiEscapeLength[T ~string | ~[]byte](data T) int {
- n := len(data)
- if n < 2 || data[0] != esc {
- return 0
- }
- b1 := data[1]
- switch b1 {
- case '[': // CSI
- body := csiBodyLength(data[2:])
- if body == 0 {
- return 0
- }
- return 2 + body
- case ']': // OSC - allows BEL or 7-bit ST terminator
- body := oscLength(data[2:])
- if body < 0 {
- return 0
- }
- return 2 + body
- case 'P', 'X', '^', '_': // DCS, SOS, PM, APC
- body := stSequenceLength(data[2:])
- if body < 0 {
- return 0
- }
- return 2 + body
- }
- if b1 >= 0x40 && b1 <= 0x7E {
- // Fe/Fs two-byte; [ ] P X ^ _ handled above
- return 2
- }
- if b1 >= 0x30 && b1 <= 0x3F {
- // Fp (private) two-byte
- return 2
- }
- if b1 >= 0x20 && b1 <= 0x2F {
- // nF: intermediates then one final (0x30-0x7E)
- i := 2
- for i < n && data[i] >= 0x20 && data[i] <= 0x2F {
- i++
- }
- if i < n && data[i] >= 0x30 && data[i] <= 0x7E {
- return i + 1
- }
- return 0
- }
- return 0
- }
- // csiBodyLength returns the length of the CSI body (param/intermediate/final bytes).
- // data is the slice after "ESC [".
- // Per ECMA-48, the CSI body has the form:
- //
- // parameters (0x30–0x3F)*, intermediates (0x20–0x2F)*, final (0x40–0x7E)
- //
- // Once an intermediate byte is seen, subsequent parameter bytes are invalid.
- func csiBodyLength[T ~string | ~[]byte](data T) int {
- seenIntermediate := false
- for i := 0; i < len(data); i++ {
- b := data[i]
- if b >= 0x30 && b <= 0x3F {
- if seenIntermediate {
- return 0
- }
- continue
- }
- if b >= 0x20 && b <= 0x2F {
- seenIntermediate = true
- continue
- }
- if b >= 0x40 && b <= 0x7E {
- return i + 1
- }
- return 0
- }
- return 0
- }
- // oscLength returns the length of the OSC body.
- // data is the slice after "ESC ]".
- //
- // Returns:
- // - n >= 0: consumed body length (includes BEL/ST terminator when present)
- // - -1: not terminated in the provided data
- //
- // OSC accepts BEL (0x07) or 7-bit ST (ESC \) as terminators by widespread convention.
- // Per ECMA-48, CAN (0x18) and SUB (0x1A) cancel the control string; in that
- // case they are not part of the OSC sequence length.
- func oscLength[T ~string | ~[]byte](data T) int {
- for i := 0; i < len(data); i++ {
- b := data[i]
- if b == bel {
- return i + 1
- }
- if b == can || b == sub {
- return i
- }
- if b == esc && i+1 < len(data) && data[i+1] == '\\' {
- return i + 2
- }
- }
- return -1
- }
- // stSequenceLength returns the length of a control-string body.
- // data is the slice after "ESC x".
- //
- // Returns:
- // - n >= 0: consumed body length (includes ST terminator when present)
- // - -1: not terminated in the provided data
- //
- // Used for DCS, SOS, PM, and APC, which per ECMA-48 terminate with ST.
- // ST here is the 7-bit form (ESC \).
- // CAN (0x18) and SUB (0x1A) cancel the control string; in that case they are
- // not part of the sequence length.
- func stSequenceLength[T ~string | ~[]byte](data T) int {
- for i := 0; i < len(data); i++ {
- if data[i] == can || data[i] == sub {
- return i
- }
- if data[i] == esc && i+1 < len(data) && data[i+1] == '\\' {
- return i + 2
- }
- }
- return -1
- }
|