Skip to content

Commit bc0b656

Browse files
committed
terminal: fix crash when selected an empty row
Previously we prevented empty rows intentionally for the purpose of accounting the scroll back buffer's capacity. However, when adding reflow() the same constraint was not kept, meaning we would end up with empty rows in the scroll back. Since empty rows are more memory efficient we should just handle them properly. Scroll back accounting now treats empty rows as having a single cell in them. Selection related code paths now handle empty rows properly.
1 parent f2bc34a commit bc0b656

File tree

3 files changed

+41
-8
lines changed

3 files changed

+41
-8
lines changed

lib/src/terminal/row_group.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ auto RowGroup::transfer_from(RowGroup& from, usize from_index, usize to_index, u
225225
auto total_cells = 0_usize;
226226
for (auto [from_row, to_row] : di::zip(di::View(from_begin, from_end), di::View(to_begin, to_end))) {
227227
auto cols_to_take = desired_cols.value_or(from_row.cells.size());
228-
total_cells += cols_to_take;
228+
total_cells += cols_to_take > 0 ? cols_to_take : 1; // Consider empty rows to have 1 cell.
229229
to_row.cells.resize(cols_to_take);
230230

231231
// Truncate any wide cells if they won't fully fit into the requested column size.
@@ -295,16 +295,16 @@ auto RowGroup::strip_trailing_empty_cells(usize row_index) -> usize {
295295
return row.cells.size();
296296
}
297297

298-
// Ensure all rows have at least size 1, as otherwise our cell based scroll back
299-
// limit breaks down (you could have an unbounded number of blank lines). Multi
300-
// cells are never empty, so no special handling is needed.
301298
while (row.cells.size() > 1) {
302299
if (row.cells.back().value().is_empty()) {
303300
row.cells.pop_back();
304301
continue;
305302
}
306303
break;
307304
}
308-
return row.cells.size();
305+
306+
// Ensure all rows have are considered at least size 1, as otherwise our cell based scroll back
307+
// limit breaks down (you could have an unbounded number of blank lines).
308+
return row.cells.empty() ? 1 : row.cells.size();
309309
}
310310
}

lib/src/terminal/screen.cpp

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,7 +1460,11 @@ auto Screen::clamp_selection_point(AbsolutePosition const& point) const
14601460
adjusted_point.row = di::clamp(point.row, absolute_row_start(), absolute_row_end() - 1);
14611461
auto [row_index, row_group] = find_row(adjusted_point.row);
14621462
auto const& row = row_group.rows()[row_index];
1463-
adjusted_point.col = di::clamp(point.col, 0_u32, u32(row.cells.size() - 1));
1463+
if (row.cells.empty()) {
1464+
adjusted_point.col = 0;
1465+
} else {
1466+
adjusted_point.col = di::clamp(point.col, 0_u32, u32(row.cells.size() - 1));
1467+
}
14641468

14651469
// Adjust the selection point to be at the start of any potential multicell.
14661470
while (adjusted_point.col > 0 && row.cells[adjusted_point.col].is_nonprimary_in_multi_cell()) {
@@ -1474,6 +1478,10 @@ void Screen::begin_selection(AbsolutePosition const& point, BeginSelectionMode m
14741478

14751479
auto [adjusted_point, row_group, row_index] = clamp_selection_point(point);
14761480
auto const& row = row_group.rows()[row_index];
1481+
if (row.cells.empty()) {
1482+
m_selection = { adjusted_point, adjusted_point };
1483+
return;
1484+
}
14771485
switch (mode) {
14781486
case BeginSelectionMode::Single: {
14791487
m_selection = { adjusted_point, adjusted_point };
@@ -1528,7 +1536,6 @@ void Screen::begin_selection(AbsolutePosition const& point, BeginSelectionMode m
15281536

15291537
void Screen::update_selection(AbsolutePosition const& point) {
15301538
auto [adjusted_point, row_group, row_index] = clamp_selection_point(point);
1531-
// auto const& row = row_group.rows()[row_index];
15321539
if (!m_selection) {
15331540
begin_selection(adjusted_point, BeginSelectionMode::Single);
15341541
}
@@ -1585,7 +1592,7 @@ void Screen::invalidate_region(Selection const& region) {
15851592
// Fast path: the entire row is contained the selection.
15861593
auto [row, group] = find_row(r);
15871594
auto const& row_object = group.rows()[row];
1588-
if (r > start.row && r < end.row) {
1595+
if ((r > start.row && r < end.row) || row_object.cells.empty()) {
15891596
row_object.stale = false;
15901597
continue;
15911598
}
@@ -1644,6 +1651,12 @@ auto Screen::selected_text(Selection selection) const -> di::String {
16441651
}
16451652
continue;
16461653
}
1654+
if (row_object.cells.empty()) {
1655+
if (r != end.row) {
1656+
text.push_back('\n');
1657+
}
1658+
continue;
1659+
}
16471660

16481661
// Slow path: iterate over the whole row and add the relevant cells.
16491662
auto iter_start_col = r == start.row ? start.col : 0_usize;

lib/test/src/test_screen.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,25 @@ static void selection() {
381381
ASSERT_EQ(screen.selected_text(), u8"ab猫\n猫fghh猫"_sv);
382382
}
383383

384+
static void selection_empty() {
385+
auto screen = Screen({ 2, 5 }, Screen::ScrollBackEnabled::Yes);
386+
387+
put_text(screen, u8"a\n"_sv
388+
u8"\n"_sv
389+
u8"b\n"_sv
390+
u8"c"_sv);
391+
392+
screen.begin_selection({ screen.absolute_row_screen_start(), 0 }, Screen::BeginSelectionMode::Word);
393+
screen.update_selection({ screen.absolute_row_start() + 1, 4 }); // Out of bounds.
394+
395+
auto text = screen.selected_text();
396+
ASSERT_EQ(text, "\nb"_sv);
397+
398+
screen.begin_selection({ screen.absolute_row_start() + 1, 4 }, Screen::BeginSelectionMode::Line); // Out of bounds.
399+
text = screen.selected_text();
400+
ASSERT_EQ(text, ""_sv);
401+
}
402+
384403
static void put_text_random() {
385404
auto screen = Screen({ 2, 200 }, Screen::ScrollBackEnabled::Yes);
386405
auto decoder = ttx::Utf8StreamDecoder {};
@@ -1084,6 +1103,7 @@ TEST(screen, put_text_wide)
10841103
TEST(screen, put_text_damage_tracking)
10851104
TEST(screen, put_text_random)
10861105
TEST(screen, selection)
1106+
TEST(screen, selection_empty)
10871107
TEST(screen, cursor_movement)
10881108
TEST(screen, origin_mode_cursor_movement)
10891109
TEST(screen, clear_row)

0 commit comments

Comments
 (0)