Skip to content

Commit

Permalink
chore: merge pull request #267 from charmbracelet/vt
Browse files Browse the repository at this point in the history
Add vt
  • Loading branch information
aymanbagabas authored Nov 20, 2024
2 parents 2bbbfa1 + 3c64c01 commit 1c56a76
Show file tree
Hide file tree
Showing 80 changed files with 7,391 additions and 1,993 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.ttf filter=lfs diff=lfs merge=lfs -text
97 changes: 94 additions & 3 deletions ansi/background.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,73 @@
package ansi

import (
"fmt"
"image/color"
)

// Colorizer is a [color.Color] interface that can be formatted as a string.
type Colorizer interface {
color.Color
fmt.Stringer
}

// HexColorizer is a [color.Color] that can be formatted as a hex string.
type HexColorizer struct{ color.Color }

var _ Colorizer = HexColorizer{}

// String returns the color as a hex string. If the color is nil, an empty
// string is returned.
func (h HexColorizer) String() string {
if h.Color == nil {
return ""
}
r, g, b, _ := h.RGBA()
// Get the lower 8 bits
r &= 0xff
g &= 0xff
b &= 0xff
return fmt.Sprintf("#%02x%02x%02x", uint8(r), uint8(g), uint8(b)) //nolint:gosec
}

// XRGBColorizer is a [color.Color] that can be formatted as an XParseColor
// rgb: string.
//
// See: https://linux.die.net/man/3/xparsecolor
type XRGBColorizer struct{ color.Color }

var _ Colorizer = XRGBColorizer{}

// String returns the color as an XParseColor rgb: string. If the color is nil,
// an empty string is returned.
func (x XRGBColorizer) String() string {
if x.Color == nil {
return ""
}
r, g, b, _ := x.RGBA()
// Get the lower 8 bits
return fmt.Sprintf("rgb:%04x/%04x/%04x", r, g, b)
}

// XRGBAColorizer is a [color.Color] that can be formatted as an XParseColor
// rgba: string.
//
// See: https://linux.die.net/man/3/xparsecolor
type XRGBAColorizer struct{ color.Color }

var _ Colorizer = XRGBAColorizer{}

// String returns the color as an XParseColor rgba: string. If the color is nil,
// an empty string is returned.
func (x XRGBAColorizer) String() string {
if x.Color == nil {
return ""
}
r, g, b, a := x.RGBA()
// Get the lower 8 bits
return fmt.Sprintf("rgba:%04x/%04x/%04x/%04x", r, g, b, a)
}

// SetForegroundColor returns a sequence that sets the default terminal
// foreground color.
//
Expand All @@ -14,7 +78,16 @@ import (
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
func SetForegroundColor(c color.Color) string {
return "\x1b]10;" + colorToHexString(c) + "\x07"
var s string
switch c := c.(type) {
case Colorizer:
s = c.String()
case fmt.Stringer:
s = c.String()
default:
s = HexColorizer{c}.String()
}
return "\x1b]10;" + s + "\x07"
}

// RequestForegroundColor is a sequence that requests the current default
Expand All @@ -39,7 +112,16 @@ const ResetForegroundColor = "\x1b]110\x07"
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
func SetBackgroundColor(c color.Color) string {
return "\x1b]11;" + colorToHexString(c) + "\x07"
var s string
switch c := c.(type) {
case Colorizer:
s = c.String()
case fmt.Stringer:
s = c.String()
default:
s = HexColorizer{c}.String()
}
return "\x1b]11;" + s + "\x07"
}

// RequestBackgroundColor is a sequence that requests the current default
Expand All @@ -63,7 +145,16 @@ const ResetBackgroundColor = "\x1b]111\x07"
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
func SetCursorColor(c color.Color) string {
return "\x1b]12;" + colorToHexString(c) + "\x07"
var s string
switch c := c.(type) {
case Colorizer:
s = c.String()
case fmt.Stringer:
s = c.String()
default:
s = HexColorizer{c}.String()
}
return "\x1b]12;" + s + "\x07"
}

// RequestCursorColor is a sequence that requests the current terminal cursor
Expand Down
16 changes: 16 additions & 0 deletions ansi/background_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,19 @@ func TestStringImplementations(t *testing.T) {
cursorColor)
}
}

func TestColorizer(t *testing.T) {
hex := ansi.HexColorizer{ansi.BrightBlack}
xrgb := ansi.XRGBColorizer{ansi.ExtendedColor(235)}
xrgba := ansi.XRGBAColorizer{ansi.TrueColor(0x00ff00)}

if seq := ansi.SetForegroundColor(hex); seq != "\x1b]10;#808080\x07" {
t.Errorf("Unexpected sequence for HexColorizer: got %q", seq)
}
if seq := ansi.SetForegroundColor(xrgb); seq != "\x1b]10;rgb:2626/2626/2626\x07" {
t.Errorf("Unexpected sequence for XRGBColorizer: got %q", seq)
}
if seq := ansi.SetForegroundColor(xrgba); seq != "\x1b]10;rgba:0000/ffff/0000/ffff\x07" {
t.Errorf("Unexpected sequence for XRGBAColorizer: got %q", seq)
}
}
7 changes: 7 additions & 0 deletions ansi/c0.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,11 @@ const (
RS = 0x1E
// US is the unit separator character (Caret: ^_).
US = 0x1F

// LS0 is the locking shift 0 character.
// This is an alias for [SI].
LS0 = SI
// LS1 is the locking shift 1 character.
// This is an alias for [SO].
LS1 = SO
)
55 changes: 55 additions & 0 deletions ansi/charset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package ansi

// SelectCharacterSet sets the G-set character designator to the specified
// character set.
//
// ESC Ps Pd
//
// Where Ps is the G-set character designator, and Pd is the identifier.
// For 94-character sets, the designator can be one of:
// - ( G0
// - ) G1
// - * G2
// - + G3
//
// For 96-character sets, the designator can be one of:
// - - G1
// - . G2
// - / G3
//
// Some common 94-character sets are:
// - 0 DEC Special Drawing Set
// - A United Kingdom (UK)
// - B United States (USASCII)
//
// Examples:
//
// ESC ( B Select character set G0 = United States (USASCII)
// ESC ( 0 Select character set G0 = Special Character and Line Drawing Set
// ESC ) 0 Select character set G1 = Special Character and Line Drawing Set
// ESC * A Select character set G2 = United Kingdom (UK)
//
// See: https://vt100.net/docs/vt510-rm/SCS.html
func SelectCharacterSet(gset byte, charset byte) string {
return "\x1b" + string(gset) + string(charset)
}

// SCS is an alias for SelectCharacterSet.
func SCS(gset byte, charset byte) string {
return SelectCharacterSet(gset, charset)
}

// Locking Shift 1 Right (LS1R) shifts G1 into GR character set.
const LS1R = "\x1b~"

// Locking Shift 2 (LS2) shifts G2 into GL character set.
const LS2 = "\x1bn"

// Locking Shift 2 Right (LS2R) shifts G2 into GR character set.
const LS2R = "\x1b}"

// Locking Shift 3 (LS3) shifts G3 into GL character set.
const LS3 = "\x1bo"

// Locking Shift 3 Right (LS3R) shifts G3 into GR character set.
const LS3R = "\x1b|"
75 changes: 27 additions & 48 deletions ansi/csi.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package ansi
import (
"bytes"
"strconv"

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

// CsiSequence represents a control sequence introducer (CSI) sequence.
Expand All @@ -23,7 +21,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 +33,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,51 +60,22 @@ 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)
return s.Cmd.Command()
}

// 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,
// Param is a helper that returns the parameter at the given index and falls
// back to the default value if the parameter is missing. If the index is out
// of bounds, it returns the default value and false.
func (s CsiSequence) Param(i, def int) (int, bool) {
if i < 0 || i >= len(s.Params) {
return def, false
}
return s.Params[i].Param(def), true
}

// String returns a string representation of the sequence.
Expand All @@ -114,23 +91,25 @@ func (s CsiSequence) buffer() *bytes.Buffer {
if m := s.Marker(); m != 0 {
b.WriteByte(byte(m))
}
s.Range(func(i, param int, hasMore bool) bool {
for i, p := range s.Params {
param := p.Param(-1)
if param >= 0 {
b.WriteString(strconv.Itoa(param))
}
if i < len(s.Params)-1 {
if hasMore {
if p.HasMore() {
b.WriteByte(':')
} else {
b.WriteByte(';')
}
}
return true
})
}
if i := s.Intermediate(); i != 0 {
b.WriteByte(byte(i))
}
b.WriteByte(byte(s.Command()))
if cmd := s.Command(); cmd != 0 {
b.WriteByte(byte(cmd))
}
return &b
}

Expand Down
Loading

0 comments on commit 1c56a76

Please sign in to comment.