Skip to content

Commit 96765ad

Browse files
Limit Text::Select to the selected line range
Select allocated one entry per line of the whole text and scanned from line 0 on every frame while a selection is active. Store only the selection/box intersection. 100k lines: 0.41ms -> 0.011ms per frame.
1 parent 12987a2 commit 96765ad

3 files changed

Lines changed: 42 additions & 14 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ Next
1616
- Performance: `text` computes its requirement once and renders only the
1717
visible lines. This makes scrolling a large text inside a `frame`
1818
significantly faster. Thanks @patlefort. See #1309.
19+
- Performance: `text` selection now only visits and stores the selected line
20+
range, instead of scanning and allocating one entry per line of the whole
21+
text on every frame.
1922

2023
7.0.1 (2026-07-14)
2124
------------------

src/ftxui/dom/selection_test.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,24 @@ TEST(SelectionTest, StyleClearedWhenSelectionCleared) {
173173
}
174174
}
175175

176+
// A selection over a subset of the lines of a multi-line text must style and
177+
// extract exactly the selected rows.
178+
TEST(SelectionTest, MultiLineTextSelection) {
179+
auto element = text("abc\ndef\nghi");
180+
auto screen = App::FixedSize(3, 3);
181+
Selection selection(1, 1, 2, 2);
182+
Render(screen, element.get(), selection);
183+
184+
EXPECT_EQ(selection.GetParts(), "ef\nghi");
185+
for (int y = 0; y < 3; y++) {
186+
for (int x = 0; x < 3; x++) {
187+
const bool selected = (y == 1 && x >= 1) || y == 2;
188+
EXPECT_EQ(screen.CellAt(x, y).inverted, selected)
189+
<< "at (" << x << "," << y << ")";
190+
}
191+
}
192+
}
193+
176194
TEST(SelectionTest, VBoxSelection) {
177195
auto element = vbox({
178196
text("Lorem ipsum dolor"),

src/ftxui/dom/text.cpp

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -56,27 +56,30 @@ class Text : public Node {
5656
}
5757

5858
void Select(Selection& selection) override {
59-
if (Box::Intersection(selection.GetBox(), box_).IsEmpty()) {
59+
const Box selection_box = Box::Intersection(selection.GetBox(), box_);
60+
if (selection_box.IsEmpty()) {
6061
return;
6162
}
6263

63-
selection_rows_.assign(lines_offsets_.size() - 1, {-1, -1});
64+
// Only store the selected line range. Sizing per line would allocate one
65+
// entry per line of the whole text on every frame.
66+
const size_t lines_count = lines_offsets_.size() - 1;
67+
const size_t first = selection_box.y_min - box_.y_min;
68+
const size_t last =
69+
std::min<size_t>(selection_box.y_max - box_.y_min + 1, lines_count);
70+
if (first >= last) {
71+
return;
72+
}
73+
selection_first_line_ = first;
74+
selection_rows_.assign(last - first, {-1, -1});
6475

65-
for (size_t i = 0; i < lines_offsets_.size() - 1; ++i) {
76+
for (size_t i = first; i < last; ++i) {
6677
const int y = box_.y_min + (int)i;
67-
if (y > box_.y_max) {
68-
break;
69-
}
70-
7178
const Box row_box{box_.x_min, box_.x_max, y, y};
72-
if (Box::Intersection(selection.GetBox(), row_box).IsEmpty()) {
73-
continue;
74-
}
75-
7679
const Selection row_sel = selection.SaturateHorizontal(row_box);
7780
const int sel_start = row_sel.GetBox().x_min;
7881
const int sel_end = row_sel.GetBox().x_max;
79-
selection_rows_[i] = {sel_start, sel_end};
82+
selection_rows_[i - first] = {sel_start, sel_end};
8083

8184
std::string part;
8285
int x = box_.x_min;
@@ -116,8 +119,9 @@ class Text : public Node {
116119
auto& cell = screen.CellAt(x, y);
117120
cell.character = *glyph;
118121

119-
if (line < selection_rows_.size()) {
120-
const auto& [sel_start, sel_end] = selection_rows_[line];
122+
const size_t sel_index = line - selection_first_line_;
123+
if (sel_index < selection_rows_.size()) {
124+
const auto& [sel_start, sel_end] = selection_rows_[sel_index];
121125
if (sel_start != -1 && x >= sel_start && x <= sel_end) {
122126
screen.GetSelectionStyle()(cell);
123127
}
@@ -129,6 +133,9 @@ class Text : public Node {
129133
private:
130134
std::vector<std::string> glyphs_;
131135
std::vector<int> lines_offsets_;
136+
// Selection state for the line range [selection_first_line_,
137+
// selection_first_line_ + selection_rows_.size()).
138+
size_t selection_first_line_ = 0;
132139
std::vector<std::pair<int, int>> selection_rows_;
133140
};
134141

0 commit comments

Comments
 (0)