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