From d1ba4bd8dc1e1d1e0e57015e4d9dd3fe0c03a3b9 Mon Sep 17 00:00:00 2001 From: Edwin Pujols Date: Mon, 12 Jan 2015 09:13:46 -0400 Subject: [PATCH 1/3] Fix #102 - Add case sensitivity. --- config/config.def.c | 2 ++ doc/rofi-manpage.markdown | 9 +++++--- doc/rofi.1 | 10 +++++++-- include/helper.h | 18 ++++++++++++++-- include/rofi.h | 5 ++++- source/helper.c | 31 ++++++++++++++++++---------- source/rofi.c | 43 +++++++++++++++++++++++---------------- source/run-dialog.c | 3 ++- source/ssh-dialog.c | 3 ++- 9 files changed, 85 insertions(+), 39 deletions(-) diff --git a/config/config.def.c b/config/config.def.c index 53a19eab..5a122def 100644 --- a/config/config.def.c +++ b/config/config.def.c @@ -99,6 +99,8 @@ Settings config = { .disable_history = FALSE, /** Use levenshtein sorting when matching */ .levenshtein_sort = FALSE, + /** Case sensitivity of the search */ + .case_sensitive = FALSE, /** Separator to use for dmenu mode */ .separator = '\n', /** Height of an element in #chars */ diff --git a/doc/rofi-manpage.markdown b/doc/rofi-manpage.markdown index 7339190c..3762b290 100644 --- a/doc/rofi-manpage.markdown +++ b/doc/rofi-manpage.markdown @@ -12,8 +12,8 @@ rofi - A window switcher, run dialog and dmenu replacement -padding *padding* ] [ -opacity *opacity%* ] [ -display *display* ] [ -bc *color* ] [ -bw *width* ] [ -dmenu [ -p *prompt* ] ] [ -ssh-client *client* ] [ -ssh-command *command* ] [ -now ] [ -rnow ] [ -snow ] [ -version ] [ -help] [ -dump-xresources ] [ -disable-history ] [ -levenshtein-sort ] [ --show *mode* ] [ -switcher *mode1,mode2* ] [ -e *message*] [ -sep *separator* ] [ -eh *element -height* ] [ -l *selected line* ] [ -run-list-command *cmd* ] +-case-sensitive ] [ -show *mode* ] [ -switcher *mode1,mode2* ] [ -e *message*] [ -sep *separator* ] +[ -eh *element height* ] [ -l *selected line* ] [ -run-list-command *cmd* ] ## DESCRIPTION @@ -119,6 +119,9 @@ The default key combinations are: rofi -switchers "window,run,ssh,Workspaces:i3_switch_workspaces.sh" -show Workspaces +`-case-sensitive` + + Start in case sensitive mode. ### Theming @@ -255,7 +258,6 @@ The default key combinations are: Override the used ssh client. Default is `ssh`. - ### SSH settings `-ssh-set-title` *true|false* @@ -384,6 +386,7 @@ Rofi supports the following keybindings: * `ctrl-/`: Switch to the previous modi. The list can be customized with the `-switchers` argument. * `Ctrl-space`: Set selected item as input text. * `Shift-Del`: Delete entry from history. +* `grave`: Toggle case sensitivity. ## FAQ diff --git a/doc/rofi.1 b/doc/rofi.1 index d8d7030e..c6d6c721 100644 --- a/doc/rofi.1 +++ b/doc/rofi.1 @@ -10,8 +10,8 @@ rofi \- A window switcher, run dialog and dmenu replacement \-padding \fIpadding\fP ] [ \-opacity \fIopacity%\fP ] [ \-display \fIdisplay\fP ] [ \-bc \fIcolor\fP ] [ \-bw \fIwidth\fP ] [ \-dmenu [ \-p \fIprompt\fP ] ] [ \-ssh\-client \fIclient\fP ] [ \-ssh\-command \fIcommand\fP ] [ \-now ] [ \-rnow ] [ \-snow ] [ \-version ] [ \-help] [ \-dump\-xresources ] [ \-disable\-history ] [ \-levenshtein\-sort ] [ -\-show \fImode\fP ] [ \-switcher \fImode1,mode2\fP ] [ \-e \fImessage\fP] [ \-sep \fIseparator\fP ] [ \-eh \fIelement -height\fP ] [ \-l \fIselected line\fP ] [ \-run\-list\-command \fIcmd\fP ] +\-case\-sensitive ] [ \-show \fImode\fP ] [ \-switcher \fImode1,mode2\fP ] [ \-e \fImessage\fP] [ \-sep \fIseparator\fP ] +[ \-eh \fIelement height\fP ] [ \-l \fIselected line\fP ] [ \-run\-list\-command \fIcmd\fP ] .SH DESCRIPTION .PP \fBrofi\fP is an X11 popup window switcher. A list is displayed center\-screen showing open window titles, WM_CLASS, and desktop number. @@ -140,6 +140,10 @@ So to have a mode 'Workspaces' using the \fB\fCi3_switch_workspace.sh\fR script rofi \-switchers "window,run,ssh,Workspaces:i3_switch_workspaces.sh" \-show Workspaces .fi .RE +.PP +\fB\fC\-case\-sensitive\fR +.IP +Start in case sensitive mode. .SS Theming .PP \fB\fC\-bg\fR @@ -500,6 +504,8 @@ Rofi supports the following keybindings: \fB\fCCtrl\-space\fR: Set selected item as input text. .IP \(bu 2 \fB\fCShift\-Del\fR: Delete entry from history. +.IP \(bu 2 +\fB\fCgrave\fR: Toggle case sensitivity. .RE .SH FAQ .PP diff --git a/include/helper.h b/include/helper.h index 45c7ae28..9e75c336 100644 --- a/include/helper.h +++ b/include/helper.h @@ -18,14 +18,27 @@ int helper_parse_setup ( char * string, char ***output, int *length, ... ); */ char* fgets_s ( char* s, int n, FILE *iop, char sep ); +/** + * @param token The string for which we want a collation key. + * @param case_sensitive Whether case is significant. + * + * Get a collation key for @p token. @p token must be a null-terminated string. + * This collation key can be used for matching the user input against the list + * of commands, windows, or ssh commands. + * + * @returns A newly allocated string containing the collation key. + */ +char *token_collate_key ( const char *token, int case_sensitive ); + /** * @param input The input string. + * @param case_sensitive Whether case is significant. * * Tokenize the string on spaces. * * @returns a newly allocated 2 dimensional array of strings. */ -char **tokenize ( const char *input ); +char **tokenize ( const char *input, int case_sensitive ); /** * @param argc Number of arguments. @@ -92,6 +105,7 @@ int find_arg ( const int argc, char * const argv[], const char * const key ); * @params tokens * @param tokens List of (input) tokens to match. * @param input The entry to match against. + * @param case_sensitive Whether case is significant. * @param index The current selected index. * @param data User data. * @@ -99,7 +113,7 @@ int find_arg ( const int argc, char * const argv[], const char * const key ); * * @returns 1 when matches, 0 otherwise */ -int token_match ( char **tokens, const char *input, +int token_match ( char **tokens, const char *input, int case_sensitive, __attribute__( ( unused ) ) int index, __attribute__( ( unused ) ) void *data ); diff --git a/include/rofi.h b/include/rofi.h index d1e4f7b4..b1fb17ff 100644 --- a/include/rofi.h +++ b/include/rofi.h @@ -62,6 +62,7 @@ typedef enum /** * @param tokens List of (input) tokens to match. * @param input The entry to match against. + * @param case_sensitive Whether case is significant. * @param index The current selected index. * @param data User data. * @@ -69,7 +70,7 @@ typedef enum * * @returns 1 when it matches, 0 if not. */ -typedef int ( *menu_match_cb )( char **tokens, const char *input, int index, void *data ); +typedef int ( *menu_match_cb )( char **tokens, const char *input, int case_sensitive, int index, void *data ); /** * @param lines An array of strings to display. @@ -194,6 +195,8 @@ typedef struct _Settings unsigned int disable_history; /** Use levenshtein sorting when matching */ unsigned int levenshtein_sort; + /** Search case sensitivity */ + unsigned int case_sensitive; /** Separator to use for dmenu mode */ char separator; /** Height of an element in #chars */ diff --git a/source/helper.c b/source/helper.c index 01b3b864..5a67e2bb 100644 --- a/source/helper.c +++ b/source/helper.c @@ -118,7 +118,23 @@ int helper_parse_setup ( char * string, char ***output, int *length, ... ) return FALSE; } -char **tokenize ( const char *input ) +char *token_collate_key ( const char *token, int case_sensitive ) +{ + char *tmp, *compk; + + if ( case_sensitive ) { + tmp = g_strdup ( token ); + } else { + tmp = g_utf8_casefold ( token, -1 ); + } + + compk = g_utf8_collate_key ( tmp, -1 ); + g_free ( tmp ); + + return compk; +} + +char **tokenize ( const char *input, int case_sensitive ) { if ( input == NULL ) { return NULL; @@ -137,15 +153,10 @@ char **tokenize ( const char *input ) for ( token = strtok_r ( str, " ", &saveptr ); token != NULL; token = strtok_r ( NULL, " ", &saveptr ) ) { - // Get case insensitive version of the string. - char *tmp = g_utf8_casefold ( token, -1 ); - retv = g_realloc ( retv, sizeof ( char* ) * ( num_tokens + 2 ) ); + retv[num_tokens] = token_collate_key ( token, case_sensitive ); retv[num_tokens + 1] = NULL; - // Create compare key from the case insensitive version. - retv[num_tokens] = g_utf8_collate_key ( tmp, -1 ); num_tokens++; - g_free ( tmp ); } // Free str. g_free ( str ); @@ -258,21 +269,19 @@ int find_arg_char ( const int argc, char * const argv[], const char * const key, * Shared 'token_match' function. * Matches tokenized. */ -int token_match ( char **tokens, const char *input, +int token_match ( char **tokens, const char *input, int case_sensitive, __attribute__( ( unused ) ) int index, __attribute__( ( unused ) ) void *data ) { int match = 1; + char *compk = token_collate_key ( input, case_sensitive ); - char *lowerc = g_utf8_casefold ( input, -1 ); - char *compk = g_utf8_collate_key ( lowerc, -1 ); // Do a tokenized match. if ( tokens ) { for ( int j = 0; match && tokens[j]; j++ ) { match = ( strstr ( compk, tokens[j] ) != NULL ); } } - g_free ( lowerc ); g_free ( compk ); return match; } diff --git a/source/rofi.c b/source/rofi.c index 4f95c6f7..8205ecd0 100644 --- a/source/rofi.c +++ b/source/rofi.c @@ -690,43 +690,39 @@ static void menu_set_arrow_text ( int filtered_lines, int selected, int max_elem } -static int window_match ( char **tokens, __attribute__( ( unused ) ) const char *input, int index, void *data ) +static int window_match ( char **tokens, __attribute__( ( unused ) ) const char *input, + int case_sensitive, int index, void *data ) { int match = 1; winlist *ids = ( winlist * ) data; client *c = window_client ( ids->array[index] ); - if ( tokens ) { for ( int j = 0; match && tokens[j]; j++ ) { int test = 0; if ( !test && c->title[0] != '\0' ) { - char *sml = g_utf8_casefold ( c->title, -1 ); - char *key = g_utf8_collate_key ( sml, -1 ); + char *key = token_collate_key ( c->title, case_sensitive ); test = ( strstr ( key, tokens[j] ) != NULL ); - g_free ( sml ); g_free ( key ); + g_free ( key ); } if ( !test && c->class[0] != '\0' ) { - char *sml = g_utf8_casefold ( c->class, -1 ); - char *key = g_utf8_collate_key ( sml, -1 ); + char *key = token_collate_key ( c->title, case_sensitive ); test = ( strstr ( key, tokens[j] ) != NULL ); - g_free ( sml ); g_free ( key ); + g_free ( key ); } if ( !test && c->role[0] != '\0' ) { - char *sml = g_utf8_casefold ( c->role, -1 ); - char *key = g_utf8_collate_key ( sml, -1 ); + char *key = token_collate_key ( c->title, case_sensitive ); test = ( strstr ( key, tokens[j] ) != NULL ); - g_free ( sml ); g_free ( key ); + g_free ( key ); } if ( !test && c->name[0] != '\0' ) { - char *sml = g_utf8_casefold ( c->name, -1 ); - char *key = g_utf8_collate_key ( sml, -1 ); + char *key = token_collate_key ( c->title, case_sensitive ); test = ( strstr ( key, tokens[j] ) != NULL ); - g_free ( sml ); g_free ( key ); + g_free ( key ); } if ( test == 0 ) { @@ -1201,15 +1197,16 @@ static void menu_mouse_navigation ( MenuState *state, XButtonEvent *xbe ) } } -static void menu_refilter ( MenuState *state, char **lines, menu_match_cb mmc, void *mmc_data, int sorting ) +static void menu_refilter ( MenuState *state, char **lines, menu_match_cb mmc, void *mmc_data, + int sorting, int case_sensitive ) { unsigned int i, j = 0; if ( strlen ( state->text->text ) > 0 ) { - char **tokens = tokenize ( state->text->text ); + char **tokens = tokenize ( state->text->text, case_sensitive ); // input changed for ( i = 0; i < state->num_lines; i++ ) { - int match = mmc ( tokens, lines[i], i, mmc_data ); + int match = mmc ( tokens, lines[i], case_sensitive, i, mmc_data ); // If each token was matched, add it to list. if ( match ) { @@ -1551,7 +1548,7 @@ MenuReturn menu ( char **lines, unsigned int num_lines, char **input, char *prom while ( !state.quit ) { // If something changed, refilter the list. (paste or text entered) if ( state.refilter ) { - menu_refilter ( &state, lines, mmc, mmc_data, sorting ); + menu_refilter ( &state, lines, mmc, mmc_data, sorting, config.case_sensitive ); } // Update if requested. if ( state.update ) { @@ -1620,6 +1617,13 @@ MenuReturn menu ( char **lines, unsigned int num_lines, char **input, char *prom state.quit = TRUE; break; } + // Toggle case sensitivity. + else if ( key == XK_grave ) { + config.case_sensitive = !config.case_sensitive; + *( state.selected_line ) = 0; + state.refilter = TRUE; + state.update = TRUE; + } // Switcher short-cut else if ( ( ( ev.xkey.state & Mod1Mask ) == Mod1Mask ) && key >= XK_1 && key <= XK_9 ) { @@ -2209,6 +2213,9 @@ static void parse_cmd_options ( int argc, char ** argv ) if ( find_arg ( argc, argv, "-levenshtein-sort" ) >= 0 ) { config.levenshtein_sort = TRUE; } + if ( find_arg ( argc, argv, "-case-sensitive" ) >= 0 ) { + config.case_sensitive = TRUE; + } // Parse commandline arguments about behavior find_arg_str ( argc, argv, "-terminal", &( config.terminal_emulator ) ); diff --git a/source/run-dialog.c b/source/run-dialog.c index ec7d7c21..964aa0f3 100644 --- a/source/run-dialog.c +++ b/source/run-dialog.c @@ -311,7 +311,8 @@ SwitcherMode run_switcher_dialog ( char **input, G_GNUC_UNUSED void *data ) } int mretv = menu ( cmd_list, cmd_list_length, input, "run:", - NULL, &shift, token_match, NULL, &selected_line, config.levenshtein_sort ); + NULL, &shift, token_match, NULL, &selected_line, + config.levenshtein_sort ); if ( mretv == MENU_NEXT ) { retv = NEXT_DIALOG; diff --git a/source/ssh-dialog.c b/source/ssh-dialog.c index 9cf7f10d..44968ad5 100644 --- a/source/ssh-dialog.c +++ b/source/ssh-dialog.c @@ -225,7 +225,8 @@ SwitcherMode ssh_switcher_dialog ( char **input, G_GNUC_UNUSED void *data ) int shift = 0; int selected_line = 0; int mretv = menu ( cmd_list, cmd_list_length, input, "ssh:", - NULL, &shift, token_match, NULL, &selected_line, config.levenshtein_sort ); + NULL, &shift, token_match, NULL, &selected_line, + config.levenshtein_sort ); if ( mretv == MENU_NEXT ) { retv = NEXT_DIALOG; From 2098f1147f3c0aa8a206846668a98acdda2eb361 Mon Sep 17 00:00:00 2001 From: Edwin Pujols Date: Mon, 12 Jan 2015 15:56:41 -0400 Subject: [PATCH 2/3] Add Xresources option for case sensitivity. --- source/xrmoptions.c | 1 + 1 file changed, 1 insertion(+) diff --git a/source/xrmoptions.c b/source/xrmoptions.c index 26bc6daf..30e3e5d7 100644 --- a/source/xrmoptions.c +++ b/source/xrmoptions.c @@ -108,6 +108,7 @@ static XrmOption xrmOptions[] = { { xrm_Boolean, "disable-history", { .num = &config.disable_history }, NULL }, { xrm_Boolean, "levenshtein-sort", { .num = &config.levenshtein_sort }, NULL }, + { xrm_Boolean, "case-sensitive", { .num = &config.case_sensitive }, NULL }, /* Key bindings */ { xrm_String, "key", { .str = &config.window_key }, NULL }, { xrm_String, "rkey", { .str = &config.run_key }, NULL }, From 93a2738c8a42ab20cf4a6ecfa06bdfb01028aefe Mon Sep 17 00:00:00 2001 From: Edwin Pujols Date: Mon, 12 Jan 2015 19:01:16 -0400 Subject: [PATCH 3/3] Fix #102 - Visual indicator of case sensitivity. --- source/rofi.c | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/source/rofi.c b/source/rofi.c index 01faa0cc..36d0c75b 100644 --- a/source/rofi.c +++ b/source/rofi.c @@ -848,6 +848,7 @@ typedef struct MenuState // Entries textbox *text; textbox *prompt_tb; + textbox *case_indicator; textbox *arrowbox_top; textbox *arrowbox_bottom; textbox **boxes; @@ -883,6 +884,7 @@ static void menu_free_state ( MenuState *state ) { textbox_free ( state->text ); textbox_free ( state->prompt_tb ); + textbox_free ( state->case_indicator ); textbox_free ( state->arrowbox_bottom ); textbox_free ( state->arrowbox_top ); @@ -1294,8 +1296,9 @@ static void menu_update ( MenuState *state ) menu_hide_arrow_text ( state->filtered_lines, state->selected, state->max_elements, state->arrowbox_top, state->arrowbox_bottom ); - textbox_draw ( state->text ); + textbox_draw ( state->case_indicator ); textbox_draw ( state->prompt_tb ); + textbox_draw ( state->text ); menu_draw ( state ); menu_set_arrow_text ( state->filtered_lines, state->selected, state->max_elements, state->arrowbox_top, @@ -1410,16 +1413,24 @@ MenuReturn menu ( char **lines, unsigned int num_lines, char **input, char *prom // search text input // we need this at this point so we can get height. + state.case_indicator = textbox_create ( main_window, TB_AUTOHEIGHT | TB_AUTOWIDTH, + ( config.padding ), ( config.padding ), + 0, 0, + NORMAL, "*" ); + state.prompt_tb = textbox_create ( main_window, TB_AUTOHEIGHT | TB_AUTOWIDTH, - ( config.padding ), ( config.padding ), + ( config.padding ) + textbox_get_width ( state.case_indicator ), + ( config.padding ), 0, 0, NORMAL, prompt ); state.text = textbox_create ( main_window, TB_AUTOHEIGHT | TB_EDITABLE, - ( config.padding ) + textbox_get_width ( state.prompt_tb ), + ( config.padding ) + textbox_get_width ( state.prompt_tb ) + + textbox_get_width ( state.case_indicator ), ( config.padding ), ( ( config.hmode == TRUE ) ? state.element_width : ( state.w - ( 2 * ( config.padding ) ) ) ) - - textbox_get_width ( state.prompt_tb ), 1, + - textbox_get_width ( state.prompt_tb ) + - textbox_get_width ( state.case_indicator ), 1, NORMAL, ( input != NULL ) ? *input : "" ); @@ -1427,6 +1438,10 @@ MenuReturn menu ( char **lines, unsigned int num_lines, char **input, char *prom textbox_show ( state.text ); textbox_show ( state.prompt_tb ); + if ( config.case_sensitive ) { + textbox_show ( state.case_indicator ); + } + // Height of a row. int line_height = textbox_get_height ( state.text ); if ( config.menu_lines == 0 ) { @@ -1624,6 +1639,11 @@ MenuReturn menu ( char **lines, unsigned int num_lines, char **input, char *prom *( state.selected_line ) = 0; state.refilter = TRUE; state.update = TRUE; + if ( config.case_sensitive ) { + textbox_show ( state.case_indicator ); + } else { + textbox_hide ( state.case_indicator ); + } } // Switcher short-cut else if ( ( ( ev.xkey.state & Mod1Mask ) == Mod1Mask ) &&