2016-04-28 04:34:55 +08:00
|
|
|
// Copyright 2014 The gocui Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package gocui
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"strconv"
|
|
|
|
)
|
|
|
|
|
|
|
|
type escapeInterpreter struct {
|
2016-10-04 01:37:08 +08:00
|
|
|
state escapeState
|
|
|
|
curch rune
|
|
|
|
csiParam []string
|
|
|
|
curFgColor, curBgColor Attribute
|
2016-04-28 04:34:55 +08:00
|
|
|
}
|
|
|
|
|
2016-04-28 05:13:54 +08:00
|
|
|
type escapeState int
|
|
|
|
|
|
|
|
const (
|
|
|
|
stateNone escapeState = iota
|
|
|
|
stateEscape
|
|
|
|
stateCSI
|
|
|
|
stateParams
|
|
|
|
)
|
|
|
|
|
2016-04-28 04:34:55 +08:00
|
|
|
var (
|
|
|
|
errNotCSI = errors.New("Not a CSI escape sequence")
|
|
|
|
errCSINotANumber = errors.New("CSI escape sequence was expecting a number or a ;")
|
|
|
|
errCSIParseError = errors.New("CSI escape sequence parsing error")
|
|
|
|
errCSITooLong = errors.New("CSI escape sequence is too long")
|
|
|
|
)
|
|
|
|
|
|
|
|
// runes in case of error will output the non-parsed runes as a string.
|
|
|
|
func (ei *escapeInterpreter) runes() []rune {
|
|
|
|
switch ei.state {
|
|
|
|
case stateNone:
|
|
|
|
return []rune{0x1b}
|
|
|
|
case stateEscape:
|
|
|
|
return []rune{0x1b, ei.curch}
|
|
|
|
case stateCSI:
|
|
|
|
return []rune{0x1b, '[', ei.curch}
|
|
|
|
case stateParams:
|
|
|
|
ret := []rune{0x1b, '['}
|
|
|
|
for _, s := range ei.csiParam {
|
|
|
|
ret = append(ret, []rune(s)...)
|
|
|
|
ret = append(ret, ';')
|
|
|
|
}
|
|
|
|
return append(ret, ei.curch)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// newEscapeInterpreter returns an escapeInterpreter that will be able to parse
|
|
|
|
// terminal escape sequences.
|
|
|
|
func newEscapeInterpreter() *escapeInterpreter {
|
|
|
|
ei := &escapeInterpreter{
|
2016-10-04 01:37:08 +08:00
|
|
|
state: stateNone,
|
|
|
|
curFgColor: ColorDefault,
|
|
|
|
curBgColor: ColorDefault,
|
2016-04-28 04:34:55 +08:00
|
|
|
}
|
|
|
|
return ei
|
|
|
|
}
|
|
|
|
|
2016-10-11 14:52:03 +08:00
|
|
|
// reset sets the escapeInterpreter in initial state.
|
2016-04-28 04:34:55 +08:00
|
|
|
func (ei *escapeInterpreter) reset() {
|
|
|
|
ei.state = stateNone
|
2016-10-04 01:37:08 +08:00
|
|
|
ei.curFgColor = ColorDefault
|
|
|
|
ei.curBgColor = ColorDefault
|
2016-04-28 04:34:55 +08:00
|
|
|
ei.csiParam = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// paramToColor returns an attribute given a terminfo coloring.
|
|
|
|
func paramToColor(p int) Attribute {
|
|
|
|
switch p {
|
|
|
|
case 0:
|
|
|
|
return ColorBlack
|
|
|
|
case 1:
|
|
|
|
return ColorRed
|
|
|
|
case 2:
|
|
|
|
return ColorGreen
|
|
|
|
case 3:
|
|
|
|
return ColorYellow
|
|
|
|
case 4:
|
|
|
|
return ColorBlue
|
|
|
|
case 5:
|
|
|
|
return ColorMagenta
|
|
|
|
case 6:
|
|
|
|
return ColorCyan
|
|
|
|
case 7:
|
|
|
|
return ColorWhite
|
|
|
|
}
|
|
|
|
return ColorDefault
|
|
|
|
}
|
|
|
|
|
|
|
|
// parseOne parses a rune. If isEscape is true, it means that the rune is part
|
2016-05-23 06:21:03 +08:00
|
|
|
// of an escape sequence, and as such should not be printed verbatim. Otherwise,
|
2016-04-28 04:34:55 +08:00
|
|
|
// it's not an escape sequence.
|
|
|
|
func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) {
|
|
|
|
// Sanity checks to make sure we're not parsing something totally bogus.
|
|
|
|
if len(ei.csiParam) > 20 {
|
|
|
|
return false, errCSITooLong
|
|
|
|
}
|
|
|
|
if len(ei.csiParam) > 0 && len(ei.csiParam[len(ei.csiParam)-1]) > 255 {
|
|
|
|
return false, errCSITooLong
|
|
|
|
}
|
|
|
|
ei.curch = ch
|
|
|
|
switch ei.state {
|
|
|
|
case stateNone:
|
|
|
|
if ch == 0x1b {
|
|
|
|
ei.state = stateEscape
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
return false, nil
|
|
|
|
case stateEscape:
|
|
|
|
if ch == '[' {
|
|
|
|
ei.state = stateCSI
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
return false, errNotCSI
|
|
|
|
case stateCSI:
|
|
|
|
if ch >= '0' && ch <= '9' {
|
|
|
|
ei.state = stateParams
|
|
|
|
ei.csiParam = append(ei.csiParam, string(ch))
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
return false, errCSINotANumber
|
|
|
|
case stateParams:
|
|
|
|
switch {
|
|
|
|
case ch >= '0' && ch <= '9':
|
|
|
|
ei.csiParam[len(ei.csiParam)-1] += string(ch)
|
|
|
|
return true, nil
|
|
|
|
case ch == ';':
|
|
|
|
ei.csiParam = append(ei.csiParam, "")
|
|
|
|
return true, nil
|
|
|
|
case ch == 'm':
|
|
|
|
if len(ei.csiParam) < 1 {
|
|
|
|
return false, errCSIParseError
|
|
|
|
}
|
|
|
|
for _, param := range ei.csiParam {
|
|
|
|
p, err := strconv.Atoi(param)
|
|
|
|
if err != nil {
|
|
|
|
return false, errCSIParseError
|
|
|
|
}
|
|
|
|
switch {
|
|
|
|
case p >= 30 && p <= 37:
|
|
|
|
ei.curFgColor = paramToColor(p - 30)
|
|
|
|
case p >= 40 && p <= 47:
|
|
|
|
ei.curBgColor = paramToColor(p - 40)
|
|
|
|
case p == 1:
|
|
|
|
ei.curFgColor |= AttrBold
|
|
|
|
case p == 4:
|
|
|
|
ei.curFgColor |= AttrUnderline
|
|
|
|
case p == 7:
|
|
|
|
ei.curFgColor |= AttrReverse
|
|
|
|
case p == 0 || p == 39:
|
2016-10-04 01:37:08 +08:00
|
|
|
ei.curFgColor = ColorDefault
|
|
|
|
ei.curBgColor = ColorDefault
|
2016-04-28 04:34:55 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
ei.state = stateNone
|
|
|
|
ei.csiParam = nil
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false, nil
|
|
|
|
}
|