From 61cabdac13986e6cb2ba69e81d5934f55d92a1a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Aug 2019 17:23:28 +0200 Subject: [PATCH 01/71] search: wip: re-direct input while searching, and build a search buffer This adds a new state, 'is_searching'. While active, input is re-directed, and stored in a search buffer. In the future, we'll use this buffer and search for its content in the scrollback buffer, and move the view and create a selection on matches. When rendering in 'is_searching', everything is dimmed. In the future, we'll render the current search buffer on-top of the dimmed "regular" terminal output. --- input.c | 18 +++++++-- meson.build | 1 + render.c | 28 ++++++++++---- search.c | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++ search.h | 8 ++++ terminal.h | 8 ++++ 6 files changed, 160 insertions(+), 10 deletions(-) create mode 100644 search.c create mode 100644 search.h diff --git a/input.c b/input.c index dc48b44d..4384cead 100644 --- a/input.c +++ b/input.c @@ -18,11 +18,12 @@ #define LOG_MODULE "input" #define LOG_ENABLE_DBG 0 #include "log.h" -#include "terminal.h" -#include "render.h" -#include "keymap.h" #include "commands.h" +#include "keymap.h" +#include "render.h" +#include "search.h" #include "selection.h" +#include "terminal.h" #include "vt.h" static void @@ -178,6 +179,12 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, xkb_mod_mask_t significant = ctrl | alt | shift | meta; xkb_mod_mask_t effective_mods = mods & ~consumed & significant; + if (term->is_searching) { + start_repeater(term, key - 8); + search_input(term, key, sym, effective_mods); + return; + } + #if 0 for (size_t i = 0; i < 32; i++) { if (mods & (1 << i)) { @@ -220,6 +227,11 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, selection_from_clipboard(term, serial); found_map = true; } + + else if (sym == XKB_KEY_R) { + search_begin(term); + found_map = true; + } } for (size_t i = 0; i < sizeof(key_map) / sizeof(key_map[0]) && !found_map; i++) { diff --git a/meson.build b/meson.build index edf354b2..01a85cc8 100644 --- a/meson.build +++ b/meson.build @@ -90,6 +90,7 @@ executable( 'main.c', 'osc.c', 'osc.h', 'render.c', 'render.h', + 'search.c', 'search.h', 'selection.c', 'selection.h', 'shm.c', 'shm.h', 'slave.c', 'slave.h', diff --git a/render.c b/render.c index 84c86232..990726d8 100644 --- a/render.c +++ b/render.c @@ -204,6 +204,11 @@ render_cell(struct terminal *term, pixman_image_t *pix, bg = color_hex_to_pixman(term->cursor_color.cursor); } + if (term->is_searching) { + pixman_color_dim(&fg); + pixman_color_dim(&bg); + } + struct font *font = attrs_to_font(term, &cell->attrs); const struct glyph *glyph = font_glyph_for_wc(font, cell->wc); @@ -216,9 +221,13 @@ render_cell(struct terminal *term, pixman_image_t *pix, /* Non-block cursors */ if (has_cursor && !block_cursor) { - pixman_color_t cursor_color = term->cursor_color.text >> 31 - ? color_hex_to_pixman(term->cursor_color.cursor) - : fg; + pixman_color_t cursor_color; + if (term->cursor_color.text >> 31) { + cursor_color = color_hex_to_pixman(term->cursor_color.cursor); + if (term->is_searching) + pixman_color_dim(&cursor_color); + } else + cursor_color = fg; if (term->cursor_style == CURSOR_BAR) draw_bar(term, pix, &cursor_color, x, y); @@ -435,9 +444,10 @@ grid_render(struct terminal *term) term_damage_view(term); /* If we resized the window, or is flashing, or just stopped flashing */ - if (term->render.last_buf != buf || term->flash.active || term->render.was_flashing) { - LOG_DBG("new buffer"); - + if (term->render.last_buf != buf || + term->flash.active || term->render.was_flashing || + term->is_searching != term->render.was_searching) + { /* Fill area outside the cell grid with the default background color */ int rmargin = term->x_margin + term->cols * term->cell_width; int bmargin = term->y_margin + term->rows * term->cell_height; @@ -445,8 +455,10 @@ grid_render(struct terminal *term) int bmargin_height = term->height - bmargin; uint32_t _bg = !term->reverse ? term->colors.bg : term->colors.fg; - pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, term->colors.alpha); + if (term->is_searching) + pixman_color_dim(&bg); + pixman_image_fill_rectangles( PIXMAN_OP_SRC, pix, &bg, 4, (pixman_rectangle16_t[]){ @@ -469,6 +481,7 @@ grid_render(struct terminal *term) term->render.last_buf = buf; term->render.was_flashing = term->flash.active; + term->render.was_searching = term->is_searching; } tll_foreach(term->grid->scroll_damage, it) { @@ -626,6 +639,7 @@ grid_render(struct terminal *term) if (term->flash.active) { /* Note: alpha is pre-computed in each color component */ + /* TODO: dim while searching */ pixman_image_fill_rectangles( PIXMAN_OP_OVER, pix, &(pixman_color_t){.red=0x7fff, .green=0x7fff, .blue=0, .alpha=0x7fff}, diff --git a/search.c b/search.c new file mode 100644 index 00000000..55c67bb0 --- /dev/null +++ b/search.c @@ -0,0 +1,107 @@ +#include "search.h" + +#include +#include + +#define LOG_MODULE "search" +#define LOG_ENABLE_DBG 1 +#include "log.h" +#include "render.h" + +void +search_begin(struct terminal *term) +{ + LOG_DBG("search: begin"); + + free(term->search.buf); + term->search.buf = NULL; + term->search.len = 0; + term->search.sz = 0; + term->is_searching = true; + + render_refresh(term); +} + +void +search_cancel(struct terminal *term) +{ + LOG_DBG("search: cancel"); + + free(term->search.buf); + term->search.buf = NULL; + term->search.len = 0; + term->search.sz = 0; + term->is_searching = false; + + render_refresh(term); +} + +void +search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask_t mods) +{ + LOG_DBG("search: input: sym=%d/0x%x, mods=0x%08x", sym, sym, mods); + + const xkb_mod_mask_t ctrl = 1 << term->kbd.mod_ctrl; + //const xkb_mod_mask_t alt = 1 << term->kbd.mod_alt; + //const xkb_mod_mask_t shift = 1 << term->kbd.mod_shift; + //const xkb_mod_mask_t meta = 1 << term->kbd.mod_meta; + + 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)) + search_cancel(term); + + else if (mods == 0 && sym == XKB_KEY_BackSpace) { + if (term->search.len > 0) + term->search.buf[--term->search.len] = L'\0'; + } + + else { + uint8_t buf[64] = {0}; + int count = 0; + + if (compose_status == XKB_COMPOSE_COMPOSED) { + count = xkb_compose_state_get_utf8( + term->kbd.xkb_compose_state, (char *)buf, sizeof(buf)); + xkb_compose_state_reset(term->kbd.xkb_compose_state); + } else { + count = xkb_state_key_get_utf8( + term->kbd.xkb_state, key, (char *)buf, sizeof(buf)); + } + + const char *src = (const char *)buf; + mbstate_t ps = {0}; + size_t wchars = mbsnrtowcs(NULL, &src, count, 0, &ps); + + if (wchars == -1) { + LOG_ERRNO("failed to convert %.*s to wchars", count, buf); + return; + } + + while (term->search.len + wchars >= term->search.sz) { + size_t new_sz = term->search.sz == 0 ? 64 : term->search.sz * 2; + wchar_t *new_buf = realloc(term->search.buf, new_sz * sizeof(term->search.buf[0])); + + if (new_buf == NULL) { + LOG_ERRNO("failed to resize search buffer"); + return; + } + + term->search.buf = new_buf; + term->search.sz = new_sz; + } + + assert(term->search.len + wchars < term->search.sz); + + memset(&ps, 0, sizeof(ps)); + mbsnrtowcs(&term->search.buf[term->search.len], &src, count, + term->search.sz - term->search.len - 1, &ps); + + term->search.len += wchars; + term->search.buf[term->search.len] = L'\0'; + } + + LOG_DBG("search: buffer: %S", term->search.buf); + render_refresh(term); +} diff --git a/search.h b/search.h new file mode 100644 index 00000000..4ea59c1f --- /dev/null +++ b/search.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include "terminal.h" + +void search_begin(struct terminal *term); +void search_cancel(struct terminal *term); +void search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask_t mods); diff --git a/terminal.h b/terminal.h index 6f373a14..fbd734bc 100644 --- a/terminal.h +++ b/terminal.h @@ -331,6 +331,13 @@ struct terminal { struct primary primary; } selection; + bool is_searching; + struct { + wchar_t *buf; + size_t len; + size_t sz; + } search; + struct grid normal; struct grid alt; struct grid *grid; @@ -368,6 +375,7 @@ struct terminal { struct buffer *last_buf; /* Buffer we rendered to last time */ bool was_flashing; /* Flash was active last time we rendered */ + bool was_searching; } render; }; From aee5045395ca2393b60d962e6e9440a59edbfd6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Aug 2019 19:33:19 +0200 Subject: [PATCH 02/71] search: wip: initial search matching * match search buffer against scrollback content * adjust view to ensure matched content is visible * create selection on a successful match * finalize selection when user presses enter (to "commit" the search) * ctrl+r searches for the next match. Needs more work though. --- grid.h | 16 ++++- search.c | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++- terminal.h | 5 ++ 3 files changed, 187 insertions(+), 3 deletions(-) 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; From bb4fd58223612330f150fe590de3673d36995c05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Aug 2019 19:40:07 +0200 Subject: [PATCH 03/71] render: don't dim selection while searching --- render.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render.c b/render.c index 990726d8..1ed2202f 100644 --- a/render.c +++ b/render.c @@ -204,7 +204,7 @@ render_cell(struct terminal *term, pixman_image_t *pix, bg = color_hex_to_pixman(term->cursor_color.cursor); } - if (term->is_searching) { + if (term->is_searching && !is_selected) { pixman_color_dim(&fg); pixman_color_dim(&bg); } From 43fc297fbd1bebc821ed3ee2b932e5cc8b806542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Aug 2019 19:43:50 +0200 Subject: [PATCH 04/71] main: free search buffer --- main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.c b/main.c index 2a3b3daa..0710f933 100644 --- a/main.c +++ b/main.c @@ -1086,6 +1086,8 @@ out: for (size_t i = 0; i < sizeof(term.fonts) / sizeof(term.fonts[0]); i++) font_destroy(&term.fonts[i]); + free(term.search.buf); + if (term.flash.fd != -1) close(term.flash.fd); if (term.blink.fd != -1) From 3f767be500d6a92d293c3cbf6cc04cce6c8dc594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Aug 2019 19:44:19 +0200 Subject: [PATCH 05/71] input: cancel search on mouse button events --- input.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/input.c b/input.c index 4384cead..237d1f68 100644 --- a/input.c +++ b/input.c @@ -448,6 +448,8 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, struct terminal *term = data; + search_cancel(term); + switch (state) { case WL_POINTER_BUTTON_STATE_PRESSED: { /* Time since last click */ From d518d9dafb218c308bd8e2be22b6c6bad6a4ac44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Aug 2019 19:55:41 +0200 Subject: [PATCH 06/71] search: don't create a new selection when match start coords haven't changed --- search.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/search.c b/search.c index cdc7ccf5..03e114c0 100644 --- a/search.c +++ b/search.c @@ -153,8 +153,14 @@ search_update(struct terminal *term) start_row--; } - /* Begin a selection (it's finalized when "committing" the search) */ - selection_start(term, start_col, start_row - term->grid->view); + /* Begin a new selection if the start coords changed */ + if (start_row != term->search.match.row || + start_col != term->search.match.col) + { + selection_start(term, start_col, start_row - term->grid->view); + } + + /* Update selection endpoint */ selection_update(term, end_col, end_row - term->grid->view); /* Update match state */ From d1974913f782ba4a5993313646b3a3fdb2eb40c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Aug 2019 19:56:02 +0200 Subject: [PATCH 07/71] search: search_update() is always called at end of search_input() --- search.c | 1 - 1 file changed, 1 deletion(-) diff --git a/search.c b/search.c index 03e114c0..30edad2e 100644 --- a/search.c +++ b/search.c @@ -219,7 +219,6 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask if (new_row >= 0) { term->search.match.col = new_col; term->search.match.row = new_row; - search_update(term); } } } From c1bbb64a4d87f8fb4d5884cccb49e47af462b45a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Aug 2019 19:56:16 +0200 Subject: [PATCH 08/71] search: return early after committing a search This fixes an issue where the selection was finalized, but then cleared. --- search.c | 1 + 1 file changed, 1 insertion(+) diff --git a/search.c b/search.c index 30edad2e..48b45b9a 100644 --- a/search.c +++ b/search.c @@ -204,6 +204,7 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask else if (mods == 0 && sym == XKB_KEY_Return) { selection_finalize(term, term->input_serial); search_cancel(term); + return; } else if (mods == ctrl && sym == XKB_KEY_r) { From c3b5fa82bec9e84db9028df65e47793b21e11851 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Aug 2019 19:58:31 +0200 Subject: [PATCH 09/71] search: 'len' is unused in release builds (it's only used in an assert) --- search.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/search.c b/search.c index 48b45b9a..37ab5684 100644 --- a/search.c +++ b/search.c @@ -54,7 +54,7 @@ search_update(struct terminal *term) int start_row = term->search.match.row; int start_col = term->search.match.col; - size_t len = term->search.match_len; + size_t len __attribute__((unused)) = term->search.match_len; assert((len == 0 && start_row == -1 && start_col == -1) || (len > 0 && start_row >= 0 && start_col >= 0)); From 2895f8fa107e9034f4d9b0731b1eddfd0cc9347b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Aug 2019 19:58:44 +0200 Subject: [PATCH 10/71] search: cancel selection and clear match stats when we don't match --- search.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/search.c b/search.c index 37ab5684..992de851 100644 --- a/search.c +++ b/search.c @@ -173,6 +173,11 @@ search_update(struct terminal *term) start_col = term->cols - 1; } + + /* No match */ + term->search.match = (struct coord){-1, -1}; + term->search.match_len = 0; + selection_cancel(term); } void From 58d0657a97d9e9f7c31ac7306ba7e2912821ebb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Aug 2019 20:57:58 +0200 Subject: [PATCH 11/71] selection: allow selections while searching scrollback --- selection.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/selection.c b/selection.c index 45f970f2..c4991906 100644 --- a/selection.c +++ b/selection.c @@ -20,7 +20,10 @@ bool selection_enabled(const struct terminal *term) { - return term->mouse_tracking == MOUSE_NONE || term->kbd.shift; + return + term->mouse_tracking == MOUSE_NONE || + term->kbd.shift || + term->is_searching; } bool From 275915228584e665f71266da30084c9ef5d67b14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Aug 2019 21:09:37 +0200 Subject: [PATCH 12/71] search: loop through the *entire* scrollback buffer --- search.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/search.c b/search.c index 992de851..a44ca7ec 100644 --- a/search.c +++ b/search.c @@ -66,11 +66,13 @@ search_update(struct terminal *term) LOG_DBG("search: update: starting at row=%d col=%d", start_row, start_col); +#define ROW_DEC(_r) ((_r) = ((_r) - 1 + term->grid->num_rows) % term->grid->num_rows) + /* Scan backward from current end-of-output */ - for (; start_row >= 0; start_row--) { + for (size_t r = 0; r < term->grid->num_rows; ROW_DEC(start_row), r++) { const struct row *row = term->grid->rows[start_row]; if (row == NULL) - break; + continue; for (; start_col >= 0; start_col--) { if (row->cells[start_col].wc != term->search.buf[0]) @@ -175,9 +177,12 @@ search_update(struct terminal *term) } /* No match */ + LOG_DBG("no match"); term->search.match = (struct coord){-1, -1}; term->search.match_len = 0; selection_cancel(term); + +#undef ROW_DEC } void From 36350b01a5a51bd105b3852047b6889cc2a91e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Aug 2019 21:11:29 +0200 Subject: [PATCH 13/71] search: disable log output --- search.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/search.c b/search.c index a44ca7ec..849a5c8a 100644 --- a/search.c +++ b/search.c @@ -4,7 +4,7 @@ #include #define LOG_MODULE "search" -#define LOG_ENABLE_DBG 1 +#define LOG_ENABLE_DBG 0 #include "log.h" #include "render.h" #include "selection.h" From bd5e5798674ebfdbe7fe198fee493505b85c0b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Aug 2019 21:11:40 +0200 Subject: [PATCH 14/71] search: sort includes --- search.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/search.c b/search.c index 849a5c8a..17ac0cb8 100644 --- a/search.c +++ b/search.c @@ -6,9 +6,9 @@ #define LOG_MODULE "search" #define LOG_ENABLE_DBG 0 #include "log.h" +#include "grid.h" #include "render.h" #include "selection.h" -#include "grid.h" void search_begin(struct terminal *term) From 064063fa4918fca4853d4c267f6316960bdb8e19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Aug 2019 21:16:57 +0200 Subject: [PATCH 15/71] search: match case insensitive --- search.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/search.c b/search.c index 17ac0cb8..d0740b02 100644 --- a/search.c +++ b/search.c @@ -75,7 +75,7 @@ search_update(struct terminal *term) continue; for (; start_col >= 0; start_col--) { - if (row->cells[start_col].wc != term->search.buf[0]) + if (wcsncasecmp(&row->cells[start_col].wc, term->search.buf, 1) != 0) continue; /* @@ -90,7 +90,7 @@ search_update(struct terminal *term) 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]) + if (wcsncasecmp(&row->cells[end_col].wc, &term->search.buf[i], 1) != 0) break; if (++end_col >= term->cols) { From 6fa1734468e0663c579cc0ab21f080e3c8be7262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Aug 2019 21:20:24 +0200 Subject: [PATCH 16/71] README: document keyboard shortcuts related to scrollback searching --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 0fafc587..23afee5b 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,15 @@ is **not** possible to define new key bindings. * `shift+page up/down` - scroll up/down in history * `ctrl+shift+c` - copy selected text to the _clipboard_ * `ctrl+shift+v` - paste from _clipboard_ +* `ctrl+shift+r` - start a scrollback search + +While doing a scrollback search, the following shortcuts are +available: + +* `ctrl+r` - search for next match +* `esc` - cancel the search +* `ctrl+g` - cancel the search (same as `esc`) +* `return` - finish the search and put the current match to the primary selection ### Mouse From 777863ac3e27464175699921fceb2c1fab163d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 28 Aug 2019 17:25:42 +0200 Subject: [PATCH 17/71] term: add term_reset_view() - make view follow end-of-output again --- terminal.c | 10 ++++++++++ terminal.h | 2 ++ 2 files changed, 12 insertions(+) diff --git a/terminal.c b/terminal.c index b9d2f0f3..95a60682 100644 --- a/terminal.c +++ b/terminal.c @@ -362,6 +362,16 @@ term_reverse_index(struct terminal *term) term_cursor_up(term, 1); } +void +term_reset_view(struct terminal *term) +{ + if (term->grid->view == term->grid->offset) + return; + + term->grid->view = term->grid->offset; + term_damage_view(term); +} + void term_restore_cursor(struct terminal *term) { diff --git a/terminal.h b/terminal.h index 5989788a..18130392 100644 --- a/terminal.h +++ b/terminal.h @@ -392,6 +392,8 @@ void term_damage_rows_in_view(struct terminal *term, int start, int end); void term_damage_all(struct terminal *term); void term_damage_view(struct terminal *term); +void term_reset_view(struct terminal *term); + void term_damage_scroll( struct terminal *term, enum damage_type damage_type, struct scroll_region region, int lines); From 64179bce4669bb1da753bd9de1f4bf3aeb1b6141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 28 Aug 2019 17:26:30 +0200 Subject: [PATCH 18/71] search: start search in current view, rather than from end-of-output --- search.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/search.c b/search.c index d0740b02..903645e5 100644 --- a/search.c +++ b/search.c @@ -60,15 +60,17 @@ search_update(struct terminal *term) (len > 0 && start_row >= 0 && start_col >= 0)); if (len == 0) { - start_row = grid_row_absolute(term->grid, term->rows - 1); + start_row = grid_row_absolute_in_view(term->grid, term->rows - 1); start_col = term->cols - 1; } - LOG_DBG("search: update: starting at row=%d col=%d", start_row, start_col); + LOG_DBG("search: update: starting at row=%d col=%d (offset = %d, view = %d)", + start_row, start_col, term->grid->offset, term->grid->view); #define ROW_DEC(_r) ((_r) = ((_r) - 1 + term->grid->num_rows) % term->grid->num_rows) /* Scan backward from current end-of-output */ + /* TODO: don't search "scrollback" in alt screen? */ for (size_t r = 0; r < term->grid->num_rows; ROW_DEC(start_row), r++) { const struct row *row = term->grid->rows[start_row]; if (row == NULL) From b8b43e2eabfd8647a9fc41d28cf7ea0003c03b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 28 Aug 2019 17:26:44 +0200 Subject: [PATCH 19/71] search: fix start/end row in selection --- search.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/search.c b/search.c index 903645e5..82902e6c 100644 --- a/search.c +++ b/search.c @@ -161,11 +161,25 @@ search_update(struct terminal *term) if (start_row != term->search.match.row || start_col != term->search.match.col) { - selection_start(term, start_col, start_row - term->grid->view); + int selection_row = start_row - term->grid->view; + while (selection_row < 0) + selection_row += term->grid->num_rows; + + assert(selection_row >= 0 && + selection_row < term->grid->num_rows); + selection_start(term, start_col, selection_row); } /* Update selection endpoint */ - selection_update(term, end_col, end_row - term->grid->view); + { + int selection_row = end_row - term->grid->view; + while (selection_row < 0) + selection_row += term->grid->num_rows; + + assert(selection_row >= 0 && + selection_row < term->grid->num_rows); + selection_update(term, end_col, selection_row); + } /* Update match state */ term->search.match.row = start_row; From 551d51bf9d201b962c897e2e9a5b36bc79cda0d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 28 Aug 2019 17:27:17 +0200 Subject: [PATCH 20/71] input: call term_reset_view() instead of duplicating code --- input.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/input.c b/input.c index 237d1f68..76bc5812 100644 --- a/input.c +++ b/input.c @@ -225,6 +225,7 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, else if (sym == XKB_KEY_V) { selection_from_clipboard(term, serial); + term_reset_view(term); found_map = true; } @@ -255,11 +256,7 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, vt_to_slave(term, info->seq, strlen(info->seq)); found_map = true; - if (term->grid->view != term->grid->offset) { - term->grid->view = term->grid->offset; - term_damage_all(term); - } - + term_reset_view(term); selection_cancel(term); break; } @@ -320,13 +317,9 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, vt_to_slave(term, "\x1b", 1); vt_to_slave(term, buf, count); - - if (term->grid->view != term->grid->offset) { - term->grid->view = term->grid->offset; - term_damage_all(term); - } } + term_reset_view(term); selection_cancel(term); } } From c2b4f4daa6f6e407f9856a3081b2d97d4efb567f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 28 Aug 2019 21:01:36 +0200 Subject: [PATCH 21/71] main: log cell width/height at info level, not debug This complements the INFO logs from the renderer when the terminal is resized. --- main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.c b/main.c index 0710f933..60d041f1 100644 --- a/main.c +++ b/main.c @@ -630,7 +630,7 @@ main(int argc, char *const *argv) term.cell_width = (int)ceil(term.fextents.max_x_advance); term.cell_height = (int)ceil(term.fextents.height); - LOG_DBG("cell width=%d, height=%d", term.cell_width, term.cell_height); + LOG_INFO("cell width=%d, height=%d", term.cell_width, term.cell_height); term.wl.display = wl_display_connect(NULL); if (term.wl.display == NULL) { From 0d0ee8f378e43bf761ea52911c5afe6fdf7c9aa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 28 Aug 2019 21:03:30 +0200 Subject: [PATCH 22/71] search: log search buffer at INFO level ... until we actually render the search buffer somehow --- search.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/search.c b/search.c index 82902e6c..46bbb153 100644 --- a/search.c +++ b/search.c @@ -300,7 +300,7 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask term->search.buf[term->search.len] = L'\0'; } - LOG_DBG("search: buffer: %S", term->search.buf); + LOG_INFO("search: buffer: %S", term->search.buf); search_update(term); render_refresh(term); } From 242ab66c97771259d0f6d750b6407e8de1bb9ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 28 Aug 2019 21:53:01 +0200 Subject: [PATCH 23/71] render: double-dim fg+bg when searching the scrollback buffer By toning down the screen content more, the selection (i.e. the search match) pops out much more. --- render.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/render.c b/render.c index 1ed2202f..6501aa4b 100644 --- a/render.c +++ b/render.c @@ -207,6 +207,8 @@ render_cell(struct terminal *term, pixman_image_t *pix, if (term->is_searching && !is_selected) { pixman_color_dim(&fg); pixman_color_dim(&bg); + pixman_color_dim(&fg); + pixman_color_dim(&bg); } struct font *font = attrs_to_font(term, &cell->attrs); From 94b4c916eebf71d399f8a2cbe820eee5d79f5514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 28 Aug 2019 21:54:02 +0200 Subject: [PATCH 24/71] search: cancel existing selection when starting a new search --- search.c | 1 + 1 file changed, 1 insertion(+) diff --git a/search.c b/search.c index 46bbb153..6aa6be5c 100644 --- a/search.c +++ b/search.c @@ -25,6 +25,7 @@ search_begin(struct terminal *term) term->search.view_followed_offset = term->grid->view == term->grid->offset; term->is_searching = true; + selection_cancel(term); render_refresh(term); } From 2a31c2fbbc2f38abef54090767f842a9e8016c29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 19:33:25 +0200 Subject: [PATCH 25/71] render: reduce amount of dim while searching scrollback history --- render.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/render.c b/render.c index 6501aa4b..8bdaeb0f 100644 --- a/render.c +++ b/render.c @@ -72,6 +72,14 @@ pixman_color_dim(pixman_color_t *color) color->blue /= 2; } +static inline void +pixman_color_dim_for_search(pixman_color_t *color) +{ + color->red /= 3; + color->green /= 3; + color->blue /= 3; +} + static void draw_bar(const struct terminal *term, pixman_image_t *pix, const pixman_color_t *color, int x, int y) @@ -205,10 +213,8 @@ render_cell(struct terminal *term, pixman_image_t *pix, } if (term->is_searching && !is_selected) { - pixman_color_dim(&fg); - pixman_color_dim(&bg); - pixman_color_dim(&fg); - pixman_color_dim(&bg); + pixman_color_dim_for_search(&fg); + pixman_color_dim_for_search(&bg); } struct font *font = attrs_to_font(term, &cell->attrs); From 66912cbfb5e44d44670582fad932932f9de6794b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 19:34:41 +0200 Subject: [PATCH 26/71] search: use a sub-surface to render the search buffer --- main.c | 5 ++ search.c | 149 ++++++++++++++++++++++++++++++++++++++++++++++++----- terminal.h | 1 + 3 files changed, 141 insertions(+), 14 deletions(-) diff --git a/main.c b/main.c index 60d041f1..b78305f7 100644 --- a/main.c +++ b/main.c @@ -197,6 +197,11 @@ handle_global(void *data, struct wl_registry *registry, term->wl.registry, name, &wl_compositor_interface, 4); } + else if (strcmp(interface, wl_subcompositor_interface.name) == 0) { + term->wl.sub_compositor = wl_registry_bind( + term->wl.registry, name, &wl_subcompositor_interface, 1); + } + else if (strcmp(interface, wl_shm_interface.name) == 0) { term->wl.shm = wl_registry_bind( term->wl.registry, name, &wl_shm_interface, 1); diff --git a/search.c b/search.c index 6aa6be5c..558f98c7 100644 --- a/search.c +++ b/search.c @@ -1,6 +1,8 @@ #include "search.h" #include + +#include #include #define LOG_MODULE "search" @@ -9,11 +11,98 @@ #include "grid.h" #include "render.h" #include "selection.h" +#include "shm.h" -void -search_begin(struct terminal *term) +#define max(x, y) ((x) > (y) ? (x) : (y)) + +static struct wl_surface *wl_surf; +static struct wl_subsurface *sub_surf; + + +static inline pixman_color_t +color_hex_to_pixman_with_alpha(uint32_t color, uint16_t alpha) { - LOG_DBG("search: begin"); + int alpha_div = 0xffff / alpha; + return (pixman_color_t){ + .red = ((color >> 16 & 0xff) | (color >> 8 & 0xff00)) / alpha_div, + .green = ((color >> 8 & 0xff) | (color >> 0 & 0xff00)) / alpha_div, + .blue = ((color >> 0 & 0xff) | (color << 8 & 0xff00)) / alpha_div, + .alpha = alpha, + }; +} + +static inline pixman_color_t +color_hex_to_pixman(uint32_t color) +{ + /* Count on the compiler optimizing this */ + return color_hex_to_pixman_with_alpha(color, 0xffff); +} + +/* TODO: move to render.c? */ +static void +render(struct terminal *term) +{ + assert(sub_surf != NULL); + + /* TODO: at least sway allows the subsurface to extend outside the + * main window. Do we want that? */ + const int scale = term->scale >= 1 ? term->scale : 1; + const int margin = scale * 3; + const int width = 2 * margin + max(20, term->search.len) * term->cell_width; + const int height = 2 * margin + 1 * term->cell_height; + + struct buffer *buf = shm_get_buffer(term->wl.shm, width, height, 1); + + /* Background - yellow on empty/match, red on mismatch */ + pixman_color_t color = color_hex_to_pixman( + term->search.match_len == term->search.len ? 0xffff00 : 0xff0000); + + pixman_image_fill_rectangles( + PIXMAN_OP_SRC, buf->pix, &color, + 1, &(pixman_rectangle16_t){0, 0, width, height}); + + int x = margin; + int y = margin; + pixman_color_t fg = color_hex_to_pixman(0x000000); + + /* Text (what the user entered - *not* match(es)) */ + for (size_t i = 0; i < term->search.len; i++) { + const struct glyph *glyph = font_glyph_for_wc(&term->fonts[0], term->search.buf[i]); + if (glyph == NULL) + continue; + + pixman_image_t *src = pixman_image_create_solid_fill(&fg); + pixman_image_composite32( + PIXMAN_OP_OVER, src, glyph->pix, buf->pix, 0, 0, 0, 0, + x + glyph->x, y + term->fextents.ascent - glyph->y, + glyph->width, glyph->height); + pixman_image_unref(src); + + x += glyph->width; + } + + LOG_INFO("match length: %zu", term->search.match_len); + + wl_subsurface_set_position( + sub_surf, term->width - width - margin, term->height - height - margin); + + wl_surface_damage_buffer(wl_surf, 0, 0, width, height); + wl_surface_attach(wl_surf, buf->wl_buf, 0, 0); + wl_surface_set_buffer_scale(wl_surf, scale); + wl_surface_commit(wl_surf); +} + +static void +search_cancel_keep_selection(struct terminal *term) +{ + if (sub_surf != NULL) { + wl_subsurface_destroy(sub_surf); + sub_surf = NULL; + } + if (wl_surf != NULL) { + wl_surface_destroy(wl_surf); + wl_surf = NULL; + } free(term->search.buf); term->search.buf = NULL; @@ -21,35 +110,65 @@ search_begin(struct terminal *term) term->search.sz = 0; term->search.match = (struct coord){-1, -1}; term->search.match_len = 0; + term->is_searching = false; + + render_refresh(term); +} + +void +search_begin(struct terminal *term) +{ + LOG_DBG("search: begin"); + + search_cancel_keep_selection(term); + selection_cancel(term); + term->search.original_view = term->grid->view; term->search.view_followed_offset = term->grid->view == term->grid->offset; term->is_searching = true; - selection_cancel(term); + wl_surf = wl_compositor_create_surface(term->wl.compositor); + if (wl_surf != NULL) { + sub_surf = wl_subcompositor_get_subsurface( + term->wl.sub_compositor, wl_surf, term->wl.surface); + + if (sub_surf != NULL) { + /* Sub-surface updates may occur without updating the main + * window */ + wl_subsurface_set_desync(sub_surf); + } + } + + if (wl_surf == NULL || sub_surf == NULL) { + LOG_ERR("failed to create sub-surface for search box"); + if (wl_surf != NULL) + wl_surface_destroy(wl_surf); + assert(sub_surf == NULL); + } + + render(term); render_refresh(term); } void search_cancel(struct terminal *term) { - LOG_DBG("search: cancel"); + if (!term->is_searching) + return; - free(term->search.buf); - term->search.buf = NULL; - term->search.len = 0; - term->search.sz = 0; - term->is_searching = false; - - render_refresh(term); + search_cancel_keep_selection(term); + selection_cancel(term); } static void search_update(struct terminal *term) { if (term->search.len == 0) { + LOG_INFO("len == 0"); term->search.match = (struct coord){-1, -1}; term->search.match_len = 0; selection_cancel(term); + render(term); return; } @@ -187,6 +306,7 @@ search_update(struct terminal *term) term->search.match.col = start_col; term->search.match_len = match_len; + render(term); return; } @@ -198,7 +318,7 @@ search_update(struct terminal *term) term->search.match = (struct coord){-1, -1}; term->search.match_len = 0; selection_cancel(term); - + render(term); #undef ROW_DEC } @@ -225,12 +345,13 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask term->grid->view = term->search.original_view; term_damage_view(term); search_cancel(term); + return; } /* "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); + search_cancel_keep_selection(term); return; } diff --git a/terminal.h b/terminal.h index 18130392..755c7b4a 100644 --- a/terminal.h +++ b/terminal.h @@ -41,6 +41,7 @@ struct wayland { struct wl_display *display; struct wl_registry *registry; struct wl_compositor *compositor; + struct wl_subcompositor *sub_compositor; struct wl_surface *surface; struct wl_shm *shm; struct wl_seat *seat; From 44a353a7f74b562cb057c23760013a12becbecf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 20:15:33 +0200 Subject: [PATCH 27/71] search: move wayland surface variables into the term struct --- main.c | 20 ++++++++++++-------- search.c | 47 +++++++++++++++++++++++------------------------ terminal.h | 16 ++++++++++++++-- 3 files changed, 49 insertions(+), 34 deletions(-) diff --git a/main.c b/main.c index b78305f7..e92996ef 100644 --- a/main.c +++ b/main.c @@ -1019,12 +1019,6 @@ out: if (term.wl.xdg_output_manager != NULL) zxdg_output_manager_v1_destroy(term.wl.xdg_output_manager); - if (term.render.frame_callback != NULL) - wl_callback_destroy(term.render.frame_callback); - if (term.wl.xdg_toplevel != NULL) - xdg_toplevel_destroy(term.wl.xdg_toplevel); - if (term.wl.xdg_surface != NULL) - xdg_surface_destroy(term.wl.xdg_surface); free(term.wl.pointer.theme_name); if (term.wl.pointer.theme != NULL) wl_cursor_theme_destroy(term.wl.pointer.theme); @@ -1054,10 +1048,20 @@ out: zwp_primary_selection_device_manager_v1_destroy(term.wl.primary_selection_device_manager); if (term.wl.seat != NULL) wl_seat_destroy(term.wl.seat); - if (term.wl.surface != NULL) - wl_surface_destroy(term.wl.surface); + if (term.wl.search_sub_surface != NULL) + wl_subsurface_destroy(term.wl.search_sub_surface); + if (term.wl.search_surface != NULL) + wl_surface_destroy(term.wl.search_surface); + if (term.render.frame_callback != NULL) + wl_callback_destroy(term.render.frame_callback); + if (term.wl.xdg_toplevel != NULL) + xdg_toplevel_destroy(term.wl.xdg_toplevel); + if (term.wl.xdg_surface != NULL) + xdg_surface_destroy(term.wl.xdg_surface); if (term.wl.shell != NULL) xdg_wm_base_destroy(term.wl.shell); + if (term.wl.surface != NULL) + wl_surface_destroy(term.wl.surface); if (term.wl.shm != NULL) wl_shm_destroy(term.wl.shm); if (term.wl.compositor != NULL) diff --git a/search.c b/search.c index 558f98c7..150bd050 100644 --- a/search.c +++ b/search.c @@ -15,8 +15,6 @@ #define max(x, y) ((x) > (y) ? (x) : (y)) -static struct wl_surface *wl_surf; -static struct wl_subsurface *sub_surf; static inline pixman_color_t @@ -42,7 +40,7 @@ color_hex_to_pixman(uint32_t color) static void render(struct terminal *term) { - assert(sub_surf != NULL); + assert(term->wl.search_sub_surface != NULL); /* TODO: at least sway allows the subsurface to extend outside the * main window. Do we want that? */ @@ -84,24 +82,25 @@ render(struct terminal *term) LOG_INFO("match length: %zu", term->search.match_len); wl_subsurface_set_position( - sub_surf, term->width - width - margin, term->height - height - margin); + term->wl.search_sub_surface, + term->width - width - margin, term->height - height - margin); - wl_surface_damage_buffer(wl_surf, 0, 0, width, height); - wl_surface_attach(wl_surf, buf->wl_buf, 0, 0); - wl_surface_set_buffer_scale(wl_surf, scale); - wl_surface_commit(wl_surf); + wl_surface_damage_buffer(term->wl.search_surface, 0, 0, width, height); + wl_surface_attach(term->wl.search_surface, buf->wl_buf, 0, 0); + wl_surface_set_buffer_scale(term->wl.search_surface, scale); + wl_surface_commit(term->wl.search_surface); } static void search_cancel_keep_selection(struct terminal *term) { - if (sub_surf != NULL) { - wl_subsurface_destroy(sub_surf); - sub_surf = NULL; + if (term->wl.search_sub_surface != NULL) { + wl_subsurface_destroy(term->wl.search_sub_surface); + term->wl.search_sub_surface = NULL; } - if (wl_surf != NULL) { - wl_surface_destroy(wl_surf); - wl_surf = NULL; + if (term->wl.search_surface != NULL) { + wl_surface_destroy(term->wl.search_surface); + term->wl.search_surface = NULL; } free(term->search.buf); @@ -127,23 +126,23 @@ search_begin(struct terminal *term) term->search.view_followed_offset = term->grid->view == term->grid->offset; term->is_searching = true; - wl_surf = wl_compositor_create_surface(term->wl.compositor); - if (wl_surf != NULL) { - sub_surf = wl_subcompositor_get_subsurface( - term->wl.sub_compositor, wl_surf, term->wl.surface); + term->wl.search_surface = wl_compositor_create_surface(term->wl.compositor); + if (term->wl.search_surface != NULL) { + term->wl.search_sub_surface = wl_subcompositor_get_subsurface( + term->wl.sub_compositor, term->wl.search_surface, term->wl.surface); - if (sub_surf != NULL) { + if (term->wl.search_sub_surface != NULL) { /* Sub-surface updates may occur without updating the main * window */ - wl_subsurface_set_desync(sub_surf); + wl_subsurface_set_desync(term->wl.search_sub_surface); } } - if (wl_surf == NULL || sub_surf == NULL) { + if (term->wl.search_surface == NULL || term->wl.search_sub_surface == NULL) { LOG_ERR("failed to create sub-surface for search box"); - if (wl_surf != NULL) - wl_surface_destroy(wl_surf); - assert(sub_surf == NULL); + if (term->wl.search_surface != NULL) + wl_surface_destroy(term->wl.search_surface); + assert(term->wl.search_sub_surface == NULL); } render(term); diff --git a/terminal.h b/terminal.h index 755c7b4a..948a8f11 100644 --- a/terminal.h +++ b/terminal.h @@ -42,15 +42,19 @@ struct wayland { struct wl_registry *registry; struct wl_compositor *compositor; struct wl_subcompositor *sub_compositor; - struct wl_surface *surface; struct wl_shm *shm; + struct wl_seat *seat; + struct wl_keyboard *keyboard; struct zxdg_output_manager_v1 *xdg_output_manager; + + /* Clipboard */ struct wl_data_device_manager *data_device_manager; struct wl_data_device *data_device; struct zwp_primary_selection_device_manager_v1 *primary_selection_device_manager; struct zwp_primary_selection_device_v1 *primary_selection_device; - struct wl_keyboard *keyboard; + + /* Cursor */ struct { struct wl_pointer *pointer; uint32_t serial; @@ -61,9 +65,17 @@ struct wayland { int size; char *theme_name; } pointer; + + /* Main window */ + struct wl_surface *surface; struct xdg_wm_base *shell; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; + + /* Scrollback search */ + struct wl_surface *search_surface; + struct wl_subsurface *search_sub_surface; + bool have_argb8888; tll(struct monitor) monitors; /* All available outputs */ tll(const struct monitor *) on_outputs; /* Outputs we're mapped on */ From af8682210ec7b4f89f4d6a52fb568da9a4523d71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 20:17:53 +0200 Subject: [PATCH 28/71] main: destroy sub-compositor at exit --- main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.c b/main.c index e92996ef..18e2a19a 100644 --- a/main.c +++ b/main.c @@ -1064,6 +1064,8 @@ out: wl_surface_destroy(term.wl.surface); if (term.wl.shm != NULL) wl_shm_destroy(term.wl.shm); + if (term.wl.sub_compositor != NULL) + wl_subcompositor_destroy(term.wl.sub_compositor); if (term.wl.compositor != NULL) wl_compositor_destroy(term.wl.compositor); if (term.wl.registry != NULL) From 2c3ab701e7fc95f30b573b20873dc4dfb5c2fa66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 20:18:06 +0200 Subject: [PATCH 29/71] search: move render() function to the 'render' module --- render.c | 54 ++++++++++++++++++++++++++++++++++++ render.h | 1 + search.c | 84 +++----------------------------------------------------- 3 files changed, 59 insertions(+), 80 deletions(-) diff --git a/render.c b/render.c index 8bdaeb0f..9c01b15f 100644 --- a/render.c +++ b/render.c @@ -691,6 +691,60 @@ frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_da grid_render(term); } +void +render_search_box(struct terminal *term) +{ + assert(term->wl.search_sub_surface != NULL); + + /* TODO: at least sway allows the subsurface to extend outside the + * main window. Do we want that? */ + const int scale = term->scale >= 1 ? term->scale : 1; + const int margin = scale * 3; + const int width = 2 * margin + max(20, term->search.len) * term->cell_width; + const int height = 2 * margin + 1 * term->cell_height; + + struct buffer *buf = shm_get_buffer(term->wl.shm, width, height, 1); + + /* Background - yellow on empty/match, red on mismatch */ + pixman_color_t color = color_hex_to_pixman( + term->search.match_len == term->search.len ? 0xffff00 : 0xff0000); + + pixman_image_fill_rectangles( + PIXMAN_OP_SRC, buf->pix, &color, + 1, &(pixman_rectangle16_t){0, 0, width, height}); + + int x = margin; + int y = margin; + pixman_color_t fg = color_hex_to_pixman(0x000000); + + /* Text (what the user entered - *not* match(es)) */ + for (size_t i = 0; i < term->search.len; i++) { + const struct glyph *glyph = font_glyph_for_wc(&term->fonts[0], term->search.buf[i]); + if (glyph == NULL) + continue; + + pixman_image_t *src = pixman_image_create_solid_fill(&fg); + pixman_image_composite32( + PIXMAN_OP_OVER, src, glyph->pix, buf->pix, 0, 0, 0, 0, + x + glyph->x, y + term->fextents.ascent - glyph->y, + glyph->width, glyph->height); + pixman_image_unref(src); + + x += glyph->width; + } + + LOG_INFO("match length: %zu", term->search.match_len); + + wl_subsurface_set_position( + term->wl.search_sub_surface, + term->width - width - margin, term->height - height - margin); + + wl_surface_damage_buffer(term->wl.search_surface, 0, 0, width, height); + wl_surface_attach(term->wl.search_surface, buf->wl_buf, 0, 0); + wl_surface_set_buffer_scale(term->wl.search_surface, scale); + wl_surface_commit(term->wl.search_surface); +} + static void reflow(struct row **new_grid, int new_cols, int new_rows, struct row *const *old_grid, int old_cols, int old_rows) diff --git a/render.h b/render.h index a3620665..c2fa489e 100644 --- a/render.h +++ b/render.h @@ -10,6 +10,7 @@ void render_resize(struct terminal *term, int width, int height); void render_set_title(struct terminal *term, const char *title); void render_refresh(struct terminal *term); +void render_search_box(struct terminal *term); bool render_reload_cursor_theme(struct terminal *term); void render_update_cursor_surface(struct terminal *term); diff --git a/search.c b/search.c index 150bd050..81bdf8f3 100644 --- a/search.c +++ b/search.c @@ -15,82 +15,6 @@ #define max(x, y) ((x) > (y) ? (x) : (y)) - - -static inline pixman_color_t -color_hex_to_pixman_with_alpha(uint32_t color, uint16_t alpha) -{ - int alpha_div = 0xffff / alpha; - return (pixman_color_t){ - .red = ((color >> 16 & 0xff) | (color >> 8 & 0xff00)) / alpha_div, - .green = ((color >> 8 & 0xff) | (color >> 0 & 0xff00)) / alpha_div, - .blue = ((color >> 0 & 0xff) | (color << 8 & 0xff00)) / alpha_div, - .alpha = alpha, - }; -} - -static inline pixman_color_t -color_hex_to_pixman(uint32_t color) -{ - /* Count on the compiler optimizing this */ - return color_hex_to_pixman_with_alpha(color, 0xffff); -} - -/* TODO: move to render.c? */ -static void -render(struct terminal *term) -{ - assert(term->wl.search_sub_surface != NULL); - - /* TODO: at least sway allows the subsurface to extend outside the - * main window. Do we want that? */ - const int scale = term->scale >= 1 ? term->scale : 1; - const int margin = scale * 3; - const int width = 2 * margin + max(20, term->search.len) * term->cell_width; - const int height = 2 * margin + 1 * term->cell_height; - - struct buffer *buf = shm_get_buffer(term->wl.shm, width, height, 1); - - /* Background - yellow on empty/match, red on mismatch */ - pixman_color_t color = color_hex_to_pixman( - term->search.match_len == term->search.len ? 0xffff00 : 0xff0000); - - pixman_image_fill_rectangles( - PIXMAN_OP_SRC, buf->pix, &color, - 1, &(pixman_rectangle16_t){0, 0, width, height}); - - int x = margin; - int y = margin; - pixman_color_t fg = color_hex_to_pixman(0x000000); - - /* Text (what the user entered - *not* match(es)) */ - for (size_t i = 0; i < term->search.len; i++) { - const struct glyph *glyph = font_glyph_for_wc(&term->fonts[0], term->search.buf[i]); - if (glyph == NULL) - continue; - - pixman_image_t *src = pixman_image_create_solid_fill(&fg); - pixman_image_composite32( - PIXMAN_OP_OVER, src, glyph->pix, buf->pix, 0, 0, 0, 0, - x + glyph->x, y + term->fextents.ascent - glyph->y, - glyph->width, glyph->height); - pixman_image_unref(src); - - x += glyph->width; - } - - LOG_INFO("match length: %zu", term->search.match_len); - - wl_subsurface_set_position( - term->wl.search_sub_surface, - term->width - width - margin, term->height - height - margin); - - wl_surface_damage_buffer(term->wl.search_surface, 0, 0, width, height); - wl_surface_attach(term->wl.search_surface, buf->wl_buf, 0, 0); - wl_surface_set_buffer_scale(term->wl.search_surface, scale); - wl_surface_commit(term->wl.search_surface); -} - static void search_cancel_keep_selection(struct terminal *term) { @@ -145,7 +69,7 @@ search_begin(struct terminal *term) assert(term->wl.search_sub_surface == NULL); } - render(term); + render_search_box(term); render_refresh(term); } @@ -167,7 +91,7 @@ search_update(struct terminal *term) term->search.match = (struct coord){-1, -1}; term->search.match_len = 0; selection_cancel(term); - render(term); + render_search_box(term); return; } @@ -305,7 +229,7 @@ search_update(struct terminal *term) term->search.match.col = start_col; term->search.match_len = match_len; - render(term); + render_search_box(term); return; } @@ -317,7 +241,7 @@ search_update(struct terminal *term) term->search.match = (struct coord){-1, -1}; term->search.match_len = 0; selection_cancel(term); - render(term); + render_search_box(term); #undef ROW_DEC } From 1e4b11b0fa73d4fa9e4f124fa04173f92f645d1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 20:22:07 +0200 Subject: [PATCH 30/71] search: don't destroy/recreate search (sub)surface Create the sub-surface once, at startup. Then, instead of destroying it when committing/cancelling a search, unmap it (by attaching a NULL buffer to it). --- main.c | 7 +++++++ search.c | 29 ++--------------------------- 2 files changed, 9 insertions(+), 27 deletions(-) diff --git a/main.c b/main.c index 18e2a19a..a076fb24 100644 --- a/main.c +++ b/main.c @@ -723,6 +723,7 @@ main(int argc, char *const *argv) goto out; } + /* Main window */ term.wl.surface = wl_compositor_create_surface(term.wl.compositor); if (term.wl.surface == NULL) { LOG_ERR("failed to create wayland surface"); @@ -740,6 +741,12 @@ main(int argc, char *const *argv) xdg_toplevel_set_app_id(term.wl.xdg_toplevel, "foot"); term_set_window_title(&term, "foot"); + /* Scrollback search box */ + term.wl.search_surface = wl_compositor_create_surface(term.wl.compositor); + term.wl.search_sub_surface = wl_subcompositor_get_subsurface( + term.wl.sub_compositor, term.wl.search_surface, term.wl.surface); + wl_subsurface_set_desync(term.wl.search_sub_surface); + wl_surface_commit(term.wl.surface); wl_display_roundtrip(term.wl.display); diff --git a/search.c b/search.c index 81bdf8f3..051a32db 100644 --- a/search.c +++ b/search.c @@ -18,14 +18,8 @@ static void search_cancel_keep_selection(struct terminal *term) { - if (term->wl.search_sub_surface != NULL) { - wl_subsurface_destroy(term->wl.search_sub_surface); - term->wl.search_sub_surface = NULL; - } - if (term->wl.search_surface != NULL) { - wl_surface_destroy(term->wl.search_surface); - term->wl.search_surface = NULL; - } + wl_surface_attach(term->wl.search_surface, NULL, 0, 0); + wl_surface_commit(term->wl.search_surface); free(term->search.buf); term->search.buf = NULL; @@ -50,25 +44,6 @@ search_begin(struct terminal *term) term->search.view_followed_offset = term->grid->view == term->grid->offset; term->is_searching = true; - term->wl.search_surface = wl_compositor_create_surface(term->wl.compositor); - if (term->wl.search_surface != NULL) { - term->wl.search_sub_surface = wl_subcompositor_get_subsurface( - term->wl.sub_compositor, term->wl.search_surface, term->wl.surface); - - if (term->wl.search_sub_surface != NULL) { - /* Sub-surface updates may occur without updating the main - * window */ - wl_subsurface_set_desync(term->wl.search_sub_surface); - } - } - - if (term->wl.search_surface == NULL || term->wl.search_sub_surface == NULL) { - LOG_ERR("failed to create sub-surface for search box"); - if (term->wl.search_surface != NULL) - wl_surface_destroy(term->wl.search_surface); - assert(term->wl.search_sub_surface == NULL); - } - render_search_box(term); render_refresh(term); } From 3ec7fbf0a320f2c7a07a4d03d3c5cbe65c2aeb18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 20:23:44 +0200 Subject: [PATCH 31/71] render: remove INFO log (that really was a debug log) --- render.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/render.c b/render.c index 9c01b15f..566db615 100644 --- a/render.c +++ b/render.c @@ -733,8 +733,6 @@ render_search_box(struct terminal *term) x += glyph->width; } - LOG_INFO("match length: %zu", term->search.match_len); - wl_subsurface_set_position( term->wl.search_sub_surface, term->width - width - margin, term->height - height - margin); From 7d920a6d1b968a72f4be9fb89ce4c101e6ba1b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 20:23:55 +0200 Subject: [PATCH 32/71] search: remove INFO log (that really was a debug log) --- search.c | 1 - 1 file changed, 1 deletion(-) diff --git a/search.c b/search.c index 051a32db..f43581f7 100644 --- a/search.c +++ b/search.c @@ -62,7 +62,6 @@ static void search_update(struct terminal *term) { if (term->search.len == 0) { - LOG_INFO("len == 0"); term->search.match = (struct coord){-1, -1}; term->search.match_len = 0; selection_cancel(term); From 6d5f2004294a25307390adaf8182a04a3aa8025a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 20:23:59 +0200 Subject: [PATCH 33/71] search: turn info log to a debug log --- search.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/search.c b/search.c index f43581f7..3d7f8b0a 100644 --- a/search.c +++ b/search.c @@ -319,7 +319,7 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask term->search.buf[term->search.len] = L'\0'; } - LOG_INFO("search: buffer: %S", term->search.buf); + LOG_DBG("search: buffer: %S", term->search.buf); search_update(term); render_refresh(term); } From 013cf61ffbd58f5f706ae6688aea56a14fce6622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 20:39:22 +0200 Subject: [PATCH 34/71] render: add font_baseline() - calculates the y-coordinate for the baseline The old baseline calculation was copy-pasted to a couple of places, and also assumed that the font's height was equal to ascent+descent. While this is typically true, it isn't necessarily so. Now, we assume that height >= ascent+descent, and then position the baseline in "center" (but adjusted for the descent). --- render.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/render.c b/render.c index 566db615..88d747b6 100644 --- a/render.c +++ b/render.c @@ -80,6 +80,25 @@ pixman_color_dim_for_search(pixman_color_t *color) color->blue /= 3; } +static inline int +font_baseline(const struct terminal *term) +{ + assert(term->fextents.ascent >= 0); + assert(term->fextents.descent >= 0); + + int diff = term->fextents.height - (term->fextents.ascent + term->fextents.descent); + assert(diff >= 0); + +#if 0 + LOG_INFO("height=%d, ascent=%d, descent=%d, diff=%d", + term->fextents.height, + term->fextents.ascent, term->fextents.descent, + diff); +#endif + + return term->fextents.height - diff / 2 - term->fextents.descent; +} + static void draw_bar(const struct terminal *term, pixman_image_t *pix, const pixman_color_t *color, int x, int y) From 6e5688d7da131acf025c9a51a0466a9fbf5c7b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 20:41:40 +0200 Subject: [PATCH 35/71] render: draw_bar: don't assume height == ascent+descent Instead, draw a bar that is ascent+descent tall, positioned around the baseline such that it starts at the ascent and ends at the descent. --- render.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/render.c b/render.c index 88d747b6..66c76f23 100644 --- a/render.c +++ b/render.c @@ -103,10 +103,12 @@ static void draw_bar(const struct terminal *term, pixman_image_t *pix, const pixman_color_t *color, int x, int y) { - /* TODO: investigate if this is the best way */ + int baseline = y + font_baseline(term) - term->fextents.ascent; pixman_image_fill_rectangles( PIXMAN_OP_SRC, pix, color, - 1, &(pixman_rectangle16_t){x, y, 1, term->cell_height}); + 1, &(pixman_rectangle16_t){ + x, baseline, + 1, term->fextents.ascent + term->fextents.descent}); } static void From 1f98c8f7879a4066aa089b520084f77568a247d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 20:42:45 +0200 Subject: [PATCH 36/71] render: draw_underline: use font_baseline() for baseline calculation --- render.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render.c b/render.c index 66c76f23..34367cdd 100644 --- a/render.c +++ b/render.c @@ -116,7 +116,7 @@ draw_underline(const struct terminal *term, pixman_image_t *pix, const struct font *font, const pixman_color_t *color, int x, int y, int cols) { - int baseline = y + term->fextents.height - term->fextents.descent; + int baseline = y + font_baseline(term); int width = font->underline.thickness; int y_under = baseline - font->underline.position - width / 2; From bffa1f05ac0a5234ee21e1a46a363af19ddee367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 20:42:57 +0200 Subject: [PATCH 37/71] render: draw_strikeout: use font_baseline() for baseline calculation --- render.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render.c b/render.c index 34367cdd..eaba6b03 100644 --- a/render.c +++ b/render.c @@ -130,7 +130,7 @@ draw_strikeout(const struct terminal *term, pixman_image_t *pix, const struct font *font, const pixman_color_t *color, int x, int y, int cols) { - int baseline = y + term->fextents.height - term->fextents.descent; + int baseline = y + font_baseline(term); int width = font->strikeout.thickness; int y_strike = baseline - font->strikeout.position - width / 2; From 27da0d7f287f49a477e5901e979230a2858689ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 21:02:35 +0200 Subject: [PATCH 38/71] term: add a 'cursor' (position) to the search state --- terminal.h | 1 + 1 file changed, 1 insertion(+) diff --git a/terminal.h b/terminal.h index 948a8f11..d81ff76d 100644 --- a/terminal.h +++ b/terminal.h @@ -349,6 +349,7 @@ struct terminal { wchar_t *buf; size_t len; size_t sz; + size_t cursor; int original_view; bool view_followed_offset; From 2ca7400cc6ed922582adcd6a2455b6608a5a5f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 21:03:00 +0200 Subject: [PATCH 39/71] render: render_search_box: draw cursor as a bar --- render.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/render.c b/render.c index eaba6b03..0be0d3d4 100644 --- a/render.c +++ b/render.c @@ -740,6 +740,9 @@ render_search_box(struct terminal *term) /* Text (what the user entered - *not* match(es)) */ for (size_t i = 0; i < term->search.len; i++) { + if (i == term->search.cursor) + draw_bar(term, buf->pix, &fg, x, y); + const struct glyph *glyph = font_glyph_for_wc(&term->fonts[0], term->search.buf[i]); if (glyph == NULL) continue; @@ -754,6 +757,9 @@ render_search_box(struct terminal *term) x += glyph->width; } + if (term->search.cursor >= term->search.len) + draw_bar(term, buf->pix, &fg, x, y); + wl_subsurface_set_position( term->wl.search_sub_surface, term->width - width - margin, term->height - height - margin); From 6003b878138315013458afbc4f3675e7f542eb55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 21:03:16 +0200 Subject: [PATCH 40/71] search: search_cancel: reset cursor position --- search.c | 1 + 1 file changed, 1 insertion(+) diff --git a/search.c b/search.c index 3d7f8b0a..ffa50c0e 100644 --- a/search.c +++ b/search.c @@ -25,6 +25,7 @@ search_cancel_keep_selection(struct terminal *term) term->search.buf = NULL; term->search.len = 0; term->search.sz = 0; + term->search.cursor = 0; term->search.match = (struct coord){-1, -1}; term->search.match_len = 0; term->is_searching = false; From 10649178f8f42d38e4e9d9760eeda4d11dfdadda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 21:03:46 +0200 Subject: [PATCH 41/71] search: insert new characters at cursor, not at the end of the buffer --- search.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/search.c b/search.c index ffa50c0e..6326aded 100644 --- a/search.c +++ b/search.c @@ -312,11 +312,16 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask assert(term->search.len + wchars < term->search.sz); + memmove(&term->search.buf[term->search.cursor + wchars], + &term->search.buf[term->search.cursor], + (term->search.len - term->search.cursor) * sizeof(wchar_t)); + memset(&ps, 0, sizeof(ps)); - mbsnrtowcs(&term->search.buf[term->search.len], &src, count, - term->search.sz - term->search.len - 1, &ps); + mbsnrtowcs(&term->search.buf[term->search.cursor], &src, count, + wchars, &ps); term->search.len += wchars; + term->search.cursor += wchars; term->search.buf[term->search.len] = L'\0'; } From 4d0ea114542312a71e6f47027b65618711198182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 21:05:18 +0200 Subject: [PATCH 42/71] search: add basic cursor navigation support --- search.c | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/search.c b/search.c index 6326aded..e453079c 100644 --- a/search.c +++ b/search.c @@ -226,7 +226,7 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask LOG_DBG("search: input: sym=%d/0x%x, mods=0x%08x", sym, sym, mods); const xkb_mod_mask_t ctrl = 1 << term->kbd.mod_ctrl; - //const xkb_mod_mask_t alt = 1 << term->kbd.mod_alt; + const xkb_mod_mask_t alt = 1 << term->kbd.mod_alt; //const xkb_mod_mask_t shift = 1 << term->kbd.mod_shift; //const xkb_mod_mask_t meta = 1 << term->kbd.mod_meta; @@ -270,9 +270,52 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask } } + else if (mods == 0 && sym == XKB_KEY_Left) { + if (term->search.cursor > 0) + term->search.cursor--; + } + + else if (mods == 0 && sym == XKB_KEY_Right) { + if (term->search.cursor < term->search.len) + term->search.cursor++; + } + + else if ((mods == 0 && sym == XKB_KEY_Home) || + (mods == ctrl && sym == XKB_KEY_a)) + term->search.cursor = 0; + + else if ((mods == 0 && sym == XKB_KEY_End) || + (mods == ctrl && sym == XKB_KEY_e)) + term->search.cursor = term->search.len; + else if (mods == 0 && sym == XKB_KEY_BackSpace) { - if (term->search.len > 0) + if (term->search.cursor > 0) { + memmove( + &term->search.buf[term->search.cursor - 1], + &term->search.buf[term->search.cursor], + (term->search.len - term->search.cursor) * sizeof(wchar_t)); + term->search.cursor--; term->search.buf[--term->search.len] = L'\0'; + } + } + + else if ((mods == alt || mods == ctrl) && sym == XKB_KEY_BackSpace) { + /* TODO: delete word backward */ + } + + else if ((mods == alt && sym == XKB_KEY_d) || + (mods == ctrl && sym == XKB_KEY_Delete)) { + /* TODO: delete word forward */ + } + + else if (mods == 0 && sym == XKB_KEY_Delete) { + if (term->search.cursor < term->search.len) { + memmove( + &term->search.buf[term->search.cursor], + &term->search.buf[term->search.cursor + 1], + (term->search.len - term->search.cursor - 1) * sizeof(wchar_t)); + term->search.buf[--term->search.len] = L'\0'; + } } else { From 4c9e7d0c19b4b07842cab7cf79e136bd4f11d059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 21:09:49 +0200 Subject: [PATCH 43/71] search: call render_search_box() from search_inptu() --- search.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/search.c b/search.c index e453079c..4812b73f 100644 --- a/search.c +++ b/search.c @@ -66,7 +66,6 @@ search_update(struct terminal *term) term->search.match = (struct coord){-1, -1}; term->search.match_len = 0; selection_cancel(term); - render_search_box(term); return; } @@ -204,7 +203,6 @@ search_update(struct terminal *term) term->search.match.col = start_col; term->search.match_len = match_len; - render_search_box(term); return; } @@ -216,7 +214,6 @@ search_update(struct terminal *term) term->search.match = (struct coord){-1, -1}; term->search.match_len = 0; selection_cancel(term); - render_search_box(term); #undef ROW_DEC } @@ -371,4 +368,5 @@ 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); + render_search_box(term); } From 9c6ece747ed328b0e922597ec52646c00bb68c8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 21:10:13 +0200 Subject: [PATCH 44/71] search: for now, reset search match state whenever the buffer changes --- search.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/search.c b/search.c index 4812b73f..7804eaa0 100644 --- a/search.c +++ b/search.c @@ -293,6 +293,9 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask (term->search.len - term->search.cursor) * sizeof(wchar_t)); term->search.cursor--; term->search.buf[--term->search.len] = L'\0'; + + term->search.match = (struct coord){-1, -1}; + term->search.match_len = 0; } } @@ -312,6 +315,9 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask &term->search.buf[term->search.cursor + 1], (term->search.len - term->search.cursor - 1) * sizeof(wchar_t)); term->search.buf[--term->search.len] = L'\0'; + + term->search.match = (struct coord){-1, -1}; + term->search.match_len = 0; } } @@ -363,6 +369,9 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask term->search.len += wchars; term->search.cursor += wchars; term->search.buf[term->search.len] = L'\0'; + + term->search.match = (struct coord){-1, -1}; + term->search.match_len = 0; } LOG_DBG("search: buffer: %S", term->search.buf); From db291573b78aaef20e87f42b217bdddae8f0f732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 21:16:25 +0200 Subject: [PATCH 45/71] Revert "search: for now, reset search match state whenever the buffer changes" This reverts commit 9c6ece747ed328b0e922597ec52646c00bb68c8c. --- search.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/search.c b/search.c index 7804eaa0..4812b73f 100644 --- a/search.c +++ b/search.c @@ -293,9 +293,6 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask (term->search.len - term->search.cursor) * sizeof(wchar_t)); term->search.cursor--; term->search.buf[--term->search.len] = L'\0'; - - term->search.match = (struct coord){-1, -1}; - term->search.match_len = 0; } } @@ -315,9 +312,6 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask &term->search.buf[term->search.cursor + 1], (term->search.len - term->search.cursor - 1) * sizeof(wchar_t)); term->search.buf[--term->search.len] = L'\0'; - - term->search.match = (struct coord){-1, -1}; - term->search.match_len = 0; } } @@ -369,9 +363,6 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask term->search.len += wchars; term->search.cursor += wchars; term->search.buf[term->search.len] = L'\0'; - - term->search.match = (struct coord){-1, -1}; - term->search.match_len = 0; } LOG_DBG("search: buffer: %S", term->search.buf); From 425d221e02e2ef5b61e2487647a692b4cf8b9e95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 22:40:44 +0200 Subject: [PATCH 46/71] README: try markup --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 23afee5b..008f85fd 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ is **not** possible to define new key bindings. ### Keyboard -* `shift+page up/down` - scroll up/down in history +* shift+page up/down` - scroll up/down in history * `ctrl+shift+c` - copy selected text to the _clipboard_ * `ctrl+shift+v` - paste from _clipboard_ * `ctrl+shift+r` - start a scrollback search From 03534a327cd862233f919c2fe7c04c2ff3267ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 22:44:06 +0200 Subject: [PATCH 47/71] README: use for keyboard key combos --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 008f85fd..437f67a1 100644 --- a/README.md +++ b/README.md @@ -51,29 +51,29 @@ is **not** possible to define new key bindings. ### Keyboard -* shift+page up/down` - scroll up/down in history -* `ctrl+shift+c` - copy selected text to the _clipboard_ -* `ctrl+shift+v` - paste from _clipboard_ -* `ctrl+shift+r` - start a scrollback search +* shift+page up/page down - scroll up/down in history +* ctrl+shift+c - copy selected text to the _clipboard_ +* ctrl+shift+v - paste from _clipboard_ +* ctrl+shift+r, - start a scrollback search While doing a scrollback search, the following shortcuts are available: -* `ctrl+r` - search for next match -* `esc` - cancel the search -* `ctrl+g` - cancel the search (same as `esc`) -* `return` - finish the search and put the current match to the primary selection +* ctrl+r - search for next match +* escape - cancel the search +* ctrl+g - cancel the search (same as `esc`) +* return - finish the search and put the current match to the primary selection ### Mouse -* `left` - single-click: drag to select; when released, the selected +* left - **single-click**: drag to select; when released, the selected text is copied to the _primary_ selection. Note that this feature is normally disabled whenever the client has enabled mouse tracking, but can be forced by holding `shift`. -* `left` - double-click: selects the _word_ (separated by spaces, +* left - **double-click**: selects the _word_ (separated by spaces, period, comma, parenthesis etc) under the pointer. Hold `ctrl` to select everything under the pointer up to, and until, the next space characters. -* `left` - triple-click: selects the entire row -* `middle` - paste from _primary_ selection +* left - **triple-click**: selects the entire row +* middle - paste from _primary_ selection From 63da83a9cc253683eb74f50841a31f939a88dfcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 22:46:26 +0200 Subject: [PATCH 48/71] README: test indented paragraph in list --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 437f67a1..5df7b7a7 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,9 @@ is **not** possible to define new key bindings. ### Keyboard -* shift+page up/page down - scroll up/down in history +* shift+page up/page down + + scroll up/down in history * ctrl+shift+c - copy selected text to the _clipboard_ * ctrl+shift+v - paste from _clipboard_ * ctrl+shift+r, - start a scrollback search From ff7566c46d7debe9c96e6cb8f6b9bedf62c158f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 22:48:25 +0200 Subject: [PATCH 49/71] README: use poor man's markdown version of definition lists --- README.md | 65 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 5df7b7a7..3966fde0 100644 --- a/README.md +++ b/README.md @@ -53,29 +53,58 @@ is **not** possible to define new key bindings. * shift+page up/page down - scroll up/down in history -* ctrl+shift+c - copy selected text to the _clipboard_ -* ctrl+shift+v - paste from _clipboard_ -* ctrl+shift+r, - start a scrollback search + scroll up/down in history + +* ctrl+shift+c + + copy selected text to the _clipboard_ + +* ctrl+shift+v + + paste from _clipboard_ + +* ctrl+shift+r + + start a scrollback search While doing a scrollback search, the following shortcuts are available: -* ctrl+r - search for next match -* escape - cancel the search -* ctrl+g - cancel the search (same as `esc`) -* return - finish the search and put the current match to the primary selection +* ctrl+r + search for next match + +* escape + + cancel the search + +* ctrl+g + + cancel the search (same as `esc`) + +* return + + finish the search and put the current match to the primary selection ### Mouse -* left - **single-click**: drag to select; when released, the selected - text is copied to the _primary_ selection. Note that this feature is - normally disabled whenever the client has enabled mouse tracking, - but can be forced by holding `shift`. -* left - **double-click**: selects the _word_ (separated by spaces, - period, comma, parenthesis etc) under the pointer. Hold `ctrl` to - select everything under the pointer up to, and until, the next space - characters. -* left - **triple-click**: selects the entire row -* middle - paste from _primary_ selection +* left - **single-click** + + drag to select; when released, the selected text is copied to the + _primary_ selection. Note that this feature is normally disabled + whenever the client has enabled mouse tracking, but can be forced by + holding `shift`. + +* left - **double-click** + + selects the _word_ (separated by spaces, period, comma, parenthesis + etc) under the pointer. Hold `ctrl` to select everything under the + pointer up to, and until, the next space characters. + +* left - **triple-click** + + selects the entire row + +* middle + + paste from _primary_ selection From 5c47fac893781c1dc538e94b90bfcd025bc06fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 22:49:33 +0200 Subject: [PATCH 50/71] README: upper case initial letters --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 3966fde0..2a4f8822 100644 --- a/README.md +++ b/README.md @@ -53,58 +53,58 @@ is **not** possible to define new key bindings. * shift+page up/page down - scroll up/down in history + Scroll up/down in history * ctrl+shift+c - copy selected text to the _clipboard_ + Copy selected text to the _clipboard_ * ctrl+shift+v - paste from _clipboard_ + Paste from _clipboard_ * ctrl+shift+r - start a scrollback search + Start a scrollback search While doing a scrollback search, the following shortcuts are available: * ctrl+r - search for next match + Search for next match * escape - cancel the search + Cancel the search * ctrl+g - cancel the search (same as `esc`) + Cancel the search (same as `esc`) * return - finish the search and put the current match to the primary selection + Finish the search and put the current match to the primary selection ### Mouse * left - **single-click** - drag to select; when released, the selected text is copied to the + Drag to select; when released, the selected text is copied to the _primary_ selection. Note that this feature is normally disabled whenever the client has enabled mouse tracking, but can be forced by holding `shift`. * left - **double-click** - selects the _word_ (separated by spaces, period, comma, parenthesis + Selects the _word_ (separated by spaces, period, comma, parenthesis etc) under the pointer. Hold `ctrl` to select everything under the pointer up to, and until, the next space characters. * left - **triple-click** - selects the entire row + Selects the entire row * middle - paste from _primary_ selection + Paste from _primary_ selection From c8494e7efa016c3fd9056664e0534e6249265782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 22:50:35 +0200 Subject: [PATCH 51/71] README: use for remaining keyboard key combos --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2a4f8822..10371158 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ available: * ctrl+g - Cancel the search (same as `esc`) + Cancel the search (same as escape) * return @@ -93,13 +93,13 @@ available: Drag to select; when released, the selected text is copied to the _primary_ selection. Note that this feature is normally disabled whenever the client has enabled mouse tracking, but can be forced by - holding `shift`. + holding shift. * left - **double-click** Selects the _word_ (separated by spaces, period, comma, parenthesis - etc) under the pointer. Hold `ctrl` to select everything under the - pointer up to, and until, the next space characters. + etc) under the pointer. Hold ctrl to select everything + under the pointer up to, and until, the next space characters. * left - **triple-click** From a6eec9a6a6dd70888ed6d03dbf3d2225f766450b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 22:55:49 +0200 Subject: [PATCH 52/71] README: add a feature list --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 10371158..19998028 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,26 @@ **foot** is a fast Wayland terminal emulator. +## Features + +* Fast (**TODO** insert benchmark results here) +* Wayland native +* DE agnostic +* User configurable font fallback +* Scrollback search +* Color emoji support + + +## Non-features + +This is a non-exhaustive list of things some people might consider +being important features (i.e. _"must-haves"_), that are unlikely to +ever be supported by foot. + +* Tabs +* Multiple windows + + ## Requirements ### Running From da9b2d12e77263c6f280973551715a8b8a6513f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 23:00:49 +0200 Subject: [PATCH 53/71] README: re-write font section --- README.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 19998028..27ab6f5b 100644 --- a/README.md +++ b/README.md @@ -47,17 +47,18 @@ In addition to the dev variant of the packages above, you need: ## Fonts -**foot** supports all fonts that can be loaded by freetype, including -**bitmap** fonts and **color emoji** fonts. +**foot** supports all fonts that can be loaded by _freetype_, +including **bitmap** fonts and **color emoji** fonts. -Foot uses its own font fallback mechanism, rather than relying on -fontconfig's fallback. This is because fontconfig is quite bad at -selecting fallback fonts suitable for a terminal (i.e. monospaced -fonts). +Foot uses _fontconfig_ to locate and configure the font(s) to +use. Since fontconfig's fallback mechanism is imperfect, especially +for monospace fonts (it doesn't prefer monospace fonts even though the +requested font is one), foot allows you, the user, to configure the +fallback fonts to use. -Instead, foot allows you to specify a font fallback list, where _each_ -font can be configured independently (for example, you can configure -the size for each font individually). +This also means you can configure _each_ fallback font individually; +you want _that_ fallback font to use _this_ size, and you want that +_other_ fallback font to be _italic_? No problem! If a glyph cannot be found in _any_ of the user configured fallback fonts, _then_ fontconfig's list is used. From 72d918da922c00acfbbb79e4495d5c52fd3ef2e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 23:02:18 +0200 Subject: [PATCH 54/71] README: more emphasis --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 27ab6f5b..a77dd271 100644 --- a/README.md +++ b/README.md @@ -112,9 +112,9 @@ available: * left - **single-click** Drag to select; when released, the selected text is copied to the - _primary_ selection. Note that this feature is normally disabled - whenever the client has enabled mouse tracking, but can be forced by - holding shift. + _primary_ selection. Note that this feature is normally **disabled** + whenever the client has enabled _mouse tracking_, but can be forced + by holding shift. * left - **double-click** From 9bdc3a84387d877af45bc4ad1dd902f9cd29f199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 23:05:19 +0200 Subject: [PATCH 55/71] README: add index --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index a77dd271..98058003 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,18 @@ **foot** is a fast Wayland terminal emulator. +## Index + +1. [Features](#features) +1. [Non-features](#non-features) +1. [Requirements](#requirements) + 1. [Running](#running) + 1. [Building](#building) +1. [Fonts](#fonts) +1. [Shortcuts](#shortcuts) + 1. [Keyboard](#keyboard) + 1. [Mouse](#mouse) + ## Features From f43fb5f13f171ef778f635d14f172af3e6dba93b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 23:05:38 +0200 Subject: [PATCH 56/71] README: hopefully fix nested lists --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 98058003..c79c9525 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,12 @@ 1. [Features](#features) 1. [Non-features](#non-features) 1. [Requirements](#requirements) - 1. [Running](#running) - 1. [Building](#building) + 1. [Running](#running) + 1. [Building](#building) 1. [Fonts](#fonts) 1. [Shortcuts](#shortcuts) - 1. [Keyboard](#keyboard) - 1. [Mouse](#mouse) + 1. [Keyboard](#keyboard) + 1. [Mouse](#mouse) ## Features From 8fa6b5fa2dc2d31700ce92391cfa7e095296f562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 23:11:11 +0200 Subject: [PATCH 57/71] README: add an 'installation' section --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index c79c9525..dc08ce83 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ 1. [Requirements](#requirements) 1. [Running](#running) 1. [Building](#building) +1. [Installing](#installing) + 1. [Arch Linux](#arch-linux) + 1. [Other](#other) 1. [Fonts](#fonts) 1. [Shortcuts](#shortcuts) 1. [Keyboard](#keyboard) @@ -57,6 +60,24 @@ In addition to the dev variant of the packages above, you need: * scdoc +## Installing + +### Arch Linux + +Use [makepkg](https://wiki.archlinux.org/index.php/Makepkg) to build +the bundled `PKGBUILD`. + + +### Other + +Foot uses _meson_. If you are unfamiliar with it, the official +[tutorial](https://mesonbuild.com/Tutorial.html) might be a good +starting point. + +I also recommend taking a look at that bundled Arch `PKGBUILD` file, +to see how it builds foot. + + ## Fonts **foot** supports all fonts that can be loaded by _freetype_, From 0c0deef4f0e02613270688478cd2b0b30fc3d110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 23:12:39 +0200 Subject: [PATCH 58/71] README: re-arrange: put requirements+installation last --- README.md | 88 +++++++++++++++++++++++++++---------------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index dc08ce83..c3060ee4 100644 --- a/README.md +++ b/README.md @@ -6,16 +6,16 @@ 1. [Features](#features) 1. [Non-features](#non-features) +1. [Fonts](#fonts) +1. [Shortcuts](#shortcuts) + 1. [Keyboard](#keyboard) + 1. [Mouse](#mouse) 1. [Requirements](#requirements) 1. [Running](#running) 1. [Building](#building) 1. [Installing](#installing) 1. [Arch Linux](#arch-linux) 1. [Other](#other) -1. [Fonts](#fonts) -1. [Shortcuts](#shortcuts) - 1. [Keyboard](#keyboard) - 1. [Mouse](#mouse) ## Features @@ -38,46 +38,6 @@ ever be supported by foot. * Multiple windows -## Requirements - -### Running - -* fontconfig -* freetype -* pixman -* wayland (_client_ and _cursor_ libraries) -* xkbcommon - - -### Building - -In addition to the dev variant of the packages above, you need: - -* meson -* ninja -* wayland protocols -* ncurses -* scdoc - - -## Installing - -### Arch Linux - -Use [makepkg](https://wiki.archlinux.org/index.php/Makepkg) to build -the bundled `PKGBUILD`. - - -### Other - -Foot uses _meson_. If you are unfamiliar with it, the official -[tutorial](https://mesonbuild.com/Tutorial.html) might be a good -starting point. - -I also recommend taking a look at that bundled Arch `PKGBUILD` file, -to see how it builds foot. - - ## Fonts **foot** supports all fonts that can be loaded by _freetype_, @@ -162,3 +122,43 @@ available: * middle Paste from _primary_ selection + + +## Requirements + +### Running + +* fontconfig +* freetype +* pixman +* wayland (_client_ and _cursor_ libraries) +* xkbcommon + + +### Building + +In addition to the dev variant of the packages above, you need: + +* meson +* ninja +* wayland protocols +* ncurses +* scdoc + + +## Installing + +### Arch Linux + +Use [makepkg](https://wiki.archlinux.org/index.php/Makepkg) to build +the bundled `PKGBUILD`. + + +### Other + +Foot uses _meson_. If you are unfamiliar with it, the official +[tutorial](https://mesonbuild.com/Tutorial.html) might be a good +starting point. + +I also recommend taking a look at that bundled Arch `PKGBUILD` file, +to see how it builds foot. From 0c8e5d39b06c7c6b6628b63cae75c940054ecd73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 23:13:56 +0200 Subject: [PATCH 59/71] README: clarify _how_ and _where_ makepkg is supposed to be run --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c3060ee4..c3a9adb7 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,8 @@ In addition to the dev variant of the packages above, you need: ### Arch Linux Use [makepkg](https://wiki.archlinux.org/index.php/Makepkg) to build -the bundled `PKGBUILD`. +the bundled `PKGBUILD` (just run `makepkg` in the source root +directory).. ### Other From f2fda437e78488037a171ca3b1d8bf29dcc528c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Aug 2019 23:15:13 +0200 Subject: [PATCH 60/71] README: mention that the PKGBUILD requires a running wayland session --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index c3a9adb7..923bb34d 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,10 @@ Use [makepkg](https://wiki.archlinux.org/index.php/Makepkg) to build the bundled `PKGBUILD` (just run `makepkg` in the source root directory).. +Note that it will do a profiling-guided build, and that this requires +a running wayland session since it needs to run an intermediate build +of foot. + ### Other From 47da5b4086d2081737ec2264f7b5b754c0166a3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 30 Aug 2019 17:55:45 +0200 Subject: [PATCH 61/71] main: request server-side decorations --- main.c | 41 +++++++++++++++++++++++++++++++++++++++++ meson.build | 1 + terminal.h | 3 +++ 3 files changed, 45 insertions(+) diff --git a/main.c b/main.c index a076fb24..c9a7b64c 100644 --- a/main.c +++ b/main.c @@ -22,6 +22,7 @@ #include #include +#include #define LOG_MODULE "main" #define LOG_ENABLE_DBG 0 @@ -215,6 +216,10 @@ handle_global(void *data, struct wl_registry *registry, xdg_wm_base_add_listener(term->wl.shell, &xdg_wm_base_listener, term); } + else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) + term->wl.xdg_decoration_manager = wl_registry_bind( + term->wl.registry, name, &zxdg_decoration_manager_v1_interface, 1); + else if (strcmp(interface, wl_seat_interface.name) == 0) { term->wl.seat = wl_registry_bind( term->wl.registry, name, &wl_seat_interface, 5); @@ -340,6 +345,30 @@ static const struct xdg_surface_listener xdg_surface_listener = { .configure = &xdg_surface_configure, }; +static void +xdg_toplevel_decoration_configure(void *data, + struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, + uint32_t mode) +{ + switch (mode) { + case ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE: + LOG_ERR("unimplemented: client-side decorations"); + break; + + case ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE: + LOG_DBG("using server-side decorations"); + break; + + default: + LOG_ERR("unimplemented: unknown XDG toplevel decoration mode: %u", mode); + break; + } +} + +static const struct zxdg_toplevel_decoration_v1_listener xdg_toplevel_decoration_listener = { + .configure = &xdg_toplevel_decoration_configure, +}; + static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { @@ -741,6 +770,14 @@ main(int argc, char *const *argv) xdg_toplevel_set_app_id(term.wl.xdg_toplevel, "foot"); term_set_window_title(&term, "foot"); + /* Request server-side decorations */ + term.wl.xdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( + term.wl.xdg_decoration_manager, term.wl.xdg_toplevel); + zxdg_toplevel_decoration_v1_set_mode( + term.wl.xdg_toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + zxdg_toplevel_decoration_v1_add_listener( + term.wl.xdg_toplevel_decoration, &xdg_toplevel_decoration_listener, &term); + /* Scrollback search box */ term.wl.search_surface = wl_compositor_create_surface(term.wl.compositor); term.wl.search_sub_surface = wl_subcompositor_get_subsurface( @@ -1061,6 +1098,10 @@ out: wl_surface_destroy(term.wl.search_surface); if (term.render.frame_callback != NULL) wl_callback_destroy(term.render.frame_callback); + if (term.wl.xdg_toplevel_decoration != NULL) + zxdg_toplevel_decoration_v1_destroy(term.wl.xdg_toplevel_decoration); + if (term.wl.xdg_decoration_manager != NULL) + zxdg_decoration_manager_v1_destroy(term.wl.xdg_decoration_manager); if (term.wl.xdg_toplevel != NULL) xdg_toplevel_destroy(term.wl.xdg_toplevel); if (term.wl.xdg_surface != NULL) diff --git a/meson.build b/meson.build index 01a85cc8..632da62d 100644 --- a/meson.build +++ b/meson.build @@ -60,6 +60,7 @@ wl_proto_headers = [] wl_proto_src = [] foreach prot : [ wayland_protocols_datadir + '/stable/xdg-shell/xdg-shell.xml', + wayland_protocols_datadir + '/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml', wayland_protocols_datadir + '/unstable/xdg-output/xdg-output-unstable-v1.xml', wayland_protocols_datadir + '/unstable/primary-selection/primary-selection-unstable-v1.xml', ] diff --git a/terminal.h b/terminal.h index d81ff76d..f5eabad1 100644 --- a/terminal.h +++ b/terminal.h @@ -72,6 +72,9 @@ struct wayland { struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; + struct zxdg_decoration_manager_v1 *xdg_decoration_manager; + struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration; + /* Scrollback search */ struct wl_surface *search_surface; struct wl_subsurface *search_sub_surface; From fb018eb64e78fd62cadbb42c26d5244ae0f8bb5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 30 Aug 2019 17:56:23 +0200 Subject: [PATCH 62/71] main: render_resize() scales the width and height Thus, when we call render_resize() with *old* with/height values, we must scale them back to their original values. This fixes an issue where, for example, moving a window between outputs with different scales caused the window to keep growing. --- main.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/main.c b/main.c index c9a7b64c..b6c90fe6 100644 --- a/main.c +++ b/main.c @@ -130,8 +130,10 @@ output_scale(void *data, struct wl_output *wl_output, int32_t factor) { struct monitor *mon = data; mon->scale = factor; + + int old_scale = mon->term->scale >= 1 ? mon->term->scale : 1; render_reload_cursor_theme(mon->term); - render_resize(mon->term, mon->term->width, mon->term->height); + render_resize(mon->term, mon->term->width / old_scale, mon->term->height / old_scale); } static const struct wl_output_listener output_listener = { @@ -271,8 +273,9 @@ surface_enter(void *data, struct wl_surface *wl_surface, tll_push_back(term->wl.on_outputs, &it->item); /* Resize, since scale-to-use may have changed */ + int scale = term->scale >= 1 ? term->scale : 1; render_reload_cursor_theme(term); - render_resize(term, term->width, term->height); + render_resize(term, term->width / scale, term->height / scale); return; } } @@ -293,8 +296,9 @@ surface_leave(void *data, struct wl_surface *wl_surface, tll_remove(term->wl.on_outputs, it); /* Resize, since scale-to-use may have changed */ + int scale = term->scale >= 1 ? term->scale : 1; render_reload_cursor_theme(term); - render_resize(term, term->width, term->height); + render_resize(term, term->width / scale, term->height / scale); return; } From 9f7ea6292e0fc114c5a3f8b5d0a77cb23f61ce89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 30 Aug 2019 17:57:46 +0200 Subject: [PATCH 63/71] main: use a timer FD to delay render refresh after client data This ensures we never wait *longer* than 1ms (previously, we could end up doing multiple polls, each with a timeout value of 1ms - thereby potentially delaying the refresh indefinitely). --- main.c | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/main.c b/main.c index b6c90fe6..afa213cb 100644 --- a/main.c +++ b/main.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -888,8 +889,9 @@ main(int argc, char *const *argv) } } + bool timeout_is_armed = false; + int timeout_fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK | TFD_CLOEXEC); - int timeout_ms = -1; while (true) { struct pollfd fds[] = { {.fd = wl_display_get_fd(term.wl.display), .events = POLLIN}, @@ -897,10 +899,11 @@ main(int argc, char *const *argv) {.fd = term.kbd.repeat.fd, .events = POLLIN}, {.fd = term.flash.fd, .events = POLLIN}, {.fd = term.blink.fd, .events = POLLIN}, + {.fd = timeout_fd, .events = POLLIN}, }; wl_display_flush(term.wl.display); - int pret = poll(fds, sizeof(fds) / sizeof(fds[0]), timeout_ms); + int pret = poll(fds, sizeof(fds) / sizeof(fds[0]), -1); if (pret == -1) { if (errno == EINTR) @@ -910,13 +913,19 @@ main(int argc, char *const *argv) break; } - if (pret == 0 || (timeout_ms != -1 && !(fds[1].revents & POLLIN))) { - /* Delayed rendering */ - render_refresh(&term); - } + if (fds[5].revents & POLLIN) { + assert(timeout_is_armed); - /* Reset poll timeout to infinity */ - timeout_ms = -1; + uint64_t unused; + ssize_t ret = read(timeout_fd, &unused, sizeof(unused)); + + if (ret < 0 && errno != EAGAIN) + LOG_ERRNO("failed to read timeout timer"); + else if (ret > 0) { + timeout_is_armed = false; + render_refresh(&term); + } + } if (fds[0].revents & POLLIN) { wl_display_dispatch(term.wl.display); @@ -968,7 +977,10 @@ main(int argc, char *const *argv) * ourselves we just received keyboard input, and in * this case *not* delay rendering? */ - timeout_ms = 1; + if (!timeout_is_armed) { + timerfd_settime(timeout_fd, 0, &(struct itimerspec){.it_value = {.tv_nsec = 1000000}}, NULL); + timeout_is_armed = true; + } } } @@ -1039,9 +1051,10 @@ main(int argc, char *const *argv) render_refresh(&term); } } - } + close(timeout_fd); + out: mtx_lock(&term.render.workers.lock); assert(tll_length(term.render.workers.queue) == 0); From fcb0e050094f9d864afd958537e11cedb13e40dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 30 Aug 2019 19:35:47 +0200 Subject: [PATCH 64/71] render: search box: fix glyph spacing We're using the same font as in the terminal, so use the same glyph spacing. --- render.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render.c b/render.c index 0be0d3d4..1611b972 100644 --- a/render.c +++ b/render.c @@ -754,7 +754,7 @@ render_search_box(struct terminal *term) glyph->width, glyph->height); pixman_image_unref(src); - x += glyph->width; + x += term->cell_width; } if (term->search.cursor >= term->search.len) From 61318d068e486eab77b833ed2bd2e4036ee54b25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 30 Aug 2019 19:36:37 +0200 Subject: [PATCH 65/71] search: utility functions distance_{next,prev}_word() These functions calculate the distance from the current cursor to the next/previous word. --- search.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/search.c b/search.c index 4812b73f..5c9c16e5 100644 --- a/search.c +++ b/search.c @@ -1,6 +1,7 @@ #include "search.h" #include +#include #include #include @@ -217,6 +218,61 @@ search_update(struct terminal *term) #undef ROW_DEC } +static size_t +distance_next_word(const struct terminal *term) +{ + size_t cursor = term->search.cursor; + + /* First eat non-whitespace. This is the word we're skipping past */ + while (cursor < term->search.len) { + if (iswspace(term->search.buf[cursor++])) + break; + } + + assert(cursor == term->search.len || iswspace(term->search.buf[cursor - 1])); + + /* Now skip past whitespace, so that we end up at the beginning of + * the next word */ + while (cursor < term->search.len) { + if (!iswspace(term->search.buf[cursor++])) + break; + } + + LOG_INFO("cursor = %zu, iswspace() = %d", cursor, iswspace(term->search.buf[cursor - 1])); + assert(cursor == term->search.len || !iswspace(term->search.buf[cursor - 1])); + + if (cursor < term->search.len && !iswspace(term->search.buf[cursor])) + cursor--; + + return cursor - term->search.cursor; +} + +static size_t +distance_prev_word(const struct terminal *term) +{ + int cursor = term->search.cursor; + + /* First, eat whitespace prefix */ + while (cursor > 0) { + if (!iswspace(term->search.buf[--cursor])) + break; + } + + assert(cursor == 0 || !iswspace(term->search.buf[cursor])); + + /* Now eat non-whitespace. This is the word we're skipping past */ + while (cursor > 0) { + if (iswspace(term->search.buf[--cursor])) + break; + } + + assert(cursor == 0 || iswspace(term->search.buf[cursor])); + if (iswspace(term->search.buf[cursor])) + cursor++; + + return term->search.cursor - cursor; +} + void search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask_t mods) { From 64460c5abe5db44a344893687e1f5cf5663452c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 30 Aug 2019 19:37:06 +0200 Subject: [PATCH 66/71] search: implement delete word backward/forward --- search.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/search.c b/search.c index 5c9c16e5..89204350 100644 --- a/search.c +++ b/search.c @@ -353,12 +353,28 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask } else if ((mods == alt || mods == ctrl) && sym == XKB_KEY_BackSpace) { - /* TODO: delete word backward */ + size_t diff = distance_prev_word(term); + size_t old_cursor = term->search.cursor; + size_t new_cursor = old_cursor - diff; + + memmove(&term->search.buf[new_cursor], + &term->search.buf[old_cursor], + (term->search.len - old_cursor) * sizeof(wchar_t)); + + term->search.len -= diff; + term->search.cursor = new_cursor; } else if ((mods == alt && sym == XKB_KEY_d) || (mods == ctrl && sym == XKB_KEY_Delete)) { - /* TODO: delete word forward */ + size_t diff = distance_next_word(term); + size_t cursor = term->search.cursor; + + memmove(&term->search.buf[cursor], + &term->search.buf[cursor + diff], + (term->search.len - (cursor + diff)) * sizeof(wchar_t)); + + term->search.len -= diff; } else if (mods == 0 && sym == XKB_KEY_Delete) { From 0fceed6f000126dce3914ad5fcd9aad774458597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 30 Aug 2019 19:37:18 +0200 Subject: [PATCH 67/71] search: implement move cursor word backward/forward --- search.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/search.c b/search.c index 89204350..1cd32a2a 100644 --- a/search.c +++ b/search.c @@ -328,11 +328,29 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask term->search.cursor--; } + else if ((mods == ctrl && sym == XKB_KEY_Left) || + (mods == alt && sym == XKB_KEY_b)) + { + size_t diff = distance_prev_word(term); + term->search.cursor -= diff; + assert(term->search.cursor >= 0); + assert(term->search.cursor <= term->search.len); + } + else if (mods == 0 && sym == XKB_KEY_Right) { if (term->search.cursor < term->search.len) term->search.cursor++; } + else if ((mods == ctrl && sym == XKB_KEY_Right) || + (mods == alt && sym == XKB_KEY_f)) + { + size_t diff = distance_next_word(term); + term->search.cursor += diff; + assert(term->search.cursor >= 0); + assert(term->search.cursor <= term->search.len); + } + else if ((mods == 0 && sym == XKB_KEY_Home) || (mods == ctrl && sym == XKB_KEY_a)) term->search.cursor = 0; From e8d187e4f3068f9a35d4b9752f913cc0efe90a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 30 Aug 2019 19:42:33 +0200 Subject: [PATCH 68/71] render: draw (cursor) bar using the same thickness as underlines --- render.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/render.c b/render.c index 1611b972..e5ec9c90 100644 --- a/render.c +++ b/render.c @@ -101,6 +101,7 @@ font_baseline(const struct terminal *term) static void draw_bar(const struct terminal *term, pixman_image_t *pix, + const struct font *font, const pixman_color_t *color, int x, int y) { int baseline = y + font_baseline(term) - term->fextents.ascent; @@ -108,7 +109,7 @@ draw_bar(const struct terminal *term, pixman_image_t *pix, PIXMAN_OP_SRC, pix, color, 1, &(pixman_rectangle16_t){ x, baseline, - 1, term->fextents.ascent + term->fextents.descent}); + font->underline.thickness, term->fextents.ascent + term->fextents.descent}); } static void @@ -259,7 +260,7 @@ render_cell(struct terminal *term, pixman_image_t *pix, cursor_color = fg; if (term->cursor_style == CURSOR_BAR) - draw_bar(term, pix, &cursor_color, x, y); + draw_bar(term, pix, font, &cursor_color, x, y); else if (term->cursor_style == CURSOR_UNDERLINE) draw_underline( term, pix, attrs_to_font(term, &cell->attrs), &cursor_color, @@ -734,6 +735,7 @@ render_search_box(struct terminal *term) PIXMAN_OP_SRC, buf->pix, &color, 1, &(pixman_rectangle16_t){0, 0, width, height}); + struct font *font = &term->fonts[0]; int x = margin; int y = margin; pixman_color_t fg = color_hex_to_pixman(0x000000); @@ -741,9 +743,9 @@ render_search_box(struct terminal *term) /* Text (what the user entered - *not* match(es)) */ for (size_t i = 0; i < term->search.len; i++) { if (i == term->search.cursor) - draw_bar(term, buf->pix, &fg, x, y); + draw_bar(term, buf->pix, font, &fg, x, y); - const struct glyph *glyph = font_glyph_for_wc(&term->fonts[0], term->search.buf[i]); + const struct glyph *glyph = font_glyph_for_wc(font, term->search.buf[i]); if (glyph == NULL) continue; @@ -758,7 +760,7 @@ render_search_box(struct terminal *term) } if (term->search.cursor >= term->search.len) - draw_bar(term, buf->pix, &fg, x, y); + draw_bar(term, buf->pix, font, &fg, x, y); wl_subsurface_set_position( term->wl.search_sub_surface, From 42714c51a9840e434ecd9e9f34e49e721dfd58ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 30 Aug 2019 19:42:48 +0200 Subject: [PATCH 69/71] search: fix distance to prev word calculation when final cursor was 0 --- search.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/search.c b/search.c index 1cd32a2a..4bb9223d 100644 --- a/search.c +++ b/search.c @@ -267,7 +267,7 @@ distance_prev_word(const struct terminal *term) } assert(cursor == 0 || iswspace(term->search.buf[cursor])); - if (iswspace(term->search.buf[cursor])) + if (cursor > 0 && iswspace(term->search.buf[cursor])) cursor++; return term->search.cursor - cursor; From 556bf95b2802fba5bc4e204a27ae9283d0cfcba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 30 Aug 2019 20:15:12 +0200 Subject: [PATCH 70/71] search: make search-direction configurable --- search.c | 30 +++++++++++++++++++++++------- terminal.h | 1 + 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/search.c b/search.c index 4bb9223d..21059f41 100644 --- a/search.c +++ b/search.c @@ -63,6 +63,9 @@ search_cancel(struct terminal *term) static void search_update(struct terminal *term) { + bool backward = term->search.direction == SEARCH_BACKWARD; + term->search.direction = SEARCH_BACKWARD; + if (term->search.len == 0) { term->search.match = (struct coord){-1, -1}; term->search.match_len = 0; @@ -78,23 +81,36 @@ search_update(struct terminal *term) (len > 0 && start_row >= 0 && start_col >= 0)); if (len == 0) { - start_row = grid_row_absolute_in_view(term->grid, term->rows - 1); - start_col = term->cols - 1; + if (backward) { + start_row = grid_row_absolute_in_view(term->grid, term->rows - 1); + start_col = term->cols - 1; + } else { + start_row = grid_row_absolute_in_view(term->grid, 0); + start_col = 0; + } } - LOG_DBG("search: update: starting at row=%d col=%d (offset = %d, view = %d)", - start_row, start_col, term->grid->offset, term->grid->view); + LOG_DBG("search: update: %s: starting at row=%d col=%d (offset = %d, view = %d)", + backward ? "backward" : "forward", start_row, start_col, + term->grid->offset, term->grid->view); #define ROW_DEC(_r) ((_r) = ((_r) - 1 + term->grid->num_rows) % term->grid->num_rows) +#define ROW_INC(_r) ((_r) = ((_r) + 1) % term->grid->num_rows) /* Scan backward from current end-of-output */ /* TODO: don't search "scrollback" in alt screen? */ - for (size_t r = 0; r < term->grid->num_rows; ROW_DEC(start_row), r++) { + for (size_t r = 0; + r < term->grid->num_rows; + backward ? ROW_DEC(start_row) : ROW_INC(start_row), r++) + { const struct row *row = term->grid->rows[start_row]; if (row == NULL) continue; - for (; start_col >= 0; start_col--) { + for (; + backward ? start_col >= 0 : start_col < term->cols; + backward ? start_col-- : start_col++) + { if (wcsncasecmp(&row->cells[start_col].wc, term->search.buf, 1) != 0) continue; @@ -207,7 +223,7 @@ search_update(struct terminal *term) return; } - start_col = term->cols - 1; + start_col = backward ? term->cols - 1 : 0; } /* No match */ diff --git a/terminal.h b/terminal.h index f5eabad1..f435ab3b 100644 --- a/terminal.h +++ b/terminal.h @@ -353,6 +353,7 @@ struct terminal { size_t len; size_t sz; size_t cursor; + enum { SEARCH_BACKWARD, SEARCH_FORWARD} direction; int original_view; bool view_followed_offset; From f24a40a3912713199473eec9452c047c28847f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 30 Aug 2019 20:15:36 +0200 Subject: [PATCH 71/71] search: ctrl+s searches forward --- search.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/search.c b/search.c index 21059f41..28a24367 100644 --- a/search.c +++ b/search.c @@ -339,6 +339,24 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask } } + else if (mods == ctrl && sym == XKB_KEY_s) { + if (term->search.match_len > 0) { + int new_col = term->search.match.col + 1; + int new_row = term->search.match.row; + + if (new_col >= term->cols) { + new_col = 0; + new_row++; + } + + if (new_row < term->grid->num_rows) { + term->search.match.col = new_col; + term->search.match.row = new_row; + term->search.direction = SEARCH_FORWARD; + } + } + } + else if (mods == 0 && sym == XKB_KEY_Left) { if (term->search.cursor > 0) term->search.cursor--;