End me
This commit is contained in:
parent
f21a41982e
commit
43a664009c
@ -1,10 +1,9 @@
|
|||||||
package ansi
|
package ansi
|
||||||
|
|
||||||
// CSI represents a list of CSI sequences that have no parameters.
|
// CSI represents a list of CSI sequences that have no parameters.
|
||||||
|
// FIXME: some of these do indeed have parameters
|
||||||
type CSI int; const (
|
type CSI int; const (
|
||||||
CSI_AuxPortOn CSI = iota
|
CSI_DeviceStatusReport CSI = iota
|
||||||
CSI_AuxPortOff
|
|
||||||
CSI_DeviceStatusReport
|
|
||||||
CSI_SaveCursorPosition
|
CSI_SaveCursorPosition
|
||||||
CSI_RestoreCursorPosition
|
CSI_RestoreCursorPosition
|
||||||
CSI_ShowCursor
|
CSI_ShowCursor
|
||||||
|
245
ansi/decoder.go
245
ansi/decoder.go
@ -1,5 +1,7 @@
|
|||||||
package ansi
|
package ansi
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
import "strings"
|
||||||
import "image/color"
|
import "image/color"
|
||||||
|
|
||||||
// Useful: https://en.wikipedia.org/wiki/ANSI_escape_code
|
// Useful: https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||||
@ -18,6 +20,9 @@ type Decoder struct {
|
|||||||
// OnC1 is called when a C1 escape sequence is processed.
|
// OnC1 is called when a C1 escape sequence is processed.
|
||||||
OnC1 func (C1)
|
OnC1 func (C1)
|
||||||
|
|
||||||
|
// OnDCS is called when a device control string is processed.
|
||||||
|
OnDCS func (string)
|
||||||
|
|
||||||
// OnCSI is called when a non-SGR CSI escape sequence with no parameters
|
// OnCSI is called when a non-SGR CSI escape sequence with no parameters
|
||||||
// is processed.
|
// is processed.
|
||||||
OnCSI func (CSI)
|
OnCSI func (CSI)
|
||||||
@ -26,6 +31,12 @@ type Decoder struct {
|
|||||||
// processed.
|
// processed.
|
||||||
OnSGR func (SGR)
|
OnSGR func (SGR)
|
||||||
|
|
||||||
|
// OnPM is called when a privacy message is processed.
|
||||||
|
OnPM func (string)
|
||||||
|
|
||||||
|
// OnAPC is called when an application program command is processed.
|
||||||
|
OnAPC func (string)
|
||||||
|
|
||||||
// Non-SGR CSI sequences with parameters:
|
// Non-SGR CSI sequences with parameters:
|
||||||
OnCursorUp func (distance int)
|
OnCursorUp func (distance int)
|
||||||
OnCursorDown func (distance int)
|
OnCursorDown func (distance int)
|
||||||
@ -65,14 +76,20 @@ type Decoder struct {
|
|||||||
OnBackgroundImage func (path string)
|
OnBackgroundImage func (path string)
|
||||||
|
|
||||||
state decodeState
|
state decodeState
|
||||||
csiParameter []byte
|
expectingST bool
|
||||||
csiIdentifier byte
|
gathered []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type decodeState int; const (
|
type decodeState int; const (
|
||||||
decodeStateText decodeState = iota
|
decodeStateText decodeState = iota
|
||||||
decodeStateAwaitC1
|
decodeStateAwaitC1
|
||||||
|
|
||||||
|
decodeStateGatherDCS
|
||||||
|
decodeStateGatherSOS
|
||||||
decodeStateGatherCSI
|
decodeStateGatherCSI
|
||||||
|
decodeStateGatherOSC
|
||||||
|
decodeStateGatherPM
|
||||||
|
decodeStateGatherAPC
|
||||||
)
|
)
|
||||||
|
|
||||||
func (decoder *Decoder) Write (buffer []byte) (wrote int, err error) {
|
func (decoder *Decoder) Write (buffer []byte) (wrote int, err error) {
|
||||||
@ -81,7 +98,7 @@ func (decoder *Decoder) Write (buffer []byte) (wrote int, err error) {
|
|||||||
for len(buffer) > 0 {
|
for len(buffer) > 0 {
|
||||||
switch decoder.state {
|
switch decoder.state {
|
||||||
case decodeStateText:
|
case decodeStateText:
|
||||||
if buffer[0] == byte(C0_Escape) {
|
if C0_Escape.Is(buffer[0]) {
|
||||||
// begin C1 control code
|
// begin C1 control code
|
||||||
decoder.state = decodeStateAwaitC1
|
decoder.state = decodeStateAwaitC1
|
||||||
buffer = buffer[1:]
|
buffer = buffer[1:]
|
||||||
@ -99,24 +116,28 @@ func (decoder *Decoder) Write (buffer []byte) (wrote int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case decodeStateAwaitC1:
|
case decodeStateAwaitC1:
|
||||||
// TODO: handle OSC sequences
|
|
||||||
// TODO: handle device control string
|
|
||||||
// TODO: handle privacy message
|
|
||||||
// TODO: handle application program command
|
|
||||||
|
|
||||||
if buffer[0] < 128 {
|
if buffer[0] < 128 {
|
||||||
// false alarm, this is just a C0 escape
|
// false alarm, this is just a C0 escape
|
||||||
if decoder.OnC0 != nil {
|
if decoder.OnC0 != nil {
|
||||||
decoder.OnC0(C0_Escape)
|
decoder.OnC0(C0_Escape)
|
||||||
}
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
} else if buffer[0] == byte(C1_ControlSequenceIntroducer) {
|
switch C1(buffer[0]) {
|
||||||
// abandon all hope ye who enter here
|
case C1_DeviceControlString:
|
||||||
|
decoder.state = decodeStateGatherDCS
|
||||||
|
case C1_StartOfString:
|
||||||
|
decoder.state = decodeStateGatherSOS
|
||||||
|
case C1_ControlSequenceIntroducer:
|
||||||
decoder.state = decodeStateGatherCSI
|
decoder.state = decodeStateGatherCSI
|
||||||
decoder.csiParameter = nil
|
case C1_OperatingSystemCommand:
|
||||||
decoder.csiIdentifier = 0
|
decoder.state = decodeStateGatherOSC
|
||||||
|
case C1_PrivacyMessage:
|
||||||
} else {
|
decoder.state = decodeStateGatherPM
|
||||||
|
case C1_ApplicationProgramCommand:
|
||||||
|
decoder.state = decodeStateGatherAPC
|
||||||
|
default:
|
||||||
// process C1 control code
|
// process C1 control code
|
||||||
if decoder.OnC1 != nil {
|
if decoder.OnC1 != nil {
|
||||||
decoder.OnC1(C1(buffer[0]))
|
decoder.OnC1(C1(buffer[0]))
|
||||||
@ -124,14 +145,37 @@ func (decoder *Decoder) Write (buffer []byte) (wrote int, err error) {
|
|||||||
}
|
}
|
||||||
buffer = buffer[1:]
|
buffer = buffer[1:]
|
||||||
|
|
||||||
|
case
|
||||||
|
decodeStateGatherDCS,
|
||||||
|
decodeStateGatherSOS,
|
||||||
|
decodeStateGatherOSC,
|
||||||
|
decodeStateGatherPM,
|
||||||
|
decodeStateGatherAPC:
|
||||||
|
|
||||||
|
if decoder.expectingST && C1_StringTerminator.Is(buffer[0]) {
|
||||||
|
// remove the trailing ESC
|
||||||
|
decoder.gathered = decoder.gathered [
|
||||||
|
:len(decoder.gathered) - 1]
|
||||||
|
|
||||||
|
if decoder.state == decodeStateGatherOSC {
|
||||||
|
// we understand some OSC codes so we
|
||||||
|
// handle them differently
|
||||||
|
decoder.processOSC()
|
||||||
|
} else {
|
||||||
|
// generic handler for uncommon stuff
|
||||||
|
decoder.processGeneric()
|
||||||
|
}
|
||||||
|
decoder.state = decodeStateText
|
||||||
|
}
|
||||||
|
if C0_Escape.Is(buffer[0]) {
|
||||||
|
decoder.expectingST = true
|
||||||
|
}
|
||||||
|
|
||||||
case decodeStateGatherCSI:
|
case decodeStateGatherCSI:
|
||||||
|
decoder.gather(buffer[0])
|
||||||
if buffer[0] < 0x30 || buffer[0] > 0x3F {
|
if buffer[0] < 0x30 || buffer[0] > 0x3F {
|
||||||
decoder.csiIdentifier = buffer[0]
|
|
||||||
decoder.processCSI()
|
decoder.processCSI()
|
||||||
} else {
|
decoder.state = decodeStateText
|
||||||
decoder.csiParameter = append (
|
|
||||||
decoder.csiParameter,
|
|
||||||
buffer[0])
|
|
||||||
}
|
}
|
||||||
buffer = buffer[1:]
|
buffer = buffer[1:]
|
||||||
}
|
}
|
||||||
@ -140,6 +184,10 @@ func (decoder *Decoder) Write (buffer []byte) (wrote int, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (decoder *Decoder) gather (character byte) {
|
||||||
|
decoder.gathered = append(decoder.gathered, character)
|
||||||
|
}
|
||||||
|
|
||||||
func (decoder *Decoder) processString (buffer []byte) []byte {
|
func (decoder *Decoder) processString (buffer []byte) []byte {
|
||||||
for index, char := range buffer {
|
for index, char := range buffer {
|
||||||
if C0_Escape.Is(char) {
|
if C0_Escape.Is(char) {
|
||||||
@ -152,6 +200,161 @@ func (decoder *Decoder) processString (buffer []byte) []byte {
|
|||||||
return buffer
|
return buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (decoder *Decoder) processCSI () {
|
func (decoder *Decoder) processGeneric () {
|
||||||
// TODO: analyze CSI parameter and id
|
parameter := string(decoder.gathered)
|
||||||
|
switch decoder.state {
|
||||||
|
case decodeStateGatherDCS:
|
||||||
|
if decoder.OnDCS != nil { decoder.OnDCS(parameter) }
|
||||||
|
case decodeStateGatherSOS:
|
||||||
|
if decoder.OnText != nil { decoder.OnText(parameter) }
|
||||||
|
case decodeStateGatherPM:
|
||||||
|
if decoder.OnPM != nil { decoder.OnPM(parameter) }
|
||||||
|
case decodeStateGatherAPC:
|
||||||
|
if decoder.OnAPC != nil { decoder.OnAPC(parameter) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (decoder *Decoder) processOSC () {
|
||||||
|
// TODO: analyze OSC
|
||||||
|
}
|
||||||
|
|
||||||
|
func (decoder *Decoder) processCSI () {
|
||||||
|
if len(decoder.gathered) < 2 { return }
|
||||||
|
parameters := ParameterInts(decoder.gathered)
|
||||||
|
|
||||||
|
var p0, p1, p2, p3 int
|
||||||
|
if len(parameters) > 0 { p0 = parameters[0] }
|
||||||
|
if len(parameters) > 1 { p1 = parameters[1] }
|
||||||
|
if len(parameters) > 2 { p2 = parameters[2] }
|
||||||
|
if len(parameters) > 3 { p3 = parameters[3] }
|
||||||
|
|
||||||
|
switch Last(decoder.gathered) {
|
||||||
|
case 'A': if decoder.OnCursorUp != nil { decoder.OnCursorUp(clampOne(p0)) }
|
||||||
|
case 'B': if decoder.OnCursorDown != nil { decoder.OnCursorDown(clampOne(p0)) }
|
||||||
|
case 'C': if decoder.OnCursorForward != nil { decoder.OnCursorForward(clampOne(p0)) }
|
||||||
|
case 'D': if decoder.OnCursorBack != nil { decoder.OnCursorBack(clampOne(p0)) }
|
||||||
|
case 'E': if decoder.OnCursorNextLine != nil { decoder.OnCursorNextLine(clampOne(p0)) }
|
||||||
|
case 'F': if decoder.OnCursorPreviousLine != nil { decoder.OnCursorPreviousLine(clampOne(p0)) }
|
||||||
|
case 'G': if decoder.OnCursorHorizontalAbsolute != nil { decoder.OnCursorHorizontalAbsolute(clampOne(p0)) }
|
||||||
|
case 'H', 'f': if decoder.OnCursorPosition != nil { decoder.OnCursorPosition(clampOne(p0), clampOne(p1)) }
|
||||||
|
|
||||||
|
case 'J': if decoder.OnEraseInDisplay != nil { decoder.OnEraseInDisplay(p0) }
|
||||||
|
case 'K': if decoder.OnEraseInLine != nil { decoder.OnEraseInLine(p0) }
|
||||||
|
case 'S': if decoder.OnScrollUp != nil { decoder.OnScrollUp(clampOne(p0)) }
|
||||||
|
case 'T': if decoder.OnScrollDown != nil { decoder.OnScrollDown(clampOne(p0)) }
|
||||||
|
|
||||||
|
case 'm':
|
||||||
|
p0 := SGR(p0)
|
||||||
|
switch {
|
||||||
|
case
|
||||||
|
p0 >= SGR_ForegroundColorBlack &&
|
||||||
|
p0 <= SGR_ForegroundColorWhite &&
|
||||||
|
decoder.OnForegroundColor != nil :
|
||||||
|
decoder.OnForegroundColor(Color(p0 - SGR_ForegroundColorBlack))
|
||||||
|
case
|
||||||
|
p0 >= SGR_ForegroundColorBrightBlack &&
|
||||||
|
p0 <= SGR_ForegroundColorBrightWhite &&
|
||||||
|
decoder.OnForegroundColor != nil :
|
||||||
|
decoder.OnForegroundColor(Color(p0 - SGR_ForegroundColorBrightBlack + 8))
|
||||||
|
|
||||||
|
case p0 == SGR_ForegroundColor:
|
||||||
|
switch p1 {
|
||||||
|
case 2:
|
||||||
|
if decoder.OnForegroundColor == nil { break }
|
||||||
|
decoder.OnForegroundColor(Color(p1))
|
||||||
|
case 5:
|
||||||
|
if decoder.OnForegroundColorTrue == nil { break }
|
||||||
|
decoder.OnForegroundColorTrue (color.RGBA {
|
||||||
|
R: uint8(p1),
|
||||||
|
G: uint8(p2),
|
||||||
|
B: uint8(p3),
|
||||||
|
A: 0xFF,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
case
|
||||||
|
p0 >= SGR_BackgroundColorBlack &&
|
||||||
|
p0 <= SGR_BackgroundColorWhite &&
|
||||||
|
decoder.OnBackgroundColor != nil :
|
||||||
|
decoder.OnBackgroundColor(Color(p0 - SGR_BackgroundColorBlack))
|
||||||
|
case
|
||||||
|
p0 >= SGR_BackgroundColorBrightBlack &&
|
||||||
|
p0 <= SGR_BackgroundColorBrightWhite &&
|
||||||
|
decoder.OnBackgroundColor != nil :
|
||||||
|
decoder.OnBackgroundColor(Color(p0 - SGR_BackgroundColorBrightBlack + 8))
|
||||||
|
|
||||||
|
case p0 == SGR_BackgroundColor:
|
||||||
|
switch p1 {
|
||||||
|
case 2:
|
||||||
|
if decoder.OnBackgroundColor == nil { break }
|
||||||
|
decoder.OnBackgroundColor(Color(p1))
|
||||||
|
case 5:
|
||||||
|
if decoder.OnBackgroundColorTrue == nil { break }
|
||||||
|
decoder.OnBackgroundColorTrue (color.RGBA {
|
||||||
|
R: uint8(p1),
|
||||||
|
G: uint8(p2),
|
||||||
|
B: uint8(p3),
|
||||||
|
A: 0xFF,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
case p0 == SGR_UnderlineColor:
|
||||||
|
switch p1 {
|
||||||
|
case 2:
|
||||||
|
if decoder.OnUnderlineColor == nil { break }
|
||||||
|
decoder.OnUnderlineColor(Color(p1))
|
||||||
|
case 5:
|
||||||
|
if decoder.OnUnderlineColorTrue == nil { break }
|
||||||
|
decoder.OnUnderlineColorTrue (color.RGBA {
|
||||||
|
R: uint8(p1),
|
||||||
|
G: uint8(p2),
|
||||||
|
B: uint8(p3),
|
||||||
|
A: 0xFF,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
default: if decoder.OnSGR != nil { decoder.OnSGR(SGR(p0)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
case 'n':
|
||||||
|
case 's':
|
||||||
|
case 'u':
|
||||||
|
case 'h':
|
||||||
|
case 'l':
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func clampOne (number int) int {
|
||||||
|
if number < 1 {
|
||||||
|
return 1
|
||||||
|
} else {
|
||||||
|
return number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last returns the last item of a slice.
|
||||||
|
func Last[T any] (source []T) T {
|
||||||
|
return source[len(source) - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParameterStrings separates a byte slice by semicolons into a list of strings.
|
||||||
|
func ParameterStrings (source []byte) (parameters []string) {
|
||||||
|
parameters = strings.Split(string(source), ";")
|
||||||
|
for index := range parameters {
|
||||||
|
parameters[index] = strings.TrimSpace(parameters[index])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParameterInts is like ParameterStrings, but returns integers instead of
|
||||||
|
// strings. If a parameter is empty or cannot be converted into an integer, that
|
||||||
|
// parameter will be zero.
|
||||||
|
func ParameterInts (source []byte) (parameters []int) {
|
||||||
|
stringParameters := ParameterStrings(source)
|
||||||
|
parameters = make([]int, len(stringParameters))
|
||||||
|
for index, parameter := range stringParameters {
|
||||||
|
parameters[index], _ = strconv.Atoi(parameter)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user