mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 07:47:35 +00:00
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.
This commit is contained in:
parent
424965954f
commit
2f2b7814d1
5 changed files with 206 additions and 22 deletions
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue