Branch data Line data Source code
1 : : #ifdef RUN_TESTS
2 : : # include <gtest/gtest.h>
3 : : #endif
4 : : /** @file tty/terminal.cc
5 : : * @brief Defines TerminalWindow, the bulk of the terminal emulator. Its main purpose is to render anything that is printed by the subprocess.
6 : : */
7 : :
8 : : #include <array>
9 : : #include <cstdio> // sprintf
10 : :
11 : : #include <SDL.h> // for window title
12 : :
13 : : #include "terminal.hh"
14 : : #include "ctype.hh"
15 : : #include "256color.hh"
16 : : #include "color.hh"
17 : : #include "ui.hh"
18 : :
19 : 403 : void TerminalWindow::ResetAttr()
20 : : {
21 : 403 : bool prot = wnd.blank.protect;
22 : 403 : wnd.blank = Cell{};
23 : 403 : wnd.blank.protect = prot;
24 : 403 : }
25 : 401 : void TerminalWindow::Reset(bool full)
26 : : {
27 : 401 : top = 0;
28 : 401 : bottom = wnd.ysize-1;
29 : :
30 : 401 : gset = {0,0,0,0}; activeset = 0; scs = 0;
31 : 401 : utfmode = 0;
32 : 401 : lastch = U' ';
33 : :
34 : 401 : state = 0;
35 [ - + ]: 401 : p.clear();
36 : :
37 : 401 : wnd.inverse = false;
38 : 401 : wnd.cursorvis = true;
39 [ + + ]: 401 : if(full)
40 : : {
41 : 400 : edgeflag = false;
42 : 400 : wnd.cursx = 0;
43 : 400 : wnd.cursy = 0;
44 : 400 : wnd.FillBox(0,0, wnd.xsize,wnd.ysize, wnd.blank); // Clear screen
45 : : }
46 : 401 : }
47 : :
48 : 2 : void TerminalWindow::YScrollDown(unsigned y1, unsigned y2, int amount) const
49 : : {
50 [ + - ]: 2 : if(amount <= 0) return;
51 : 2 : unsigned hei = y2-y1+1;
52 [ - + ]: 2 : if(unsigned(amount) > hei) amount = hei;
53 : : //fprintf(stderr, "Height=%d, amount=%d, scrolling DOWN by %d lines\n", hei,amount, hei-amount);
54 : 2 : wnd.CopyText(0,y1+amount, 0,y1, wnd.xsize,hei-amount);
55 : 2 : wnd.FillBox(0,y1, wnd.xsize,amount);
56 : : }
57 : :
58 : 25 : void TerminalWindow::YScrollUp(unsigned y1, unsigned y2, int amount) const
59 : : {
60 [ + - ]: 25 : if(amount <= 0) return;
61 : 25 : unsigned hei = y2-y1+1;
62 [ - + ]: 25 : if(unsigned(amount) > hei) amount = hei;
63 : : //fprintf(stderr, "Height=%d, amount=%d, scrolling UP by %d lines\n", hei,amount, hei-amount);
64 : 25 : wnd.CopyText(0,y1, 0,y1+amount, wnd.xsize,hei-amount);
65 : 25 : wnd.FillBox(0,y2-amount+1, wnd.xsize,amount);
66 : : }
67 : :
68 : 773 : void TerminalWindow::Write(std::u32string_view s)
69 : : {
70 : 773 : unsigned color = 0;
71 : : /** Repositions cursor horizontally and ensures the new location is within allowed range. */
72 : 970 : auto ClampedMoveX = [&](int tgtx)
73 : : {
74 [ + + + + ]: 358 : wnd.cursx = std::min(std::size_t(std::max(0,tgtx)), wnd.xsize-1);
75 : 197 : edgeflag = false;
76 : 970 : };
77 : : /** Repositions cursor vertically and ensures the new location is within allowed range.
78 : : * param tgty = Target y coordinate
79 : : * param strict = If set, only permits moving inside current window; otherwise permits moving anywhere on screen.
80 : : */
81 : 966 : auto ClampedMoveY = [&](int tgty, bool strict = true)
82 : : {
83 [ + - + - : 193 : if(wnd.cursy >= top && wnd.cursy <= bottom && strict)
+ + ]
84 : : {
85 : : // Only permit moving inside window
86 [ + + + + ]: 288 : wnd.cursy = std::min(std::size_t(std::max(int(top), tgty)), bottom);
87 : : }
88 : : else
89 : : {
90 : : // Permit moving anywhere
91 [ + + + + ]: 78 : wnd.cursy = std::min(std::size_t(std::max(0,tgty)), wnd.ysize-1);
92 : : }
93 : 966 : };
94 : : /** Combination of ClampedMoveX and ClampedMoveY. */
95 : 820 : auto ClampedMove = [&](int tgtx, int tgty, bool strict = true)
96 : : {
97 : 47 : ClampedMoveX(tgtx);
98 : 47 : ClampedMoveY(tgty, strict);
99 : 820 : };
100 : : /** Performs line feed. */
101 : 924 : auto Lf = [&]
102 : : {
103 [ + + ]: 151 : if(wnd.cursy == bottom)
104 : 23 : YScrollUp(top, bottom, 1);
105 : : else
106 : 128 : ClampedMoveY(wnd.cursy+1);
107 : 924 : };
108 : : /** Performs typewriter write for one character. */
109 : 6862 : auto PutC = [&](char32_t c, bool doublewidth)
110 : : {
111 [ + + ]: 6089 : if(edgeflag)
112 : : {
113 [ + - ]: 135 : if(wnd.cursx == wnd.xsize-1)
114 : : {
115 : 135 : Lf();
116 : 135 : wnd.cursx = 0;
117 : : }
118 : 135 : edgeflag = false;
119 : : }
120 : 6089 : wnd.PutCh(wnd.cursx,wnd.cursy, c, gset[activeset]);
121 [ + + ]: 6089 : if(wnd.cursx == wnd.xsize-1) edgeflag = true;
122 : 5951 : else ++wnd.cursx;
123 : :
124 [ + + ]: 6089 : if(doublewidth)
125 : : {
126 : : // If this character was double-width,
127 : : // skip the next column but mark it also dirty.
128 [ - + ]: 32 : wnd.Dirtify(wnd.cursx,wnd.cursy);
129 [ - + ]: 32 : if(wnd.cursx == wnd.xsize-1) edgeflag = true;
130 : 32 : else ++wnd.cursx;
131 : : }
132 : 6862 : };
133 : :
134 : 773 : enum : unsigned {
135 : : MODE38_2 = 70, MODE38_2_x1, MODE38_2_x2, MODE_38_x = 108,
136 : : MODE38_3 = 80, MODE38_3_x1, MODE38_3_x2, MODE38_5 = 56,
137 : : MODE48_2 = 87, MODE48_2_x1, MODE48_2_x2, MODE_48_x = 109,
138 : : MODE48_3 = 111, MODE48_3_x1, MODE48_3_x2, MODE48_5 = 57,
139 : : MODE58_2 = 114, MODE58_2_x1, MODE58_2_x2, MODE_58_x = 110,
140 : : MODE58_3 = 117, MODE58_3_x1, MODE58_3_x2, MODE58_5 = 99,
141 : : MODE38_4 = 66, MODE38_4_x1, MODE38_4_x2, MODE38_4_x3,
142 : : MODE48_4 = 76, MODE48_4_x1, MODE48_4_x2, MODE48_4_x3,
143 : : MODE58_4 = 83, MODE58_4_x1, MODE58_4_x2, MODE58_4_x3,
144 : : };
145 : 773 : static_assert(MODE38_4_x1 == MODE38_4+1); static_assert(MODE38_3_x1 == MODE38_3+1); static_assert(MODE38_2_x1 == MODE38_2+1);
146 : 773 : static_assert(MODE48_4_x1 == MODE48_4+1); static_assert(MODE48_3_x1 == MODE48_3+1); static_assert(MODE48_2_x1 == MODE48_2+1);
147 : 773 : static_assert(MODE58_4_x1 == MODE58_4+1); static_assert(MODE58_3_x1 == MODE58_3+1); static_assert(MODE58_2_x1 == MODE58_2+1);
148 : 773 : static_assert(MODE38_4_x2 == MODE38_4_x1+1); static_assert(MODE38_3_x2 == MODE38_3_x1+1); static_assert(MODE38_2_x2 == MODE38_2_x1+1);
149 : 773 : static_assert(MODE48_4_x2 == MODE48_4_x1+1); static_assert(MODE48_3_x2 == MODE48_3_x1+1); static_assert(MODE48_2_x2 == MODE48_2_x1+1);
150 : 773 : static_assert(MODE58_4_x2 == MODE58_4_x1+1); static_assert(MODE58_3_x2 == MODE58_3_x1+1); static_assert(MODE58_2_x2 == MODE58_2_x1+1);
151 : 773 : static_assert(MODE38_4_x3 == MODE38_4_x2+1);
152 : 773 : static_assert(MODE48_4_x3 == MODE48_4_x2+1);
153 : 773 : static_assert(MODE58_4_x3 == MODE58_4_x2+1);
154 : 773 : static_assert(MODE_48_x == MODE_38_x+1); static_assert(MODE_58_x == MODE_48_x+1);
155 : : /* Translation maps for ProcessSGR for a smaller jump table */
156 : 773 : auto M = [](unsigned n) constexpr -> unsigned char
157 : : {
158 : : // 16? 36, 37?
159 : : unsigned char result = 0;
160 : : // SKIP the following, as they are not implemented:
161 : : // 8 (conceal)
162 : : // 20 (fraktur)
163 : : // 26 (proportional)
164 : : // 28 (clear conceal)
165 : : // 50 (clear proportional)
166 : : // 51-52 (framed & encircled)
167 : : // 54 (clear framed & encircled)
168 : : // 59 (default underline color)
169 : : // 60-65 (ideogram settings)
170 : : // 73-75 (superscript & subscript settings)
171 : : // This makes the code smaller. They can be enabled when implemented.
172 : : //
173 : : // Put most common things first
174 : : if(/*n >= 0 &&*/ n <= 1) return result+n-0; else result += 2;
175 : : //
176 : : if(n >= 30 && n <= 37) return result; else result+=1; // 2 -- handled by a single case
177 : : if(n >= 90 && n <= 97) return result; else result+=1; // 3 -- handled by a single case
178 : : if(n == MODE38_5) return result; else result+=1; // 4 -- fallback from above
179 : : if(n == MODE38_2_x2) return result; else result+=1; // 5 -- uncommon, but works as fallback from MODE38_5
180 : : if(n == MODE38_3_x2) return result; else result+=1; // 6
181 : : if(n == MODE38_4_x3) return result; else result+=1; // 7
182 : : //
183 : : if(n >= 40 && n <= 47) return result; else result+=1; // 8 -- handled by a single case
184 : : if(n >=100 && n <=107) return result; else result+=1; // 9 -- handled by a single case
185 : : if(n == MODE48_5) return result; else result+=1; // 10 -- fallback from above
186 : : if(n == MODE48_2_x2) return result; else result+=1; // 11 -- uncommon, but works as fallback from MODE48_5
187 : : if(n == MODE48_3_x2) return result; else result+=1; // 12
188 : : if(n == MODE48_4_x3) return result; else result+=1; // 13
189 : : //
190 : : if(n == 38) return result; else result+=1; // 14 -- sets MODE_38_x
191 : : if(n == 48) return result; else result+=1; // 15 -- sets MODE_48_x
192 : : if(n == 58) return result; else result+=1; // 16 -- sets MODE_58_x
193 : : if(n == MODE_38_x
194 : : || n == MODE_48_x
195 : : || n == MODE_58_x) return result; else result+=1; // 17 -- handled by a single case
196 : : //
197 : : if((n >= MODE38_4 && n <= MODE38_4_x2)
198 : : || (n >= MODE38_3 && n <= MODE38_3_x1)
199 : : || (n >= MODE38_2 && n <= MODE38_2_x1)
200 : : || (n >= MODE48_4 && n <= MODE48_4_x2)
201 : : || (n >= MODE48_3 && n <= MODE48_3_x1)
202 : : || (n >= MODE48_2 && n <= MODE48_2_x1)
203 : : || (n >= MODE58_4 && n <= MODE58_4_x2)
204 : : || (n >= MODE58_3 && n <= MODE58_3_x1)
205 : : || (n >= MODE58_2 && n <= MODE58_2_x1)) return result; else result+=1; // 18 -- handled by a single case
206 : : // Skip these, as they are not implemented:
207 : : //if(n == MODE58_4_x3) return result; else result+=1;
208 : : //if(n == MODE58_3_x2) return result; else result+=1;
209 : : //if(n == MODE58_2_x2) return result; else result+=1;
210 : : //if(n == MODE58_5) return result; else result+=1;
211 : : //
212 : : if(n == 39) return result; else result+=1; // 19
213 : : if(n == 49) return result; else result+=1; // 20
214 : : //
215 : : if(n == 9) return result; else result+=1; // 21
216 : : if(n >= 2 && n <= 7) return result+n-2; else result += 6; // 22..27
217 : : //
218 : : if(n == 27) return result; else result+=1; // 28
219 : : if(n == 29) return result; else result+=1; // 29
220 : : if(n == 53) return result; else result+=1; // 30
221 : : if(n >= 21 && n <= 25) return result+n-21; else result+=5; // 31..35
222 : : if(n == 55) return result; else result+=1; // 36
223 : : //
224 : : return result;
225 : : };
226 : 773 : static constexpr unsigned char __ = M(~0u);
227 : 773 : static constexpr unsigned char translate[] =
228 : : {
229 : : #define a(n) M(n+0),M(n+1),M(n+2),M(n+3),M(n+4),M(n+5),M(n+6),M(n+7),M(n+8),M(n+9),
230 : : a(0)a(10)a(20)a(30)a(40)a(50)a(60)a(70)a(80)a(90)a(100)a(110)
231 : : #undef a
232 : : };
233 : 773 : static constexpr unsigned char modetab[] = {MODE38_2,MODE38_3,MODE38_4,MODE38_5,
234 : : MODE48_2,MODE48_3,MODE48_4,MODE48_5,
235 : : MODE58_2,MODE58_3,MODE58_4,MODE58_5};
236 : :
237 : : /** ProcessSGR processes a SGR command. */
238 : 5042 : auto ProcessSGR = [&](char32_t& c, unsigned a,
239 : : auto&& reset_attr,
240 : : auto&& change_attr)
241 : : {
242 : : /* c is a state code internal to this function, a is the input number from the SGR code.
243 : : * Codes:
244 : : * 0 = default state -- Will parse "a" (input number) as verbatim
245 : : * Other, see below:
246 : : * Switch-case map (* = regular use, _ = UDEFINED, # = internal use):
247 : : * 0-9 **********
248 : : * 10-19 __________ (font commands, 0=default, 1-9=alt font)
249 : : * 20-29 **********
250 : : * 30-39 **********
251 : : * 40-49 **********
252 : : * 50-59 ******##** 56: MODE38_5[1] 57: MODE48_5[1]
253 : : * 60-69 ******#### 66: MODE38_4[4]
254 : : * 70-79 ###***#### 70: MODE38_2[3] 76: MODE48_4[4]
255 : : * 80-89 ########## 80: MODE38_3[3] 83: MODE58_4[4] 87: MODE48_2[3]
256 : : * 90-99 ********_# 99: MODE58_5[1]
257 : : * 100-109 ********## 108: MODE_38_x[3]
258 : : * 110-119 ########## 111: MODE48_3[3] 114: MODE58_2[3] 117: MODE58_3[3]
259 : : *
260 : : * Detailed layout on internal use codes --- what happens if "a" has that same value:
261 : : * 56 mistakenly interpreted as 38;5;56
262 : : * 57 mistakenly interpreted as 48;5;57
263 : : * 99 mistakenly interpreted as 58;5;99
264 : : * 72 mistakenly interpreted as 38;2;?;?;72
265 : : * 82 mistakenly interpreted as 38;3;?;?;82
266 : : * 89 mistakenly interpreted as 48;2;?;?;89
267 : : * 113 mistakenly interpreted as 48;3;?;?;113
268 : : * 116 mistakenly interpreted as 58;2;?;?;116
269 : : * 119 mistakenly interpreted as 58;3;?;?;119
270 : : * 69 mistakenly interpreted as 38;4;?;?;?;69
271 : : * 79 mistakenly interpreted as 48;4;?;?;?;79
272 : : * 86 mistakenly interpreted as 58;4;?;?;?;86
273 : : * 108 mistakenly interpreted as 38;108 -- does nothing
274 : : * 109 mistakenly interpreted as 48;109 -- does nothing
275 : : * 110 mistakenly interpreted as 58;110 -- does nothing
276 : : * Anything else: ignores 1 parameter and sets bold mode (as in 1)
277 : : */
278 [ + + ]: 4269 : unsigned switchval = c ? c : a;
279 : 4269 : auto m = [](unsigned n) constexpr
280 : : {
281 [ + + ]: 4269 : return (n < sizeof(translate)) ? translate[n] : __;
282 : : };
283 [ + + + + : 4133 : switch(m(switchval))
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + ]
284 : : {
285 : : #define set(what, v) change_attr([&](Cell& c, auto x){c.what = x;}, \
286 : : [&](const Cell& c) { return c.what; }, \
287 : : v)
288 : 2 : case m(0): reset_attr(); c = 0; break;
289 : 26 : case m(1): set(bold, true); c = 0; break;
290 : 9 : case m(2): set(dim, true); c = 0; break;
291 : 7 : case m(3): set(italic, true); c = 0; break;
292 : 8 : case m(4): set(underline, true); c = 0; break;
293 : 3 : case m(5): set(blink, 1); c = 0; break;
294 : 2 : case m(6): set(blink, 2); c = 0; break;
295 : 8 : case m(7): set(inverse, true); c = 0; break;
296 : : //case m(8): set(conceal, true); c = 0; break;
297 : 3 : case m(9): set(overstrike, true); c = 0; break;
298 : : //case m(20): set(fraktur, true); c = 0; break;
299 : 8 : case m(21): set(underline2, true); c = 0; break;
300 : 2 : case m(22): set(dim, false); set(bold, false); c = 0; break;
301 : 2 : case m(23): set(italic, false); set(fraktur, false); c = 0; break;
302 : 2 : case m(24): set(underline, false); set(underline2, false); c = 0; break;
303 : 2 : case m(25): set(blink, 0); c = 0; break;
304 : : //case m(26): set(proportional, true); c = 0; break;
305 : 2 : case m(27): set(inverse, false); c = 0; break;
306 : : //case m(28): set(conceal, false); c = 0; break;
307 : 2 : case m(29): set(overstrike, false); c = 0; break;
308 : 1 : case m(39): set(underline, false); set(underline2, false);
309 : 2 : set(fgcolor, Cell{}.fgcolor); c = 0; break; // Set default foreground color
310 : 1 : case m(49): set(bgcolor, Cell{}.bgcolor); c = 0; break; // Set default background color
311 : : //case m(59): c = 0; break; // Set default underline color
312 : : //case m(50): set(proportional, false); c = 0; break;
313 : : //case m(51): set(framed, true); c = 0; break;
314 : : //case m(52): set(encircled, true); c = 0; break;
315 : 3 : case m(53): set(overlined, true); c = 0; break;
316 : : //case m(54): set(framed, false); set(encircled, false); c = 0; break;
317 : 2 : case m(55): set(overlined, false); c = 0; break;
318 : : //case m(60): set(ideo_underline, true); c = 0; break;
319 : : //case m(61): set(ideo_underline2, true); c = 0; break;
320 : : //case m(62): set(ideo_overline, true); c = 0; break;
321 : : //case m(63): set(ideo_overline2, true); c = 0; break;
322 : : //case m(64): set(ideo_stress, true); c = 0; break;
323 : : //case m(65): set(ideo_underline, false); set(ideo_underline2, false);
324 : : // set(ideo_overline, false); set(ideo_overline2, false);
325 : : // set(ideo_stress, false); c = 0; break;
326 : : //case m(73): set(scriptsize, 1); c = 0; break;
327 : : //case m(74): set(scriptsize, 2); c = 0; break;
328 : : //case m(75): set(scriptsize, 0); c = 0; break;
329 : 518 : case m(38): c = MODE_38_x; break;
330 : 518 : case m(48): c = MODE_48_x; break;
331 : 259 : case m(58): c = MODE_58_x; break;
332 : :
333 : : // MODE_38_x, MODE_48_x, MODE_58_x are handled by single case.
334 : 1298 : case m(MODE_38_x):/*
335 : : case m(MODE_48_x):
336 : : case m(MODE_58_x):*/
337 : : {
338 : 3549 : color = 0;
339 [ + + ]: 1298 : if(a >= 2 && a <= 5) { c = modetab[(c-MODE_38_x)*4 + a - 2]; break; }
340 : 3 : c = 0;
341 : 3 : break;
342 : : }
343 : :
344 : : // All of these are handled by a single case. They update color and increment mode number.
345 : 47 : case m(MODE38_4): /*//color = (color << 8) + a; c+=1; break; // 38;4;n
346 : : case m(MODE38_3): //color = (color << 8) + a; c+=1; break; // 38;3;n
347 : : case m(MODE38_2): //color = (color << 8) + a; c+=1; break; // 38;2;n
348 : : case m(MODE48_4): //color = (color << 8) + a; c+=1; break; // 48;4;n
349 : : case m(MODE48_3): //color = (color << 8) + a; c+=1; break; // 48;3;n
350 : : case m(MODE48_2): //color = (color << 8) + a; c+=1; break; // 48;2;n
351 : : case m(MODE58_4): //color = (color << 8) + a; c+=1; break; // 58;4;n
352 : : case m(MODE58_3): //color = (color << 8) + a; c+=1; break; // 58;3;n
353 : : case m(MODE58_2): //color = (color << 8) + a; c+=1; break; // 58;2;n
354 : : case m(MODE38_4_x1): //color = (color << 8) + a; c+=1; break; // 38;4;#;n
355 : : case m(MODE38_3_x1)://color = (color << 8) + a; c+=1; break; // 38;3;#;n
356 : : case m(MODE38_2_x1)://color = (color << 8) + a; c+=1; break; // 38;2;#;n
357 : : case m(MODE48_4_x1)://color = (color << 8) + a; c+=1; break; // 48;4;#;n
358 : : case m(MODE48_3_x1)://color = (color << 8) + a; c+=1; break; // 48;3;#;n
359 : : case m(MODE48_2_x1)://color = (color << 8) + a; c+=1; break; // 48;2;#;n
360 : : case m(MODE58_4_x1)://color = (color << 8) + a; c+=1; break; // 58;4;#;n
361 : : case m(MODE58_3_x1)://color = (color << 8) + a; c+=1; break; // 58;3;#;n
362 : : case m(MODE58_2_x1)://color = (color << 8) + a; c+=1; break; // 58;2;#;n
363 : : case m(MODE38_4_x2)://color = (color << 8) + a; c+=1; break; // 38;4;#;#;n
364 : : case m(MODE48_4_x2)://color = (color << 8) + a; c+=1; break; // 48;4;#;#;n
365 : 47 : case m(MODE58_4_x2):*/color = (color << 8) + a; c+=1; break; // 58;4;#;#;n
366 : :
367 : : // Foreground colors. 30..37 are handled by a single case, likewise 90..97
368 : 20 : case m(30):/*case m(31):case m(32):case m(33):
369 : 20 : case m(34):case m(35):case m(36):case m(37):*/ a -= 30; a += 90-8; [[fallthrough]];
370 : 36 : case m(90):/*case m(91):case m(92):case m(93):
371 : 36 : case m(94):case m(95):case m(96):case m(97):*/ a -= 90-8; [[fallthrough]];
372 : 549 : case m(MODE38_5): color = 0; a = xterm256table[a & 0xFF]; [[fallthrough]]; // 38;5;n
373 : 552 : case m(MODE38_2_x2): color = (color << 8) + a; set(fgcolor, color); c = 0; break; // 38;2;#;#;n (RGB24)
374 [ # # ]: 3 : case m(MODE38_3_x2): color = (color << 8) + a; set(fgcolor, cmy2rgb(color)); c = 0; break; // 38;3;#;#;n (CMY->RGB)
375 : 2 : case m(MODE38_4_x3): color = (color << 8) + a; set(fgcolor, cmyk2rgb(color)); c = 0; break; // 38;4;#;#;#;n (CMYK->RGB)
376 : :
377 : : // Background colors. 40..47 are handled by a single case, likewise 100..107
378 : 16 : case m(40):/*case m(41):case m(42):case m(43):
379 : 16 : case m(44):case m(45):case m(46):case m(47):*/ a -= 40; a += 100-8; [[fallthrough]];
380 : 32 : case m(100):/*case m(101):case m(102):case m(103):
381 : 32 : case m(104):case m(105):case m(106):case m(107):*/ a -= 100-8; [[fallthrough]];
382 : 545 : case m(MODE48_5): color = 0; a = xterm256table[a & 0xFF]; [[fallthrough]]; // 48;5;n
383 : 548 : case m(MODE48_2_x2): color = (color << 8) + a; set(bgcolor, color); c = 0; break; // 48;2;#;#;n (RGB24)
384 [ # # ]: 3 : case m(MODE48_3_x2): color = (color << 8) + a; set(bgcolor, cmy2rgb(color)); c = 0; break; // 48;3;#;#;n (CMY->RGB)
385 : 2 : case m(MODE48_4_x3): color = (color << 8) + a; set(bgcolor, cmyk2rgb(color)); c = 0; break; // 48;4;#;#;#;n (CMYK->RGB)
386 : :
387 : : // These don't do anything, so we just ignore them and reset the mode number c.
388 : : //
389 : : //case m(MODE58_5): color = 0; a = xterm256table[a & 0xFF]; [[fallthrough]]; // 58;5;n
390 : : //case m(MODE58_4_x3)://color = (color << 8) + a; /*IGNORED*/ c = 0; break; // 58;4;#;#;#;n (TODO CMYK->RGB)
391 : : //case m(MODE58_3_x2)://color = (color << 8) + a; /*IGNORED*/ c = 0; break; // 58;3;#;#;n (TODO CMY->RGB)
392 : : //case m(MODE58_2_x2): color = (color << 8) + a; /*IGNORED*/ c = 0; break; // 58;2;#;#;n (RGB24)
393 : :
394 : 427 : default: case m(~0u): c = 0; break; // undefined
395 : : #undef set
396 : : }
397 : 5042 : };
398 : :
399 [ - + ]: 773 : if(bottom >= wnd.ysize) bottom = wnd.ysize-1;
400 [ - + ]: 773 : if(top > bottom) top = bottom;
401 : :
402 : 773 : enum States: unsigned
403 : : {
404 : : st_default,
405 : : st_esc,
406 : : st_scs, // esc ()*+-./
407 : : st_scr, // esc #
408 : : st_esc_percent, // esc %
409 : : st_csi, // esc [
410 : : st_csi_dec, // csi ?
411 : : st_csi_dec2, // csi >
412 : : st_csi_dec3, // csi =
413 : : st_csi_ex, // csi !
414 : : st_csi_quo, // csi "
415 : : st_csi_dol, // csi $
416 : : st_csi_star, // csi *
417 : : st_csi_dec_dol, // csi ? $
418 : : st_string,
419 : : st_string_str,
420 : : //
421 : : st_num_states
422 : : };
423 : 23260 : auto State = [](char32_t c, unsigned st) constexpr
424 : : {
425 : 22487 : return c*st_num_states + st;
426 : : };
427 : 2120 : auto GetParams = [&](unsigned min_params, bool change_zero_to_one)
428 : : {
429 [ + + ]: 1347 : if(p.size() < min_params) { p.resize(min_params); }
430 [ + + + + : 1606 : if(change_zero_to_one) for(auto& v: p) if(!v) v = 1;
+ + ]
431 : 1347 : state = st_default;
432 : 2120 : };
433 : :
434 [ + + ]: 23260 : for(char32_t c: s)
435 : : {
436 [ + + + + : 22487 : switch(State(c,state))
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + - + -
+ - + - +
- + - + +
+ + + + -
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + ]
437 : : {
438 : : #define CsiState(c) State(c,st_csi): case State(c,st_csi_dec2): \
439 : : case State(c,st_csi_dec): case State(c,st_csi_dec3): \
440 : : case State(c,st_string)
441 : : /* csi_quo, csi_dol, csi_dec_dol, csi_ex are excluded
442 : : * from csistate because you can't have parameters
443 : : * after this particular symbol.
444 : : */
445 : : #define AnyState(c) State(c,st_default): case State(c,st_esc): \
446 : : case State(c,st_scs): case State(c,st_csi_ex): \
447 : : case State(c,st_string_str): case State(c,st_csi_quo): \
448 : : case State(c,st_scr): case State(c,st_esc_percent): \
449 : : case State(c,st_csi_dol): case State(c,st_csi_dec_dol): \
450 : : case CsiState(c)
451 : :
452 : : // Note: These escapes are recognized even in the middle of an ANSI/VT code.
453 : 2 : case AnyState(U'\b'): { lastch=c; ClampedMoveX(wnd.cursx-1); break; }
454 : 16 : case AnyState(U'\t'): { lastch=c; ClampedMoveX(wnd.cursx + 8 - (wnd.cursx & 7)); break; }
455 : 9 : case AnyState(U'\r'): { lastch=c; ClampedMoveX(0); break; }
456 : 27 : case AnyState(U'\16'): { activeset = 1; break; }
457 : 27 : case AnyState(U'\17'): { activeset = 0; break; }
458 : : case AnyState(U'\177'): { /* del - ignore */ break; }
459 : 149 : case AnyState(U'\30'): [[fallthrough]];
460 : 149 : case AnyState(U'\32'): [[fallthrough]];
461 : 149 : case AnyState(U'\u0080'): [[fallthrough]];
462 : 149 : case AnyState(U'\u0081'): [[fallthrough]];
463 : 149 : case AnyState(U'\u0082'): [[fallthrough]];
464 : 149 : case AnyState(U'\u0083'): [[fallthrough]];
465 : 149 : case AnyState(U'\u0086'): [[fallthrough]];
466 : 149 : case AnyState(U'\u0087'): [[fallthrough]];
467 : 149 : case AnyState(U'\u0089'): [[fallthrough]];
468 : 149 : case AnyState(U'\u008A'): [[fallthrough]];
469 : 149 : case AnyState(U'\u008B'): [[fallthrough]];
470 : 149 : case AnyState(U'\u008C'): [[fallthrough]];
471 : 149 : case AnyState(U'\u0091'): [[fallthrough]];
472 : 149 : case AnyState(U'\u0092'): [[fallthrough]];
473 : 149 : case AnyState(U'\u0093'): [[fallthrough]];
474 : 149 : case AnyState(U'\u0094'): [[fallthrough]];
475 : 149 : case AnyState(U'\u0095'): [[fallthrough]];
476 : 149 : case AnyState(U'\u0099'): { Ground: scs=0; state=st_default; break; }
477 : 46 : case State(U'\33', st_string): [[fallthrough]];
478 : 46 : case State(U'\33', st_string_str): state = st_esc; break; // don't clear params
479 [ + + ]: 1446 : case State(U'\33', st_default): state = st_esc; p.clear(); break;
480 : 3 : case State(U'(', st_esc): scs = 0; state = st_scs; break; // esc (
481 : 30 : case State(U')', st_esc): scs = 1; state = st_scs; break; // esc )
482 : 3 : case State(U'*', st_esc): scs = 2; state = st_scs; break; // esc *
483 : 3 : case State(U'+', st_esc): scs = 3; state = st_scs; break; // esc +
484 : 3 : case State(U'-', st_esc): scs = 1; state = st_scs; break; // esc -
485 : 4 : case State(U'.', st_esc): scs = 2; state = st_scs; break; // esc .
486 : 9 : case State(U'/', st_esc): scs = 3; state = st_scs; break; // esc /
487 : 6 : case State(U'#', st_esc): state = st_scr; break; // esc #
488 : 1325 : case State(U'[', st_esc): state = st_csi; break; // esc [
489 : 3 : case State(U'%', st_esc): state = st_esc_percent; break; // esc %
490 : 5 : case State(U'?', st_csi): state = st_csi_dec; break; // csi ?
491 : 2 : case State(U'>', st_csi): state = st_csi_dec2; break; // csi >
492 : 2 : case State(U'=', st_csi): state = st_csi_dec3; break; // csi =
493 : 2 : case State(U'!', st_csi): state = st_csi_ex; break; // csi ! (note: after numbers)
494 : 7 : case State(U'"', st_csi): state = st_csi_quo; break; // csi " (note: after numbers)
495 : 8 : case State(U'$', st_csi): state = st_csi_dol; break; // csi $ (note: after numbers)
496 : 1 : case State(U'*', st_csi): state = st_csi_star; break; // csi * (note: after numbers)
497 : 2 : case State(U'$', st_csi_dec): state = st_csi_dec_dol; break; // csi ? $ (note: after numbers)
498 : 0 : case AnyState(U'\7'):
499 : 0 : {
500 [ # # ]: 0 : if(state == st_string || state == st_string_str) // Treat as ST (string termination)
501 : : {
502 : 44 : case AnyState(U'\u009C'): [[fallthrough]];
503 : 44 : case State(U'\\', st_esc): // String termination
504 [ + + + ]: 44 : switch(scs)
505 : : {
506 : 25 : case 4: // Parse DCS
507 : 25 : GetParams(1, false);
508 [ + - ]: 25 : if(string.empty()) break;
509 [ + + + ]: 25 : switch(string[0])
510 : : {
511 : 9 : case U'$':
512 [ + + - + ]: 9 : if(string.size() <= 1 || string[1] != 'q')
513 : 2 : EchoBack(U"\u0018");
514 : : else
515 : : {
516 : 8 : string.erase(0,2);
517 [ + + ]: 8 : std::string response;
518 : 8 : char Buf[40];
519 [ + + + - ]: 8 : if(string == U"r") response.assign(Buf, std::sprintf(Buf, "%zu;%zu", top,bottom));
520 [ + + ]: 7 : else if(string == U"m") response.assign(Buf, std::sprintf(Buf, "38;2;%u;%u;%u;48;2;%u;%u;%u",
521 : 1 : (wnd.blank.fgcolor >> 16)&0xFF,
522 : 1 : (wnd.blank.fgcolor >> 8)&0xFF,
523 : 1 : (wnd.blank.fgcolor >> 0)&0xFF,
524 : 1 : (wnd.blank.bgcolor >> 16)&0xFF,
525 : 1 : (wnd.blank.bgcolor >> 8)&0xFF,
526 [ + - ]: 1 : (wnd.blank.bgcolor >> 0)&0xFF));
527 [ + + - + : 6 : else if(string == U"t") response = std::to_string(std::max(25u, unsigned(wnd.ysize))-1);
+ - ]
528 [ + + + - ]: 5 : else if(string == U" q") response = "1"; //cursor type
529 [ + + + - ]: 4 : else if(string == U"\"q") response = wnd.blank.protect ? "1" : "0"; //protected mode
530 [ + + + - ]: 3 : else if(string == U"\"p") response = "64;1"; //vt index, 8-bit controls disabled
531 [ + + + - ]: 2 : else if(string == U"$|") response = std::to_string(wnd.xsize); //window width
532 [ + - + - ]: 1 : else if(string == U"*|") response.assign(Buf, std::sprintf(Buf, "%zu", wnd.ysize));
533 [ # # # # ]: 0 : else {fprintf(stderr, "Unrecognized DCS<%s>\n", ToUTF8(string).c_str());
534 [ # # ]: 0 : EchoBack(U"\033$P0\033\\");}
535 [ + - ]: 8 : if(!response.empty())
536 [ + - + - : 16 : EchoBack(U"\033$P1$r" + FromUTF8(response) + string + U"\033\\");
+ - + - +
- ]
537 : 8 : }
538 : : break;
539 : 15 : case U'q': /* Sixel graphics */
540 : 15 : {
541 : 15 : [[maybe_unused]] unsigned pad=1, pan=2, ph=1, pv=1, rep=1, x=0, y=0, color=3;
542 : 15 : [[maybe_unused]] bool trans=false;
543 : : // Parse pre-q parameters
544 [ + - + - ]: 30 : if(p.size()>=1) pad = std::array<int,10>{2,2,5,4,4,3,3,2,2,1}[std::min(9u,p[0])];
545 : 15 : if(p.size()>=2) { trans=p[1]; }
546 [ + + ]: 15 : if(p.size()>=2) { pad = std::max(1u,pad*p[2]/10); pan = std::max(1u,pan*p[2]/10); }
547 : 15 : if(p.size()>=7) { pad=p[3]; pan=p[4]; ph=p[5]; pv=p[6]; }
548 : 31 : for(std::size_t b=0,a=0; a<string.size(); a = ++b)
549 : : {
550 : : // Parse parameters that follow the command
551 [ + + + + ]: 38 : for(p.clear(); (b+1) < string.size(); ++b)
552 [ + + ]: 7 : if(string[b+1] >= U';') p.emplace_back();
553 [ + + + - ]: 5 : else if(string[b+1] >= U'0' && string[b+1] <= U'9')
554 : : {
555 [ + - ]: 4 : if(p.empty()) p.emplace_back();
556 : 4 : p.back() = p.back() * 10u + (string[b+1]-U'0');
557 : : }
558 : : else break;
559 [ - - - + : 16 : switch(string[a])
+ ]
560 : : {
561 : 0 : case '\u0034': GetParams(4,false);
562 : 0 : pad = std::max(1u,p[0]);
563 : 0 : pan = std::max(1u,p[1]);
564 : 0 : if(p[2]) ph = p[2];
565 : 0 : if(p[3]) pv = p[3];
566 : : break;
567 : 0 : case '\u0035': switch(GetParams(5,false); p[1])
568 : : {
569 : 31 : case 0: color=p[0]; break; // choose color p[0]
570 : : case 1: break; // change color p[0] into hls: p[2..4]
571 : : case 2: break; // change color p[0] into rgb: p[2..4]
572 : : }
573 : : break;
574 [ + + ]: 31 : case '\u002D': y+=6; [[fallthrough]]; // TODO: scroll if necessary
575 : : case '\u0036': x=0; break;
576 [ # # ]: 0 : case '\u0033': GetParams(1,false); rep = std::max(1u, p[0]); break;
577 : 15 : default:
578 [ + - + - ]: 15 : if(string[a] >= '\u003F' && string[a] <= '\u007E')
579 : : {
580 : 30 : unsigned bitmask = string[a] - '\u003F';
581 [ + + ]: 30 : for(; rep-- > 0; ++x)
582 [ + + ]: 105 : for(unsigned py=0; py<6; ++py)
583 : : if(bitmask & (1u << py))
584 : : {
585 : : }
586 : : rep = 1;
587 : : }
588 : : }
589 : : }
590 : : break;
591 : : }
592 : : case U'p': /* ReGIS graphics */ break;
593 : : }
594 : : break;
595 : 13 : case 5: // Parse OSC
596 : 13 : {
597 : 13 : GetParams(2,false);
598 [ + + + + : 13 : bool dfl = p[0] >= 100;
+ + + + +
+ ]
599 : 21 : auto DoColor = [&](const char* params,unsigned idx, unsigned& color, unsigned dflcolor)
600 : : {
601 : 8 : char Buf[32];
602 [ - + ]: 8 : if(string == U"?")
603 : : {
604 : 0 : char st[3] = {'\33', char(c), '\0'};
605 [ # # ]: 0 : if(c != '\\') { st[0] = char(c); st[1] = '\0'; }
606 : : // ^ Echo back using the same ST code
607 [ # # ]: 0 : EchoBack(FromUTF8({Buf, 0u+std::sprintf(Buf, "\33]%s%u;#%06X%s", params,idx, color, st)}));
608 : : }
609 [ - + ]: 8 : else if(dfl) color = dflcolor;
610 : 8 : else color = ParseColorName(string);
611 : 8 : };
612 [ + + + + : 13 : switch(unsigned p100 = p[0] % 100)
+ + + + +
+ ]
613 : : {
614 : 3 : case 0:
615 : 3 : case 1:
616 : 3 : case 2:
617 : 3 : {
618 : 3 : std::string utstring = ToUTF8(string);
619 [ + + ]: 3 : if(!(p100 & 2)) // 0 and 1: set icon name
620 [ + - ]: 2 : ui.SetIconName(utstring);
621 [ + + ]: 3 : if(!(p100 & 1)) // 0 and 2: set window title
622 [ + - ]: 2 : ui.SetWindowTitle(utstring);
623 : 3 : break;
624 : 3 : }
625 : : case 6: break; // set or clear color{BD,UL,BL,RV,IT} mode
626 : 1 : case 5: p[1] += 256; [[fallthrough]];
627 : 2 : case 4: {unsigned v = xterm256table[p[1]&0xFF];
628 : 2 : DoColor("4;",p[1]&0xFF, v, v);
629 : 2 : break;} // change color [p[1]] to string, or if ?, report the string
630 : 1 : case 10: DoColor("",p[0], wnd.blank.fgcolor, Cell{}.fgcolor); break; // Change text foreground color
631 : 1 : case 11: DoColor("",p[0], wnd.blank.bgcolor, Cell{}.bgcolor); break; // Change text background color
632 : 1 : case 12: DoColor("",p[0], wnd.cursorcolor, 0xFFFFFF); break; // Change text cursor color
633 : 1 : case 13: DoColor("",p[0], wnd.mousecolor1, 0xFFFFFF); break; // Change mouse foreground color
634 : 1 : case 14: DoColor("",p[0], wnd.mousecolor2, 0xFFFFFF); break; // Change mouse background color
635 : 1 : case 17: DoColor("",p[0], wnd.mouseselectcolor, 0xFFFFFF); break; // Change mouse select-text background color
636 : : case 50: break; // set font
637 : : }
638 : 13 : break;
639 : : }
640 : : default:
641 : : break;
642 : : }
643 : 44 : goto Ground;
644 : : }
645 : 0 : lastch=c; ui.BeepOn(); break;
646 : : }
647 [ - - ]: 26 : case AnyState(U'\u0090'): p.clear(); [[fallthrough]];
648 : 26 : case State(U'P', st_esc): state = st_string; scs = 4; string.clear(); break; // DCS
649 [ - - ]: 14 : case AnyState(U'\u009D'): p.clear(); [[fallthrough]];
650 : 14 : case State(U']', st_esc): state = st_string; scs = 5; string.clear(); break; // OSC
651 [ - - ]: 2 : case AnyState(U'\u009E'): p.clear(); [[fallthrough]];
652 : 2 : case State(U'^', st_esc): state = st_string; scs = 6; string.clear(); break; // PM
653 [ - - ]: 2 : case AnyState(U'\u009F'): p.clear(); [[fallthrough]];
654 : 2 : case State(U'_', st_esc): state = st_string; scs = 7; string.clear(); break; // APC
655 [ - - ]: 2 : case AnyState(U'\u0098'): p.clear(); [[fallthrough]];
656 : 2 : case State(U'X', st_esc): state = st_string; scs = 8; string.clear(); break; // SOS
657 : :
658 : 8506 : case CsiState(U'0'): case CsiState(U'1'):
659 : 8506 : case CsiState(U'2'): case CsiState(U'3'):
660 : 8506 : case CsiState(U'4'): case CsiState(U'5'):
661 : 8506 : case CsiState(U'6'): case CsiState(U'7'):
662 : 8506 : case CsiState(U'8'): case CsiState(U'9'):
663 [ + + ]: 8506 : if(p.empty()) p.emplace_back();
664 : 8506 : p.back() = p.back() * 10u + (c - U'0');
665 : 8506 : break;
666 : 3295 : case CsiState(U':'): case CsiState(U';'):
667 : 3295 : p.emplace_back();
668 : : break;
669 : :
670 : : //case AnyState(U'\u0085'): // CASE_NEL
671 : 3 : case State(U'E', st_esc): // esc E = CR+LF
672 : 3 : ClampedMoveX(0);
673 : 3 : state = st_default;
674 : 16 : [[fallthrough]];
675 : 16 : case AnyState(10):
676 : 16 : case AnyState(11):
677 : 16 : case AnyState(12):
678 : : //case AnyState(U'\u0084'):
679 : 16 : case State(U'D', st_esc): // esc D = CASE_IND
680 : 16 : lastch = U'\n';
681 : : /* Within window: move cursor down; scroll the window up if at bottom */
682 : 16 : Lf();
683 : 16 : goto Ground;
684 : :
685 : : //case AnyState(U'\u008D'):
686 : 1 : case State(U'M', st_esc): // esc M = CASE_RI
687 : : /* Within window: move cursor up; scroll the window down if at top */
688 [ - + ]: 1 : if(wnd.cursy == top)
689 : 0 : YScrollDown(top, bottom, 1);
690 : : else
691 : 1 : ClampedMoveY(wnd.cursy-1);
692 : 1 : goto Ground;
693 : 0 : case State(U'q', st_csi_quo): // DECSCA: if param0=1, do CASE_SPA; if 0 or 2, do CASE_EPA. Otherwise ignore.
694 : 0 : {
695 : 0 : GetParams(1,false);
696 [ # # ]: 0 : if(p[0]==1)
697 : : {
698 : : //case AnyState(U'\u0096'):
699 : 1 : case State(U'V', st_esc): // esc V = SPA: start protected area
700 : 1 : wnd.blank.protect = true;
701 : : }
702 [ # # # # ]: 0 : else if(p[0]==0 || p[0]==2)
703 : : {
704 : : //case AnyState(U'\u0097'):
705 : 1 : case State(U'W', st_esc): // esc W = EPA: end protected area
706 : 1 : wnd.blank.protect = false;
707 : : }
708 : 2 : goto Ground;
709 : : }
710 : : /*//case AnyState(U'\u0088'): [[fallthrough]];
711 : : case State(U'H', st_esc): // esc H = CASE_HTS: horizontal tab set
712 : : goto Ground;
713 : : //case AnyState(U'\u0090'): [[fallthrough]];
714 : : case State(U'P', st_esc): // esc P = CASE_DCS (starts string)
715 : : goto Ground;
716 : : //case AnyState(U'\u009D'): [[fallthrough]];
717 : : case State(U']', st_esc): // esc ] = CASE_OSC (starts string)
718 : : goto Ground;
719 : : //case AnyState(U'\u009E'): [[fallthrough]];
720 : : case State(U'^', st_esc): // esc ^ = CASE_PM (starts string)
721 : : goto Ground;
722 : : //case AnyState(U'\u009F'): [[fallthrough]];
723 : : case State(U'_', st_esc): // esc _ = CASE_APC (starts string)
724 : : goto Ground;
725 : : //case AnyState(U'\u0098'): [[fallthrough]];
726 : : case State(U'X', st_esc): // esc X = CASE_SOS: start of string
727 : : goto Ground;
728 : : //case AnyState(U'\u009C'): [[fallthrough]];
729 : : case State(U'\\', st_esc):// esc \\ = CASE_ST: end of string
730 : : goto Ground;*/
731 : :
732 : 2 : case State(U'c', st_esc): ResetAttr(); Reset(); goto Ground; // esc c (RI - full reset)
733 : 1 : case State(U'p', st_csi_ex): ResetAttr(); Reset(false); goto Ground; // CSI ! p (DECSTR - CSI reset)
734 : :
735 : 3 : case State(U'7', st_esc): [[fallthrough]]; // esc 7 (DECSC), csi s (ANSI_SC)
736 : 3 : case State(U's', st_csi): SaveCur(); goto Ground;
737 : 3 : case State(U'8', st_esc): [[fallthrough]]; // esc 8 (DECRC), csi u (ANSI_RC)
738 : 3 : case State(U'u', st_csi): RestoreCur(); goto Ground;
739 : 2 : case State(U'0', st_scs): gset[scs&3] = 1; goto Ground; // DEC graphics (TODO)
740 : 2 : case State(U'1', st_scs): gset[scs&3] = 0; goto Ground; // DEC alt chars?
741 : 2 : case State(U'2', st_scs): gset[scs&3] = 0; goto Ground; // DEC alt gfx?
742 : 2 : case State(U'`', st_scs): gset[scs&3] = 0; goto Ground; // nor/dan?
743 : 2 : case State(U'4', st_scs): gset[scs&3] = 0; goto Ground; // dut?
744 : 2 : case State(U'5', st_scs): gset[scs&3] = 0; goto Ground; // fin?
745 : 2 : case State(U'6', st_scs): gset[scs&3] = 0; goto Ground; // nor/dan3?
746 : 2 : case State(U'7', st_scs): gset[scs&3] = 0; goto Ground; // swe?
747 : 2 : case State(U'9', st_scs): gset[scs&3] = 0; goto Ground; // fre/can2?
748 : 2 : case State(U'A', st_scs): gset[scs&3] = 0; goto Ground; // british?
749 : 2 : case State(U'B', st_scs): gset[scs&3] = 0; goto Ground; // ASCII (TODO)
750 : 2 : case State(U'C', st_scs): gset[scs&3] = 0; goto Ground; // fin2?
751 : 2 : case State(U'E', st_scs): gset[scs&3] = 0; goto Ground; // nor/dan2?
752 : 2 : case State(U'f', st_scs): gset[scs&3] = 0; goto Ground; // fre2?
753 : 2 : case State(U'F', st_scs): gset[scs&3] = 0; goto Ground; // iso greek supp?
754 : 2 : case State(U'H', st_scs): gset[scs&3] = 0; goto Ground; // swe2? iso hebrew supp?
755 : 2 : case State(U'K', st_scs): gset[scs&3] = 0; goto Ground; // ger?
756 : 2 : case State(U'L', st_scs): gset[scs&3] = 0; goto Ground; // iso cyr?
757 : 2 : case State(U'M', st_scs): gset[scs&3] = 0; goto Ground; // iso5 supp?
758 : 2 : case State(U'Q', st_scs): gset[scs&3] = 0; goto Ground; // fre/can?
759 : 2 : case State(U'R', st_scs): gset[scs&3] = 0; goto Ground; // fre?
760 : 2 : case State(U'<', st_scs): gset[scs&3] = 0; goto Ground; // DEC supp?
761 : 2 : case State(U'=', st_scs): gset[scs&3] = 0; goto Ground; // swiss?
762 : 2 : case State(U'>', st_scs): gset[scs&3] = 0; goto Ground; // DEC technical
763 : 2 : case State(U'U', st_scs): gset[scs&3] = 0; goto Ground; // linux pc?
764 : 2 : case State(U'Y', st_scs): gset[scs&3] = 0; goto Ground; // ita?
765 : 2 : case State(U'Z', st_scs): gset[scs&3] = 0; goto Ground; // spa?
766 : 1 : case State(U'3', st_scr): wnd.LineSetRenderSize(2); goto Ground; // DECDHL top
767 : 1 : case State(U'4', st_scr): wnd.LineSetRenderSize(3); goto Ground; // DECDHL bottom
768 : 1 : case State(U'5', st_scr): wnd.LineSetRenderSize(0); goto Ground; // DECSWL
769 : 1 : case State(U'6', st_scr): wnd.LineSetRenderSize(1); goto Ground; // DECDWL
770 : 1 : case State(U'8', st_scr): // clear screen with 'E' // esc # 8
771 : 1 : wnd.blank.ch = U'E';
772 : 1 : wnd.FillBox(0,0, wnd.xsize,wnd.ysize);
773 : 1 : wnd.blank.ch = U' ';
774 : 1 : wnd.cursx = wnd.cursy = 0;
775 : 1 : goto Ground;
776 : 1 : case State(U'@', st_esc_percent): utfmode = 0; goto Ground; // esc % @
777 : 2 : case State(U'G', st_esc_percent): [[fallthrough]]; // esc % G
778 : 2 : case State(U'8', st_esc_percent): utfmode = 1; goto Ground; // esc % 8
779 : 1 : case State(U'g', st_csi): /* TODO: set tab stops */ goto Ground;
780 : 1 : case State(U'q', st_csi): /* TODO: set leds */ goto Ground;
781 : 2 : case State(U'G', st_csi): [[fallthrough]];
782 : 2 : case State(U'`', st_csi): { GetParams(1,true); ClampedMoveX(p[0]-1); break; } // absolute hpos
783 : 1 : case State(U'd', st_csi): { GetParams(1,true); ClampedMoveY(p[0]-1, false); break; } // absolute vpos
784 : 2 : case State(U'F', st_csi): ClampedMoveX(0); [[fallthrough]];
785 : 7 : case State(U'A', st_csi): { GetParams(1,true); ClampedMoveY(wnd.cursy-p[0]); break; }
786 : 2 : case State(U'E', st_csi): ClampedMoveX(0); [[fallthrough]];
787 : 9 : case State(U'e', st_csi): [[fallthrough]];
788 : 9 : case State(U'B', st_csi): { GetParams(1,true); ClampedMoveY(wnd.cursy+p[0]); break; }
789 : 109 : case State(U'a', st_csi): [[fallthrough]];
790 : 109 : case State(U'C', st_csi): { GetParams(1,true); ClampedMoveX(wnd.cursx+p[0]); break; }
791 : 5 : case State(U'D', st_csi): { GetParams(1,true); ClampedMoveX(wnd.cursx-p[0]); break; }
792 : 45 : case State(U'H', st_csi): [[fallthrough]];
793 : 45 : case State(U'f', st_csi): { GetParams(2,true); ClampedMove(p[1]-1, p[0]-1, false); break; }
794 : 6 : case State(U'J', st_csi):
795 : 6 : case State(U'J', st_csi_quo):
796 : 6 : GetParams(1,false);
797 [ + + + - ]: 6 : switch(p[0])
798 : : {
799 : 2 : case 0: // erase from cursor to end of display
800 [ + - ]: 2 : if(wnd.cursy < wnd.ysize-1)
801 : 2 : wnd.FillBox(0,wnd.cursy+1, wnd.xsize, wnd.ysize-wnd.cursy-1, wnd.blank);
802 : 2 : goto clreol;
803 : 2 : case 1: // erase from start to cursor
804 [ - + ]: 2 : if(wnd.cursy > 0) wnd.FillBox(0,0, wnd.xsize,wnd.cursy, wnd.blank);
805 : 2 : goto clrbol;
806 : 2 : case 2: // erase whole display
807 : 2 : wnd.FillBox(0,0, wnd.xsize,wnd.ysize, wnd.blank);
808 : : break;
809 : : }
810 : : break;
811 : 6 : case State(U'K', st_csi):
812 : 6 : case State(U'K', st_csi_quo):
813 : 6 : GetParams(1,false);
814 : : // 0: erase from cursor to end of line
815 : : // 1: erase from start of line to cursor
816 : : // 2: erase whole line
817 [ + + + - ]: 6 : switch(p[0])
818 : : {
819 : 4 : case 0: clreol: wnd.FillBox(wnd.cursx,wnd.cursy, wnd.xsize-wnd.cursx, 1, wnd.blank); break;
820 : 4 : case 1: clrbol: wnd.FillBox(0, wnd.cursy, wnd.cursx+1, 1, wnd.blank); break;
821 : 2 : case 2: wnd.FillBox(0, wnd.cursy, wnd.xsize, 1, wnd.blank); break;
822 : : }
823 : : break;
824 : 1 : case State(U'M', st_csi):
825 : 1 : GetParams(1,true);
826 : 1 : YScrollUp(wnd.cursy, bottom, p[0]);
827 : : break;
828 : 1 : case State(U'L', st_csi):
829 : 1 : GetParams(1,true);
830 : : // scroll the rest of window c lines down,
831 : : // including where cursor is. Don't move cursor.
832 : 1 : YScrollDown(wnd.cursy, bottom, p[0]);
833 : : break;
834 : 1 : case State(U'S', st_csi): // xterm version?
835 : 1 : GetParams(1,true);
836 : 1 : YScrollUp(top, bottom, p[0]);
837 : : break;
838 : 1 : case State(U'T', st_csi): // csi T, track mouse
839 [ + - - + : 1 : if(p.size() > 1 || p.empty() || p[0]==0)
- - ]
840 : : {
841 : : // mouse track
842 : 1 : goto Ground;
843 : : }
844 : 1 : [[fallthrough]];
845 : 1 : case State(U'^', st_csi): // csi ^ , scroll down
846 : 1 : GetParams(1,true);
847 : : // Reverse scrolling by N lines
848 : : // scroll the entire of window c lines down. Don't move cursor.
849 : 1 : YScrollDown(top, bottom, p[0]);
850 : : break;
851 : 1 : case State(U'P', st_csi):
852 [ - + ]: 1 : GetParams(1,true); c = std::min(p[0], unsigned(wnd.xsize-wnd.cursx));
853 : : // insert c black holes at cursor (eat c characters
854 : : // and scroll line horizontally to left)
855 [ + - ]: 1 : if(c)
856 : : {
857 : 1 : unsigned remain = wnd.xsize - (wnd.cursx+c);
858 : 1 : wnd.CopyText(wnd.cursx,wnd.cursy, wnd.xsize-remain,wnd.cursy, remain,1);
859 : 1 : wnd.FillBox(wnd.xsize-c,wnd.cursy, c,1);
860 : : }
861 : : break;
862 : 1 : case State(U'X', st_csi): // (ECH)
863 : 1 : GetParams(1,true);
864 : : // write c spaces at cursor (overwrite)
865 [ - + ]: 1 : wnd.FillBox(wnd.cursx,wnd.cursy, std::min(std::size_t(p[0]), wnd.xsize-wnd.cursx), 1);
866 : 1 : break;
867 : 1 : case State(U'@', st_csi):
868 [ - + ]: 1 : GetParams(1,true); c = std::min(p[0], unsigned(wnd.xsize-wnd.cursx));
869 : : // insert c spaces at cursor
870 [ + - ]: 1 : if(c)
871 : : {
872 : 1 : unsigned remain = wnd.xsize - (wnd.cursx+c);
873 : 1 : wnd.CopyText(wnd.cursx+c,wnd.cursy, wnd.cursx,wnd.cursy, remain,1);
874 : 1 : wnd.FillBox(wnd.cursx,wnd.cursy, c,1);
875 : : }
876 : : break;
877 : 1 : case State(U'r', st_csi): // CSI r
878 : 1 : GetParams(2,false);
879 [ + - ]: 1 : if(!p[0]) p[0]=1;
880 [ + - ]: 1 : if(!p[1]) p[1]=wnd.ysize;
881 [ + - + - ]: 1 : if(p[0] < p[1] && p[1] <= wnd.ysize)
882 : : {
883 : 1 : top = p[0]-1; bottom = p[1]-1;
884 : : //fprintf(stderr, "Creating a window with top=%zu, bottom=%zu\n", top,bottom);
885 : 1 : ClampedMove(0, top, false);
886 : : }
887 : : break;
888 : 3 : case State(U'n', st_csi):
889 : 3 : GetParams(1,false);
890 [ + + + ]: 3 : switch(p[0])
891 : : {
892 : 1 : char Buf[32];
893 : 2 : case 5: EchoBack(U"\33[0n"); break;
894 [ + - ]: 2 : case 6: EchoBack(FromUTF8(std::string_view{Buf, (std::size_t)std::sprintf(Buf, "\33[%zu;%zuR", wnd.cursy+1, wnd.cursx+1)})); break;
895 : : }
896 : : break;
897 : 1 : case State(U'c', st_csi_dec3): // csi = 0 c, Tertiary device attributes (printer?)
898 : 1 : GetParams(1,false); // Tertiary device attributes (printer?)
899 : : // Example response: <ESC> P ! | 0 <ST>
900 [ + - ]: 2 : if(!p[0]) EchoBack(U"\33P!|00000000\x9C"); // Note: DCS response
901 : : break;
902 : 1 : case State(U'c', st_csi_dec2): // csi > 0 c, Secondary device attributes (terminal)
903 : 1 : GetParams(1,false);
904 : : // Example response: ^[[>41;344;0c (middle=firmware version)
905 [ + - ]: 2 : if(!p[0]) EchoBack(U"\33[>1;1;0c");
906 : : break;
907 : 2 : case State(U'c', st_csi): // csi 0 c // Primary device attributes (host computer)
908 : 2 : GetParams(1,false);
909 [ + - ]: 2 : if(!p[0])
910 : : {
911 : : //case AnyState(U'\u009A'): [[fallthrough]];
912 : 3 : case State(U'Z', st_esc): // esc Z = CASE_DECID
913 : 6 : EchoBack(U"\33[?65;1;6;8;15;22c");
914 : : }
915 : : // Example response: ^[[?64;1;2;6;9;15;18;21;22c
916 : : // 1=132 columns, 2=printer port, 4=sixel extension
917 : : // 6=selective erase, 7=DRCS, 8=user-defines keys
918 : : // 9=national replacement charsets, 12=SCS extension
919 : : // 15=technical charset, 18=windowing capability, 21=horiz scrolling,
920 : : // 22=ansi color/vt525, 29=ansi text locator
921 : : // 23=greek ext, 24=turkish ext, 42=latin2 cset, 44=pcterm,
922 : : // 45=softkeymap, 46=ascii emulation
923 : : // 62..69 = VT level (62=VT200, 63=VT300, 64=VT400)
924 : : break;
925 : 3 : case State(U'h', st_csi_dec): // csi ? h, misc modes on
926 : 3 : case State(U'l', st_csi_dec): // csi ? l, misc modes off
927 : 3 : case State(U'p', st_csi_dec_dol): // csi ? $p, query
928 : 3 : {
929 : 3 : bool set = c == U'h', query = c == U'p';
930 : 3 : char Buf[32];
931 : : // 1=CKM, 2=ANM, 3=COLM, 4=SCLM, 5=SCNM, 6=OM, 7=AWM, 8=ARM,
932 : : // 18=PFF, 19=PEX, 25=TCEM, 40=132COLS, 42=NRCM,
933 : : // 44=MARGINBELL, ...
934 : : // 6 puts cursor at (0,0) both set,clear
935 : : // 25 enables/disables cursor visibility
936 : : // 40 enables/disables 80/132 mode (note: if enabled, RESET changes to one of these)
937 : : // 3 sets width at 132(enable), 80(disable) if "40" is enabled
938 : : // 5 = screenwide inverse color
939 : 3 : GetParams(0, false);
940 [ + + ]: 6 : for(auto a: p)
941 : : {
942 : 3 : unsigned value = 0; // unsupported
943 [ + + + - ]: 3 : switch(a)
944 : : {
945 [ + - ]: 1 : case 6: if(query) value = 2; else ClampedMove(0, top, false); break;
946 [ + - - + ]: 1 : case 25: if(query) value = wnd.cursorvis?1:2; else wnd.cursorvis = set; break;
947 [ - + - - ]: 1 : case 5: if(query) value = wnd.inverse?1:2; else wnd.inverse = set; break;
948 : : }
949 [ + + ]: 3 : if(query)
950 [ + - ]: 2 : EchoBack(FromUTF8(std::string_view{Buf, (std::size_t)
951 [ + - ]: 1 : std::sprintf(Buf, "\33[?%u;%u$y", a, value)}));
952 : : }
953 : 3 : break;
954 : : }
955 : 4 : case State(U'h', st_csi): // csi h, ansi modes on
956 : 4 : case State(U'l', st_csi): // csi l, ansi modes off
957 : 4 : case State(U'p', st_csi_dol): // csi $p, query
958 : 4 : {
959 : 4 : bool /*set = c == U'h',*/ query = c == U'p';
960 : 4 : char Buf[32];
961 : : // 2 = keyboard locked
962 : : // 4 = insert mode
963 : : // 12 = no local echo
964 : : // 20 = auto linefeed
965 : 4 : GetParams(0, false);
966 [ + + ]: 8 : for(auto a: p)
967 : : {
968 : 4 : unsigned value = 0; // unsupported
969 [ + + + + : 4 : switch(a)
- ]
970 : : {
971 : 1 : case 2: value = 4; break; // permanently unset
972 : 1 : case 4: value = 4; break; // permanently unset
973 : 1 : case 12: value = 4; break; // permanently unset
974 : 1 : case 20: value = 4; break; // permanently unset
975 : : }
976 [ + + ]: 4 : if(query)
977 [ + - ]: 4 : EchoBack(FromUTF8(std::string_view{Buf, (std::size_t)
978 : 2 : std::sprintf(Buf, "\33[%u;%u$y", a, value)}));
979 : : }
980 : 4 : break;
981 : : }
982 : 1 : case State(U'v', st_csi_dol): // csi $v (DECCRA): copy rectangular area
983 : 1 : {
984 : 1 : GetParams(7,true);
985 [ - + ]: 1 : unsigned pts=p[0], pls=p[1], pbs=p[2], prs=p[3], ptd=p[5], pld=p[6];
986 : : // Ignores [4] = source page number, [7] = target page number.
987 : : // Note: Xterm parses params from right to left, meaning that
988 : : // for xterm, our [3] is actually [n-5]
989 [ - + ]: 1 : if(pbs > wnd.ysize) pbs = wnd.ysize;
990 [ - + ]: 1 : if(prs > wnd.xsize) prs = wnd.xsize;
991 [ - + ]: 1 : if(ptd > wnd.ysize) ptd = wnd.ysize;
992 [ - + ]: 1 : if(pld > wnd.xsize) pld = wnd.ysize;
993 [ + - ]: 1 : if(pts <= pbs && pls <= prs)
994 : : {
995 : 1 : unsigned width = prs-pls+1, height = pbs-pts+1;
996 : 1 : --pld;--ptd;--pls;--pts;
997 [ - + ]: 1 : if(pld+width > wnd.xsize) width = wnd.xsize-pld;
998 [ - + ]: 1 : if(ptd+height > wnd.ysize) height = wnd.ysize-ptd;
999 [ + - ]: 1 : if(width && height)
1000 : 1 : wnd.CopyText(pld,ptd, pls,pts, width,height);
1001 : : }
1002 : : break;
1003 : : }
1004 : 1 : case State(U'z', st_csi_dol): // csi $z: fill rectangular area with space
1005 : 1 : { // but don't touch attributes.
1006 : 1 : GetParams(4,true);
1007 [ - + ]: 1 : unsigned pts=p[0], pls=p[1], pbs=p[2], prs=p[3];
1008 [ - + ]: 1 : if(pbs > wnd.ysize) pbs = wnd.ysize;
1009 [ - + ]: 1 : if(prs > wnd.xsize) prs = wnd.xsize;
1010 [ + + ]: 2 : for(unsigned y=pts; y<=pbs; ++y)
1011 [ + + ]: 2 : for(unsigned x=pls; x<=prs; ++x)
1012 : 1 : wnd.PutCh_KeepAttr(x-1, y-1, U' ', false);
1013 : : break;
1014 : : }
1015 : 1 : case State(U'x', st_csi_dol): // csi $x: fill rectangular area with given char
1016 : 1 : {
1017 : 1 : GetParams(5,true);
1018 [ - + ]: 1 : unsigned ch=p[0], pts=p[1], pls=p[2], pbs=p[3], prs=p[4];
1019 [ - + ]: 1 : if(pbs > wnd.ysize) pbs = wnd.ysize;
1020 [ - + ]: 1 : if(prs > wnd.xsize) prs = wnd.xsize;
1021 : 1 : bool dbl = isdouble(ch);
1022 [ + + ]: 2 : for(unsigned y=pts; y<=pbs; ++y)
1023 [ + + ]: 2 : for(unsigned x=pls; x<=prs; ++x)
1024 : 1 : wnd.PutCh(x-1, y-1, ch, dbl);
1025 : : break;
1026 : : }
1027 : 2 : case State(U'r', st_csi_dol): // csi $r: DECCARA: change attributes in rectangular area
1028 : 2 : case State(U't', st_csi_dol): // csi $t: DECRARA: toggle attributes in rectangular area
1029 : 2 : {
1030 : 2 : bool toggle = (c == U't');
1031 : 2 : GetParams(5, true); // Make sure there is at least 1 SGR param
1032 [ - + ]: 2 : unsigned pts=p[0], pls=p[1], pbs=p[2], prs=p[3];
1033 [ - + ]: 2 : if(pbs > wnd.ysize) pbs = wnd.ysize;
1034 [ - + ]: 2 : if(prs > wnd.xsize) prs = wnd.xsize;
1035 : 2 : --pts;--pbs;--pls;--prs;
1036 [ + + ]: 4 : for(unsigned y=pts; y<=pbs; ++y)
1037 [ + + ]: 4 : for(unsigned x=pls; x<=prs; ++x)
1038 : : {
1039 : 2 : auto& cell = wnd.cells[y*wnd.xsize+x];
1040 : 2 : auto temp = cell;
1041 : 2 : c=0;
1042 [ + + ]: 4 : for(auto ai = std::next(p.cbegin(), 4); ai != p.end(); ++ai)
1043 : 2 : ProcessSGR(c, *ai,
1044 [ # # ]: 0 : [&]() { if(!toggle) temp = Cell{}; },
1045 : 2 : [&](auto&& setter, auto&& getter, auto newvalue)
1046 : : {
1047 [ + + - - : 2 : if(toggle)
- - - - -
- - - - -
- - - - -
- - - - -
- - - - -
- - - - -
- - - - -
- - - - -
- - - - -
- - - ]
1048 : : {
1049 : : // Note: toggling using a SGR command
1050 : : // that clears the attribute does nothing,
1051 : : // because value XOR 0 is value.
1052 : : // This matches what XTerm does.
1053 : 1 : newvalue ^= getter(temp);
1054 : : }
1055 [ - - - - : 2 : setter(temp, newvalue);
- - - - ]
1056 : : }
1057 : : );
1058 : 2 : temp.ch = cell.ch;
1059 [ + - ]: 2 : if(temp != cell)
1060 : : {
1061 : 2 : cell = temp;
1062 : 2 : cell.dirty = true;
1063 : : }
1064 : : }
1065 : 2 : break;
1066 : : }
1067 : 1 : case State(U'b', st_csi):
1068 : 1 : {
1069 : 1 : GetParams(1,true);
1070 : : // Repeat last printed character n times
1071 : 1 : bool dbl = isdouble(lastch);
1072 [ - + + + ]: 6 : for(unsigned m = std::min(p[0], unsigned(wnd.xsize*wnd.ysize)), c=0; c<m; ++c)
1073 : : {
1074 : 5 : PutC(lastch, dbl);
1075 : : }
1076 : : break;
1077 : : }
1078 : 1091 : case State(U'm', st_csi): // csi m (SGR)
1079 : 1091 : {
1080 : 1091 : GetParams(1, false); // Make sure there is at least 1 param
1081 : 1091 : c=0;
1082 [ + + ]: 5358 : for(auto a: p)
1083 : 4267 : ProcessSGR(c,a,
1084 : 2 : [&]() { ResetAttr(); },
1085 : 1199 : [&](auto&& setter, auto&& /*getter*/, auto newvalue)
1086 : 1198 : { setter(wnd.blank, newvalue); }
1087 : : );
1088 : 1091 : break;
1089 : : }
1090 : :
1091 : 6200 : default:
1092 [ + + ]: 6200 : if(state == st_string) state = st_string_str;
1093 [ + + ]: 6200 : if(state == st_string_str)
1094 : : {
1095 : 22592 : string += c;
1096 : : break;
1097 : : }
1098 [ + + ]: 6095 : if(state != st_default) goto Ground;
1099 : 6084 : PutC(lastch = c, isdouble(c));
1100 : : break;
1101 : : }
1102 : : }
1103 : : #undef AnyState
1104 : 773 : }
1105 : :
1106 : 19 : void TerminalWindow::EchoBack(std::u32string_view buffer)
1107 : : {
1108 : : //Write(buffer, size); // DEBUGGING
1109 : 19 : OutBuffer.insert(OutBuffer.end(), buffer.begin(), buffer.end());
1110 : 19 : }
1111 : :
1112 : 401 : void TerminalWindow::SaveCur()
1113 : : {
1114 : 401 : backup.cx = wnd.cursx;
1115 : 401 : backup.cy = wnd.cursy;
1116 : 401 : backup.attr = wnd.blank;
1117 : 401 : }
1118 : :
1119 : 3 : void TerminalWindow::RestoreCur()
1120 : : {
1121 : 3 : wnd.cursx = backup.cx;
1122 : 3 : wnd.cursy = backup.cy;
1123 : 3 : wnd.blank = backup.attr;
1124 : 3 : }
1125 : :
1126 : 1 : void TerminalWindow::Resize(std::size_t newsx, std::size_t newsy)
1127 : : {
1128 [ + - ]: 1 : if(bottom == wnd.ysize-1)
1129 : : {
1130 : 1 : bottom = newsy-1;
1131 : : //fprintf(stderr, "Creating a window with top=%zu, bottom=%zu\n", top,bottom);
1132 : : }
1133 : 1 : wnd.Resize(newsx, newsy);
1134 : 1 : }
1135 : :
1136 : : #ifdef RUN_TESTS
1137 : : template<typename F>
1138 : 397 : static auto TerminalTest(int w, int h, std::u32string_view sample, F&& test)
1139 : : {
1140 : 397 : Window wnd(w, h);
1141 [ + - ]: 397 : TerminalWindow term(wnd);
1142 [ + - ]: 397 : term.Write(sample);
1143 : 741 : return test(wnd);
1144 : 397 : }
1145 : : template<typename F>
1146 : 345 : static auto TerminalTest(std::u32string_view sample, F&& test)
1147 : : {
1148 : 345 : return TerminalTest(80, 25, sample, test);
1149 : : }
1150 : 309 : static auto TerminalTestSGR(std::u32string_view sample)
1151 : : {
1152 : 618 : return TerminalTest(sample, [&](auto& wnd) { return wnd.blank; });
1153 : : }
1154 : 52 : static auto TerminalTestCursor(int w, int h, std::u32string_view sample)
1155 : : {
1156 : 52 : return TerminalTest(w,h, sample, [&](auto& wnd)
1157 : : {
1158 : : // Return the following:
1159 : : // Cursor location (x, y)
1160 : : // Location of first symbol non-space within window
1161 : : // Location of last symbol non-space within window
1162 : : // Number of non-space symbols within window
1163 : 52 : std::array<unsigned,5> result{ (unsigned)wnd.cursx, (unsigned)wnd.cursy, ~0u, 0, 0 };
1164 [ + + ]: 1716 : for(unsigned p=0; p<wnd.cells.size(); ++p)
1165 [ + + ]: 1664 : if(wnd.cells[p].ch != U' ')
1166 : : {
1167 [ + + ]: 54 : if(result[2] == ~0u) result[2] = p;
1168 : 54 : result[3] = p;
1169 : 54 : ++result[4];
1170 : : }
1171 [ + + ]: 52 : if(result[2] == ~0u) result[2] = 0u;
1172 : 52 : return result;
1173 : 52 : });
1174 : : }
1175 : :
1176 : :
1177 : 3 : TEST(terminal, text_rendering_works)
1178 : : {
1179 : 2 : EXPECT_TRUE(TerminalTest(U"", [&](auto& wnd)
1180 : : {
1181 : : return wnd.cells[0].ch == U' ';
1182 : 0 : }));
1183 : 3 : EXPECT_TRUE(TerminalTest(U"OK", [&](auto& wnd)
1184 : : {
1185 : : return wnd.cells[0].ch == U'O' && wnd.cells[1].ch == U'K' && wnd.cells[2].ch == U' ';
1186 : 0 : }));
1187 : 3 : EXPECT_TRUE(TerminalTest(U"O\rK", [&](auto& wnd)
1188 : : {
1189 : : return wnd.cells[0].ch == U'K' && wnd.cells[1].ch == U' ';
1190 : 0 : }));
1191 : 3 : EXPECT_TRUE(TerminalTest(U"OK\b!", [&](auto& wnd)
1192 : : {
1193 : : return wnd.cells[0].ch == U'O' && wnd.cells[1].ch == U'!';
1194 : 0 : }));
1195 : 3 : EXPECT_TRUE(TerminalTest(U"O\tK", [&](auto& wnd)
1196 : : {
1197 : : return wnd.cells[0].ch == U'O' && wnd.cells[8].ch == U'K';
1198 : 0 : }));
1199 : 3 : EXPECT_TRUE(TerminalTest(U" O\tK", [&](auto& wnd)
1200 : : {
1201 : : return wnd.cells[1].ch == U'O' && wnd.cells[8].ch == U'K';
1202 : 0 : }));
1203 : 3 : EXPECT_TRUE(TerminalTest(U"O\nK", [&](auto& wnd)
1204 : : {
1205 : : return wnd.cells[0].ch == U'O' && wnd.cells[81].ch == U'K';
1206 : 1 : }));
1207 : 1 : }
1208 : 3 : TEST(terminal, sgr_attributes)
1209 : : {
1210 : 3 : EXPECT_EQ(TerminalTestSGR(U"\33[0m"), []{Cell tmp; return tmp; }());
1211 : 3 : EXPECT_EQ(TerminalTestSGR(U"\33[1m"), []{Cell tmp; tmp.bold=true; return tmp; }());
1212 : 3 : EXPECT_EQ(TerminalTestSGR(U"\33[2m"), []{Cell tmp; tmp.dim=true; return tmp; }());
1213 : 3 : EXPECT_EQ(TerminalTestSGR(U"\33[3m"), []{Cell tmp; tmp.italic=true; return tmp; }());
1214 : 3 : EXPECT_EQ(TerminalTestSGR(U"\33[4m"), []{Cell tmp; tmp.underline=true; return tmp; }());
1215 : 3 : EXPECT_EQ(TerminalTestSGR(U"\33[5m"), []{Cell tmp; tmp.blink=1; return tmp; }());
1216 : 3 : EXPECT_EQ(TerminalTestSGR(U"\33[6m"), []{Cell tmp; tmp.blink=2; return tmp; }());
1217 : 3 : EXPECT_EQ(TerminalTestSGR(U"\33[7m"), []{Cell tmp; tmp.inverse=true; return tmp; }());
1218 : 3 : EXPECT_EQ(TerminalTestSGR(U"\33[9m"), []{Cell tmp; tmp.overstrike=true; return tmp; }());
1219 : 3 : EXPECT_EQ(TerminalTestSGR(U"\33[21m"), []{Cell tmp; tmp.underline2=true; return tmp; }());
1220 : 3 : EXPECT_EQ(TerminalTestSGR(U"\33[53m"), []{Cell tmp; tmp.overlined=true; return tmp; }());
1221 : :
1222 : 4 : EXPECT_EQ(TerminalTestSGR(U"\33[1;2;3;4;7;21;22m"), []{Cell tmp; tmp.bold=tmp.dim=tmp.italic=tmp.underline=tmp.underline2=tmp.inverse=1; tmp.dim=tmp.bold=0; return tmp; }());
1223 : 4 : EXPECT_EQ(TerminalTestSGR(U"\33[1;2;3;4;7;21;23m"), []{Cell tmp; tmp.bold=tmp.dim=tmp.italic=tmp.underline=tmp.underline2=tmp.inverse=1; tmp.italic=tmp.fraktur=0; return tmp; }());
1224 : 4 : EXPECT_EQ(TerminalTestSGR(U"\33[1;2;3;4;7;21;24m"), []{Cell tmp; tmp.bold=tmp.dim=tmp.italic=tmp.underline=tmp.underline2=tmp.inverse=1; tmp.underline=tmp.underline2=0; return tmp; }());
1225 : 4 : EXPECT_EQ(TerminalTestSGR(U"\33[1;2;3;4;7;21;25m"), []{Cell tmp; tmp.bold=tmp.dim=tmp.italic=tmp.underline=tmp.underline2=tmp.inverse=1; tmp.blink=0; return tmp; }());
1226 : 4 : EXPECT_EQ(TerminalTestSGR(U"\33[1;2;3;4;7;21;27m"), []{Cell tmp; tmp.bold=tmp.dim=tmp.italic=tmp.underline=tmp.underline2=tmp.inverse=1; tmp.inverse=0; return tmp; }());
1227 : 4 : EXPECT_EQ(TerminalTestSGR(U"\33[1;2;9;4;7;21;29m"), []{Cell tmp; tmp.bold=tmp.dim=tmp.overstrike=tmp.underline=tmp.underline2=tmp.inverse=1; tmp.overstrike=0; return tmp; }());
1228 : 4 : EXPECT_EQ(TerminalTestSGR(U"\33[1;5;53;2;55m"), []{Cell tmp; tmp.blink=tmp.bold=tmp.overlined=tmp.dim=1; tmp.overlined=0; return tmp; }());
1229 : 1 : }
1230 : 3 : TEST(terminal, sgr_colors)
1231 : : {
1232 : 1 : char Buf[32];
1233 [ + + ]: 9 : for(unsigned n=0; n<8; ++n)
1234 : : {
1235 : 8 : std::sprintf(Buf, "\33[%dm", 30+n);
1236 : 24 : EXPECT_EQ(TerminalTestSGR(FromUTF8(Buf)), [n]{Cell tmp; tmp.fgcolor=xterm256table[n]; return tmp; }());
1237 : 8 : std::sprintf(Buf, "\33[%dm", 40+n);
1238 : 24 : EXPECT_EQ(TerminalTestSGR(FromUTF8(Buf)), [n]{Cell tmp; tmp.bgcolor=xterm256table[n]; return tmp; }());
1239 : 8 : std::sprintf(Buf, "\33[%dm", 90+n);
1240 : 24 : EXPECT_EQ(TerminalTestSGR(FromUTF8(Buf)), [n]{Cell tmp; tmp.fgcolor=xterm256table[n+8]; return tmp; }());
1241 : 8 : std::sprintf(Buf, "\33[%dm", 100+n);
1242 : 32 : EXPECT_EQ(TerminalTestSGR(FromUTF8(Buf)), [n]{Cell tmp; tmp.bgcolor=xterm256table[n+8]; return tmp; }());
1243 : : }
1244 [ + + ]: 257 : for(unsigned n=0; n<256; ++n)
1245 : : {
1246 : 256 : std::sprintf(Buf, "\33[38;5;%d;48;5;%dm", n, 255-n);
1247 : 768 : EXPECT_EQ(TerminalTestSGR(FromUTF8(Buf)), [n]{Cell tmp; tmp.fgcolor=xterm256table[n];
1248 : 256 : tmp.bgcolor=xterm256table[255-n]; return tmp; }());
1249 : : }
1250 : : // RGB24
1251 : 3 : EXPECT_EQ(TerminalTestSGR(U"\33[38;2;0;0;0;48;2;255;255;255m"),
1252 : : []{Cell tmp; tmp.fgcolor=0x000000;
1253 : 0 : tmp.bgcolor=0xFFFFFF; return tmp; }());
1254 : : // CMY
1255 : 3 : EXPECT_EQ(TerminalTestSGR(U"\33[38;3;0;0;0;48;3;255;255;255m"),
1256 : : []{Cell tmp; tmp.fgcolor=cmy2rgb(0x000000);
1257 : 0 : tmp.bgcolor=cmy2rgb(0xFFFFFF); return tmp; }());
1258 : : // CMYK
1259 : 4 : EXPECT_EQ(TerminalTestSGR(U"\33[38;4;0;0;0;0;48;4;255;255;255;255m"),
1260 : : []{Cell tmp; tmp.fgcolor=cmyk2rgb(0x00000000);
1261 : 0 : tmp.bgcolor=cmyk2rgb(0xFFFFFFFF); return tmp; }());
1262 : : // Make sure it gets rendered by this color
1263 : 3 : EXPECT_TRUE(TerminalTest(U"\33[32mA", [&](Window& wnd)
1264 : : {
1265 : : return wnd.cells[0].fgcolor == xterm256table[2];
1266 : 1 : }));
1267 : 1 : }
1268 : 3 : TEST(terminal, save_restore)
1269 : : {
1270 : 3 : EXPECT_TRUE(TerminalTest(U"ABC" U"\33[s" U"\33[32mDE\33[34mF" U"\33[u" U"G\33[35mH", [&](Window& wnd)
1271 : : {
1272 : : return wnd.cells[0].ch == 'A' && wnd.cells[1].ch == 'B' && wnd.cells[2].ch == 'C'
1273 : : && wnd.cells[3].ch == 'G' && wnd.cells[4].ch == 'H' && wnd.cells[5].ch == 'F'
1274 : : && wnd.cells[3].fgcolor == Cell{}.fgcolor
1275 : : && wnd.cells[4].fgcolor == xterm256table[5]
1276 : : && wnd.cells[5].fgcolor == xterm256table[4];
1277 : 1 : }));
1278 : 1 : }
1279 : 3 : TEST(terminal, cursor_movements)
1280 : : {
1281 : : // CR, LF....
1282 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"ABC"), (std::array{3u,0u, 0u,2u,3u}));
1283 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"AB\rC"), (std::array{1u,0u, 0u,1u,2u})); // CR
1284 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"AB\nC"), (std::array{3u,1u, 0u,10u,3u})); // LF
1285 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"AB\013C"), (std::array{3u,1u, 0u,10u,3u})); // LF, alias
1286 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"AB\014C"), (std::array{3u,1u, 0u,10u,3u})); // LF, alias
1287 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"AB\033DC"), (std::array{3u,1u, 0u,10u,3u})); // LF, alias
1288 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"AB\033EC"), (std::array{1u,1u, 0u,8u,3u})); // CR+LF
1289 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"AB\033EC"), (std::array{1u,1u, 0u,8u,3u})); // CR+LF
1290 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"AAAAAAAA"), (std::array{7u,0u, 0u,7u,8u})); // test word wrap
1291 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"AAAAAAAAA"),(std::array{1u,1u, 0u,8u,9u})); // test word wrap+1
1292 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"A\r\nB\r\nC\r\nAAAAAAAA"),(std::array{7u,3u, 0u,037u,11u})); // right-bottom column
1293 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"A\r\nB\r\nC\r\nD\r\n"), (std::array{0u,3u, 0u,020u,3u})); // scroll down
1294 : : // Absolute
1295 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;2H"), (std::array{1u,1u, 0u,0u,0u}));
1296 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[1;1H"), (std::array{0u,0u, 0u,0u,0u}));
1297 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;2H\33[1;1H"), (std::array{0u,0u, 0u,0u,0u}));
1298 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[8;1H"), (std::array{0u,3u, 0u,0u,0u})); // clamped
1299 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[1;8H"), (std::array{7u,0u, 0u,0u,0u})); // clamped
1300 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;4H"), (std::array{3u,1u, 0u,0u,0u}));
1301 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;4;5H"), (std::array{3u,1u, 0u,0u,0u})); // extra param
1302 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;2H\33[H"), (std::array{0u,0u, 0u,0u,0u})); // no params
1303 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;2H\33[0;0H"), (std::array{0u,0u, 0u,0u,0u})); // zero params
1304 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4;8H"), (std::array{7u,3u, 0u,0u,0u})); // bottom-right corner
1305 : : // Same set with 'f'
1306 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;2f"), (std::array{1u,1u, 0u,0u,0u}));
1307 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[1;1f"), (std::array{0u,0u, 0u,0u,0u}));
1308 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;2H\33[1;1f"), (std::array{0u,0u, 0u,0u,0u}));
1309 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[8;1f"), (std::array{0u,3u, 0u,0u,0u})); // clamped
1310 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[1;8f"), (std::array{7u,0u, 0u,0u,0u})); // clamped
1311 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;4f"), (std::array{3u,1u, 0u,0u,0u}));
1312 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;4;5f"), (std::array{3u,1u, 0u,0u,0u})); // extra param
1313 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;2H\33[f"), (std::array{0u,0u, 0u,0u,0u})); // no params
1314 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;2H\33[0;0f"), (std::array{0u,0u, 0u,0u,0u})); // zero params
1315 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4;8f"), (std::array{7u,3u, 0u,0u,0u})); // bottom-right corner
1316 : : // Right
1317 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[C"), (std::array{1u,0u, 0u,0u,0u}));
1318 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[1C"), (std::array{1u,0u, 0u,0u,0u}));
1319 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4C"), (std::array{4u,0u, 0u,0u,0u}));
1320 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[8C"), (std::array{7u,0u, 0u,0u,0u}));
1321 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;3H\33[2a"), (std::array{4u,1u, 0u,0u,0u})); // a = alias to C
1322 : : // Down
1323 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[B"), (std::array{0u,1u, 0u,0u,0u}));
1324 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[1B"), (std::array{0u,1u, 0u,0u,0u}));
1325 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2B"), (std::array{0u,2u, 0u,0u,0u}));
1326 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[8B"), (std::array{0u,3u, 0u,0u,0u}));
1327 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;3H\33[2e"), (std::array{2u,3u, 0u,0u,0u})); // e = alias to B
1328 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;3H\33[2E"), (std::array{0u,3u, 0u,0u,0u})); // E = alias to e, resets X to 0
1329 : : // Up
1330 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4;8H\33[A"), (std::array{7u,2u, 0u,0u,0u}));
1331 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4;8H\33[1A"), (std::array{7u,2u, 0u,0u,0u}));
1332 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4;8H\33[2A"), (std::array{7u,1u, 0u,0u,0u}));
1333 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4;8H\33[8A"), (std::array{7u,0u, 0u,0u,0u}));
1334 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4;8H\33[1F"), (std::array{0u,2u, 0u,0u,0u})); // F = same as A, puts X to 0
1335 : : // Left
1336 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4;8H\33[D"), (std::array{6u,3u, 0u,0u,0u}));
1337 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4;8H\33[1D"), (std::array{6u,3u, 0u,0u,0u}));
1338 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4;8H\33[2D"), (std::array{5u,3u, 0u,0u,0u}));
1339 : 3 : EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4;8H\33[8D"), (std::array{0u,3u, 0u,0u,0u}));
1340 : 1 : }
1341 : 3 : TEST(terminal, scs)
1342 : : {
1343 : 1 : char32_t pattern[] = U"\033)0\016z\017";
1344 : 1 : char32_t symbols[] = U"012`45679ABCEfFHKLMQR<=>UYZ";
1345 [ + + ]: 29 : for(char32_t c: symbols)
1346 : : {
1347 : 28 : char32_t expect = 'z';
1348 : 28 : pattern[2] = c;
1349 [ + + ]: 28 : if(c == U'0') expect = U'\u2265';
1350 [ + + ]: 28 : if(c)
1351 : : {
1352 : 108 : EXPECT_EQ(TerminalTest(pattern, [&](auto& wnd) { return wnd.cells[0].ch; }), expect);
1353 : : }
1354 : : }
1355 : 1 : }
1356 : :
1357 : : #include <fstream>
1358 : 3 : TEST(terminal, stress_test)
1359 : : {
1360 : 1 : Window wnd(4, 4);
1361 [ + - ]: 1 : TerminalWindow term(wnd);
1362 [ + - ]: 1 : term.Resize(80, 25);
1363 : :
1364 : : // Issue the patterns from the following files:
1365 : 5 : for(std::string s: {"test/escapes.txt", "test/xterm-symbols.txt", "test/xterm-symbols2.txt",
1366 [ + - + + ]: 5 : "test/ocs_dcs.txt"})
1367 : : {
1368 [ + - ]: 4 : std::ifstream f(s);
1369 [ + - + + ]: 380 : for(std::string line; std::getline(f,line), f; )
1370 [ + - + - ]: 752 : term.Write(FromUTF8(line));
1371 : 4 : }
1372 [ + - + - ]: 1 : std::u32string result(term.OutBuffer.begin(), term.OutBuffer.end());
1373 : 1 : EXPECT_EQ(result,
1374 : : U"\33[?65;1;6;8;15;22c\33[?65;1;6;8;15;22c\33[0n\33[1;1R\033P!|00000000\x9C\33[>1;1;0c\33[?65;1;6;8;15;22c\33[?25;1$y\33[12;4$y\33[20;4$y"
1375 : : U"\x18\033$P1$r0;24r\033\\\033$P1$r38;2;204;204;204;48;2;0;0;0m\033\\\033$P1$r24t\033\\\033$P1$r1 q\033\\\033$P1$r0\"q\033\\\033$P1$r64;1\"p\033\\\033$P1$r80$|\033\\\033$P1$r25*|\033\\"
1376 : 1 : );
1377 : 1 : }
1378 : :
1379 : : #endif
|