Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2021, Daniel Bertalan <dani@danielbertalan.dev>
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include <AK/Debug.h>
9#include <AK/Queue.h>
10#include <AK/StringBuilder.h>
11#include <AK/StringView.h>
12#include <AK/TemporaryChange.h>
13#include <LibVT/Color.h>
14#include <LibVT/Terminal.h>
15#ifdef KERNEL
16# include <Kernel/TTY/VirtualConsole.h>
17#endif
18
19namespace VT {
20
21#ifndef KERNEL
22Terminal::Terminal(TerminalClient& client)
23#else
24Terminal::Terminal(Kernel::VirtualConsole& client)
25#endif
26 : m_client(client)
27 , m_parser(*this)
28{
29}
30
31#ifndef KERNEL
32void Terminal::clear()
33{
34 dbgln_if(TERMINAL_DEBUG, "Clear the entire screen");
35 for (size_t i = 0; i < rows(); ++i)
36 active_buffer()[i]->clear();
37 set_cursor(0, 0);
38}
39
40void Terminal::clear_history()
41{
42 dbgln_if(TERMINAL_DEBUG, "Clear history");
43 auto previous_history_size = m_history.size();
44 m_history.clear();
45 m_history_start = 0;
46 m_client.terminal_history_changed(-previous_history_size);
47}
48#endif
49
50void Terminal::alter_ansi_mode(bool should_set, Parameters params)
51{
52 for (auto mode : params) {
53 switch (mode) {
54 // FIXME: implement *something* for this
55 default:
56 dbgln("Terminal::alter_ansi_mode: Unimplemented mode {} (should_set={})", mode, should_set);
57 break;
58 }
59 }
60}
61
62void Terminal::alter_private_mode(bool should_set, Parameters params)
63{
64 for (auto mode : params) {
65 switch (mode) {
66 case 1:
67 // Cursor Keys Mode (DECCKM)
68 dbgln_if(TERMINAL_DEBUG, "Setting cursor keys mode (should_set={})", should_set);
69 m_cursor_keys_mode = should_set ? CursorKeysMode::Application : CursorKeysMode::Cursor;
70 break;
71 case 3: {
72 // 80/132-column mode (DECCOLM)
73 unsigned new_columns = should_set ? 132 : 80;
74 dbgln_if(TERMINAL_DEBUG, "Setting {}-column mode", new_columns);
75 set_size(new_columns, rows());
76 clear();
77 break;
78 }
79 case 12:
80 if (should_set) {
81 // Start blinking cursor
82 m_client.set_cursor_blinking(true);
83 } else {
84 // Stop blinking cursor
85 m_client.set_cursor_blinking(false);
86 }
87 break;
88 case 25:
89 if (should_set) {
90 // Show cursor
91 m_cursor_shape = m_saved_cursor_shape;
92 m_client.set_cursor_shape(m_cursor_shape);
93 } else {
94 // Hide cursor
95 m_saved_cursor_shape = m_cursor_shape;
96 m_cursor_shape = VT::CursorShape::None;
97 m_client.set_cursor_shape(VT::CursorShape::None);
98 }
99 break;
100 case 1047:
101#ifndef KERNEL
102 if (should_set) {
103 dbgln_if(TERMINAL_DEBUG, "Switching to Alternate Screen Buffer");
104 m_use_alternate_screen_buffer = true;
105 clear();
106 m_client.terminal_history_changed(-m_history.size());
107 } else {
108 dbgln_if(TERMINAL_DEBUG, "Switching to Normal Screen Buffer");
109 m_use_alternate_screen_buffer = false;
110 m_client.terminal_history_changed(m_history.size());
111 }
112 m_need_full_flush = true;
113#else
114 dbgln("Alternate Screen Buffer is not supported");
115#endif
116 break;
117 case 1048:
118 if (should_set)
119 SCOSC();
120 else
121 SCORC();
122 break;
123 case 1049:
124#ifndef KERNEL
125 if (should_set) {
126 dbgln_if(TERMINAL_DEBUG, "Switching to Alternate Screen Buffer and saving state");
127 m_normal_saved_state = m_current_state;
128 m_use_alternate_screen_buffer = true;
129 clear();
130 m_client.terminal_history_changed(-m_history.size());
131 } else {
132 dbgln_if(TERMINAL_DEBUG, "Switching to Normal Screen Buffer and restoring state");
133 m_current_state = m_normal_saved_state;
134 m_use_alternate_screen_buffer = false;
135 set_cursor(cursor_row(), cursor_column());
136 m_client.terminal_history_changed(m_history.size());
137 }
138 m_need_full_flush = true;
139#else
140 dbgln("Alternate Screen Buffer is not supported");
141#endif
142 break;
143 case 2004:
144 dbgln_if(TERMINAL_DEBUG, "Setting bracketed mode enabled={}", should_set);
145 m_needs_bracketed_paste = should_set;
146 break;
147 default:
148 dbgln("Terminal::alter_private_mode: Unimplemented private mode {} (should_set={})", mode, should_set);
149 break;
150 }
151 }
152}
153
154void Terminal::RM(Parameters params)
155{
156 alter_ansi_mode(false, params);
157}
158
159void Terminal::DECRST(Parameters params)
160{
161 alter_private_mode(false, params);
162}
163
164void Terminal::SM(Parameters params)
165{
166 alter_ansi_mode(true, params);
167}
168
169void Terminal::DECSET(Parameters params)
170{
171 alter_private_mode(true, params);
172}
173
174void Terminal::SGR(Parameters params)
175{
176 if (params.is_empty()) {
177 m_current_state.attribute.reset();
178 return;
179 }
180 auto parse_color = [&]() -> Optional<Color> {
181 if (params.size() < 2) {
182 dbgln("Color code has no type");
183 return {};
184 }
185 u32 rgb = 0;
186 switch (params[1]) {
187 case 5: // 8-bit
188 if (params.size() < 3) {
189 dbgln("8-bit color code has too few parameters");
190 return {};
191 }
192 if (params[2] > 255) {
193 dbgln("8-bit color code has out-of-bounds value");
194 return {};
195 }
196 return Color::indexed(params[2]);
197 case 2: // 24-bit
198 if (params.size() < 5) {
199 dbgln("24-bit color code has too few parameters");
200 return {};
201 }
202 for (size_t i = 0; i < 3; ++i) {
203 rgb <<= 8;
204 rgb |= params[i + 2];
205 }
206 return Color::rgb(rgb);
207 default:
208 dbgln("Unknown color type {}", params[1]);
209 return {};
210 }
211 };
212
213 if (params[0] == 38) {
214 m_current_state.attribute.foreground_color = parse_color().value_or(m_current_state.attribute.foreground_color);
215 } else if (params[0] == 48) {
216 m_current_state.attribute.background_color = parse_color().value_or(m_current_state.attribute.background_color);
217 } else {
218 // A single escape sequence may set multiple parameters.
219 for (auto param : params) {
220 switch (param) {
221 case 0:
222 // Reset
223 m_current_state.attribute.reset();
224 break;
225 case 1:
226 m_current_state.attribute.flags |= Attribute::Flags::Bold;
227 break;
228 case 3:
229 m_current_state.attribute.flags |= Attribute::Flags::Italic;
230 break;
231 case 4:
232 m_current_state.attribute.flags |= Attribute::Flags::Underline;
233 break;
234 case 5:
235 m_current_state.attribute.flags |= Attribute::Flags::Blink;
236 break;
237 case 7:
238 m_current_state.attribute.flags |= Attribute::Flags::Negative;
239 break;
240 case 22:
241 m_current_state.attribute.flags &= ~Attribute::Flags::Bold;
242 break;
243 case 23:
244 m_current_state.attribute.flags &= ~Attribute::Flags::Italic;
245 break;
246 case 24:
247 m_current_state.attribute.flags &= ~Attribute::Flags::Underline;
248 break;
249 case 25:
250 m_current_state.attribute.flags &= ~Attribute::Flags::Blink;
251 break;
252 case 27:
253 m_current_state.attribute.flags &= ~Attribute::Flags::Negative;
254 break;
255 case 30:
256 case 31:
257 case 32:
258 case 33:
259 case 34:
260 case 35:
261 case 36:
262 case 37:
263 // Foreground color
264 m_current_state.attribute.foreground_color = Color::named(static_cast<Color::ANSIColor>(param - 30));
265 break;
266 case 39:
267 // reset foreground
268 m_current_state.attribute.foreground_color = Attribute::default_foreground_color;
269 break;
270 case 40:
271 case 41:
272 case 42:
273 case 43:
274 case 44:
275 case 45:
276 case 46:
277 case 47:
278 // Background color
279 m_current_state.attribute.background_color = Color::named(static_cast<Color::ANSIColor>(param - 40));
280 break;
281 case 49:
282 // reset background
283 m_current_state.attribute.background_color = Attribute::default_background_color;
284 break;
285 case 90:
286 case 91:
287 case 92:
288 case 93:
289 case 94:
290 case 95:
291 case 96:
292 case 97:
293 // Bright foreground color
294 m_current_state.attribute.foreground_color = Color::named(static_cast<Color::ANSIColor>(8 + param - 90));
295 break;
296 case 100:
297 case 101:
298 case 102:
299 case 103:
300 case 104:
301 case 105:
302 case 106:
303 case 107:
304 // Bright background color
305 m_current_state.attribute.background_color = Color::named(static_cast<Color::ANSIColor>(8 + param - 100));
306 break;
307 default:
308 dbgln("FIXME: SGR: p: {}", param);
309 }
310 }
311 }
312}
313
314void Terminal::SCOSC()
315{
316 dbgln_if(TERMINAL_DEBUG, "Save cursor position");
317 m_saved_cursor_position = m_current_state.cursor;
318}
319
320void Terminal::SCORC()
321{
322 dbgln_if(TERMINAL_DEBUG, "Restore cursor position");
323 m_current_state.cursor = m_saved_cursor_position;
324 set_cursor(cursor_row(), cursor_column());
325}
326
327void Terminal::DECSC()
328{
329 dbgln_if(TERMINAL_DEBUG, "Save cursor (and other state)");
330 if (m_use_alternate_screen_buffer) {
331 m_alternate_saved_state = m_current_state;
332 } else {
333 m_normal_saved_state = m_current_state;
334 }
335}
336
337void Terminal::DECRC()
338{
339 dbgln_if(TERMINAL_DEBUG, "Restore cursor (and other state)");
340 if (m_use_alternate_screen_buffer) {
341 m_current_state = m_alternate_saved_state;
342 } else {
343 m_current_state = m_normal_saved_state;
344 }
345 set_cursor(cursor_row(), cursor_column());
346}
347
348void Terminal::XTERM_WM(Parameters params)
349{
350 if (params.size() < 1)
351 return;
352 switch (params[0]) {
353 case 22: {
354#ifndef KERNEL
355 if (params.size() > 1 && params[1] == 1) {
356 dbgln("FIXME: we don't support icon titles");
357 return;
358 }
359 dbgln_if(TERMINAL_DEBUG, "Title stack push: {}", m_current_window_title);
360 (void)m_title_stack.try_append(move(m_current_window_title));
361#endif
362 break;
363 }
364 case 23: {
365#ifndef KERNEL
366 if (params.size() > 1 && params[1] == 1)
367 return;
368 if (m_title_stack.is_empty()) {
369 dbgln("Shenanigans: Tried to pop from empty title stack");
370 return;
371 }
372 m_current_window_title = m_title_stack.take_last();
373 dbgln_if(TERMINAL_DEBUG, "Title stack pop: {}", m_current_window_title);
374 m_client.set_window_title(m_current_window_title);
375#endif
376 break;
377 }
378 default:
379 dbgln("FIXME: XTERM_WM: Ps: {} (param count: {})", params[0], params.size());
380 }
381}
382
383void Terminal::DECSTBM(Parameters params)
384{
385 unsigned top = 1;
386 unsigned bottom = m_rows;
387 if (params.size() >= 1 && params[0] != 0)
388 top = params[0];
389 if (params.size() >= 2 && params[1] != 0)
390 bottom = params[1];
391 if ((bottom - top) < 2 || bottom > m_rows) {
392 dbgln("Error: DECSTBM: scrolling region invalid: {}-{}", top, bottom);
393 return;
394 }
395 if (top >= bottom) {
396 return;
397 }
398 m_scroll_region_top = top - 1;
399 m_scroll_region_bottom = bottom - 1;
400 set_cursor(0, 0);
401 dbgln_if(TERMINAL_DEBUG, "Set scrolling region: {}-{}", m_scroll_region_top, m_scroll_region_bottom);
402}
403
404void Terminal::CUP(Parameters params)
405{
406 // CUP – Cursor Position
407 unsigned row = 1;
408 unsigned col = 1;
409 if (params.size() >= 1 && params[0] != 0)
410 row = params[0];
411 if (params.size() >= 2 && params[1] != 0)
412 col = params[1];
413 set_cursor(row - 1, col - 1);
414}
415
416void Terminal::HVP(Parameters params)
417{
418 unsigned row = 1;
419 unsigned col = 1;
420 if (params.size() >= 1 && params[0] != 0)
421 row = params[0];
422 if (params.size() >= 2 && params[1] != 0)
423 col = params[1];
424 set_cursor(row - 1, col - 1);
425}
426
427void Terminal::CUU(Parameters params)
428{
429 unsigned num = 1;
430 if (params.size() >= 1 && params[0] != 0)
431 num = params[0];
432 int new_row = cursor_row() - num;
433 if (new_row < 0)
434 new_row = 0;
435 set_cursor(new_row, cursor_column());
436}
437
438void Terminal::CUD(Parameters params)
439{
440 unsigned num = 1;
441 if (params.size() >= 1 && params[0] != 0)
442 num = params[0];
443 unsigned new_row = cursor_row() + num;
444 if (new_row >= m_rows)
445 new_row = m_rows - 1;
446 set_cursor(new_row, cursor_column());
447}
448
449void Terminal::CUF(Parameters params)
450{
451 unsigned num = 1;
452 if (params.size() >= 1 && params[0] != 0)
453 num = params[0];
454 unsigned new_column = cursor_column() + num;
455 if (new_column >= m_columns)
456 new_column = m_columns - 1;
457 set_cursor(cursor_row(), new_column);
458}
459
460void Terminal::CUB(Parameters params)
461{
462 unsigned num = 1;
463 if (params.size() >= 1 && params[0] != 0)
464 num = params[0];
465 int new_column = (int)cursor_column() - num;
466 if (new_column < 0)
467 new_column = 0;
468 set_cursor(cursor_row(), new_column);
469}
470
471void Terminal::CNL(Parameters params)
472{
473 unsigned num = 1;
474 if (params.size() >= 1 && params[0] != 0)
475 num = params[0];
476 unsigned new_row = cursor_row() + num;
477 if (new_row >= m_columns)
478 new_row = m_columns - 1;
479 set_cursor(new_row, 0);
480}
481
482void Terminal::CPL(Parameters params)
483{
484 unsigned num = 1;
485 if (params.size() >= 1 && params[0] != 0)
486 num = params[0];
487 int new_row = (int)cursor_row() - num;
488 if (new_row < 0)
489 new_row = 0;
490 set_cursor(new_row, 0);
491}
492
493void Terminal::CHA(Parameters params)
494{
495 unsigned new_column = 1;
496 if (params.size() >= 1 && params[0] != 0)
497 new_column = params[0];
498 if (new_column > m_columns)
499 new_column = m_columns;
500 set_cursor(cursor_row(), new_column - 1);
501}
502
503void Terminal::REP(Parameters params)
504{
505 unsigned count = 1;
506 if (params.size() >= 1 && params[0] != 0)
507 count = params[0];
508
509 for (unsigned i = 0; i < count; ++i)
510 put_character_at(m_current_state.cursor.row, m_current_state.cursor.column++, m_last_code_point);
511}
512
513void Terminal::VPA(Parameters params)
514{
515 unsigned new_row = 1;
516 if (params.size() >= 1 && params[0] != 0)
517 new_row = params[0];
518 if (new_row > m_rows)
519 new_row = m_rows;
520 set_cursor(new_row - 1, cursor_column());
521}
522
523void Terminal::VPR(Parameters params)
524{
525 unsigned num = 1;
526 if (params.size() >= 1 && params[0] != 0)
527 num = params[0];
528 int new_row = cursor_row() + num;
529 if (new_row >= m_rows)
530 new_row = m_rows - 1;
531 set_cursor(new_row, cursor_column());
532}
533
534void Terminal::HPA(Parameters params)
535{
536 unsigned new_column = 1;
537 if (params.size() >= 1 && params[0] != 0)
538 new_column = params[0];
539 if (new_column > m_columns)
540 new_column = m_columns;
541 set_cursor(cursor_row(), new_column - 1);
542}
543
544void Terminal::HPR(Parameters params)
545{
546 unsigned num = 1;
547 if (params.size() >= 1 && params[0] != 0)
548 num = params[0];
549 unsigned new_column = cursor_column() + num;
550 if (new_column >= m_columns)
551 new_column = m_columns - 1;
552 set_cursor(cursor_row(), new_column);
553}
554
555void Terminal::ECH(Parameters params)
556{
557 // Erase characters (without moving cursor)
558 unsigned num = 1;
559 if (params.size() >= 1 && params[0] != 0)
560 num = params[0];
561 // Clear num characters from the right of the cursor.
562 auto clear_end = min<unsigned>(m_columns, cursor_column() + num - 1);
563 dbgln_if(TERMINAL_DEBUG, "Erase characters {}-{} on line {}", cursor_column(), clear_end, cursor_row());
564 clear_in_line(cursor_row(), cursor_column(), clear_end);
565}
566
567void Terminal::EL(Parameters params)
568{
569 unsigned mode = 0;
570 if (params.size() >= 1)
571 mode = params[0];
572 switch (mode) {
573 case 0:
574 dbgln_if(TERMINAL_DEBUG, "Clear line {} from cursor column ({}) to the end", cursor_row(), cursor_column());
575 clear_in_line(cursor_row(), cursor_column(), m_columns - 1);
576 break;
577 case 1:
578 dbgln_if(TERMINAL_DEBUG, "Clear line {} from the start to cursor column ({})", cursor_row(), cursor_column());
579 clear_in_line(cursor_row(), 0, cursor_column());
580 break;
581 case 2:
582 dbgln_if(TERMINAL_DEBUG, "Clear line {} completely", cursor_row());
583 clear_in_line(cursor_row(), 0, m_columns - 1);
584 break;
585 default:
586 unimplemented_csi_sequence(params, {}, 'K');
587 break;
588 }
589}
590
591void Terminal::ED(Parameters params)
592{
593 unsigned mode = 0;
594 if (params.size() >= 1)
595 mode = params[0];
596 switch (mode) {
597 case 0:
598 dbgln_if(TERMINAL_DEBUG, "Clear from cursor ({},{}) to end of screen", cursor_row(), cursor_column());
599 clear_in_line(cursor_row(), cursor_column(), m_columns - 1);
600 for (int row = cursor_row() + 1; row < m_rows; ++row)
601 clear_in_line(row, 0, m_columns - 1);
602 break;
603 case 1:
604 dbgln_if(TERMINAL_DEBUG, "Clear from beginning of screen to cursor ({},{})", cursor_row(), cursor_column());
605 clear_in_line(cursor_row(), 0, cursor_column());
606 for (int row = cursor_row() - 1; row >= 0; --row)
607 clear_in_line(row, 0, m_columns - 1);
608 break;
609 case 2:
610 clear();
611 break;
612 case 3:
613 clear_history();
614 break;
615 default:
616 unimplemented_csi_sequence(params, {}, 'J');
617 break;
618 }
619}
620
621void Terminal::SU(Parameters params)
622{
623 unsigned count = 1;
624 if (params.size() >= 1 && params[0] != 0)
625 count = params[0];
626
627 scroll_up(count);
628}
629
630void Terminal::SD(Parameters params)
631{
632 unsigned count = 1;
633 if (params.size() >= 1 && params[0] != 0)
634 count = params[0];
635
636 scroll_down(count);
637}
638
639void Terminal::DECSCUSR(Parameters params)
640{
641 unsigned style = 1;
642 if (params.size() >= 1 && params[0] != 0)
643 style = params[0];
644 switch (style) {
645 case 1:
646 m_client.set_cursor_shape(VT::CursorShape::Block);
647 m_client.set_cursor_blinking(true);
648 break;
649 case 2:
650 m_client.set_cursor_shape(VT::CursorShape::Block);
651 m_client.set_cursor_blinking(false);
652 break;
653 case 3:
654 m_client.set_cursor_shape(VT::CursorShape::Underline);
655 m_client.set_cursor_blinking(true);
656 break;
657 case 4:
658 m_client.set_cursor_shape(VT::CursorShape::Underline);
659 m_client.set_cursor_blinking(false);
660 break;
661 case 5:
662 m_client.set_cursor_shape(VT::CursorShape::Bar);
663 m_client.set_cursor_blinking(true);
664 break;
665 case 6:
666 m_client.set_cursor_shape(VT::CursorShape::Bar);
667 m_client.set_cursor_blinking(false);
668 break;
669 default:
670 dbgln("Unknown cursor style {}", style);
671 }
672}
673
674void Terminal::IL(Parameters params)
675{
676 size_t count = 1;
677 if (params.size() >= 1 && params[0] != 0)
678 count = params[0];
679 if (!is_within_scroll_region(cursor_row())) {
680 dbgln("Shenanigans! Tried to insert line outside the scroll region");
681 return;
682 }
683 scroll_down(cursor_row(), m_scroll_region_bottom, count);
684}
685
686void Terminal::DA(Parameters)
687{
688 emit_string("\033[?1;0c"sv);
689}
690
691void Terminal::DL(Parameters params)
692{
693 size_t count = 1;
694 if (params.size() >= 1 && params[0] != 0)
695 count = params[0];
696 if (!is_within_scroll_region(cursor_row())) {
697 dbgln("Shenanigans! Tried to delete line outside the scroll region");
698 return;
699 }
700 scroll_up(cursor_row(), m_scroll_region_bottom, count);
701}
702
703void Terminal::DCH(Parameters params)
704{
705 int num = 1;
706 if (params.size() >= 1 && params[0] != 0)
707 num = params[0];
708
709 num = min<int>(num, columns() - cursor_column());
710 scroll_left(cursor_row(), cursor_column(), num);
711}
712
713void Terminal::linefeed()
714{
715 u16 new_row = cursor_row();
716#ifndef KERNEL
717 if (!m_controls_are_logically_generated)
718 active_buffer()[new_row]->set_terminated(m_column_before_carriage_return.value_or(cursor_column()));
719#endif
720 if (cursor_row() == m_scroll_region_bottom) {
721 scroll_up();
722 } else {
723 ++new_row;
724 };
725 // We shouldn't jump to the first column after receiving a line feed.
726 // The TTY will take care of generating the carriage return.
727 set_cursor(new_row, cursor_column());
728}
729
730void Terminal::carriage_return()
731{
732 dbgln_if(TERMINAL_DEBUG, "Carriage return");
733 m_column_before_carriage_return = cursor_column();
734 set_cursor(cursor_row(), 0);
735}
736
737void Terminal::scroll_up(size_t count)
738{
739 scroll_up(m_scroll_region_top, m_scroll_region_bottom, count);
740}
741
742void Terminal::scroll_down(size_t count)
743{
744 scroll_down(m_scroll_region_top, m_scroll_region_bottom, count);
745}
746
747#ifndef KERNEL
748// Insert `count` blank lines at the bottom of the region. Text moves up, top lines get added to the scrollback.
749void Terminal::scroll_up(u16 region_top, u16 region_bottom, size_t count)
750{
751 VERIFY(region_top <= region_bottom);
752 VERIFY(region_bottom < rows());
753 // Only the specified region should be affected.
754 size_t region_size = region_bottom - region_top + 1;
755 count = min(count, region_size);
756 dbgln_if(TERMINAL_DEBUG, "Scroll up {} lines in region {}-{}", count, region_top, region_bottom);
757 // NOTE: We have to invalidate the cursor first.
758 invalidate_cursor();
759
760 int history_delta = -count;
761 bool should_move_to_scrollback = !m_use_alternate_screen_buffer && max_history_size() != 0;
762 if (should_move_to_scrollback) {
763 auto remaining_lines = max_history_size() - history_size();
764 history_delta = (count > remaining_lines) ? remaining_lines - count : 0;
765 for (size_t i = 0; i < count; ++i)
766 add_line_to_history(move(active_buffer().at(region_top + i)));
767 }
768
769 // Move lines into their new place.
770 for (u16 row = region_top; row + count <= region_bottom; ++row)
771 swap(active_buffer().at(row), active_buffer().at(row + count));
772 // Clear 'new' lines at the bottom.
773 if (should_move_to_scrollback) {
774 // Since we moved the previous lines into history, we can't just clear them.
775 for (u16 row = region_bottom + 1 - count; row <= region_bottom; ++row)
776 active_buffer().at(row) = make<Line>(columns());
777 } else {
778 // The new lines haven't been moved and we don't want to leak memory.
779 for (u16 row = region_bottom + 1 - count; row <= region_bottom; ++row)
780 active_buffer()[row]->clear();
781 }
782 // Set dirty flag on swapped lines.
783 // The other lines have implicitly been set dirty by being cleared.
784 for (u16 row = region_top; row + count <= region_bottom; ++row)
785 active_buffer()[row]->set_dirty(true);
786 m_client.terminal_history_changed(history_delta);
787}
788
789// Insert `count` blank lines at the top of the region. Text moves down. Does not affect the scrollback buffer.
790void Terminal::scroll_down(u16 region_top, u16 region_bottom, size_t count)
791{
792 VERIFY(region_top <= region_bottom);
793 VERIFY(region_bottom < rows());
794 // Only the specified region should be affected.
795 size_t region_size = region_bottom - region_top + 1;
796 count = min(count, region_size);
797 dbgln_if(TERMINAL_DEBUG, "Scroll down {} lines in region {}-{}", count, region_top, region_bottom);
798 // NOTE: We have to invalidate the cursor first.
799 invalidate_cursor();
800
801 // Move lines into their new place.
802 for (int row = region_bottom; row >= static_cast<int>(region_top + count); --row)
803 swap(active_buffer().at(row), active_buffer().at(row - count));
804 // Clear the 'new' lines at the top.
805 for (u16 row = region_top; row < region_top + count; ++row)
806 active_buffer()[row]->clear();
807 // Set dirty flag on swapped lines.
808 // The other lines have implicitly been set dirty by being cleared.
809 for (u16 row = region_top + count; row <= region_bottom; ++row)
810 active_buffer()[row]->set_dirty(true);
811}
812
813// Insert `count` blank cells at the end of the line. Text moves left.
814void Terminal::scroll_left(u16 row, u16 column, size_t count)
815{
816 VERIFY(row < rows());
817 VERIFY(column < columns());
818 count = min<size_t>(count, columns() - column);
819 dbgln_if(TERMINAL_DEBUG, "Scroll left {} columns from line {} column {}", count, row, column);
820
821 auto& line = active_buffer()[row];
822 for (size_t i = column; i < columns() - count; ++i)
823 swap(line->cell_at(i), line->cell_at(i + count));
824 clear_in_line(row, columns() - count, columns() - 1);
825 line->set_dirty(true);
826}
827
828// Insert `count` blank cells after `row`. Text moves right.
829void Terminal::scroll_right(u16 row, u16 column, size_t count)
830{
831 VERIFY(row < rows());
832 VERIFY(column < columns());
833 count = min<size_t>(count, columns() - column);
834 dbgln_if(TERMINAL_DEBUG, "Scroll right {} columns from line {} column {}", count, row, column);
835
836 auto& line = active_buffer()[row];
837 for (int i = columns() - 1; i >= static_cast<int>(column + count); --i)
838 swap(line->cell_at(i), line->cell_at(i - count));
839 clear_in_line(row, column, column + count - 1);
840 line->set_dirty(true);
841}
842
843void Terminal::put_character_at(unsigned row, unsigned column, u32 code_point)
844{
845 VERIFY(row < rows());
846 VERIFY(column < columns());
847 auto& line = active_buffer()[row];
848 line->set_code_point(column, code_point);
849 line->attribute_at(column) = m_current_state.attribute;
850 line->attribute_at(column).flags |= Attribute::Flags::Touched;
851 line->set_dirty(true);
852
853 m_last_code_point = code_point;
854}
855
856void Terminal::clear_in_line(u16 row, u16 first_column, u16 last_column)
857{
858 VERIFY(row < rows());
859 active_buffer()[row]->clear_range(first_column, last_column, m_current_state.attribute);
860}
861#endif
862
863void Terminal::set_cursor(unsigned a_row, unsigned a_column, bool skip_debug)
864{
865 unsigned row = min(a_row, m_rows - 1u);
866 unsigned column = min(a_column, m_columns - 1u);
867 m_stomp = false;
868 if (row == cursor_row() && column == cursor_column())
869 return;
870 VERIFY(row < rows());
871 VERIFY(column < columns());
872 invalidate_cursor();
873 m_current_state.cursor.row = row;
874 m_current_state.cursor.column = column;
875 invalidate_cursor();
876 if (!skip_debug)
877 dbgln_if(TERMINAL_DEBUG, "Set cursor position: {},{}", cursor_row(), cursor_column());
878}
879
880void Terminal::NEL()
881{
882 if (cursor_row() == m_scroll_region_bottom)
883 scroll_up();
884 else
885 set_cursor(cursor_row() + 1, 0);
886}
887
888void Terminal::IND()
889{
890 // Not equivalent to CUD: if we are at the bottom margin, we have to scroll up.
891 if (cursor_row() == m_scroll_region_bottom)
892 scroll_up();
893 else
894 set_cursor(cursor_row() + 1, cursor_column());
895}
896
897void Terminal::RI()
898{
899 // Not equivalent to CUU : if we at the top margin , we have to scroll down.
900 if (cursor_row() == m_scroll_region_top)
901 scroll_down();
902 else
903 set_cursor(cursor_row() - 1, cursor_column());
904}
905
906void Terminal::DECFI()
907{
908 if (cursor_column() == columns() - 1)
909 scroll_left(cursor_row(), 0, 1);
910 else
911 set_cursor(cursor_row(), cursor_column() + 1);
912}
913
914void Terminal::DECBI()
915{
916 if (cursor_column() == 0)
917 scroll_right(cursor_row(), 0, 1);
918 else
919 set_cursor(cursor_row(), cursor_column() - 1);
920}
921
922void Terminal::DECIC(Parameters params)
923{
924 unsigned num = 1;
925 if (params.size() >= 1 && params[0] != 0)
926 num = params[0];
927
928 num = min<unsigned>(num, columns() - cursor_column());
929 for (unsigned row = cursor_row(); row <= m_scroll_region_bottom; ++row)
930 scroll_right(row, cursor_column(), num);
931}
932
933void Terminal::DECDC(Parameters params)
934{
935 unsigned num = 1;
936 if (params.size() >= 1 && params[0] != 0)
937 num = params[0];
938
939 num = min<unsigned>(num, columns() - cursor_column());
940 for (unsigned row = cursor_row(); row <= m_scroll_region_bottom; ++row)
941 scroll_left(row, cursor_column(), num);
942}
943
944void Terminal::DECPNM()
945{
946 dbgln("FIXME: implement setting the keypad to numeric mode");
947}
948
949void Terminal::DECPAM()
950{
951 dbgln("FIXME: implement setting the keypad to application mode");
952}
953
954void Terminal::DSR(Parameters params)
955{
956 if (params.size() == 1 && params[0] == 5) {
957 // Device status
958 emit_string("\033[0n"sv); // Terminal status OK!
959 } else if (params.size() == 1 && params[0] == 6) {
960 // Cursor position query
961 StringBuilder builder;
962 MUST(builder.try_appendff("\e[{};{}R", cursor_row() + 1, cursor_column() + 1)); // StringBuilder's inline capacity of 256 is enough to guarantee no allocations
963 emit_string(builder.string_view());
964 } else {
965 dbgln("Unknown DSR");
966 }
967}
968
969void Terminal::ICH(Parameters params)
970{
971 unsigned num = 1;
972 if (params.size() >= 1 && params[0] != 0)
973 num = params[0];
974
975 num = min<unsigned>(num, columns() - cursor_column());
976 scroll_right(cursor_row(), cursor_column(), num);
977}
978
979void Terminal::on_input(u8 byte)
980{
981 m_parser.on_input(byte);
982}
983
984void Terminal::emit_code_point(u32 code_point)
985{
986 auto working_set = m_working_sets[m_active_working_set_index];
987 code_point = m_character_set_translator.translate_code_point(working_set, code_point);
988
989 auto new_column = cursor_column() + 1;
990 if (new_column < columns()) {
991 put_character_at(cursor_row(), cursor_column(), code_point);
992 set_cursor(cursor_row(), new_column, true);
993 return;
994 }
995 if (m_stomp) {
996 m_stomp = false;
997 TemporaryChange change { m_controls_are_logically_generated, true };
998 carriage_return();
999 linefeed();
1000 put_character_at(cursor_row(), cursor_column(), code_point);
1001 set_cursor(cursor_row(), 1);
1002 } else {
1003 // Curious: We wait once on the right-hand side
1004 m_stomp = true;
1005 put_character_at(cursor_row(), cursor_column(), code_point);
1006 }
1007}
1008
1009void Terminal::execute_control_code(u8 code)
1010{
1011 ArmedScopeGuard clear_position_before_cr {
1012 [&] {
1013 m_column_before_carriage_return.clear();
1014 }
1015 };
1016 switch (code) {
1017 case '\a':
1018 m_client.beep();
1019 return;
1020 case '\b':
1021 if (cursor_column()) {
1022 set_cursor(cursor_row(), cursor_column() - 1);
1023 return;
1024 }
1025 return;
1026 case '\t': {
1027 for (unsigned i = cursor_column() + 1; i < columns(); ++i) {
1028 if (m_horizontal_tabs[i]) {
1029 set_cursor(cursor_row(), i);
1030 return;
1031 }
1032 }
1033 return;
1034 }
1035 case '\n':
1036 case '\v':
1037 case '\f':
1038 if (m_column_before_carriage_return == m_columns - 1)
1039 m_column_before_carriage_return = m_columns;
1040 linefeed();
1041 return;
1042 case '\r':
1043 carriage_return();
1044 clear_position_before_cr.disarm();
1045 return;
1046 default:
1047 unimplemented_control_code(code);
1048 }
1049}
1050
1051void Terminal::execute_escape_sequence(Intermediates intermediates, bool ignore, u8 last_byte)
1052{
1053 // FIXME: Handle it somehow?
1054 if (ignore)
1055 dbgln("Escape sequence has its ignore flag set.");
1056
1057 if (intermediates.size() == 0) {
1058 switch (last_byte) {
1059 case 'D':
1060 IND();
1061 return;
1062 case 'E':
1063 NEL();
1064 return;
1065 case 'M':
1066 RI();
1067 return;
1068 case '\\':
1069 // ST (string terminator) -- do nothing
1070 return;
1071 case '6':
1072 DECBI();
1073 return;
1074 case '7':
1075 DECSC();
1076 return;
1077 case '8':
1078 DECRC();
1079 return;
1080 case '9':
1081 DECFI();
1082 return;
1083 case '=':
1084 DECPAM();
1085 return;
1086 case '>':
1087 DECPNM();
1088 return;
1089 }
1090 unimplemented_escape_sequence(intermediates, last_byte);
1091 return;
1092 }
1093
1094 char intermediate = intermediates[0];
1095 switch (intermediate) {
1096 case '#':
1097 switch (last_byte) {
1098 case '8':
1099 // Confidence Test - Fill screen with E's
1100 for (size_t row = 0; row < m_rows; ++row) {
1101 for (size_t column = 0; column < m_columns; ++column) {
1102 put_character_at(row, column, 'E');
1103 }
1104 }
1105 return;
1106 }
1107 break;
1108 case '(':
1109 case ')':
1110 case '*':
1111 case '+':
1112 // Determine G0..G3 index
1113 size_t working_set_index = intermediate - '(';
1114
1115 CharacterSet new_set;
1116 switch (last_byte) {
1117 case 'B':
1118 new_set = CharacterSet::Iso_8859_1;
1119 break;
1120 case '0':
1121 new_set = CharacterSet::VT100;
1122 break;
1123 case 'U':
1124 new_set = CharacterSet::Null;
1125 break;
1126 case 'K':
1127 new_set = CharacterSet::UserDefined;
1128 break;
1129 default:
1130 unimplemented_escape_sequence(intermediates, last_byte);
1131 return;
1132 }
1133
1134 dbgln_if(TERMINAL_DEBUG, "Setting G{} working set to character set {}", working_set_index, to_underlying(new_set));
1135 VERIFY(working_set_index <= 3);
1136 m_working_sets[working_set_index] = new_set;
1137 return;
1138 }
1139
1140 unimplemented_escape_sequence(intermediates, last_byte);
1141}
1142
1143void Terminal::execute_csi_sequence(Parameters parameters, Intermediates intermediates, bool ignore, u8 last_byte)
1144{
1145 // FIXME: Handle it somehow?
1146 if (ignore)
1147 dbgln("CSI sequence has its ignore flag set.");
1148
1149 if (intermediates.is_empty()) {
1150 switch (last_byte) {
1151 case '@':
1152 return ICH(parameters);
1153 case 'A':
1154 return CUU(parameters);
1155 case 'B':
1156 return CUD(parameters);
1157 case 'C':
1158 return CUF(parameters);
1159 case 'D':
1160 return CUB(parameters);
1161 case 'E':
1162 return CNL(parameters);
1163 case 'F':
1164 return CPL(parameters);
1165 case 'G':
1166 return CHA(parameters);
1167 case 'H':
1168 return CUP(parameters);
1169 case 'J':
1170 return ED(parameters);
1171 case 'K':
1172 return EL(parameters);
1173 case 'L':
1174 return IL(parameters);
1175 case 'M':
1176 return DL(parameters);
1177 case 'P':
1178 return DCH(parameters);
1179 case 'S':
1180 return SU(parameters);
1181 case 'T':
1182 return SD(parameters);
1183 case 'X':
1184 return ECH(parameters);
1185 case '`':
1186 return HPA(parameters);
1187 case 'a':
1188 return HPR(parameters);
1189 case 'b':
1190 return REP(parameters);
1191 case 'c':
1192 return DA(parameters);
1193 case 'd':
1194 return VPA(parameters);
1195 case 'e':
1196 return VPR(parameters);
1197 case 'f':
1198 return HVP(parameters);
1199 case 'h':
1200 return SM(parameters);
1201 case 'l':
1202 return RM(parameters);
1203 case 'm':
1204 return SGR(parameters);
1205 case 'n':
1206 return DSR(parameters);
1207 case 'r':
1208 return DECSTBM(parameters);
1209 case 's':
1210 return SCOSC();
1211 case 't':
1212 return XTERM_WM(parameters);
1213 case 'u':
1214 return SCORC();
1215 }
1216 } else if (intermediates.size() == 1 && intermediates[0] == '?') {
1217 switch (last_byte) {
1218 case 'h':
1219 return DECSET(parameters);
1220 case 'l':
1221 return DECRST(parameters);
1222 }
1223 } else if (intermediates.size() == 1 && intermediates[0] == '\'') {
1224 switch (last_byte) {
1225 case '}':
1226 return DECIC(parameters);
1227 case '~':
1228 return DECDC(parameters);
1229 }
1230 } else if (intermediates.size() == 1 && intermediates[0] == ' ') {
1231 switch (last_byte) {
1232 case 'q':
1233 return DECSCUSR(parameters);
1234 }
1235 }
1236
1237 unimplemented_csi_sequence(parameters, intermediates, last_byte);
1238}
1239
1240void Terminal::execute_osc_sequence(OscParameters parameters, u8 last_byte)
1241{
1242 auto stringview_ify = [&](size_t param_idx) {
1243 return StringView(parameters[param_idx]);
1244 };
1245
1246 if (parameters.size() == 0 || parameters[0].is_empty()) {
1247 unimplemented_osc_sequence(parameters, last_byte);
1248 return;
1249 }
1250
1251 auto command_number = stringview_ify(0).to_uint();
1252 if (!command_number.has_value()) {
1253 unimplemented_osc_sequence(parameters, last_byte);
1254 return;
1255 }
1256
1257 switch (command_number.value()) {
1258 case 0:
1259 case 1:
1260 case 2:
1261 if (parameters.size() < 2) {
1262 dbgln("Attempted to set window title without any parameters");
1263 } else {
1264 // FIXME: the split breaks titles containing semicolons.
1265 // Should we expose the raw OSC string from the parser? Or join by semicolon?
1266#ifndef KERNEL
1267 m_current_window_title = stringview_ify(1).to_deprecated_string();
1268 m_client.set_window_title(m_current_window_title);
1269#endif
1270 }
1271 break;
1272 case 8:
1273#ifndef KERNEL
1274 if (parameters.size() < 3) {
1275 dbgln("Attempted to set href but gave too few parameters");
1276 } else if (parameters[1].is_empty() && parameters[2].is_empty()) {
1277 // Clear hyperlink
1278 m_current_state.attribute.href = DeprecatedString();
1279 m_current_state.attribute.href_id = DeprecatedString();
1280 } else {
1281 m_current_state.attribute.href = stringview_ify(2);
1282 // FIXME: Respect the provided ID
1283 m_current_state.attribute.href_id = DeprecatedString::number(m_next_href_id++);
1284 }
1285#endif
1286 break;
1287 case 9:
1288 if (parameters.size() < 2)
1289 dbgln("Atttempted to set window progress but gave too few parameters");
1290 else if (parameters.size() == 2)
1291 m_client.set_window_progress(stringview_ify(1).to_int().value_or(-1), 0);
1292 else
1293 m_client.set_window_progress(stringview_ify(1).to_int().value_or(-1), stringview_ify(2).to_int().value_or(0));
1294 break;
1295 default:
1296 unimplemented_osc_sequence(parameters, last_byte);
1297 }
1298}
1299
1300void Terminal::dcs_hook(Parameters, Intermediates, bool, u8)
1301{
1302 dbgln("Received DCS parameters, but we don't support it yet");
1303}
1304
1305void Terminal::receive_dcs_char(u8 byte)
1306{
1307 dbgln_if(TERMINAL_DEBUG, "DCS string character {:c}", byte);
1308}
1309
1310void Terminal::execute_dcs_sequence()
1311{
1312}
1313
1314void Terminal::inject_string(StringView str)
1315{
1316 for (size_t i = 0; i < str.length(); ++i)
1317 on_input(str[i]);
1318}
1319
1320void Terminal::emit_string(StringView string)
1321{
1322 m_client.emit((u8 const*)string.characters_without_null_termination(), string.length());
1323}
1324
1325void Terminal::handle_key_press(KeyCode key, u32 code_point, u8 flags)
1326{
1327 bool ctrl = flags & Mod_Ctrl;
1328 bool alt = flags & Mod_Alt;
1329 bool shift = flags & Mod_Shift;
1330 unsigned modifier_mask = int(shift) + (int(alt) << 1) + (int(ctrl) << 2);
1331
1332 auto emit_final_with_modifier = [this, modifier_mask](char final) {
1333 char escape_character = m_cursor_keys_mode == CursorKeysMode::Application ? 'O' : '[';
1334 StringBuilder builder;
1335 if (modifier_mask)
1336 MUST(builder.try_appendff("\e{}1;{}{:c}", escape_character, modifier_mask + 1, final)); // StringBuilder's inline capacity of 256 is enough to guarantee no allocations
1337 else
1338 MUST(builder.try_appendff("\e{}{:c}", escape_character, final)); // StringBuilder's inline capacity of 256 is enough to guarantee no allocations
1339 emit_string(builder.string_view());
1340 };
1341 auto emit_tilde_with_modifier = [this, modifier_mask](unsigned num) {
1342 StringBuilder builder;
1343 if (modifier_mask)
1344 MUST(builder.try_appendff("\e[{};{}~", num, modifier_mask + 1)); // StringBuilder's inline capacity of 256 is enough to guarantee no allocations
1345 else
1346 MUST(builder.try_appendff("\e[{}~", num)); // StringBuilder's inline capacity of 256 is enough to guarantee no allocations
1347 emit_string(builder.string_view());
1348 };
1349
1350 switch (key) {
1351 case KeyCode::Key_Up:
1352 emit_final_with_modifier('A');
1353 return;
1354 case KeyCode::Key_Down:
1355 emit_final_with_modifier('B');
1356 return;
1357 case KeyCode::Key_Right:
1358 emit_final_with_modifier('C');
1359 return;
1360 case KeyCode::Key_Left:
1361 emit_final_with_modifier('D');
1362 return;
1363 case KeyCode::Key_Insert:
1364 emit_tilde_with_modifier(2);
1365 return;
1366 case KeyCode::Key_Delete:
1367 emit_tilde_with_modifier(3);
1368 return;
1369 case KeyCode::Key_Home:
1370 emit_final_with_modifier('H');
1371 return;
1372 case KeyCode::Key_End:
1373 emit_final_with_modifier('F');
1374 return;
1375 case KeyCode::Key_PageUp:
1376 emit_tilde_with_modifier(5);
1377 return;
1378 case KeyCode::Key_PageDown:
1379 emit_tilde_with_modifier(6);
1380 return;
1381 case KeyCode::Key_Backspace:
1382 if (ctrl) {
1383 // This is an extension that allows Editor.cpp to delete whole words when
1384 // Ctrl+Backspace is pressed. Ctrl cannot be transmitted without a CSI, and
1385 // ANSI delete (127) is within the valid range for CSI codes in Editor.cpp.
1386 // The code also has the same behavior as backspace when emitted with no CSI,
1387 // though the backspace code (8) is preserved when Ctrl is not pressed.
1388 emit_final_with_modifier(127);
1389 return;
1390 }
1391 break;
1392 case KeyCode::Key_Return:
1393 // The standard says that CR should be generated by the return key.
1394 // The TTY will take care of translating it to CR LF for the terminal.
1395 emit_string("\r"sv);
1396 return;
1397 default:
1398 break;
1399 }
1400
1401 if (!code_point) {
1402 // Probably a modifier being pressed.
1403 return;
1404 }
1405
1406 if (shift && key == KeyCode::Key_Tab) {
1407 emit_string("\033[Z"sv);
1408 return;
1409 }
1410
1411 // Key event was not one of the above special cases,
1412 // attempt to treat it as a character...
1413 if (ctrl) {
1414 if (code_point >= 'a' && code_point <= 'z') {
1415 code_point = code_point - 'a' + 1;
1416 } else if (code_point == '\\') {
1417 code_point = 0x1c;
1418 }
1419 }
1420
1421 // Alt modifier sends escape prefix.
1422 if (alt)
1423 emit_string("\033"sv);
1424
1425 StringBuilder sb;
1426 sb.append_code_point(code_point);
1427 emit_string(sb.string_view());
1428}
1429
1430void Terminal::unimplemented_control_code(u8 code)
1431{
1432 dbgln_if(TERMINAL_DEBUG, "Unimplemented control code {:02x}", code);
1433}
1434
1435void Terminal::unimplemented_escape_sequence(Intermediates intermediates, u8 last_byte)
1436{
1437 StringBuilder builder;
1438 builder.appendff("Unimplemented escape sequence {:c}", last_byte);
1439 if (!intermediates.is_empty()) {
1440 builder.append(", intermediates: "sv);
1441 for (size_t i = 0; i < intermediates.size(); ++i)
1442 builder.append((char)intermediates[i]);
1443 }
1444 dbgln("{}", builder.string_view());
1445}
1446
1447void Terminal::unimplemented_csi_sequence(Parameters parameters, Intermediates intermediates, u8 last_byte)
1448{
1449 StringBuilder builder;
1450 builder.appendff("Unimplemented CSI sequence: {:c}", last_byte);
1451 if (!parameters.is_empty()) {
1452 builder.append(", parameters: ["sv);
1453 for (size_t i = 0; i < parameters.size(); ++i)
1454 builder.appendff("{}{}", (i == 0) ? "" : ", ", parameters[i]);
1455 builder.append("]"sv);
1456 }
1457 if (!intermediates.is_empty()) {
1458 builder.append(", intermediates:"sv);
1459 for (size_t i = 0; i < intermediates.size(); ++i)
1460 builder.append((char)intermediates[i]);
1461 }
1462 dbgln("{}", builder.string_view());
1463}
1464
1465void Terminal::unimplemented_osc_sequence(OscParameters parameters, u8 last_byte)
1466{
1467 StringBuilder builder;
1468 builder.appendff("Unimplemented OSC sequence parameters: (bel_terminated={}) [ ", last_byte == '\a');
1469 bool first = true;
1470 for (auto parameter : parameters) {
1471 if (!first)
1472 builder.append(", "sv);
1473 builder.append('[');
1474 for (auto character : parameter)
1475 builder.append((char)character);
1476 builder.append(']');
1477 first = false;
1478 }
1479
1480 builder.append(" ]"sv);
1481 dbgln("{}", builder.string_view());
1482}
1483
1484#ifndef KERNEL
1485void Terminal::set_size(u16 columns, u16 rows)
1486{
1487 if (!columns)
1488 columns = 1;
1489 if (!rows)
1490 rows = 1;
1491
1492 if (columns == m_columns && rows == m_rows)
1493 return;
1494
1495 // If we're making the terminal larger (column-wise), start at the end and go up, taking cells from the line below.
1496 // otherwise start at the beginning and go down, pushing cells into the line below.
1497 auto resize_and_rewrap = [&](auto& buffer, auto& old_cursor) {
1498 auto cursor_on_line = [&](auto index) {
1499 return index == old_cursor.row ? &old_cursor : nullptr;
1500 };
1501 // Two passes, one from top to bottom, another from bottom to top
1502 for (size_t pass = 0; pass < 2; ++pass) {
1503 auto forwards = (pass == 0) ^ (columns < m_columns);
1504 if (forwards) {
1505 for (size_t i = 1; i <= buffer.size(); ++i) {
1506 auto is_at_seam = i == 1;
1507 Line* next_line = is_at_seam ? nullptr : buffer[buffer.size() - i + 1].ptr();
1508 Line* line = buffer[buffer.size() - i].ptr();
1509 auto next_cursor = cursor_on_line(buffer.size() - i + 1);
1510 line->rewrap(columns, next_line, next_cursor ?: cursor_on_line(buffer.size() - i), !!next_cursor);
1511 }
1512 } else {
1513 for (size_t i = 0; i < buffer.size(); ++i) {
1514 auto is_at_seam = i + 1 == buffer.size();
1515 Line* next_line = is_at_seam ? nullptr : buffer[i + 1].ptr();
1516 auto next_cursor = cursor_on_line(i + 1);
1517 buffer[i]->rewrap(columns, next_line, next_cursor ?: cursor_on_line(i), !!next_cursor);
1518 }
1519 }
1520
1521 Queue<size_t> lines_to_reevaluate;
1522 for (size_t i = 0; i < buffer.size(); ++i) {
1523 if (buffer[i]->length() != columns)
1524 lines_to_reevaluate.enqueue(i);
1525 }
1526 while (!lines_to_reevaluate.is_empty()) {
1527 auto index = lines_to_reevaluate.dequeue();
1528 auto is_at_seam = index + 1 == buffer.size();
1529 Line* const next_line = is_at_seam ? nullptr : buffer[index + 1].ptr();
1530 Line* const line = buffer[index].ptr();
1531 auto next_cursor = cursor_on_line(index + 1);
1532 line->rewrap(columns, next_line, next_cursor ?: cursor_on_line(index), !!next_cursor);
1533 if (line->length() > columns) {
1534 auto current_cursor = cursor_on_line(index);
1535 // Split the line into two (or more)
1536 ++index;
1537 buffer.insert(index, make<Line>(0));
1538 VERIFY(buffer[index]->length() == 0);
1539 line->rewrap(columns, buffer[index].ptr(), current_cursor, false);
1540 // If we inserted a line and the old cursor was after that line, increment its row
1541 if (!current_cursor && old_cursor.row >= index)
1542 ++old_cursor.row;
1543
1544 if (buffer[index]->length() != columns)
1545 lines_to_reevaluate.enqueue(index);
1546 }
1547 if (next_line && next_line->length() != columns)
1548 lines_to_reevaluate.enqueue(index + 1);
1549 }
1550 }
1551
1552 for (auto& line : buffer)
1553 line->set_length(columns);
1554
1555 return old_cursor;
1556 };
1557
1558 auto old_history_size = m_history.size();
1559 m_history.extend(move(m_normal_screen_buffer));
1560 CursorPosition cursor_tracker { cursor_row() + old_history_size, cursor_column() };
1561 resize_and_rewrap(m_history, cursor_tracker);
1562 if (auto extra_lines = m_history.size() - rows) {
1563 while (extra_lines > 0) {
1564 if (m_history.size() <= cursor_tracker.row)
1565 break;
1566 if (m_history.last()->is_empty()) {
1567 if (m_history.size() >= 2 && m_history[m_history.size() - 2]->termination_column().has_value())
1568 break;
1569 --extra_lines;
1570 (void)m_history.take_last();
1571 continue;
1572 }
1573 break;
1574 }
1575 }
1576
1577 // FIXME: This can use a more performant way to move the last N entries
1578 // from the history into the normal buffer
1579 m_normal_screen_buffer.ensure_capacity(rows);
1580 while (m_normal_screen_buffer.size() < rows) {
1581 if (!m_history.is_empty())
1582 m_normal_screen_buffer.prepend(m_history.take_last());
1583 else
1584 m_normal_screen_buffer.unchecked_append(make<Line>(columns));
1585 }
1586
1587 cursor_tracker.row -= m_history.size();
1588
1589 if (m_history.size() != old_history_size) {
1590 m_client.terminal_history_changed(-old_history_size);
1591 m_client.terminal_history_changed(m_history.size());
1592 }
1593
1594 CursorPosition dummy_cursor_tracker {};
1595 resize_and_rewrap(m_alternate_screen_buffer, dummy_cursor_tracker);
1596 if (m_alternate_screen_buffer.size() > rows)
1597 m_alternate_screen_buffer.remove(0, m_alternate_screen_buffer.size() - rows);
1598
1599 if (rows > m_rows) {
1600 while (m_normal_screen_buffer.size() < rows)
1601 m_normal_screen_buffer.append(make<Line>(columns));
1602 while (m_alternate_screen_buffer.size() < rows)
1603 m_alternate_screen_buffer.append(make<Line>(columns));
1604 } else {
1605 m_normal_screen_buffer.shrink(rows);
1606 m_alternate_screen_buffer.shrink(rows);
1607 }
1608
1609 m_columns = columns;
1610 m_rows = rows;
1611
1612 m_scroll_region_top = 0;
1613 m_scroll_region_bottom = rows - 1;
1614
1615 m_current_state.cursor.clamp(m_rows - 1, m_columns - 1);
1616 m_normal_saved_state.cursor.clamp(m_rows - 1, m_columns - 1);
1617 m_alternate_saved_state.cursor.clamp(m_rows - 1, m_columns - 1);
1618 m_saved_cursor_position.clamp(m_rows - 1, m_columns - 1);
1619
1620 m_horizontal_tabs.resize(columns);
1621 for (unsigned i = 0; i < columns; ++i)
1622 m_horizontal_tabs[i] = (i % 8) == 0;
1623 // Rightmost column is always last tab on line.
1624 m_horizontal_tabs[columns - 1] = 1;
1625
1626 set_cursor(cursor_tracker.row, cursor_tracker.column);
1627
1628 m_client.terminal_did_resize(m_columns, m_rows);
1629
1630 dbgln_if(TERMINAL_DEBUG, "Set terminal size: {}x{}", m_rows, m_columns);
1631}
1632#endif
1633
1634#ifndef KERNEL
1635void Terminal::invalidate_cursor()
1636{
1637 if (cursor_row() < active_buffer().size())
1638 active_buffer()[cursor_row()]->set_dirty(true);
1639}
1640
1641Attribute Terminal::attribute_at(Position const& position) const
1642{
1643 if (!position.is_valid())
1644 return {};
1645 if (position.row() >= static_cast<int>(line_count()))
1646 return {};
1647 auto& line = this->line(position.row());
1648 if (static_cast<size_t>(position.column()) >= line.length())
1649 return {};
1650 return line.attribute_at(position.column());
1651}
1652#endif
1653}