From 88456b6adca44f3132c7437722c1e0ffb2e43e04 Mon Sep 17 00:00:00 2001 From: Dave Davenport Date: Sun, 9 Feb 2025 17:34:57 +0100 Subject: [PATCH] [WIP] [View] Allow cycling through matching methods (#2091) * [View] Allow cycling through matching methods * [Doc] Fix documentation and test for keybindings * [Helper] Allow selecting different matching modes for cycling at runtime. * [View] Fix bool to gboolean * [Doc] Small type fix, theme cleanup * [Overlay] Make timeout configurable. * [Helper] Reduce scope of variable. --- doc/default_theme.rasi | 13 ++++-- doc/rofi-keys.5.markdown | 12 +++++ doc/rofi-theme.5.markdown | 4 ++ doc/rofi.1.markdown | 3 ++ include/helper.h | 13 ++++++ include/keyb.h | 2 + include/settings.h | 3 +- include/view.h | 8 ++++ source/helper.c | 79 +++++++++++++++++++++++++-------- source/keyb.c | 8 ++++ source/modes/recursivebrowser.c | 3 +- source/view.c | 78 +++++++++++++++++++++++++------- test/mode-test.c | 2 +- 13 files changed, 187 insertions(+), 41 deletions(-) diff --git a/doc/default_theme.rasi b/doc/default_theme.rasi index e2ffd351a..c18a84fe7 100644 --- a/doc/default_theme.rasi +++ b/doc/default_theme.rasi @@ -141,10 +141,17 @@ textbox-num-sep { str: "/"; } inputbar { - padding: 1px ; - spacing: 0px ; + padding: 1px; + spacing: 0px; text-color: var(normal-foreground); - children: [ prompt,textbox-prompt-colon,entry, num-filtered-rows, textbox-num-sep, num-rows, case-indicator ]; + children: [ prompt,textbox-prompt-colon,entry, overlay,num-filtered-rows, textbox-num-sep, num-rows, case-indicator ]; +} +overlay { + background-color: var(normal-foreground); + foreground-color: var(normal-background); + text-color: var(normal-background); + padding: 0 0.2em; + margin: 0 0.2em; } case-indicator { spacing: 0; diff --git a/doc/rofi-keys.5.markdown b/doc/rofi-keys.5.markdown index 8aeefacc2..d664958ac 100644 --- a/doc/rofi-keys.5.markdown +++ b/doc/rofi-keys.5.markdown @@ -495,6 +495,18 @@ Go down in the entry history. Default: Control+Down +`kb-matcher-up` + +Select the next matcher. + +Default: Super+equal + +`kb-matcher-down` + +Select the previous matcher. + +Default: Super+minus + ## Mouse Bindings `ml-row-left` diff --git a/doc/rofi-theme.5.markdown b/doc/rofi-theme.5.markdown index 2fc60df39..c03810356 100644 --- a/doc/rofi-theme.5.markdown +++ b/doc/rofi-theme.5.markdown @@ -1118,6 +1118,10 @@ The following properties are currently supported: - **require-input**: boolean Listview requires user input to be unhidden. The list is still present and hitting accept will activate the first entry. +### Overlay widget + +- **timeout**: The time the widget is visible when showing a temporary message. + ## Listview widget The listview widget is special container widget. diff --git a/doc/rofi.1.markdown b/doc/rofi.1.markdown index 1e7055015..28be15fb3 100644 --- a/doc/rofi.1.markdown +++ b/doc/rofi.1.markdown @@ -361,6 +361,9 @@ Currently, the following methods are supported: Default: *normal* +Multiple matching methods can be specified in a comma separated list. +The matching up/down keybinding allows cycling through at runtime. + Note: glob matching might be slow for larger lists `-tokenize` diff --git a/include/helper.h b/include/helper.h index 937bb30d3..c16daeb02 100644 --- a/include/helper.h +++ b/include/helper.h @@ -442,6 +442,19 @@ ConfigEntry *rofi_config_find_widget(const char *name, const char *state, */ Property *rofi_theme_find_property(ConfigEntry *widget, PropertyType type, const char *property, gboolean exact); + +/** + * @returns get a human readable string with the current matching method. + */ +const char *helper_get_matching_mode_str(void); +/** + * Switch to the next matching method. + */ +void helper_select_next_matching_mode(void); +/** + * Switch to the previous matching method. + */ +void helper_select_previous_matching_mode(void); G_END_DECLS /**@} */ diff --git a/include/keyb.h b/include/keyb.h index 850df0e6a..33c3f39f4 100644 --- a/include/keyb.h +++ b/include/keyb.h @@ -145,6 +145,8 @@ typedef enum { SELECT_ELEMENT_10, ENTRY_HISTORY_UP, ENTRY_HISTORY_DOWN, + MATCHER_UP, + MATCHER_DOWN } KeyBindingAction; /** diff --git a/include/settings.h b/include/settings.h index d0eb40741..a958b4784 100644 --- a/include/settings.h +++ b/include/settings.h @@ -40,7 +40,8 @@ typedef enum { MM_REGEX = 1, MM_GLOB = 2, MM_FUZZY = 3, - MM_PREFIX = 4 + MM_PREFIX = 4, + MM_NUM_MATCHERS = 5 } MatchingMethod; /** diff --git a/include/view.h b/include/view.h index cef073e1c..c0250163b 100644 --- a/include/view.h +++ b/include/view.h @@ -281,6 +281,14 @@ void rofi_view_switch_mode(RofiViewState *state, Mode *mode); * Overlays text over the current view. Passing NULL for text hides the overlay. */ void rofi_view_set_overlay(RofiViewState *state, const char *text); +/** + * @param state The handle to the view + * @param text An UTF-8 encoded character array with the text to overlay. + * + * Overlays text over the current view. Passing NULL for text hides the overlay. + * This message is automatically removed after X seconds. + */ +void rofi_view_set_overlay_timeout (RofiViewState *state, const char *text); /** * @param state The handle to the view. diff --git a/source/helper.c b/source/helper.c index a5b77a9aa..91c10151b 100644 --- a/source/helper.c +++ b/source/helper.c @@ -55,6 +55,16 @@ #include #include +const char *const MatchingMethodStr[MM_NUM_MATCHERS] = { + "Normal", "Regex", "Glob", "Fuzzy", "Prefix"}; + +static int MatchingMethodEnabled[MM_NUM_MATCHERS] = { + MM_NORMAL, + -1, +}; +static int NUMMatchingMethodEnabled = 1; +static int CurrentMatchingMethod = 0; + /** * Textual description of positioning rofi. */ @@ -68,6 +78,23 @@ char **stored_argv = NULL; char *helper_string_replace_if_exists_v(char *string, GHashTable *h); +const char *helper_get_matching_mode_str(void) { + return MatchingMethodStr[config.matching_method]; +} +void helper_select_next_matching_mode(void) { + + CurrentMatchingMethod++; + CurrentMatchingMethod %= NUMMatchingMethodEnabled; + config.matching_method = MatchingMethodEnabled[CurrentMatchingMethod]; +} +void helper_select_previous_matching_mode(void) { + CurrentMatchingMethod--; + if (CurrentMatchingMethod < 0) { + CurrentMatchingMethod = NUMMatchingMethodEnabled - 1; + } + config.matching_method = MatchingMethodEnabled[CurrentMatchingMethod]; +} + void cmd_set_arguments(int argc, char **argv) { stored_argc = argc; stored_argv = argv; @@ -665,24 +692,40 @@ int config_sanity_check(void) { } if (config.matching) { - if (g_strcmp0(config.matching, "regex") == 0) { - config.matching_method = MM_REGEX; - } else if (g_strcmp0(config.matching, "glob") == 0) { - config.matching_method = MM_GLOB; - } else if (g_strcmp0(config.matching, "fuzzy") == 0) { - config.matching_method = MM_FUZZY; - } else if (g_strcmp0(config.matching, "normal") == 0) { - config.matching_method = MM_NORMAL; - ; - } else if (g_strcmp0(config.matching, "prefix") == 0) { - config.matching_method = MM_PREFIX; - } else { - g_string_append_printf(msg, - "\tconfig.matching=%s is not a valid " - "matching strategy.\nValid options are: glob, " - "regex, fuzzy, prefix or normal.\n", - config.matching); - found_error = 1; + char **strv = g_strsplit(config.matching, ",", 0); + if (strv) { + int matching_method_index = 0; + for (char **str = strv; *str && matching_method_index < MM_NUM_MATCHERS; + str++) { + gboolean found = FALSE; + for (unsigned i = 0; + i < MM_NUM_MATCHERS && matching_method_index < MM_NUM_MATCHERS; + i++) { + if (g_ascii_strcasecmp(*str, MatchingMethodStr[i]) == 0) { + MatchingMethodEnabled[matching_method_index] = i; + matching_method_index++; + NUMMatchingMethodEnabled = matching_method_index; + if (matching_method_index == MM_NUM_MATCHERS) { + found_error = 1; + g_string_append_printf(msg, + "\tconfig.matching = %s to many " + "matching options enabled.\n", + config.matching); + } + found = TRUE; + } + } + if (!found) { + g_string_append_printf(msg, + "\tconfig.matching=%s is not a valid " + "matching strategy.\nValid options are: glob, " + "regex, fuzzy, prefix or normal.\n", + *str); + found_error = 1; + } + } + config.matching_method = MatchingMethodEnabled[0]; + g_strfreev(strv); } } diff --git a/source/keyb.c b/source/keyb.c index 9de5829b9..c1bb2229b 100644 --- a/source/keyb.c +++ b/source/keyb.c @@ -332,6 +332,14 @@ ActionBindingEntry rofi_bindings[] = { .name = "kb-entry-history-down", .binding = "Control+Down", .comment = "Go down in the history of the entry box"}, + {.id = MATCHER_UP, + .name = "kb-matcher-up", + .binding = "Super+equal", + .comment = "Switch to the previous matcher"}, + {.id = MATCHER_DOWN, + .name = "kb-mather-down", + .binding = "Super+minus", + .comment = "Switch to the next matcher"}, /* Mouse-aware bindings */ diff --git a/source/modes/recursivebrowser.c b/source/modes/recursivebrowser.c index febf86a9d..96db43f08 100644 --- a/source/modes/recursivebrowser.c +++ b/source/modes/recursivebrowser.c @@ -349,7 +349,8 @@ static gboolean recursive_browser_async_read_proc(gint fd, } } else if (command == 'q') { if (pd->loading) { - rofi_view_set_overlay(rofi_view_get_active(), NULL); + // TODO: add enable. + //rofi_view_set_overlay(rofi_view_get_active(), NULL); } } } diff --git a/source/view.c b/source/view.c index c21d7d12b..59d4d6633 100644 --- a/source/view.c +++ b/source/view.c @@ -143,6 +143,8 @@ struct { gboolean delayed_mode; /** timeout handling */ guint user_timeout; + /** timeout overlay */ + guint overlay_timeout; /** debug counter for redraws */ unsigned long long count; /** redraw idle time. */ @@ -172,6 +174,7 @@ struct { .max_refilter_time = 0.0, .delayed_mode = FALSE, .user_timeout = 0, + .overlay_timeout = 0, .count = 0L, .repaint_source = 0, .fullscreen = FALSE, @@ -218,8 +221,8 @@ static void screenshot_taken_user_callback(const char *path) { char **args = NULL; int argv = 0; - helper_parse_setup(config.on_screenshot_taken, &args, &argv, "{path}", - path, (char *)0); + helper_parse_setup(config.on_screenshot_taken, &args, &argv, "{path}", path, + (char *)0); if (args != NULL) helper_execute(NULL, args, "", config.on_screenshot_taken, NULL); } @@ -1299,17 +1302,18 @@ inline static void rofi_view_nav_last(RofiViewState *state) { // state->selected = state->filtered_lines - 1; listview_set_selected(state->list_view, -1); } -static void selection_changed_user_callback(unsigned int index, RofiViewState *state) { +static void selection_changed_user_callback(unsigned int index, + RofiViewState *state) { if (config.on_selection_changed == NULL) return; int fstate = 0; char *text = mode_get_display_value(state->sw, state->line_map[index], - &fstate, NULL, TRUE); + &fstate, NULL, TRUE); char **args = NULL; int argv = 0; - helper_parse_setup(config.on_selection_changed, &args, &argv, "{entry}", - text, (char *)0); + helper_parse_setup(config.on_selection_changed, &args, &argv, "{entry}", text, + (char *)0); if (args != NULL) helper_execute(NULL, args, "", config.on_selection_changed, NULL); g_free(text); @@ -1486,7 +1490,7 @@ static gboolean rofi_view_refilter_real(RofiViewState *state) { state->case_sensitive = parse_case_sensitivity(state->text->text); state->tokens = helper_tokenize(pattern, state->case_sensitive); - if ( config.case_smart && state->case_indicator ) { + if (config.case_smart && state->case_indicator) { textbox_text(state->case_indicator, get_matching_state(state)); } /** @@ -1979,6 +1983,16 @@ static void rofi_view_trigger_global_action(KeyBindingAction action) { } break; } + case MATCHER_UP: + helper_select_next_matching_mode(); + rofi_view_refilter(state); + rofi_view_set_overlay_timeout(state, helper_get_matching_mode_str()); + break; + case MATCHER_DOWN: + helper_select_previous_matching_mode(); + rofi_view_refilter(state); + rofi_view_set_overlay_timeout(state, helper_get_matching_mode_str()); + break; } } @@ -2135,22 +2149,20 @@ static void rofi_quit_user_callback(RofiViewState *state) { return; // Pass selected text to custom command char *text = mode_get_display_value(state->sw, state->line_map[selected], - &fstate, NULL, TRUE); + &fstate, NULL, TRUE); char **args = NULL; int argv = 0; - helper_parse_setup(config.on_entry_accepted, &args, &argv, "{entry}", - text, (char *)0); + helper_parse_setup(config.on_entry_accepted, &args, &argv, "{entry}", text, + (char *)0); if (args != NULL) helper_execute(NULL, args, "", config.on_entry_accepted, NULL); g_free(text); - } else if(state->retv & MENU_CANCEL) { + } else if (state->retv & MENU_CANCEL) { if (config.on_menu_canceled == NULL) return; helper_execute_command(NULL, config.on_menu_canceled, FALSE, NULL); - } else if (state->retv & MENU_NEXT || - state->retv & MENU_PREVIOUS || - state->retv & MENU_QUICK_SWITCH || - state->retv & MENU_COMPLETE) { + } else if (state->retv & MENU_NEXT || state->retv & MENU_PREVIOUS || + state->retv & MENU_QUICK_SWITCH || state->retv & MENU_COMPLETE) { if (config.on_mode_changed == NULL) return; // TODO: pass mode name to custom command @@ -2651,8 +2663,8 @@ static void rofi_error_user_callback(const char *msg) { char **args = NULL; int argv = 0; - helper_parse_setup(config.on_menu_error, &args, &argv, "{error}", - msg, (char *)0); + helper_parse_setup(config.on_menu_error, &args, &argv, "{error}", msg, + (char *)0); if (args != NULL) helper_execute(NULL, args, "", config.on_menu_error, NULL); } @@ -2722,6 +2734,10 @@ void rofi_view_cleanup(void) { g_source_remove(CacheState.refilter_timeout); CacheState.refilter_timeout = 0; } + if (CacheState.overlay_timeout) { + g_source_remove(CacheState.overlay_timeout); + CacheState.overlay_timeout = 0; + } if (CacheState.user_timeout > 0) { g_source_remove(CacheState.user_timeout); CacheState.user_timeout = 0; @@ -2825,10 +2841,38 @@ void rofi_view_workers_finalize(void) { } Mode *rofi_view_get_mode(RofiViewState *state) { return state->sw; } +static gboolean rofi_view_overlay_timeout(G_GNUC_UNUSED gpointer user_data) { + RofiViewState *state = rofi_view_get_active(); + if (state) { + widget_disable(WIDGET(state->overlay)); + } + CacheState.overlay_timeout = 0; + rofi_view_queue_redraw(); + return G_SOURCE_REMOVE; +} + +void rofi_view_set_overlay_timeout(RofiViewState *state, const char *text) { + if (state->overlay == NULL || state->list_view == NULL) { + return; + } + if (text == NULL) { + widget_disable(WIDGET(state->overlay)); + return; + } + rofi_view_set_overlay(state, text); + int timeout = rofi_theme_get_integer(WIDGET(state->overlay), "timeout", 3); + CacheState.overlay_timeout = + g_timeout_add_seconds(timeout, rofi_view_overlay_timeout, state); +} + void rofi_view_set_overlay(RofiViewState *state, const char *text) { if (state->overlay == NULL || state->list_view == NULL) { return; } + if (CacheState.overlay_timeout > 0) { + g_source_remove(CacheState.overlay_timeout); + CacheState.overlay_timeout = 0; + } if (text == NULL) { widget_disable(WIDGET(state->overlay)); return; diff --git a/test/mode-test.c b/test/mode-test.c index c67c89a84..98b1f48da 100644 --- a/test/mode-test.c +++ b/test/mode-test.c @@ -127,7 +127,7 @@ END_TEST START_TEST(test_mode_num_items) { unsigned int rows = mode_get_num_entries(&help_keys_mode); - ck_assert_int_eq(rows, 79); + ck_assert_int_eq(rows, 81); for (unsigned int i = 0; i < rows; i++) { int state = 0; GList *list = NULL;