120 lines
3.1 KiB
Go
120 lines
3.1 KiB
Go
package graphemes
|
||
|
||
// ansiEscapeLength returns the byte length of a valid ANSI escape sequence at the
|
||
// start of data, or 0 if none. Input is UTF-8; only 7-bit ESC sequences are
|
||
// recognized (C1 0x80–0x9F can be UTF-8 continuation bytes).
|
||
//
|
||
// Recognized forms (ECMA-48 / ISO 6429):
|
||
// - CSI: ESC [ then parameter bytes (0x30–0x3F), intermediate (0x20–0x2F), final (0x40–0x7E)
|
||
// - OSC: ESC ] then payload until ST (ESC \) or BEL (0x07)
|
||
// - DCS, SOS, PM, APC: ESC P / X / ^ / _ then payload until ST (ESC \)
|
||
// - Two-byte: ESC + Fe (0x40–0x5F 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 {
|
||
return 0
|
||
}
|
||
if data[0] != esc {
|
||
return 0
|
||
}
|
||
|
||
b1 := data[1]
|
||
switch b1 {
|
||
case '[': // CSI
|
||
body := csiLength(data[2:])
|
||
if body == 0 {
|
||
return 0
|
||
}
|
||
return 2 + body
|
||
case ']': // OSC – allows BEL or ST as terminator
|
||
body := oscLength(data[2:])
|
||
if body == 0 {
|
||
return 0
|
||
}
|
||
return 2 + body
|
||
case 'P', 'X', '^', '_': // DCS, SOS, PM, APC – require ST (ESC \) only
|
||
body := stSequenceLength(data[2:])
|
||
if body == 0 {
|
||
return 0
|
||
}
|
||
return 2 + body
|
||
}
|
||
if b1 >= 0x40 && b1 <= 0x5F {
|
||
// Fe (C1) 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
|
||
}
|
||
|
||
// csiLength 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 csiLength[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 up to and including
|
||
// the terminator. OSC accepts either BEL (0x07) or ST (ESC \) per
|
||
// widespread terminal convention. data is the slice after "ESC ]".
|
||
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 == esc && i+1 < len(data) && data[i+1] == '\\' {
|
||
return i + 2
|
||
}
|
||
}
|
||
return 0
|
||
}
|
||
|
||
// stSequenceLength returns the length of a control-string body up to and
|
||
// including the ST (ESC \) terminator. Used for DCS, SOS, PM, and APC, which
|
||
// per ECMA-48 require ST and do not accept BEL. data is the slice after "ESC x".
|
||
func stSequenceLength[T ~string | ~[]byte](data T) int {
|
||
for i := 0; i < len(data); i++ {
|
||
if data[i] == esc && i+1 < len(data) && data[i+1] == '\\' {
|
||
return i + 2
|
||
}
|
||
}
|
||
return 0
|
||
}
|