Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <AK/StringBuilder.h>
28#include <LibVT/Terminal.h>
29
30//#define TERMINAL_DEBUG
31
32namespace VT {
33
34Terminal::Terminal(TerminalClient& client)
35 : m_client(client)
36{
37}
38
39Terminal::~Terminal()
40{
41}
42
43Terminal::Line::Line(u16 length)
44{
45 set_length(length);
46}
47
48Terminal::Line::~Line()
49{
50 delete[] characters;
51 delete[] attributes;
52}
53
54void Terminal::Line::set_length(u16 new_length)
55{
56 if (m_length == new_length)
57 return;
58 auto* new_characters = new u8[new_length];
59 auto* new_attributes = new Attribute[new_length];
60 memset(new_characters, ' ', new_length);
61 if (characters && attributes) {
62 memcpy(new_characters, characters, min(m_length, new_length));
63 memcpy(new_attributes, attributes, min(m_length, new_length) * sizeof(Attribute));
64 }
65 delete[] characters;
66 delete[] attributes;
67 characters = new_characters;
68 attributes = new_attributes;
69 m_length = new_length;
70}
71
72void Terminal::Line::clear(Attribute attribute)
73{
74 if (dirty) {
75 memset(characters, ' ', m_length);
76 for (u16 i = 0; i < m_length; ++i)
77 attributes[i] = attribute;
78 return;
79 }
80 for (unsigned i = 0; i < m_length; ++i) {
81 if (characters[i] != ' ')
82 dirty = true;
83 characters[i] = ' ';
84 }
85 for (unsigned i = 0; i < m_length; ++i) {
86 if (attributes[i] != attribute)
87 dirty = true;
88 attributes[i] = attribute;
89 }
90}
91
92bool Terminal::Line::has_only_one_background_color() const
93{
94 if (!m_length)
95 return true;
96 // FIXME: Cache this result?
97 auto color = attributes[0].background_color;
98 for (size_t i = 1; i < m_length; ++i) {
99 if (attributes[i].background_color != color)
100 return false;
101 }
102 return true;
103}
104
105void Terminal::clear()
106{
107 for (size_t i = 0; i < rows(); ++i)
108 line(i).clear(m_current_attribute);
109 set_cursor(0, 0);
110}
111
112inline bool is_valid_parameter_character(u8 ch)
113{
114 return ch >= 0x30 && ch <= 0x3f;
115}
116
117inline bool is_valid_intermediate_character(u8 ch)
118{
119 return ch >= 0x20 && ch <= 0x2f;
120}
121
122inline bool is_valid_final_character(u8 ch)
123{
124 return ch >= 0x40 && ch <= 0x7e;
125}
126
127void Terminal::alter_mode(bool should_set, bool question_param, const ParamVector& params)
128{
129 int mode = 2;
130 if (params.size() > 0) {
131 mode = params[0];
132 }
133 if (!question_param) {
134 switch (mode) {
135 // FIXME: implement *something* for this
136 default:
137 unimplemented_escape();
138 break;
139 }
140 } else {
141 switch (mode) {
142 case 25:
143 // Hide cursor command, but doesn't need to be run (for now, because
144 // we don't do inverse control codes anyways)
145 if (should_set)
146 dbgprintf("Terminal: Hide Cursor escapecode recieved. Not needed: ignored.\n");
147 else
148 dbgprintf("Terminal: Show Cursor escapecode recieved. Not needed: ignored.\n");
149 break;
150 default:
151 break;
152 }
153 }
154}
155
156void Terminal::RM(bool question_param, const ParamVector& params)
157{
158 // RM – Reset Mode
159 alter_mode(true, question_param, params);
160}
161
162void Terminal::SM(bool question_param, const ParamVector& params)
163{
164 // SM – Set Mode
165 alter_mode(false, question_param, params);
166}
167
168void Terminal::SGR(const ParamVector& params)
169{
170 // SGR – Select Graphic Rendition
171 if (params.is_empty()) {
172 m_current_attribute.reset();
173 return;
174 }
175 if (params.size() == 3 && params[1] == 5) {
176 if (params[0] == 38) {
177 m_current_attribute.foreground_color = params[2];
178 return;
179 } else if (params[0] == 48) {
180 m_current_attribute.background_color = params[2];
181 return;
182 }
183 }
184 for (auto param : params) {
185 switch (param) {
186 case 0:
187 // Reset
188 m_current_attribute.reset();
189 break;
190 case 1:
191 m_current_attribute.flags |= Attribute::Bold;
192 break;
193 case 3:
194 m_current_attribute.flags |= Attribute::Italic;
195 break;
196 case 4:
197 m_current_attribute.flags |= Attribute::Underline;
198 break;
199 case 5:
200 m_current_attribute.flags |= Attribute::Blink;
201 break;
202 case 7:
203 m_current_attribute.flags |= Attribute::Negative;
204 break;
205 case 22:
206 m_current_attribute.flags &= ~Attribute::Bold;
207 break;
208 case 23:
209 m_current_attribute.flags &= ~Attribute::Italic;
210 break;
211 case 24:
212 m_current_attribute.flags &= ~Attribute::Underline;
213 break;
214 case 25:
215 m_current_attribute.flags &= ~Attribute::Blink;
216 break;
217 case 27:
218 m_current_attribute.flags &= ~Attribute::Negative;
219 break;
220 case 30:
221 case 31:
222 case 32:
223 case 33:
224 case 34:
225 case 35:
226 case 36:
227 case 37:
228 // Foreground color
229 if (m_current_attribute.flags & Attribute::Bold)
230 param += 8;
231 m_current_attribute.foreground_color = param - 30;
232 break;
233 case 39:
234 // reset foreground
235 m_current_attribute.foreground_color = Attribute::default_foreground_color;
236 break;
237 case 40:
238 case 41:
239 case 42:
240 case 43:
241 case 44:
242 case 45:
243 case 46:
244 case 47:
245 // Background color
246 if (m_current_attribute.flags & Attribute::Bold)
247 param += 8;
248 m_current_attribute.background_color = param - 40;
249 break;
250 case 49:
251 // reset background
252 m_current_attribute.background_color = Attribute::default_background_color;
253 break;
254 default:
255 dbgprintf("FIXME: SGR: p: %u\n", param);
256 }
257 }
258}
259
260void Terminal::escape$s(const ParamVector&)
261{
262 m_saved_cursor_row = m_cursor_row;
263 m_saved_cursor_column = m_cursor_column;
264}
265
266void Terminal::escape$u(const ParamVector&)
267{
268 set_cursor(m_saved_cursor_row, m_saved_cursor_column);
269}
270
271void Terminal::escape$t(const ParamVector& params)
272{
273 if (params.size() < 1)
274 return;
275 dbgprintf("FIXME: escape$t: Ps: %u (param count: %zu)\n", params[0], params.size());
276}
277
278void Terminal::DECSTBM(const ParamVector& params)
279{
280 // DECSTBM – Set Top and Bottom Margins ("Scrolling Region")
281 unsigned top = 1;
282 unsigned bottom = m_rows;
283 if (params.size() >= 1)
284 top = params[0];
285 if (params.size() >= 2)
286 bottom = params[1];
287 if ((bottom - top) < 2 || bottom > m_rows) {
288 dbgprintf("Error: DECSTBM: scrolling region invalid: %u-%u\n", top, bottom);
289 return;
290 }
291 m_scroll_region_top = top - 1;
292 m_scroll_region_bottom = bottom - 1;
293 set_cursor(0, 0);
294}
295
296void Terminal::CUP(const ParamVector& params)
297{
298 // CUP – Cursor Position
299 unsigned row = 1;
300 unsigned col = 1;
301 if (params.size() >= 1)
302 row = params[0];
303 if (params.size() >= 2)
304 col = params[1];
305 set_cursor(row - 1, col - 1);
306}
307
308void Terminal::HVP(const ParamVector& params)
309{
310 // HVP – Horizontal and Vertical Position
311 unsigned row = 1;
312 unsigned col = 1;
313 if (params.size() >= 1)
314 row = params[0];
315 if (params.size() >= 2)
316 col = params[1];
317 set_cursor(row - 1, col - 1);
318}
319
320void Terminal::CUU(const ParamVector& params)
321{
322 // CUU – Cursor Up
323 int num = 1;
324 if (params.size() >= 1)
325 num = params[0];
326 if (num == 0)
327 num = 1;
328 int new_row = (int)m_cursor_row - num;
329 if (new_row < 0)
330 new_row = 0;
331 set_cursor(new_row, m_cursor_column);
332}
333
334void Terminal::CUD(const ParamVector& params)
335{
336 // CUD – Cursor Down
337 int num = 1;
338 if (params.size() >= 1)
339 num = params[0];
340 if (num == 0)
341 num = 1;
342 int new_row = (int)m_cursor_row + num;
343 if (new_row >= m_rows)
344 new_row = m_rows - 1;
345 set_cursor(new_row, m_cursor_column);
346}
347
348void Terminal::CUF(const ParamVector& params)
349{
350 // CUF – Cursor Forward
351 int num = 1;
352 if (params.size() >= 1)
353 num = params[0];
354 if (num == 0)
355 num = 1;
356 int new_column = (int)m_cursor_column + num;
357 if (new_column >= m_columns)
358 new_column = m_columns - 1;
359 set_cursor(m_cursor_row, new_column);
360}
361
362void Terminal::CUB(const ParamVector& params)
363{
364 // CUB – Cursor Backward
365 int num = 1;
366 if (params.size() >= 1)
367 num = params[0];
368 if (num == 0)
369 num = 1;
370 int new_column = (int)m_cursor_column - num;
371 if (new_column < 0)
372 new_column = 0;
373 set_cursor(m_cursor_row, new_column);
374}
375
376void Terminal::escape$G(const ParamVector& params)
377{
378 int new_column = 1;
379 if (params.size() >= 1)
380 new_column = params[0] - 1;
381 if (new_column < 0)
382 new_column = 0;
383 set_cursor(m_cursor_row, new_column);
384}
385
386void Terminal::escape$b(const ParamVector& params)
387{
388 if (params.size() < 1)
389 return;
390
391 for (unsigned i = 0; i < params[0]; ++i)
392 put_character_at(m_cursor_row, m_cursor_column++, m_last_char);
393}
394
395void Terminal::escape$d(const ParamVector& params)
396{
397 int new_row = 1;
398 if (params.size() >= 1)
399 new_row = params[0] - 1;
400 if (new_row < 0)
401 new_row = 0;
402 set_cursor(new_row, m_cursor_column);
403}
404
405void Terminal::escape$X(const ParamVector& params)
406{
407 // Erase characters (without moving cursor)
408 int num = 1;
409 if (params.size() >= 1)
410 num = params[0];
411 if (num == 0)
412 num = 1;
413 // Clear from cursor to end of line.
414 for (int i = m_cursor_column; i < num; ++i) {
415 put_character_at(m_cursor_row, i, ' ');
416 }
417}
418
419void Terminal::EL(const ParamVector& params)
420{
421 int mode = 0;
422 if (params.size() >= 1)
423 mode = params[0];
424 switch (mode) {
425 case 0:
426 // Clear from cursor to end of line.
427 for (int i = m_cursor_column; i < m_columns; ++i) {
428 put_character_at(m_cursor_row, i, ' ');
429 }
430 break;
431 case 1:
432 // Clear from cursor to beginning of line.
433 for (int i = 0; i <= m_cursor_column; ++i) {
434 put_character_at(m_cursor_row, i, ' ');
435 }
436 break;
437 case 2:
438 // Clear the complete line
439 for (int i = 0; i < m_columns; ++i) {
440 put_character_at(m_cursor_row, i, ' ');
441 }
442 break;
443 default:
444 unimplemented_escape();
445 break;
446 }
447}
448
449void Terminal::ED(const ParamVector& params)
450{
451 // ED - Erase in Display
452 int mode = 0;
453 if (params.size() >= 1)
454 mode = params[0];
455 switch (mode) {
456 case 0:
457 // Clear from cursor to end of screen.
458 for (int i = m_cursor_column; i < m_columns; ++i)
459 put_character_at(m_cursor_row, i, ' ');
460 for (int row = m_cursor_row + 1; row < m_rows; ++row) {
461 for (int column = 0; column < m_columns; ++column) {
462 put_character_at(row, column, ' ');
463 }
464 }
465 break;
466 case 1:
467 // Clear from cursor to beginning of screen.
468 for (int i = m_cursor_column; i >= 0; --i)
469 put_character_at(m_cursor_row, i, ' ');
470 for (int row = m_cursor_row - 1; row >= 0; --row) {
471 for (int column = 0; column < m_columns; ++column) {
472 put_character_at(row, column, ' ');
473 }
474 }
475 break;
476 case 2:
477 clear();
478 break;
479 case 3:
480 // FIXME: <esc>[3J should also clear the scrollback buffer.
481 clear();
482 break;
483 default:
484 unimplemented_escape();
485 break;
486 }
487}
488
489void Terminal::escape$S(const ParamVector& params)
490{
491 int count = 1;
492 if (params.size() >= 1)
493 count = params[0];
494
495 for (u16 i = 0; i < count; i++)
496 scroll_up();
497}
498
499void Terminal::escape$T(const ParamVector& params)
500{
501 int count = 1;
502 if (params.size() >= 1)
503 count = params[0];
504
505 for (u16 i = 0; i < count; i++)
506 scroll_down();
507}
508
509void Terminal::escape$L(const ParamVector& params)
510{
511 int count = 1;
512 if (params.size() >= 1)
513 count = params[0];
514 invalidate_cursor();
515 for (; count > 0; --count) {
516 m_lines.insert(m_cursor_row + m_scroll_region_top, make<Line>(m_columns));
517 if (m_scroll_region_bottom + 1 < m_lines.size())
518 m_lines.remove(m_scroll_region_bottom + 1);
519 else
520 m_lines.remove(m_lines.size() - 1);
521 }
522
523 m_need_full_flush = true;
524}
525
526void Terminal::DA(const ParamVector&)
527{
528 // DA - Device Attributes
529 emit_string("\033[?1;0c");
530}
531
532void Terminal::escape$M(const ParamVector& params)
533{
534 int count = 1;
535 if (params.size() >= 1)
536 count = params[0];
537
538 if (count == 1 && m_cursor_row == 0) {
539 scroll_up();
540 return;
541 }
542
543 int max_count = m_rows - (m_scroll_region_top + m_cursor_row);
544 count = min(count, max_count);
545
546 for (int c = count; c > 0; --c) {
547 m_lines.remove(m_cursor_row + m_scroll_region_top);
548 if (m_scroll_region_bottom < m_lines.size())
549 m_lines.insert(m_scroll_region_bottom, make<Line>(m_columns));
550 else
551 m_lines.append(make<Line>(m_columns));
552 }
553}
554
555void Terminal::escape$P(const ParamVector& params)
556{
557 int num = 1;
558 if (params.size() >= 1)
559 num = params[0];
560
561 if (num == 0)
562 num = 1;
563
564 auto& line = this->line(m_cursor_row);
565
566 // Move n characters of line to the left
567 for (int i = m_cursor_column; i < line.m_length - num; i++)
568 line.characters[i] = line.characters[i + num];
569
570 // Fill remainder of line with blanks
571 for (int i = line.m_length - num; i < line.m_length; i++)
572 line.characters[i] = ' ';
573
574 line.dirty = true;
575}
576
577void Terminal::execute_xterm_command()
578{
579 m_final = '@';
580 bool ok;
581 unsigned value = String::copy(m_xterm_param1).to_uint(ok);
582 if (ok) {
583 switch (value) {
584 case 0:
585 case 1:
586 case 2:
587 m_client.set_window_title(String::copy(m_xterm_param2));
588 break;
589 default:
590 unimplemented_xterm_escape();
591 break;
592 }
593 }
594 m_xterm_param1.clear_with_capacity();
595 m_xterm_param2.clear_with_capacity();
596}
597
598void Terminal::execute_escape_sequence(u8 final)
599{
600 bool question_param = false;
601 m_final = final;
602 ParamVector params;
603
604 if (m_parameters.size() > 0 && m_parameters[0] == '?') {
605 question_param = true;
606 m_parameters.remove(0);
607 }
608 auto paramparts = String::copy(m_parameters).split(';');
609 for (auto& parampart : paramparts) {
610 bool ok;
611 unsigned value = parampart.to_uint(ok);
612 if (!ok) {
613 // FIXME: Should we do something else?
614 m_parameters.clear_with_capacity();
615 m_intermediates.clear_with_capacity();
616 return;
617 }
618 params.append(value);
619 }
620
621#if defined(TERMINAL_DEBUG)
622 dbgprintf("Terminal::execute_escape_sequence: Handled final '%c'\n", final);
623 dbgprintf("Params: ");
624 for (auto& p : params) {
625 dbgprintf("%d ", p);
626 }
627 dbgprintf("\b\n");
628#endif
629
630 switch (final) {
631 case 'A':
632 CUU(params);
633 break;
634 case 'B':
635 CUD(params);
636 break;
637 case 'C':
638 CUF(params);
639 break;
640 case 'D':
641 CUB(params);
642 break;
643 case 'H':
644 CUP(params);
645 break;
646 case 'J':
647 ED(params);
648 break;
649 case 'K':
650 EL(params);
651 break;
652 case 'M':
653 escape$M(params);
654 break;
655 case 'P':
656 escape$P(params);
657 break;
658 case 'S':
659 escape$S(params);
660 break;
661 case 'T':
662 escape$T(params);
663 break;
664 case 'L':
665 escape$L(params);
666 break;
667 case 'G':
668 escape$G(params);
669 break;
670 case 'X':
671 escape$X(params);
672 break;
673 case 'b':
674 escape$b(params);
675 break;
676 case 'd':
677 escape$d(params);
678 break;
679 case 'm':
680 SGR(params);
681 break;
682 case 's':
683 escape$s(params);
684 break;
685 case 'u':
686 escape$u(params);
687 break;
688 case 't':
689 escape$t(params);
690 break;
691 case 'r':
692 DECSTBM(params);
693 break;
694 case 'l':
695 RM(question_param, params);
696 break;
697 case 'h':
698 SM(question_param, params);
699 break;
700 case 'c':
701 DA(params);
702 break;
703 case 'f':
704 HVP(params);
705 break;
706 default:
707 dbgprintf("Terminal::execute_escape_sequence: Unhandled final '%c'\n", final);
708 break;
709 }
710
711#if defined(TERMINAL_DEBUG)
712 dbgprintf("\n");
713 for (auto& line : m_lines) {
714 dbgprintf("Terminal: Line: ");
715 for (int i = 0; i < line.m_length; i++) {
716 dbgprintf("%c", line.characters[i]);
717 }
718 dbgprintf("\n");
719 }
720#endif
721
722 m_parameters.clear_with_capacity();
723 m_intermediates.clear_with_capacity();
724}
725
726void Terminal::newline()
727{
728 u16 new_row = m_cursor_row;
729 if (m_cursor_row == m_scroll_region_bottom) {
730 scroll_up();
731 } else {
732 ++new_row;
733 }
734 set_cursor(new_row, 0);
735}
736
737void Terminal::scroll_up()
738{
739 // NOTE: We have to invalidate the cursor first.
740 invalidate_cursor();
741 if (m_scroll_region_top == 0) {
742 auto line = move(m_lines.ptr_at(m_scroll_region_top));
743 m_history.append(move(line));
744 while (m_history.size() > max_history_size())
745 m_history.take_first();
746 m_client.terminal_history_changed();
747 }
748 m_lines.remove(m_scroll_region_top);
749 m_lines.insert(m_scroll_region_bottom, make<Line>(m_columns));
750 m_need_full_flush = true;
751}
752
753void Terminal::scroll_down()
754{
755 // NOTE: We have to invalidate the cursor first.
756 invalidate_cursor();
757 m_lines.remove(m_scroll_region_bottom);
758 m_lines.insert(m_scroll_region_top, make<Line>(m_columns));
759 m_need_full_flush = true;
760}
761
762void Terminal::set_cursor(unsigned a_row, unsigned a_column)
763{
764 unsigned row = min(a_row, m_rows - 1u);
765 unsigned column = min(a_column, m_columns - 1u);
766 if (row == m_cursor_row && column == m_cursor_column)
767 return;
768 ASSERT(row < rows());
769 ASSERT(column < columns());
770 invalidate_cursor();
771 m_cursor_row = row;
772 m_cursor_column = column;
773 m_stomp = false;
774 invalidate_cursor();
775}
776
777void Terminal::put_character_at(unsigned row, unsigned column, u8 ch)
778{
779 ASSERT(row < rows());
780 ASSERT(column < columns());
781 auto& line = this->line(row);
782 line.characters[column] = ch;
783 line.attributes[column] = m_current_attribute;
784 line.attributes[column].flags |= Attribute::Touched;
785 line.dirty = true;
786
787 m_last_char = ch;
788}
789
790void Terminal::NEL()
791{
792 // NEL - Next Line
793 newline();
794}
795
796void Terminal::IND()
797{
798 // IND - Index (move down)
799 CUD({});
800}
801
802void Terminal::RI()
803{
804 // RI - Reverse Index (move up)
805 CUU({});
806}
807
808void Terminal::on_char(u8 ch)
809{
810#ifdef TERMINAL_DEBUG
811 dbgprintf("Terminal::on_char: %b (%c), fg=%u, bg=%u\n", ch, ch, m_current_attribute.foreground_color, m_current_attribute.background_color);
812#endif
813 switch (m_escape_state) {
814 case GotEscape:
815 if (ch == '[') {
816 m_escape_state = ExpectParameter;
817 } else if (ch == '(') {
818 m_swallow_current = true;
819 m_escape_state = ExpectParameter;
820 } else if (ch == ']') {
821 m_escape_state = ExpectXtermParameter1;
822 } else if (ch == '#') {
823 m_escape_state = ExpectHashtagDigit;
824 } else if (ch == 'D') {
825 IND();
826 m_escape_state = Normal;
827 return;
828 } else if (ch == 'M') {
829 RI();
830 m_escape_state = Normal;
831 return;
832 } else if (ch == 'E') {
833 NEL();
834 m_escape_state = Normal;
835 return;
836 } else {
837 dbg() << "Unexpected character in GotEscape '" << (char)ch << "'";
838 m_escape_state = Normal;
839 }
840 return;
841 case ExpectHashtagDigit:
842 if (ch >= '0' && ch <= '9') {
843 execute_hashtag(ch);
844 m_escape_state = Normal;
845 }
846 break;
847 case ExpectXtermParameter1:
848 if (ch != ';') {
849 m_xterm_param1.append(ch);
850 return;
851 }
852 m_escape_state = ExpectXtermParameter2;
853 return;
854 case ExpectXtermParameter2:
855 if (ch != '\007') {
856 m_xterm_param2.append(ch);
857 return;
858 }
859 m_escape_state = ExpectXtermFinal;
860 [[fallthrough]];
861 case ExpectXtermFinal:
862 m_escape_state = Normal;
863 if (ch == '\007')
864 execute_xterm_command();
865 return;
866 case ExpectParameter:
867 if (is_valid_parameter_character(ch)) {
868 m_parameters.append(ch);
869 return;
870 }
871 m_escape_state = ExpectIntermediate;
872 [[fallthrough]];
873 case ExpectIntermediate:
874 if (is_valid_intermediate_character(ch)) {
875 m_intermediates.append(ch);
876 return;
877 }
878 m_escape_state = ExpectFinal;
879 [[fallthrough]];
880 case ExpectFinal:
881 if (is_valid_final_character(ch)) {
882 m_escape_state = Normal;
883 if (!m_swallow_current)
884 execute_escape_sequence(ch);
885 m_swallow_current = false;
886 return;
887 }
888 m_escape_state = Normal;
889 m_swallow_current = false;
890 return;
891 case Normal:
892 break;
893 }
894
895 switch (ch) {
896 case '\0':
897 return;
898 case '\033':
899 m_escape_state = GotEscape;
900 m_swallow_current = false;
901 return;
902 case 8: // Backspace
903 if (m_cursor_column) {
904 set_cursor(m_cursor_row, m_cursor_column - 1);
905 return;
906 }
907 return;
908 case '\a':
909 m_client.beep();
910 return;
911 case '\t': {
912 for (unsigned i = m_cursor_column + 1; i < columns(); ++i) {
913 if (m_horizontal_tabs[i]) {
914 set_cursor(m_cursor_row, i);
915 return;
916 }
917 }
918 return;
919 }
920 case '\r':
921 set_cursor(m_cursor_row, 0);
922 return;
923 case '\n':
924 newline();
925 return;
926 }
927
928 auto new_column = m_cursor_column + 1;
929 if (new_column < columns()) {
930 put_character_at(m_cursor_row, m_cursor_column, ch);
931 set_cursor(m_cursor_row, new_column);
932 } else {
933 if (m_stomp) {
934 m_stomp = false;
935 newline();
936 put_character_at(m_cursor_row, m_cursor_column, ch);
937 set_cursor(m_cursor_row, 1);
938 } else {
939 // Curious: We wait once on the right-hand side
940 m_stomp = true;
941 put_character_at(m_cursor_row, m_cursor_column, ch);
942 }
943 }
944}
945
946void Terminal::inject_string(const StringView& str)
947{
948 for (size_t i = 0; i < str.length(); ++i)
949 on_char(str[i]);
950}
951
952void Terminal::emit_string(const StringView& str)
953{
954 for (size_t i = 0; i < str.length(); ++i)
955 m_client.emit_char(str[i]);
956}
957
958void Terminal::unimplemented_escape()
959{
960 StringBuilder builder;
961 builder.appendf("((Unimplemented escape: %c", m_final);
962 if (!m_parameters.is_empty()) {
963 builder.append(" parameters:");
964 for (size_t i = 0; i < m_parameters.size(); ++i)
965 builder.append((char)m_parameters[i]);
966 }
967 if (!m_intermediates.is_empty()) {
968 builder.append(" intermediates:");
969 for (size_t i = 0; i < m_intermediates.size(); ++i)
970 builder.append((char)m_intermediates[i]);
971 }
972 builder.append("))");
973 inject_string(builder.to_string());
974}
975
976void Terminal::unimplemented_xterm_escape()
977{
978 auto message = String::format("((Unimplemented xterm escape: %c))\n", m_final);
979 inject_string(message);
980}
981
982void Terminal::set_size(u16 columns, u16 rows)
983{
984 if (!columns)
985 columns = 1;
986 if (!rows)
987 rows = 1;
988
989 if (columns == m_columns && rows == m_rows)
990 return;
991
992#if defined(TERMINAL_DEBUG)
993 dbgprintf("Terminal: RESIZE to: %d rows\n", rows);
994#endif
995
996 if (rows > m_rows) {
997 while (m_lines.size() < rows)
998 m_lines.append(make<Line>(columns));
999 } else {
1000 m_lines.shrink(rows);
1001 }
1002
1003 for (int i = 0; i < rows; ++i)
1004 m_lines[i].set_length(columns);
1005
1006 m_columns = columns;
1007 m_rows = rows;
1008
1009 m_scroll_region_top = 0;
1010 m_scroll_region_bottom = rows - 1;
1011
1012 m_cursor_row = min((int)m_cursor_row, m_rows - 1);
1013 m_cursor_column = min((int)m_cursor_column, m_columns - 1);
1014 m_saved_cursor_row = min((int)m_saved_cursor_row, m_rows - 1);
1015 m_saved_cursor_column = min((int)m_saved_cursor_column, m_columns - 1);
1016
1017 m_horizontal_tabs.resize(columns);
1018 for (unsigned i = 0; i < columns; ++i)
1019 m_horizontal_tabs[i] = (i % 8) == 0;
1020 // Rightmost column is always last tab on line.
1021 m_horizontal_tabs[columns - 1] = 1;
1022
1023 m_client.terminal_did_resize(m_columns, m_rows);
1024}
1025
1026void Terminal::invalidate_cursor()
1027{
1028 line(m_cursor_row).dirty = true;
1029}
1030
1031void Terminal::execute_hashtag(u8 hashtag)
1032{
1033 switch (hashtag) {
1034 case '8':
1035 // Confidence Test - Fill screen with E's
1036 for (size_t row = 0; row < m_rows; ++row) {
1037 for (size_t column = 0; column < m_columns; ++column) {
1038 put_character_at(row, column, 'E');
1039 }
1040 }
1041 break;
1042 default:
1043 dbg() << "Unknown hashtag: '" << hashtag << "'";
1044 }
1045}
1046
1047}