From e415f858295ced5dbb1a0af961c4307c92b0c634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 28 Mar 2021 20:59:35 +0200 Subject: [PATCH 01/12] search: find_next(): proper check for scrollback wrap around MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Besides disallowing matches that crosses the scrollback wrap-around, this also fixes a crash when the trying to search beyond the last output, when the scrollback history hasn’t yet been completely filled. --- CHANGELOG.md | 1 + search.c | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10ea7595..f1ac30d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -153,6 +153,7 @@ * PTY not being drained when the client application terminates. * `auto_left_margin` not being limited to `cub1` (https://codeberg.org/dnkl/foot/issues/441). +* Crash in scrollback search mode when searching beyond the last output. ### Contributors diff --git a/search.c b/search.c index 911b13c0..5033c42c 100644 --- a/search.c +++ b/search.c @@ -77,6 +77,16 @@ search_ensure_size(struct terminal *term, size_t wanted_size) return true; } +static bool +has_wrapped_around(const struct terminal *term, int abs_row_no) +{ + int scrollback_start = term->grid->offset + term->rows; + int rebased_row = abs_row_no - scrollback_start + term->grid->num_rows; + rebased_row &= term->grid->num_rows - 1; + + return rebased_row == 0; +} + static void search_cancel_keep_selection(struct terminal *term) { @@ -327,13 +337,12 @@ search_find_next(struct terminal *term) for (size_t i = 0; i < term->search.len;) { if (end_col >= term->cols) { - if (end_row + 1 > grid_row_absolute(term->grid, term->grid->offset + term->rows - 1)) { - /* Don't continue past end of the world */ - break; - } - end_row++; end_col = 0; + + if (has_wrapped_around(term, end_row)) + break; + row = term->grid->rows[end_row]; } From eab874eb06de11763683f312e6c1557319788cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 28 Mar 2021 21:03:53 +0200 Subject: [PATCH 02/12] selection: expose find_word_boundary_{left,right}() --- selection.c | 28 ++++++++++++++++------------ selection.h | 5 +++++ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/selection.c b/selection.c index 5bb386ab..7b0d93d7 100644 --- a/selection.c +++ b/selection.c @@ -227,9 +227,9 @@ selection_to_text(const struct terminal *term) return extract_finish(ctx, &text, NULL) ? text : NULL; } -static void -find_word_boundary_left(struct terminal *term, struct coord *pos, - bool spaces_only) +void +selection_find_word_boundary_left(struct terminal *term, struct coord *pos, + bool spaces_only) { const struct row *r = grid_row_in_view(term->grid, pos->row); wchar_t c = r->cells[pos->col].wc; @@ -299,9 +299,9 @@ find_word_boundary_left(struct terminal *term, struct coord *pos, } } -static void -find_word_boundary_right(struct terminal *term, struct coord *pos, - bool spaces_only) +void +selection_find_word_boundary_right(struct terminal *term, struct coord *pos, + bool spaces_only) { const struct row *r = grid_row_in_view(term->grid, pos->row); wchar_t c = r->cells[pos->col].wc; @@ -403,8 +403,8 @@ selection_start(struct terminal *term, int col, int row, case SELECTION_WORD_WISE: { struct coord start = {col, row}, end = {col, row}; - find_word_boundary_left(term, &start, spaces_only); - find_word_boundary_right(term, &end, spaces_only); + selection_find_word_boundary_left(term, &start, spaces_only); + selection_find_word_boundary_right(term, &end, spaces_only); term->selection.start = (struct coord){ start.col, term->grid->view + start.row}; @@ -694,14 +694,16 @@ selection_update(struct terminal *term, int col, int row) switch (term->selection.direction) { case SELECTION_LEFT: { struct coord end = {col, row}; - find_word_boundary_left(term, &end, term->selection.spaces_only); + selection_find_word_boundary_left( + term, &end, term->selection.spaces_only); new_end = (struct coord){end.col, term->grid->view + end.row}; break; } case SELECTION_RIGHT: { struct coord end = {col, row}; - find_word_boundary_right(term, &end, term->selection.spaces_only); + selection_find_word_boundary_right( + term, &end, term->selection.spaces_only); new_end = (struct coord){end.col, term->grid->view + end.row}; break; } @@ -840,8 +842,10 @@ selection_extend_normal(struct terminal *term, int col, int row, struct coord pivot_start = {new_start.col, new_start.row - term->grid->view}; struct coord pivot_end = pivot_start; - find_word_boundary_left(term, &pivot_start, term->selection.spaces_only); - find_word_boundary_right(term, &pivot_end, term->selection.spaces_only); + selection_find_word_boundary_left( + term, &pivot_start, term->selection.spaces_only); + selection_find_word_boundary_right( + term, &pivot_end, term->selection.spaces_only); term->selection.pivot.start = (struct coord){pivot_start.col, term->grid->view + pivot_start.row}; diff --git a/selection.h b/selection.h index 241ec262..295d8d1c 100644 --- a/selection.h +++ b/selection.h @@ -74,3 +74,8 @@ void selection_start_scroll_timer( struct terminal *term, int interval_ns, enum selection_scroll_direction direction, int col); void selection_stop_scroll_timer(struct terminal *term); + +void selection_find_word_boundary_left( + struct terminal *term, struct coord *pos, bool spaces_only); +void selection_find_word_boundary_right( + struct terminal *term, struct coord *pos, bool spaces_only); From 1bc9fd5fe14a60db861fb317f731a5e27e857bf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 28 Mar 2021 21:04:48 +0200 Subject: [PATCH 03/12] extract: add extract_finish_wide(), and optionally skip stripping trailing empty cells extract_finish() returns the extracted text in UTF-8, while extract_finish_wide() returns the extracted text in Unicode. This patch also adds a new argument to extract_finish{,_wide}, that when set to true, skips stripping trailing empty cells. --- extract.c | 57 +++++++++++++++++++++++++++++++++++++++++++---------- extract.h | 7 ++++++- selection.c | 2 +- terminal.c | 2 +- 4 files changed, 55 insertions(+), 13 deletions(-) diff --git a/extract.c b/extract.c index ac5f3897..dd72ea92 100644 --- a/extract.c +++ b/extract.c @@ -51,10 +51,9 @@ ensure_size(struct extraction_context *ctx, size_t additional_chars) } bool -extract_finish(struct extraction_context *ctx, char **text, size_t *len) +extract_finish_wide(struct extraction_context *ctx, bool strip_trailing_empty, + wchar_t **text, size_t *len) { - bool ret = false; - if (text == NULL) return false; @@ -63,12 +62,24 @@ extract_finish(struct extraction_context *ctx, char **text, size_t *len) *len = 0; if (ctx->failed) - goto out; + goto err; + + if (!strip_trailing_empty) { + /* Insert pending newlines, and replace empty cells with spaces */ + if (!ensure_size(ctx, ctx->newline_count + ctx->empty_count)) + goto err; + + for (size_t i = 0; i < ctx->newline_count; i++) + ctx->buf[ctx->idx++] = L'\n'; + + for (size_t i = 0; i < ctx->empty_count; i++) + ctx->buf[ctx->idx++] = L' '; + } if (ctx->idx == 0) { /* Selection of empty cells only */ if (!ensure_size(ctx, 1)) - goto out; + goto err; ctx->buf[ctx->idx++] = L'\0'; } else { xassert(ctx->idx > 0); @@ -77,12 +88,39 @@ extract_finish(struct extraction_context *ctx, char **text, size_t *len) ctx->buf[ctx->idx - 1] = L'\0'; else { if (!ensure_size(ctx, 1)) - goto out; + goto err; ctx->buf[ctx->idx++] = L'\0'; } } - size_t _len = wcstombs(NULL, ctx->buf, 0); + *text = ctx->buf; + if (len != NULL) + *len = ctx->idx - 1; + free(ctx); + return true; + +err: + free(ctx->buf); + free(ctx); + return false; +} + +bool +extract_finish(struct extraction_context *ctx, bool strip_trailing_empty, + char **text, size_t *len) +{ + if (text == NULL) + return false; + if (len != NULL) + *len = 0; + + wchar_t *wtext; + if (!extract_finish_wide(ctx, strip_trailing_empty, &wtext, NULL)) + return false; + + bool ret = false; + + size_t _len = wcstombs(NULL, wtext, 0); if (_len == (size_t)-1) { LOG_ERRNO("failed to convert selection to UTF-8"); goto out; @@ -94,7 +132,7 @@ extract_finish(struct extraction_context *ctx, char **text, size_t *len) goto out; } - wcstombs(*text, ctx->buf, _len + 1); + wcstombs(*text, wtext, _len + 1); if (len != NULL) *len = _len; @@ -102,8 +140,7 @@ extract_finish(struct extraction_context *ctx, char **text, size_t *len) ret = true; out: - free(ctx->buf); - free(ctx); + free(wtext); return ret; } diff --git a/extract.h b/extract.h index 0b3693d2..879461cf 100644 --- a/extract.h +++ b/extract.h @@ -2,6 +2,7 @@ #include #include +#include #include "terminal.h" @@ -14,4 +15,8 @@ bool extract_one( int col, void *context); bool extract_finish( - struct extraction_context *context, char **text, size_t *len); + struct extraction_context *context, bool strip_trailing_empty, + char **text, size_t *len); +bool extract_finish_wide( + struct extraction_context *context, bool strip_trailing_empty, + wchar_t **text, size_t *len); diff --git a/selection.c b/selection.c index 7b0d93d7..7316dfc3 100644 --- a/selection.c +++ b/selection.c @@ -224,7 +224,7 @@ selection_to_text(const struct terminal *term) &extract_one_const_wrapper, ctx); char *text; - return extract_finish(ctx, &text, NULL) ? text : NULL; + return extract_finish(ctx, true, &text, NULL) ? text : NULL; } void diff --git a/terminal.c b/terminal.c index 4a39b659..c2bcf86f 100644 --- a/terminal.c +++ b/terminal.c @@ -2917,7 +2917,7 @@ rows_to_text(const struct terminal *term, int start, int end, } out: - return extract_finish(ctx, text, len); + return extract_finish(ctx, true, text, len); } bool From e460727afd5fb19ed8bf42d6ecbbd9e9f98a20c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 28 Mar 2021 21:06:21 +0200 Subject: [PATCH 04/12] search: match_to_end_of_word(): refactor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rewrite match_to_end_of_word() in terms of * selection_find_word_boundary_right() * extract_begin() + extract_one() + extract_finish() This adds a small overhead, in that extract_*() allocates an internal buffer, from which we then immediately copy, into our newly resized prompt buffer. On the other hand, this makes the matching behavior more consistent with regular mouse selections, and we don’t have to keep two very similar match-to-next-word-boundary implementations in sync. --- search.c | 103 ++++++++++++++++++++++--------------------------------- 1 file changed, 41 insertions(+), 62 deletions(-) diff --git a/search.c b/search.c index 5033c42c..acc1faee 100644 --- a/search.c +++ b/search.c @@ -11,6 +11,7 @@ #define LOG_ENABLE_DBG 0 #include "log.h" #include "config.h" +#include "extract.h" #include "grid.h" #include "input.h" #include "misc.h" @@ -437,91 +438,69 @@ search_match_to_end_of_word(struct terminal *term, bool spaces_only) if (term->search.match_len == 0) return; - xassert(term->search.match.row != -1); - xassert(term->search.match.col != -1); + xassert(term->selection.end.row != -1); - int end_row = term->search.match.row; - int end_col = term->search.match.col; - size_t len = term->search.match_len; + const bool move_cursor = term->search.cursor == term->search.len; - /* Calculate end coord - note: assumed to be valid */ - for (size_t i = 0; i < len; i++) { - for (size_t j = 0; j < wcwidth(term->search.buf[i]); j++) { - if (++end_col >= term->cols) { - end_row = (end_row + 1) & (term->grid->num_rows - 1); - end_col = 0; - } - } + const struct coord old_end = term->selection.end; + struct coord new_end = old_end; + + /* First character to consider is the *next* character */ + if (++new_end.col >= term->cols) { + new_end.row = (new_end.row + 1) & (term->grid->num_rows - 1); + new_end.col = 0; + + if (has_wrapped_around(term, new_end.row)) + return; } - tll(wchar_t) new_chars = tll_init(); + xassert(term->grid->rows[new_end.row] != NULL); - /* Always append at least one character *if* possible */ - bool first = true; + new_end.row -= term->grid->view; + selection_find_word_boundary_right(term, &new_end, spaces_only); + new_end.row += term->grid->view; - for (size_t r = 0; - r < term->grid->num_rows; - end_row = (end_row + 1) & (term->grid->num_rows - 1), r++) - { - const struct row *row = term->grid->rows[end_row]; - if (row == NULL) - break; + if (new_end.row == old_end.row && new_end.col == old_end.col) + return; - bool done = false; - for (; end_col < term->cols; end_col++) { - wchar_t wc = row->cells[end_col].wc; - if (wc >= CELL_SPACER) - continue; + struct coord pos = old_end; + const struct row *row = term->grid->rows[pos.row]; - const struct composed *composed = NULL; - if (wc >= CELL_COMB_CHARS_LO && - wc < (CELL_COMB_CHARS_LO + term->composed_count)) - { - composed = &term->composed[wc - CELL_COMB_CHARS_LO]; - wc = composed->base; - } + struct extraction_context *ctx = extract_begin(SELECTION_NONE); + if (ctx == NULL) + return; - if (wc == 0 || (!first && !isword(wc, spaces_only, term->conf->word_delimiters))) { - done = true; - break; - } - - first = false; - tll_push_back(new_chars, wc); - - if (composed != NULL) { - for (size_t i = 0; i < composed->count; i++) - tll_push_back(new_chars, composed->combining[i]); - } + do { + if (++pos.col >= term->cols) { + pos.row = (pos.row + 1) & (term->grid->num_rows - 1); + pos.col = 0; + row = term->grid->rows[pos.row]; } - - if (done) + if (!extract_one(term, row, &row->cells[pos.col], pos.col, ctx)) break; + } while (pos.col != new_end.col || pos.row != new_end.row); - end_col = 0; - } + wchar_t *new_text; + size_t new_len; - if (tll_length(new_chars) == 0) + if (!extract_finish_wide(ctx, false, &new_text, &new_len)) return; - if (!search_ensure_size(term, term->search.len + tll_length(new_chars))) + if (!search_ensure_size(term, term->search.len + new_len)) return; - /* Keep cursor at the end, but don't move it if not */ - bool move_cursor = term->search.cursor == term->search.len; + for (size_t i = 0; i < new_len; i++) + term->search.buf[term->search.len++] = new_text[i]; - /* Append newly found characters to the search buffer */ - tll_foreach(new_chars, it) - term->search.buf[term->search.len++] = it->item; term->search.buf[term->search.len] = L'\0'; + free(new_text); if (move_cursor) - term->search.cursor += tll_length(new_chars); - - tll_free(new_chars); + term->search.cursor = term->search.len; search_update_selection( - term, term->search.match.row, term->search.match.col, end_row, end_col); + term, term->search.match.row, term->search.match.col, + new_end.row, new_end.col); } static size_t From 7cf0e2aae40f83d6a830165776d52476a457596a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 28 Mar 2021 21:11:07 +0200 Subject: [PATCH 05/12] search: match_to_end_of_word(): skip newlines when copying from extraction buffer --- search.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/search.c b/search.c index acc1faee..dd10ee6c 100644 --- a/search.c +++ b/search.c @@ -489,8 +489,14 @@ search_match_to_end_of_word(struct terminal *term, bool spaces_only) if (!search_ensure_size(term, term->search.len + new_len)) return; - for (size_t i = 0; i < new_len; i++) + for (size_t i = 0; i < new_len; i++) { + if (new_text[i] == L'\n') { + /* extract() adds newlines, which we never match against */ + continue; + } + term->search.buf[term->search.len++] = new_text[i]; + } term->search.buf[term->search.len] = L'\0'; free(new_text); From 11f7a6616b8d4636c8df513cd5626f7ca66017b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 28 Mar 2021 21:15:39 +0200 Subject: [PATCH 06/12] search: match_to_end_of_word(): we always extend the selection with at least one character --- search.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/search.c b/search.c index dd10ee6c..c4bc4a62 100644 --- a/search.c +++ b/search.c @@ -456,13 +456,11 @@ search_match_to_end_of_word(struct terminal *term, bool spaces_only) xassert(term->grid->rows[new_end.row] != NULL); + /* Find next word boundary */ new_end.row -= term->grid->view; selection_find_word_boundary_right(term, &new_end, spaces_only); new_end.row += term->grid->view; - if (new_end.row == old_end.row && new_end.col == old_end.col) - return; - struct coord pos = old_end; const struct row *row = term->grid->rows[pos.row]; From 1c8bdf34ced8f263d61293f3559b2dd570a1c23c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 28 Mar 2021 21:16:42 +0200 Subject: [PATCH 07/12] search: match_to_end_of_word(): use has_wrapped_around() while extracting text --- search.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/search.c b/search.c index c4bc4a62..1c27e751 100644 --- a/search.c +++ b/search.c @@ -472,7 +472,9 @@ search_match_to_end_of_word(struct terminal *term, bool spaces_only) if (++pos.col >= term->cols) { pos.row = (pos.row + 1) & (term->grid->num_rows - 1); pos.col = 0; - row = term->grid->rows[pos.row]; + + if (has_wrapped_around(term, pos.row)) + break; } if (!extract_one(term, row, &row->cells[pos.col], pos.col, ctx)) break; From 5e621bbdb9aa612b79d2e2e2278561a9602d3f8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 30 Mar 2021 13:49:30 +0200 Subject: [PATCH 08/12] search: match_to_end_of_word(): use a local macro to bump coord --- search.c | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/search.c b/search.c index 1c27e751..86cea232 100644 --- a/search.c +++ b/search.c @@ -444,15 +444,24 @@ search_match_to_end_of_word(struct terminal *term, bool spaces_only) const struct coord old_end = term->selection.end; struct coord new_end = old_end; + struct row *row = NULL; + +#define newline(coord) __extension__ \ + ({ \ + bool wrapped_around = false; \ + if (++(coord).col >= term->cols) { \ + (coord).row = ((coord).row + 1) & (term->grid->num_rows - 1); \ + (coord).col = 0; \ + row = term->grid->rows[(coord).row]; \ + if (has_wrapped_around(term, (coord.row))) \ + wrapped_around = true; \ + } \ + wrapped_around; \ + }) /* First character to consider is the *next* character */ - if (++new_end.col >= term->cols) { - new_end.row = (new_end.row + 1) & (term->grid->num_rows - 1); - new_end.col = 0; - - if (has_wrapped_around(term, new_end.row)) - return; - } + if (newline(new_end)) + return; xassert(term->grid->rows[new_end.row] != NULL); @@ -462,20 +471,15 @@ search_match_to_end_of_word(struct terminal *term, bool spaces_only) new_end.row += term->grid->view; struct coord pos = old_end; - const struct row *row = term->grid->rows[pos.row]; + row = term->grid->rows[pos.row]; struct extraction_context *ctx = extract_begin(SELECTION_NONE); if (ctx == NULL) return; do { - if (++pos.col >= term->cols) { - pos.row = (pos.row + 1) & (term->grid->num_rows - 1); - pos.col = 0; - - if (has_wrapped_around(term, pos.row)) - break; - } + if (newline(pos)) + break; if (!extract_one(term, row, &row->cells[pos.col], pos.col, ctx)) break; } while (pos.col != new_end.col || pos.row != new_end.row); @@ -507,6 +511,8 @@ search_match_to_end_of_word(struct terminal *term, bool spaces_only) search_update_selection( term, term->search.match.row, term->search.match.col, new_end.row, new_end.col); + +#undef newline } static size_t From 0945e71572c853dfdeedc90b512668ab946a5468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 30 Mar 2021 14:29:48 +0200 Subject: [PATCH 09/12] search: find_next(): correctly wrap row number --- search.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/search.c b/search.c index 86cea232..cce205d4 100644 --- a/search.c +++ b/search.c @@ -338,7 +338,7 @@ search_find_next(struct terminal *term) for (size_t i = 0; i < term->search.len;) { if (end_col >= term->cols) { - end_row++; + end_row = (end_row + 1) & (term->grid->num_rows - 1); end_col = 0; if (has_wrapped_around(term, end_row)) From a6d9f01c0d1d914362b73eacd89e1e09877632cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 30 Mar 2021 14:40:21 +0200 Subject: [PATCH 10/12] =?UTF-8?q?extract:=20move=20=E2=80=98strip=5Ftraili?= =?UTF-8?q?ng=5Fempty=E2=80=99=20parameter=20from=20extra=5Ffinish()=20to?= =?UTF-8?q?=20extract=5Fbegin()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extract.c | 28 +++++++++++++++++++++------- extract.h | 9 ++++----- pgo/pgo.c | 2 +- search.c | 4 ++-- selection.c | 4 ++-- terminal.c | 4 ++-- 6 files changed, 32 insertions(+), 19 deletions(-) diff --git a/extract.c b/extract.c index dd72ea92..b9fd3331 100644 --- a/extract.c +++ b/extract.c @@ -11,6 +11,7 @@ struct extraction_context { size_t idx; size_t empty_count; size_t newline_count; + bool strip_trailing_empty; bool failed; const struct row *last_row; const struct cell *last_cell; @@ -18,7 +19,7 @@ struct extraction_context { }; struct extraction_context * -extract_begin(enum selection_kind kind) +extract_begin(enum selection_kind kind, bool strip_trailing_empty) { struct extraction_context *ctx = malloc(sizeof(*ctx)); if (unlikely(ctx == NULL)) { @@ -28,6 +29,7 @@ extract_begin(enum selection_kind kind) *ctx = (struct extraction_context){ .selection_kind = kind, + .strip_trailing_empty = strip_trailing_empty, }; return ctx; } @@ -51,8 +53,7 @@ ensure_size(struct extraction_context *ctx, size_t additional_chars) } bool -extract_finish_wide(struct extraction_context *ctx, bool strip_trailing_empty, - wchar_t **text, size_t *len) +extract_finish_wide(struct extraction_context *ctx, wchar_t **text, size_t *len) { if (text == NULL) return false; @@ -64,7 +65,7 @@ extract_finish_wide(struct extraction_context *ctx, bool strip_trailing_empty, if (ctx->failed) goto err; - if (!strip_trailing_empty) { + if (!ctx->strip_trailing_empty) { /* Insert pending newlines, and replace empty cells with spaces */ if (!ensure_size(ctx, ctx->newline_count + ctx->empty_count)) goto err; @@ -106,8 +107,7 @@ err: } bool -extract_finish(struct extraction_context *ctx, bool strip_trailing_empty, - char **text, size_t *len) +extract_finish(struct extraction_context *ctx, char **text, size_t *len) { if (text == NULL) return false; @@ -115,7 +115,7 @@ extract_finish(struct extraction_context *ctx, bool strip_trailing_empty, *len = 0; wchar_t *wtext; - if (!extract_finish_wide(ctx, strip_trailing_empty, &wtext, NULL)) + if (!extract_finish_wide(ctx, &wtext, NULL)) return false; bool ret = false; @@ -167,6 +167,13 @@ extract_one(const struct terminal *term, const struct row *row, /* Don't emit newline just yet - only if there are * non-empty cells following it */ ctx->newline_count++; + + if (!ctx->strip_trailing_empty) { + if (!ensure_size(ctx, ctx->empty_count)) + goto err; + for (size_t i = 0; i < ctx->empty_count; i++) + ctx->buf[ctx->idx++] = L' '; + } ctx->empty_count = 0; } } else { @@ -175,6 +182,13 @@ extract_one(const struct terminal *term, const struct row *row, goto err; ctx->buf[ctx->idx++] = L'\n'; + + if (!ctx->strip_trailing_empty) { + if (!ensure_size(ctx, ctx->empty_count)) + goto err; + for (size_t i = 0; i < ctx->empty_count; i++) + ctx->buf[ctx->idx++] = L' '; + } ctx->empty_count = 0; } } diff --git a/extract.h b/extract.h index 879461cf..dc802bc5 100644 --- a/extract.h +++ b/extract.h @@ -8,15 +8,14 @@ struct extraction_context; -struct extraction_context *extract_begin(enum selection_kind kind); +struct extraction_context *extract_begin( + enum selection_kind kind, bool strip_trailing_empty); bool extract_one( const struct terminal *term, const struct row *row, const struct cell *cell, int col, void *context); bool extract_finish( - struct extraction_context *context, bool strip_trailing_empty, - char **text, size_t *len); + struct extraction_context *context, char **text, size_t *len); bool extract_finish_wide( - struct extraction_context *context, bool strip_trailing_empty, - wchar_t **text, size_t *len); + struct extraction_context *context, wchar_t **text, size_t *len); diff --git a/pgo/pgo.c b/pgo/pgo.c index c1620b04..34af7115 100644 --- a/pgo/pgo.c +++ b/pgo/pgo.c @@ -106,7 +106,7 @@ render_worker_thread(void *_ctx) } struct extraction_context * -extract_begin(enum selection_kind kind) +extract_begin(enum selection_kind kind, bool strip_trailing_empty) { return NULL; } diff --git a/search.c b/search.c index cce205d4..e7d03fe1 100644 --- a/search.c +++ b/search.c @@ -473,7 +473,7 @@ search_match_to_end_of_word(struct terminal *term, bool spaces_only) struct coord pos = old_end; row = term->grid->rows[pos.row]; - struct extraction_context *ctx = extract_begin(SELECTION_NONE); + struct extraction_context *ctx = extract_begin(SELECTION_NONE, false); if (ctx == NULL) return; @@ -487,7 +487,7 @@ search_match_to_end_of_word(struct terminal *term, bool spaces_only) wchar_t *new_text; size_t new_len; - if (!extract_finish_wide(ctx, false, &new_text, &new_len)) + if (!extract_finish_wide(ctx, &new_text, &new_len)) return; if (!search_ensure_size(term, term->search.len + new_len)) diff --git a/selection.c b/selection.c index 7316dfc3..f684c2fa 100644 --- a/selection.c +++ b/selection.c @@ -215,7 +215,7 @@ selection_to_text(const struct terminal *term) if (term->selection.end.row == -1) return NULL; - struct extraction_context *ctx = extract_begin(term->selection.kind); + struct extraction_context *ctx = extract_begin(term->selection.kind, true); if (ctx == NULL) return NULL; @@ -224,7 +224,7 @@ selection_to_text(const struct terminal *term) &extract_one_const_wrapper, ctx); char *text; - return extract_finish(ctx, true, &text, NULL) ? text : NULL; + return extract_finish(ctx, &text, NULL) ? text : NULL; } void diff --git a/terminal.c b/terminal.c index c2bcf86f..8264274a 100644 --- a/terminal.c +++ b/terminal.c @@ -2900,7 +2900,7 @@ static bool rows_to_text(const struct terminal *term, int start, int end, char **text, size_t *len) { - struct extraction_context *ctx = extract_begin(SELECTION_NONE); + struct extraction_context *ctx = extract_begin(SELECTION_NONE, true); if (ctx == NULL) return false; @@ -2917,7 +2917,7 @@ rows_to_text(const struct terminal *term, int start, int end, } out: - return extract_finish(ctx, true, text, len); + return extract_finish(ctx, text, len); } bool From 96b19212d34ffb713927d28669ad0b2f011f4598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 30 Mar 2021 14:40:59 +0200 Subject: [PATCH 11/12] search: match_to_end_of_word(): we update the selection ourselves No need to call search_find_next() after doing a search_match_to_end_of_word(), since we already *know* we have a match, and where it is. --- search.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/search.c b/search.c index e7d03fe1..3d1ace4b 100644 --- a/search.c +++ b/search.c @@ -508,10 +508,14 @@ search_match_to_end_of_word(struct terminal *term, bool spaces_only) if (move_cursor) term->search.cursor = term->search.len; + /* search_update_selection() expected end coordinate to be *exclusive* */ + newline(new_end); search_update_selection( term, term->search.match.row, term->search.match.col, new_end.row, new_end.col); + term->search.match_len = term->search.len; + #undef newline } @@ -755,12 +759,14 @@ execute_binding(struct seat *seat, struct terminal *term, case BIND_ACTION_SEARCH_EXTEND_WORD: search_match_to_end_of_word(term, false); - *update_search_result = *redraw = true; + *update_search_result = false; + *redraw = true; return true; case BIND_ACTION_SEARCH_EXTEND_WORD_WS: search_match_to_end_of_word(term, true); - *update_search_result = *redraw = true; + *update_search_result = false; + *redraw = true; return true; case BIND_ACTION_SEARCH_CLIPBOARD_PASTE: From 48cc9596f1195f46009000229bbc5c1a10c9ad76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 17 May 2021 18:19:55 +0200 Subject: [PATCH 12/12] changelog: ctrl+w can now be used across lines --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1ac30d5..658faa35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,8 @@ * Improved performance on compositors that does not release shm buffers immediately, e.g. KWin (https://codeberg.org/dnkl/foot/issues/478). +* `ctrl + w` (_extend-to-word-boundary_) can now be used across lines + (https://codeberg.org/dnkl/foot/issues/421). ### Deprecated