Skip to content
Snippets Groups Projects
Verified Commit 260e43dd authored by Volker Schukai's avatar Volker Schukai :alien:
Browse files

fix: exclude files

parent fbefdfc5
No related branches found
No related tags found
No related merge requests found
Showing
with 2281 additions and 188 deletions
package ansi
import (
"bytes"
)
// Params parses and returns a list of control sequence parameters.
//
// Parameters are positive integers separated by semicolons. Empty parameters
// default to zero. Parameters can have sub-parameters separated by colons.
//
// Any non-parameter bytes are ignored. This includes bytes that are not in the
// range of 0x30-0x3B.
//
// See ECMA-48 § 5.4.1.
func Params(p []byte) [][]uint {
if len(p) == 0 {
return [][]uint{}
}
// Filter out non-parameter bytes i.e. non 0x30-0x3B.
p = bytes.TrimFunc(p, func(r rune) bool {
return r < 0x30 || r > 0x3B
})
parts := bytes.Split(p, []byte{';'})
params := make([][]uint, len(parts))
for i, part := range parts {
sparts := bytes.Split(part, []byte{':'})
params[i] = make([]uint, len(sparts))
for j, spart := range sparts {
params[i][j] = bytesToUint16(spart)
}
}
return params
}
func bytesToUint16(b []byte) uint {
var n uint
for _, c := range b {
n = n*10 + uint(c-'0')
}
return n
}
package ansi
import (
"unicode/utf8"
"github.com/charmbracelet/x/ansi/parser"
)
// ParserDispatcher is a function that dispatches a sequence.
type ParserDispatcher func(Sequence)
// Parser represents a DEC ANSI compatible sequence parser.
//
// It uses a state machine to parse ANSI escape sequences and control
// characters. The parser is designed to be used with a terminal emulator or
// similar application that needs to parse ANSI escape sequences and control
// characters.
// See package [parser] for more information.
//
//go:generate go run ./gen.go
type Parser struct {
// Params contains the raw parameters of the sequence.
// These parameters used when constructing CSI and DCS sequences.
Params []int
// Data contains the raw data of the sequence.
// These data used when constructing OSC, DCS, SOS, PM, and APC sequences.
Data []byte
// DataLen keeps track of the length of the data buffer.
// If DataLen is -1, the data buffer is unlimited and will grow as needed.
// Otherwise, DataLen is limited by the size of the Data buffer.
DataLen int
// ParamsLen keeps track of the number of parameters.
// This is limited by the size of the Params buffer.
ParamsLen int
// Cmd contains the raw command along with the private marker and
// intermediate bytes of the sequence.
// The first lower byte contains the command byte, the next byte contains
// the private marker, and the next byte contains the intermediate byte.
Cmd int
// RuneLen keeps track of the number of bytes collected for a UTF-8 rune.
RuneLen int
// RuneBuf contains the bytes collected for a UTF-8 rune.
RuneBuf [utf8.MaxRune]byte
// State is the current state of the parser.
State byte
}
// NewParser returns a new parser with the given sizes allocated.
// If dataSize is zero, the underlying data buffer will be unlimited and will
// grow as needed.
func NewParser(paramsSize, dataSize int) *Parser {
s := &Parser{
Params: make([]int, paramsSize),
Data: make([]byte, dataSize),
}
if dataSize <= 0 {
s.DataLen = -1
}
return s
}
// Reset resets the parser to its initial state.
func (p *Parser) Reset() {
p.clear()
p.State = parser.GroundState
}
// clear clears the parser parameters and command.
func (p *Parser) clear() {
if len(p.Params) > 0 {
p.Params[0] = parser.MissingParam
}
p.ParamsLen = 0
p.Cmd = 0
p.RuneLen = 0
}
// StateName returns the name of the current state.
func (p *Parser) StateName() string {
return parser.StateNames[p.State]
}
// Parse parses the given dispatcher and byte buffer.
func (p *Parser) Parse(dispatcher ParserDispatcher, b []byte) {
for i := 0; i < len(b); i++ {
p.Advance(dispatcher, b[i], i < len(b)-1)
}
}
// Advance advances the parser with the given dispatcher and byte.
func (p *Parser) Advance(dispatcher ParserDispatcher, b byte, more bool) parser.Action {
switch p.State {
case parser.Utf8State:
// We handle UTF-8 here.
return p.advanceUtf8(dispatcher, b)
default:
return p.advance(dispatcher, b, more)
}
}
func (p *Parser) collectRune(b byte) {
if p.RuneLen < utf8.UTFMax {
p.RuneBuf[p.RuneLen] = b
p.RuneLen++
}
}
func (p *Parser) advanceUtf8(dispatcher ParserDispatcher, b byte) parser.Action {
// Collect UTF-8 rune bytes.
p.collectRune(b)
rw := utf8ByteLen(p.RuneBuf[0])
if rw == -1 {
// We panic here because the first byte comes from the state machine,
// if this panics, it means there is a bug in the state machine!
panic("invalid rune") // unreachable
}
if p.RuneLen < rw {
return parser.NoneAction
}
// We have enough bytes to decode the rune
bts := p.RuneBuf[:rw]
r, _ := utf8.DecodeRune(bts)
if dispatcher != nil {
dispatcher(Rune(r))
}
p.State = parser.GroundState
p.RuneLen = 0
return parser.NoneAction
}
func (p *Parser) advance(d ParserDispatcher, b byte, more bool) parser.Action {
state, action := parser.Table.Transition(p.State, b)
// We need to clear the parser state if the state changes from EscapeState.
// This is because when we enter the EscapeState, we don't get a chance to
// clear the parser state. For example, when a sequence terminates with a
// ST (\x1b\\ or \x9c), we dispatch the current sequence and transition to
// EscapeState. However, the parser state is not cleared in this case and
// we need to clear it here before dispatching the esc sequence.
if p.State != state {
switch p.State {
case parser.EscapeState:
p.performAction(d, parser.ClearAction, b)
}
if action == parser.PutAction &&
p.State == parser.DcsEntryState && state == parser.DcsStringState {
// XXX: This is a special case where we need to start collecting
// non-string parameterized data i.e. doesn't follow the ECMA-48 §
// 5.4.1 string parameters format.
p.performAction(d, parser.StartAction, 0)
}
}
// Handle special cases
switch {
case b == ESC && p.State == parser.EscapeState:
// Two ESCs in a row
p.performAction(d, parser.ExecuteAction, b)
if !more {
// Two ESCs at the end of the buffer
p.performAction(d, parser.ExecuteAction, b)
}
case b == ESC && !more:
// Last byte is an ESC
p.performAction(d, parser.ExecuteAction, b)
case p.State == parser.EscapeState && b == 'P' && !more:
// ESC P (DCS) at the end of the buffer
p.performAction(d, parser.DispatchAction, b)
case p.State == parser.EscapeState && b == 'X' && !more:
// ESC X (SOS) at the end of the buffer
p.performAction(d, parser.DispatchAction, b)
case p.State == parser.EscapeState && b == '[' && !more:
// ESC [ (CSI) at the end of the buffer
p.performAction(d, parser.DispatchAction, b)
case p.State == parser.EscapeState && b == ']' && !more:
// ESC ] (OSC) at the end of the buffer
p.performAction(d, parser.DispatchAction, b)
case p.State == parser.EscapeState && b == '^' && !more:
// ESC ^ (PM) at the end of the buffer
p.performAction(d, parser.DispatchAction, b)
case p.State == parser.EscapeState && b == '_' && !more:
// ESC _ (APC) at the end of the buffer
p.performAction(d, parser.DispatchAction, b)
default:
p.performAction(d, action, b)
}
p.State = state
return action
}
func (p *Parser) performAction(dispatcher ParserDispatcher, action parser.Action, b byte) {
switch action {
case parser.IgnoreAction:
break
case parser.ClearAction:
p.clear()
case parser.PrintAction:
if utf8ByteLen(b) > 1 {
p.collectRune(b)
} else if dispatcher != nil {
dispatcher(Rune(b))
}
case parser.ExecuteAction:
if dispatcher != nil {
dispatcher(ControlCode(b))
}
case parser.MarkerAction:
// Collect private marker
// we only store the last marker
p.Cmd &^= 0xff << parser.MarkerShift
p.Cmd |= int(b) << parser.MarkerShift
case parser.CollectAction:
// Collect intermediate bytes
// we only store the last intermediate byte
p.Cmd &^= 0xff << parser.IntermedShift
p.Cmd |= int(b) << parser.IntermedShift
case parser.ParamAction:
// Collect parameters
if p.ParamsLen >= len(p.Params) {
break
}
if b >= '0' && b <= '9' {
if p.Params[p.ParamsLen] == parser.MissingParam {
p.Params[p.ParamsLen] = 0
}
p.Params[p.ParamsLen] *= 10
p.Params[p.ParamsLen] += int(b - '0')
}
if b == ':' {
p.Params[p.ParamsLen] |= parser.HasMoreFlag
}
if b == ';' || b == ':' {
p.ParamsLen++
if p.ParamsLen < len(p.Params) {
p.Params[p.ParamsLen] = parser.MissingParam
}
}
case parser.StartAction:
if p.DataLen < 0 {
p.Data = make([]byte, 0)
} else {
p.DataLen = 0
}
if p.State >= parser.DcsEntryState && p.State <= parser.DcsStringState {
// Collect the command byte for DCS
p.Cmd |= int(b)
} else {
p.Cmd = parser.MissingCommand
}
case parser.PutAction:
switch p.State {
case parser.OscStringState:
if b == ';' && p.Cmd == parser.MissingCommand {
// Try to parse the command
datalen := len(p.Data)
if p.DataLen >= 0 {
datalen = p.DataLen
}
for i := 0; i < datalen; i++ {
d := p.Data[i]
if d < '0' || d > '9' {
break
}
if p.Cmd == parser.MissingCommand {
p.Cmd = 0
}
p.Cmd *= 10
p.Cmd += int(d - '0')
}
}
}
if p.DataLen < 0 {
p.Data = append(p.Data, b)
} else {
if p.DataLen < len(p.Data) {
p.Data[p.DataLen] = b
p.DataLen++
}
}
case parser.DispatchAction:
// Increment the last parameter
if p.ParamsLen > 0 && p.ParamsLen < len(p.Params)-1 ||
p.ParamsLen == 0 && len(p.Params) > 0 && p.Params[0] != parser.MissingParam {
p.ParamsLen++
}
if dispatcher == nil {
break
}
var seq Sequence
data := p.Data
if p.DataLen >= 0 {
data = data[:p.DataLen]
}
switch p.State {
case parser.CsiEntryState, parser.CsiParamState, parser.CsiIntermediateState:
p.Cmd |= int(b)
seq = CsiSequence{Cmd: p.Cmd, Params: p.Params[:p.ParamsLen]}
case parser.EscapeState, parser.EscapeIntermediateState:
p.Cmd |= int(b)
seq = EscSequence(p.Cmd)
case parser.DcsEntryState, parser.DcsParamState, parser.DcsIntermediateState, parser.DcsStringState:
seq = DcsSequence{Cmd: p.Cmd, Params: p.Params[:p.ParamsLen], Data: data}
case parser.OscStringState:
seq = OscSequence{Cmd: p.Cmd, Data: data}
case parser.SosStringState:
seq = SosSequence{Data: data}
case parser.PmStringState:
seq = PmSequence{Data: data}
case parser.ApcStringState:
seq = ApcSequence{Data: data}
}
dispatcher(seq)
}
}
func utf8ByteLen(b byte) int {
if b <= 0b0111_1111 { // 0x00-0x7F
return 1
} else if b >= 0b1100_0000 && b <= 0b1101_1111 { // 0xC0-0xDF
return 2
} else if b >= 0b1110_0000 && b <= 0b1110_1111 { // 0xE0-0xEF
return 3
} else if b >= 0b1111_0000 && b <= 0b1111_0111 { // 0xF0-0xF7
return 4
}
return -1
}
package parser
// Action is a DEC ANSI parser action.
type Action = byte
// These are the actions that the parser can take.
const (
NoneAction Action = iota
ClearAction
CollectAction
MarkerAction
DispatchAction
ExecuteAction
StartAction // Start of a data string
PutAction // Put into the data string
ParamAction
PrintAction
IgnoreAction = NoneAction
)
// nolint: unused
var ActionNames = []string{
"NoneAction",
"ClearAction",
"CollectAction",
"MarkerAction",
"DispatchAction",
"ExecuteAction",
"StartAction",
"PutAction",
"ParamAction",
"PrintAction",
}
// State is a DEC ANSI parser state.
type State = byte
// These are the states that the parser can be in.
const (
GroundState State = iota
CsiEntryState
CsiIntermediateState
CsiParamState
DcsEntryState
DcsIntermediateState
DcsParamState
DcsStringState
EscapeState
EscapeIntermediateState
OscStringState
SosStringState
PmStringState
ApcStringState
// Utf8State is not part of the DEC ANSI standard. It is used to handle
// UTF-8 sequences.
Utf8State
)
// nolint: unused
var StateNames = []string{
"GroundState",
"CsiEntryState",
"CsiIntermediateState",
"CsiParamState",
"DcsEntryState",
"DcsIntermediateState",
"DcsParamState",
"DcsStringState",
"EscapeState",
"EscapeIntermediateState",
"OscStringState",
"SosStringState",
"PmStringState",
"ApcStringState",
"Utf8State",
}
package parser
import "math"
// Shift and masks for sequence parameters and intermediates.
const (
MarkerShift = 8
IntermedShift = 16
CommandMask = 0xff
HasMoreFlag = math.MinInt32
ParamMask = ^HasMoreFlag
MissingParam = ParamMask
MissingCommand = MissingParam
MaxParam = math.MaxUint16 // the maximum value a parameter can have
)
const (
// MaxParamsSize is the maximum number of parameters a sequence can have.
MaxParamsSize = 32
// DefaultParamValue is the default value used for missing parameters.
DefaultParamValue = 0
)
// Marker returns the marker byte of the 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 Marker(cmd int) int {
return (cmd >> MarkerShift) & CommandMask
}
// Intermediate returns the intermediate byte of the sequence.
// An intermediate byte is in the range of 0x20-0x2F. This includes these
// characters from ' ', '!', '"', '#', '$', '%', '&', ”', '(', ')', '*', '+',
// ',', '-', '.', '/'.
// Zero is returned if the sequence does not have an intermediate byte.
func Intermediate(cmd int) int {
return (cmd >> IntermedShift) & CommandMask
}
// Command returns the command byte of the CSI sequence.
func Command(cmd int) int {
return cmd & CommandMask
}
// Param returns the parameter at the given index.
// It returns -1 if the parameter does not exist.
func Param(params []int, i int) int {
if len(params) == 0 || i < 0 || i >= len(params) {
return -1
}
p := params[i] & ParamMask
if p == MissingParam {
return -1
}
return p
}
// HasMore returns true if the parameter has more sub-parameters.
func HasMore(params []int, i int) bool {
if len(params) == 0 || i >= len(params) {
return false
}
return params[i]&HasMoreFlag != 0
}
// Subparams returns the sub-parameters of the given parameter.
// It returns nil if the parameter does not exist.
func Subparams(params []int, i int) []int {
if len(params) == 0 || i < 0 || i >= len(params) {
return nil
}
// Count the number of parameters before the given parameter index.
var count int
var j int
for j = 0; j < len(params); j++ {
if count == i {
break
}
if !HasMore(params, j) {
count++
}
}
if count > i || j >= len(params) {
return nil
}
var subs []int
for ; j < len(params); j++ {
if !HasMore(params, j) {
break
}
p := Param(params, j)
if p == -1 {
p = DefaultParamValue
}
subs = append(subs, p)
}
p := Param(params, j)
if p == -1 {
p = DefaultParamValue
}
return append(subs, p)
}
// Len returns the number of parameters in the sequence.
// This will return the number of parameters in the sequence, excluding any
// sub-parameters.
func Len(params []int) int {
var n int
for i := 0; i < len(params); i++ {
if !HasMore(params, i) {
n++
}
}
return n
}
// 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 Range(params []int, fn func(i int, param int, hasMore bool) bool) {
for i := 0; i < len(params); i++ {
if !fn(i, Param(params, i), HasMore(params, i)) {
break
}
}
}
package parser
// Table values are generated like this:
//
// index: currentState << IndexStateShift | charCode
// value: action << TransitionActionShift | nextState
const (
TransitionActionShift = 4
TransitionStateMask = 15
IndexStateShift = 8
// DefaultTableSize is the default size of the transition table.
DefaultTableSize = 4096
)
// Table is a DEC ANSI transition table.
var Table = GenerateTransitionTable()
// TransitionTable is a DEC ANSI transition table.
// https://vt100.net/emu/dec_ansi_parser
type TransitionTable []byte
// NewTransitionTable returns a new DEC ANSI transition table.
func NewTransitionTable(size int) TransitionTable {
if size <= 0 {
size = DefaultTableSize
}
return TransitionTable(make([]byte, size))
}
// SetDefault sets default transition.
func (t TransitionTable) SetDefault(action Action, state State) {
for i := 0; i < len(t); i++ {
t[i] = action<<TransitionActionShift | state
}
}
// AddOne adds a transition.
func (t TransitionTable) AddOne(code byte, state State, action Action, next State) {
idx := int(state)<<IndexStateShift | int(code)
value := action<<TransitionActionShift | next
t[idx] = value
}
// AddMany adds many transitions.
func (t TransitionTable) AddMany(codes []byte, state State, action Action, next State) {
for _, code := range codes {
t.AddOne(code, state, action, next)
}
}
// AddRange adds a range of transitions.
func (t TransitionTable) AddRange(start, end byte, state State, action Action, next State) {
for i := int(start); i <= int(end); i++ {
t.AddOne(byte(i), state, action, next)
}
}
// Transition returns the next state and action for the given state and byte.
func (t TransitionTable) Transition(state State, code byte) (State, Action) {
index := int(state)<<IndexStateShift | int(code)
value := t[index]
return value & TransitionStateMask, value >> TransitionActionShift
}
// byte range macro
func r(start, end byte) []byte {
var a []byte
for i := int(start); i <= int(end); i++ {
a = append(a, byte(i))
}
return a
}
// GenerateTransitionTable generates a DEC ANSI transition table compatible
// with the VT500-series of terminals. This implementation includes a few
// modifications that include:
// - A new Utf8State is introduced to handle UTF8 sequences.
// - Osc and Dcs data accept UTF8 sequences by extending the printable range
// to 0xFF and 0xFE respectively.
// - We don't ignore 0x3A (':') when building Csi and Dcs parameters and
// instead use it to denote sub-parameters.
// - Support dispatching SosPmApc sequences.
func GenerateTransitionTable() TransitionTable {
table := NewTransitionTable(DefaultTableSize)
table.SetDefault(NoneAction, GroundState)
// Anywhere
for _, state := range r(GroundState, Utf8State) {
// Anywhere -> Ground
table.AddMany([]byte{0x18, 0x1a, 0x99, 0x9a}, state, ExecuteAction, GroundState)
table.AddRange(0x80, 0x8F, state, ExecuteAction, GroundState)
table.AddRange(0x90, 0x97, state, ExecuteAction, GroundState)
table.AddOne(0x9C, state, IgnoreAction, GroundState)
// Anywhere -> Escape
table.AddOne(0x1B, state, ClearAction, EscapeState)
// Anywhere -> SosStringState
table.AddOne(0x98, state, StartAction, SosStringState)
// Anywhere -> PmStringState
table.AddOne(0x9E, state, StartAction, PmStringState)
// Anywhere -> ApcStringState
table.AddOne(0x9F, state, StartAction, ApcStringState)
// Anywhere -> CsiEntry
table.AddOne(0x9B, state, ClearAction, CsiEntryState)
// Anywhere -> DcsEntry
table.AddOne(0x90, state, ClearAction, DcsEntryState)
// Anywhere -> OscString
table.AddOne(0x9D, state, StartAction, OscStringState)
// Anywhere -> Utf8
table.AddRange(0xC2, 0xDF, state, PrintAction, Utf8State) // UTF8 2 byte sequence
table.AddRange(0xE0, 0xEF, state, PrintAction, Utf8State) // UTF8 3 byte sequence
table.AddRange(0xF0, 0xF4, state, PrintAction, Utf8State) // UTF8 4 byte sequence
}
// Ground
table.AddRange(0x00, 0x17, GroundState, ExecuteAction, GroundState)
table.AddOne(0x19, GroundState, ExecuteAction, GroundState)
table.AddRange(0x1C, 0x1F, GroundState, ExecuteAction, GroundState)
table.AddRange(0x20, 0x7F, GroundState, PrintAction, GroundState)
// EscapeIntermediate
table.AddRange(0x00, 0x17, EscapeIntermediateState, ExecuteAction, EscapeIntermediateState)
table.AddOne(0x19, EscapeIntermediateState, ExecuteAction, EscapeIntermediateState)
table.AddRange(0x1C, 0x1F, EscapeIntermediateState, ExecuteAction, EscapeIntermediateState)
table.AddRange(0x20, 0x2F, EscapeIntermediateState, CollectAction, EscapeIntermediateState)
table.AddOne(0x7F, EscapeIntermediateState, IgnoreAction, EscapeIntermediateState)
// EscapeIntermediate -> Ground
table.AddRange(0x30, 0x7E, EscapeIntermediateState, DispatchAction, GroundState)
// Escape
table.AddRange(0x00, 0x17, EscapeState, ExecuteAction, EscapeState)
table.AddOne(0x19, EscapeState, ExecuteAction, EscapeState)
table.AddRange(0x1C, 0x1F, EscapeState, ExecuteAction, EscapeState)
table.AddOne(0x7F, EscapeState, IgnoreAction, EscapeState)
// Escape -> Ground
table.AddRange(0x30, 0x4F, EscapeState, DispatchAction, GroundState)
table.AddRange(0x51, 0x57, EscapeState, DispatchAction, GroundState)
table.AddOne(0x59, EscapeState, DispatchAction, GroundState)
table.AddOne(0x5A, EscapeState, DispatchAction, GroundState)
table.AddOne(0x5C, EscapeState, DispatchAction, GroundState)
table.AddRange(0x60, 0x7E, EscapeState, DispatchAction, GroundState)
// Escape -> Escape_intermediate
table.AddRange(0x20, 0x2F, EscapeState, CollectAction, EscapeIntermediateState)
// Escape -> Sos_pm_apc_string
table.AddOne('X', EscapeState, StartAction, SosStringState) // SOS
table.AddOne('^', EscapeState, StartAction, PmStringState) // PM
table.AddOne('_', EscapeState, StartAction, ApcStringState) // APC
// Escape -> Dcs_entry
table.AddOne('P', EscapeState, ClearAction, DcsEntryState)
// Escape -> Csi_entry
table.AddOne('[', EscapeState, ClearAction, CsiEntryState)
// Escape -> Osc_string
table.AddOne(']', EscapeState, StartAction, OscStringState)
// Sos_pm_apc_string
for _, state := range r(SosStringState, ApcStringState) {
table.AddRange(0x00, 0x17, state, PutAction, state)
table.AddOne(0x19, state, PutAction, state)
table.AddRange(0x1C, 0x1F, state, PutAction, state)
table.AddRange(0x20, 0x7F, state, PutAction, state)
// ESC, ST, CAN, and SUB terminate the sequence
table.AddOne(0x1B, state, DispatchAction, EscapeState)
table.AddOne(0x9C, state, DispatchAction, GroundState)
table.AddMany([]byte{0x18, 0x1A}, state, IgnoreAction, GroundState)
}
// Dcs_entry
table.AddRange(0x00, 0x07, DcsEntryState, IgnoreAction, DcsEntryState)
table.AddRange(0x0E, 0x17, DcsEntryState, IgnoreAction, DcsEntryState)
table.AddOne(0x19, DcsEntryState, IgnoreAction, DcsEntryState)
table.AddRange(0x1C, 0x1F, DcsEntryState, IgnoreAction, DcsEntryState)
table.AddOne(0x7F, DcsEntryState, IgnoreAction, DcsEntryState)
// Dcs_entry -> Dcs_intermediate
table.AddRange(0x20, 0x2F, DcsEntryState, CollectAction, DcsIntermediateState)
// Dcs_entry -> Dcs_param
table.AddRange(0x30, 0x3B, DcsEntryState, ParamAction, DcsParamState)
table.AddRange(0x3C, 0x3F, DcsEntryState, MarkerAction, DcsParamState)
// Dcs_entry -> Dcs_passthrough
table.AddRange(0x08, 0x0D, DcsEntryState, PutAction, DcsStringState) // Follows ECMA-48 § 8.3.27
// XXX: allows passing ESC (not a ECMA-48 standard) this to allow for
// passthrough of ANSI sequences like in Screen or Tmux passthrough mode.
table.AddOne(0x1B, DcsEntryState, PutAction, DcsStringState)
table.AddRange(0x40, 0x7E, DcsEntryState, StartAction, DcsStringState)
// Dcs_intermediate
table.AddRange(0x00, 0x17, DcsIntermediateState, IgnoreAction, DcsIntermediateState)
table.AddOne(0x19, DcsIntermediateState, IgnoreAction, DcsIntermediateState)
table.AddRange(0x1C, 0x1F, DcsIntermediateState, IgnoreAction, DcsIntermediateState)
table.AddRange(0x20, 0x2F, DcsIntermediateState, CollectAction, DcsIntermediateState)
table.AddOne(0x7F, DcsIntermediateState, IgnoreAction, DcsIntermediateState)
// Dcs_intermediate -> Dcs_passthrough
table.AddRange(0x30, 0x3F, DcsIntermediateState, StartAction, DcsStringState)
table.AddRange(0x40, 0x7E, DcsIntermediateState, StartAction, DcsStringState)
// Dcs_param
table.AddRange(0x00, 0x17, DcsParamState, IgnoreAction, DcsParamState)
table.AddOne(0x19, DcsParamState, IgnoreAction, DcsParamState)
table.AddRange(0x1C, 0x1F, DcsParamState, IgnoreAction, DcsParamState)
table.AddRange(0x30, 0x3B, DcsParamState, ParamAction, DcsParamState)
table.AddOne(0x7F, DcsParamState, IgnoreAction, DcsParamState)
table.AddRange(0x3C, 0x3F, DcsParamState, IgnoreAction, DcsParamState)
// Dcs_param -> Dcs_intermediate
table.AddRange(0x20, 0x2F, DcsParamState, CollectAction, DcsIntermediateState)
// Dcs_param -> Dcs_passthrough
table.AddRange(0x40, 0x7E, DcsParamState, StartAction, DcsStringState)
// Dcs_passthrough
table.AddRange(0x00, 0x17, DcsStringState, PutAction, DcsStringState)
table.AddOne(0x19, DcsStringState, PutAction, DcsStringState)
table.AddRange(0x1C, 0x1F, DcsStringState, PutAction, DcsStringState)
table.AddRange(0x20, 0x7E, DcsStringState, PutAction, DcsStringState)
table.AddOne(0x7F, DcsStringState, IgnoreAction, DcsStringState)
table.AddRange(0x80, 0xFF, DcsStringState, PutAction, DcsStringState) // Allow Utf8 characters by extending the printable range from 0x7F to 0xFF
// ST, CAN, SUB, and ESC terminate the sequence
table.AddOne(0x1B, DcsStringState, DispatchAction, EscapeState)
table.AddOne(0x9C, DcsStringState, DispatchAction, GroundState)
table.AddMany([]byte{0x18, 0x1A}, DcsStringState, IgnoreAction, GroundState)
// Csi_param
table.AddRange(0x00, 0x17, CsiParamState, ExecuteAction, CsiParamState)
table.AddOne(0x19, CsiParamState, ExecuteAction, CsiParamState)
table.AddRange(0x1C, 0x1F, CsiParamState, ExecuteAction, CsiParamState)
table.AddRange(0x30, 0x3B, CsiParamState, ParamAction, CsiParamState)
table.AddOne(0x7F, CsiParamState, IgnoreAction, CsiParamState)
table.AddRange(0x3C, 0x3F, CsiParamState, IgnoreAction, CsiParamState)
// Csi_param -> Ground
table.AddRange(0x40, 0x7E, CsiParamState, DispatchAction, GroundState)
// Csi_param -> Csi_intermediate
table.AddRange(0x20, 0x2F, CsiParamState, CollectAction, CsiIntermediateState)
// Csi_intermediate
table.AddRange(0x00, 0x17, CsiIntermediateState, ExecuteAction, CsiIntermediateState)
table.AddOne(0x19, CsiIntermediateState, ExecuteAction, CsiIntermediateState)
table.AddRange(0x1C, 0x1F, CsiIntermediateState, ExecuteAction, CsiIntermediateState)
table.AddRange(0x20, 0x2F, CsiIntermediateState, CollectAction, CsiIntermediateState)
table.AddOne(0x7F, CsiIntermediateState, IgnoreAction, CsiIntermediateState)
// Csi_intermediate -> Ground
table.AddRange(0x40, 0x7E, CsiIntermediateState, DispatchAction, GroundState)
// Csi_intermediate -> Csi_ignore
table.AddRange(0x30, 0x3F, CsiIntermediateState, IgnoreAction, GroundState)
// Csi_entry
table.AddRange(0x00, 0x17, CsiEntryState, ExecuteAction, CsiEntryState)
table.AddOne(0x19, CsiEntryState, ExecuteAction, CsiEntryState)
table.AddRange(0x1C, 0x1F, CsiEntryState, ExecuteAction, CsiEntryState)
table.AddOne(0x7F, CsiEntryState, IgnoreAction, CsiEntryState)
// Csi_entry -> Ground
table.AddRange(0x40, 0x7E, CsiEntryState, DispatchAction, GroundState)
// Csi_entry -> Csi_intermediate
table.AddRange(0x20, 0x2F, CsiEntryState, CollectAction, CsiIntermediateState)
// Csi_entry -> Csi_param
table.AddRange(0x30, 0x3B, CsiEntryState, ParamAction, CsiParamState)
table.AddRange(0x3C, 0x3F, CsiEntryState, MarkerAction, CsiParamState)
// Osc_string
table.AddRange(0x00, 0x06, OscStringState, IgnoreAction, OscStringState)
table.AddRange(0x08, 0x17, OscStringState, IgnoreAction, OscStringState)
table.AddOne(0x19, OscStringState, IgnoreAction, OscStringState)
table.AddRange(0x1C, 0x1F, OscStringState, IgnoreAction, OscStringState)
table.AddRange(0x20, 0xFF, OscStringState, PutAction, OscStringState) // Allow Utf8 characters by extending the printable range from 0x7F to 0xFF
// ST, CAN, SUB, ESC, and BEL terminate the sequence
table.AddOne(0x1B, OscStringState, DispatchAction, EscapeState)
table.AddOne(0x07, OscStringState, DispatchAction, GroundState)
table.AddOne(0x9C, OscStringState, DispatchAction, GroundState)
table.AddMany([]byte{0x18, 0x1A}, OscStringState, IgnoreAction, GroundState)
return table
}
package ansi
import (
"bytes"
)
// ScreenPassthrough wraps the given ANSI sequence in a DCS passthrough
// sequence to be sent to the outer terminal. This is used to send raw escape
// sequences to the outer terminal when running inside GNU Screen.
//
// DCS <data> ST
//
// Note: Screen limits the length of string sequences to 768 bytes (since 2014).
// Use zero to indicate no limit, otherwise, this will chunk the returned
// string into limit sized chunks.
//
// See: https://www.gnu.org/software/screen/manual/screen.html#String-Escapes
// See: https://git.savannah.gnu.org/cgit/screen.git/tree/src/screen.h?id=c184c6ec27683ff1a860c45be5cf520d896fd2ef#n44
func ScreenPassthrough(seq string, limit int) string {
var b bytes.Buffer
b.WriteString("\x1bP")
if limit > 0 {
for i := 0; i < len(seq); i += limit {
end := i + limit
if end > len(seq) {
end = len(seq)
}
b.WriteString(seq[i:end])
if end < len(seq) {
b.WriteString("\x1b\\\x1bP")
}
}
} else {
b.WriteString(seq)
}
b.WriteString("\x1b\\")
return b.String()
}
// TmuxPassthrough wraps the given ANSI sequence in a special DCS passthrough
// sequence to be sent to the outer terminal. This is used to send raw escape
// sequences to the outer terminal when running inside Tmux.
//
// DCS tmux ; <escaped-data> ST
//
// Where <escaped-data> is the given sequence in which all occurrences of ESC
// (0x1b) are doubled i.e. replaced with ESC ESC (0x1b 0x1b).
//
// Note: this needs the `allow-passthrough` option to be set to `on`.
//
// See: https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it
func TmuxPassthrough(seq string) string {
var b bytes.Buffer
b.WriteString("\x1bPtmux;")
for i := 0; i < len(seq); i++ {
if seq[i] == ESC {
b.WriteByte(ESC)
}
b.WriteByte(seq[i])
}
b.WriteString("\x1b\\")
return b.String()
}
package ansi
import "strconv"
// EraseDisplay (ED) clears the screen or parts of the screen. Possible values:
//
// 0: Clear from cursor to end of screen.
// 1: Clear from cursor to beginning of the screen.
// 2: Clear entire screen (and moves cursor to upper left on DOS).
// 3: Clear entire screen and delete all lines saved in the scrollback buffer.
//
// CSI <n> J
//
// See: https://vt100.net/docs/vt510-rm/ED.html
func EraseDisplay(n int) string {
if n < 0 {
n = 0
}
return "\x1b[" + strconv.Itoa(n) + "J"
}
// EraseDisplay constants.
// These are the possible values for the EraseDisplay function.
const (
EraseDisplayRight = "\x1b[0J"
EraseDisplayLeft = "\x1b[1J"
EraseEntireDisplay = "\x1b[2J"
)
// EraseLine (EL) clears the current line or parts of the line. Possible values:
//
// 0: Clear from cursor to end of line.
// 1: Clear from cursor to beginning of the line.
// 2: Clear entire line.
//
// The cursor position is not affected.
//
// CSI <n> K
//
// See: https://vt100.net/docs/vt510-rm/EL.html
func EraseLine(n int) string {
if n < 0 {
n = 0
}
return "\x1b[" + strconv.Itoa(n) + "K"
}
// EraseLine constants.
// These are the possible values for the EraseLine function.
const (
EraseLineRight = "\x1b[0K"
EraseLineLeft = "\x1b[1K"
EraseEntireLine = "\x1b[2K"
)
// ScrollUp (SU) scrolls the screen up n lines. New lines are added at the
// bottom of the screen.
//
// CSI <n> S
//
// See: https://vt100.net/docs/vt510-rm/SU.html
func ScrollUp(n int) string {
var s string
if n > 1 {
s = strconv.Itoa(n)
}
return "\x1b[" + s + "S"
}
// ScrollDown (SD) scrolls the screen down n lines. New lines are added at the
// top of the screen.
//
// CSI <n> T
//
// See: https://vt100.net/docs/vt510-rm/SD.html
func ScrollDown(n int) string {
var s string
if n > 1 {
s = strconv.Itoa(n)
}
return "\x1b[" + s + "T"
}
// InsertLine (IL) inserts n blank lines at the current cursor position.
// Existing lines are moved down.
//
// CSI <n> L
//
// See: https://vt100.net/docs/vt510-rm/IL.html
func InsertLine(n int) string {
var s string
if n > 1 {
s = strconv.Itoa(n)
}
return "\x1b[" + s + "L"
}
// DeleteLine (DL) deletes n lines at the current cursor position. Existing
// lines are moved up.
//
// CSI <n> M
//
// See: https://vt100.net/docs/vt510-rm/DL.html
func DeleteLine(n int) string {
var s string
if n > 1 {
s = strconv.Itoa(n)
}
return "\x1b[" + s + "M"
}
// SetScrollingRegion (DECSTBM) sets the top and bottom margins for the scrolling
// region. The default is the entire screen.
//
// CSI <top> ; <bottom> r
//
// See: https://vt100.net/docs/vt510-rm/DECSTBM.html
func SetScrollingRegion(t, b int) string {
if t < 0 {
t = 0
}
if b < 0 {
b = 0
}
return "\x1b[" + strconv.Itoa(t) + ";" + strconv.Itoa(b) + "r"
}
package ansi
import (
"bytes"
"github.com/charmbracelet/x/ansi/parser"
)
// Sequence represents an ANSI sequence. This can be a control sequence, escape
// sequence, a printable character, etc.
type Sequence interface {
// String returns the string representation of the sequence.
String() string
// Bytes returns the byte representation of the sequence.
Bytes() []byte
// Clone returns a copy of the sequence.
Clone() Sequence
}
// Rune represents a printable character.
type Rune rune
var _ Sequence = Rune(0)
// Bytes implements Sequence.
func (r Rune) Bytes() []byte {
return []byte(string(r))
}
// String implements Sequence.
func (r Rune) String() string {
return string(r)
}
// Clone implements Sequence.
func (r Rune) Clone() Sequence {
return r
}
// ControlCode represents a control code character. This is a character that
// is not printable and is used to control the terminal. This would be a
// character in the C0 or C1 set in the range of 0x00-0x1F and 0x80-0x9F.
type ControlCode byte
var _ Sequence = ControlCode(0)
// Bytes implements Sequence.
func (c ControlCode) Bytes() []byte {
return []byte{byte(c)}
}
// String implements Sequence.
func (c ControlCode) String() string {
return string(c)
}
// Clone implements Sequence.
func (c ControlCode) Clone() Sequence {
return c
}
// EscSequence represents an escape sequence.
type EscSequence int
var _ Sequence = EscSequence(0)
// buffer returns the buffer of the escape sequence.
func (e EscSequence) buffer() *bytes.Buffer {
var b bytes.Buffer
b.WriteByte('\x1b')
if i := parser.Intermediate(int(e)); i != 0 {
b.WriteByte(byte(i))
}
b.WriteByte(byte(e.Command()))
return &b
}
// Bytes implements Sequence.
func (e EscSequence) Bytes() []byte {
return e.buffer().Bytes()
}
// String implements Sequence.
func (e EscSequence) String() string {
return e.buffer().String()
}
// Clone implements Sequence.
func (e EscSequence) Clone() Sequence {
return e
}
// Command returns the command byte of the escape sequence.
func (e EscSequence) Command() int {
return parser.Command(int(e))
}
// Intermediate returns the intermediate byte of the escape sequence.
func (e EscSequence) Intermediate() int {
return parser.Intermediate(int(e))
}
// SosSequence represents a SOS sequence.
type SosSequence struct {
// Data contains the raw data of the sequence.
Data []byte
}
var _ Sequence = &SosSequence{}
// Clone implements Sequence.
func (s SosSequence) Clone() Sequence {
return SosSequence{Data: append([]byte(nil), s.Data...)}
}
// Bytes implements Sequence.
func (s SosSequence) Bytes() []byte {
return s.buffer().Bytes()
}
// String implements Sequence.
func (s SosSequence) String() string {
return s.buffer().String()
}
func (s SosSequence) buffer() *bytes.Buffer {
var b bytes.Buffer
b.WriteByte('\x1b')
b.WriteByte('X')
b.Write(s.Data)
b.WriteString("\x1b\\")
return &b
}
// PmSequence represents a PM sequence.
type PmSequence struct {
// Data contains the raw data of the sequence.
Data []byte
}
var _ Sequence = &PmSequence{}
// Clone implements Sequence.
func (s PmSequence) Clone() Sequence {
return PmSequence{Data: append([]byte(nil), s.Data...)}
}
// Bytes implements Sequence.
func (s PmSequence) Bytes() []byte {
return s.buffer().Bytes()
}
// String implements Sequence.
func (s PmSequence) String() string {
return s.buffer().String()
}
// buffer returns the buffer of the PM sequence.
func (s PmSequence) buffer() *bytes.Buffer {
var b bytes.Buffer
b.WriteByte('\x1b')
b.WriteByte('^')
b.Write(s.Data)
b.WriteString("\x1b\\")
return &b
}
// ApcSequence represents an APC sequence.
type ApcSequence struct {
// Data contains the raw data of the sequence.
Data []byte
}
var _ Sequence = &ApcSequence{}
// Clone implements Sequence.
func (s ApcSequence) Clone() Sequence {
return ApcSequence{Data: append([]byte(nil), s.Data...)}
}
// Bytes implements Sequence.
func (s ApcSequence) Bytes() []byte {
return s.buffer().Bytes()
}
// String implements Sequence.
func (s ApcSequence) String() string {
return s.buffer().String()
}
// buffer returns the buffer of the APC sequence.
func (s ApcSequence) buffer() *bytes.Buffer {
var b bytes.Buffer
b.WriteByte('\x1b')
b.WriteByte('_')
b.Write(s.Data)
b.WriteString("\x1b\\")
return &b
}
package ansi
import (
"image/color"
"strconv"
"strings"
)
// ResetStyle is a SGR (Select Graphic Rendition) style sequence that resets
// all attributes.
// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
const ResetStyle = "\x1b[m"
// Attr is a SGR (Select Graphic Rendition) style attribute.
type Attr = string
// Style represents an ANSI SGR (Select Graphic Rendition) style.
type Style []Attr
// String returns the ANSI SGR (Select Graphic Rendition) style sequence for
// the given style.
func (s Style) String() string {
if len(s) == 0 {
return ResetStyle
}
return "\x1b[" + strings.Join(s, ";") + "m"
}
// Styled returns a styled string with the given style applied.
func (s Style) Styled(str string) string {
if len(s) == 0 {
return str
}
return s.String() + str + ResetStyle
}
// Reset appends the reset style attribute to the style.
func (s Style) Reset() Style {
return append(s, ResetAttr)
}
// Bold appends the bold style attribute to the style.
func (s Style) Bold() Style {
return append(s, BoldAttr)
}
// Faint appends the faint style attribute to the style.
func (s Style) Faint() Style {
return append(s, FaintAttr)
}
// Italic appends the italic style attribute to the style.
func (s Style) Italic() Style {
return append(s, ItalicAttr)
}
// Underline appends the underline style attribute to the style.
func (s Style) Underline() Style {
return append(s, UnderlineAttr)
}
// DoubleUnderline appends the double underline style attribute to the style.
func (s Style) DoubleUnderline() Style {
return append(s, DoubleUnderlineAttr)
}
// CurlyUnderline appends the curly underline style attribute to the style.
func (s Style) CurlyUnderline() Style {
return append(s, CurlyUnderlineAttr)
}
// DottedUnderline appends the dotted underline style attribute to the style.
func (s Style) DottedUnderline() Style {
return append(s, DottedUnderlineAttr)
}
// DashedUnderline appends the dashed underline style attribute to the style.
func (s Style) DashedUnderline() Style {
return append(s, DashedUnderlineAttr)
}
// SlowBlink appends the slow blink style attribute to the style.
func (s Style) SlowBlink() Style {
return append(s, SlowBlinkAttr)
}
// RapidBlink appends the rapid blink style attribute to the style.
func (s Style) RapidBlink() Style {
return append(s, RapidBlinkAttr)
}
// Reverse appends the reverse style attribute to the style.
func (s Style) Reverse() Style {
return append(s, ReverseAttr)
}
// Conceal appends the conceal style attribute to the style.
func (s Style) Conceal() Style {
return append(s, ConcealAttr)
}
// Strikethrough appends the strikethrough style attribute to the style.
func (s Style) Strikethrough() Style {
return append(s, StrikethroughAttr)
}
// NoBold appends the no bold style attribute to the style.
func (s Style) NoBold() Style {
return append(s, NoBoldAttr)
}
// NormalIntensity appends the normal intensity style attribute to the style.
func (s Style) NormalIntensity() Style {
return append(s, NormalIntensityAttr)
}
// NoItalic appends the no italic style attribute to the style.
func (s Style) NoItalic() Style {
return append(s, NoItalicAttr)
}
// NoUnderline appends the no underline style attribute to the style.
func (s Style) NoUnderline() Style {
return append(s, NoUnderlineAttr)
}
// NoBlink appends the no blink style attribute to the style.
func (s Style) NoBlink() Style {
return append(s, NoBlinkAttr)
}
// NoReverse appends the no reverse style attribute to the style.
func (s Style) NoReverse() Style {
return append(s, NoReverseAttr)
}
// NoConceal appends the no conceal style attribute to the style.
func (s Style) NoConceal() Style {
return append(s, NoConcealAttr)
}
// NoStrikethrough appends the no strikethrough style attribute to the style.
func (s Style) NoStrikethrough() Style {
return append(s, NoStrikethroughAttr)
}
// DefaultForegroundColor appends the default foreground color style attribute to the style.
func (s Style) DefaultForegroundColor() Style {
return append(s, DefaultForegroundColorAttr)
}
// DefaultBackgroundColor appends the default background color style attribute to the style.
func (s Style) DefaultBackgroundColor() Style {
return append(s, DefaultBackgroundColorAttr)
}
// DefaultUnderlineColor appends the default underline color style attribute to the style.
func (s Style) DefaultUnderlineColor() Style {
return append(s, DefaultUnderlineColorAttr)
}
// ForegroundColor appends the foreground color style attribute to the style.
func (s Style) ForegroundColor(c Color) Style {
return append(s, ForegroundColorAttr(c))
}
// BackgroundColor appends the background color style attribute to the style.
func (s Style) BackgroundColor(c Color) Style {
return append(s, BackgroundColorAttr(c))
}
// UnderlineColor appends the underline color style attribute to the style.
func (s Style) UnderlineColor(c Color) Style {
return append(s, UnderlineColorAttr(c))
}
// SGR (Select Graphic Rendition) style attributes.
// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
const (
ResetAttr Attr = "0"
BoldAttr Attr = "1"
FaintAttr Attr = "2"
ItalicAttr Attr = "3"
UnderlineAttr Attr = "4"
DoubleUnderlineAttr Attr = "4:2"
CurlyUnderlineAttr Attr = "4:3"
DottedUnderlineAttr Attr = "4:4"
DashedUnderlineAttr Attr = "4:5"
SlowBlinkAttr Attr = "5"
RapidBlinkAttr Attr = "6"
ReverseAttr Attr = "7"
ConcealAttr Attr = "8"
StrikethroughAttr Attr = "9"
NoBoldAttr Attr = "21" // Some terminals treat this as double underline.
NormalIntensityAttr Attr = "22"
NoItalicAttr Attr = "23"
NoUnderlineAttr Attr = "24"
NoBlinkAttr Attr = "25"
NoReverseAttr Attr = "27"
NoConcealAttr Attr = "28"
NoStrikethroughAttr Attr = "29"
DefaultForegroundColorAttr Attr = "39"
DefaultBackgroundColorAttr Attr = "49"
DefaultUnderlineColorAttr Attr = "59"
)
// ForegroundColorAttr returns the style SGR attribute for the given foreground
// color.
// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
func ForegroundColorAttr(c Color) Attr {
switch c := c.(type) {
case BasicColor:
// 3-bit or 4-bit ANSI foreground
// "3<n>" or "9<n>" where n is the color number from 0 to 7
if c < 8 {
return "3" + string('0'+c)
} else if c < 16 {
return "9" + string('0'+c-8)
}
case ExtendedColor:
// 256-color ANSI foreground
// "38;5;<n>"
return "38;5;" + strconv.FormatUint(uint64(c), 10)
case TrueColor, color.Color:
// 24-bit "true color" foreground
// "38;2;<r>;<g>;<b>"
r, g, b, _ := c.RGBA()
return "38;2;" +
strconv.FormatUint(uint64(shift(r)), 10) + ";" +
strconv.FormatUint(uint64(shift(g)), 10) + ";" +
strconv.FormatUint(uint64(shift(b)), 10)
}
return DefaultForegroundColorAttr
}
// BackgroundColorAttr returns the style SGR attribute for the given background
// color.
// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
func BackgroundColorAttr(c Color) Attr {
switch c := c.(type) {
case BasicColor:
// 3-bit or 4-bit ANSI foreground
// "4<n>" or "10<n>" where n is the color number from 0 to 7
if c < 8 {
return "4" + string('0'+c)
} else {
return "10" + string('0'+c-8)
}
case ExtendedColor:
// 256-color ANSI foreground
// "48;5;<n>"
return "48;5;" + strconv.FormatUint(uint64(c), 10)
case TrueColor, color.Color:
// 24-bit "true color" foreground
// "38;2;<r>;<g>;<b>"
r, g, b, _ := c.RGBA()
return "48;2;" +
strconv.FormatUint(uint64(shift(r)), 10) + ";" +
strconv.FormatUint(uint64(shift(g)), 10) + ";" +
strconv.FormatUint(uint64(shift(b)), 10)
}
return DefaultBackgroundColorAttr
}
// UnderlineColorAttr returns the style SGR attribute for the given underline
// color.
// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
func UnderlineColorAttr(c Color) Attr {
switch c := c.(type) {
// NOTE: we can't use 3-bit and 4-bit ANSI color codes with underline
// color, use 256-color instead.
//
// 256-color ANSI underline color
// "58;5;<n>"
case BasicColor:
return "58;5;" + strconv.FormatUint(uint64(c), 10)
case ExtendedColor:
return "58;5;" + strconv.FormatUint(uint64(c), 10)
case TrueColor, color.Color:
// 24-bit "true color" foreground
// "38;2;<r>;<g>;<b>"
r, g, b, _ := c.RGBA()
return "58;2;" +
strconv.FormatUint(uint64(shift(r)), 10) + ";" +
strconv.FormatUint(uint64(shift(g)), 10) + ";" +
strconv.FormatUint(uint64(shift(b)), 10)
}
return DefaultUnderlineColorAttr
}
func shift(v uint32) uint32 {
if v > 0xff {
return v >> 8
}
return v
}
package ansi
import (
"encoding/hex"
"strings"
)
// RequestTermcap (XTGETTCAP) requests Termcap/Terminfo strings.
//
// DCS + q <Pt> ST
//
// Where <Pt> is a list of Termcap/Terminfo capabilities, encoded in 2-digit
// hexadecimals, separated by semicolons.
//
// See: https://man7.org/linux/man-pages/man5/terminfo.5.html
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
func RequestTermcap(caps ...string) string {
if len(caps) == 0 {
return ""
}
s := "\x1bP+q"
for i, c := range caps {
if i > 0 {
s += ";"
}
s += strings.ToUpper(hex.EncodeToString([]byte(c)))
}
return s + "\x1b\\"
}
package ansi
// SetIconNameWindowTitle returns a sequence for setting the icon name and
// window title.
//
// OSC 0 ; title ST
// OSC 0 ; title BEL
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands
func SetIconNameWindowTitle(s string) string {
return "\x1b]0;" + s + "\x07"
}
// SetIconName returns a sequence for setting the icon name.
//
// OSC 1 ; title ST
// OSC 1 ; title BEL
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands
func SetIconName(s string) string {
return "\x1b]1;" + s + "\x07"
}
// SetWindowTitle returns a sequence for setting the window title.
//
// OSC 2 ; title ST
// OSC 2 ; title BEL
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands
func SetWindowTitle(s string) string {
return "\x1b]2;" + s + "\x07"
}
package ansi
import (
"bytes"
"github.com/charmbracelet/x/ansi/parser"
"github.com/rivo/uniseg"
)
// Truncate truncates a string to a given length, adding a tail to the
// end if the string is longer than the given length.
// This function is aware of ANSI escape codes and will not break them, and
// accounts for wide-characters (such as East Asians and emojis).
func Truncate(s string, length int, tail string) string {
tw := StringWidth(tail)
length -= tw
if length < 0 {
return ""
}
var cluster []byte
var buf bytes.Buffer
curWidth := 0
ignoring := false
gstate := -1
pstate := parser.GroundState // initial state
b := []byte(s)
i := 0
// Here we iterate over the bytes of the string and collect printable
// characters and runes. We also keep track of the width of the string
// in cells.
// Once we reach the given length, we start ignoring characters and only
// collect ANSI escape codes until we reach the end of string.
for i < len(b) {
state, action := parser.Table.Transition(pstate, b[i])
switch action {
case parser.PrintAction:
if utf8ByteLen(b[i]) > 1 {
// This action happens when we transition to the Utf8State.
var width int
cluster, _, width, gstate = uniseg.FirstGraphemeCluster(b[i:], gstate)
// increment the index by the length of the cluster
i += len(cluster)
// Are we ignoring? Skip to the next byte
if ignoring {
continue
}
// Is this gonna be too wide?
// If so write the tail and stop collecting.
if curWidth+width > length && !ignoring {
ignoring = true
buf.WriteString(tail)
}
if curWidth+width > length {
continue
}
curWidth += width
for _, r := range cluster {
buf.WriteByte(r)
}
gstate = -1 // reset grapheme state otherwise, width calculation might be off
// Done collecting, now we're back in the ground state.
pstate = parser.GroundState
continue
}
// Is this gonna be too wide?
// If so write the tail and stop collecting.
if curWidth >= length && !ignoring {
ignoring = true
buf.WriteString(tail)
}
// Skip to the next byte if we're ignoring
if ignoring {
i++
continue
}
// collects printable ASCII
curWidth++
fallthrough
default:
buf.WriteByte(b[i])
i++
}
// Transition to the next state.
pstate = state
// Once we reach the given length, we start ignoring runes and write
// the tail to the buffer.
if curWidth > length && !ignoring {
ignoring = true
buf.WriteString(tail)
}
}
return buf.String()
}
package ansi
import (
"fmt"
"image/color"
)
// colorToHexString returns a hex string representation of a color.
func colorToHexString(c color.Color) string {
if c == nil {
return ""
}
shift := func(v uint32) uint32 {
if v > 0xff {
return v >> 8
}
return v
}
r, g, b, _ := c.RGBA()
r, g, b = shift(r), shift(g), shift(b)
return fmt.Sprintf("#%02x%02x%02x", r, g, b)
}
// rgbToHex converts red, green, and blue values to a hexadecimal value.
//
// hex := rgbToHex(0, 0, 255) // 0x0000FF
func rgbToHex(r, g, b uint32) uint32 {
return r<<16 + g<<8 + b
}
This diff is collapsed.
This diff is collapsed.
package ansi
// DisableModifyOtherKeys disables the modifyOtherKeys mode.
//
// CSI > 4 ; 0 m
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
// See: https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:modifyOtherKeys
const DisableModifyOtherKeys = "\x1b[>4;0m"
// EnableModifyOtherKeys1 enables the modifyOtherKeys mode 1.
//
// CSI > 4 ; 1 m
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
// See: https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:modifyOtherKeys
const EnableModifyOtherKeys1 = "\x1b[>4;1m"
// EnableModifyOtherKeys2 enables the modifyOtherKeys mode 2.
//
// CSI > 4 ; 2 m
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
// See: https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:modifyOtherKeys
const EnableModifyOtherKeys2 = "\x1b[>4;2m"
// RequestModifyOtherKeys requests the modifyOtherKeys mode.
//
// CSI ? 4 m
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
// See: https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:modifyOtherKeys
const RequestModifyOtherKeys = "\x1b[?4m"
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
*.out
.DS_Store
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment