2019-06-16 10:14:30 +03:00
|
|
|
|
package m
|
|
|
|
|
|
|
|
|
|
import (
|
2019-06-16 21:57:03 +03:00
|
|
|
|
"log"
|
2019-06-16 10:14:30 +03:00
|
|
|
|
"regexp"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"github.com/gdamore/tcell"
|
|
|
|
|
)
|
|
|
|
|
|
2019-06-17 22:39:57 +03:00
|
|
|
|
const _TabSize = 4
|
|
|
|
|
|
2019-06-16 21:57:03 +03:00
|
|
|
|
// Token is a rune with a style to be written to a cell on screen
|
2019-06-16 10:14:30 +03:00
|
|
|
|
type Token struct {
|
|
|
|
|
Rune rune
|
|
|
|
|
Style tcell.Style
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TokensFromString turns a string into a series of tokens
|
2019-06-16 21:57:03 +03:00
|
|
|
|
func TokensFromString(logger *log.Logger, s string) []Token {
|
2019-06-16 10:14:30 +03:00
|
|
|
|
var tokens []Token
|
|
|
|
|
|
2019-06-18 20:08:35 +03:00
|
|
|
|
styleBrokenUtf8 := tcell.StyleDefault.Background(7).Foreground(1)
|
|
|
|
|
|
2019-06-16 21:57:03 +03:00
|
|
|
|
for _, styledString := range _StyledStringsFromString(logger, s) {
|
2019-06-27 22:39:46 +03:00
|
|
|
|
for _, token := range _TokensFromStyledString(styledString) {
|
|
|
|
|
switch token.Rune {
|
2019-06-18 20:08:35 +03:00
|
|
|
|
|
|
|
|
|
case '\x09': // TAB
|
2019-06-17 22:39:57 +03:00
|
|
|
|
for {
|
|
|
|
|
tokens = append(tokens, Token{
|
|
|
|
|
Rune: ' ',
|
|
|
|
|
Style: styledString.Style,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (len(tokens))%_TabSize == 0 {
|
|
|
|
|
// We arrived at the next tab stop
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-18 20:08:35 +03:00
|
|
|
|
|
|
|
|
|
case '<27>': // Go's broken-UTF8 marker
|
|
|
|
|
tokens = append(tokens, Token{
|
|
|
|
|
Rune: '?',
|
|
|
|
|
Style: styleBrokenUtf8,
|
|
|
|
|
})
|
|
|
|
|
|
2019-06-27 22:39:46 +03:00
|
|
|
|
case '\x08': // Backspace
|
2019-06-17 22:39:57 +03:00
|
|
|
|
tokens = append(tokens, Token{
|
2019-06-27 22:39:46 +03:00
|
|
|
|
Rune: '<',
|
|
|
|
|
Style: styleBrokenUtf8,
|
2019-06-17 22:39:57 +03:00
|
|
|
|
})
|
2019-06-27 22:39:46 +03:00
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
tokens = append(tokens, token)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return tokens
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func _TokensFromStyledString(styledString _StyledString) []Token {
|
|
|
|
|
tokens := make([]Token, 0, len(styledString.String)+1)
|
|
|
|
|
oneBack := '\x00'
|
|
|
|
|
twoBack := '\x00'
|
|
|
|
|
for _, char := range []rune(styledString.String) {
|
|
|
|
|
if oneBack == '\x08' && twoBack != '\x00' {
|
|
|
|
|
// Something-Backspace-Something
|
|
|
|
|
|
|
|
|
|
replacement := (*Token)(nil)
|
|
|
|
|
|
|
|
|
|
if char == twoBack {
|
|
|
|
|
replacement = &Token{
|
|
|
|
|
Rune: twoBack,
|
|
|
|
|
Style: styledString.Style.Bold(true),
|
|
|
|
|
}
|
2019-06-17 22:39:57 +03:00
|
|
|
|
}
|
2019-06-27 22:39:46 +03:00
|
|
|
|
|
|
|
|
|
if twoBack == '_' {
|
|
|
|
|
replacement = &Token{
|
|
|
|
|
Rune: char,
|
|
|
|
|
Style: styledString.Style.Underline(true),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if replacement != nil {
|
|
|
|
|
tokens = append(tokens[0:len(tokens)-2], *replacement)
|
|
|
|
|
|
|
|
|
|
twoBack = oneBack
|
|
|
|
|
oneBack = char
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No match, just keep going
|
2019-06-16 10:14:30 +03:00
|
|
|
|
}
|
2019-06-27 22:39:46 +03:00
|
|
|
|
|
|
|
|
|
tokens = append(tokens, Token{
|
|
|
|
|
Rune: char,
|
|
|
|
|
Style: styledString.Style,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
twoBack = oneBack
|
|
|
|
|
oneBack = char
|
2019-06-16 10:14:30 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return tokens
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type _StyledString struct {
|
|
|
|
|
String string
|
|
|
|
|
Style tcell.Style
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-16 21:57:03 +03:00
|
|
|
|
func _StyledStringsFromString(logger *log.Logger, s string) []_StyledString {
|
2019-06-16 10:14:30 +03:00
|
|
|
|
// This function was inspired by the
|
|
|
|
|
// https://golang.org/pkg/regexp/#Regexp.Split source code
|
|
|
|
|
|
|
|
|
|
pattern := regexp.MustCompile("\x1b\\[([0-9;]*m)")
|
|
|
|
|
|
|
|
|
|
matches := pattern.FindAllStringIndex(s, -1)
|
|
|
|
|
styledStrings := make([]_StyledString, 0, len(matches)+1)
|
|
|
|
|
|
|
|
|
|
style := tcell.StyleDefault
|
|
|
|
|
|
|
|
|
|
beg := 0
|
|
|
|
|
end := 0
|
|
|
|
|
for _, match := range matches {
|
|
|
|
|
end = match[0]
|
|
|
|
|
|
2019-06-16 10:23:25 +03:00
|
|
|
|
if end > beg {
|
2019-06-27 22:39:46 +03:00
|
|
|
|
// Found non-zero length string
|
2019-06-16 10:14:30 +03:00
|
|
|
|
styledStrings = append(styledStrings, _StyledString{
|
|
|
|
|
String: s[beg:end],
|
|
|
|
|
Style: style,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
matchedPart := s[match[0]:match[1]]
|
2019-06-16 21:57:03 +03:00
|
|
|
|
style = _UpdateStyle(logger, style, matchedPart)
|
2019-06-16 10:14:30 +03:00
|
|
|
|
|
|
|
|
|
beg = match[1]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if end != len(s) {
|
|
|
|
|
styledStrings = append(styledStrings, _StyledString{
|
|
|
|
|
String: s[beg:],
|
|
|
|
|
Style: style,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return styledStrings
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// _UpdateStyle parses a string of the form "ESC[33m" into changes to style
|
2019-06-16 21:57:03 +03:00
|
|
|
|
func _UpdateStyle(logger *log.Logger, style tcell.Style, escapeSequence string) tcell.Style {
|
2019-06-16 10:14:30 +03:00
|
|
|
|
for _, number := range strings.Split(escapeSequence[2:len(escapeSequence)-1], ";") {
|
|
|
|
|
switch number {
|
2019-06-18 21:12:35 +03:00
|
|
|
|
case "", "0", "00":
|
2019-06-16 10:14:30 +03:00
|
|
|
|
style = tcell.StyleDefault
|
2019-06-16 21:57:03 +03:00
|
|
|
|
|
2019-06-16 21:58:19 +03:00
|
|
|
|
case "1":
|
|
|
|
|
style = style.Bold(true)
|
|
|
|
|
|
2019-06-16 22:39:27 +03:00
|
|
|
|
case "7":
|
|
|
|
|
style = style.Reverse(true)
|
|
|
|
|
|
|
|
|
|
case "27":
|
|
|
|
|
style = style.Reverse(false)
|
|
|
|
|
|
2019-06-16 21:57:03 +03:00
|
|
|
|
// Foreground colors
|
2019-06-16 10:14:30 +03:00
|
|
|
|
case "30":
|
2019-06-16 22:26:04 +03:00
|
|
|
|
style = style.Foreground(0)
|
2019-06-16 10:14:30 +03:00
|
|
|
|
case "31":
|
2019-06-16 22:26:04 +03:00
|
|
|
|
style = style.Foreground(1)
|
2019-06-16 10:14:30 +03:00
|
|
|
|
case "32":
|
2019-06-16 22:26:04 +03:00
|
|
|
|
style = style.Foreground(2)
|
2019-06-16 10:14:30 +03:00
|
|
|
|
case "33":
|
2019-06-16 22:26:04 +03:00
|
|
|
|
style = style.Foreground(3)
|
2019-06-16 10:14:30 +03:00
|
|
|
|
case "34":
|
2019-06-16 22:26:04 +03:00
|
|
|
|
style = style.Foreground(4)
|
2019-06-16 10:14:30 +03:00
|
|
|
|
case "35":
|
2019-06-16 22:26:04 +03:00
|
|
|
|
style = style.Foreground(5)
|
2019-06-16 10:14:30 +03:00
|
|
|
|
case "36":
|
2019-06-16 22:26:04 +03:00
|
|
|
|
style = style.Foreground(6)
|
2019-06-16 10:14:30 +03:00
|
|
|
|
case "37":
|
2019-06-16 22:26:04 +03:00
|
|
|
|
style = style.Foreground(7)
|
2019-06-16 21:57:03 +03:00
|
|
|
|
|
|
|
|
|
// Background colors
|
|
|
|
|
case "40":
|
2019-06-16 22:26:04 +03:00
|
|
|
|
style = style.Background(0)
|
2019-06-16 21:57:03 +03:00
|
|
|
|
case "41":
|
2019-06-16 22:26:04 +03:00
|
|
|
|
style = style.Background(1)
|
2019-06-16 21:57:03 +03:00
|
|
|
|
case "42":
|
2019-06-16 22:26:04 +03:00
|
|
|
|
style = style.Background(2)
|
2019-06-16 21:57:03 +03:00
|
|
|
|
case "43":
|
2019-06-16 22:26:04 +03:00
|
|
|
|
style = style.Background(3)
|
2019-06-16 21:57:03 +03:00
|
|
|
|
case "44":
|
2019-06-16 22:26:04 +03:00
|
|
|
|
style = style.Background(4)
|
2019-06-16 21:57:03 +03:00
|
|
|
|
case "45":
|
2019-06-16 22:26:04 +03:00
|
|
|
|
style = style.Background(5)
|
2019-06-16 21:57:03 +03:00
|
|
|
|
case "46":
|
2019-06-16 22:26:04 +03:00
|
|
|
|
style = style.Background(6)
|
2019-06-16 21:57:03 +03:00
|
|
|
|
case "47":
|
2019-06-16 22:26:04 +03:00
|
|
|
|
style = style.Background(7)
|
2019-06-16 21:57:03 +03:00
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
logger.Printf("Unrecognized ANSI SGI code <%s>", number)
|
2019-06-16 10:14:30 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return style
|
|
|
|
|
}
|