turbo-editors/turbo-corepublic Fork 0
v1.0.0
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_csi.go · 216 lines · 6.0 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 14h ago1package terminal
2
3// stepCSI collects the parameters of a control sequence and runs it when its
4// final byte arrives.
5//
6// The shape is ESC [ [private] [params] [intermediate] final, where the
7// parameters are decimal numbers separated by semicolons.
8func (p *Parser) stepCSI(b byte) {
9 switch {
10 case b >= '0' && b <= '9':
11 p.digit(b)
12 case b == ';':
13 p.nextParam()
14 case b == '?' || b == '<' || b == '=' || b == '>':
15 p.private = b
16 case b >= ' ' && b <= '/':
17 p.intermediate = b
18 case b >= '@' && b <= '~':
19 p.runCSI(b)
20 p.state = ground
21 default:
22 // A control character inside a sequence is acted on where it stands,
23 // which is what a real terminal does with, say, a carriage return
24 // arriving mid-sequence.
25 p.stepGround(b)
26 }
27}
28
29// digit adds a decimal digit to the parameter being collected.
30func (p *Parser) digit(b byte) {
31 if len(p.params) == 0 {
32 p.params = append(p.params, 0)
33 }
34 last := len(p.params) - 1
35 p.params[last] = p.params[last]*10 + int(b-'0')
36}
37
38// nextParam starts the next parameter of the sequence.
39func (p *Parser) nextParam() {
40 if len(p.params) == 0 {
41 p.params = append(p.params, 0)
42 }
43 if len(p.params) < maxParams {
44 p.params = append(p.params, 0)
45 }
46}
47
48// param returns parameter i, or a default when it was not given. An omitted
49// parameter is zero in the protocol, and nearly every sequence reads zero as
50// "the default", which is what makes this one helper enough.
51func (p *Parser) param(i, fallback int) int {
52 if i >= len(p.params) || p.params[i] == 0 {
53 return fallback
54 }
55 return p.params[i]
56}
57
58// runCSI performs the sequence its final byte names.
59func (p *Parser) runCSI(final byte) {
60 if p.private == '?' {
61 p.runPrivateMode(final)
62 return
63 }
64
65 switch final {
66 case 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'f', 'd':
67 p.runMovement(final)
68 case 'J':
69 p.screen.EraseDisplay(eraseMode(p.param(0, 0)))
70 case 'K':
71 p.screen.EraseLine(eraseMode(p.param(0, 0)))
72 case 'L':
73 p.screen.InsertLines(p.param(0, 1))
74 case 'M':
75 p.screen.DeleteLines(p.param(0, 1))
76 case '@':
77 p.screen.InsertChars(p.param(0, 1))
78 case 'P':
79 p.screen.DeleteChars(p.param(0, 1))
80 case 'X':
81 p.screen.EraseChars(p.param(0, 1))
82 case 'S':
83 p.screen.ScrollUp(p.param(0, 1))
84 case 'T':
85 p.screen.ScrollDown(p.param(0, 1))
86 case 'r':
87 p.runSetScrollRegion()
88 case 'm':
89 p.runSGR()
90 case 's':
91 p.screen.SaveCursor()
92 case 'u':
93 p.screen.RestoreCursor()
94 }
95 // Everything else is dropped: device reports, tab-stop management, window
96 // manipulation, and the non-private modes — of which only insert mode would
97 // mean anything here, and programs that want to push text along use ICH,
98 // which is implemented. A terminal that does not implement a sequence must
99 // swallow it rather than print it.
100}
101
102// runMovement performs the sequences that only move the cursor.
103func (p *Parser) runMovement(final byte) {
104 switch final {
105 case 'A': // CUU, up
106 p.screen.MoveBy(-p.param(0, 1), 0)
107 case 'B': // CUD, down
108 p.screen.MoveBy(p.param(0, 1), 0)
109 case 'C': // CUF, forward
110 p.screen.MoveBy(0, p.param(0, 1))
111 case 'D': // CUB, back
112 p.screen.MoveBy(0, -p.param(0, 1))
113 case 'E': // CNL, down and to the first column
114 p.screen.MoveBy(p.param(0, 1), 0)
115 p.screen.CarriageReturn()
116 case 'F': // CPL, up and to the first column
117 p.screen.MoveBy(-p.param(0, 1), 0)
118 p.screen.CarriageReturn()
119 case 'G': // CHA, to a column
120 p.screen.MoveTo(p.screen.Cursor().Row, p.param(0, 1)-1)
121 case 'd': // VPA, to a row
122 p.screen.MoveTo(p.param(0, 1)-1, p.screen.Cursor().Col)
123 case 'H', 'f': // CUP, HVP, to a row and column
124 p.screen.MoveTo(p.param(0, 1)-1, p.param(1, 1)-1)
125 }
126}
127
128// runSetScrollRegion performs DECSTBM. With no parameters it opens the region
129// back up to the whole screen.
130func (p *Parser) runSetScrollRegion() {
131 _, height := p.screen.Size()
132
133 top := p.param(0, 1) - 1
134 bottom := p.param(1, height) - 1
135 if bottom <= top {
136 bottom = height - 1
137 }
138
139 p.screen.SetScrollRegion(top, bottom)
140}
141
142// eraseMode turns the number in an erase sequence into a mode, treating
143// anything unexpected as "from the cursor onwards".
144func eraseMode(n int) EraseMode {
145 switch n {
146 case 1:
147 return EraseToStart
148 case 2, 3:
149 return EraseAll
150 default:
151 return EraseToEnd
152 }
153}
154
155// runPrivateMode performs the DEC private set and reset sequences, ESC [ ? … h
156// and ESC [ ? … l.
157func (p *Parser) runPrivateMode(final byte) {
158 if final != 'h' && final != 'l' {
159 return
160 }
161 set := final == 'h'
162
163 for i := range max(len(p.params), 1) {
164 p.setPrivateMode(p.param(i, 0), set)
165 }
166}
167
168// The DEC private modes this emulator implements.
169const (
170 modeApplicationCursor = 1 // DECCKM
171 modeAutoWrap = 7 // DECAWM
172 modeCursorVisible = 25 // DECTCEM
173 modeAlternate47 = 47 // the oldest alternate-screen switch
174 modeAlternate1047 = 1047 // alternate screen
175 modeSaveCursor1048 = 1048 // save and restore the cursor
176 modeAlternate1049 = 1049 // save the cursor, then the alternate screen
177)
178
179// setPrivateMode turns one DEC private mode on or off.
180func (p *Parser) setPrivateMode(mode int, set bool) {
181 switch mode {
182 case modeApplicationCursor:
183 p.screen.SetApplicationCursor(set)
184 case modeAutoWrap:
185 p.screen.SetAutoWrap(set)
186 case modeCursorVisible:
187 p.screen.SetCursorVisible(set)
188 case modeAlternate47, modeAlternate1047:
189 p.screen.UseAlternate(set)
190 case modeSaveCursor1048:
191 p.saveOrRestore(set)
192 case modeAlternate1049:
193 // The one programs actually use: save the cursor on the way in and put
194 // it back on the way out, around the alternate screen.
195 if set {
196 p.screen.SaveCursor()
197 p.screen.UseAlternate(true)
198 return
199 }
200 p.screen.UseAlternate(false)
201 p.screen.RestoreCursor()
202 }
203 // Every other private mode — mouse reporting, bracketed paste, focus
204 // events — is accepted and ignored. The program is told nothing, which is
205 // exactly what a terminal without those features looks like.
206}
207
208// saveOrRestore saves the cursor when setting a mode and restores it when
209// resetting one.
210func (p *Parser) saveOrRestore(set bool) {
211 if set {
212 p.screen.SaveCursor()
213 return
214 }
215 p.screen.RestoreCursor()
216}