1
0
mirror of https://github.com/gizak/termui.git synced 2025-04-30 13:49:00 +08:00
termui/style_parser.go

211 lines
4.8 KiB
Go
Raw Normal View History

2019-01-23 20:12:10 -08:00
// 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 (
"strings"
)
const (
tokenFg = "fg"
tokenBg = "bg"
tokenModifier = "mod"
tokenItemSeparator = ","
tokenValueSeparator = ":"
tokenBeginStyledText = '['
tokenEndStyledText = ']'
tokenBeginStyle = '('
tokenEndStyle = ')'
2022-11-09 05:38:01 -08:00
tokenStyleKey = "]("
2019-01-23 20:12:10 -08:00
)
type parserState uint
2022-11-09 05:38:01 -08:00
type StyleBlock struct {
Start int
End int
}
2019-01-23 20:12:10 -08:00
const (
parserStateDefault parserState = iota
parserStateStyleItems
parserStateStyledText
)
2019-02-23 16:54:20 -08:00
// StyleParserColorMap can be modified to add custom color parsing to text
var StyleParserColorMap = map[string]Color{
2019-01-23 20:12:10 -08:00
"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:
2019-02-23 16:54:20 -08:00
style.Fg = StyleParserColorMap[pair[1]]
2019-01-23 20:12:10 -08:00
case tokenBg:
2019-02-23 16:54:20 -08:00
style.Bg = StyleParserColorMap[pair[1]]
2019-01-23 20:12:10 -08:00
case tokenModifier:
style.Modifier = modifierMap[pair[1]]
}
}
}
return style
}
2022-11-09 05:38:01 -08:00
// this will start at ]( and look backwards to find the [ and forward
// to find the ) and record these Start and End indexes in a StyleBlock
2022-11-08 13:32:59 -08:00
func findStartEndOfStyle(pos int, runes []rune) StyleBlock {
current := pos
sb := StyleBlock{0, 0}
for {
current--
if runes[current] == tokenBeginStyledText {
sb.Start = current
break
}
}
current = pos
for {
current++
if runes[current] == tokenEndStyle {
sb.End = current
break
}
}
return sb
}
2022-11-09 05:38:01 -08:00
// if are string is "foo [thing](style) foo [more](style)"
// this will return "foo ", "[thing](style)", " foo ", "[more](style)"
func breakBlocksIntoStrings(s string) []string {
2022-11-08 14:14:06 -08:00
buff := []string{}
2022-11-09 05:38:01 -08:00
blocks := findStyleBlocks(s)
2022-11-08 14:26:28 -08:00
if len(blocks) == 0 {
return buff
}
2022-11-08 14:09:43 -08:00
startEnd := len(s)
for i := len(blocks) - 1; i >= 0; i-- {
b := blocks[i]
2022-11-08 14:14:06 -08:00
item := s[b.End+1 : startEnd]
if item != "" {
buff = append([]string{item}, buff...)
}
item = s[b.Start : b.End+1]
buff = append([]string{item}, buff...)
2022-11-08 14:09:43 -08:00
startEnd = b.Start
2022-11-08 13:46:07 -08:00
}
2022-11-08 14:14:06 -08:00
item := s[0:startEnd]
if item != "" {
buff = append([]string{item}, buff...)
}
return buff
2022-11-08 13:46:07 -08:00
}
2022-11-09 05:38:01 -08:00
// loop through positions and make [] of StyleBlocks
func findStyleBlocks(s string) []StyleBlock {
items := []StyleBlock{}
2022-11-08 13:32:59 -08:00
runes := []rune(s)
2022-11-09 05:38:01 -08:00
positions := findStylePositions(s)
2022-11-08 13:32:59 -08:00
for _, pos := range positions {
sb := findStartEndOfStyle(pos, runes)
items = append(items, sb)
}
return items
}
2022-11-09 05:38:01 -08:00
// uses tokenStyleKey ]( which tells us we have both a [text] and a (style)
// if are string is "foo [thing](style) foo [more](style)"
// this func will return a list of two ints: the index of the first ]( and
// the index of the next one
func findStylePositions(s string) []int {
index := strings.Index(s, tokenStyleKey)
2022-11-08 08:22:41 -08:00
if index == -1 {
return []int{}
2022-11-07 08:26:51 -08:00
}
buff := []int{}
2022-11-08 06:09:14 -08:00
2022-11-08 08:22:41 -08:00
toProcess := s
offset := 0
2022-11-08 08:22:41 -08:00
for {
buff = append(buff, index+offset)
2022-11-08 08:22:41 -08:00
toProcess = toProcess[index+1:]
offset += index + 1
2022-11-09 05:38:01 -08:00
index = strings.Index(toProcess, tokenStyleKey)
2022-11-08 08:22:41 -08:00
if index == -1 {
break
2022-11-07 11:30:57 -08:00
}
2022-11-07 08:26:51 -08:00
}
2022-11-08 08:22:41 -08:00
return buff
2022-11-08 14:26:28 -08:00
}
2022-11-08 14:26:28 -08:00
func containsStyle(s string) bool {
2022-11-09 05:46:13 -08:00
if strings.HasPrefix(s, string(tokenBeginStyledText)) &&
strings.HasSuffix(s, string(tokenEndStyle)) &&
strings.Contains(s, string(tokenEndStyledText)) &&
strings.Contains(s, string(tokenBeginStyle)) {
return true
}
2022-11-08 14:26:28 -08:00
return false
}
2022-11-08 08:22:41 -08:00
2022-11-08 14:26:28 -08:00
func extractTextFromBlock(item string) string {
return "hi"
}
2022-11-08 06:09:14 -08:00
2022-11-08 14:26:28 -08:00
func extractStyleFromBlock(item string) string {
return "fg:red"
2022-11-07 08:26:51 -08:00
}
2019-02-23 16:54:20 -08:00
// ParseStyles parses a string for embedded Styles and returns []Cell with the correct styling.
2019-02-01 22:16:02 -08:00
// 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.
2019-02-23 16:54:20 -08:00
func ParseStyles(s string, defaultStyle Style) []Cell {
cells := []Cell{}
2022-11-09 05:38:01 -08:00
items := breakBlocksIntoStrings(s)
2022-11-08 14:26:28 -08:00
if len(items) == 0 {
return RunesToStyledCells([]rune(s), defaultStyle)
}
2022-11-07 03:55:40 -08:00
2022-11-08 14:26:28 -08:00
for _, item := range items {
if containsStyle(item) {
text := extractTextFromBlock(item)
styleText := extractStyleFromBlock(item)
style := readStyle([]rune(styleText), defaultStyle)
cells = append(RunesToStyledCells([]rune(text), style), cells...)
} else {
cells = append(RunesToStyledCells([]rune(item), defaultStyle), cells...)
2022-11-07 08:54:53 -08:00
}
2022-11-08 14:26:28 -08:00
}
return cells
}