escape.go

  1// Copyright 2014 The gocui Authors. All rights reserved.
  2// Use of this source code is governed by a BSD-style
  3// license that can be found in the LICENSE file.
  4
  5package gocui
  6
  7import (
  8	"errors"
  9	"strconv"
 10)
 11
 12type escapeInterpreter struct {
 13	state                  escapeState
 14	curch                  rune
 15	csiParam               []string
 16	curFgColor, curBgColor Attribute
 17}
 18
 19type escapeState int
 20
 21const (
 22	stateNone escapeState = iota
 23	stateEscape
 24	stateCSI
 25	stateParams
 26)
 27
 28var (
 29	errNotCSI        = errors.New("Not a CSI escape sequence")
 30	errCSINotANumber = errors.New("CSI escape sequence was expecting a number or a ;")
 31	errCSIParseError = errors.New("CSI escape sequence parsing error")
 32	errCSITooLong    = errors.New("CSI escape sequence is too long")
 33)
 34
 35// runes in case of error will output the non-parsed runes as a string.
 36func (ei *escapeInterpreter) runes() []rune {
 37	switch ei.state {
 38	case stateNone:
 39		return []rune{0x1b}
 40	case stateEscape:
 41		return []rune{0x1b, ei.curch}
 42	case stateCSI:
 43		return []rune{0x1b, '[', ei.curch}
 44	case stateParams:
 45		ret := []rune{0x1b, '['}
 46		for _, s := range ei.csiParam {
 47			ret = append(ret, []rune(s)...)
 48			ret = append(ret, ';')
 49		}
 50		return append(ret, ei.curch)
 51	}
 52	return nil
 53}
 54
 55// newEscapeInterpreter returns an escapeInterpreter that will be able to parse
 56// terminal escape sequences.
 57func newEscapeInterpreter() *escapeInterpreter {
 58	ei := &escapeInterpreter{
 59		state:      stateNone,
 60		curFgColor: ColorDefault,
 61		curBgColor: ColorDefault,
 62	}
 63	return ei
 64}
 65
 66// reset sets the escapeInterpreter in initial state.
 67func (ei *escapeInterpreter) reset() {
 68	ei.state = stateNone
 69	ei.curFgColor = ColorDefault
 70	ei.curBgColor = ColorDefault
 71	ei.csiParam = nil
 72}
 73
 74// paramToColor returns an attribute given a terminfo coloring.
 75func paramToColor(p int) Attribute {
 76	switch p {
 77	case 0:
 78		return ColorBlack
 79	case 1:
 80		return ColorRed
 81	case 2:
 82		return ColorGreen
 83	case 3:
 84		return ColorYellow
 85	case 4:
 86		return ColorBlue
 87	case 5:
 88		return ColorMagenta
 89	case 6:
 90		return ColorCyan
 91	case 7:
 92		return ColorWhite
 93	}
 94	return ColorDefault
 95}
 96
 97// parseOne parses a rune. If isEscape is true, it means that the rune is part
 98// of an escape sequence, and as such should not be printed verbatim. Otherwise,
 99// it's not an escape sequence.
100func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) {
101	// Sanity checks to make sure we're not parsing something totally bogus.
102	if len(ei.csiParam) > 20 {
103		return false, errCSITooLong
104	}
105	if len(ei.csiParam) > 0 && len(ei.csiParam[len(ei.csiParam)-1]) > 255 {
106		return false, errCSITooLong
107	}
108	ei.curch = ch
109	switch ei.state {
110	case stateNone:
111		if ch == 0x1b {
112			ei.state = stateEscape
113			return true, nil
114		}
115		return false, nil
116	case stateEscape:
117		if ch == '[' {
118			ei.state = stateCSI
119			return true, nil
120		}
121		return false, errNotCSI
122	case stateCSI:
123		if ch >= '0' && ch <= '9' {
124			ei.state = stateParams
125			ei.csiParam = append(ei.csiParam, string(ch))
126			return true, nil
127		}
128		return false, errCSINotANumber
129	case stateParams:
130		switch {
131		case ch >= '0' && ch <= '9':
132			ei.csiParam[len(ei.csiParam)-1] += string(ch)
133			return true, nil
134		case ch == ';':
135			ei.csiParam = append(ei.csiParam, "")
136			return true, nil
137		case ch == 'm':
138			if len(ei.csiParam) < 1 {
139				return false, errCSIParseError
140			}
141			for _, param := range ei.csiParam {
142				p, err := strconv.Atoi(param)
143				if err != nil {
144					return false, errCSIParseError
145				}
146				switch {
147				case p >= 30 && p <= 37:
148					ei.curFgColor = paramToColor(p - 30)
149				case p >= 40 && p <= 47:
150					ei.curBgColor = paramToColor(p - 40)
151				case p == 1:
152					ei.curFgColor |= AttrBold
153				case p == 4:
154					ei.curFgColor |= AttrUnderline
155				case p == 7:
156					ei.curFgColor |= AttrReverse
157				case p == 0 || p == 39:
158					ei.curFgColor = ColorDefault
159					ei.curBgColor = ColorDefault
160				}
161			}
162			ei.state = stateNone
163			ei.csiParam = nil
164			return true, nil
165		}
166	}
167	return false, nil
168}