From 655bde52896b4d6995d0c1b349a7f34d0bb221b1 Mon Sep 17 00:00:00 2001 From: Phil Jones Date: Sat, 30 Jul 2022 09:17:52 +0100 Subject: Add option to colour matching portion of results. This was done by breaking the selected result into three parts, and rendering each separately. A side-effect is that ligatures split when a match ends inside them, but I think that's the correct behaviour (rather than highlighting the whole ligature). There may be issues with some non-latin languages that make much more extensive use of ligatures / combining characters, however. --- completions/tofi | 1 + doc/config | 3 ++ doc/tofi.5.md | 8 ++++ doc/tofi.5.scd | 7 ++++ src/config.c | 2 + src/entry.h | 1 + src/entry_backend/harfbuzz.c | 87 ++++++++++++++++++++++++++++++++++++++++---- src/entry_backend/pango.c | 52 ++++++++++++++++++++++---- src/main.c | 3 ++ 9 files changed, 149 insertions(+), 15 deletions(-) diff --git a/completions/tofi b/completions/tofi index 5942cf7..add8aab 100644 --- a/completions/tofi +++ b/completions/tofi @@ -16,6 +16,7 @@ _tofi() --font-size --num-results --selection-color + --selection-match-color --selection-padding --selection-background --outline-width diff --git a/doc/config b/doc/config index eb360e4..454f275 100644 --- a/doc/config +++ b/doc/config @@ -42,6 +42,9 @@ # Selection text selection-color = #F92672 + # Matching portion of selection text + selection-match-color = #00000000 + # Selection background selection-background = #00000000 diff --git a/doc/tofi.5.md b/doc/tofi.5.md index 539b525..c922fac 100644 --- a/doc/tofi.5.md +++ b/doc/tofi.5.md @@ -94,6 +94,14 @@ options. > > Default: \#F92672 +**selection-match-color**=*color* + +> Color of the matching portion of the selected result. Any color that +> is fully transparent (alpha = 0) will disable this highlighting. See +> **COLORS** for more information. +> +> Default: \#00000000 + **selection-padding**=*px* > Extra horizontal padding of the selection background. If *px* = -1, diff --git a/doc/tofi.5.scd b/doc/tofi.5.scd index 8341200..3993e78 100644 --- a/doc/tofi.5.scd +++ b/doc/tofi.5.scd @@ -85,6 +85,13 @@ options. Default: #F92672 +*selection-match-color*=_color_ + Color of the matching portion of the selected result. Any color that is + fully transparent (alpha = 0) will disable this highlighting. + See *COLORS* for more information. + + Default: #00000000 + *selection-padding*=_px_ Extra horizontal padding of the selection background. If _px_ = -1, the padding will fill the whole window width. diff --git a/src/config.c b/src/config.c index a0e77b2..8cb3763 100644 --- a/src/config.c +++ b/src/config.c @@ -263,6 +263,8 @@ bool parse_option(struct tofi *tofi, const char *filename, size_t lineno, const tofi->window.entry.foreground_color = parse_color(filename, lineno, value, &err); } else if (strcasecmp(option, "selection-color") == 0) { tofi->window.entry.selection_foreground_color = parse_color(filename, lineno, value, &err); + } else if (strcasecmp(option, "selection-match-color") == 0) { + tofi->window.entry.selection_highlight_color = parse_color(filename, lineno, value, &err); } else if (strcasecmp(option, "selection-padding") == 0) { tofi->window.entry.selection_background_padding = parse_int32(filename, lineno, value, &err); } else if (strcasecmp(option, "selection-background") == 0) { diff --git a/src/entry.h b/src/entry.h index 5536e06..562c085 100644 --- a/src/entry.h +++ b/src/entry.h @@ -65,6 +65,7 @@ struct entry { uint32_t outline_width; struct color foreground_color; struct color background_color; + struct color selection_highlight_color; struct color selection_foreground_color; struct color selection_background_color; struct color border_color; diff --git a/src/entry_backend/harfbuzz.c b/src/entry_backend/harfbuzz.c index cba1fd8..e3217a7 100644 --- a/src/entry_backend/harfbuzz.c +++ b/src/entry_backend/harfbuzz.c @@ -242,17 +242,88 @@ void entry_backend_harfbuzz_update(struct entry *entry) break; } - hb_buffer_clear_contents(buffer); - setup_hb_buffer(buffer); - hb_buffer_add_utf8(buffer, entry->results.buf[i].string, -1, 0, -1); - hb_shape(entry->harfbuzz.hb_font, buffer, NULL, 0); - if (i == entry->selection) { + /* If this isn't the selected result, just print as normal. */ + if (i != entry->selection) { + hb_buffer_clear_contents(buffer); + setup_hb_buffer(buffer); + hb_buffer_add_utf8(buffer, entry->results.buf[i].string, -1, 0, -1); + hb_shape(entry->harfbuzz.hb_font, buffer, NULL, 0); + width = render_hb_buffer(cr, buffer); + } else { + /* + * For the selected result, there's a bit more to do. + * + * First, we need to use a different foreground color - + * simple enough. + * + * Next, we may need to draw a background box - this + * involves rendering to a cairo group, measuring the + * size of the text, drawing the background on the main + * canvas, then finally drawing the group on top of + * that. + * + * Finally, we may need to highlight the matching + * portion of text - this is achieved simply by + * splitting the text into prematch, match and + * postmatch chunks, and drawing each separately. + */ + size_t prematch_len; + char *prematch = xstrdup(entry->results.buf[i].string); + char *match = NULL; + char *postmatch = NULL; + uint32_t subwidth; + if (entry->input_mb_length > 0 && entry->selection_highlight_color.a != 0) { + char *match_pos = strcasestr(prematch, entry->input_mb); + if (match_pos != NULL) { + match = xstrdup(entry->results.buf[i].string); + postmatch = xstrdup(entry->results.buf[i].string); + prematch_len = (match_pos - prematch); + prematch[prematch_len] = '\0'; + match[entry->input_mb_length + prematch_len] = '\0'; + } + } + cairo_push_group(cr); color = entry->selection_foreground_color; cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); - } - width = render_hb_buffer(cr, buffer); - if (i == entry->selection) { + + hb_buffer_clear_contents(buffer); + setup_hb_buffer(buffer); + hb_buffer_add_utf8(buffer, prematch, -1, 0, -1); + hb_shape(entry->harfbuzz.hb_font, buffer, NULL, 0); + subwidth = render_hb_buffer(cr, buffer); + width = subwidth; + + if (match != NULL) { + cairo_translate(cr, subwidth, 0); + color = entry->selection_highlight_color; + cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); + hb_buffer_clear_contents(buffer); + setup_hb_buffer(buffer); + hb_buffer_add_utf8(buffer, &match[prematch_len], -1, 0, -1); + hb_shape(entry->harfbuzz.hb_font, buffer, NULL, 0); + subwidth = render_hb_buffer(cr, buffer); + width += subwidth; + + cairo_translate(cr, subwidth, 0); + color = entry->selection_foreground_color; + cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); + hb_buffer_clear_contents(buffer); + setup_hb_buffer(buffer); + hb_buffer_add_utf8(buffer, &postmatch[entry->input_mb_length + prematch_len], -1, 0, -1); + hb_shape(entry->harfbuzz.hb_font, buffer, NULL, 0); + subwidth = render_hb_buffer(cr, buffer); + width += subwidth; + + free(match); + free(postmatch); + match = NULL; + postmatch = NULL; + } + + free(prematch); + prematch = NULL; + cairo_pop_group_to_source(cr); cairo_save(cr); color = entry->selection_background_color; diff --git a/src/entry_backend/pango.c b/src/entry_backend/pango.c index 73eeb49..c182e4c 100644 --- a/src/entry_backend/pango.c +++ b/src/entry_backend/pango.c @@ -4,6 +4,7 @@ #include "../entry.h" #include "../log.h" #include "../nelem.h" +#include "../xmalloc.h" #undef MAX #define MAX(a, b) ((a) > (b) ? (a) : (b)) @@ -95,16 +96,53 @@ void entry_backend_pango_update(struct entry *entry) } else { str = ""; } - pango_layout_set_text(layout, str, -1); - pango_cairo_update_layout(cr, layout); - if (i == entry->selection) { + if (i != entry->selection) { + pango_layout_set_text(layout, str, -1); + pango_cairo_update_layout(cr, layout); + pango_cairo_show_layout(cr, layout); + pango_layout_get_size(layout, &width, &height); + } else { + ssize_t prematch_len = -1; + size_t match_len = entry->input_mb_length; + int32_t subwidth; + if (entry->input_mb_length > 0 && entry->selection_highlight_color.a != 0) { + char *match_pos = strcasestr(str, entry->input_mb); + if (match_pos != NULL) { + prematch_len = (match_pos - str); + } + } + cairo_push_group(cr); color = entry->selection_foreground_color; cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); - } - pango_cairo_show_layout(cr, layout); - pango_layout_get_size(layout, &width, &height); - if (i == entry->selection) { + + pango_layout_set_text(layout, str, prematch_len); + pango_cairo_update_layout(cr, layout); + pango_cairo_show_layout(cr, layout); + pango_layout_get_size(layout, &subwidth, &height); + width = subwidth; + + if (prematch_len != -1) { + cairo_translate(cr, (int)(subwidth / PANGO_SCALE), 0); + color = entry->selection_highlight_color; + cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); + pango_layout_set_text(layout, &str[prematch_len], match_len); + pango_cairo_update_layout(cr, layout); + pango_cairo_show_layout(cr, layout); + pango_layout_get_size(layout, &subwidth, &height); + width += subwidth; + + cairo_translate(cr, (int)(subwidth / PANGO_SCALE), 0); + color = entry->selection_foreground_color; + cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); + pango_layout_set_text(layout, &str[prematch_len + match_len], -1); + pango_cairo_update_layout(cr, layout); + pango_cairo_show_layout(cr, layout); + pango_layout_get_size(layout, &subwidth, &height); + width += subwidth; + + } + cairo_pop_group_to_source(cr); cairo_save(cr); color = entry->selection_background_color; diff --git a/src/main.c b/src/main.c index 9a3ace1..4c5f497 100644 --- a/src/main.c +++ b/src/main.c @@ -687,6 +687,8 @@ static void usage() " --prompt-text Prompt text.\n" " --num-results Maximum number of results to display.\n" " --selection-color Color of selected result.\n" +" --selection-match-color Color of the matching portion of the\n" +" selected result.\n" " --selection-padding Extra horizontal padding for selected\n" " result background.\n" " --selection-background Color of selected result background.\n" @@ -729,6 +731,7 @@ const struct option long_options[] = { {"font-size", required_argument, NULL, 0}, {"num-results", required_argument, NULL, 0}, {"selection-color", required_argument, NULL, 0}, + {"selection-match-color", required_argument, NULL, 0}, {"selection-padding", required_argument, NULL, 0}, {"selection-background", required_argument, NULL, 0}, {"outline-width", required_argument, NULL, 0}, -- cgit v1.2.3