From 2e828248d07490bd56baa3458a970e1ad34ede7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 7 Feb 2022 13:56:55 +0100 Subject: [PATCH 1/4] selection: ensure start/end coordinates are bounded by the current grid Closes #924 --- selection.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/selection.c b/selection.c index 0ed25cd7..5d2cd61d 100644 --- a/selection.c +++ b/selection.c @@ -1108,16 +1108,8 @@ selection_finalize(struct seat *seat, struct terminal *term, uint32_t serial) xassert(term->selection.start.row != -1); xassert(term->selection.end.row != -1); - if (term->selection.start.row > term->selection.end.row || - (term->selection.start.row == term->selection.end.row && - term->selection.start.col > term->selection.end.col)) - { - struct coord tmp = term->selection.start; - term->selection.start = term->selection.end; - term->selection.end = tmp; - } - - xassert(term->selection.start.row <= term->selection.end.row); + term->selection.start.row &= (term->grid->num_rows - 1); + term->selection.end.row &= (term->grid->num_rows - 1); switch (term->conf->selection_target) { case SELECTION_TARGET_NONE: From 0800515c04ba52b5b31c43d326bd590fe6cb5954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 7 Feb 2022 13:57:25 +0100 Subject: [PATCH 2/4] grid: reflow: add TODO to detect selection on re-used rows, and cancel it --- grid.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/grid.c b/grid.c index 844ea90c..5a0b5c7e 100644 --- a/grid.c +++ b/grid.c @@ -482,6 +482,13 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row, tll_remove(old_grid->sixel_images, it); } } + + /* + * TODO: detect if the re-used row is covered by the + * selection. Of so, cancel the selection. The problem: we + * don’t know if we’ve translated the selection coordinates + * yet. + */ } struct row_data *extra = row->extra; From ef522e292faccff82fde76bd4b4c65cf211a93ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 7 Feb 2022 14:13:38 +0100 Subject: [PATCH 3/4] selection: foreach: sort start/end based on their scrollback-start relative values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When iterating the characters in a selection, we want to go from the “start” to the “end”, where start is the upper left-most character, and “end” is the lower right-most character. There are two things to consider: * The ‘start’ coordinate may actually sort after the ‘end’ coordinate (user selected from bottom of the window and upward) * The scrollback wraparound. What we do is calculate both the star and end coordinates’ scrollback-start relative row numbers. That is, the number of rows from the beginning of the scrollback. So if the very first row (i.e. the oldest) in the scrollback is selected, that has the scrollback-start relative number “0”. Then we loop from whichever (start or end coordinate) is highest up in the scrollback, to the “other” coordinate. --- selection.c | 76 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/selection.c b/selection.c index 5d2cd61d..e9a9f752 100644 --- a/selection.c +++ b/selection.c @@ -107,24 +107,34 @@ selection_view_down(struct terminal *term, int new_view) static void foreach_selected_normal( struct terminal *term, struct coord _start, struct coord _end, - bool (*cb)(struct terminal *term, struct row *row, struct cell *cell, int row_no, int col, void *data), + bool (*cb)(struct terminal *term, struct row *row, struct cell *cell, + int row_no, int col, void *data), void *data) { const struct coord *start = &_start; const struct coord *end = &_end; + const int scrollback_start = term->grid->offset + term->rows; + const int grid_rows = term->grid->num_rows; + + /* Start/end rows, relative to the scrollback start */ + const int rel_start_row = + (start->row - scrollback_start + grid_rows) & (grid_rows - 1); + const int rel_end_row = + (end->row - scrollback_start + grid_rows) & (grid_rows - 1); + int start_row, end_row; int start_col, end_col; - if (start->row < end->row) { + if (rel_start_row < rel_end_row) { start_row = start->row; - end_row = end->row; start_col = start->col; + end_row = end->row; end_col = end->col; - } else if (start->row > end->row) { + } else if (rel_start_row > rel_end_row) { start_row = end->row; - end_row = start->row; start_col = end->col; + end_row = start->row; end_col = start->col; } else { start_row = end_row = start->row; @@ -132,51 +142,77 @@ foreach_selected_normal( end_col = max(start->col, end->col); } - for (int r = start_row; r <= end_row; r++) { - size_t real_r = r & (term->grid->num_rows - 1); - struct row *row = term->grid->rows[real_r]; + start_row &= (grid_rows - 1); + end_row &= (grid_rows - 1); + + for (int r = start_row; r != end_row; r = (r + 1) & (grid_rows - 1)) { + struct row *row = term->grid->rows[r]; xassert(row != NULL); - for (int c = start_col; - c <= (r == end_row ? end_col : term->cols - 1); - c++) - { - if (!cb(term, row, &row->cells[c], real_r, c, data)) + for (int c = start_col; c <= term->cols - 1; c++) { + if (!cb(term, row, &row->cells[c], r, c, data)) return; } start_col = 0; } + + /* Last, partial row */ + struct row *row = term->grid->rows[end_row]; + xassert(row != NULL); + + for (int c = start_col; c <= end_col; c++) { + if (!cb(term, row, &row->cells[c], end_row, c, data)) + return; + } } static void foreach_selected_block( struct terminal *term, struct coord _start, struct coord _end, - bool (*cb)(struct terminal *term, struct row *row, struct cell *cell, int row_no, int col, void *data), + bool (*cb)(struct terminal *term, struct row *row, struct cell *cell, + int row_no, int col, void *data), void *data) { const struct coord *start = &_start; const struct coord *end = &_end; + const int scrollback_start = term->grid->offset + term->rows; + const int grid_rows = term->grid->num_rows; + + /* Start/end rows, relative to the scrollback start */ + const int rel_start_row = + (start->row - scrollback_start + grid_rows) & (grid_rows - 1); + const int rel_end_row = + (end->row - scrollback_start + grid_rows) & (grid_rows - 1); + struct coord top_left = { - .row = min(start->row, end->row), + .row = (rel_start_row < rel_end_row + ? start->row : end->row) & (grid_rows - 1), .col = min(start->col, end->col), }; struct coord bottom_right = { - .row = max(start->row, end->row), + .row = (rel_start_row > rel_end_row + ? start->row : end->row) & (grid_rows - 1), .col = max(start->col, end->col), }; - for (int r = top_left.row; r <= bottom_right.row; r++) { - size_t real_r = r & (term->grid->num_rows - 1); - struct row *row = term->grid->rows[real_r]; + int r = top_left.row; + while (true) { + struct row *row = term->grid->rows[r]; xassert(row != NULL); for (int c = top_left.col; c <= bottom_right.col; c++) { - if (!cb(term, row, &row->cells[c], real_r, c, data)) + if (!cb(term, row, &row->cells[c], r, c, data)) return; } + + if (r == bottom_right.row) + break; + + r++; + r &= grid_rows - 1; } } From 1b0cfafb9ef7e2b628e968452689f3b869054281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 7 Feb 2022 14:37:39 +0100 Subject: [PATCH 4/4] changelog: scrollback wrap-around crossing selections --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index abc72229..309b8e99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,8 @@ about a non-monospaced primary font. * Rare crash when the window is resized while a mouse selection is ongoing (https://codeberg.org/dnkl/foot/issues/922). +* Large selections crossing the scrollback wrap-around + (https://codeberg.org/dnkl/foot/issues/924). ### Security