diff --git a/grid.h b/grid.h index a421a5c0..96ef4181 100644 --- a/grid.h +++ b/grid.h @@ -7,12 +7,24 @@ void grid_swap_row(struct grid *grid, int row_a, int row_b, bool initialize); struct row *grid_row_alloc(int cols, bool initialize); void grid_row_free(struct row *row); +static inline int +grid_row_absolute(const struct grid *grid, int row_no) +{ + return (grid->offset + row_no) & (grid->num_rows - 1); +} + +static inline int +grid_row_absolute_in_view(const struct grid *grid, int row_no) +{ + return (grid->view + row_no) & (grid->num_rows - 1); +} + static inline struct row * _grid_row_maybe_alloc(struct grid *grid, int row_no, bool alloc_if_null) { assert(grid->offset >= 0); - int real_row = (grid->offset + row_no) & (grid->num_rows - 1); + int real_row = grid_row_absolute(grid, row_no); struct row *row = grid->rows[real_row]; if (row == NULL && alloc_if_null) { @@ -41,7 +53,7 @@ grid_row_in_view(struct grid *grid, int row_no) { assert(grid->view >= 0); - int real_row = (grid->view + row_no) & (grid->num_rows - 1); + int real_row = grid_row_absolute_in_view(grid, row_no); struct row *row = grid->rows[real_row]; assert(row != NULL); diff --git a/search.c b/search.c index 55c67bb0..cdc7ccf5 100644 --- a/search.c +++ b/search.c @@ -7,6 +7,8 @@ #define LOG_ENABLE_DBG 1 #include "log.h" #include "render.h" +#include "selection.h" +#include "grid.h" void search_begin(struct terminal *term) @@ -17,6 +19,10 @@ search_begin(struct terminal *term) term->search.buf = NULL; term->search.len = 0; term->search.sz = 0; + term->search.match = (struct coord){-1, -1}; + term->search.match_len = 0; + term->search.original_view = term->grid->view; + term->search.view_followed_offset = term->grid->view == term->grid->offset; term->is_searching = true; render_refresh(term); @@ -36,6 +42,133 @@ search_cancel(struct terminal *term) render_refresh(term); } +static void +search_update(struct terminal *term) +{ + if (term->search.len == 0) { + term->search.match = (struct coord){-1, -1}; + term->search.match_len = 0; + selection_cancel(term); + return; + } + + int start_row = term->search.match.row; + int start_col = term->search.match.col; + size_t len = term->search.match_len; + + assert((len == 0 && start_row == -1 && start_col == -1) || + (len > 0 && start_row >= 0 && start_col >= 0)); + + if (len == 0) { + start_row = grid_row_absolute(term->grid, term->rows - 1); + start_col = term->cols - 1; + } + + LOG_DBG("search: update: starting at row=%d col=%d", start_row, start_col); + + /* Scan backward from current end-of-output */ + for (; start_row >= 0; start_row--) { + const struct row *row = term->grid->rows[start_row]; + if (row == NULL) + break; + + for (; start_col >= 0; start_col--) { + if (row->cells[start_col].wc != term->search.buf[0]) + continue; + + /* + * Got a match on the first letter. Now we'll see if the + * rest of the search buffer matches. + */ + + LOG_DBG("search: initial match at row=%d, col=%d", start_row, start_col); + + int end_row = start_row; + int end_col = start_col; + size_t match_len = 0; + + for (size_t i = 0; i < term->search.len; i++, match_len++) { + if (row->cells[end_col].wc != term->search.buf[i]) + break; + + 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; + row = term->grid->rows[end_row]; + } + } + + if (match_len != term->search.len) { + /* Didn't match (completely) */ + continue; + } + + /* + * We matched the entire buffer. Move view to ensure the + * match is visible, create a selection and return. + */ + + int old_view = term->grid->view; + int new_view = start_row; + + /* Prevent scrolling in uninitialized rows */ + bool all_initialized = false; + do { + all_initialized = true; + + for (int i = 0; i < term->rows; i++) { + int row_no = (new_view + i) % term->grid->num_rows; + if (term->grid->rows[row_no] == NULL) { + all_initialized = false; + new_view--; + break; + } + } + } while (!all_initialized); + + /* Don't scroll past scrollback history */ + int end = (term->grid->offset + term->rows - 1) % term->grid->num_rows; + if (end >= term->grid->offset) { + /* Not wrapped */ + if (new_view >= term->grid->offset && new_view <= end) + new_view = term->grid->offset; + } else { + if (new_view >= term->grid->offset || new_view <= end) + new_view = term->grid->offset; + } + + /* Update view */ + term->grid->view = new_view; + if (new_view != old_view) + term_damage_view(term); + + /* Selection endpoint is inclusive */ + if (--end_col < 0) { + end_col = term->cols - 1; + start_row--; + } + + /* Begin a selection (it's finalized when "committing" the search) */ + selection_start(term, start_col, start_row - term->grid->view); + selection_update(term, end_col, end_row - term->grid->view); + + /* Update match state */ + term->search.match.row = start_row; + term->search.match.col = start_col; + term->search.match_len = match_len; + + return; + } + + start_col = term->cols - 1; + } +} + void search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask_t mods) { @@ -49,8 +182,41 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask enum xkb_compose_status compose_status = xkb_compose_state_get_status( term->kbd.xkb_compose_state); - if ((mods == 0 && sym == XKB_KEY_Escape) || (mods == ctrl && sym == XKB_KEY_g)) + /* Cancel search */ + if ((mods == 0 && sym == XKB_KEY_Escape) || + (mods == ctrl && sym == XKB_KEY_g)) + { + if (term->search.view_followed_offset) + term->grid->view = term->grid->offset; + else + term->grid->view = term->search.original_view; + term_damage_view(term); search_cancel(term); + } + + /* "Commit" search - copy selection to primary and cancel search */ + else if (mods == 0 && sym == XKB_KEY_Return) { + selection_finalize(term, term->input_serial); + search_cancel(term); + } + + else if (mods == ctrl && sym == XKB_KEY_r) { + if (term->search.match_len > 0) { + int new_col = term->search.match.col - 1; + int new_row = term->search.match.row; + + if (new_col < 0) { + new_col = term->cols - 1; + new_row--; + } + + if (new_row >= 0) { + term->search.match.col = new_col; + term->search.match.row = new_row; + search_update(term); + } + } + } else if (mods == 0 && sym == XKB_KEY_BackSpace) { if (term->search.len > 0) @@ -103,5 +269,6 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask } LOG_DBG("search: buffer: %S", term->search.buf); + search_update(term); render_refresh(term); } diff --git a/terminal.h b/terminal.h index fbd734bc..5989788a 100644 --- a/terminal.h +++ b/terminal.h @@ -336,6 +336,11 @@ struct terminal { wchar_t *buf; size_t len; size_t sz; + + int original_view; + bool view_followed_offset; + struct coord match; + size_t match_len; } search; struct grid normal;