package terminal import "unicode/utf8" // maxParams caps how many numbers one control sequence may carry. Real // sequences use a handful; the limit is there so that a stream of digits // cannot make the parser allocate without bound. const maxParams = 32 // maxOSC caps the length of an operating-system command — a window title, say. // Anything longer is a program that will never send its terminator. const maxOSC = 4096 // state is where the parser is in a control sequence. type state int const ( // ground is ordinary text, the state the parser spends its life in. ground state = iota // afterEscape has just seen ESC and is waiting to learn what kind of // sequence this is. afterEscape // inCSI is collecting the parameters of a control sequence, ESC [ … . inCSI // inOSC is collecting an operating-system command, ESC ] … , which runs // until a BEL or a string terminator. inOSC // inOSCEscape has seen ESC inside an OSC and is checking whether it is the // backslash that ends it. inOSCEscape // ignoring is skipping a sequence this emulator does not implement, up to // and including its final byte. ignoring ) // Parser turns a byte stream from a terminal program into changes to a Screen. // // It implements io.Writer, so the output of a pseudo-terminal can be copied // straight into it. Bytes may arrive split anywhere — in the middle of a // control sequence or of a UTF-8 character — and the parser carries its state // across calls. // // screen := terminal.NewScreen(80, 24) // parser := terminal.NewParser(screen) // parser.Write([]byte("\x1b[1;32mgreen\x1b[0m")) // fmt.Println(screen.LineText(0)) // green type Parser struct { screen *Screen state state params []int // private holds the '?' of a DEC private sequence, or zero. private byte // intermediate holds a byte such as the '>' of a secondary device // attributes request, or zero. intermediate byte osc []byte // partial holds the bytes of a UTF-8 character that arrived split across // two writes. partial []byte // title is the last window title the program asked for. title string // bell is set when the program rang the terminal bell. bell bool } // NewParser returns a parser writing onto a screen. func NewParser(screen *Screen) *Parser { return &Parser{screen: screen, params: make([]int, 0, maxParams)} } // Screen returns the screen the parser writes onto. func (p *Parser) Screen() *Screen { return p.screen } // Title returns the last window title the program asked for, or the empty // string when it has asked for none. func (p *Parser) Title() string { return p.title } // TakeBell reports whether the bell has rung since it was last asked, and // forgets it. A caller that never asks simply never rings. func (p *Parser) TakeBell() bool { rang := p.bell p.bell = false return rang } // Write feeds bytes to the parser. It never fails and always consumes // everything, because there is nothing a terminal can do with a byte it does // not understand except carry on. func (p *Parser) Write(data []byte) (int, error) { for _, b := range data { p.step(b) } return len(data), nil } // step advances the parser by one byte. func (p *Parser) step(b byte) { switch p.state { case ground: p.stepGround(b) case afterEscape: p.stepAfterEscape(b) case inCSI: p.stepCSI(b) case inOSC: p.stepOSC(b) case inOSCEscape: p.stepOSCEscape(b) case ignoring: p.stepIgnoring(b) } } // escape and the other control bytes the parser acts on directly. const ( bell = 0x07 backspace = 0x08 tab = 0x09 lineFeed = 0x0a verticalTab = 0x0b formFeed = 0x0c carriageReturn = 0x0d escape = 0x1b del = 0x7f ) // stepGround handles ordinary text and the C0 control characters. func (p *Parser) stepGround(b byte) { switch b { case escape: p.beginEscape() case bell: p.bell = true case backspace: p.screen.Backspace() case tab: p.screen.Tab() case lineFeed, verticalTab, formFeed: p.screen.LineFeed() case carriageReturn: p.screen.CarriageReturn() case del: // Delete is padding on a real terminal, and prints nothing. default: p.text(b) } } // beginEscape starts a new escape sequence, forgetting whatever an unfinished // one had collected. func (p *Parser) beginEscape() { p.state = afterEscape p.params = p.params[:0] p.private, p.intermediate = 0, 0 p.partial = p.partial[:0] } // text handles a byte of ordinary text, assembling UTF-8 characters that // arrive split across writes. func (p *Parser) text(b byte) { if b < utf8.RuneSelf { p.screen.WriteRune(rune(b)) return } p.partial = append(p.partial, b) r, size := utf8.DecodeRune(p.partial) if r == utf8.RuneError && size <= 1 { // Either more bytes are still to come, or this is not UTF-8 at all. // Waiting is right for the first and harmless for the second, until // the buffer grows past any legal character. if len(p.partial) >= utf8.UTFMax { p.screen.WriteRune(utf8.RuneError) p.partial = p.partial[:0] } return } p.screen.WriteRune(r) p.partial = p.partial[:0] }