summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAli Mohammad Pur <ali.mpfard@gmail.com>2021-06-19 03:53:03 +0430
committerAndreas Kling <kling@serenityos.org>2021-06-23 19:04:08 +0200
commit2f2b7814d15c9030f6794c5e844545104421778d (patch)
tree83a6df31f02a409d096b29250a11b32bc9c6fce7
parent424965954f88bbac0516529ee984f7a2276a139e (diff)
downloadserenity-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.cpp88
-rw-r--r--Userland/Libraries/LibVT/Line.h18
-rw-r--r--Userland/Libraries/LibVT/Position.h11
-rw-r--r--Userland/Libraries/LibVT/Terminal.cpp100
-rw-r--r--Userland/Libraries/LibVT/Terminal.h11
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;