The Ansi parser and their associated actions have been decoupled. Now parsing results in call backs to an interface which performs the appropriate actions depending on the environment. This improvement provides a functional Vi experience and the vttest no longer panics. This PR replaces docker/docker #13224 with the latest console updates. Signed-off-by: John Howard <jhoward@microsoft.com> Upstream-commit: c923774c41678e4044081a87ec5841173d9577e7 Component: engine
138 lines
3.2 KiB
Go
138 lines
3.2 KiB
Go
package ansiterm
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
)
|
|
|
|
var logger *logrus.Logger
|
|
|
|
type AnsiParser struct {
|
|
currState State
|
|
eventHandler AnsiEventHandler
|
|
context *AnsiContext
|
|
CsiEntry State
|
|
CsiParam State
|
|
DcsEntry State
|
|
Escape State
|
|
EscapeIntermediate State
|
|
Error State
|
|
Ground State
|
|
OscString State
|
|
stateMap []State
|
|
}
|
|
|
|
func CreateParser(initialState string, evtHandler AnsiEventHandler) *AnsiParser {
|
|
logFile := ioutil.Discard
|
|
|
|
if isDebugEnv := os.Getenv(LogEnv); isDebugEnv == "1" {
|
|
logFile, _ = os.Create("ansiParser.log")
|
|
}
|
|
|
|
logger = &logrus.Logger{
|
|
Out: logFile,
|
|
Formatter: new(logrus.TextFormatter),
|
|
Level: logrus.InfoLevel,
|
|
}
|
|
|
|
parser := &AnsiParser{
|
|
eventHandler: evtHandler,
|
|
context: &AnsiContext{},
|
|
}
|
|
|
|
parser.CsiEntry = CsiEntryState{BaseState{name: "CsiEntry", parser: parser}}
|
|
parser.CsiParam = CsiParamState{BaseState{name: "CsiParam", parser: parser}}
|
|
parser.DcsEntry = DcsEntryState{BaseState{name: "DcsEntry", parser: parser}}
|
|
parser.Escape = EscapeState{BaseState{name: "Escape", parser: parser}}
|
|
parser.EscapeIntermediate = EscapeIntermediateState{BaseState{name: "EscapeIntermediate", parser: parser}}
|
|
parser.Error = ErrorState{BaseState{name: "Error", parser: parser}}
|
|
parser.Ground = GroundState{BaseState{name: "Ground", parser: parser}}
|
|
parser.OscString = OscStringState{BaseState{name: "OscString", parser: parser}}
|
|
|
|
parser.stateMap = []State{
|
|
parser.CsiEntry,
|
|
parser.CsiParam,
|
|
parser.DcsEntry,
|
|
parser.Escape,
|
|
parser.EscapeIntermediate,
|
|
parser.Error,
|
|
parser.Ground,
|
|
parser.OscString,
|
|
}
|
|
|
|
parser.currState = getState(initialState, parser.stateMap)
|
|
|
|
logger.Infof("CreateParser: parser %p", parser)
|
|
return parser
|
|
}
|
|
|
|
func getState(name string, states []State) State {
|
|
for _, el := range states {
|
|
if el.Name() == name {
|
|
return el
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ap *AnsiParser) Parse(bytes []byte) (int, error) {
|
|
for i, b := range bytes {
|
|
if err := ap.handle(b); err != nil {
|
|
return i, err
|
|
}
|
|
}
|
|
|
|
return len(bytes), ap.eventHandler.Flush()
|
|
}
|
|
|
|
func (ap *AnsiParser) handle(b byte) error {
|
|
ap.context.currentChar = b
|
|
newState, err := ap.currState.Handle(b)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if newState == nil {
|
|
logger.Warning("newState is nil")
|
|
return errors.New(fmt.Sprintf("New state of 'nil' is invalid."))
|
|
}
|
|
|
|
if newState != ap.currState {
|
|
if err := ap.changeState(newState); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ap *AnsiParser) changeState(newState State) error {
|
|
logger.Infof("ChangeState %s --> %s", ap.currState.Name(), newState.Name())
|
|
|
|
// Exit old state
|
|
if err := ap.currState.Exit(); err != nil {
|
|
logger.Infof("Exit state '%s' failed with : '%v'", ap.currState.Name(), err)
|
|
return err
|
|
}
|
|
|
|
// Perform transition action
|
|
if err := ap.currState.Transition(newState); err != nil {
|
|
logger.Infof("Transition from '%s' to '%s' failed with: '%v'", ap.currState.Name(), newState.Name, err)
|
|
return err
|
|
}
|
|
|
|
// Enter new state
|
|
if err := newState.Enter(); err != nil {
|
|
logger.Infof("Enter state '%s' failed with: '%v'", newState.Name(), err)
|
|
return err
|
|
}
|
|
|
|
ap.currState = newState
|
|
return nil
|
|
}
|