Skip to content

Commit

Permalink
refactor!: use Parameter and Command types in Parser
Browse files Browse the repository at this point in the history
This unexports Parser fields and instead export the underlying data as methods.
  • Loading branch information
aymanbagabas committed Nov 14, 2024
1 parent 2519b5b commit 3ea22e2
Show file tree
Hide file tree
Showing 28 changed files with 453 additions and 813 deletions.
103 changes: 13 additions & 90 deletions ansi/csi.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
package ansi

import (
"bytes"
"strconv"

"github.com/charmbracelet/x/ansi/parser"
)

// CsiSequence represents a control sequence introducer (CSI) sequence.
//
// The sequence starts with a CSI sequence, CSI (0x9B) in a 8-bit environment
Expand All @@ -23,7 +16,7 @@ type CsiSequence struct {
// This is a slice of integers, where each integer is a 32-bit integer
// containing the parameter value in the lower 31 bits and a flag in the
// most significant bit indicating whether there are more sub-parameters.
Params []int
Params []Parameter

// Cmd contains the raw command of the sequence.
// The command is a 32-bit integer containing the CSI command byte in the
Expand All @@ -35,17 +28,25 @@ type CsiSequence struct {
// Is represented as:
//
// 'u' | '?' << 8
Cmd int
Cmd Command
}

var _ Sequence = CsiSequence{}

// Clone returns a deep copy of the CSI sequence.
func (s CsiSequence) Clone() Sequence {
return CsiSequence{
Params: append([]Parameter(nil), s.Params...),
Cmd: s.Cmd,
}
}

// Marker returns the marker byte of the CSI sequence.
// This is always gonna be one of the following '<' '=' '>' '?' and in the
// range of 0x3C-0x3F.
// Zero is returned if the sequence does not have a marker.
func (s CsiSequence) Marker() int {
return parser.Marker(s.Cmd)
return s.Cmd.Marker()
}

// Intermediate returns the intermediate byte of the CSI sequence.
Expand All @@ -54,88 +55,10 @@ func (s CsiSequence) Marker() int {
// ',', '-', '.', '/'.
// Zero is returned if the sequence does not have an intermediate byte.
func (s CsiSequence) Intermediate() int {
return parser.Intermediate(s.Cmd)
return s.Cmd.Intermediate()
}

// Command returns the command byte of the CSI sequence.
func (s CsiSequence) Command() int {
return parser.Command(s.Cmd)
}

// Param returns the parameter at the given index.
// It returns -1 if the parameter does not exist.
func (s CsiSequence) Param(i int) int {
return parser.Param(s.Params, i)
}

// HasMore returns true if the parameter has more sub-parameters.
func (s CsiSequence) HasMore(i int) bool {
return parser.HasMore(s.Params, i)
}

// Subparams returns the sub-parameters of the given parameter.
// It returns nil if the parameter does not exist.
func (s CsiSequence) Subparams(i int) []int {
return parser.Subparams(s.Params, i)
}

// Len returns the number of parameters in the sequence.
// This will return the number of parameters in the sequence, excluding any
// sub-parameters.
func (s CsiSequence) Len() int {
return parser.Len(s.Params)
}

// Range iterates over the parameters of the sequence and calls the given
// function for each parameter.
// The function should return false to stop the iteration.
func (s CsiSequence) Range(fn func(i int, param int, hasMore bool) bool) {
parser.Range(s.Params, fn)
}

// Clone returns a copy of the CSI sequence.
func (s CsiSequence) Clone() Sequence {
return CsiSequence{
Params: append([]int(nil), s.Params...),
Cmd: s.Cmd,
}
}

// String returns a string representation of the sequence.
// The string will always be in the 7-bit format i.e (ESC [ P..P I..I F).
func (s CsiSequence) String() string {
return s.buffer().String()
}

// buffer returns a buffer containing the sequence.
func (s CsiSequence) buffer() *bytes.Buffer {
var b bytes.Buffer
b.WriteString("\x1b[")
if m := s.Marker(); m != 0 {
b.WriteByte(byte(m))
}
s.Range(func(i, param int, hasMore bool) bool {
if param >= 0 {
b.WriteString(strconv.Itoa(param))
}
if i < len(s.Params)-1 {
if hasMore {
b.WriteByte(':')
} else {
b.WriteByte(';')
}
}
return true
})
if i := s.Intermediate(); i != 0 {
b.WriteByte(byte(i))
}
b.WriteByte(byte(s.Command()))
return &b
}

// Bytes returns the byte representation of the sequence.
// The bytes will always be in the 7-bit format i.e (ESC [ P..P I..I F).
func (s CsiSequence) Bytes() []byte {
return s.buffer().Bytes()
return s.Cmd.Command()
}
107 changes: 23 additions & 84 deletions ansi/csi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,32 +104,32 @@ func TestCsiSequence_Param(t *testing.T) {
i int
want int
}{
{
name: "no param",
s: CsiSequence{},
i: 0,
want: -1,
},
// {
// name: "no param",
// s: CsiSequence{},
// i: 0,
// want: -1,
// },
{
name: "param",
s: CsiSequence{
Params: []int{1, 2, 3},
Params: []Parameter{1, 2, 3},
},
i: 1,
want: 2,
},
{
name: "missing param",
s: CsiSequence{
Params: []int{1, parser.MissingParam, 3},
Params: []Parameter{1, parser.MissingParam, 3},
},
i: 1,
want: -1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.s.Param(tt.i); got != tt.want {
if got := tt.s.Params[tt.i]; got.Param(-1) != tt.want {
t.Errorf("CsiSequence.Param() = %v, want %v", got, tt.want)
}
})
Expand All @@ -143,32 +143,32 @@ func TestCsiSequence_HasMore(t *testing.T) {
i int
want bool
}{
{
name: "no param",
s: CsiSequence{},
i: 0,
want: false,
},
// {
// name: "no param",
// s: CsiSequence{},
// i: 0,
// want: false,
// },
{
name: "has more",
s: CsiSequence{
Params: []int{1 | parser.HasMoreFlag, 2, 3},
Params: []Parameter{1 | parser.HasMoreFlag, 2, 3},
},
i: 0,
want: true,
},
{
name: "no more",
s: CsiSequence{
Params: []int{1, 2, 3},
Params: []Parameter{1, 2, 3},
},
i: 0,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.s.HasMore(tt.i); got != tt.want {
if got := tt.s.Params[tt.i].HasMore(); got != tt.want {
t.Errorf("CsiSequence.HasMore() = %v, want %v", got, tt.want)
}
})
Expand All @@ -189,91 +189,30 @@ func TestCsiSequence_Len(t *testing.T) {
{
name: "len",
s: CsiSequence{
Params: []int{1, 2, 3},
Params: []Parameter{1, 2, 3},
},
want: 3,
},
{
name: "len with missing param",
s: CsiSequence{
Params: []int{1, parser.MissingParam, 3},
Params: []Parameter{1, parser.MissingParam, 3},
},
want: 3,
},
{
name: "len with more flag",
s: CsiSequence{
Params: []int{1 | parser.HasMoreFlag, 2, 3},
Params: []Parameter{1 | parser.HasMoreFlag, 2, 3},
},
want: 2,
want: 3,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.s.Len(); got != tt.want {
if got := len(tt.s.Params); got != tt.want {
t.Errorf("CsiSequence.Len() = %v, want %v", got, tt.want)
}
})
}
}

func TestCsiSequence_String(t *testing.T) {
tests := []struct {
name string
s CsiSequence
want string
}{
{
name: "empty",
s: CsiSequence{Cmd: 'R'},
want: "\x1b[R",
},
{
name: "with data",
s: CsiSequence{
Cmd: 'A',
Params: []int{1, 2, 3},
},
want: "\x1b[1;2;3A",
},
{
name: "with more flag",
s: CsiSequence{
Cmd: 'A',
Params: []int{1 | parser.HasMoreFlag, 2, 3},
},
want: "\x1b[1:2;3A",
},
{
name: "with intermediate",
s: CsiSequence{
Cmd: 'A' | '$'<<parser.IntermedShift,
Params: []int{1, 2, 3},
},
want: "\x1b[1;2;3$A",
},
{
name: "with marker",
s: CsiSequence{
Cmd: 'A' | '?'<<parser.MarkerShift,
Params: []int{1, 2, 3},
},
want: "\x1b[?1;2;3A",
},
{
name: "with marker intermediate and more flag",
s: CsiSequence{
Cmd: 'A' | '?'<<parser.MarkerShift | '$'<<parser.IntermedShift,
Params: []int{1, 2 | parser.HasMoreFlag, 3},
},
want: "\x1b[?1;2:3$A",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.s.String(); got != tt.want {
t.Errorf("CsiSequence.String() = %q, want %q", got, tt.want)
}
})
}
}
Loading

0 comments on commit 3ea22e2

Please sign in to comment.