1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-24 15:07:45 +00:00

LibVT: Make VT::Line use a Vector for storage

This is preparation for non-destructive terminal resizing which will
require more dynamic storage for lines.
This commit is contained in:
Andreas Kling 2021-02-26 20:28:22 +01:00
parent c58570ebaf
commit b7c66233f6
5 changed files with 60 additions and 123 deletions

View file

@ -293,7 +293,7 @@ void VirtualConsole::flush_dirty_lines()
continue; continue;
for (size_t column = 0; column < line.length(); ++column) { for (size_t column = 0; column < line.length(); ++column) {
u32 code_point = line.code_point(column); u32 code_point = line.code_point(column);
auto attribute = line.attributes()[column]; auto attribute = line.attribute_at(column);
u16 vga_index = (visual_row * 160) + (column * 2); u16 vga_index = (visual_row * 160) + (column * 2);
m_current_vga_window[vga_index] = code_point < 128 ? code_point : '?'; m_current_vga_window[vga_index] = code_point < 128 ? code_point : '?';
m_current_vga_window[vga_index + 1] = attribute_to_vga(attribute); m_current_vga_window[vga_index + 1] = attribute_to_vga(attribute);

View file

@ -25,103 +25,52 @@
*/ */
#include <LibVT/Line.h> #include <LibVT/Line.h>
#include <string.h>
namespace VT { namespace VT {
Line::Line(u16 length) Line::Line(size_t length)
{ {
set_length(length); set_length(length);
} }
Line::~Line() Line::~Line()
{ {
if (m_utf32)
delete[] m_code_points.as_u32;
else
delete[] m_code_points.as_u8;
delete[] m_attributes;
} }
template<typename CodepointType> void Line::set_length(size_t new_length)
static CodepointType* create_new_code_point_array(size_t new_length, const CodepointType* old_code_points, size_t old_length)
{ {
auto* new_code_points = new CodepointType[new_length]; size_t old_length = length();
for (size_t i = 0; i < new_length; ++i) if (old_length == new_length)
new_code_points[i] = ' ';
if (old_code_points) {
for (size_t i = 0; i < min(old_length, new_length); ++i) {
new_code_points[i] = old_code_points[i];
}
}
delete[] old_code_points;
return new_code_points;
}
void Line::set_length(u16 new_length)
{
if (m_length == new_length)
return; return;
m_cells.resize(new_length);
if (m_utf32)
m_code_points.as_u32 = create_new_code_point_array<u32>(new_length, m_code_points.as_u32, m_length);
else
m_code_points.as_u8 = create_new_code_point_array<u8>(new_length, m_code_points.as_u8, m_length);
auto* new_attributes = new Attribute[new_length];
if (m_attributes) {
for (size_t i = 0; i < min(m_length, new_length); ++i)
new_attributes[i] = m_attributes[i];
}
delete[] m_attributes;
m_attributes = new_attributes;
m_length = new_length;
} }
void Line::clear(Attribute attribute) void Line::clear(const Attribute& attribute)
{ {
if (m_dirty) { if (m_dirty) {
for (u16 i = 0; i < m_length; ++i) { for (auto& cell : m_cells) {
set_code_point(i, ' '); cell = Cell { .code_point = ' ', .attribute = attribute };
m_attributes[i] = attribute;
} }
return; return;
} }
for (unsigned i = 0; i < m_length; ++i) { for (auto& cell : m_cells) {
if (code_point(i) != ' ') if (!m_dirty)
m_dirty = true; m_dirty = cell.code_point != ' ' || cell.attribute != attribute;
set_code_point(i, ' '); cell = Cell { .code_point = ' ', .attribute = attribute };
}
for (unsigned i = 0; i < m_length; ++i) {
if (m_attributes[i] != attribute)
m_dirty = true;
m_attributes[i] = attribute;
} }
} }
bool Line::has_only_one_background_color() const bool Line::has_only_one_background_color() const
{ {
if (!m_length) if (!length())
return true; return true;
// FIXME: Cache this result? // FIXME: Cache this result?
auto color = m_attributes[0].effective_background_color(); auto color = attribute_at(0).effective_background_color();
for (size_t i = 1; i < m_length; ++i) { for (size_t i = 1; i < length(); ++i) {
if (m_attributes[i].effective_background_color() != color) if (attribute_at(i).effective_background_color() != color)
return false; return false;
} }
return true; return true;
} }
void Line::convert_to_utf32()
{
VERIFY(!m_utf32);
auto* new_code_points = new u32[m_length];
for (size_t i = 0; i < m_length; ++i) {
new_code_points[i] = m_code_points.as_u8[i];
}
delete m_code_points.as_u8;
m_code_points.as_u32 = new_code_points;
m_utf32 = true;
}
} }

View file

@ -28,6 +28,7 @@
#include <AK/Noncopyable.h> #include <AK/Noncopyable.h>
#include <AK/String.h> #include <AK/String.h>
#include <AK/Vector.h>
#include <LibVT/XtermColors.h> #include <LibVT/XtermColors.h>
namespace VT { namespace VT {
@ -44,8 +45,8 @@ struct Attribute {
background_color = default_background_color; background_color = default_background_color;
flags = Flags::NoAttributes; flags = Flags::NoAttributes;
} }
u32 foreground_color; u32 foreground_color {};
u32 background_color; u32 background_color {};
u32 effective_background_color() const { return flags & Negative ? foreground_color : background_color; } u32 effective_background_color() const { return flags & Negative ? foreground_color : background_color; }
u32 effective_foreground_color() const { return flags & Negative ? background_color : foreground_color; } u32 effective_foreground_color() const { return flags & Negative ? background_color : foreground_color; }
@ -84,52 +85,42 @@ class Line {
AK_MAKE_NONMOVABLE(Line); AK_MAKE_NONMOVABLE(Line);
public: public:
explicit Line(u16 columns); explicit Line(size_t length);
~Line(); ~Line();
void clear(Attribute); struct Cell {
bool has_only_one_background_color() const; u32 code_point {};
void set_length(u16); Attribute attribute;
};
u16 length() const { return m_length; } const Attribute& attribute_at(size_t index) const { return m_cells[index].attribute; }
Attribute& attribute_at(size_t index) { return m_cells[index].attribute; }
Cell& cell_at(size_t index) { return m_cells[index]; }
const Cell& cell_at(size_t index) const { return m_cells[index]; }
void clear(const Attribute&);
bool has_only_one_background_color() const;
size_t length() const { return m_cells.size(); }
void set_length(size_t);
u32 code_point(size_t index) const u32 code_point(size_t index) const
{ {
if (m_utf32) return m_cells[index].code_point;
return m_code_points.as_u32[index];
return m_code_points.as_u8[index];
} }
void set_code_point(size_t index, u32 code_point) void set_code_point(size_t index, u32 code_point)
{ {
if (!m_utf32 && code_point & 0xffffff80u) m_cells[index].code_point = code_point;
convert_to_utf32();
if (m_utf32)
m_code_points.as_u32[index] = code_point;
else
m_code_points.as_u8[index] = code_point;
} }
bool is_dirty() const { return m_dirty; } bool is_dirty() const { return m_dirty; }
void set_dirty(bool b) { m_dirty = b; } void set_dirty(bool b) { m_dirty = b; }
const Attribute* attributes() const { return m_attributes; }
Attribute* attributes() { return m_attributes; }
void convert_to_utf32();
bool is_utf32() const { return m_utf32; }
private: private:
union { Vector<Cell> m_cells;
u8* as_u8;
u32* as_u32;
} m_code_points { nullptr };
Attribute* m_attributes { nullptr };
bool m_dirty { false }; bool m_dirty { false };
bool m_utf32 { false };
u16 m_length { 0 };
}; };
} }

View file

@ -28,7 +28,6 @@
#include <AK/StringBuilder.h> #include <AK/StringBuilder.h>
#include <AK/StringView.h> #include <AK/StringView.h>
#include <LibVT/Terminal.h> #include <LibVT/Terminal.h>
#include <string.h>
namespace VT { namespace VT {
@ -535,11 +534,11 @@ void Terminal::DCH(const ParamVector& params)
auto& line = m_lines[m_cursor_row]; auto& line = m_lines[m_cursor_row];
// Move n characters of line to the left // Move n characters of line to the left
for (int i = m_cursor_column; i < line.length() - num; i++) for (size_t i = m_cursor_column; i < line.length() - num; i++)
line.set_code_point(i, line.code_point(i + num)); line.set_code_point(i, line.code_point(i + num));
// Fill remainder of line with blanks // Fill remainder of line with blanks
for (int i = line.length() - num; i < line.length(); i++) for (size_t i = line.length() - num; i < line.length(); i++)
line.set_code_point(i, ' '); line.set_code_point(i, ' ');
line.set_dirty(true); line.set_dirty(true);
@ -759,8 +758,8 @@ void Terminal::put_character_at(unsigned row, unsigned column, u32 code_point)
VERIFY(column < columns()); VERIFY(column < columns());
auto& line = m_lines[row]; auto& line = m_lines[row];
line.set_code_point(column, code_point); line.set_code_point(column, code_point);
line.attributes()[column] = m_current_attribute; line.attribute_at(column) = m_current_attribute;
line.attributes()[column].flags |= Attribute::Touched; line.attribute_at(column).flags |= Attribute::Touched;
line.set_dirty(true); line.set_dirty(true);
m_last_code_point = code_point; m_last_code_point = code_point;
@ -1191,9 +1190,9 @@ Attribute Terminal::attribute_at(const Position& position) const
if (position.row() >= static_cast<int>(line_count())) if (position.row() >= static_cast<int>(line_count()))
return {}; return {};
auto& line = this->line(position.row()); auto& line = this->line(position.row());
if (position.column() >= line.length()) if (static_cast<size_t>(position.column()) >= line.length())
return {}; return {};
return line.attributes()[position.column()]; return line.attribute_at(position.column());
} }
} }

View file

@ -313,7 +313,7 @@ void TerminalWidget::paint_event(GUI::PaintEvent& event)
for (u16 visual_row = 0; visual_row < m_terminal.rows(); ++visual_row) { for (u16 visual_row = 0; visual_row < m_terminal.rows(); ++visual_row) {
auto& line = m_terminal.line(first_row_from_history + visual_row); auto& line = m_terminal.line(first_row_from_history + visual_row);
for (size_t column = 0; column < line.length(); ++column) { for (size_t column = 0; column < line.length(); ++column) {
if (m_hovered_href_id == line.attributes()[column].href_id) { if (m_hovered_href_id == line.attribute_at(column).href_id) {
bool merged_with_existing_rect = false; bool merged_with_existing_rect = false;
auto glyph_rect = this->glyph_rect(visual_row, column); auto glyph_rect = this->glyph_rect(visual_row, column);
for (auto& rect : hovered_href_rects) { for (auto& rect : hovered_href_rects) {
@ -340,7 +340,7 @@ void TerminalWidget::paint_event(GUI::PaintEvent& event)
if (visual_beep_active) if (visual_beep_active)
painter.clear_rect(row_rect, Color::Red); painter.clear_rect(row_rect, Color::Red);
else if (has_only_one_background_color) else if (has_only_one_background_color)
painter.clear_rect(row_rect, color_from_rgb(line.attributes()[0].effective_background_color()).with_alpha(m_opacity)); painter.clear_rect(row_rect, color_from_rgb(line.attribute_at(0).effective_background_color()).with_alpha(m_opacity));
for (size_t column = 0; column < line.length(); ++column) { for (size_t column = 0; column < line.length(); ++column) {
bool should_reverse_fill_for_cursor_or_selection = m_cursor_blink_state bool should_reverse_fill_for_cursor_or_selection = m_cursor_blink_state
@ -348,7 +348,7 @@ void TerminalWidget::paint_event(GUI::PaintEvent& event)
&& visual_row == row_with_cursor && visual_row == row_with_cursor
&& column == m_terminal.cursor_column(); && column == m_terminal.cursor_column();
should_reverse_fill_for_cursor_or_selection |= selection_contains({ first_row_from_history + visual_row, (int)column }); should_reverse_fill_for_cursor_or_selection |= selection_contains({ first_row_from_history + visual_row, (int)column });
auto attribute = line.attributes()[column]; auto attribute = line.attribute_at(column);
auto character_rect = glyph_rect(visual_row, column); auto character_rect = glyph_rect(visual_row, column);
auto cell_rect = character_rect.inflated(0, m_line_spacing); auto cell_rect = character_rect.inflated(0, m_line_spacing);
auto text_color = color_from_rgb(should_reverse_fill_for_cursor_or_selection ? attribute.effective_background_color() : attribute.effective_foreground_color()); auto text_color = color_from_rgb(should_reverse_fill_for_cursor_or_selection ? attribute.effective_background_color() : attribute.effective_foreground_color());
@ -405,7 +405,7 @@ void TerminalWidget::paint_event(GUI::PaintEvent& event)
continue; continue;
auto& line = m_terminal.line(first_row_from_history + visual_row); auto& line = m_terminal.line(first_row_from_history + visual_row);
for (size_t column = 0; column < line.length(); ++column) { for (size_t column = 0; column < line.length(); ++column) {
auto attribute = line.attributes()[column]; auto attribute = line.attribute_at(column);
bool should_reverse_fill_for_cursor_or_selection = m_cursor_blink_state bool should_reverse_fill_for_cursor_or_selection = m_cursor_blink_state
&& m_has_logical_focus && m_has_logical_focus
&& visual_row == row_with_cursor && visual_row == row_with_cursor
@ -436,7 +436,7 @@ void TerminalWidget::paint_event(GUI::PaintEvent& event)
auto& cursor_line = m_terminal.line(first_row_from_history + row_with_cursor); auto& cursor_line = m_terminal.line(first_row_from_history + row_with_cursor);
if (m_terminal.cursor_row() < (m_terminal.rows() - rows_from_history)) { if (m_terminal.cursor_row() < (m_terminal.rows() - rows_from_history)) {
auto cell_rect = glyph_rect(row_with_cursor, m_terminal.cursor_column()).inflated(0, m_line_spacing); auto cell_rect = glyph_rect(row_with_cursor, m_terminal.cursor_column()).inflated(0, m_line_spacing);
painter.draw_rect(cell_rect, color_from_rgb(cursor_line.attributes()[m_terminal.cursor_column()].effective_foreground_color())); painter.draw_rect(cell_rect, color_from_rgb(cursor_line.attribute_at(m_terminal.cursor_column()).effective_foreground_color()));
} }
} }
} }
@ -590,18 +590,20 @@ VT::Position TerminalWidget::buffer_position_at(const Gfx::IntPoint& position) c
u32 TerminalWidget::code_point_at(const VT::Position& position) const u32 TerminalWidget::code_point_at(const VT::Position& position) const
{ {
VERIFY(position.is_valid());
VERIFY(position.row() >= 0 && static_cast<size_t>(position.row()) < m_terminal.line_count()); VERIFY(position.row() >= 0 && static_cast<size_t>(position.row()) < m_terminal.line_count());
auto& line = m_terminal.line(position.row()); auto& line = m_terminal.line(position.row());
if (position.column() == line.length()) if (static_cast<size_t>(position.column()) == line.length())
return '\n'; return '\n';
return line.code_point(position.column()); return line.code_point(position.column());
} }
VT::Position TerminalWidget::next_position_after(const VT::Position& position, bool should_wrap) const VT::Position TerminalWidget::next_position_after(const VT::Position& position, bool should_wrap) const
{ {
VERIFY(position.is_valid());
VERIFY(position.row() >= 0 && static_cast<size_t>(position.row()) < m_terminal.line_count()); VERIFY(position.row() >= 0 && static_cast<size_t>(position.row()) < m_terminal.line_count());
auto& line = m_terminal.line(position.row()); auto& line = m_terminal.line(position.row());
if (position.column() == line.length()) { if (static_cast<size_t>(position.column()) == line.length()) {
if (static_cast<size_t>(position.row()) == m_terminal.line_count() - 1) { if (static_cast<size_t>(position.row()) == m_terminal.line_count() - 1) {
if (should_wrap) if (should_wrap)
return { 0, 0 }; return { 0, 0 };
@ -619,12 +621,12 @@ VT::Position TerminalWidget::previous_position_before(const VT::Position& positi
if (position.row() == 0) { if (position.row() == 0) {
if (should_wrap) { if (should_wrap) {
auto& last_line = m_terminal.line(m_terminal.line_count() - 1); auto& last_line = m_terminal.line(m_terminal.line_count() - 1);
return { static_cast<int>(m_terminal.line_count() - 1), last_line.length() }; return { static_cast<int>(m_terminal.line_count() - 1), static_cast<int>(last_line.length()) };
} }
return {}; return {};
} }
auto& prev_line = m_terminal.line(position.row() - 1); auto& prev_line = m_terminal.line(position.row() - 1);
return { position.row() - 1, prev_line.length() }; return { position.row() - 1, static_cast<int>(prev_line.length()) };
} }
return { position.row(), position.column() - 1 }; return { position.row(), position.column() - 1 };
} }
@ -909,18 +911,14 @@ String TerminalWidget::selected_text() const
int last_column = last_selection_column_on_row(row); int last_column = last_selection_column_on_row(row);
for (int column = first_column; column <= last_column; ++column) { for (int column = first_column; column <= last_column; ++column) {
auto& line = m_terminal.line(row); auto& line = m_terminal.line(row);
if (line.attributes()[column].is_untouched()) { if (line.attribute_at(column).is_untouched()) {
builder.append('\n'); builder.append('\n');
break; break;
} }
// FIXME: This is a bit hackish. // FIXME: This is a bit hackish.
if (line.is_utf32()) { u32 code_point = line.code_point(column);
u32 code_point = line.code_point(column); builder.append(Utf32View(&code_point, 1));
builder.append(Utf32View(&code_point, 1)); if (column == static_cast<int>(line.length()) - 1 || (m_rectangle_selection && column == last_column)) {
} else {
builder.append(line.code_point(column));
}
if (column == line.length() - 1 || (m_rectangle_selection && column == last_column)) {
builder.append('\n'); builder.append('\n');
} }
} }