package terminal // stepCSI collects the parameters of a control sequence and runs it when its // final byte arrives. // // The shape is ESC [ [private] [params] [intermediate] final, where the // parameters are decimal numbers separated by semicolons. func (p *Parser) stepCSI(b byte) { switch { case b >= '0' && b <= '9': p.digit(b) case b == ';': p.nextParam() case b == '?' || b == '<' || b == '=' || b == '>': p.private = b case b >= ' ' && b <= '/': p.intermediate = b case b >= '@' && b <= '~': p.runCSI(b) p.state = ground default: // A control character inside a sequence is acted on where it stands, // which is what a real terminal does with, say, a carriage return // arriving mid-sequence. p.stepGround(b) } } // digit adds a decimal digit to the parameter being collected. func (p *Parser) digit(b byte) { if len(p.params) == 0 { p.params = append(p.params, 0) } last := len(p.params) - 1 p.params[last] = p.params[last]*10 + int(b-'0') } // nextParam starts the next parameter of the sequence. func (p *Parser) nextParam() { if len(p.params) == 0 { p.params = append(p.params, 0) } if len(p.params) < maxParams { p.params = append(p.params, 0) } } // param returns parameter i, or a default when it was not given. An omitted // parameter is zero in the protocol, and nearly every sequence reads zero as // "the default", which is what makes this one helper enough. func (p *Parser) param(i, fallback int) int { if i >= len(p.params) || p.params[i] == 0 { return fallback } return p.params[i] } // runCSI performs the sequence its final byte names. func (p *Parser) runCSI(final byte) { if p.private == '?' { p.runPrivateMode(final) return } switch final { case 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'f', 'd': p.runMovement(final) case 'J': p.screen.EraseDisplay(eraseMode(p.param(0, 0))) case 'K': p.screen.EraseLine(eraseMode(p.param(0, 0))) case 'L': p.screen.InsertLines(p.param(0, 1)) case 'M': p.screen.DeleteLines(p.param(0, 1)) case '@': p.screen.InsertChars(p.param(0, 1)) case 'P': p.screen.DeleteChars(p.param(0, 1)) case 'X': p.screen.EraseChars(p.param(0, 1)) case 'S': p.screen.ScrollUp(p.param(0, 1)) case 'T': p.screen.ScrollDown(p.param(0, 1)) case 'r': p.runSetScrollRegion() case 'm': p.runSGR() case 's': p.screen.SaveCursor() case 'u': p.screen.RestoreCursor() } // Everything else is dropped: device reports, tab-stop management, window // manipulation, and the non-private modes — of which only insert mode would // mean anything here, and programs that want to push text along use ICH, // which is implemented. A terminal that does not implement a sequence must // swallow it rather than print it. } // runMovement performs the sequences that only move the cursor. func (p *Parser) runMovement(final byte) { switch final { case 'A': // CUU, up p.screen.MoveBy(-p.param(0, 1), 0) case 'B': // CUD, down p.screen.MoveBy(p.param(0, 1), 0) case 'C': // CUF, forward p.screen.MoveBy(0, p.param(0, 1)) case 'D': // CUB, back p.screen.MoveBy(0, -p.param(0, 1)) case 'E': // CNL, down and to the first column p.screen.MoveBy(p.param(0, 1), 0) p.screen.CarriageReturn() case 'F': // CPL, up and to the first column p.screen.MoveBy(-p.param(0, 1), 0) p.screen.CarriageReturn() case 'G': // CHA, to a column p.screen.MoveTo(p.screen.Cursor().Row, p.param(0, 1)-1) case 'd': // VPA, to a row p.screen.MoveTo(p.param(0, 1)-1, p.screen.Cursor().Col) case 'H', 'f': // CUP, HVP, to a row and column p.screen.MoveTo(p.param(0, 1)-1, p.param(1, 1)-1) } } // runSetScrollRegion performs DECSTBM. With no parameters it opens the region // back up to the whole screen. func (p *Parser) runSetScrollRegion() { _, height := p.screen.Size() top := p.param(0, 1) - 1 bottom := p.param(1, height) - 1 if bottom <= top { bottom = height - 1 } p.screen.SetScrollRegion(top, bottom) } // eraseMode turns the number in an erase sequence into a mode, treating // anything unexpected as "from the cursor onwards". func eraseMode(n int) EraseMode { switch n { case 1: return EraseToStart case 2, 3: return EraseAll default: return EraseToEnd } } // runPrivateMode performs the DEC private set and reset sequences, ESC [ ? … h // and ESC [ ? … l. func (p *Parser) runPrivateMode(final byte) { if final != 'h' && final != 'l' { return } set := final == 'h' for i := range max(len(p.params), 1) { p.setPrivateMode(p.param(i, 0), set) } } // The DEC private modes this emulator implements. const ( modeApplicationCursor = 1 // DECCKM modeAutoWrap = 7 // DECAWM modeCursorVisible = 25 // DECTCEM modeAlternate47 = 47 // the oldest alternate-screen switch modeAlternate1047 = 1047 // alternate screen modeSaveCursor1048 = 1048 // save and restore the cursor modeAlternate1049 = 1049 // save the cursor, then the alternate screen ) // setPrivateMode turns one DEC private mode on or off. func (p *Parser) setPrivateMode(mode int, set bool) { switch mode { case modeApplicationCursor: p.screen.SetApplicationCursor(set) case modeAutoWrap: p.screen.SetAutoWrap(set) case modeCursorVisible: p.screen.SetCursorVisible(set) case modeAlternate47, modeAlternate1047: p.screen.UseAlternate(set) case modeSaveCursor1048: p.saveOrRestore(set) case modeAlternate1049: // The one programs actually use: save the cursor on the way in and put // it back on the way out, around the alternate screen. if set { p.screen.SaveCursor() p.screen.UseAlternate(true) return } p.screen.UseAlternate(false) p.screen.RestoreCursor() } // Every other private mode — mouse reporting, bracketed paste, focus // events — is accepted and ignored. The program is told nothing, which is // exactly what a terminal without those features looks like. } // saveOrRestore saves the cursor when setting a mode and restores it when // resetting one. func (p *Parser) saveOrRestore(set bool) { if set { p.screen.SaveCursor() return } p.screen.RestoreCursor() }