1
0
mirror of https://github.com/gizak/termui.git synced 2025-04-26 13:48:54 +08:00
termui/style_parser.go
2022-11-08 06:09:14 -08:00

249 lines
5.6 KiB
Go

// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package termui
import (
"fmt"
"strings"
)
const (
tokenFg = "fg"
tokenBg = "bg"
tokenModifier = "mod"
tokenItemSeparator = ","
tokenValueSeparator = ":"
tokenBeginStyledText = '['
tokenEndStyledText = ']'
tokenBeginStyle = '('
tokenEndStyle = ')'
)
type parserState uint
const (
parserStateDefault parserState = iota
parserStateStyleItems
parserStateStyledText
)
// StyleParserColorMap can be modified to add custom color parsing to text
var StyleParserColorMap = map[string]Color{
"red": ColorRed,
"blue": ColorBlue,
"black": ColorBlack,
"cyan": ColorCyan,
"yellow": ColorYellow,
"white": ColorWhite,
"clear": ColorClear,
"green": ColorGreen,
"magenta": ColorMagenta,
}
var modifierMap = map[string]Modifier{
"bold": ModifierBold,
"underline": ModifierUnderline,
"reverse": ModifierReverse,
}
// readStyle translates an []rune like `fg:red,mod:bold,bg:white` to a style
func readStyle(runes []rune, defaultStyle Style) Style {
style := defaultStyle
split := strings.Split(string(runes), tokenItemSeparator)
for _, item := range split {
pair := strings.Split(item, tokenValueSeparator)
if len(pair) == 2 {
switch pair[0] {
case tokenFg:
style.Fg = StyleParserColorMap[pair[1]]
case tokenBg:
style.Bg = StyleParserColorMap[pair[1]]
case tokenModifier:
style.Modifier = modifierMap[pair[1]]
}
}
}
return style
}
func lookLeftForBracket(s string) int {
return strings.LastIndex(s, "[")
}
func lookRightForEndStyle(s string) int {
return strings.Index(s, ")")
}
func BreakByStyles(s string) []string {
fmt.Println(s)
tokens := strings.Split(s, "](")
if len(tokens) == 1 {
return tokens
}
buff := []string{}
// test [blue](fg:blue,mod:bold) and [red](fg:red) and maybe even [foo](bg:red)!
for i, token := range tokens {
if i%2 == 0 {
index := lookLeftForBracket(token)
fmt.Println(i, "even", len(token), index)
} else {
index := lookRightForEndStyle(token)
fmt.Println(i, "odd", len(token), index)
}
buff = append(buff, token)
}
/*
styleString := ""
remainder := tokens[0]
i := 1
for {
prefix, item := lookLeftForBracket(remainder)
styleString, remainder = lookRightForEndStyle(tokens[i])
i++
buff = append(buff, prefix)
buff = append(buff, item)
buff = append(buff, styleString)
if !strings.Contains(remainder, "*") {
buff = append(buff, remainder)
break
}
if i > len(tokens)-1 {
break
}
}*/
return buff
}
func containsColorOrMod(s string) bool {
if strings.Contains(s, "fg:") {
return true
}
if strings.Contains(s, "bg:") {
return true
}
if strings.Contains(s, "mod:") {
return true
}
return false
}
// ParseStyles parses a string for embedded Styles and returns []Cell with the correct styling.
// Uses defaultStyle for any text without an embedded style.
// Syntax is of the form [text](fg:<color>,mod:<attribute>,bg:<color>).
// Ordering does not matter. All fields are optional.
func ParseStyles(s string, defaultStyle Style) []Cell {
cells := []Cell{}
items := BreakByStyles(s)
if len(items) == 1 {
runes := []rune(s)
for _, _rune := range runes {
cells = append(cells, Cell{_rune, defaultStyle})
}
return cells
}
style := defaultStyle
for i := len(items) - 1; i > -1; i-- {
if containsColorOrMod(items[i]) {
style = readStyle([]rune(items[i]), defaultStyle)
} else {
cells = append(RunesToStyledCells([]rune(items[i]), style), cells...)
style = defaultStyle
}
}
return cells
}
func ParseStyles2(s string, defaultStyle Style) []Cell {
cells := []Cell{}
runes := []rune(s)
state := parserStateDefault
styledText := []rune{}
styleItems := []rune{}
squareCount := 0
reset := func() {
styledText = []rune{}
styleItems = []rune{}
state = parserStateDefault
squareCount = 0
}
rollback := func() {
cells = append(cells, RunesToStyledCells(styledText, defaultStyle)...)
cells = append(cells, RunesToStyledCells(styleItems, defaultStyle)...)
reset()
}
// chop first and last runes
chop := func(s []rune) []rune {
return s[1 : len(s)-1]
}
for i, _rune := range runes {
switch state {
case parserStateDefault:
if _rune == tokenBeginStyledText {
state = parserStateStyledText
squareCount = 1
styledText = append(styledText, _rune)
} else {
cells = append(cells, Cell{_rune, defaultStyle})
}
case parserStateStyledText:
switch {
case squareCount == 0:
switch _rune {
case tokenBeginStyle:
state = parserStateStyleItems
styleItems = append(styleItems, _rune)
default:
rollback()
switch _rune {
case tokenBeginStyledText:
state = parserStateStyledText
squareCount = 1
styleItems = append(styleItems, _rune)
default:
cells = append(cells, Cell{_rune, defaultStyle})
}
}
case len(runes) == i+1:
rollback()
styledText = append(styledText, _rune)
case _rune == tokenBeginStyledText:
squareCount++
styledText = append(styledText, _rune)
case _rune == tokenEndStyledText:
squareCount--
styledText = append(styledText, _rune)
default:
styledText = append(styledText, _rune)
}
case parserStateStyleItems:
styleItems = append(styleItems, _rune)
if _rune == tokenEndStyle {
style := readStyle(chop(styleItems), defaultStyle)
cells = append(cells, RunesToStyledCells(chop(styledText), style)...)
reset()
} else if len(runes) == i+1 {
rollback()
}
}
}
return cells
}