Skip to content

Commit 14adf04

Browse files
committed
feat: add smartcase support like vim
1 parent 54ebfa7 commit 14adf04

File tree

10 files changed

+79
-38
lines changed

10 files changed

+79
-38
lines changed

config/config.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ Settings config = {
9292
.sorting_method = "normal",
9393
/** Case sensitivity of the search */
9494
.case_sensitive = FALSE,
95+
/** Case smart of the search */
96+
.case_smart = FALSE,
9597
/** Cycle through in the element list */
9698
.cycle = TRUE,
9799
/** Height of an element in #chars */

doc/rofi.1.markdown

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,12 @@ exec command. For that case, `#` can be used as a separator.
246246
Start in case-sensitive mode. This option can be changed at run-time using the
247247
`-kb-toggle-case-sensitivity` key binding.
248248

249+
`-case-smart`
250+
251+
Start in case-smart mode behave like vim's `smartcase`, which determines
252+
case-sensitivity by input. When enabled, this will suppress `-case-sensitive`
253+
config.
254+
249255
`-cycle`
250256

251257
Cycle through the result list. Default is 'true'.

include/helper.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,13 +200,15 @@ char *rofi_expand_path(const char *input);
200200
* @param needlelen The length of the needle
201201
* @param haystack The string to match against
202202
* @param haystacklen The length of the haystack
203+
* @param case_sensitive Whether case is significant.
203204
*
204205
* UTF-8 aware levenshtein distance calculation
205206
*
206207
* @returns the levenshtein distance between needle and haystack
207208
*/
208209
unsigned int levenshtein(const char *needle, const glong needlelen,
209-
const char *haystack, const glong haystacklen);
210+
const char *haystack, const glong haystacklen,
211+
int case_sensitive);
210212

211213
/**
212214
* @param data the unvalidated character array holding possible UTF-8 data
@@ -234,6 +236,7 @@ char *rofi_latin_to_utf8_strdup(const char *input, gssize length);
234236
* @param plen Pattern length.
235237
* @param str The input to match against pattern.
236238
* @param slen Length of str.
239+
* @param case_sensitive Whether case is significant.
237240
*
238241
* rofi_scorer_fuzzy_evaluate implements a global sequence alignment algorithm
239242
* to find the maximum accumulated score by aligning `pattern` to `str`. It
@@ -263,7 +266,7 @@ char *rofi_latin_to_utf8_strdup(const char *input, gssize length);
263266
* @returns the sorting weight.
264267
*/
265268
int rofi_scorer_fuzzy_evaluate(const char *pattern, glong plen, const char *str,
266-
glong slen);
269+
glong slen, int case_sensitive);
267270
/*@}*/
268271

269272
/**
@@ -353,6 +356,13 @@ cairo_surface_t *cairo_image_surface_create_from_svg(const gchar *file,
353356
*/
354357
void parse_ranges(char *input, rofi_range_pair **list, unsigned int *length);
355358

359+
/**
360+
* @param input String to parse
361+
*
362+
* @returns String matching should be case sensitive or insensitive
363+
*/
364+
int parse_case_sensitivity(char *input);
365+
356366
/**
357367
* @param format The format string used. See below for possible syntax.
358368
* @param string The selected entry.

include/settings.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ typedef struct {
115115

116116
/** Search case sensitivity */
117117
unsigned int case_sensitive;
118+
/** Smart case sensitivity like vim */
119+
unsigned int case_smart;
118120
/** Cycle through in the element list */
119121
unsigned int cycle;
120122
/** Height of an element in number of rows */

include/widgets/textbox.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ typedef struct {
6969
int tbft;
7070
int markup;
7171
int changed;
72+
int case_sensitive;
7273

7374
int blink;
7475
guint blink_timeout;

source/helper.c

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -768,7 +768,8 @@ char *rofi_expand_path(const char *input) {
768768
((a) < (b) ? ((a) < (c) ? (a) : (c)) : ((b) < (c) ? (b) : (c)))
769769

770770
unsigned int levenshtein(const char *needle, const glong needlelen,
771-
const char *haystack, const glong haystacklen) {
771+
const char *haystack, const glong haystacklen,
772+
int case_sensitive) {
772773
if (needlelen == G_MAXLONG) {
773774
// String to long, we cannot handle this.
774775
return UINT_MAX;
@@ -784,12 +785,12 @@ unsigned int levenshtein(const char *needle, const glong needlelen,
784785
const char *needles = needle;
785786
column[0] = x;
786787
gunichar haystackc = g_utf8_get_char(haystack);
787-
if (!config.case_sensitive) {
788+
if (!case_sensitive) {
788789
haystackc = g_unichar_tolower(haystackc);
789790
}
790791
for (glong y = 1, lastdiag = x - 1; y <= needlelen; y++) {
791792
gunichar needlec = g_utf8_get_char(needles);
792-
if (!config.case_sensitive) {
793+
if (!case_sensitive) {
793794
needlec = g_unichar_tolower(needlec);
794795
}
795796
unsigned int olddiag = column[y];
@@ -916,7 +917,7 @@ static int rofi_scorer_get_score_for(enum CharClass prev, enum CharClass curr) {
916917
}
917918

918919
int rofi_scorer_fuzzy_evaluate(const char *pattern, glong plen, const char *str,
919-
glong slen) {
920+
glong slen, int case_sensitive) {
920921
if (slen > FUZZY_SCORER_MAX_LENGTH) {
921922
return -MIN_SCORE;
922923
}
@@ -951,9 +952,8 @@ int rofi_scorer_fuzzy_evaluate(const char *pattern, glong plen, const char *str,
951952
left = dp[si];
952953
lefts = MAX(lefts + GAP_SCORE, left);
953954
sc = g_utf8_get_char(sit);
954-
if (config.case_sensitive
955-
? pc == sc
956-
: g_unichar_tolower(pc) == g_unichar_tolower(sc)) {
955+
if (case_sensitive ? pc == sc
956+
: g_unichar_tolower(pc) == g_unichar_tolower(sc)) {
957957
int t = score[si] * (pstart ? PATTERN_START_MULTIPLIER
958958
: PATTERN_NON_START_MULTIPLIER);
959959
dp[si] = pfirst ? LEADING_GAP_SCORE * si + t
@@ -1232,6 +1232,16 @@ void parse_ranges(char *input, rofi_range_pair **list, unsigned int *length) {
12321232
}
12331233
}
12341234
}
1235+
1236+
int parse_case_sensitivity(char *input) {
1237+
gchar *lowercase = g_utf8_strdown(input, -1);
1238+
int case_sensitive = config.case_smart ? g_utf8_collate(input, lowercase)
1239+
: config.case_sensitive;
1240+
g_free(lowercase);
1241+
1242+
return case_sensitive;
1243+
}
1244+
12351245
void rofi_output_formatted_line(const char *format, const char *string,
12361246
int selected_line, const char *filter) {
12371247
for (int i = 0; format && format[i]; i++) {

source/modes/dmenu.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -954,7 +954,8 @@ int dmenu_mode_dialog(void) {
954954
char *select = NULL;
955955
find_arg_str("-select", &select);
956956
if (select != NULL) {
957-
rofi_int_matcher **tokens = helper_tokenize(select, config.case_sensitive);
957+
rofi_int_matcher **tokens =
958+
helper_tokenize(select, parse_case_sensitivity(select));
958959
unsigned int i = 0;
959960
for (i = 0; i < cmd_list_length; i++) {
960961
if (helper_token_match(tokens, cmd_list[i].entry)) {
@@ -965,8 +966,9 @@ int dmenu_mode_dialog(void) {
965966
helper_tokenize_free(tokens);
966967
}
967968
if (find_arg("-dump") >= 0) {
968-
rofi_int_matcher **tokens = helper_tokenize(
969-
config.filter ? config.filter : "", config.case_sensitive);
969+
char *filter = config.filter ? config.filter : "";
970+
rofi_int_matcher **tokens =
971+
helper_tokenize(filter, parse_case_sensitivity(filter));
970972
unsigned int i = 0;
971973
for (i = 0; i < cmd_list_length; i++) {
972974
if (tokens == NULL || helper_token_match(tokens, cmd_list[i].entry)) {

source/view.c

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,8 @@ void rofi_view_get_current_monitor(int *width, int *height) {
188188
*height = CacheState.mon.h;
189189
}
190190
}
191-
static char *get_matching_state(void) {
192-
if (config.case_sensitive) {
191+
static char *get_matching_state(RofiViewState *state) {
192+
if (state->text->case_sensitive) {
193193
if (config.sort) {
194194
return "±";
195195
}
@@ -762,12 +762,13 @@ static void filter_elements(thread_state *ts,
762762
glong slen = g_utf8_strlen(str, -1);
763763
switch (config.sorting_method_enum) {
764764
case SORT_FZF:
765-
t->state->distance[i] =
766-
rofi_scorer_fuzzy_evaluate(t->pattern, t->plen, str, slen);
765+
t->state->distance[i] = rofi_scorer_fuzzy_evaluate(
766+
t->pattern, t->plen, str, slen, t->state->text->case_sensitive);
767767
break;
768768
case SORT_NORMAL:
769769
default:
770-
t->state->distance[i] = levenshtein(t->pattern, t->plen, str, slen);
770+
t->state->distance[i] = levenshtein(t->pattern, t->plen, str, slen,
771+
t->state->text->case_sensitive);
771772
break;
772773
}
773774
g_free(str);
@@ -1449,7 +1450,8 @@ static gboolean rofi_view_refilter_real(RofiViewState *state) {
14491450
unsigned int j = 0;
14501451
gchar *pattern = mode_preprocess_input(state->sw, state->text->text);
14511452
glong plen = pattern ? g_utf8_strlen(pattern, -1) : 0;
1452-
state->tokens = helper_tokenize(pattern, config.case_sensitive);
1453+
state->text->case_sensitive = parse_case_sensitivity(state->text->text);
1454+
state->tokens = helper_tokenize(pattern, state->text->case_sensitive);
14531455
/**
14541456
* On long lists it can be beneficial to parallelize.
14551457
* If number of threads is 1, no thread is spawn.
@@ -1478,7 +1480,7 @@ static gboolean rofi_view_refilter_real(RofiViewState *state) {
14781480
states[i].acount = &count;
14791481
states[i].plen = plen;
14801482
states[i].pattern = pattern;
1481-
states[i].st.callback = filter_elements;
1483+
states[i].st.callback = filter_elements; // here
14821484
states[i].st.free = NULL;
14831485
states[i].st.priority = G_PRIORITY_HIGH;
14841486
if (i > 0) {
@@ -1676,7 +1678,7 @@ static void rofi_view_trigger_global_action(KeyBindingAction action) {
16761678
if (state->case_indicator != NULL) {
16771679
config.sort = !config.sort;
16781680
state->refilter = TRUE;
1679-
textbox_text(state->case_indicator, get_matching_state());
1681+
textbox_text(state->case_indicator, get_matching_state(state));
16801682
}
16811683
break;
16821684
case MODE_PREVIOUS:
@@ -1706,7 +1708,7 @@ static void rofi_view_trigger_global_action(KeyBindingAction action) {
17061708
config.case_sensitive = !config.case_sensitive;
17071709
(state->selected_line) = 0;
17081710
state->refilter = TRUE;
1709-
textbox_text(state->case_indicator, get_matching_state());
1711+
textbox_text(state->case_indicator, get_matching_state(state));
17101712
}
17111713
break;
17121714
// Special delete entry command.
@@ -2330,7 +2332,7 @@ static void rofi_view_add_widget(RofiViewState *state, widget *parent_widget,
23302332
TB_AUTOWIDTH | TB_AUTOHEIGHT, NORMAL, "*", 0, 0);
23312333
// Add small separator between case indicator and text box.
23322334
box_add((box *)parent_widget, WIDGET(state->case_indicator), FALSE);
2333-
textbox_text(state->case_indicator, get_matching_state());
2335+
textbox_text(state->case_indicator, get_matching_state(state));
23342336
}
23352337
/**
23362338
* ENTRY BOX

source/xrmoptions.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,12 @@ static XrmOption xrmOptions[] = {
253253
NULL,
254254
"Set case-sensitivity",
255255
CONFIG_DEFAULT},
256+
{xrm_Boolean,
257+
"case-smart",
258+
{.num = &config.case_smart},
259+
NULL,
260+
"Set smartcase like vim (determine case-sensitivity by input)",
261+
CONFIG_DEFAULT},
256262
{xrm_Boolean,
257263
"cycle",
258264
{.num = &config.cycle},

test/helper-test.c

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -142,25 +142,25 @@ int main(int argc, char **argv) {
142142
*/
143143

144144
TASSERT(levenshtein("aap", g_utf8_strlen("aap", -1), "aap",
145-
g_utf8_strlen("aap", -1)) == 0);
145+
g_utf8_strlen("aap", -1), 0) == 0);
146146
TASSERT(levenshtein("aap", g_utf8_strlen("aap", -1), "aap ",
147-
g_utf8_strlen("aap ", -1)) == 1);
147+
g_utf8_strlen("aap ", -1), 0) == 1);
148148
TASSERT(levenshtein("aap ", g_utf8_strlen("aap ", -1), "aap",
149-
g_utf8_strlen("aap", -1)) == 1);
149+
g_utf8_strlen("aap", -1), 0) == 1);
150150
TASSERTE(levenshtein("aap", g_utf8_strlen("aap", -1), "aap noot",
151-
g_utf8_strlen("aap noot", -1)),
151+
g_utf8_strlen("aap noot", -1), 0),
152152
5u);
153153
TASSERTE(levenshtein("aap", g_utf8_strlen("aap", -1), "noot aap",
154-
g_utf8_strlen("noot aap", -1)),
154+
g_utf8_strlen("noot aap", -1), 0),
155155
5u);
156156
TASSERTE(levenshtein("aap", g_utf8_strlen("aap", -1), "noot aap mies",
157-
g_utf8_strlen("noot aap mies", -1)),
157+
g_utf8_strlen("noot aap mies", -1), 0),
158158
10u);
159159
TASSERTE(levenshtein("noot aap mies", g_utf8_strlen("noot aap mies", -1),
160-
"aap", g_utf8_strlen("aap", -1)),
160+
"aap", g_utf8_strlen("aap", -1), 0),
161161
10u);
162162
TASSERTE(levenshtein("otp", g_utf8_strlen("otp", -1), "noot aap",
163-
g_utf8_strlen("noot aap", -1)),
163+
g_utf8_strlen("noot aap", -1), 0),
164164
5u);
165165
/**
166166
* Quick converision check.
@@ -192,17 +192,17 @@ int main(int argc, char **argv) {
192192
}
193193
{
194194
TASSERTL(
195-
rofi_scorer_fuzzy_evaluate("aap noot mies", 12, "aap noot mies", 12),
195+
rofi_scorer_fuzzy_evaluate("aap noot mies", 12, "aap noot mies", 12, 0),
196196
-605);
197-
TASSERTL(rofi_scorer_fuzzy_evaluate("anm", 3, "aap noot mies", 12), -155);
198-
TASSERTL(rofi_scorer_fuzzy_evaluate("blu", 3, "aap noot mies", 12),
197+
TASSERTL(rofi_scorer_fuzzy_evaluate("anm", 3, "aap noot mies", 12, 0),
198+
-155);
199+
TASSERTL(rofi_scorer_fuzzy_evaluate("blu", 3, "aap noot mies", 12, 0),
199200
1073741824);
200-
config.case_sensitive = TRUE;
201-
TASSERTL(rofi_scorer_fuzzy_evaluate("Anm", 3, "aap noot mies", 12),
201+
TASSERTL(rofi_scorer_fuzzy_evaluate("Anm", 3, "aap noot mies", 12, 1),
202202
1073741754);
203-
config.case_sensitive = FALSE;
204-
TASSERTL(rofi_scorer_fuzzy_evaluate("Anm", 3, "aap noot mies", 12), -155);
205-
TASSERTL(rofi_scorer_fuzzy_evaluate("aap noot mies", 12, "Anm", 3),
203+
TASSERTL(rofi_scorer_fuzzy_evaluate("Anm", 3, "aap noot mies", 12, 0),
204+
-155);
205+
TASSERTL(rofi_scorer_fuzzy_evaluate("aap noot mies", 12, "Anm", 3, 0),
206206
1073741824);
207207
}
208208

0 commit comments

Comments
 (0)