| 🛟 Updated. 28d5985 k33g 4h ago | 1 | package terminal |
| 2 | |
| 3 | import "unicode/utf8" |
| 4 | |
| 5 | // maxParams caps how many numbers one control sequence may carry. Real |
| 6 | // sequences use a handful; the limit is there so that a stream of digits |
| 7 | // cannot make the parser allocate without bound. |
| 8 | const maxParams = 32 |
| 9 | |
| 10 | // maxOSC caps the length of an operating-system command — a window title, say. |
| 11 | // Anything longer is a program that will never send its terminator. |
| 12 | const maxOSC = 4096 |
| 13 | |
| 14 | // state is where the parser is in a control sequence. |
| 15 | type state int |
| 16 | |
| 17 | const ( |
| 18 | // ground is ordinary text, the state the parser spends its life in. |
| 19 | ground state = iota |
| 20 | // afterEscape has just seen ESC and is waiting to learn what kind of |
| 21 | // sequence this is. |
| 22 | afterEscape |
| 23 | // inCSI is collecting the parameters of a control sequence, ESC [ … . |
| 24 | inCSI |
| 25 | // inOSC is collecting an operating-system command, ESC ] … , which runs |
| 26 | // until a BEL or a string terminator. |
| 27 | inOSC |
| 28 | // inOSCEscape has seen ESC inside an OSC and is checking whether it is the |
| 29 | // backslash that ends it. |
| 30 | inOSCEscape |
| 31 | // ignoring is skipping a sequence this emulator does not implement, up to |
| 32 | // and including its final byte. |
| 33 | ignoring |
| 34 | ) |
| 35 | |
| 36 | // Parser turns a byte stream from a terminal program into changes to a Screen. |
| 37 | // |
| 38 | // It implements io.Writer, so the output of a pseudo-terminal can be copied |
| 39 | // straight into it. Bytes may arrive split anywhere — in the middle of a |
| 40 | // control sequence or of a UTF-8 character — and the parser carries its state |
| 41 | // across calls. |
| 42 | // |
| 43 | // screen := terminal.NewScreen(80, 24) |
| 44 | // parser := terminal.NewParser(screen) |
| 45 | // parser.Write([]byte("\x1b[1;32mgreen\x1b[0m")) |
| 46 | // fmt.Println(screen.LineText(0)) // green |
| 47 | type Parser struct { |
| 48 | screen *Screen |
| 49 | |
| 50 | state state |
| 51 | params []int |
| 52 | // private holds the '?' of a DEC private sequence, or zero. |
| 53 | private byte |
| 54 | // intermediate holds a byte such as the '>' of a secondary device |
| 55 | // attributes request, or zero. |
| 56 | intermediate byte |
| 57 | |
| 58 | osc []byte |
| 59 | |
| 60 | // partial holds the bytes of a UTF-8 character that arrived split across |
| 61 | // two writes. |
| 62 | partial []byte |
| 63 | |
| 64 | // title is the last window title the program asked for. |
| 65 | title string |
| 66 | // bell is set when the program rang the terminal bell. |
| 67 | bell bool |
| 68 | } |
| 69 | |
| 70 | // NewParser returns a parser writing onto a screen. |
| 71 | func NewParser(screen *Screen) *Parser { |
| 72 | return &Parser{screen: screen, params: make([]int, 0, maxParams)} |
| 73 | } |
| 74 | |
| 75 | // Screen returns the screen the parser writes onto. |
| 76 | func (p *Parser) Screen() *Screen { return p.screen } |
| 77 | |
| 78 | // Title returns the last window title the program asked for, or the empty |
| 79 | // string when it has asked for none. |
| 80 | func (p *Parser) Title() string { return p.title } |
| 81 | |
| 82 | // TakeBell reports whether the bell has rung since it was last asked, and |
| 83 | // forgets it. A caller that never asks simply never rings. |
| 84 | func (p *Parser) TakeBell() bool { |
| 85 | rang := p.bell |
| 86 | p.bell = false |
| 87 | return rang |
| 88 | } |
| 89 | |
| 90 | // Write feeds bytes to the parser. It never fails and always consumes |
| 91 | // everything, because there is nothing a terminal can do with a byte it does |
| 92 | // not understand except carry on. |
| 93 | func (p *Parser) Write(data []byte) (int, error) { |
| 94 | for _, b := range data { |
| 95 | p.step(b) |
| 96 | } |
| 97 | return len(data), nil |
| 98 | } |
| 99 | |
| 100 | // step advances the parser by one byte. |
| 101 | func (p *Parser) step(b byte) { |
| 102 | switch p.state { |
| 103 | case ground: |
| 104 | p.stepGround(b) |
| 105 | case afterEscape: |
| 106 | p.stepAfterEscape(b) |
| 107 | case inCSI: |
| 108 | p.stepCSI(b) |
| 109 | case inOSC: |
| 110 | p.stepOSC(b) |
| 111 | case inOSCEscape: |
| 112 | p.stepOSCEscape(b) |
| 113 | case ignoring: |
| 114 | p.stepIgnoring(b) |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | // escape and the other control bytes the parser acts on directly. |
| 119 | const ( |
| 120 | bell = 0x07 |
| 121 | backspace = 0x08 |
| 122 | tab = 0x09 |
| 123 | lineFeed = 0x0a |
| 124 | verticalTab = 0x0b |
| 125 | formFeed = 0x0c |
| 126 | carriageReturn = 0x0d |
| 127 | escape = 0x1b |
| 128 | del = 0x7f |
| 129 | ) |
| 130 | |
| 131 | // stepGround handles ordinary text and the C0 control characters. |
| 132 | func (p *Parser) stepGround(b byte) { |
| 133 | switch b { |
| 134 | case escape: |
| 135 | p.beginEscape() |
| 136 | case bell: |
| 137 | p.bell = true |
| 138 | case backspace: |
| 139 | p.screen.Backspace() |
| 140 | case tab: |
| 141 | p.screen.Tab() |
| 142 | case lineFeed, verticalTab, formFeed: |
| 143 | p.screen.LineFeed() |
| 144 | case carriageReturn: |
| 145 | p.screen.CarriageReturn() |
| 146 | case del: |
| 147 | // Delete is padding on a real terminal, and prints nothing. |
| 148 | default: |
| 149 | p.text(b) |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | // beginEscape starts a new escape sequence, forgetting whatever an unfinished |
| 154 | // one had collected. |
| 155 | func (p *Parser) beginEscape() { |
| 156 | p.state = afterEscape |
| 157 | p.params = p.params[:0] |
| 158 | p.private, p.intermediate = 0, 0 |
| 159 | p.partial = p.partial[:0] |
| 160 | } |
| 161 | |
| 162 | // text handles a byte of ordinary text, assembling UTF-8 characters that |
| 163 | // arrive split across writes. |
| 164 | func (p *Parser) text(b byte) { |
| 165 | if b < utf8.RuneSelf { |
| 166 | p.screen.WriteRune(rune(b)) |
| 167 | return |
| 168 | } |
| 169 | |
| 170 | p.partial = append(p.partial, b) |
| 171 | r, size := utf8.DecodeRune(p.partial) |
| 172 | |
| 173 | if r == utf8.RuneError && size <= 1 { |
| 174 | // Either more bytes are still to come, or this is not UTF-8 at all. |
| 175 | // Waiting is right for the first and harmless for the second, until |
| 176 | // the buffer grows past any legal character. |
| 177 | if len(p.partial) >= utf8.UTFMax { |
| 178 | p.screen.WriteRune(utf8.RuneError) |
| 179 | p.partial = p.partial[:0] |
| 180 | } |
| 181 | return |
| 182 | } |
| 183 | |
| 184 | p.screen.WriteRune(r) |
| 185 | p.partial = p.partial[:0] |
| 186 | } |