1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
|
package ansi
import (
"bytes"
"github.com/charmbracelet/x/exp/term/ansi/parser"
"github.com/rivo/uniseg"
)
// Strip removes ANSI escape codes from a string.
func Strip(s string) string {
var (
buf bytes.Buffer // buffer for collecting printable characters
ri int // rune index
rw int // rune width
pstate = parser.GroundState // initial state
)
// This implements a subset of the Parser to only collect runes and
// printable characters.
for i := 0; i < len(s); i++ {
var state, action byte
if pstate != parser.Utf8State {
state, action = parser.Table.Transition(pstate, s[i])
}
switch {
case pstate == parser.Utf8State:
// During this state, collect rw bytes to form a valid rune in the
// buffer. After getting all the rune bytes into the buffer,
// transition to GroundState and reset the counters.
buf.WriteByte(s[i])
ri++
if ri < rw {
continue
}
pstate = parser.GroundState
ri = 0
rw = 0
case action == parser.PrintAction:
// This action happens when we transition to the Utf8State.
if w := utf8ByteLen(s[i]); w > 1 {
rw = w
buf.WriteByte(s[i])
ri++
break
}
fallthrough
case action == parser.ExecuteAction:
// collects printable ASCII and non-printable characters
buf.WriteByte(s[i])
}
// Transition to the next state.
// The Utf8State is managed separately above.
if pstate != parser.Utf8State {
pstate = state
}
}
return buf.String()
}
// StringWidth returns the width of a string in cells. This is the number of
// cells that the string will occupy when printed in a terminal. ANSI escape
// codes are ignored and wide characters (such as East Asians and emojis) are
// accounted for.
func StringWidth(s string) int {
if s == "" {
return 0
}
return uniseg.StringWidth(Strip(s))
}
|