diff options
author | Ali Mohammad Pur <ali.mpfard@gmail.com> | 2021-06-19 03:53:03 +0430 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-06-23 19:04:08 +0200 |
commit | 2f2b7814d15c9030f6794c5e844545104421778d (patch) | |
tree | 83a6df31f02a409d096b29250a11b32bc9c6fce7 | |
parent | 424965954f88bbac0516529ee984f7a2276a139e (diff) | |
download | serenity-2f2b7814d15c9030f6794c5e844545104421778d.zip |
LibVT+Terminal: Implement line wrapping
This commit implements line wrapping in the terminal, and tries its best
to move the cursor to the "correct" position.
-rw-r--r-- | Userland/Libraries/LibVT/Line.cpp | 88 | ||||
-rw-r--r-- | Userland/Libraries/LibVT/Line.h | 18 | ||||
-rw-r--r-- | Userland/Libraries/LibVT/Position.h | 11 | ||||
-rw-r--r-- | Userland/Libraries/LibVT/Terminal.cpp | 100 | ||||
-rw-r--r-- | Userland/Libraries/LibVT/Terminal.h | 11 |
5 files changed, 206 insertions, 22 deletions
diff --git a/Userland/Libraries/LibVT/Line.cpp b/Userland/Libraries/LibVT/Line.cpp index 30b971b9d9..9e080158c0 100644 --- a/Userland/Libraries/LibVT/Line.cpp +++ b/Userland/Libraries/LibVT/Line.cpp @@ -10,19 +10,101 @@ namespace VT { Line::Line(size_t length) { - set_length(length); + set_length(length, nullptr, nullptr); } Line::~Line() { } -void Line::set_length(size_t new_length) +void Line::set_length(size_t new_length, Line* next_line, CursorPosition* cursor, bool cursor_is_on_next_line) { size_t old_length = length(); if (old_length == new_length) return; - m_cells.resize(new_length); + + // Drop the empty cells + if (m_terminated_at.has_value()) + m_cells.remove(m_terminated_at.value(), m_cells.size() - m_terminated_at.value()); + + if (!next_line) + return m_cells.resize(new_length); + + if (old_length < new_length) + take_cells_from_next_line(old_length, new_length, next_line, cursor_is_on_next_line, cursor); + else + push_cells_into_next_line(old_length, new_length, next_line, cursor_is_on_next_line, cursor); + + m_cells.resize(max(new_length, static_cast<size_t>(m_terminated_at.value_or(new_length)))); +} + +void Line::push_cells_into_next_line(size_t old_length, size_t new_length, Line* next_line, bool cursor_is_on_next_line, CursorPosition* cursor) +{ + // Push as many cells as _wouldn't_ fit into the next line. + auto cells_to_preserve = !next_line->m_terminated_at.has_value() && next_line->is_empty() ? 0 : m_terminated_at.value_or(0); + auto cells_to_push_into_next_line = min(old_length - new_length, length() - cells_to_preserve); + if (!cells_to_push_into_next_line) + return; + + auto preserved_cells = length() - cells_to_push_into_next_line; + if (next_line->m_terminated_at.has_value()) + next_line->m_terminated_at = next_line->m_terminated_at.value() + cells_to_push_into_next_line; + + if (m_terminated_at.has_value() && cells_to_preserve == 0) { + m_terminated_at.clear(); + if (!next_line->m_terminated_at.has_value()) + next_line->m_terminated_at = cells_to_push_into_next_line; + } + + if (cursor) { + if (cursor_is_on_next_line) { + cursor->column += cells_to_push_into_next_line; + } else if (cursor->column >= preserved_cells) { + cursor->row++; + cursor->column = cursor->column - preserved_cells; + } + } + + next_line->m_cells.prepend(m_cells.span().slice_from_end(cells_to_push_into_next_line).data(), cells_to_push_into_next_line); + m_cells.remove(m_cells.size() - cells_to_push_into_next_line, cells_to_push_into_next_line); + if (m_terminated_at.has_value()) + m_terminated_at = m_terminated_at.value() - cells_to_push_into_next_line; +} + +void Line::take_cells_from_next_line(size_t old_length, size_t new_length, Line* next_line, bool cursor_is_on_next_line, CursorPosition* cursor) +{ + // Take as many cells as would fit from the next line + if (m_terminated_at.has_value()) + return; + + auto cells_to_grab_from_next_line = min(new_length - old_length, next_line->length()); + auto clear_next_line = false; + if (next_line->m_terminated_at.has_value()) { + cells_to_grab_from_next_line = min(cells_to_grab_from_next_line, static_cast<size_t>(next_line->m_terminated_at.value())); + if (cells_to_grab_from_next_line == *next_line->m_terminated_at) { + m_terminated_at = old_length + *next_line->m_terminated_at; + next_line->m_terminated_at.clear(); + clear_next_line = true; + } else { + next_line->m_terminated_at = next_line->m_terminated_at.value() - cells_to_grab_from_next_line; + } + } + + if (cells_to_grab_from_next_line) { + if (cursor && cursor_is_on_next_line) { + if (cursor->column <= cells_to_grab_from_next_line) { + cursor->row--; + cursor->column += m_cells.size(); + } else { + cursor->column -= cells_to_grab_from_next_line; + } + } + m_cells.append(next_line->m_cells.data(), cells_to_grab_from_next_line); + next_line->m_cells.remove(0, cells_to_grab_from_next_line); + } + + if (clear_next_line) + next_line->m_cells.clear(); } void Line::clear_range(size_t first_column, size_t last_column, const Attribute& attribute) diff --git a/Userland/Libraries/LibVT/Line.h b/Userland/Libraries/LibVT/Line.h index 030274560c..0939bc77f1 100644 --- a/Userland/Libraries/LibVT/Line.h +++ b/Userland/Libraries/LibVT/Line.h @@ -10,6 +10,7 @@ #include <AK/String.h> #include <AK/Vector.h> #include <LibVT/Attribute.h> +#include <LibVT/Position.h> #include <LibVT/XtermColors.h> namespace VT { @@ -25,6 +26,8 @@ public: struct Cell { u32 code_point { ' ' }; Attribute attribute; + + bool operator!=(Cell const& other) const { return code_point != other.code_point || attribute != other.attribute; } }; const Attribute& attribute_at(size_t index) const { return m_cells[index].attribute; } @@ -41,8 +44,16 @@ public: void clear_range(size_t first_column, size_t last_column, const Attribute& attribute = Attribute()); bool has_only_one_background_color() const; - size_t length() const { return m_cells.size(); } - void set_length(size_t); + bool is_empty() const + { + return !any_of(m_cells.begin(), m_cells.end(), [](auto& cell) { return cell != Cell(); }); + } + + size_t length() const + { + return m_cells.size(); + } + void set_length(size_t, Line* next_line, CursorPosition* cursor, bool cursor_is_on_next_line = true); u32 code_point(size_t index) const { @@ -67,6 +78,9 @@ public: void set_terminated(u16 column) { m_terminated_at = column; } private: + void take_cells_from_next_line(size_t old_length, size_t new_length, Line* next_line, bool cursor_is_on_next_line, CursorPosition* cursor); + void push_cells_into_next_line(size_t old_length, size_t new_length, Line* next_line, bool cursor_is_on_next_line, CursorPosition* cursor); + Vector<Cell> m_cells; bool m_dirty { false }; // Note: The alignment is 8, so this member lives in the padding (that already existed before it was introduced) diff --git a/Userland/Libraries/LibVT/Position.h b/Userland/Libraries/LibVT/Position.h index 13a8e5b4b7..90b6d724aa 100644 --- a/Userland/Libraries/LibVT/Position.h +++ b/Userland/Libraries/LibVT/Position.h @@ -51,4 +51,15 @@ private: int m_column { -1 }; }; +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); + } +}; + } diff --git a/Userland/Libraries/LibVT/Terminal.cpp b/Userland/Libraries/LibVT/Terminal.cpp index ce7f61f67a..c3673666fd 100644 --- a/Userland/Libraries/LibVT/Terminal.cpp +++ b/Userland/Libraries/LibVT/Terminal.cpp @@ -7,6 +7,7 @@ #include "Terminal.h" #include <AK/Debug.h> +#include <AK/Queue.h> #include <AK/StringBuilder.h> #include <AK/StringView.h> #include <AK/TemporaryChange.h> @@ -1439,6 +1440,95 @@ void Terminal::set_size(u16 columns, u16 rows) if (columns == m_columns && rows == m_rows) return; + // If we're making the terminal larger (column-wise), start at the end and go up, taking cells from the line below. + // otherwise start at the beginning and go down, pushing cells into the line below. + auto resize_and_rewrap = [&](auto& buffer, auto& old_cursor) { + auto cursor_on_line = [&](auto index) { + return index == old_cursor.row ? &old_cursor : nullptr; + }; + // Two passes, one from top to bottom, another from bottom to top + for (size_t pass = 0; pass < 2; ++pass) { + auto forwards = (pass == 0) ^ (columns < m_columns); + if (forwards) { + for (size_t i = 1; i <= buffer.size(); ++i) { + auto is_at_seam = i == 1; + auto next_line = is_at_seam ? nullptr : &buffer[buffer.size() - i + 1]; + auto& line = buffer[buffer.size() - i]; + auto next_cursor = cursor_on_line(buffer.size() - i + 1); + line.set_length(columns, next_line, next_cursor ?: cursor_on_line(buffer.size() - i), !!next_cursor); + } + } else { + for (size_t i = 0; i < buffer.size(); ++i) { + auto is_at_seam = i + 1 == buffer.size(); + auto next_line = is_at_seam ? nullptr : &buffer[i + 1]; + auto next_cursor = cursor_on_line(i + 1); + buffer[i].set_length(columns, next_line, next_cursor ?: cursor_on_line(i), !!next_cursor); + } + } + + Queue<size_t> lines_to_reevaluate; + for (size_t i = 0; i < buffer.size(); ++i) { + if (buffer[i].length() != columns) + lines_to_reevaluate.enqueue(i); + } + size_t rows_inserted = 0; + while (!lines_to_reevaluate.is_empty()) { + auto index = lines_to_reevaluate.dequeue(); + auto is_at_seam = index + 1 == buffer.size(); + auto next_line = is_at_seam ? nullptr : &buffer[index + 1]; + auto& line = buffer[index]; + auto next_cursor = cursor_on_line(index + 1); + line.set_length(columns, next_line, next_cursor ?: cursor_on_line(index), !!next_cursor); + if (line.length() > columns) { + auto current_cursor = cursor_on_line(index); + // Split the line into two (or more) + ++index; + ++rows_inserted; + buffer.insert(index, make<Line>(0)); + VERIFY(buffer[index].length() == 0); + line.set_length(columns, &buffer[index], current_cursor, false); + // If we inserted a line and the old cursor was after that line, increment its row + if (!current_cursor && old_cursor.row >= index) + ++old_cursor.row; + + if (buffer[index].length() != columns) + lines_to_reevaluate.enqueue(index); + } + if (next_line && next_line->length() != columns) + lines_to_reevaluate.enqueue(index + 1); + } + } + + return old_cursor; + }; + + CursorPosition cursor_tracker { cursor_row(), cursor_column() }; + resize_and_rewrap(m_normal_screen_buffer, cursor_tracker); + if (m_normal_screen_buffer.size() > rows) { + if (auto extra_lines = m_normal_screen_buffer.size() - rows) { + while (extra_lines > 0) { + if (m_normal_screen_buffer.size() <= cursor_tracker.row) + break; + if (m_normal_screen_buffer.last().is_empty()) { + if (m_normal_screen_buffer[m_normal_screen_buffer.size() - 2].termination_column().has_value()) + break; + --extra_lines; + m_normal_screen_buffer.take_last(); + continue; + } + break; + } + for (size_t i = 0; i < extra_lines; ++i) + m_history.append(m_normal_screen_buffer.take_first()); + m_client.terminal_history_changed(extra_lines); + } + } + + CursorPosition dummy_cursor_tracker {}; + resize_and_rewrap(m_alternate_screen_buffer, dummy_cursor_tracker); + if (m_alternate_screen_buffer.size() > rows) + m_alternate_screen_buffer.remove(0, m_alternate_screen_buffer.size() - rows); + if (rows > m_rows) { while (m_normal_screen_buffer.size() < rows) m_normal_screen_buffer.append(make<Line>(columns)); @@ -1449,11 +1539,6 @@ void Terminal::set_size(u16 columns, u16 rows) m_alternate_screen_buffer.shrink(rows); } - 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; @@ -1471,6 +1556,8 @@ void Terminal::set_size(u16 columns, u16 rows) // Rightmost column is always last tab on line. m_horizontal_tabs[columns - 1] = 1; + set_cursor(cursor_tracker.row, cursor_tracker.column); + m_client.terminal_did_resize(m_columns, m_rows); dbgln_if(TERMINAL_DEBUG, "Set terminal size: {}x{}", m_rows, m_columns); @@ -1480,7 +1567,8 @@ void Terminal::set_size(u16 columns, u16 rows) #ifndef KERNEL void Terminal::invalidate_cursor() { - active_buffer()[cursor_row()].set_dirty(true); + if (cursor_row() < active_buffer().size()) + 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 671312aad2..d41cc64ed7 100644 --- a/Userland/Libraries/LibVT/Terminal.h +++ b/Userland/Libraries/LibVT/Terminal.h @@ -194,17 +194,6 @@ 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; |