From ce8005545d5bc94775f616d0caacd55e691e400f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 14 Feb 2020 22:39:26 +0100 Subject: [PATCH 1/7] term: convert cell 'linefeed' attribute to a row 'linebreak' property To do text reflow, we only need to know if a line has been explicitly linebreaked or not. If not, that means it wrapped, and that we should *not* insert a linebreak when reflowing text. When reflowing text, when reaching the end of a row in the old grid, only insert a linebreak in the new grid if the old row had been explicitly linebreaked. Furthermore, when reflowing text and wrapping a row in the new grid, mark the previous row as linebreaked if either the last cell was (the last column in the last row) empty, or the current cell (the first column in the new row) is empty. If both are non-empty, then we assume a linewrap. --- grid.c | 1 + render.c | 58 ++++++++++++++++++++++------------------------------- selection.c | 2 +- terminal.c | 7 ++----- terminal.h | 4 ++-- 5 files changed, 30 insertions(+), 42 deletions(-) diff --git a/grid.c b/grid.c index 744dd175..a9d155e0 100644 --- a/grid.c +++ b/grid.c @@ -28,6 +28,7 @@ grid_row_alloc(int cols, bool initialize) { struct row *row = malloc(sizeof(*row)); row->dirty = false; + row->linebreak = false; if (initialize) { row->cells = calloc(cols, sizeof(row->cells[0])); diff --git a/render.c b/render.c index 2c48629b..14c534d0 100644 --- a/render.c +++ b/render.c @@ -1009,17 +1009,16 @@ reflow(struct terminal *term, struct row **new_grid, int new_cols, int new_rows, /* Walk current line of the old grid */ for (int c = 0; c < old_cols; c++) { - const struct cell *old_cell = &old_row->cells[c]; - - if (old_cell->wc == 0) { + if (old_row->cells[c].wc == 0) { empty_count++; continue; } - assert(old_cell->wc != 0); - - /* Non-empty cell. Emit preceeding string of empty cells, - * and possibly line break for current cell */ + int old_cols_left = old_cols - c; + int cols_needed = empty_count + old_cols_left; + int new_cols_left = new_cols - new_col_idx; + if (new_cols_left < cols_needed && new_cols_left >= old_cols_left) + empty_count = max(0, empty_count - (cols_needed - new_cols_left)); for (int i = 0; i < empty_count + 1; i++) { if (new_col_idx >= new_cols) { @@ -1036,32 +1035,30 @@ reflow(struct terminal *term, struct row **new_grid, int new_cols, int new_rows, new_row->dirty = true; } + assert(new_row != NULL); + assert(new_col_idx >= 0); + assert(new_col_idx < new_cols); + + const struct cell *old_cell = &old_row->cells[c - empty_count + i]; + + if (new_col_idx == 0 && new_row_idx > 0 && + (new_grid[new_row_idx - 1]->cells[new_cols - 1].wc == 0 || + old_cell->wc == 0)) + { + new_grid[new_row_idx - 1]->linebreak = true; + } + + new_row->cells[new_col_idx] = *old_cell; + new_row->cells[new_col_idx].attrs.clean = 1; new_col_idx++; } empty_count = 0; - new_col_idx--; - - assert(new_row != NULL); - assert(new_col_idx >= 0); - assert(new_col_idx < new_cols); - - /* Copy current cell */ - new_row->cells[new_col_idx].attrs.clean = 1; - new_row->cells[new_col_idx++] = *old_cell; } - /* - * If last cell of the old grid's line is empty, then we - * insert a linebreak in the new grid's line too. Unless, the - * *entire* old line was empty. - */ + if (old_row->linebreak) { + new_row->linebreak = true; - if (empty_count < old_cols && - //r < old_rows - 1 && - (old_row->cells[old_cols - 1].wc == 0 || - old_row->cells[old_cols - 1].attrs.linefeed)) - { new_col_idx = 0; new_row_idx = (new_row_idx + 1) & (new_rows - 1); @@ -1237,19 +1234,12 @@ maybe_resize(struct terminal *term, int width, int height, bool force) while (cursor_row < 0) cursor_row += term->grid->num_rows; - /* Heuristic to prevent a new prompt from being printed a new line */ - bool do_linefeed = false; - if (term->cursor.point.col > 0) - cursor_row--; - else if (cursor_row >= term->rows) - do_linefeed = true; - term_cursor_to( term, min(max(cursor_row, 0), term->rows - 1), min(term->cursor.point.col, term->cols - 1)); - if (do_linefeed) + if (cursor_row >= term->rows) term_linefeed(term); term->render.last_cursor.cell = NULL; diff --git a/selection.c b/selection.c index f1485f45..189119d9 100644 --- a/selection.c +++ b/selection.c @@ -213,7 +213,7 @@ extract_one(struct terminal *term, struct row *row, struct cell *cell, if (ctx->last_row != NULL && row != ctx->last_row && ((term->selection.kind == SELECTION_NORMAL && - (ctx->last_cell->wc == 0 || ctx->last_cell->attrs.linefeed)) || + ctx->last_row->linebreak) || term->selection.kind == SELECTION_BLOCK)) { /* Last cell was the last column in the selection */ diff --git a/terminal.c b/terminal.c index cb52a416..b2fb9233 100644 --- a/terminal.c +++ b/terminal.c @@ -1253,6 +1253,7 @@ static inline void erase_line(struct terminal *term, struct row *row) { erase_cell_range(term, row, 0, term->cols - 1); + row->linebreak = false; } void @@ -1498,17 +1499,13 @@ term_scroll_reverse(struct terminal *term, int rows) void term_formfeed(struct terminal *term) { - int col = term->cursor.point.col; - if (!term->cursor.lcf) - col--; - if (col >= 0) - term->grid->cur_row->cells[col].attrs.linefeed = 1; term_cursor_left(term, term->cursor.point.col); } void term_linefeed(struct terminal *term) { + term->grid->cur_row->linebreak = true; if (term->cursor.point.row == term->scroll_region.end - 1) term_scroll(term, 1); else diff --git a/terminal.h b/terminal.h index 449a1329..1d19c9d7 100644 --- a/terminal.h +++ b/terminal.h @@ -42,8 +42,7 @@ struct attributes { uint32_t have_fg:1; uint32_t have_bg:1; uint32_t selected:2; - uint32_t linefeed:1; - uint32_t reserved:2; + uint32_t reserved:3; uint32_t bg:24; }; static_assert(sizeof(struct attributes) == 8, "bad size"); @@ -84,6 +83,7 @@ struct damage { struct row { struct cell *cells; bool dirty; + bool linebreak; }; struct grid { From d11a71e0b221cc24404da37ea928708a03d65c47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 14 Feb 2020 22:43:23 +0100 Subject: [PATCH 2/7] term: reset: use grid_row_alloc() with initialize=true This will both zero out the cells, *and* re-initialize the row properties correctly. --- terminal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terminal.c b/terminal.c index b2fb9233..bec0ce7b 100644 --- a/terminal.c +++ b/terminal.c @@ -1090,8 +1090,8 @@ term_reset(struct terminal *term, bool hard) term->normal.offset = term->normal.view = 0; term->alt.offset = term->alt.view = 0; for (size_t i = 0; i < term->rows; i++) { - memset(grid_row_and_alloc(&term->normal, i)->cells, 0, term->cols * sizeof(struct cell)); - memset(grid_row_and_alloc(&term->alt, i)->cells, 0, term->cols * sizeof(struct cell)); + term->normal.rows[i] = grid_row_alloc(term->cols, true); + term->alt.rows[i] = grid_row_alloc(term->cols, true); } for (size_t i = term->rows; i < term->normal.num_rows; i++) { grid_row_free(term->normal.rows[i]); From 9e6404be113f28f6755049075220460c08cecdeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 15 Feb 2020 18:56:16 +0100 Subject: [PATCH 3/7] render: reflow: bug: fix off-by-one The 'last-row' variable points to the last row the cursor is *on*, thus it's not a counter, and we need to add one when calculating the new grid offsets, or we get an off-by-one error. With this, there's no longer any need to scroll the reflowed text. --- render.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/render.c b/render.c index 14c534d0..3dd98533 100644 --- a/render.c +++ b/render.c @@ -1150,8 +1150,8 @@ maybe_resize(struct terminal *term, int width, int height, bool force) /* Reset offset such that the last copied row ends up at the * bottom of the screen */ - term->normal.offset = last_normal_row - new_rows; - term->alt.offset = last_alt_row - new_rows; + term->normal.offset = last_normal_row - new_rows + 1; + term->alt.offset = last_alt_row - new_rows + 1; /* Can't have negative offsets, so wrap 'em */ while (term->normal.offset < 0) @@ -1165,6 +1165,7 @@ maybe_resize(struct terminal *term, int width, int height, bool force) while (alt[term->alt.offset] == NULL) term->alt.offset = (term->alt.offset + 1) & (new_alt_grid_rows - 1); + /* TODO: try to keep old view */ term->normal.view = term->normal.offset; term->alt.view = term->alt.offset; @@ -1234,14 +1235,14 @@ maybe_resize(struct terminal *term, int width, int height, bool force) while (cursor_row < 0) cursor_row += term->grid->num_rows; + assert(cursor_row >= 0); + assert(cursor_row < term->rows); + term_cursor_to( term, - min(max(cursor_row, 0), term->rows - 1), + cursor_row, min(term->cursor.point.col, term->cols - 1)); - if (cursor_row >= term->rows) - term_linefeed(term); - term->render.last_cursor.cell = NULL; tll_free(term->normal.scroll_damage); tll_free(term->alt.scroll_damage); From ab43a6fd37981a286873cb614ac3565e024948c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 15 Feb 2020 18:57:52 +0100 Subject: [PATCH 4/7] render: resize: cancel selection before reflowing text The old selection will no longer be valid once we resize the grids. --- render.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/render.c b/render.c index 3dd98533..fd40a71e 100644 --- a/render.c +++ b/render.c @@ -1105,6 +1105,8 @@ maybe_resize(struct terminal *term, int width, int height, bool force) if (!force && width == term->width && height == term->height && scale == term->scale) return; + selection_cancel(term); + /* Cancel an application initiated "Synchronized Update" */ term_disable_app_sync_updates(term); From 56c2dd41992da9d15e84ded81cc929fb3db43d97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 15 Feb 2020 18:58:36 +0100 Subject: [PATCH 5/7] render: reflow: no need to mark new rows as dirty --- render.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/render.c b/render.c index fd40a71e..9c18b7fb 100644 --- a/render.c +++ b/render.c @@ -16,8 +16,10 @@ #define LOG_MODULE "render" #define LOG_ENABLE_DBG 0 #include "log.h" -#include "shm.h" +#include "config.h" #include "grid.h" +#include "selection.h" +#include "shm.h" #define min(x, y) ((x) < (y) ? (x) : (y)) #define max(x, y) ((x) > (y) ? (x) : (y)) @@ -981,7 +983,6 @@ reflow(struct terminal *term, struct row **new_grid, int new_cols, int new_rows, assert(new_row == NULL); new_row = grid_row_alloc(new_cols, true); - new_row->dirty = true; new_grid[new_row_idx] = new_row; /* Start at the beginning of the old grid's scrollback. That is, @@ -1032,7 +1033,6 @@ reflow(struct terminal *term, struct row **new_grid, int new_cols, int new_rows, } else memset(new_row->cells, 0, new_cols * sizeof(new_row->cells[0])); - new_row->dirty = true; } assert(new_row != NULL); @@ -1069,7 +1069,6 @@ reflow(struct terminal *term, struct row **new_grid, int new_cols, int new_rows, } else memset(new_row->cells, 0, new_cols * sizeof(new_row->cells[0])); - new_row->dirty = true; } } From 3019d5936980a0c3ec35329c5c393d8af3ce4ea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 15 Feb 2020 18:58:57 +0100 Subject: [PATCH 6/7] render: reflow: remove 'linebreak' flag from recycled rows --- render.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/render.c b/render.c index 9c18b7fb..a75a5935 100644 --- a/render.c +++ b/render.c @@ -1030,9 +1030,10 @@ reflow(struct terminal *term, struct row **new_grid, int new_cols, int new_rows, if (new_row == NULL) { new_row = grid_row_alloc(new_cols, true); new_grid[new_row_idx] = new_row; - } else + } else { memset(new_row->cells, 0, new_cols * sizeof(new_row->cells[0])); - + new_row->linebreak = false; + } } assert(new_row != NULL); @@ -1066,9 +1067,10 @@ reflow(struct terminal *term, struct row **new_grid, int new_cols, int new_rows, if (new_row == NULL) { new_row = grid_row_alloc(new_cols, true); new_grid[new_row_idx] = new_row; - } else + } else { memset(new_row->cells, 0, new_cols * sizeof(new_row->cells[0])); - + new_row->linebreak = false; + } } } From f07a124c11fbbef03afbf9885964840b230d066c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 15 Feb 2020 18:59:21 +0100 Subject: [PATCH 7/7] render: reflow: set 'linebreak' flag before inserting new line --- render.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/render.c b/render.c index a75a5935..b78e6454 100644 --- a/render.c +++ b/render.c @@ -1022,7 +1022,21 @@ reflow(struct terminal *term, struct row **new_grid, int new_cols, int new_rows, empty_count = max(0, empty_count - (cols_needed - new_cols_left)); for (int i = 0; i < empty_count + 1; i++) { + const struct cell *old_cell = &old_row->cells[c - empty_count + i]; + + /* Out of columns on current row in new grid? */ if (new_col_idx >= new_cols) { + /* + * If last cell on last row and first cell on new + * row are non-empty, wrap the line, otherwise + * insert a hard line break. + */ + if (new_row->cells[new_cols - 1].wc == 0 || + old_cell->wc == 0) + { + new_row->linebreak = true; + } + new_col_idx = 0; new_row_idx = (new_row_idx + 1) & (new_rows - 1); @@ -1040,15 +1054,6 @@ reflow(struct terminal *term, struct row **new_grid, int new_cols, int new_rows, assert(new_col_idx >= 0); assert(new_col_idx < new_cols); - const struct cell *old_cell = &old_row->cells[c - empty_count + i]; - - if (new_col_idx == 0 && new_row_idx > 0 && - (new_grid[new_row_idx - 1]->cells[new_cols - 1].wc == 0 || - old_cell->wc == 0)) - { - new_grid[new_row_idx - 1]->linebreak = true; - } - new_row->cells[new_col_idx] = *old_cell; new_row->cells[new_col_idx].attrs.clean = 1; new_col_idx++;