From 78665a7e809afd470e1236867761d51de9737eaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 26 Sep 2023 17:54:03 +0200 Subject: [PATCH] search: add more key bindings to extend the current match This patch adds the following new search key bindings: * extend-char (shift+right) * extend-line-down (shift+down) * extend-backward-char (shift+left) * extend-backward-to-word-boundary (ctrl+shift+left) * extend-backward-to-next-whitespace (ctrl+shift+alt+left) * extend-line-up (shift+up) They can be used to extend the search match (i.e. the selection). This patch also adds an initial set of key bindings to scroll in the scrollback history: * scrollback-up-page * scrollback-down-page These work just like the key bindings for the normal mode. Also note that it was already possible to scroll using the mouse. This patch also fixes a couple of search mode bugs: * crashing when a search match ends in the last column * grapheme clusters not being matched correctly * Search match not being "extendable" after a pointer leave event * A few others, related to either large matches, or extending matches after moving the viewport. There are still a couple of (known) issues: * A search match isn't correctly highlighted if its *starting* point is outside the viewport. * Extending the match to end of the scrollback (i.e. the most recent output) is simply buggy. Related to #419 --- CHANGELOG.md | 13 ++ config.c | 27 +++- key-binding.h | 9 ++ search.c | 417 +++++++++++++++++++++++++++++++++++++++++++------- selection.c | 93 ++++++----- selection.h | 4 +- 6 files changed, 457 insertions(+), 106 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f55792d8..912289a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,9 +60,20 @@ row ([#1364][1364]). * Support for DECSET/DECRST/DECRQM 2027 (_Grapheme cluster processing_). +* New search mode key bindings (along with their defaults) + ([#419][419]): + - `extend-char` (shift+right) + - `extend-line-down` (shift+down) + - `extend-backward-char` (shift+left) + - `extend-backward-to-word-boundary` (ctrl+shift+left) + - `extend-backward-to-next-whitespace` (ctrl+alt+shift+left) + - `extend-line-up` (shift+up) + - `scrollback-up-page` (shift+page-up) + - `scrollback-down-page` (shift+page-down) [1077]: https://codeberg.org/dnkl/foot/issues/1077 [1364]: https://codeberg.org/dnkl/foot/issues/1364 +[419]: https://codeberg.org/dnkl/foot/issues/419 ### Changed @@ -111,6 +122,8 @@ turned off and then back on again) ([#1498][1498]). * Transparency in margins (padding) not being disabled in fullscreen mode ([#1503][1503]). +* Crash when a scrollback search match is in the last column. +* Scrollback search: grapheme clusters not matching correctly. [1436]: https://codeberg.org/dnkl/foot/issues/1436 [1464]: https://codeberg.org/dnkl/foot/issues/1464 diff --git a/config.c b/config.c index 6ba371e8..15ec52ff 100644 --- a/config.c +++ b/config.c @@ -148,11 +148,19 @@ static const char *const search_binding_action_map[] = { [BIND_ACTION_SEARCH_DELETE_PREV_WORD] = "delete-prev-word", [BIND_ACTION_SEARCH_DELETE_NEXT] = "delete-next", [BIND_ACTION_SEARCH_DELETE_NEXT_WORD] = "delete-next-word", + [BIND_ACTION_SEARCH_EXTEND_CHAR] = "extend-char", [BIND_ACTION_SEARCH_EXTEND_WORD] = "extend-to-word-boundary", [BIND_ACTION_SEARCH_EXTEND_WORD_WS] = "extend-to-next-whitespace", + [BIND_ACTION_SEARCH_EXTEND_LINE_DOWN] = "extend-line-down", + [BIND_ACTION_SEARCH_EXTEND_BACKWARD_CHAR] = "extend-backward-char", + [BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD] = "extend-backward-to-word-boundary", + [BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD_WS] = "extend-backward-to-next-whitespace", + [BIND_ACTION_SEARCH_EXTEND_LINE_UP] = "extend-line-up", [BIND_ACTION_SEARCH_CLIPBOARD_PASTE] = "clipboard-paste", [BIND_ACTION_SEARCH_PRIMARY_PASTE] = "primary-paste", [BIND_ACTION_SEARCH_UNICODE_INPUT] = "unicode-input", + [BIND_ACTION_SEARCH_SCROLLBACK_UP_PAGE] = "scrollback-up-page", + [BIND_ACTION_SEARCH_SCROLLBACK_DOWN_PAGE] = "scrollback-down-page", }; static const char *const url_binding_action_map[] = { @@ -2774,11 +2782,12 @@ get_server_socket_path(void) return xasprintf("%s/foot-%s.sock", xdg_runtime, wayland_display); } -#define m_none {0} -#define m_alt {.alt = true} -#define m_ctrl {.ctrl = true} -#define m_shift {.shift = true} -#define m_ctrl_shift {.ctrl = true, .shift = true} +#define m_none {0} +#define m_alt {.alt = true} +#define m_ctrl {.ctrl = true} +#define m_shift {.shift = true} +#define m_ctrl_shift {.ctrl = true, .shift = true} +#define m_ctrl_shift_alt {.ctrl = true, .shift = true, .alt = true} static void add_default_key_bindings(struct config *conf) @@ -2816,6 +2825,8 @@ static void add_default_search_bindings(struct config *conf) { static const struct config_key_binding bindings[] = { + {BIND_ACTION_SEARCH_SCROLLBACK_UP_PAGE, m_shift, {{XKB_KEY_Prior}}}, + {BIND_ACTION_SEARCH_SCROLLBACK_DOWN_PAGE, m_shift, {{XKB_KEY_Next}}}, {BIND_ACTION_SEARCH_CANCEL, m_ctrl, {{XKB_KEY_c}}}, {BIND_ACTION_SEARCH_CANCEL, m_ctrl, {{XKB_KEY_g}}}, {BIND_ACTION_SEARCH_CANCEL, m_none, {{XKB_KEY_Escape}}}, @@ -2840,8 +2851,14 @@ add_default_search_bindings(struct config *conf) {BIND_ACTION_SEARCH_DELETE_NEXT, m_none, {{XKB_KEY_Delete}}}, {BIND_ACTION_SEARCH_DELETE_NEXT_WORD, m_ctrl, {{XKB_KEY_Delete}}}, {BIND_ACTION_SEARCH_DELETE_NEXT_WORD, m_alt, {{XKB_KEY_d}}}, + {BIND_ACTION_SEARCH_EXTEND_CHAR, m_shift, {{XKB_KEY_Right}}}, {BIND_ACTION_SEARCH_EXTEND_WORD, m_ctrl, {{XKB_KEY_w}}}, {BIND_ACTION_SEARCH_EXTEND_WORD_WS, m_ctrl_shift, {{XKB_KEY_w}}}, + {BIND_ACTION_SEARCH_EXTEND_LINE_DOWN, m_shift, {{XKB_KEY_Down}}}, + {BIND_ACTION_SEARCH_EXTEND_BACKWARD_CHAR, m_shift, {{XKB_KEY_Left}}}, + {BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD, m_ctrl_shift, {{XKB_KEY_Left}}}, + {BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD_WS, m_ctrl_shift_alt, {{XKB_KEY_Left}}}, + {BIND_ACTION_SEARCH_EXTEND_LINE_UP, m_shift, {{XKB_KEY_Up}}}, {BIND_ACTION_SEARCH_CLIPBOARD_PASTE, m_ctrl, {{XKB_KEY_v}}}, {BIND_ACTION_SEARCH_CLIPBOARD_PASTE, m_ctrl_shift, {{XKB_KEY_v}}}, {BIND_ACTION_SEARCH_CLIPBOARD_PASTE, m_ctrl, {{XKB_KEY_y}}}, diff --git a/key-binding.h b/key-binding.h index ea2f3d6d..b85fc072 100644 --- a/key-binding.h +++ b/key-binding.h @@ -58,6 +58,9 @@ enum bind_action_normal { enum bind_action_search { BIND_ACTION_SEARCH_NONE, + BIND_ACTION_SEARCH_SCROLLBACK_UP_PAGE, + BIND_ACTION_SEARCH_SCROLLBACK_DOWN_PAGE, + // TODO: copy the remaining scrollback key-bindings from normal mode BIND_ACTION_SEARCH_CANCEL, BIND_ACTION_SEARCH_COMMIT, BIND_ACTION_SEARCH_FIND_PREV, @@ -72,8 +75,14 @@ enum bind_action_search { BIND_ACTION_SEARCH_DELETE_PREV_WORD, BIND_ACTION_SEARCH_DELETE_NEXT, BIND_ACTION_SEARCH_DELETE_NEXT_WORD, + BIND_ACTION_SEARCH_EXTEND_CHAR, BIND_ACTION_SEARCH_EXTEND_WORD, BIND_ACTION_SEARCH_EXTEND_WORD_WS, + BIND_ACTION_SEARCH_EXTEND_LINE_DOWN, + BIND_ACTION_SEARCH_EXTEND_BACKWARD_CHAR, + BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD, + BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD_WS, + BIND_ACTION_SEARCH_EXTEND_LINE_UP, BIND_ACTION_SEARCH_CLIPBOARD_PASTE, BIND_ACTION_SEARCH_PRIMARY_PASTE, BIND_ACTION_SEARCH_UNICODE_INPUT, diff --git a/search.c b/search.c index 6c2a2a7e..1fb6bb3e 100644 --- a/search.c +++ b/search.c @@ -9,6 +9,7 @@ #define LOG_ENABLE_DBG 0 #include "log.h" #include "char32.h" +#include "commands.h" #include "config.h" #include "extract.h" #include "grid.h" @@ -82,7 +83,14 @@ search_ensure_size(struct terminal *term, size_t wanted_size) } static bool -has_wrapped_around(const struct terminal *term, int abs_row_no) +has_wrapped_around_left(const struct terminal *term, int abs_row_no) +{ + int rebased_row = grid_row_abs_to_sb(term->grid, term->rows, abs_row_no); + return rebased_row == term->grid->num_rows - 1 || term->grid->rows[abs_row_no] == NULL; +} + +static bool +has_wrapped_around_right(const struct terminal *term, int abs_row_no) { int rebased_row = grid_row_abs_to_sb(term->grid, term->rows, abs_row_no); return rebased_row == 0; @@ -235,13 +243,19 @@ search_update_selection(struct terminal *term, const struct range *match) } if (start_row != term->search.match.row || - start_col != term->search.match.col) + start_col != term->search.match.col || + + /* Pointer leave events trigger selection_finalize() :/ */ + !term->selection.ongoing) { int selection_row = start_row - grid->view + grid->num_rows; selection_row &= grid->num_rows - 1; selection_start( term, start_col, selection_row, SELECTION_CHAR_WISE, false); + + term->search.match.row = start_row; + term->search.match.col = start_col; } /* Update selection endpoint */ @@ -273,16 +287,16 @@ matches_cell(const struct terminal *term, const struct cell *cell, size_t search return -1; if (composed != NULL) { - if (search_ofs + 1 + composed->count > term->search.len) + if (search_ofs + composed->count > term->search.len) return -1; for (size_t j = 1; j < composed->count; j++) { - if (composed->chars[j] != term->search.buf[search_ofs + 1 + j]) + if (composed->chars[j] != term->search.buf[search_ofs + j]) return -1; } } - return composed != NULL ? 1 + composed->count : 1; + return composed != NULL ? composed->count : 1; } static bool @@ -369,8 +383,11 @@ find_next(struct terminal *term, enum search_direction direction, match_len += additional_chars; match_end_col++; - while (match_row->cells[match_end_col].wc > CELL_SPACER) + while (match_end_col < term->cols && + match_row->cells[match_end_col].wc > CELL_SPACER) + { match_end_col++; + } } if (match_len != term->search.len) { @@ -546,6 +563,7 @@ search_matches_next(struct search_match_iterator *iter) term->cols - 1, grid_row_absolute_in_view(grid, term->rows - 1)}; + /* BUG: matches *starting* outside the view, but ending *inside*, aren't matched */ struct range match; bool found = find_next(term, SEARCH_FORWARD, abs_start, abs_end, &match); if (!found) @@ -565,11 +583,6 @@ search_matches_next(struct search_match_iterator *iter) match.start.row, match.start.col, match.end.row, match.end.col, grid->view); - xassert(match.start.row >= 0); - xassert(match.start.row < term->rows); - xassert(match.end.row >= 0); - xassert(match.end.row < term->rows); - /* Assert match end comes *after* the match start */ xassert(match.end.row > match.start.row || (match.end.row == match.start.row && @@ -642,67 +655,282 @@ search_add_chars(struct terminal *term, const char *src, size_t count) add_wchars(term, c32s, chars); } -static void -search_match_to_end_of_word(struct terminal *term, bool spaces_only) +enum extend_direction {SEARCH_EXTEND_LEFT, SEARCH_EXTEND_RIGHT}; + +static bool +coord_advance_left(const struct terminal *term, struct coord *pos, + const struct row **row) { - if (term->search.match_len == 0) + const struct grid *grid = term->grid; + struct coord new_pos = *pos; + + if (--new_pos.col < 0) { + new_pos.row = (new_pos.row - 1 + grid->num_rows) & (grid->num_rows - 1); + new_pos.col = term->cols - 1; + + if (has_wrapped_around_left(term, new_pos.row)) + return false; + + if (row != NULL) + *row = grid->rows[new_pos.row]; + } + + *pos = new_pos; + return true; +} + +static bool +coord_advance_right(const struct terminal *term, struct coord *pos, + const struct row **row) +{ + const struct grid *grid = term->grid; + struct coord new_pos = *pos; + + if (++new_pos.col >= term->cols) { + new_pos.row = (new_pos.row + 1) & (grid->num_rows - 1); + new_pos.col = 0; + + if (has_wrapped_around_right(term, new_pos.row)) + return false; + + if (row != NULL) + *row = grid->rows[new_pos.row]; + } + + *pos = new_pos; + return true; +} + +static void +search_extend_find_char(const struct terminal *term, struct coord *target, + enum extend_direction direction) +{ + struct coord pos = direction == SEARCH_EXTEND_LEFT + ? selection_get_start(term) : selection_get_end(term); + xassert(pos.row >= 0); + xassert(pos.row < term->grid->num_rows); + + *target = pos; + + const struct row *row = term->grid->rows[pos.row]; + + while (true) { + switch (direction) { + case SEARCH_EXTEND_LEFT: + if (!coord_advance_left(term, &pos, &row)) + return; + break; + + case SEARCH_EXTEND_RIGHT: + if (!coord_advance_right(term, &pos, &row)) + return; + break; + } + + const char32_t wc = row->cells[pos.col].wc; + + if (wc >= CELL_SPACER || wc == U'\0') + continue; + + *target = pos; return; + } +} - xassert(term->selection.coords.end.row >= 0); +static void +search_extend_find_char_left(const struct terminal *term, struct coord *target) +{ + search_extend_find_char(term, target, SEARCH_EXTEND_LEFT); +} +static void +search_extend_find_char_right(const struct terminal *term, struct coord *target) +{ + search_extend_find_char(term, target, SEARCH_EXTEND_RIGHT); +} + +static void +search_extend_find_word(const struct terminal *term, bool spaces_only, + struct coord *target, enum extend_direction direction) +{ struct grid *grid = term->grid; - const bool move_cursor = term->search.cursor == term->search.len; + struct coord pos = direction == SEARCH_EXTEND_LEFT + ? selection_get_start(term) + : selection_get_end(term); + + xassert(pos.row >= 0); + xassert(pos.row < grid->num_rows); - struct coord old_end = selection_get_end(term); - struct coord new_end = old_end; - struct row *row = NULL; - - xassert(new_end.row >= 0); - xassert(new_end.row < grid->num_rows); - - /* Advances a coordinate by one column, to the right. Returns - * false if we’ve reached the scrollback wrap-around */ -#define advance_pos(coord) __extension__ \ - ({ \ - bool wrapped_around = false; \ - if (++(coord).col >= term->cols) { \ - (coord).row = ((coord).row + 1) & (grid->num_rows - 1); \ - (coord).col = 0; \ - row = grid->rows[(coord).row]; \ - if (has_wrapped_around(term, (coord.row))) \ - wrapped_around = true; \ - } \ - !wrapped_around; \ - }) + *target = pos; /* First character to consider is the *next* character */ - if (!advance_pos(new_end)) - return; + switch (direction) { + case SEARCH_EXTEND_LEFT: + if (!coord_advance_left(term, &pos, NULL)) + return; + break; - xassert(new_end.row >= 0); - xassert(new_end.row < grid->num_rows); - xassert(grid->rows[new_end.row] != NULL); + case SEARCH_EXTEND_RIGHT: + if (!coord_advance_right(term, &pos, NULL)) + return; + break; + } + + xassert(pos.row >= 0); + xassert(pos.row < grid->num_rows); + xassert(grid->rows[pos.row] != NULL); /* Find next word boundary */ - new_end.row -= grid->view + grid->num_rows; - new_end.row &= grid->num_rows - 1; - selection_find_word_boundary_right(term, &new_end, spaces_only, false); - new_end.row += grid->view; - new_end.row &= grid->num_rows - 1; + switch (direction) { + case SEARCH_EXTEND_LEFT: + selection_find_word_boundary_left(term, &pos, spaces_only); + break; - struct coord pos = old_end; - row = grid->rows[pos.row]; + case SEARCH_EXTEND_RIGHT: + selection_find_word_boundary_right(term, &pos, spaces_only, false); + break; + } + + *target = pos; +} + +static void +search_extend_find_word_left(const struct terminal *term, bool spaces_only, + struct coord *target) +{ + search_extend_find_word(term, spaces_only, target, SEARCH_EXTEND_LEFT); +} + +static void +search_extend_find_word_right(const struct terminal *term, bool spaces_only, + struct coord *target) +{ + search_extend_find_word(term, spaces_only, target, SEARCH_EXTEND_RIGHT); +} + +static void +search_extend_find_line(const struct terminal *term, struct coord *target, + enum extend_direction direction) +{ + struct coord pos = direction == SEARCH_EXTEND_LEFT + ? selection_get_start(term) : selection_get_end(term); + + xassert(pos.row >= 0); + xassert(pos.row < term->grid->num_rows); + + *target = pos; + + const struct grid *grid = term->grid; + + switch (direction) { + case SEARCH_EXTEND_LEFT: + pos.row = (pos.row - 1 + grid->num_rows) & (grid->num_rows - 1); + if (has_wrapped_around_right(term, pos.row)) + return; + break; + + case SEARCH_EXTEND_RIGHT: + pos.row = (pos.row + 1) & (grid->num_rows - 1); + if (has_wrapped_around_left(term, pos.row)) + return; + break; + } + + *target = pos; +} + +static void +search_extend_find_line_up(const struct terminal *term, struct coord *target) +{ + search_extend_find_line(term, target, SEARCH_EXTEND_LEFT); +} + +static void +search_extend_find_line_down(const struct terminal *term, struct coord *target) +{ + search_extend_find_line(term, target, SEARCH_EXTEND_RIGHT); +} + +static void +search_extend_left(struct terminal *term, const struct coord *target) +{ + const struct coord last_coord = selection_get_start(term); + struct coord pos = *target; + const struct row *row = term->grid->rows[pos.row]; + + const bool move_cursor = term->search.cursor != 0; + + struct extraction_context *ctx = extract_begin(SELECTION_NONE, false); + if (ctx == NULL) + return; + + while (pos.col != last_coord.col || pos.row != last_coord.row) { + if (!extract_one(term, row, &row->cells[pos.col], pos.col, ctx)) + break; + if (!coord_advance_right(term, &pos, &row)) + break; + } + + char32_t *new_text; + size_t new_len; + + if (!extract_finish_wide(ctx, &new_text, &new_len)) + return; + + if (!search_ensure_size(term, term->search.len + new_len)) + return; + + memmove(&term->search.buf[new_len], &term->search.buf[0], + term->search.len * sizeof(term->search.buf[0])); + + size_t actually_copied = 0; + for (size_t i = 0; i < new_len; i++) { + if (new_text[i] == U'\n') { + /* extract() adds newlines, which we never match against */ + continue; + } + + term->search.buf[actually_copied++] = new_text[i]; + term->search.len++; + } + + xassert(actually_copied <= new_len); + if (actually_copied < new_len) { + memmove( + &term->search.buf[actually_copied], &term->search.buf[new_len], + (term->search.len - actually_copied) * sizeof(term->search.buf[0])); + } + + term->search.buf[term->search.len] = U'\0'; + free(new_text); + + if (move_cursor) + term->search.cursor += actually_copied; + + struct range match = {.start = *target, .end = selection_get_end(term)}; + search_update_selection(term, &match); + + term->search.match_len = term->search.len; +} + +static void +search_extend_right(struct terminal *term, const struct coord *target) +{ + struct coord pos = selection_get_end(term); + const struct row *row = term->grid->rows[pos.row]; + + const bool move_cursor = term->search.cursor == term->search.len; struct extraction_context *ctx = extract_begin(SELECTION_NONE, false); if (ctx == NULL) return; do { - if (!advance_pos(pos)) + if (!coord_advance_right(term, &pos, &row)) 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); + } while (pos.col != target->col || pos.row != target->row); char32_t *new_text; size_t new_len; @@ -728,12 +956,9 @@ search_match_to_end_of_word(struct terminal *term, bool spaces_only) if (move_cursor) term->search.cursor = term->search.len; - struct range match = {.start = term->search.match, .end = new_end}; + struct range match = {.start = term->search.match, .end = *target}; search_update_selection(term, &match); - term->search.match_len = term->search.len; - -#undef advance_pos } static size_t @@ -822,6 +1047,20 @@ execute_binding(struct seat *seat, struct terminal *term, case BIND_ACTION_SEARCH_NONE: return false; + case BIND_ACTION_SEARCH_SCROLLBACK_UP_PAGE: + if (term->grid == &term->normal) { + cmd_scrollback_up(term, term->rows); + return true; + } + return false; + + case BIND_ACTION_SEARCH_SCROLLBACK_DOWN_PAGE: + if (term->grid == &term->normal) { + cmd_scrollback_down(term, term->rows); + return true; + } + return false; + case BIND_ACTION_SEARCH_CANCEL: if (term->search.view_followed_offset) grid->view = grid->offset; @@ -967,17 +1206,77 @@ execute_binding(struct seat *seat, struct terminal *term, return true; } - case BIND_ACTION_SEARCH_EXTEND_WORD: - search_match_to_end_of_word(term, false); + case BIND_ACTION_SEARCH_EXTEND_CHAR: { + struct coord target; + search_extend_find_char_right(term, &target); + search_extend_right(term, &target); *update_search_result = false; *redraw = true; return true; + } - case BIND_ACTION_SEARCH_EXTEND_WORD_WS: - search_match_to_end_of_word(term, true); + case BIND_ACTION_SEARCH_EXTEND_WORD: { + struct coord target; + search_extend_find_word_right(term, false, &target); + search_extend_right(term, &target); *update_search_result = false; *redraw = true; return true; + } + + case BIND_ACTION_SEARCH_EXTEND_WORD_WS: { + struct coord target; + search_extend_find_word_right(term, true, &target); + search_extend_right(term, &target); + *update_search_result = false; + *redraw = true; + return true; + } + + case BIND_ACTION_SEARCH_EXTEND_LINE_DOWN: { + struct coord target; + search_extend_find_line_down(term, &target); + search_extend_right(term, &target); + *update_search_result = false; + *redraw = true; + return true; + } + + case BIND_ACTION_SEARCH_EXTEND_BACKWARD_CHAR: { + struct coord target; + search_extend_find_char_left(term, &target); + search_extend_left(term, &target); + *update_search_result = false; + *redraw = true; + return true; + } + + case BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD: { + struct coord target; + search_extend_find_word_left(term, false, &target); + search_extend_left(term, &target); + *update_search_result = false; + *redraw = true; + return true; + } + + case BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD_WS: { + struct coord target; + search_extend_find_word_left(term, true, &target); + search_extend_left(term, &target); + *update_search_result = false; + *redraw = true; + return true; + } + + case BIND_ACTION_SEARCH_EXTEND_LINE_UP: { + struct coord target; + search_extend_find_line_up(term, &target); + search_extend_left(term, &target); + *update_search_result = false; + *redraw = true; + return true; + } case BIND_ACTION_SEARCH_CLIPBOARD_PASTE: text_from_clipboard( diff --git a/selection.c b/selection.c index f03a3d5c..7b508828 100644 --- a/selection.c +++ b/selection.c @@ -340,16 +340,18 @@ selection_to_text(const struct terminal *term) return extract_finish(ctx, &text, NULL) ? text : NULL; } +/* Coordinates are in *absolute* row numbers (NOT view local) */ void -selection_find_word_boundary_left(struct terminal *term, struct coord *pos, +selection_find_word_boundary_left(const struct terminal *term, struct coord *pos, bool spaces_only) { xassert(pos->row >= 0); - xassert(pos->row < term->rows); + xassert(pos->row < term->grid->num_rows); xassert(pos->col >= 0); xassert(pos->col < term->cols); - const struct row *r = grid_row_in_view(term->grid, pos->row); + const struct grid *grid = term->grid; + const struct row *r = grid->rows[pos->row]; char32_t c = r->cells[pos->col].wc; while (c >= CELL_SPACER) { @@ -373,15 +375,22 @@ selection_find_word_boundary_left(struct terminal *term, struct coord *pos, int next_col = pos->col - 1; int next_row = pos->row; - const struct row *row = grid_row_in_view(term->grid, next_row); + const struct row *row = grid->rows[next_row]; /* Linewrap */ if (next_col < 0) { next_col = term->cols - 1; - if (--next_row < 0) - break; - row = grid_row_in_view(term->grid, next_row); + next_row = (next_row - 1 + grid->num_rows) & (grid->num_rows - 1); + + if (grid_row_abs_to_sb(grid, term->rows, next_row) == term->grid->num_rows - 1 || + grid->rows[next_row] == NULL) + { + /* Scrollback wrap-around */ + break; + } + + row = grid->rows[next_row]; if (row->linebreak) { /* Hard linebreak, treat as space. I.e. break selection */ @@ -418,17 +427,19 @@ selection_find_word_boundary_left(struct terminal *term, struct coord *pos, } } +/* Coordinates are in *absolute* row numbers (NOT view local) */ void -selection_find_word_boundary_right(struct terminal *term, struct coord *pos, +selection_find_word_boundary_right(const struct terminal *term, struct coord *pos, bool spaces_only, bool stop_on_space_to_word_boundary) { xassert(pos->row >= 0); - xassert(pos->row < term->rows); + xassert(pos->row < term->grid->num_rows); xassert(pos->col >= 0); xassert(pos->col < term->cols); - const struct row *r = grid_row_in_view(term->grid, pos->row); + const struct grid *grid = term->grid; + const struct row *r = grid->rows[pos->row]; char32_t c = r->cells[pos->col].wc; while (c >= CELL_SPACER) { @@ -453,7 +464,7 @@ selection_find_word_boundary_right(struct terminal *term, struct coord *pos, int next_col = pos->col + 1; int next_row = pos->row; - const struct row *row = grid_row_in_view(term->grid, next_row); + const struct row *row = term->grid->rows[next_row]; /* Linewrap */ if (next_col >= term->cols) { @@ -463,10 +474,14 @@ selection_find_word_boundary_right(struct terminal *term, struct coord *pos, } next_col = 0; - if (++next_row >= term->rows) - break; + next_row = (next_row + 1) & (grid->num_rows - 1); - row = grid_row_in_view(term->grid, next_row); + if (grid_row_abs_to_sb(grid, term->rows, next_row) == 0) { + /* Scrollback wrap-around */ + break; + } + + row = grid->rows[next_row]; } c = row->cells[next_col].wc; @@ -659,15 +674,15 @@ selection_start(struct terminal *term, int col, int row, break; case SELECTION_WORD_WISE: { - struct coord start = {col, row}, end = {col, row}; + struct coord start = {col, term->grid->view + row}; + struct coord end = {col, term->grid->view + row}; selection_find_word_boundary_left(term, &start, spaces_only); selection_find_word_boundary_right(term, &end, spaces_only, true); - term->selection.coords.start = (struct coord){ - start.col, term->grid->view + start.row}; + term->selection.coords.start = start; term->selection.pivot.start = term->selection.coords.start; - term->selection.pivot.end = (struct coord){end.col, term->grid->view + end.row}; + term->selection.pivot.end = end; selection_update(term, end.col, end.row); break; @@ -1085,22 +1100,26 @@ set_pivot_point_for_block_and_char_wise(struct terminal *term, void selection_update(struct terminal *term, int col, int row) { - if (term->selection.coords.start.row < 0) + if (term->selection.coords.start.row < 0) { + LOG_ERR("NO SELECTION"); return; + } - if (!term->selection.ongoing) + if (!term->selection.ongoing) { + LOG_ERR("NOT ON-GOING"); return; - - LOG_DBG("selection updated: start = %d,%d, end = %d,%d -> %d, %d", - term->selection.coords.start.row, term->selection.coords.start.col, - term->selection.coords.end.row, term->selection.coords.end.col, - row, col); + } xassert(term->grid->view + row != -1); struct coord new_start = term->selection.coords.start; struct coord new_end = {col, term->grid->view + row}; + LOG_DBG("selection updated: start = %d,%d, end = %d,%d -> %d, %d", + term->selection.coords.start.row, term->selection.coords.start.col, + term->selection.coords.end.row, term->selection.coords.end.col, + new_end.row, new_end.col); + /* Adjust start point if the selection has changed 'direction' */ if (!(new_end.row == new_start.row && new_end.col == new_start.col)) { enum selection_direction new_direction = term->selection.direction; @@ -1160,21 +1179,17 @@ selection_update(struct terminal *term, int col, int row) case SELECTION_WORD_WISE: switch (term->selection.direction) { - case SELECTION_LEFT: { - struct coord end = {col, row}; + case SELECTION_LEFT: + new_end = (struct coord){col, term->grid->view + row}; selection_find_word_boundary_left( - term, &end, term->selection.spaces_only); - new_end = (struct coord){end.col, term->grid->view + end.row}; + term, &new_end, term->selection.spaces_only); break; - } - case SELECTION_RIGHT: { - struct coord end = {col, row}; + case SELECTION_RIGHT: + new_end = (struct coord){col, term->grid->view + row}; selection_find_word_boundary_right( - term, &end, term->selection.spaces_only, true); - new_end = (struct coord){end.col, term->grid->view + end.row}; + term, &new_end, term->selection.spaces_only, true); break; - } case SELECTION_UNDIR: break; @@ -1346,16 +1361,14 @@ selection_extend_normal(struct terminal *term, int col, int row, xassert(new_kind == SELECTION_CHAR_WISE || new_kind == SELECTION_WORD_WISE); - struct coord pivot_start = {new_start.col, new_start.row - term->grid->view}; + struct coord pivot_start = {new_start.col, new_start.row}; struct coord pivot_end = pivot_start; selection_find_word_boundary_left(term, &pivot_start, spaces_only); selection_find_word_boundary_right(term, &pivot_end, spaces_only, true); - term->selection.pivot.start = - (struct coord){pivot_start.col, term->grid->view + pivot_start.row}; - term->selection.pivot.end = - (struct coord){pivot_end.col, term->grid->view + pivot_end.row}; + term->selection.pivot.start = pivot_start; + term->selection.pivot.end = pivot_end; break; } diff --git a/selection.h b/selection.h index c6d7f968..26298457 100644 --- a/selection.h +++ b/selection.h @@ -78,9 +78,9 @@ void selection_start_scroll_timer( void selection_stop_scroll_timer(struct terminal *term); void selection_find_word_boundary_left( - struct terminal *term, struct coord *pos, bool spaces_only); + const 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 terminal *term, struct coord *pos, bool spaces_only, bool stop_on_space_to_word_boundary); struct coord selection_get_start(const struct terminal *term);