diff --git a/Kernel/TTY/VirtualConsole.cpp b/Kernel/TTY/VirtualConsole.cpp index feaec5ad4f..fe77f24704 100644 --- a/Kernel/TTY/VirtualConsole.cpp +++ b/Kernel/TTY/VirtualConsole.cpp @@ -47,11 +47,10 @@ void ConsoleImpl::set_size(u16 determined_columns, u16 determined_rows) m_columns = determined_columns; m_rows = determined_rows; - m_cursor_row = min((int)m_cursor_row, rows() - 1); - m_cursor_column = min((int)m_cursor_column, columns() - 1); - m_saved_cursor_row = min((int)m_saved_cursor_row, rows() - 1); - m_saved_cursor_column = min((int)m_saved_cursor_column, columns() - 1); - + m_current_state.cursor.clamp(rows() - 1, columns() - 1); + m_normal_saved_state.cursor.clamp(rows() - 1, columns() - 1); + m_alternate_saved_state.cursor.clamp(rows() - 1, columns() - 1); + m_saved_cursor_position.clamp(rows() - 1, columns() - 1); m_horizontal_tabs.resize(determined_columns); for (unsigned i = 0; i < determined_columns; ++i) m_horizontal_tabs[i] = (i % 8) == 0; @@ -62,7 +61,7 @@ void ConsoleImpl::set_size(u16 determined_columns, u16 determined_rows) void ConsoleImpl::scroll_up() { // NOTE: We have to invalidate the cursor first. - m_client.invalidate_cursor(m_cursor_row); + m_client.invalidate_cursor(cursor_row()); m_client.scroll_up(); } void ConsoleImpl::scroll_down() @@ -70,7 +69,7 @@ void ConsoleImpl::scroll_down() } void ConsoleImpl::linefeed() { - u16 new_row = m_cursor_row; + u16 new_row = cursor_row(); u16 max_row = rows() - 1; if (new_row == max_row) { // NOTE: We have to invalidate the cursor first. @@ -83,7 +82,7 @@ void ConsoleImpl::linefeed() } void ConsoleImpl::put_character_at(unsigned row, unsigned column, u32 ch) { - m_client.put_character_at(row, column, ch, m_current_attribute); + m_client.put_character_at(row, column, ch, m_current_state.attribute); m_last_code_point = ch; } void ConsoleImpl::set_window_title(const String&) diff --git a/Userland/Libraries/LibVT/Terminal.cpp b/Userland/Libraries/LibVT/Terminal.cpp index 0e1bd262b8..df457be975 100644 --- a/Userland/Libraries/LibVT/Terminal.cpp +++ b/Userland/Libraries/LibVT/Terminal.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, Daniel Bertalan * * SPDX-License-Identifier: BSD-2-Clause */ @@ -29,7 +30,7 @@ Terminal::Terminal(Kernel::VirtualConsole& client) void Terminal::clear() { for (size_t i = 0; i < rows(); ++i) - m_lines[i].clear(m_current_attribute); + active_buffer()[i].clear(m_current_state.attribute); set_cursor(0, 0); } @@ -105,6 +106,45 @@ void Terminal::alter_mode(bool should_set, Parameters params, Intermediates inte m_client.set_cursor_style(None); } break; + case 1047: +#ifndef KERNEL + if (should_set) { + dbgln_if(TERMINAL_DEBUG, "Switching to Alternate Screen Buffer"); + m_use_alternate_screen_buffer = true; + clear(); + } else { + dbgln_if(TERMINAL_DEBUG, "Switching to Normal Screen Buffer"); + m_use_alternate_screen_buffer = false; + } + m_need_full_flush = true; +#else + dbgln("Alternate Screen Buffer is not supported"); +#endif + break; + case 1048: + if (should_set) + SCOSC(); + else + SCORC(); + break; + case 1049: +#ifndef KERNEL + if (should_set) { + dbgln_if(TERMINAL_DEBUG, "Switching to Alternate Screen Buffer and saving state"); + m_normal_saved_state = m_current_state; + m_use_alternate_screen_buffer = true; + clear(); + } else { + dbgln_if(TERMINAL_DEBUG, "Switching to Normal Screen Buffer and restoring state"); + m_current_state = m_normal_saved_state; + m_use_alternate_screen_buffer = false; + set_cursor(cursor_row(), cursor_column()); + } + m_need_full_flush = true; +#else + dbgln("Alternate Screen Buffer is not supported"); +#endif + break; case 2004: dbgln_if(TERMINAL_DEBUG, "Setting bracketed mode enabled={}", should_set); m_needs_bracketed_paste = should_set; @@ -139,10 +179,9 @@ void Terminal::SM(Parameters params, Intermediates intermediates) void Terminal::SGR(Parameters params) { if (params.is_empty()) { - m_current_attribute.reset(); + m_current_state.attribute.reset(); return; } - auto parse_color = [&]() -> Optional { if (params.size() < 2) { dbgln("Color code has no type"); @@ -173,46 +212,46 @@ void Terminal::SGR(Parameters params) }; if (params[0] == 38) { - m_current_attribute.foreground_color = parse_color().value_or(m_current_attribute.foreground_color); + m_current_state.attribute.foreground_color = parse_color().value_or(m_current_state.attribute.foreground_color); } else if (params[0] == 48) { - m_current_attribute.background_color = parse_color().value_or(m_current_attribute.background_color); + m_current_state.attribute.background_color = parse_color().value_or(m_current_state.attribute.background_color); } else { // A single escape sequence may set multiple parameters. for (auto param : params) { switch (param) { case 0: // Reset - m_current_attribute.reset(); + m_current_state.attribute.reset(); break; case 1: - m_current_attribute.flags |= Attribute::Bold; + m_current_state.attribute.flags |= Attribute::Bold; break; case 3: - m_current_attribute.flags |= Attribute::Italic; + m_current_state.attribute.flags |= Attribute::Italic; break; case 4: - m_current_attribute.flags |= Attribute::Underline; + m_current_state.attribute.flags |= Attribute::Underline; break; case 5: - m_current_attribute.flags |= Attribute::Blink; + m_current_state.attribute.flags |= Attribute::Blink; break; case 7: - m_current_attribute.flags |= Attribute::Negative; + m_current_state.attribute.flags |= Attribute::Negative; break; case 22: - m_current_attribute.flags &= ~Attribute::Bold; + m_current_state.attribute.flags &= ~Attribute::Bold; break; case 23: - m_current_attribute.flags &= ~Attribute::Italic; + m_current_state.attribute.flags &= ~Attribute::Italic; break; case 24: - m_current_attribute.flags &= ~Attribute::Underline; + m_current_state.attribute.flags &= ~Attribute::Underline; break; case 25: - m_current_attribute.flags &= ~Attribute::Blink; + m_current_state.attribute.flags &= ~Attribute::Blink; break; case 27: - m_current_attribute.flags &= ~Attribute::Negative; + m_current_state.attribute.flags &= ~Attribute::Negative; break; case 30: case 31: @@ -223,13 +262,13 @@ void Terminal::SGR(Parameters params) case 36: case 37: // Foreground color - if (m_current_attribute.flags & Attribute::Bold) + if (m_current_state.attribute.flags & Attribute::Bold) param += 8; - m_current_attribute.foreground_color = xterm_colors[param - 30]; + m_current_state.attribute.foreground_color = xterm_colors[param - 30]; break; case 39: // reset foreground - m_current_attribute.foreground_color = Attribute::default_foreground_color; + m_current_state.attribute.foreground_color = Attribute::default_foreground_color; break; case 40: case 41: @@ -240,13 +279,13 @@ void Terminal::SGR(Parameters params) case 46: case 47: // Background color - if (m_current_attribute.flags & Attribute::Bold) + if (m_current_state.attribute.flags & Attribute::Bold) param += 8; - m_current_attribute.background_color = xterm_colors[param - 40]; + m_current_state.attribute.background_color = xterm_colors[param - 40]; break; case 49: // reset background - m_current_attribute.background_color = Attribute::default_background_color; + m_current_state.attribute.background_color = Attribute::default_background_color; break; default: dbgln("FIXME: SGR: p: {}", param); @@ -257,14 +296,36 @@ void Terminal::SGR(Parameters params) void Terminal::SCOSC() { - m_saved_cursor_row = m_cursor_row; - m_saved_cursor_column = m_cursor_column; - m_saved_attribute = m_current_attribute; + dbgln_if(TERMINAL_DEBUG, "Save cursor position"); + m_saved_cursor_position = m_current_state.cursor; } -void Terminal::SCORC(Parameters) +void Terminal::SCORC() { - set_cursor(m_saved_cursor_row, m_saved_cursor_column); + dbgln_if(TERMINAL_DEBUG, "Restore cursor position"); + m_current_state.cursor = m_saved_cursor_position; + set_cursor(cursor_row(), cursor_column()); +} + +void Terminal::DECSC() +{ + dbgln_if(TERMINAL_DEBUG, "Save cursor (and other state)"); + if (m_use_alternate_screen_buffer) { + m_alternate_saved_state = m_current_state; + } else { + m_normal_saved_state = m_current_state; + } +} + +void Terminal::DECRC() +{ + dbgln_if(TERMINAL_DEBUG, "Restore cursor (and other state)"); + if (m_use_alternate_screen_buffer) { + m_current_state = m_alternate_saved_state; + } else { + m_current_state = m_normal_saved_state; + } + set_cursor(cursor_row(), cursor_column()); } void Terminal::XTERM_WM(Parameters params) @@ -331,130 +392,117 @@ void Terminal::HVP(Parameters params) { unsigned row = 1; unsigned col = 1; - if (params.size() >= 1) + if (params.size() >= 1 && params[0] != 0) row = params[0]; - if (params.size() >= 2) + if (params.size() >= 2 && params[1] != 0) col = params[1]; set_cursor(row - 1, col - 1); } void Terminal::CUU(Parameters params) { - int num = 1; - if (params.size() >= 1) + unsigned num = 1; + if (params.size() >= 1 && params[0] != 0) num = params[0]; - if (num == 0) - num = 1; - int new_row = (int)m_cursor_row - num; + int new_row = cursor_row() - num; if (new_row < 0) new_row = 0; - set_cursor(new_row, m_cursor_column); + set_cursor(new_row, cursor_column()); } void Terminal::CUD(Parameters params) { - int num = 1; - if (params.size() >= 1) + unsigned num = 1; + if (params.size() >= 1 && params[0] != 0) num = params[0]; - if (num == 0) - num = 1; - int new_row = (int)m_cursor_row + num; + unsigned new_row = cursor_row() + num; if (new_row >= m_rows) new_row = m_rows - 1; - set_cursor(new_row, m_cursor_column); + set_cursor(new_row, cursor_column()); } void Terminal::CUF(Parameters params) { - int num = 1; - if (params.size() >= 1) + unsigned num = 1; + if (params.size() >= 1 && params[0] != 0) num = params[0]; - if (num == 0) - num = 1; - int new_column = (int)m_cursor_column + num; + unsigned new_column = cursor_column() + num; if (new_column >= m_columns) new_column = m_columns - 1; - set_cursor(m_cursor_row, new_column); + set_cursor(cursor_row(), new_column); } void Terminal::CUB(Parameters params) { - int num = 1; - if (params.size() >= 1) + unsigned num = 1; + if (params.size() >= 1 && params[0] != 0) num = params[0]; - if (num == 0) - num = 1; - int new_column = (int)m_cursor_column - num; + int new_column = (int)cursor_column() - num; if (new_column < 0) new_column = 0; - set_cursor(m_cursor_row, new_column); + set_cursor(cursor_row(), new_column); } void Terminal::CHA(Parameters params) { - int new_column = 1; - if (params.size() >= 1) + unsigned new_column = 1; + if (params.size() >= 1 && params[0] != 0) new_column = params[0] - 1; - if (new_column < 0) - new_column = 0; - set_cursor(m_cursor_row, new_column); + set_cursor(cursor_row(), new_column); } void Terminal::REP(Parameters params) { - if (params.size() < 1) - return; + unsigned count = 1; + if (params.size() >= 1 && params[0] != 0) + count = params[0]; - for (unsigned i = 0; i < params[0]; ++i) - put_character_at(m_cursor_row, m_cursor_column++, m_last_code_point); + for (unsigned i = 0; i < count; ++i) + put_character_at(m_current_state.cursor.row, m_current_state.cursor.column++, m_last_code_point); } void Terminal::VPA(Parameters params) { - int new_row = 1; - if (params.size() >= 1) + unsigned new_row = 1; + if (params.size() >= 1 && params[0] != 0) new_row = params[0] - 1; - if (new_row < 0) - new_row = 0; - set_cursor(new_row, m_cursor_column); + set_cursor(new_row, cursor_column()); } void Terminal::ECH(Parameters params) { // Erase characters (without moving cursor) - int num = 1; - if (params.size() >= 1) + unsigned num = 1; + if (params.size() >= 1 && params[0] != 0) num = params[0]; - if (num == 0) - num = 1; // Clear from cursor to end of line. - for (int i = m_cursor_column; i < num; ++i) { - put_character_at(m_cursor_row, i, ' '); + for (unsigned i = cursor_column(); i < num; ++i) { + put_character_at(cursor_row(), i, ' '); } } void Terminal::EL(Parameters params) { - int mode = 0; + unsigned mode = 0; if (params.size() >= 1) mode = params[0]; switch (mode) { case 0: // Clear from cursor to end of line. - for (int i = m_cursor_column; i < m_columns; ++i) { - put_character_at(m_cursor_row, i, ' '); + for (int i = cursor_column(); i < m_columns; ++i) { + put_character_at(cursor_row(), i, ' '); } break; case 1: // Clear from cursor to beginning of line. - for (int i = 0; i <= m_cursor_column; ++i) { - put_character_at(m_cursor_row, i, ' '); + for (int i = 0; i <= cursor_column(); ++i) { + put_character_at(cursor_row(), i, ' '); } break; case 2: // Clear the complete line for (int i = 0; i < m_columns; ++i) { - put_character_at(m_cursor_row, i, ' '); + put_character_at(cursor_row(), i, ' '); } break; default: @@ -465,15 +513,15 @@ void Terminal::EL(Parameters params) void Terminal::ED(Parameters params) { - int mode = 0; + unsigned mode = 0; if (params.size() >= 1) mode = params[0]; switch (mode) { case 0: // Clear from cursor to end of screen. - for (int i = m_cursor_column; i < m_columns; ++i) - put_character_at(m_cursor_row, i, ' '); - for (int row = m_cursor_row + 1; row < m_rows; ++row) { + for (int i = cursor_column(); i < m_columns; ++i) + put_character_at(cursor_row(), i, ' '); + for (int row = cursor_row() + 1; row < m_rows; ++row) { for (int column = 0; column < m_columns; ++column) { put_character_at(row, column, ' '); } @@ -481,9 +529,9 @@ void Terminal::ED(Parameters params) break; case 1: // Clear from cursor to beginning of screen. - for (int i = m_cursor_column; i >= 0; --i) - put_character_at(m_cursor_row, i, ' '); - for (int row = m_cursor_row - 1; row >= 0; --row) { + for (int i = cursor_column(); i >= 0; --i) + put_character_at(cursor_row(), i, ' '); + for (int row = cursor_row() - 1; row >= 0; --row) { for (int column = 0; column < m_columns; ++column) { put_character_at(row, column, ' '); } @@ -554,16 +602,16 @@ void Terminal::DECSCUSR(Parameters params) #ifndef KERNEL void Terminal::IL(Parameters params) { - int count = 1; - if (params.size() >= 1) + unsigned count = 1; + if (params.size() >= 1 && params[0] != 0) count = params[0]; invalidate_cursor(); for (; count > 0; --count) { - m_lines.insert(m_cursor_row + m_scroll_region_top, make(m_columns)); - if (m_scroll_region_bottom + 1 < m_lines.size()) - m_lines.remove(m_scroll_region_bottom + 1); + active_buffer().insert(cursor_row() + m_scroll_region_top, make(m_columns)); + if (m_scroll_region_bottom + 1 < active_buffer().size()) + active_buffer().remove(m_scroll_region_bottom + 1); else - m_lines.remove(m_lines.size() - 1); + active_buffer().remove(active_buffer().size() - 1); } m_need_full_flush = true; @@ -579,39 +627,35 @@ void Terminal::DA(Parameters) void Terminal::DL(Parameters params) { int count = 1; - if (params.size() >= 1) + if (params.size() >= 1 && params[0] != 0) count = params[0]; - if (count == 1 && m_cursor_row == 0) { + if (count == 1 && cursor_row() == 0) { scroll_up(); return; } - int max_count = m_rows - (m_scroll_region_top + m_cursor_row); + int max_count = m_rows - (m_scroll_region_top + cursor_row()); count = min(count, max_count); for (int c = count; c > 0; --c) { - m_lines.remove(m_cursor_row + m_scroll_region_top); - if (m_scroll_region_bottom < m_lines.size()) - m_lines.insert(m_scroll_region_bottom, make(m_columns)); + active_buffer().remove(cursor_row() + m_scroll_region_top); + if (m_scroll_region_bottom < active_buffer().size()) + active_buffer().insert(m_scroll_region_bottom, make(m_columns)); else - m_lines.append(make(m_columns)); + active_buffer().append(make(m_columns)); } } void Terminal::DCH(Parameters params) { int num = 1; - if (params.size() >= 1) + if (params.size() >= 1 && params[0] != 0) num = params[0]; - if (num == 0) - num = 1; - - auto& line = m_lines[m_cursor_row]; - + auto& line = active_buffer()[cursor_row()]; // Move n characters of line to the left - for (size_t i = m_cursor_column; i < line.length() - num; i++) + for (size_t i = cursor_column(); i < line.length() - num; i++) line.set_code_point(i, line.code_point(i + num)); // Fill remainder of line with blanks @@ -624,43 +668,45 @@ void Terminal::DCH(Parameters params) void Terminal::linefeed() { - u16 new_row = m_cursor_row; - if (m_cursor_row == m_scroll_region_bottom) { + u16 new_row = cursor_row(); + if (cursor_row() == m_scroll_region_bottom) { scroll_up(); } else { ++new_row; }; // We shouldn't jump to the first column after receiving a line feed. // The TTY will take care of generating the carriage return. - set_cursor(new_row, m_cursor_column); + set_cursor(new_row, cursor_column()); } void Terminal::carriage_return() { - set_cursor(m_cursor_row, 0); + set_cursor(cursor_row(), 0); } #ifndef KERNEL void Terminal::scroll_up() { + dbgln_if(TERMINAL_DEBUG, "Scroll up 1 line"); // NOTE: We have to invalidate the cursor first. invalidate_cursor(); - if (m_scroll_region_top == 0) { - auto line = move(m_lines.ptr_at(m_scroll_region_top)); + if (m_scroll_region_top == 0 && !m_use_alternate_screen_buffer) { + auto line = move(active_buffer().ptr_at(m_scroll_region_top)); add_line_to_history(move(line)); m_client.terminal_history_changed(); } - m_lines.remove(m_scroll_region_top); - m_lines.insert(m_scroll_region_bottom, make(m_columns)); + active_buffer().remove(m_scroll_region_top); + active_buffer().insert(m_scroll_region_bottom, make(m_columns)); m_need_full_flush = true; } void Terminal::scroll_down() { + dbgln_if(TERMINAL_DEBUG, "Scroll down 1 line"); // NOTE: We have to invalidate the cursor first. invalidate_cursor(); - m_lines.remove(m_scroll_region_bottom); - m_lines.insert(m_scroll_region_top, make(m_columns)); + active_buffer().remove(m_scroll_region_bottom); + active_buffer().insert(m_scroll_region_top, make(m_columns)); m_need_full_flush = true; } @@ -668,9 +714,9 @@ void Terminal::put_character_at(unsigned row, unsigned column, u32 code_point) { VERIFY(row < rows()); VERIFY(column < columns()); - auto& line = m_lines[row]; + auto& line = active_buffer()[row]; line.set_code_point(column, code_point); - line.attribute_at(column) = m_current_attribute; + line.attribute_at(column) = m_current_state.attribute; line.attribute_at(column).flags |= Attribute::Touched; line.set_dirty(true); @@ -678,19 +724,21 @@ void Terminal::put_character_at(unsigned row, unsigned column, u32 code_point) } #endif -void Terminal::set_cursor(unsigned a_row, unsigned a_column) +void Terminal::set_cursor(unsigned a_row, unsigned a_column, bool skip_debug) { unsigned row = min(a_row, m_rows - 1u); unsigned column = min(a_column, m_columns - 1u); - if (row == m_cursor_row && column == m_cursor_column) + if (row == cursor_row() && column == cursor_column()) return; VERIFY(row < rows()); VERIFY(column < columns()); invalidate_cursor(); - m_cursor_row = row; - m_cursor_column = column; + m_current_state.cursor.row = row; + m_current_state.cursor.column = column; m_stomp = false; invalidate_cursor(); + if (!skip_debug) + dbgln_if(TERMINAL_DEBUG, "Set cursor position: {},{}", cursor_row(), cursor_column()); } void Terminal::NEL() @@ -716,7 +764,7 @@ void Terminal::DSR(Parameters params) emit_string("\033[0n"); // Terminal status OK! } else if (params.size() == 1 && params[0] == 6) { // Cursor position query - emit_string(String::formatted("\e[{};{}R", m_cursor_row + 1, m_cursor_column + 1)); + emit_string(String::formatted("\e[{};{}R", cursor_row() + 1, cursor_column() + 1)); } else { dbgln("Unknown DSR"); } @@ -725,22 +773,18 @@ void Terminal::DSR(Parameters params) #ifndef KERNEL void Terminal::ICH(Parameters params) { - int num = 0; - if (params.size() >= 1) { + unsigned num = 1; + if (params.size() >= 1 && params[0] != 0) num = params[0]; - } - if (num == 0) - num = 1; - - auto& line = m_lines[m_cursor_row]; + auto& line = active_buffer()[cursor_row()]; // Move characters after cursor to the right - for (int i = line.length() - num; i >= m_cursor_column; --i) + for (unsigned i = line.length() - num; i >= cursor_column(); --i) line.set_code_point(i + num, line.code_point(i)); // Fill n characters after cursor with blanks - for (int i = 0; i < num; i++) - line.set_code_point(m_cursor_column + i, ' '); + for (unsigned i = 0; i < num; i++) + line.set_code_point(cursor_column() + i, ' '); line.set_dirty(true); } @@ -753,22 +797,22 @@ void Terminal::on_input(u8 byte) void Terminal::emit_code_point(u32 code_point) { - auto new_column = m_cursor_column + 1; + auto new_column = cursor_column() + 1; if (new_column < columns()) { - put_character_at(m_cursor_row, m_cursor_column, code_point); - set_cursor(m_cursor_row, new_column); + put_character_at(cursor_row(), cursor_column(), code_point); + set_cursor(cursor_row(), new_column, true); return; } if (m_stomp) { m_stomp = false; carriage_return(); linefeed(); - put_character_at(m_cursor_row, m_cursor_column, code_point); - set_cursor(m_cursor_row, 1); + put_character_at(cursor_row(), cursor_column(), code_point); + set_cursor(cursor_row(), 1); } else { // Curious: We wait once on the right-hand side m_stomp = true; - put_character_at(m_cursor_row, m_cursor_column, code_point); + put_character_at(cursor_row(), cursor_column(), code_point); } } @@ -779,15 +823,15 @@ void Terminal::execute_control_code(u8 code) m_client.beep(); return; case '\b': - if (m_cursor_column) { - set_cursor(m_cursor_row, m_cursor_column - 1); + if (cursor_column()) { + set_cursor(cursor_row(), cursor_column() - 1); return; } return; case '\t': { - for (unsigned i = m_cursor_column + 1; i < columns(); ++i) { + for (unsigned i = cursor_column() + 1; i < columns(); ++i) { if (m_horizontal_tabs[i]) { - set_cursor(m_cursor_row, i); + set_cursor(cursor_row(), i); return; } } @@ -826,6 +870,12 @@ void Terminal::execute_escape_sequence(Intermediates intermediates, bool ignore, case '\\': // ST (string terminator) -- do nothing return; + case '7': + DECSC(); + return; + case '8': + DECRC(); + return; } } else if (intermediates[0] == '#') { switch (last_byte) { @@ -907,7 +957,7 @@ void Terminal::execute_csi_sequence(Parameters parameters, Intermediates interme SCOSC(); break; case 'u': - SCORC(parameters); + SCORC(); break; case 't': XTERM_WM(parameters); @@ -977,12 +1027,12 @@ void Terminal::execute_osc_sequence(OscParameters parameters, u8 last_byte) dbgln("Attempted to set href but gave too few parameters"); } else if (parameters[1].is_empty() && parameters[2].is_empty()) { // Clear hyperlink - m_current_attribute.href = String(); - m_current_attribute.href_id = String(); + m_current_state.attribute.href = String(); + m_current_state.attribute.href_id = String(); } else { - m_current_attribute.href = stringview_ify(2); + m_current_state.attribute.href = stringview_ify(2); // FIXME: Respect the provided ID - m_current_attribute.href_id = String::number(m_next_href_id++); + m_current_state.attribute.href_id = String::number(m_next_href_id++); } #endif break; @@ -1180,14 +1230,19 @@ void Terminal::set_size(u16 columns, u16 rows) return; if (rows > m_rows) { - while (m_lines.size() < rows) - m_lines.append(make(columns)); + while (m_normal_screen_buffer.size() < rows) + m_normal_screen_buffer.append(make(columns)); + while (m_alternate_screen_buffer.size() < rows) + m_alternate_screen_buffer.append(make(columns)); } else { - m_lines.shrink(rows); + m_normal_screen_buffer.shrink(rows); + m_alternate_screen_buffer.shrink(rows); } - for (int i = 0; i < rows; ++i) - m_lines[i].set_length(columns); + for (int i = 0; i < rows; ++i) { + m_normal_screen_buffer[i].set_length(columns); + m_alternate_screen_buffer[i].set_length(columns); + } m_columns = columns; m_rows = rows; @@ -1195,10 +1250,10 @@ void Terminal::set_size(u16 columns, u16 rows) m_scroll_region_top = 0; m_scroll_region_bottom = rows - 1; - m_cursor_row = min((int)m_cursor_row, m_rows - 1); - m_cursor_column = min((int)m_cursor_column, m_columns - 1); - m_saved_cursor_row = min((int)m_saved_cursor_row, m_rows - 1); - m_saved_cursor_column = min((int)m_saved_cursor_column, m_columns - 1); + m_current_state.cursor.clamp(m_rows - 1, m_columns - 1); + m_normal_saved_state.cursor.clamp(m_rows - 1, m_columns - 1); + m_alternate_saved_state.cursor.clamp(m_rows - 1, m_columns - 1); + m_saved_cursor_position.clamp(m_rows - 1, m_columns - 1); m_horizontal_tabs.resize(columns); for (unsigned i = 0; i < columns; ++i) @@ -1213,7 +1268,7 @@ void Terminal::set_size(u16 columns, u16 rows) #ifndef KERNEL void Terminal::invalidate_cursor() { - m_lines[m_cursor_row].set_dirty(true); + active_buffer()[cursor_row()].set_dirty(true); } Attribute Terminal::attribute_at(const Position& position) const diff --git a/Userland/Libraries/LibVT/Terminal.h b/Userland/Libraries/LibVT/Terminal.h index 2605299bbd..c150d6dbc2 100644 --- a/Userland/Libraries/LibVT/Terminal.h +++ b/Userland/Libraries/LibVT/Terminal.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, Daniel Bertalan * * SPDX-License-Identifier: BSD-2-Clause */ @@ -71,7 +72,7 @@ public: void on_input(u8); - void set_cursor(unsigned row, unsigned column); + void set_cursor(unsigned row, unsigned column, bool skip_debug = false); #ifndef KERNEL void clear(); @@ -93,20 +94,27 @@ public: } u16 rows() const { return m_rows; } - u16 cursor_column() const { return m_cursor_column; } - u16 cursor_row() const { return m_cursor_row; } + u16 cursor_column() const { return m_current_state.cursor.column; } + u16 cursor_row() const { return m_current_state.cursor.row; } #ifndef KERNEL size_t line_count() const { - return m_history.size() + m_lines.size(); + if (m_use_alternate_screen_buffer) + return m_alternate_screen_buffer.size(); + else + return m_history.size() + m_normal_screen_buffer.size(); } Line& line(size_t index) { - if (index < m_history.size()) - return m_history[(m_history_start + index) % m_history.size()]; - return m_lines[index - m_history.size()]; + if (m_use_alternate_screen_buffer) { + return m_alternate_screen_buffer[index]; + } else { + if (index < m_history.size()) + return m_history[(m_history_start + index) % m_history.size()]; + return m_normal_screen_buffer[index - m_history.size()]; + } } const Line& line(size_t index) const { @@ -115,11 +123,12 @@ public: Line& visible_line(size_t index) { - return m_lines[index]; + return active_buffer()[index]; } + const Line& visible_line(size_t index) const { - return m_lines[index]; + return active_buffer()[index]; } size_t max_history_size() const { return m_max_history_lines; } @@ -173,6 +182,22 @@ protected: virtual void receive_dcs_char(u8 byte) override; virtual void execute_dcs_sequence() override; + struct CursorPosition { + u16 row { 0 }; + u16 column { 0 }; + + void clamp(u16 max_row, u16 max_column) + { + row = min(row, max_row); + column = min(column, max_column); + } + }; + + struct BufferState { + Attribute attribute; + CursorPosition cursor; + }; + void carriage_return(); #ifndef KERNEL void scroll_up(); @@ -225,7 +250,13 @@ protected: void SCOSC(); // Restore Saved Cursor Position - void SCORC(Parameters); + void SCORC(); + + // Save Cursor (and other attributes) + void DECSC(); + + // Restore Cursor (and other attributes) + void DECRC(); // DECSTBM – Set Top and Bottom Margins ("Scrolling Region") void DECSTBM(Parameters); @@ -322,19 +353,28 @@ protected: m_history_start = (m_history_start + 1) % m_history.size(); } - NonnullOwnPtrVector m_lines; + NonnullOwnPtrVector& active_buffer() { return m_use_alternate_screen_buffer ? m_alternate_screen_buffer : m_normal_screen_buffer; }; + const NonnullOwnPtrVector& active_buffer() const { return m_use_alternate_screen_buffer ? m_alternate_screen_buffer : m_normal_screen_buffer; }; + NonnullOwnPtrVector m_normal_screen_buffer; + NonnullOwnPtrVector m_alternate_screen_buffer; #endif + bool m_use_alternate_screen_buffer { false }; + size_t m_scroll_region_top { 0 }; size_t m_scroll_region_bottom { 0 }; u16 m_columns { 1 }; u16 m_rows { 1 }; - u16 m_cursor_row { 0 }; - u16 m_cursor_column { 0 }; - u16 m_saved_cursor_row { 0 }; - u16 m_saved_cursor_column { 0 }; + BufferState m_current_state; + BufferState m_normal_saved_state; + BufferState m_alternate_saved_state; + + // Separate from *_saved_state: some escape sequences only save/restore the cursor position, + // while others impact the text attributes and other state too. + CursorPosition m_saved_cursor_position; + bool m_swallow_current { false }; bool m_stomp { false };