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
This commit is contained in:
Daniel Eklöf 2023-09-26 17:54:03 +02:00
parent 56d5d4cc21
commit 78665a7e80
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
6 changed files with 457 additions and 106 deletions

View file

@ -60,9 +60,20 @@
row ([#1364][1364]). row ([#1364][1364]).
* Support for DECSET/DECRST/DECRQM 2027 (_Grapheme cluster * Support for DECSET/DECRST/DECRQM 2027 (_Grapheme cluster
processing_). 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 [1077]: https://codeberg.org/dnkl/foot/issues/1077
[1364]: https://codeberg.org/dnkl/foot/issues/1364 [1364]: https://codeberg.org/dnkl/foot/issues/1364
[419]: https://codeberg.org/dnkl/foot/issues/419
### Changed ### Changed
@ -111,6 +122,8 @@
turned off and then back on again) ([#1498][1498]). turned off and then back on again) ([#1498][1498]).
* Transparency in margins (padding) not being disabled in fullscreen * Transparency in margins (padding) not being disabled in fullscreen
mode ([#1503][1503]). 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 [1436]: https://codeberg.org/dnkl/foot/issues/1436
[1464]: https://codeberg.org/dnkl/foot/issues/1464 [1464]: https://codeberg.org/dnkl/foot/issues/1464

View file

@ -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_PREV_WORD] = "delete-prev-word",
[BIND_ACTION_SEARCH_DELETE_NEXT] = "delete-next", [BIND_ACTION_SEARCH_DELETE_NEXT] = "delete-next",
[BIND_ACTION_SEARCH_DELETE_NEXT_WORD] = "delete-next-word", [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] = "extend-to-word-boundary",
[BIND_ACTION_SEARCH_EXTEND_WORD_WS] = "extend-to-next-whitespace", [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_CLIPBOARD_PASTE] = "clipboard-paste",
[BIND_ACTION_SEARCH_PRIMARY_PASTE] = "primary-paste", [BIND_ACTION_SEARCH_PRIMARY_PASTE] = "primary-paste",
[BIND_ACTION_SEARCH_UNICODE_INPUT] = "unicode-input", [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[] = { 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); return xasprintf("%s/foot-%s.sock", xdg_runtime, wayland_display);
} }
#define m_none {0} #define m_none {0}
#define m_alt {.alt = true} #define m_alt {.alt = true}
#define m_ctrl {.ctrl = true} #define m_ctrl {.ctrl = true}
#define m_shift {.shift = true} #define m_shift {.shift = true}
#define m_ctrl_shift {.ctrl = true, .shift = true} #define m_ctrl_shift {.ctrl = true, .shift = true}
#define m_ctrl_shift_alt {.ctrl = true, .shift = true, .alt = true}
static void static void
add_default_key_bindings(struct config *conf) add_default_key_bindings(struct config *conf)
@ -2816,6 +2825,8 @@ static void
add_default_search_bindings(struct config *conf) add_default_search_bindings(struct config *conf)
{ {
static const struct config_key_binding bindings[] = { 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_c}}},
{BIND_ACTION_SEARCH_CANCEL, m_ctrl, {{XKB_KEY_g}}}, {BIND_ACTION_SEARCH_CANCEL, m_ctrl, {{XKB_KEY_g}}},
{BIND_ACTION_SEARCH_CANCEL, m_none, {{XKB_KEY_Escape}}}, {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, m_none, {{XKB_KEY_Delete}}},
{BIND_ACTION_SEARCH_DELETE_NEXT_WORD, m_ctrl, {{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_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, m_ctrl, {{XKB_KEY_w}}},
{BIND_ACTION_SEARCH_EXTEND_WORD_WS, m_ctrl_shift, {{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, {{XKB_KEY_v}}},
{BIND_ACTION_SEARCH_CLIPBOARD_PASTE, m_ctrl_shift, {{XKB_KEY_v}}}, {BIND_ACTION_SEARCH_CLIPBOARD_PASTE, m_ctrl_shift, {{XKB_KEY_v}}},
{BIND_ACTION_SEARCH_CLIPBOARD_PASTE, m_ctrl, {{XKB_KEY_y}}}, {BIND_ACTION_SEARCH_CLIPBOARD_PASTE, m_ctrl, {{XKB_KEY_y}}},

View file

@ -58,6 +58,9 @@ enum bind_action_normal {
enum bind_action_search { enum bind_action_search {
BIND_ACTION_SEARCH_NONE, 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_CANCEL,
BIND_ACTION_SEARCH_COMMIT, BIND_ACTION_SEARCH_COMMIT,
BIND_ACTION_SEARCH_FIND_PREV, BIND_ACTION_SEARCH_FIND_PREV,
@ -72,8 +75,14 @@ enum bind_action_search {
BIND_ACTION_SEARCH_DELETE_PREV_WORD, BIND_ACTION_SEARCH_DELETE_PREV_WORD,
BIND_ACTION_SEARCH_DELETE_NEXT, BIND_ACTION_SEARCH_DELETE_NEXT,
BIND_ACTION_SEARCH_DELETE_NEXT_WORD, BIND_ACTION_SEARCH_DELETE_NEXT_WORD,
BIND_ACTION_SEARCH_EXTEND_CHAR,
BIND_ACTION_SEARCH_EXTEND_WORD, BIND_ACTION_SEARCH_EXTEND_WORD,
BIND_ACTION_SEARCH_EXTEND_WORD_WS, 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_CLIPBOARD_PASTE,
BIND_ACTION_SEARCH_PRIMARY_PASTE, BIND_ACTION_SEARCH_PRIMARY_PASTE,
BIND_ACTION_SEARCH_UNICODE_INPUT, BIND_ACTION_SEARCH_UNICODE_INPUT,

417
search.c
View file

@ -9,6 +9,7 @@
#define LOG_ENABLE_DBG 0 #define LOG_ENABLE_DBG 0
#include "log.h" #include "log.h"
#include "char32.h" #include "char32.h"
#include "commands.h"
#include "config.h" #include "config.h"
#include "extract.h" #include "extract.h"
#include "grid.h" #include "grid.h"
@ -82,7 +83,14 @@ search_ensure_size(struct terminal *term, size_t wanted_size)
} }
static bool 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); int rebased_row = grid_row_abs_to_sb(term->grid, term->rows, abs_row_no);
return rebased_row == 0; 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 || 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; int selection_row = start_row - grid->view + grid->num_rows;
selection_row &= grid->num_rows - 1; selection_row &= grid->num_rows - 1;
selection_start( selection_start(
term, start_col, selection_row, SELECTION_CHAR_WISE, false); term, start_col, selection_row, SELECTION_CHAR_WISE, false);
term->search.match.row = start_row;
term->search.match.col = start_col;
} }
/* Update selection endpoint */ /* Update selection endpoint */
@ -273,16 +287,16 @@ matches_cell(const struct terminal *term, const struct cell *cell, size_t search
return -1; return -1;
if (composed != NULL) { if (composed != NULL) {
if (search_ofs + 1 + composed->count > term->search.len) if (search_ofs + composed->count > term->search.len)
return -1; return -1;
for (size_t j = 1; j < composed->count; j++) { 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 -1;
} }
} }
return composed != NULL ? 1 + composed->count : 1; return composed != NULL ? composed->count : 1;
} }
static bool static bool
@ -369,8 +383,11 @@ find_next(struct terminal *term, enum search_direction direction,
match_len += additional_chars; match_len += additional_chars;
match_end_col++; 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++; match_end_col++;
}
} }
if (match_len != term->search.len) { if (match_len != term->search.len) {
@ -546,6 +563,7 @@ search_matches_next(struct search_match_iterator *iter)
term->cols - 1, term->cols - 1,
grid_row_absolute_in_view(grid, term->rows - 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; struct range match;
bool found = find_next(term, SEARCH_FORWARD, abs_start, abs_end, &match); bool found = find_next(term, SEARCH_FORWARD, abs_start, abs_end, &match);
if (!found) if (!found)
@ -565,11 +583,6 @@ search_matches_next(struct search_match_iterator *iter)
match.start.row, match.start.col, match.start.row, match.start.col,
match.end.row, match.end.col, grid->view); 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 */ /* Assert match end comes *after* the match start */
xassert(match.end.row > match.start.row || xassert(match.end.row > match.start.row ||
(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); add_wchars(term, c32s, chars);
} }
static void enum extend_direction {SEARCH_EXTEND_LEFT, SEARCH_EXTEND_RIGHT};
search_match_to_end_of_word(struct terminal *term, bool spaces_only)
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; 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; 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); *target = pos;
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 weve 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; \
})
/* First character to consider is the *next* character */ /* First character to consider is the *next* character */
if (!advance_pos(new_end)) switch (direction) {
return; case SEARCH_EXTEND_LEFT:
if (!coord_advance_left(term, &pos, NULL))
return;
break;
xassert(new_end.row >= 0); case SEARCH_EXTEND_RIGHT:
xassert(new_end.row < grid->num_rows); if (!coord_advance_right(term, &pos, NULL))
xassert(grid->rows[new_end.row] != NULL); return;
break;
}
xassert(pos.row >= 0);
xassert(pos.row < grid->num_rows);
xassert(grid->rows[pos.row] != NULL);
/* Find next word boundary */ /* Find next word boundary */
new_end.row -= grid->view + grid->num_rows; switch (direction) {
new_end.row &= grid->num_rows - 1; case SEARCH_EXTEND_LEFT:
selection_find_word_boundary_right(term, &new_end, spaces_only, false); selection_find_word_boundary_left(term, &pos, spaces_only);
new_end.row += grid->view; break;
new_end.row &= grid->num_rows - 1;
struct coord pos = old_end; case SEARCH_EXTEND_RIGHT:
row = grid->rows[pos.row]; 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); struct extraction_context *ctx = extract_begin(SELECTION_NONE, false);
if (ctx == NULL) if (ctx == NULL)
return; return;
do { do {
if (!advance_pos(pos)) if (!coord_advance_right(term, &pos, &row))
break; break;
if (!extract_one(term, row, &row->cells[pos.col], pos.col, ctx)) if (!extract_one(term, row, &row->cells[pos.col], pos.col, ctx))
break; 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; char32_t *new_text;
size_t new_len; size_t new_len;
@ -728,12 +956,9 @@ search_match_to_end_of_word(struct terminal *term, bool spaces_only)
if (move_cursor) if (move_cursor)
term->search.cursor = term->search.len; 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); search_update_selection(term, &match);
term->search.match_len = term->search.len; term->search.match_len = term->search.len;
#undef advance_pos
} }
static size_t static size_t
@ -822,6 +1047,20 @@ execute_binding(struct seat *seat, struct terminal *term,
case BIND_ACTION_SEARCH_NONE: case BIND_ACTION_SEARCH_NONE:
return false; 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: case BIND_ACTION_SEARCH_CANCEL:
if (term->search.view_followed_offset) if (term->search.view_followed_offset)
grid->view = grid->offset; grid->view = grid->offset;
@ -967,17 +1206,77 @@ execute_binding(struct seat *seat, struct terminal *term,
return true; return true;
} }
case BIND_ACTION_SEARCH_EXTEND_WORD: case BIND_ACTION_SEARCH_EXTEND_CHAR: {
search_match_to_end_of_word(term, false); struct coord target;
search_extend_find_char_right(term, &target);
search_extend_right(term, &target);
*update_search_result = false; *update_search_result = false;
*redraw = true; *redraw = true;
return true; return true;
}
case BIND_ACTION_SEARCH_EXTEND_WORD_WS: case BIND_ACTION_SEARCH_EXTEND_WORD: {
search_match_to_end_of_word(term, true); struct coord target;
search_extend_find_word_right(term, false, &target);
search_extend_right(term, &target);
*update_search_result = false; *update_search_result = false;
*redraw = true; *redraw = true;
return 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: case BIND_ACTION_SEARCH_CLIPBOARD_PASTE:
text_from_clipboard( text_from_clipboard(

View file

@ -340,16 +340,18 @@ selection_to_text(const struct terminal *term)
return extract_finish(ctx, &text, NULL) ? text : NULL; return extract_finish(ctx, &text, NULL) ? text : NULL;
} }
/* Coordinates are in *absolute* row numbers (NOT view local) */
void 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) bool spaces_only)
{ {
xassert(pos->row >= 0); xassert(pos->row >= 0);
xassert(pos->row < term->rows); xassert(pos->row < term->grid->num_rows);
xassert(pos->col >= 0); xassert(pos->col >= 0);
xassert(pos->col < term->cols); 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; char32_t c = r->cells[pos->col].wc;
while (c >= CELL_SPACER) { 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_col = pos->col - 1;
int next_row = pos->row; 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 */ /* Linewrap */
if (next_col < 0) { if (next_col < 0) {
next_col = term->cols - 1; 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) { if (row->linebreak) {
/* Hard linebreak, treat as space. I.e. break selection */ /* 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 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 spaces_only,
bool stop_on_space_to_word_boundary) bool stop_on_space_to_word_boundary)
{ {
xassert(pos->row >= 0); xassert(pos->row >= 0);
xassert(pos->row < term->rows); xassert(pos->row < term->grid->num_rows);
xassert(pos->col >= 0); xassert(pos->col >= 0);
xassert(pos->col < term->cols); 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; char32_t c = r->cells[pos->col].wc;
while (c >= CELL_SPACER) { 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_col = pos->col + 1;
int next_row = pos->row; 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 */ /* Linewrap */
if (next_col >= term->cols) { if (next_col >= term->cols) {
@ -463,10 +474,14 @@ selection_find_word_boundary_right(struct terminal *term, struct coord *pos,
} }
next_col = 0; next_col = 0;
if (++next_row >= term->rows) next_row = (next_row + 1) & (grid->num_rows - 1);
break;
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; c = row->cells[next_col].wc;
@ -659,15 +674,15 @@ selection_start(struct terminal *term, int col, int row,
break; break;
case SELECTION_WORD_WISE: { 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_left(term, &start, spaces_only);
selection_find_word_boundary_right(term, &end, spaces_only, true); selection_find_word_boundary_right(term, &end, spaces_only, true);
term->selection.coords.start = (struct coord){ term->selection.coords.start = start;
start.col, term->grid->view + start.row};
term->selection.pivot.start = term->selection.coords.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); selection_update(term, end.col, end.row);
break; break;
@ -1085,22 +1100,26 @@ set_pivot_point_for_block_and_char_wise(struct terminal *term,
void void
selection_update(struct terminal *term, int col, int row) 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; return;
}
if (!term->selection.ongoing) if (!term->selection.ongoing) {
LOG_ERR("NOT ON-GOING");
return; 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); xassert(term->grid->view + row != -1);
struct coord new_start = term->selection.coords.start; struct coord new_start = term->selection.coords.start;
struct coord new_end = {col, term->grid->view + row}; 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' */ /* Adjust start point if the selection has changed 'direction' */
if (!(new_end.row == new_start.row && new_end.col == new_start.col)) { if (!(new_end.row == new_start.row && new_end.col == new_start.col)) {
enum selection_direction new_direction = term->selection.direction; 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: case SELECTION_WORD_WISE:
switch (term->selection.direction) { switch (term->selection.direction) {
case SELECTION_LEFT: { case SELECTION_LEFT:
struct coord end = {col, row}; new_end = (struct coord){col, term->grid->view + row};
selection_find_word_boundary_left( selection_find_word_boundary_left(
term, &end, term->selection.spaces_only); term, &new_end, term->selection.spaces_only);
new_end = (struct coord){end.col, term->grid->view + end.row};
break; break;
}
case SELECTION_RIGHT: { case SELECTION_RIGHT:
struct coord end = {col, row}; new_end = (struct coord){col, term->grid->view + row};
selection_find_word_boundary_right( selection_find_word_boundary_right(
term, &end, term->selection.spaces_only, true); term, &new_end, term->selection.spaces_only, true);
new_end = (struct coord){end.col, term->grid->view + end.row};
break; break;
}
case SELECTION_UNDIR: case SELECTION_UNDIR:
break; break;
@ -1346,16 +1361,14 @@ selection_extend_normal(struct terminal *term, int col, int row,
xassert(new_kind == SELECTION_CHAR_WISE || xassert(new_kind == SELECTION_CHAR_WISE ||
new_kind == SELECTION_WORD_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; struct coord pivot_end = pivot_start;
selection_find_word_boundary_left(term, &pivot_start, spaces_only); selection_find_word_boundary_left(term, &pivot_start, spaces_only);
selection_find_word_boundary_right(term, &pivot_end, spaces_only, true); selection_find_word_boundary_right(term, &pivot_end, spaces_only, true);
term->selection.pivot.start = term->selection.pivot.start = pivot_start;
(struct coord){pivot_start.col, term->grid->view + pivot_start.row}; term->selection.pivot.end = pivot_end;
term->selection.pivot.end =
(struct coord){pivot_end.col, term->grid->view + pivot_end.row};
break; break;
} }

View file

@ -78,9 +78,9 @@ void selection_start_scroll_timer(
void selection_stop_scroll_timer(struct terminal *term); void selection_stop_scroll_timer(struct terminal *term);
void selection_find_word_boundary_left( 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( 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); bool stop_on_space_to_word_boundary);
struct coord selection_get_start(const struct terminal *term); struct coord selection_get_start(const struct terminal *term);