mirror of
https://github.com/RGBCube/serenity
synced 2025-07-23 13:17:36 +00:00
LibVT: Add Alternate Screen Buffer support
The Alternate Screen Buffer is used by full-screen terminal applications (like `vim` and `nano`). Its data is stored separately from the normal buffer, therefore after applications using it exit, everything looks like it was before, the bottom of their interfaces isn't visible. An interesting feature is that it does not support scrollback, so it consumes less memory by not having to allocate lines for history. Because of the need to save and restore state between the switches, some correctness issues relating to it were also fixed in this commit.
This commit is contained in:
parent
cb8d0c8d0d
commit
146bd794eb
3 changed files with 275 additions and 181 deletions
|
@ -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<size_t>((int)m_cursor_row, rows() - 1);
|
||||
m_cursor_column = min<size_t>((int)m_cursor_column, columns() - 1);
|
||||
m_saved_cursor_row = min<size_t>((int)m_saved_cursor_row, rows() - 1);
|
||||
m_saved_cursor_column = min<size_t>((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&)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Daniel Bertalan <dani@danielbertalan.dev>
|
||||
*
|
||||
* 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<u32> {
|
||||
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<Line>(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<Line>(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<Line>(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<Line>(m_columns));
|
||||
else
|
||||
m_lines.append(make<Line>(m_columns));
|
||||
active_buffer().append(make<Line>(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<Line>(m_columns));
|
||||
active_buffer().remove(m_scroll_region_top);
|
||||
active_buffer().insert(m_scroll_region_bottom, make<Line>(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<Line>(m_columns));
|
||||
active_buffer().remove(m_scroll_region_bottom);
|
||||
active_buffer().insert(m_scroll_region_top, make<Line>(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<Line>(columns));
|
||||
while (m_normal_screen_buffer.size() < rows)
|
||||
m_normal_screen_buffer.append(make<Line>(columns));
|
||||
while (m_alternate_screen_buffer.size() < rows)
|
||||
m_alternate_screen_buffer.append(make<Line>(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
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Daniel Bertalan <dani@danielbertalan.dev>
|
||||
*
|
||||
* 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 (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_lines[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<Line> m_lines;
|
||||
NonnullOwnPtrVector<Line>& active_buffer() { return m_use_alternate_screen_buffer ? m_alternate_screen_buffer : m_normal_screen_buffer; };
|
||||
const NonnullOwnPtrVector<Line>& active_buffer() const { return m_use_alternate_screen_buffer ? m_alternate_screen_buffer : m_normal_screen_buffer; };
|
||||
NonnullOwnPtrVector<Line> m_normal_screen_buffer;
|
||||
NonnullOwnPtrVector<Line> 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 };
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue