turbo-editors/turbo-corepublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

parser.go · 186 lines · 5.0 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 4h ago1package terminal
2
3import "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.
8const 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.
12const maxOSC = 4096
13
14// state is where the parser is in a control sequence.
15type state int
16
17const (
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
47type 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.
71func 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.
76func (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.
80func (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.
84func (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.
93func (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.
101func (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.
119const (
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.
132func (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.
155func (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.
164func (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}