diff --git a/config.c b/config.c index 41c40ff9..b42730da 100644 --- a/config.c +++ b/config.c @@ -3304,6 +3304,7 @@ add_default_vimode_search_bindings(struct config *conf) const struct config_key_binding bindings[] = { {BIND_ACTION_VIMODE_SEARCH_CONFIRM, m("none"), {{XKB_KEY_Return}}}, {BIND_ACTION_VIMODE_SEARCH_CANCEL, m("none"), {{XKB_KEY_Escape}}}, + {BIND_ACTION_VIMODE_SEARCH_CANCEL, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_c}}}, {BIND_ACTION_VIMODE_SEARCH_DELETE_PREV_CHAR, m("none"), {{XKB_KEY_BackSpace}}}, {BIND_ACTION_VIMODE_SEARCH_LEFT, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_h}}}, {BIND_ACTION_VIMODE_SEARCH_LEFT, m("none"), {{XKB_KEY_leftarrow}}}, diff --git a/render.c b/render.c index 2d5b0899..f8f9ff3f 100644 --- a/render.c +++ b/render.c @@ -12,6 +12,7 @@ #include #include "macros.h" +#include "terminal.h" #if HAS_INCLUDE() #include #define pthread_setname_np(thread, name) (pthread_set_name_np(thread, name), 0) @@ -688,6 +689,30 @@ draw_cursor(const struct terminal *term, const struct cell *cell, } } +static bool is_cell_highlighted(struct terminal* const term, + struct coord const cell) +{ + struct highlight_location const* location = term->vimode.highlights; + int const row_abs = grid_row_absolute_in_view(term->grid, cell.row); + while(location != NULL) { + // End the search early if we are past the possible locations. + if(location->range.start.row > row_abs) { + break; + } + + int const start_col = location->range.start.col; + int const end_col = location->range.end.col; + if(location->range.start.row == row_abs && cell.col >= start_col && + cell.col <= end_col) + { + return true; + } + + location = location->next; + } + return false; +} + static int render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damage, struct row *row, int row_no, int col, @@ -706,6 +731,8 @@ render_cell(struct terminal *term, pixman_image_t *pix, const int y = term->margins.top + row_no * height; const bool is_selected = cell->attrs.selected; + const bool is_highlighted = + is_cell_highlighted(term, (struct coord){.row = row_no, .col = col}); uint32_t _fg = 0; uint32_t _bg = 0; @@ -785,6 +812,12 @@ render_cell(struct terminal *term, pixman_image_t *pix, alpha = 0xffff; } } else { + if(is_highlighted) { + // TODO (kociap): colors for the highlight. + _fg = term->colors.table[0]; + _bg = term->colors.table[1]; + } + if (unlikely(cell->attrs.reverse)) { uint32_t swap = _fg; _fg = _bg; @@ -1980,7 +2013,7 @@ render_overlay(struct terminal *term) const bool unicode_mode_active = term->unicode_mode.active; const enum overlay_style style = - term->is_vimming ? OVERLAY_SEARCH : + term->is_vimming ? OVERLAY_NONE : term->flash.active ? OVERLAY_FLASH : unicode_mode_active ? OVERLAY_UNICODE_MODE : OVERLAY_NONE; @@ -1999,7 +2032,6 @@ render_overlay(struct terminal *term) pixman_color_t color; switch (style) { - case OVERLAY_SEARCH: case OVERLAY_UNICODE_MODE: color = (pixman_color_t){0, 0, 0, 0x7fff}; break; @@ -2033,125 +2065,8 @@ render_overlay(struct terminal *term) /* Bounding rectangle of damaged areas - for wl_surface_damage_buffer() */ pixman_box32_t damage_bounds; - if (style == OVERLAY_SEARCH) { - /* - * When possible, we only update the areas that have *changed* - * since the last frame. That means: - * - * - clearing/erasing cells that are now selected, but weren't - * in the last frame - * - dimming cells that were selected, but aren't anymore - * - * To do this, we save the last frame's selected cells as a - * pixman region. - * - * Then, we calculate the corresponding region for this - * frame's selected cells. - * - * Last frame's region minus this frame's region gives us the - * region that needs to be *dimmed* in this frame - * - * This frame's region minus last frame's region gives us the - * region that needs to be *cleared* in this frame. - * - * Finally, the union of the two "diff" regions above, gives - * us the total region affected by a change, in either way. We - * use this as the bounding box for the - * wl_surface_damage_buffer() call. - */ - pixman_region32_t *see_through = &term->render.last_overlay_clip; - pixman_region32_t old_see_through; - const bool buffer_reuse = - buf == term->render.last_overlay_buf && - style == term->render.last_overlay_style && - buf->age == 0; - - if (!buffer_reuse) { - /* Can't reuse last frame's damage - set to full window, - * to ensure *everything* is updated */ - pixman_region32_init_rect( - &old_see_through, 0, 0, buf->width, buf->height); - } else { - /* Use last frame's saved region */ - pixman_region32_init(&old_see_through); - pixman_region32_copy(&old_see_through, see_through); - } - - pixman_region32_clear(see_through); - - /* Build region consisting of all current search matches */ - // TODO (kociap): commented this because no search atm. - // struct search_match_iterator iter = search_matches_new_iter(term); - // for (struct range match = search_matches_next(&iter); - // match.start.row >= 0; - // match = search_matches_next(&iter)) - // { - // int r = match.start.row; - // int start_col = match.start.col; - // const int end_row = match.end.row; - // - // while (true) { - // const int end_col = - // r == end_row ? match.end.col : term->cols - 1; - // - // int x = term->margins.left + start_col * term->cell_width; - // int y = term->margins.top + r * term->cell_height; - // int width = (end_col + 1 - start_col) * term->cell_width; - // int height = 1 * term->cell_height; - // - // pixman_region32_union_rect( - // see_through, see_through, x, y, width, height); - // - // if (++r > end_row) - // break; - // - // start_col = 0; - // } - // } - - /* Areas that need to be cleared: cells that were dimmed in - * the last frame but is now see-through */ - pixman_region32_t new_see_through; - pixman_region32_init(&new_see_through); - - if (buffer_reuse) - pixman_region32_subtract(&new_see_through, see_through, &old_see_through); - else { - /* Buffer content is unknown - explicitly clear *all* - * current see-through areas */ - pixman_region32_copy(&new_see_through, see_through); - } - pixman_image_set_clip_region32(buf->pix[0], &new_see_through); - - /* Areas that need to be dimmed: cells that were cleared in - * the last frame but is not anymore */ - pixman_region32_t new_dimmed; - pixman_region32_init(&new_dimmed); - pixman_region32_subtract(&new_dimmed, &old_see_through, see_through); - pixman_region32_fini(&old_see_through); - - /* Total affected area */ - pixman_region32_t damage; - pixman_region32_init(&damage); - pixman_region32_union(&damage, &new_see_through, &new_dimmed); - damage_bounds = damage.extents; - - /* Clear cells that became selected in this frame. */ - pixman_image_fill_rectangles( - PIXMAN_OP_SRC, buf->pix[0], &(pixman_color_t){0}, 1, - &(pixman_rectangle16_t){0, 0, term->width, term->height}); - - /* Set clip region for the newly dimmed cells. The actual - * paint call is done below */ - pixman_image_set_clip_region32(buf->pix[0], &new_dimmed); - - pixman_region32_fini(&new_see_through); - pixman_region32_fini(&new_dimmed); - pixman_region32_fini(&damage); - } - - else if (buf == term->render.last_overlay_buf && - style == term->render.last_overlay_style) + if (buf == term->render.last_overlay_buf && + style == term->render.last_overlay_style) { xassert(style == OVERLAY_FLASH || style == OVERLAY_UNICODE_MODE); shm_did_not_use_buf(buf); @@ -3627,8 +3542,7 @@ grid_render(struct terminal *term) pixman_region32_fini(&damage); - // TODO (kociap): we want to eventually render the overlays. - // render_overlay(term); + render_overlay(term); render_ime_preedit(term, buf); render_scrollback_position(term); @@ -3747,7 +3661,6 @@ render_search_box_cursor( static void render_search_box(struct terminal *term) { - printf("REDRAWING SEARCH BOX\n"); xassert(term->window->search.sub != NULL); /* diff --git a/terminal.c b/terminal.c index a9fa5f9e..e5247760 100644 --- a/terminal.c +++ b/terminal.c @@ -2479,6 +2479,18 @@ term_font_baseline(const struct terminal *term) return term->font_y_ofs + line_height - glyph_top_y - font->descent; } +void +term_damage_cell(struct terminal* const term, int const row, int const col) +{ + if(col >= term->grid->num_cols || col < 0) { + return; + } + + struct row* const r = grid_row(term->grid, row); + r->dirty = true; + r->cells[col].attrs.clean = 0; +} + void term_damage_cell_in_view(struct terminal* const term, int const row, int const col) { diff --git a/terminal.h b/terminal.h index 9cc9a8af..1a91eb28 100644 --- a/terminal.h +++ b/terminal.h @@ -374,8 +374,6 @@ enum term_surface { enum overlay_style { OVERLAY_NONE, - // TODO (kociap): rename to OVERLAY_VIMODE - OVERLAY_SEARCH, OVERLAY_FLASH, OVERLAY_UNICODE_MODE, }; @@ -658,6 +656,15 @@ struct terminal { size_t len; enum search_direction direction; } confirmed_search; + + /* + * Highlight ranges in absolute coordinates. + * Sorted in ascending order by the start row and column. + */ + struct highlight_location { + struct highlight_location const* next; + struct range range; + } const* highlights; } vimode; struct wayland *wl; @@ -899,6 +906,7 @@ int term_pt_or_px_as_pixels( void term_window_configured(struct terminal *term); +void term_damage_cell(struct terminal* term, int row, int col); void term_damage_cell_in_view(struct terminal* term, int row, int col); void term_damage_rows(struct terminal *term, int start, int end); void term_damage_rows_in_view(struct terminal *term, int start, int end); diff --git a/vimode.c b/vimode.c index d4ccdfcb..420fb854 100644 --- a/vimode.c +++ b/vimode.c @@ -88,6 +88,7 @@ static void damage_cursor_cell(struct terminal *const term) { struct coord const cursor = offset_to_view_relative(term, term->vimode.cursor); term_damage_cell_in_view(term, cursor.row, cursor.col); + render_refresh(term); } static void clip_cursor_to_view(struct terminal *const term) { @@ -140,6 +141,102 @@ static void update_selection(struct seat *const seat, } } +static void damage_highlights(struct terminal *const term) { + struct highlight_location const *location = term->vimode.highlights; + int const offset = term->grid->offset; + printf("DAMAGING HIGHLIGHT CELLS: "); + while (location != NULL) { + struct coord const start = location->range.start; + struct coord const end = location->range.end; + for (int col = start.col; col <= end.col; col += 1) { + printf("(%d, %d) ", start.row, col); + term_damage_cell(term, start.row - offset, col); + } + location = location->next; + } + printf("\n"); + render_refresh(term); +} + +static void clear_highlights(struct terminal *const term) { + damage_highlights(term); + struct highlight_location const *location = term->vimode.highlights; + while (location != NULL) { + struct highlight_location const *next = location->next; + free((void *)location); + location = next; + } + term->vimode.highlights = NULL; +} + +// calculate_highlight_regions +// +// Build a list of regions consisting of all current search matches. +// The regions are split so that each one spans at most a single line. +// The regions are in absolute row coordinates. +// +static void calculate_highlight_regions(struct terminal *const term) { + char32_t const *search_buf = term->vimode.search.buf; + size_t search_len = term->vimode.search.len; + if (search_buf == NULL) { + search_buf = term->vimode.confirmed_search.buf; + search_len = term->vimode.confirmed_search.len; + } + + struct highlight_location *start = NULL; + struct highlight_location *current = NULL; + struct search_match_iterator iter = + search_matches_new_iter(term, search_buf, search_len); + for (struct range match = search_matches_next(&iter); match.start.row >= 0; + match = search_matches_next(&iter)) { + int r = match.start.row; + int start_col = match.start.col; + int const end_row = match.end.row; + + while (true) { + const int end_col = r == end_row ? match.end.col : term->cols - 1; + struct highlight_location *location = + xmalloc(sizeof(struct highlight_location)); + location->next = NULL; + location->range = (struct range){ + .start.row = r, + .start.col = start_col, + .end.row = end_row, + .end.col = end_col, + }; + r += 1; + start_col = 0; + if (start != NULL) { + current->next = location; + current = location; + } else { + start = location; + current = location; + } + if (r > end_row) { + break; + } + } + } + term->vimode.highlights = start; +} + +static void update_highlights(struct terminal *const term) { + clear_highlights(term); + calculate_highlight_regions(term); + struct highlight_location const *location = term->vimode.highlights; + printf("NEW HIGHLIGHT REGIONS: "); + while (location != NULL) { + struct highlight_location const *next = location->next; + printf("[(%d, %d) - (%d, %d)] ", location->range.start.row, + location->range.start.col, location->range.end.row, + location->range.end.col); + location = next; + } + printf("\n"); + damage_highlights(term); +} + /* * Ensures a "new" viewport doesn't contain any unallocated rows. * @@ -309,6 +406,7 @@ void vimode_cancel(struct terminal *term) { printf("VIMODE CANCEL\n"); cancel_search(term, false); + clear_highlights(term); term->is_vimming = false; @@ -534,22 +632,25 @@ static bool find_next_from_cursor(struct terminal *const term, return find_next(term, buf, len, direction, start, end, match); } -struct search_match_iterator search_matches_new_iter(struct terminal *term) { +struct search_match_iterator +search_matches_new_iter(struct terminal *const term, char32_t const *const buf, + size_t const len) { return (struct search_match_iterator){ .term = term, .start = {0, 0}, + .buf = buf, + .len = len, }; } struct range search_matches_next(struct search_match_iterator *iter) { struct terminal *term = iter->term; struct grid *grid = term->grid; - - if (term->vimode.search.match_len == 0) - goto no_match; - - if (iter->start.row >= term->rows) - goto no_match; + if (iter->buf == NULL || iter->len == 0 || iter->start.row >= term->rows) { + iter->start.row = -1; + iter->start.col = -1; + return (struct range){{-1, -1}, {-1, -1}}; + } xassert(iter->start.row >= 0); xassert(iter->start.row < term->rows); @@ -565,43 +666,34 @@ struct range search_matches_next(struct search_match_iterator *iter) { /* BUG: matches *starting* outside the view, but ending *inside*, aren't * matched */ struct range match; - // TODO (kociap): consider whether using active search buffer is - // reasonable. - bool found = find_next(term, term->vimode.search.buf, term->vimode.search.len, - SEARCH_FORWARD, abs_start, abs_end, &match); - if (!found) - goto no_match; + bool found = find_next(term, iter->buf, iter->len, SEARCH_FORWARD, abs_start, + abs_end, &match); + if (!found) { + iter->start.row = -1; + iter->start.col = -1; + return (struct range){{-1, -1}, {-1, -1}}; + } LOG_DBG("match at (absolute coordinates) %dx%d-%dx%d", match.start.row, match.start.col, match.end.row, match.end.col); - /* Convert absolute row numbers back to view relative */ - match.start.row = match.start.row - grid->view + grid->num_rows; - match.start.row &= grid->num_rows - 1; - match.end.row = match.end.row - grid->view + grid->num_rows; - match.end.row &= grid->num_rows - 1; - - LOG_DBG("match at (view-local coordinates) %dx%d-%dx%d, view=%d", - match.start.row, match.start.col, match.end.row, match.end.col, - grid->view); - /* Assert match end comes *after* the match start */ xassert( match.end.row > match.start.row || (match.end.row == match.start.row && match.end.col >= match.start.col)); /* Assert the match starts at, or after, the iterator position */ - xassert(match.start.row > iter->start.row || - (match.start.row == iter->start.row && - match.start.col >= iter->start.col)); + xassert( + match.start.row > abs_start.row || + (match.start.row == abs_start.row && match.start.col >= abs_start.col)); /* Continue at next column, next time */ - iter->start.row = match.start.row; + iter->start.row += match.start.row - abs_start.row; iter->start.col = match.start.col + 1; if (iter->start.col >= term->cols) { iter->start.col = 0; - iter->start.row++; /* Overflow is caught in next iteration */ + iter->start.row += 1; /* Overflow is caught in next iteration */ } xassert(iter->start.row >= 0); @@ -609,11 +701,6 @@ struct range search_matches_next(struct search_match_iterator *iter) { xassert(iter->start.col >= 0); xassert(iter->start.col < term->cols); return match; - -no_match: - iter->start.row = -1; - iter->start.col = -1; - return (struct range){{-1, -1}, {-1, -1}}; } static void add_wchars(struct terminal *term, char32_t *src, size_t count) { @@ -717,6 +804,7 @@ void vimode_view_down(struct terminal *const term, int const delta) { term->vimode.cursor.row -= delta; printf("VIMODE VIEW DOWN [delta=%d]\n", delta); clip_cursor_to_view(term); + update_highlights(term); } static void move_cursor_delta(struct terminal *const term, @@ -792,11 +880,13 @@ static void execute_vimode_binding(struct seat *seat, struct terminal *term, case BIND_ACTION_VIMODE_UP: move_cursor_vertical(term, -1); update_selection(seat, term); + update_highlights(term); break; case BIND_ACTION_VIMODE_DOWN: move_cursor_vertical(term, 1); update_selection(seat, term); + update_highlights(term); break; case BIND_ACTION_VIMODE_LEFT: @@ -813,36 +903,42 @@ static void execute_vimode_binding(struct seat *seat, struct terminal *term, cmd_scrollback_up(term, term->rows); clip_cursor_to_view(term); update_selection(seat, term); + update_highlights(term); break; case BIND_ACTION_VIMODE_DOWN_PAGE: cmd_scrollback_down(term, term->rows); clip_cursor_to_view(term); update_selection(seat, term); + update_highlights(term); break; case BIND_ACTION_VIMODE_UP_HALF_PAGE: cmd_scrollback_up(term, max(term->rows / 2, 1)); clip_cursor_to_view(term); update_selection(seat, term); + update_highlights(term); break; case BIND_ACTION_VIMODE_DOWN_HALF_PAGE: cmd_scrollback_down(term, max(term->rows / 2, 1)); clip_cursor_to_view(term); update_selection(seat, term); + update_highlights(term); break; case BIND_ACTION_VIMODE_UP_LINE: cmd_scrollback_up(term, 1); clip_cursor_to_view(term); update_selection(seat, term); + update_highlights(term); break; case BIND_ACTION_VIMODE_DOWN_LINE: cmd_scrollback_down(term, 1); clip_cursor_to_view(term); update_selection(seat, term); + update_highlights(term); break; case BIND_ACTION_VIMODE_FIRST_LINE: @@ -851,6 +947,7 @@ static void execute_vimode_binding(struct seat *seat, struct terminal *term, term->vimode.cursor.row = cursor_from_scrollback_relative(term, 0); damage_cursor_cell(term); update_selection(seat, term); + update_highlights(term); break; case BIND_ACTION_VIMODE_LAST_LINE: @@ -859,6 +956,7 @@ static void execute_vimode_binding(struct seat *seat, struct terminal *term, term->vimode.cursor.row = term->rows - 1; damage_cursor_cell(term); update_selection(seat, term); + update_highlights(term); break; case BIND_ACTION_VIMODE_CANCEL: { @@ -906,6 +1004,7 @@ static void execute_vimode_binding(struct seat *seat, struct terminal *term, // TODO (kociap): update selection. move_cursor_delta(term, delta); } + update_highlights(term); break; } @@ -1170,6 +1269,7 @@ void vimode_input(struct seat *seat, struct terminal *term, } else { restore_pre_search_state(term); } + update_highlights(term); } } } diff --git a/vimode.h b/vimode.h index 7946b110..ff39b660 100644 --- a/vimode.h +++ b/vimode.h @@ -25,9 +25,13 @@ void vimode_input(struct seat *seat, struct terminal *term, struct search_match_iterator { struct terminal *term; struct coord start; + char32_t const *buf; + size_t len; }; -struct search_match_iterator search_matches_new_iter(struct terminal *term); +struct search_match_iterator search_matches_new_iter(struct terminal *term, + char32_t const *const buf, + size_t const len); struct range search_matches_next(struct search_match_iterator *iter); void vimode_view_down(struct terminal *term, int delta);