mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-13 09:57:51 +08:00
89d95c17a6
This will preserve private use characters that are sometimes used by icon fonts. Control characters are already escaped by cli/term.Writer.
159 lines
3.5 KiB
Go
159 lines
3.5 KiB
Go
package ui
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type sgrTokenizer struct {
|
|
text string
|
|
|
|
styling Styling
|
|
content string
|
|
}
|
|
|
|
const sgrPrefix = "\033["
|
|
|
|
func (st *sgrTokenizer) Next() bool {
|
|
for strings.HasPrefix(st.text, sgrPrefix) {
|
|
trimmed := strings.TrimPrefix(st.text, sgrPrefix)
|
|
// Find the terminator of this sequence.
|
|
termIndex := strings.IndexFunc(trimmed, func(r rune) bool {
|
|
return r != ';' && (r < '0' || r > '9')
|
|
})
|
|
if termIndex == -1 {
|
|
// The string ends with an unterminated escape sequence; ignore
|
|
// it.
|
|
st.text = ""
|
|
return false
|
|
}
|
|
term := trimmed[termIndex]
|
|
sgr := trimmed[:termIndex]
|
|
st.text = trimmed[termIndex+1:]
|
|
if term == 'm' {
|
|
st.styling = StylingFromSGR(sgr)
|
|
st.content = ""
|
|
return true
|
|
}
|
|
// If the terminator is not 'm'; we have seen a non-SGR escape sequence;
|
|
// ignore it and continue.
|
|
}
|
|
if st.text == "" {
|
|
return false
|
|
}
|
|
// Parse a content segment until the next SGR prefix.
|
|
content := ""
|
|
nextSGR := strings.Index(st.text, sgrPrefix)
|
|
if nextSGR == -1 {
|
|
content = st.text
|
|
} else {
|
|
content = st.text[:nextSGR]
|
|
}
|
|
st.text = st.text[len(content):]
|
|
st.styling = nil
|
|
st.content = content
|
|
return true
|
|
}
|
|
|
|
func (st *sgrTokenizer) Token() (Styling, string) {
|
|
return st.styling, st.content
|
|
}
|
|
|
|
// ParseSGREscapedText parses SGR-escaped text into a Text. It also removes
|
|
// non-SGR CSI sequences sequences in the text.
|
|
func ParseSGREscapedText(s string) Text {
|
|
var text Text
|
|
var style Style
|
|
|
|
tokenizer := sgrTokenizer{text: s}
|
|
for tokenizer.Next() {
|
|
styling, content := tokenizer.Token()
|
|
if styling != nil {
|
|
styling.transform(&style)
|
|
}
|
|
if content != "" {
|
|
text = append(text, &Segment{style, content})
|
|
}
|
|
}
|
|
return text
|
|
}
|
|
|
|
var sgrStyling = map[int]Styling{
|
|
0: Reset,
|
|
1: Bold,
|
|
2: Dim,
|
|
4: Underlined,
|
|
5: Blink,
|
|
7: Inverse,
|
|
}
|
|
|
|
// StyleFromSGR builds a Style from an SGR sequence.
|
|
func StyleFromSGR(s string) Style {
|
|
var ret Style
|
|
StylingFromSGR(s).transform(&ret)
|
|
return ret
|
|
}
|
|
|
|
// StylingFromSGR builds a Style from an SGR sequence.
|
|
func StylingFromSGR(s string) Styling {
|
|
styling := jointStyling{}
|
|
codes := getSGRCodes(s)
|
|
if len(codes) == 0 {
|
|
return Reset
|
|
}
|
|
for len(codes) > 0 {
|
|
code := codes[0]
|
|
consume := 1
|
|
var moreStyling Styling
|
|
|
|
switch {
|
|
case sgrStyling[code] != nil:
|
|
moreStyling = sgrStyling[code]
|
|
case 30 <= code && code <= 37:
|
|
moreStyling = Fg(ansiColor(code - 30))
|
|
case 40 <= code && code <= 47:
|
|
moreStyling = Bg(ansiColor(code - 40))
|
|
case 90 <= code && code <= 97:
|
|
moreStyling = Fg(ansiBrightColor(code - 90))
|
|
case 100 <= code && code <= 107:
|
|
moreStyling = Bg(ansiBrightColor(code - 100))
|
|
case code == 38 && len(codes) >= 3 && codes[1] == 5:
|
|
moreStyling = Fg(xterm256Color(codes[2]))
|
|
consume = 3
|
|
case code == 48 && len(codes) >= 3 && codes[1] == 5:
|
|
moreStyling = Bg(xterm256Color(codes[2]))
|
|
consume = 3
|
|
case code == 38 && len(codes) >= 5 && codes[1] == 2:
|
|
moreStyling = Fg(trueColor{
|
|
uint8(codes[2]), uint8(codes[3]), uint8(codes[4])})
|
|
consume = 5
|
|
case code == 48 && len(codes) >= 5 && codes[1] == 2:
|
|
moreStyling = Bg(trueColor{
|
|
uint8(codes[2]), uint8(codes[3]), uint8(codes[4])})
|
|
consume = 5
|
|
default:
|
|
// Do nothing; skip this code
|
|
}
|
|
codes = codes[consume:]
|
|
if moreStyling != nil {
|
|
styling = append(styling, moreStyling)
|
|
}
|
|
}
|
|
return styling
|
|
}
|
|
|
|
func getSGRCodes(s string) []int {
|
|
var codes []int
|
|
for _, part := range strings.Split(s, ";") {
|
|
if part == "" {
|
|
codes = append(codes, 0)
|
|
} else {
|
|
code, err := strconv.Atoi(part)
|
|
if err == nil {
|
|
codes = append(codes, code)
|
|
}
|
|
}
|
|
}
|
|
return codes
|
|
}
|