mirror of
https://github.com/BigJk/crt.git
synced 2026-02-06 10:47:25 +00:00
Prototype of other parser.
This commit is contained in:
parent
032bbda893
commit
7491df1479
335
ansi/parse.go
Normal file
335
ansi/parse.go
Normal file
@ -0,0 +1,335 @@
|
||||
package ansi
|
||||
|
||||
import (
|
||||
"github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
type SequenceType int
|
||||
type SGRType int
|
||||
|
||||
const (
|
||||
SequenceTypeNone SequenceType = iota
|
||||
SequenceTypeCursorUp
|
||||
SequenceTypeCursorDown
|
||||
SequenceTypeCursorForward
|
||||
SequenceTypeCursorBack
|
||||
SequenceTypeCursorNextLine
|
||||
SequenceTypeCursorPreviousLine
|
||||
SequenceTypeCursorHorizontal
|
||||
SequenceTypeCursorPosition
|
||||
SequenceTypeEraseDisplay
|
||||
SequenceTypeEraseLine
|
||||
SequenceTypeScrollUp
|
||||
SequenceTypeScrollDown
|
||||
SequenceTypeSaveCursorPosition
|
||||
SequenceTypeSGR
|
||||
SequenceTypeShowCursor
|
||||
SequenceTypeHideCursor
|
||||
SequenceTypeUnimplemented
|
||||
)
|
||||
|
||||
const (
|
||||
SGRTypeReset SGRType = 0
|
||||
SGRTypeBold SGRType = 1
|
||||
SGRTypeUnsetBold SGRType = 22
|
||||
SGRTypeItalic SGRType = 3
|
||||
SGRTypeUnsetItalic SGRType = 23
|
||||
SGRTypeUnderline SGRType = 4
|
||||
SGRTypeUnsetUnderline SGRType = 24
|
||||
SGRTypeFgColor SGRType = 38
|
||||
SGRTypeBgColor SGRType = 48
|
||||
SGRTypeFgDefaultColor SGRType = 39
|
||||
SGRTypeBgDefaultColor SGRType = 49
|
||||
)
|
||||
|
||||
const (
|
||||
// States for the internal state machine
|
||||
stateStart = 0
|
||||
stateParam = 1
|
||||
stateInter = 2
|
||||
)
|
||||
|
||||
var (
|
||||
csi = []rune(termenv.CSI)
|
||||
)
|
||||
|
||||
type Cursor struct {
|
||||
SeqType SequenceType
|
||||
|
||||
runes *[]rune
|
||||
start, end int
|
||||
}
|
||||
|
||||
func (c *Cursor) Len() int {
|
||||
diff := c.end - c.start
|
||||
if diff < 0 {
|
||||
return 0
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
func (c *Cursor) View() []rune {
|
||||
if c.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (*c.runes)[c.start:c.end]
|
||||
}
|
||||
|
||||
func (c *Cursor) Arg1() byte {
|
||||
if c.Len() == 0 {
|
||||
return 0
|
||||
}
|
||||
id, _ := parseNum(c.View())
|
||||
return byte(id)
|
||||
}
|
||||
|
||||
func (c *Cursor) Arg2() (byte, byte) {
|
||||
if c.Len() == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
a1, off := parseNum(c.View())
|
||||
a2, _ := parseNum(c.View()[off+1:])
|
||||
|
||||
return byte(a1), byte(a2)
|
||||
}
|
||||
|
||||
func (c *Cursor) VisitSGR(fn func(t SGRType, params []byte)) {
|
||||
if c.SeqType != SequenceTypeSGR {
|
||||
return
|
||||
}
|
||||
|
||||
// No parameters at all in ESC[m acts like a 0 reset code
|
||||
if c.Len() == 0 {
|
||||
fn(SGRTypeReset, []byte{})
|
||||
return
|
||||
}
|
||||
|
||||
params := make([]byte, 0, 3)
|
||||
|
||||
// We have parameters, so we parse them
|
||||
view := c.View()
|
||||
for i := 0; i < len(view); i++ {
|
||||
if id, off := parseNum(view[i:]); off > 0 {
|
||||
i += off - 1
|
||||
t := SGRType(id)
|
||||
|
||||
switch t {
|
||||
case SGRTypeFgColor, SGRTypeBgColor:
|
||||
if ok, trueColor, r, g, b, off := parseColor(view[i+2:]); ok {
|
||||
i += off + 2
|
||||
|
||||
if trueColor {
|
||||
params = append(params[:0], byte(r), byte(g), byte(b))
|
||||
} else {
|
||||
params = append(params[:0], byte(r))
|
||||
}
|
||||
|
||||
fn(t, params)
|
||||
}
|
||||
default:
|
||||
fn(t, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cursor) reset(start, end int) {
|
||||
c.SeqType = SequenceTypeNone
|
||||
c.start = start
|
||||
c.end = end
|
||||
}
|
||||
|
||||
func Parse(runes []rune, fn func(cursor *Cursor)) {
|
||||
state := stateStart
|
||||
cursor := &Cursor{SeqType: SequenceTypeNone, runes: &runes, start: 0, end: 0}
|
||||
|
||||
for i := 0; i < len(runes); i++ {
|
||||
switch state {
|
||||
// Start state where we don't know what the sequence is, or if it even is one.
|
||||
case stateStart:
|
||||
if isCSI(runes[i:]) {
|
||||
// If we have some runes left we visit them
|
||||
if cursor.Len() > 0 {
|
||||
cursor.end = i
|
||||
fn(cursor)
|
||||
}
|
||||
|
||||
cursor.start = i + 2
|
||||
cursor.end = cursor.start
|
||||
i += 1
|
||||
|
||||
// We found the CSI start, so we go to the param state
|
||||
state = stateParam
|
||||
} else {
|
||||
cursor.SeqType = SequenceTypeNone
|
||||
cursor.end = i + 1
|
||||
}
|
||||
// In the param state we look for the end of the param sequence.
|
||||
case stateParam:
|
||||
if isParam(runes[i]) {
|
||||
cursor.end = i
|
||||
} else if isInter(runes[i]) {
|
||||
state = stateInter
|
||||
i -= 1
|
||||
} else if isFinal(runes[i]) {
|
||||
state = stateStart
|
||||
cursor.end = i
|
||||
cursor.SeqType = finalToType(runes[i], cursor)
|
||||
fn(cursor)
|
||||
cursor.reset(i+1, i+1)
|
||||
} else {
|
||||
state = stateStart
|
||||
cursor.reset(i, i)
|
||||
}
|
||||
// In the inter state we look for the end of the intermediate sequence.
|
||||
case stateInter:
|
||||
if isInter(runes[i]) {
|
||||
cursor.end = i
|
||||
} else if isFinal(runes[i]) {
|
||||
state = stateStart
|
||||
cursor.end = i
|
||||
cursor.SeqType = finalToType(runes[i], cursor)
|
||||
fn(cursor)
|
||||
cursor.reset(i+1, i+1)
|
||||
} else {
|
||||
state = stateStart
|
||||
cursor.reset(i, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cursor.Len() > 0 {
|
||||
fn(cursor)
|
||||
}
|
||||
}
|
||||
|
||||
func parseColor(rune []rune) (bool, bool, byte, byte, byte, int) {
|
||||
// Check if it can be a color sequence
|
||||
if len(rune) < 2 || !(rune[0] == '5' || rune[0] == '2') || rune[1] != ';' {
|
||||
return false, false, 0, 0, 0, 2
|
||||
}
|
||||
|
||||
// 5;n case
|
||||
if rune[0] == '5' {
|
||||
// If n is not there the color id is 0
|
||||
if len(rune) == 2 {
|
||||
return true, false, 0, 0, 0, len(rune)
|
||||
}
|
||||
|
||||
if num, off := parseNum(rune[2:]); off > 0 {
|
||||
return true, false, byte(num), 0, 0, 2 + off
|
||||
}
|
||||
|
||||
return false, false, 0, 0, 0, 2
|
||||
}
|
||||
|
||||
rgb := make([]byte, 3)
|
||||
comp := 0
|
||||
stop := 0
|
||||
|
||||
// 2;r;g;b case
|
||||
for i := 2; i < len(rune); i++ {
|
||||
if v, off := parseNum(rune[i:]); off > 0 {
|
||||
i += off - 1
|
||||
rgb[comp] = byte(v)
|
||||
comp++
|
||||
}
|
||||
stop = i
|
||||
if comp == 3 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return true, true, rgb[0], rgb[1], rgb[2], stop
|
||||
}
|
||||
|
||||
func isCSI(rune []rune) bool {
|
||||
if len(rune) == 1 {
|
||||
return false
|
||||
}
|
||||
return rune[0] == csi[0] && rune[1] == csi[1]
|
||||
}
|
||||
|
||||
func isParam(r rune) bool {
|
||||
return r >= 0x30 && r <= 0x3F
|
||||
}
|
||||
|
||||
func isInter(r rune) bool {
|
||||
return r >= 0x20 && r <= 0x2F
|
||||
}
|
||||
|
||||
func isFinal(r rune) bool {
|
||||
return r >= 0x40 && r <= 0x7E
|
||||
}
|
||||
|
||||
func isNum(r rune) bool {
|
||||
return r >= 0x30 && r <= 0x39
|
||||
}
|
||||
|
||||
func parseNum(rune []rune) (int, int) {
|
||||
if len(rune) == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
var num int
|
||||
var digits int
|
||||
|
||||
for _, r := range rune {
|
||||
if isNum(r) {
|
||||
num = num*10 + int(r-'0')
|
||||
digits++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return num, digits
|
||||
}
|
||||
|
||||
func finalToType(lastRune rune, cursor *Cursor) SequenceType {
|
||||
switch lastRune {
|
||||
case 'A':
|
||||
return SequenceTypeCursorUp
|
||||
case 'B':
|
||||
return SequenceTypeCursorDown
|
||||
case 'C':
|
||||
return SequenceTypeCursorForward
|
||||
case 'D':
|
||||
return SequenceTypeCursorBack
|
||||
case 'E':
|
||||
return SequenceTypeCursorNextLine
|
||||
case 'F':
|
||||
return SequenceTypeCursorPreviousLine
|
||||
case 'G':
|
||||
return SequenceTypeCursorHorizontal
|
||||
case 'H':
|
||||
return SequenceTypeCursorPosition
|
||||
case 'J':
|
||||
return SequenceTypeEraseDisplay
|
||||
case 'K':
|
||||
return SequenceTypeEraseLine
|
||||
case 'S':
|
||||
return SequenceTypeScrollUp
|
||||
case 'T':
|
||||
return SequenceTypeScrollDown
|
||||
case 's':
|
||||
return SequenceTypeSaveCursorPosition
|
||||
case 'm':
|
||||
return SequenceTypeSGR
|
||||
case 'h':
|
||||
view := cursor.View()
|
||||
if cursor.Len() >= 3 && view[len(view)-3] == '?' && view[len(view)-2] == '2' && view[len(view)-1] == '5' {
|
||||
return SequenceTypeShowCursor
|
||||
}
|
||||
return SequenceTypeUnimplemented
|
||||
case 'l':
|
||||
view := cursor.View()
|
||||
if cursor.Len() >= 3 && view[len(view)-3] == '?' && view[len(view)-2] == '2' && view[len(view)-1] == '5' {
|
||||
return SequenceTypeHideCursor
|
||||
}
|
||||
return SequenceTypeUnimplemented
|
||||
}
|
||||
return SequenceTypeNone
|
||||
}
|
||||
436
ansi/parse_test.go
Normal file
436
ansi/parse_test.go
Normal file
@ -0,0 +1,436 @@
|
||||
package ansi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/muesli/termenv"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseNum(t *testing.T) {
|
||||
for i := 0; i < 1000; i++ {
|
||||
num := i
|
||||
if i > 255 {
|
||||
num = rand.Intn(1000)
|
||||
}
|
||||
s := fmt.Sprintf("%d;", num)
|
||||
pnum, off := parseNum([]rune(s))
|
||||
assert.Equal(t, num, pnum)
|
||||
assert.Equal(t, len(s)-1, off)
|
||||
}
|
||||
}
|
||||
|
||||
func randomString(n int) string {
|
||||
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
b := make([]rune, n)
|
||||
for i := range b {
|
||||
b[i] = letters[rand.Intn(len(letters))]
|
||||
}
|
||||
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func removeAnsiReset(s string) string {
|
||||
return strings.Replace(s, "\x1b[0m", "", 1)
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
lipgloss.SetColorProfile(termenv.TrueColor)
|
||||
|
||||
type SGRArgs struct {
|
||||
Bold bool
|
||||
Italic bool
|
||||
Underline bool
|
||||
Arg1 byte
|
||||
Arg2 byte
|
||||
Arg3 byte
|
||||
}
|
||||
|
||||
type Args struct {
|
||||
Arg1 byte
|
||||
Arg2 byte
|
||||
SGR SGRArgs
|
||||
}
|
||||
|
||||
type SequenceTester struct {
|
||||
Args int
|
||||
Gen func() (string, Args)
|
||||
Check func(cursor *Cursor, args Args)
|
||||
}
|
||||
|
||||
type SelectedTester struct {
|
||||
Name string
|
||||
Args Args
|
||||
}
|
||||
|
||||
sequenceTests := map[string]SequenceTester{
|
||||
"CursorUp": {
|
||||
Args: 1,
|
||||
Gen: func() (string, Args) {
|
||||
arg := byte(rand.Intn(255))
|
||||
return fmt.Sprintf(termenv.CSI+termenv.CursorUpSeq, arg), Args{
|
||||
Arg1: arg,
|
||||
Arg2: 0,
|
||||
}
|
||||
},
|
||||
Check: func(cursor *Cursor, args Args) {
|
||||
assert.Equal(t, SequenceTypeCursorUp, cursor.SeqType, "CursorUp")
|
||||
assert.Equal(t, args.Arg1, cursor.Arg1(), "CursorUp")
|
||||
},
|
||||
},
|
||||
"CursorDown": {
|
||||
Args: 1,
|
||||
Gen: func() (string, Args) {
|
||||
arg := byte(rand.Intn(255))
|
||||
return fmt.Sprintf(termenv.CSI+termenv.CursorDownSeq, arg), Args{
|
||||
Arg1: arg,
|
||||
Arg2: 0,
|
||||
}
|
||||
},
|
||||
Check: func(cursor *Cursor, args Args) {
|
||||
assert.Equal(t, SequenceTypeCursorDown, cursor.SeqType, "CursorDown")
|
||||
assert.Equal(t, args.Arg1, cursor.Arg1(), "CursorDown")
|
||||
},
|
||||
},
|
||||
"CursorForward": {
|
||||
Args: 1,
|
||||
Gen: func() (string, Args) {
|
||||
arg := byte(rand.Intn(255))
|
||||
return fmt.Sprintf(termenv.CSI+termenv.CursorForwardSeq, arg), Args{
|
||||
Arg1: arg,
|
||||
Arg2: 0,
|
||||
}
|
||||
},
|
||||
Check: func(cursor *Cursor, args Args) {
|
||||
assert.Equal(t, SequenceTypeCursorForward, cursor.SeqType, "CursorForward")
|
||||
assert.Equal(t, args.Arg1, cursor.Arg1(), "CursorForward")
|
||||
},
|
||||
},
|
||||
"CursorBack": {
|
||||
Args: 1,
|
||||
Gen: func() (string, Args) {
|
||||
arg := byte(rand.Intn(255))
|
||||
return fmt.Sprintf(termenv.CSI+termenv.CursorBackSeq, arg), Args{
|
||||
Arg1: arg,
|
||||
Arg2: 0,
|
||||
}
|
||||
},
|
||||
Check: func(cursor *Cursor, args Args) {
|
||||
assert.Equal(t, SequenceTypeCursorBack, cursor.SeqType, "CursorBack")
|
||||
assert.Equal(t, args.Arg1, cursor.Arg1(), "CursorBack")
|
||||
},
|
||||
},
|
||||
"CursorNextLine": {
|
||||
Args: 1,
|
||||
Gen: func() (string, Args) {
|
||||
arg := byte(rand.Intn(255))
|
||||
return fmt.Sprintf(termenv.CSI+termenv.CursorNextLineSeq, arg), Args{
|
||||
Arg1: arg,
|
||||
Arg2: 0,
|
||||
}
|
||||
},
|
||||
Check: func(cursor *Cursor, args Args) {
|
||||
assert.Equal(t, SequenceTypeCursorNextLine, cursor.SeqType, "CursorNextLine")
|
||||
assert.Equal(t, args.Arg1, cursor.Arg1(), "CursorNextLine")
|
||||
},
|
||||
},
|
||||
"CursorPreviousLine": {
|
||||
Args: 1,
|
||||
Gen: func() (string, Args) {
|
||||
arg := byte(rand.Intn(255))
|
||||
return fmt.Sprintf(termenv.CSI+termenv.CursorPreviousLineSeq, arg), Args{
|
||||
Arg1: arg,
|
||||
Arg2: 0,
|
||||
}
|
||||
},
|
||||
Check: func(cursor *Cursor, args Args) {
|
||||
assert.Equal(t, SequenceTypeCursorPreviousLine, cursor.SeqType, "CursorPreviousLine")
|
||||
assert.Equal(t, args.Arg1, cursor.Arg1(), "CursorPreviousLine")
|
||||
},
|
||||
},
|
||||
"CursorHorizontalAbsolute": {
|
||||
Args: 1,
|
||||
Gen: func() (string, Args) {
|
||||
arg := byte(rand.Intn(255))
|
||||
return fmt.Sprintf(termenv.CSI+termenv.CursorHorizontalSeq, arg), Args{
|
||||
Arg1: arg,
|
||||
Arg2: 0,
|
||||
}
|
||||
},
|
||||
Check: func(cursor *Cursor, args Args) {
|
||||
assert.Equal(t, SequenceTypeCursorHorizontal, cursor.SeqType, "CursorHorizontalAbsolute")
|
||||
assert.Equal(t, args.Arg1, cursor.Arg1(), "CursorHorizontalAbsolute")
|
||||
},
|
||||
},
|
||||
"CursorPosition": {
|
||||
Args: 2,
|
||||
Gen: func() (string, Args) {
|
||||
arg1 := byte(rand.Intn(255))
|
||||
arg2 := byte(rand.Intn(255))
|
||||
return fmt.Sprintf(termenv.CSI+termenv.CursorPositionSeq, arg1, arg2), Args{
|
||||
Arg1: arg1,
|
||||
Arg2: arg2,
|
||||
}
|
||||
},
|
||||
Check: func(cursor *Cursor, args Args) {
|
||||
assert.Equal(t, SequenceTypeCursorPosition, cursor.SeqType, "CursorPosition")
|
||||
arg1, arg2 := cursor.Arg2()
|
||||
assert.Equal(t, args.Arg1, arg1, "CursorPosition")
|
||||
assert.Equal(t, args.Arg2, arg2, "CursorPosition")
|
||||
},
|
||||
},
|
||||
"CursorShow": {
|
||||
Args: 0,
|
||||
Gen: func() (string, Args) {
|
||||
return fmt.Sprintf(termenv.CSI + termenv.ShowCursorSeq), Args{
|
||||
Arg1: 0,
|
||||
Arg2: 0,
|
||||
}
|
||||
},
|
||||
Check: func(cursor *Cursor, args Args) {
|
||||
assert.Equal(t, SequenceTypeShowCursor, cursor.SeqType, "CursorShow")
|
||||
},
|
||||
},
|
||||
"CursorHide": {
|
||||
Args: 0,
|
||||
Gen: func() (string, Args) {
|
||||
return fmt.Sprintf(termenv.CSI + termenv.HideCursorSeq), Args{
|
||||
Arg1: 0,
|
||||
Arg2: 0,
|
||||
}
|
||||
},
|
||||
Check: func(cursor *Cursor, args Args) {
|
||||
assert.Equal(t, SequenceTypeHideCursor, cursor.SeqType, "CursorHide")
|
||||
},
|
||||
},
|
||||
"EraseDisplay": {
|
||||
Args: 1,
|
||||
Gen: func() (string, Args) {
|
||||
arg := byte(rand.Intn(3))
|
||||
return fmt.Sprintf(termenv.CSI+termenv.EraseDisplaySeq, arg), Args{
|
||||
Arg1: arg,
|
||||
Arg2: 0,
|
||||
}
|
||||
},
|
||||
Check: func(cursor *Cursor, args Args) {
|
||||
assert.Equal(t, SequenceTypeEraseDisplay, cursor.SeqType, "EraseDisplay")
|
||||
assert.Equal(t, args.Arg1, cursor.Arg1(), "EraseDisplay")
|
||||
},
|
||||
},
|
||||
"EraseLine": {
|
||||
Args: 1,
|
||||
Gen: func() (string, Args) {
|
||||
arg := byte(rand.Intn(3))
|
||||
return fmt.Sprintf(termenv.CSI+termenv.EraseLineSeq, arg), Args{
|
||||
Arg1: arg,
|
||||
Arg2: 0,
|
||||
}
|
||||
},
|
||||
Check: func(cursor *Cursor, args Args) {
|
||||
assert.Equal(t, SequenceTypeEraseLine, cursor.SeqType, "EraseLine")
|
||||
assert.Equal(t, args.Arg1, cursor.Arg1(), "EraseLine")
|
||||
},
|
||||
},
|
||||
"ScrollUp": {
|
||||
Args: 1,
|
||||
Gen: func() (string, Args) {
|
||||
arg := byte(rand.Intn(255))
|
||||
return fmt.Sprintf(termenv.CSI+termenv.ScrollUpSeq, arg), Args{
|
||||
Arg1: arg,
|
||||
Arg2: 0,
|
||||
}
|
||||
},
|
||||
Check: func(cursor *Cursor, args Args) {
|
||||
assert.Equal(t, SequenceTypeScrollUp, cursor.SeqType, "ScrollUp")
|
||||
assert.Equal(t, args.Arg1, cursor.Arg1(), "ScrollUp")
|
||||
},
|
||||
},
|
||||
"ScrollDown": {
|
||||
Args: 1,
|
||||
Gen: func() (string, Args) {
|
||||
arg := byte(rand.Intn(255))
|
||||
return fmt.Sprintf(termenv.CSI+termenv.ScrollDownSeq, arg), Args{
|
||||
Arg1: arg,
|
||||
Arg2: 0,
|
||||
}
|
||||
},
|
||||
Check: func(cursor *Cursor, args Args) {
|
||||
assert.Equal(t, SequenceTypeScrollDown, cursor.SeqType, "ScrollDown")
|
||||
},
|
||||
},
|
||||
"SGR": {
|
||||
Args: 0,
|
||||
Gen: func() (string, Args) {
|
||||
bold := rand.Intn(2) == 1
|
||||
italic := rand.Intn(2) == 1
|
||||
underline := rand.Intn(2) == 1
|
||||
|
||||
if rand.Intn(2) == 1 {
|
||||
color := byte(rand.Intn(255))
|
||||
return removeAnsiReset(lipgloss.NewStyle().Bold(bold).Italic(italic).Underline(underline).Foreground(lipgloss.Color(fmt.Sprint(color))).Render("X")), Args{
|
||||
Arg1: 0,
|
||||
Arg2: 0,
|
||||
SGR: SGRArgs{
|
||||
Bold: bold,
|
||||
Italic: italic,
|
||||
Underline: underline,
|
||||
Arg1: color,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
r := byte(rand.Intn(255))
|
||||
g := byte(rand.Intn(255))
|
||||
b := byte(rand.Intn(255))
|
||||
|
||||
return removeAnsiReset(lipgloss.NewStyle().Bold(bold).Italic(italic).Underline(underline).Foreground(lipgloss.Color(fmt.Sprintf("#%02X%02X%02X", r, g, b))).Render("X")), Args{
|
||||
Arg1: 0,
|
||||
Arg2: 0,
|
||||
SGR: SGRArgs{
|
||||
Bold: bold,
|
||||
Italic: italic,
|
||||
Underline: underline,
|
||||
Arg1: r,
|
||||
Arg2: g,
|
||||
Arg3: b,
|
||||
},
|
||||
}
|
||||
},
|
||||
Check: func(cursor *Cursor, args Args) {
|
||||
if !assert.Equal(t, SequenceTypeSGR, cursor.SeqType, "SGR") {
|
||||
return
|
||||
}
|
||||
|
||||
cursor.VisitSGR(func(code SGRType, params []byte) {
|
||||
switch code {
|
||||
case SGRTypeBold:
|
||||
assert.Equal(t, true, args.SGR.Bold, "SGR")
|
||||
case SGRTypeItalic:
|
||||
assert.Equal(t, true, args.SGR.Italic, "SGR")
|
||||
case SGRTypeUnderline:
|
||||
assert.Equal(t, true, args.SGR.Underline, "SGR")
|
||||
case SGRTypeFgColor:
|
||||
if len(params) == 1 {
|
||||
assert.Equal(t, args.SGR.Arg1, params[0], "SGR")
|
||||
} else {
|
||||
// Termenv seems to change colors slightly
|
||||
assert.InDelta(t, args.SGR.Arg1, params[0], 2, "SGR")
|
||||
assert.InDelta(t, args.SGR.Arg2, params[1], 2, "SGR")
|
||||
assert.InDelta(t, args.SGR.Arg3, params[2], 2, "SGR")
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var possibleTests []string
|
||||
var selected []SelectedTester
|
||||
var testString string
|
||||
|
||||
// Try everything at least once
|
||||
for name, test := range sequenceTests {
|
||||
possibleTests = append(possibleTests, name)
|
||||
|
||||
str, expectedArgs := test.Gen()
|
||||
|
||||
testString += str
|
||||
selected = append(selected, SelectedTester{
|
||||
Name: name,
|
||||
Args: expectedArgs,
|
||||
})
|
||||
}
|
||||
|
||||
// Randomly generate 100 extra ones
|
||||
for i := 0; i < 1000; i++ {
|
||||
selectedName := possibleTests[rand.Intn(len(possibleTests))]
|
||||
test := sequenceTests[selectedName]
|
||||
str, expectedArgs := test.Gen()
|
||||
|
||||
testString += str
|
||||
selected = append(selected, SelectedTester{
|
||||
Name: selectedName,
|
||||
Args: expectedArgs,
|
||||
})
|
||||
|
||||
if rand.Intn(2) == 0 {
|
||||
testString += randomString(1 + rand.Intn(25))
|
||||
}
|
||||
}
|
||||
|
||||
var i int
|
||||
Parse([]rune(testString), func(cursor *Cursor) {
|
||||
// If its a non-sequence, skip it
|
||||
if cursor.SeqType == SequenceTypeNone {
|
||||
return
|
||||
}
|
||||
|
||||
sequenceTests[selected[i].Name].Check(cursor, selected[i].Args)
|
||||
i++
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseColor(t *testing.T) {
|
||||
testVal1 := []rune("2;255;0;255")
|
||||
ok, trueColor, r, g, b, off := parseColor(testVal1)
|
||||
assert.Equal(t, true, ok)
|
||||
assert.Equal(t, true, trueColor)
|
||||
assert.Equal(t, byte(255), r)
|
||||
assert.Equal(t, byte(0), g)
|
||||
assert.Equal(t, byte(255), b)
|
||||
assert.Equal(t, len(testVal1), off)
|
||||
|
||||
testVal2 := []rune("5;23")
|
||||
ok, trueColor, r, g, b, off = parseColor(testVal2)
|
||||
assert.Equal(t, true, ok)
|
||||
assert.Equal(t, false, trueColor)
|
||||
assert.Equal(t, byte(23), r)
|
||||
assert.Equal(t, byte(0), g)
|
||||
assert.Equal(t, byte(0), b)
|
||||
assert.Equal(t, len(testVal2), off)
|
||||
|
||||
testVal3 := []rune("5;")
|
||||
ok, trueColor, r, g, b, off = parseColor(testVal3)
|
||||
assert.Equal(t, true, ok)
|
||||
assert.Equal(t, false, trueColor)
|
||||
assert.Equal(t, byte(0), r)
|
||||
assert.Equal(t, byte(0), g)
|
||||
assert.Equal(t, byte(0), b)
|
||||
assert.Equal(t, len(testVal3), off)
|
||||
|
||||
testVal4 := []rune("2;1;;1")
|
||||
ok, trueColor, r, g, b, off = parseColor(testVal4)
|
||||
assert.Equal(t, true, ok)
|
||||
assert.Equal(t, true, trueColor)
|
||||
assert.Equal(t, byte(1), r)
|
||||
assert.Equal(t, byte(0), g)
|
||||
assert.Equal(t, byte(1), b)
|
||||
assert.Equal(t, len(testVal4), off)
|
||||
}
|
||||
|
||||
func BenchmarkParse(b *testing.B) {
|
||||
var testString string
|
||||
|
||||
testString += "AbcDefG"
|
||||
testString += fmt.Sprintf(termenv.CSI+termenv.EraseDisplaySeq, 20)
|
||||
testString += "HELLO WORLD"
|
||||
testString += fmt.Sprintf(termenv.CSI+termenv.CursorPositionSeq, 1, 29)
|
||||
testString += fmt.Sprintf(termenv.CSI+termenv.CursorPositionSeq, 1, 2)
|
||||
testString += "HELLO WORLD"
|
||||
testString += termenv.CSI + termenv.ShowCursorSeq
|
||||
testString += fmt.Sprintf(termenv.CSI+termenv.CursorPositionSeq, 1, 29)
|
||||
testString += fmt.Sprintf(termenv.CSI+termenv.CursorBackSeq, 5)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
lip := lipgloss.NewRenderer(buf, termenv.WithProfile(termenv.TrueColor))
|
||||
testString += lip.NewStyle().Bold(true).Foreground(lipgloss.Color("#ff00ff")).Render("Hello World") + "asdasdasdasdasd" + lip.NewStyle().Bold(true).Foreground(lipgloss.Color("10")).Render("Hello World") + lip.NewStyle().Italic(true).Background(lipgloss.Color("#ff00ff")).Render("Hello World")
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
Parse([]rune(testString), func(cursor *Cursor) {})
|
||||
}
|
||||
}
|
||||
124
examples/chat/main.go
Normal file
124
examples/chat/main.go
Normal file
@ -0,0 +1,124 @@
|
||||
package main
|
||||
|
||||
// A simple program demonstrating the text area component from the Bubbles
|
||||
// component library.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/BigJk/crt"
|
||||
bubbleadapter "github.com/BigJk/crt/bubbletea"
|
||||
"image/color"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/textarea"
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
const (
|
||||
Width = 1000
|
||||
Height = 600
|
||||
)
|
||||
|
||||
func main() {
|
||||
fonts, err := crt.LoadFaces("./fonts/IosevkaTermNerdFontMono-Regular.ttf", "./fonts/IosevkaTermNerdFontMono-Bold.ttf", "./fonts/IosevkaTermNerdFontMono-Italic.ttf", 72.0, 16.0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
win, _, err := bubbleadapter.Window(Width, Height, fonts, initialModel(), color.Black, tea.WithAltScreen())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := win.Run("Simple"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
errMsg error
|
||||
)
|
||||
|
||||
type model struct {
|
||||
viewport viewport.Model
|
||||
messages []string
|
||||
textarea textarea.Model
|
||||
senderStyle lipgloss.Style
|
||||
err error
|
||||
}
|
||||
|
||||
func initialModel() model {
|
||||
ta := textarea.New()
|
||||
ta.Placeholder = "Send a message..."
|
||||
ta.Focus()
|
||||
|
||||
ta.Prompt = "┃ "
|
||||
ta.CharLimit = 280
|
||||
|
||||
ta.SetWidth(30)
|
||||
ta.SetHeight(3)
|
||||
|
||||
// Remove cursor line styling
|
||||
ta.FocusedStyle.CursorLine = lipgloss.NewStyle()
|
||||
|
||||
ta.ShowLineNumbers = false
|
||||
|
||||
vp := viewport.New(30, 5)
|
||||
vp.SetContent(`Welcome to the chat room!
|
||||
Type a message and press Enter to send.`)
|
||||
|
||||
ta.KeyMap.InsertNewline.SetEnabled(false)
|
||||
|
||||
return model{
|
||||
textarea: ta,
|
||||
messages: []string{},
|
||||
viewport: vp,
|
||||
senderStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("5")),
|
||||
err: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (m model) Init() tea.Cmd {
|
||||
return textarea.Blink
|
||||
}
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var (
|
||||
tiCmd tea.Cmd
|
||||
vpCmd tea.Cmd
|
||||
)
|
||||
|
||||
m.textarea, tiCmd = m.textarea.Update(msg)
|
||||
m.viewport, vpCmd = m.viewport.Update(msg)
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.Type {
|
||||
case tea.KeyCtrlC, tea.KeyEsc:
|
||||
fmt.Println(m.textarea.Value())
|
||||
return m, tea.Quit
|
||||
case tea.KeyEnter:
|
||||
m.messages = append(m.messages, m.senderStyle.Render("You: ")+m.textarea.Value())
|
||||
m.viewport.SetContent(strings.Join(m.messages, "\n"))
|
||||
m.textarea.Reset()
|
||||
m.viewport.GotoBottom()
|
||||
}
|
||||
|
||||
// We handle errors just like any other message
|
||||
case errMsg:
|
||||
m.err = msg
|
||||
return m, nil
|
||||
}
|
||||
|
||||
return m, tea.Batch(tiCmd, vpCmd)
|
||||
}
|
||||
|
||||
func (m model) View() string {
|
||||
return fmt.Sprintf(
|
||||
"%s\n\n%s",
|
||||
m.viewport.View(),
|
||||
m.textarea.View(),
|
||||
) + "\n\n"
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user