Serenity Operating System
at master 1653 lines 52 kB view raw
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}