Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add smartcase support like vim #2060

Open
wants to merge 1 commit into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion config/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ Settings config = {
/** Use levenshtein sorting when matching */
.sorting_method = "normal",
/** Case sensitivity of the search */
.case_sensitive = FALSE,
.case_sensitive = CASE_SMART,
/** Cycle through in the element list */
.cycle = TRUE,
/** Height of an element in #chars */
Expand Down
14 changes: 12 additions & 2 deletions include/helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,13 +200,15 @@ char *rofi_expand_path(const char *input);
* @param needlelen The length of the needle
* @param haystack The string to match against
* @param haystacklen The length of the haystack
* @param case_sensitive Whether case is significant.
*
* UTF-8 aware levenshtein distance calculation
*
* @returns the levenshtein distance between needle and haystack
*/
unsigned int levenshtein(const char *needle, const glong needlelen,
const char *haystack, const glong haystacklen);
const char *haystack, const glong haystacklen,
int case_sensitive);

/**
* @param data the unvalidated character array holding possible UTF-8 data
Expand Down Expand Up @@ -234,6 +236,7 @@ char *rofi_latin_to_utf8_strdup(const char *input, gssize length);
* @param plen Pattern length.
* @param str The input to match against pattern.
* @param slen Length of str.
* @param case_sensitive Whether case is significant.
*
* rofi_scorer_fuzzy_evaluate implements a global sequence alignment algorithm
* to find the maximum accumulated score by aligning `pattern` to `str`. It
Expand Down Expand Up @@ -263,7 +266,7 @@ char *rofi_latin_to_utf8_strdup(const char *input, gssize length);
* @returns the sorting weight.
*/
int rofi_scorer_fuzzy_evaluate(const char *pattern, glong plen, const char *str,
glong slen);
glong slen, int case_sensitive);
/*@}*/

/**
Expand Down Expand Up @@ -353,6 +356,13 @@ cairo_surface_t *cairo_image_surface_create_from_svg(const gchar *file,
*/
void parse_ranges(char *input, rofi_range_pair **list, unsigned int *length);

/**
* @param input String to parse
*
* @returns String matching should be case sensitive or insensitive
*/
int parse_case_sensitivity(char *input);

/**
* @param format The format string used. See below for possible syntax.
* @param string The selected entry.
Expand Down
9 changes: 8 additions & 1 deletion include/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ typedef enum {
*/
typedef enum { SORT_NORMAL = 0, SORT_FZF = 1 } SortingMethod;

typedef enum {
CASE_SMART = 0,
CASE_INSENSITIVE = 1,
CASE_SENSITIVE = 2,
} Case;

/**
* Settings structure holding all (static) configurable options.
* @ingroup CONFIGURATION
Expand Down Expand Up @@ -114,7 +120,8 @@ typedef struct {
char *drun_url_launcher;

/** Search case sensitivity */
unsigned int case_sensitive;
Case case_sensitive;

/** Cycle through in the element list */
unsigned int cycle;
/** Height of an element in number of rows */
Expand Down
1 change: 1 addition & 0 deletions include/widgets/textbox.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ typedef struct {
int tbft;
int markup;
int changed;
int case_sensitive;

int blink;
guint blink_timeout;
Expand Down
25 changes: 18 additions & 7 deletions source/helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,8 @@ char *rofi_expand_path(const char *input) {
((a) < (b) ? ((a) < (c) ? (a) : (c)) : ((b) < (c) ? (b) : (c)))

unsigned int levenshtein(const char *needle, const glong needlelen,
const char *haystack, const glong haystacklen) {
const char *haystack, const glong haystacklen,
int case_sensitive) {
if (needlelen == G_MAXLONG) {
// String to long, we cannot handle this.
return UINT_MAX;
Expand All @@ -784,12 +785,12 @@ unsigned int levenshtein(const char *needle, const glong needlelen,
const char *needles = needle;
column[0] = x;
gunichar haystackc = g_utf8_get_char(haystack);
if (!config.case_sensitive) {
if (!case_sensitive) {
haystackc = g_unichar_tolower(haystackc);
}
for (glong y = 1, lastdiag = x - 1; y <= needlelen; y++) {
gunichar needlec = g_utf8_get_char(needles);
if (!config.case_sensitive) {
if (!case_sensitive) {
needlec = g_unichar_tolower(needlec);
}
unsigned int olddiag = column[y];
Expand Down Expand Up @@ -916,7 +917,7 @@ static int rofi_scorer_get_score_for(enum CharClass prev, enum CharClass curr) {
}

int rofi_scorer_fuzzy_evaluate(const char *pattern, glong plen, const char *str,
glong slen) {
glong slen, int case_sensitive) {
if (slen > FUZZY_SCORER_MAX_LENGTH) {
return -MIN_SCORE;
}
Expand Down Expand Up @@ -951,9 +952,8 @@ int rofi_scorer_fuzzy_evaluate(const char *pattern, glong plen, const char *str,
left = dp[si];
lefts = MAX(lefts + GAP_SCORE, left);
sc = g_utf8_get_char(sit);
if (config.case_sensitive
? pc == sc
: g_unichar_tolower(pc) == g_unichar_tolower(sc)) {
if (case_sensitive ? pc == sc
: g_unichar_tolower(pc) == g_unichar_tolower(sc)) {
int t = score[si] * (pstart ? PATTERN_START_MULTIPLIER
: PATTERN_NON_START_MULTIPLIER);
dp[si] = pfirst ? LEADING_GAP_SCORE * si + t
Expand Down Expand Up @@ -1232,6 +1232,17 @@ void parse_ranges(char *input, rofi_range_pair **list, unsigned int *length) {
}
}
}

int parse_case_sensitivity(char *input) {
gchar *lowercase = g_utf8_strdown(input, -1);
int case_sensitive =
config.case_sensitive == CASE_SENSITIVE ||
(config.case_sensitive == CASE_SMART && g_utf8_collate(input, lowercase));
g_free(lowercase);

return case_sensitive;
}

void rofi_output_formatted_line(const char *format, const char *string,
int selected_line, const char *filter) {
for (int i = 0; format && format[i]; i++) {
Expand Down
14 changes: 9 additions & 5 deletions source/modes/dmenu.c
Original file line number Diff line number Diff line change
Expand Up @@ -598,10 +598,12 @@ static int dmenu_mode_init(Mode *sw) {
config.location = 6;
}
/* -i case insensitive */
config.case_sensitive = TRUE;
/* +i case sensitive */
config.case_sensitive = CASE_SMART;
if (find_arg("-i") >= 0) {
config.case_sensitive = FALSE;
config.case_sensitive = CASE_INSENSITIVE;
}

if (pd->async) {
pd->fd = STDIN_FILENO;
if (find_arg_str("-input", &str)) {
Expand Down Expand Up @@ -954,7 +956,8 @@ int dmenu_mode_dialog(void) {
char *select = NULL;
find_arg_str("-select", &select);
if (select != NULL) {
rofi_int_matcher **tokens = helper_tokenize(select, config.case_sensitive);
int case_sensitive = parse_case_sensitivity(select);
rofi_int_matcher **tokens = helper_tokenize(select, case_sensitive);
unsigned int i = 0;
for (i = 0; i < cmd_list_length; i++) {
if (helper_token_match(tokens, cmd_list[i].entry)) {
Expand All @@ -965,8 +968,9 @@ int dmenu_mode_dialog(void) {
helper_tokenize_free(tokens);
}
if (find_arg("-dump") >= 0) {
rofi_int_matcher **tokens = helper_tokenize(
config.filter ? config.filter : "", config.case_sensitive);
char *filter = config.filter ? config.filter : "";
rofi_int_matcher **tokens =
helper_tokenize(filter, parse_case_sensitivity(filter));
unsigned int i = 0;
for (i = 0; i < cmd_list_length; i++) {
if (tokens == NULL || helper_token_match(tokens, cmd_list[i].entry)) {
Expand Down
2 changes: 2 additions & 0 deletions source/rofi.c
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ static void run_mode_index(ModeMode mode) {
curr_mode = mode;
RofiViewState *state =
rofi_view_create(modes[mode], config.filter, 0, process_result);
// fprintf(stderr, "DEBUGPRINT[44]: rofi.c:220: filter=%s\n", config.filter);
// return;

// User can pre-select a row.
if (find_arg("-selected-row") >= 0) {
Expand Down
27 changes: 16 additions & 11 deletions source/view.c
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,8 @@ void rofi_view_get_current_monitor(int *width, int *height) {
*height = CacheState.mon.h;
}
}
static char *get_matching_state(void) {
if (config.case_sensitive) {
static char *get_matching_state(RofiViewState *state) {
if (state->text->case_sensitive) {
if (config.sort) {
return "±";
}
Expand Down Expand Up @@ -762,12 +762,13 @@ static void filter_elements(thread_state *ts,
glong slen = g_utf8_strlen(str, -1);
switch (config.sorting_method_enum) {
case SORT_FZF:
t->state->distance[i] =
rofi_scorer_fuzzy_evaluate(t->pattern, t->plen, str, slen);
t->state->distance[i] = rofi_scorer_fuzzy_evaluate(
t->pattern, t->plen, str, slen, t->state->text->case_sensitive);
break;
case SORT_NORMAL:
default:
t->state->distance[i] = levenshtein(t->pattern, t->plen, str, slen);
t->state->distance[i] = levenshtein(t->pattern, t->plen, str, slen,
t->state->text->case_sensitive);
break;
}
g_free(str);
Expand Down Expand Up @@ -1445,11 +1446,12 @@ static gboolean rofi_view_refilter_real(RofiViewState *state) {
TICK_N("Filter tokenize");
if (state->text && strlen(state->text->text) > 0) {

state->text->case_sensitive = parse_case_sensitivity(state->text->text);
listview_set_filtered(state->list_view, TRUE);
unsigned int j = 0;
gchar *pattern = mode_preprocess_input(state->sw, state->text->text);
glong plen = pattern ? g_utf8_strlen(pattern, -1) : 0;
state->tokens = helper_tokenize(pattern, config.case_sensitive);
state->tokens = helper_tokenize(pattern, state->text->case_sensitive);
/**
* On long lists it can be beneficial to parallelize.
* If number of threads is 1, no thread is spawn.
Expand Down Expand Up @@ -1478,7 +1480,7 @@ static gboolean rofi_view_refilter_real(RofiViewState *state) {
states[i].acount = &count;
states[i].plen = plen;
states[i].pattern = pattern;
states[i].st.callback = filter_elements;
states[i].st.callback = filter_elements; // here
states[i].st.free = NULL;
states[i].st.priority = G_PRIORITY_HIGH;
if (i > 0) {
Expand Down Expand Up @@ -1676,7 +1678,7 @@ static void rofi_view_trigger_global_action(KeyBindingAction action) {
if (state->case_indicator != NULL) {
config.sort = !config.sort;
state->refilter = TRUE;
textbox_text(state->case_indicator, get_matching_state());
textbox_text(state->case_indicator, get_matching_state(state));
}
break;
case MODE_PREVIOUS:
Expand All @@ -1703,10 +1705,13 @@ static void rofi_view_trigger_global_action(KeyBindingAction action) {
// Toggle case sensitivity.
case TOGGLE_CASE_SENSITIVITY:
if (state->case_indicator != NULL) {
config.case_sensitive = !config.case_sensitive;
if (config.case_sensitive == CASE_SENSITIVE)
config.case_sensitive = CASE_INSENSITIVE;
else
config.case_sensitive = CASE_SENSITIVE;
(state->selected_line) = 0;
state->refilter = TRUE;
textbox_text(state->case_indicator, get_matching_state());
textbox_text(state->case_indicator, get_matching_state(state));
}
break;
// Special delete entry command.
Expand Down Expand Up @@ -2330,7 +2335,7 @@ static void rofi_view_add_widget(RofiViewState *state, widget *parent_widget,
TB_AUTOWIDTH | TB_AUTOHEIGHT, NORMAL, "*", 0, 0);
// Add small separator between case indicator and text box.
box_add((box *)parent_widget, WIDGET(state->case_indicator), FALSE);
textbox_text(state->case_indicator, get_matching_state());
textbox_text(state->case_indicator, get_matching_state(state));
}
/**
* ENTRY BOX
Expand Down
32 changes: 16 additions & 16 deletions test/helper-test.c
Original file line number Diff line number Diff line change
Expand Up @@ -142,25 +142,25 @@ int main(int argc, char **argv) {
*/

TASSERT(levenshtein("aap", g_utf8_strlen("aap", -1), "aap",
g_utf8_strlen("aap", -1)) == 0);
g_utf8_strlen("aap", -1), 0) == 0);
TASSERT(levenshtein("aap", g_utf8_strlen("aap", -1), "aap ",
g_utf8_strlen("aap ", -1)) == 1);
g_utf8_strlen("aap ", -1), 0) == 1);
TASSERT(levenshtein("aap ", g_utf8_strlen("aap ", -1), "aap",
g_utf8_strlen("aap", -1)) == 1);
g_utf8_strlen("aap", -1), 0) == 1);
TASSERTE(levenshtein("aap", g_utf8_strlen("aap", -1), "aap noot",
g_utf8_strlen("aap noot", -1)),
g_utf8_strlen("aap noot", -1), 0),
5u);
TASSERTE(levenshtein("aap", g_utf8_strlen("aap", -1), "noot aap",
g_utf8_strlen("noot aap", -1)),
g_utf8_strlen("noot aap", -1), 0),
5u);
TASSERTE(levenshtein("aap", g_utf8_strlen("aap", -1), "noot aap mies",
g_utf8_strlen("noot aap mies", -1)),
g_utf8_strlen("noot aap mies", -1), 0),
10u);
TASSERTE(levenshtein("noot aap mies", g_utf8_strlen("noot aap mies", -1),
"aap", g_utf8_strlen("aap", -1)),
"aap", g_utf8_strlen("aap", -1), 0),
10u);
TASSERTE(levenshtein("otp", g_utf8_strlen("otp", -1), "noot aap",
g_utf8_strlen("noot aap", -1)),
g_utf8_strlen("noot aap", -1), 0),
5u);
/**
* Quick converision check.
Expand Down Expand Up @@ -192,17 +192,17 @@ int main(int argc, char **argv) {
}
{
TASSERTL(
rofi_scorer_fuzzy_evaluate("aap noot mies", 12, "aap noot mies", 12),
rofi_scorer_fuzzy_evaluate("aap noot mies", 12, "aap noot mies", 12, 0),
-605);
TASSERTL(rofi_scorer_fuzzy_evaluate("anm", 3, "aap noot mies", 12), -155);
TASSERTL(rofi_scorer_fuzzy_evaluate("blu", 3, "aap noot mies", 12),
TASSERTL(rofi_scorer_fuzzy_evaluate("anm", 3, "aap noot mies", 12, 0),
-155);
TASSERTL(rofi_scorer_fuzzy_evaluate("blu", 3, "aap noot mies", 12, 0),
1073741824);
config.case_sensitive = TRUE;
TASSERTL(rofi_scorer_fuzzy_evaluate("Anm", 3, "aap noot mies", 12),
TASSERTL(rofi_scorer_fuzzy_evaluate("Anm", 3, "aap noot mies", 12, 1),
1073741754);
config.case_sensitive = FALSE;
TASSERTL(rofi_scorer_fuzzy_evaluate("Anm", 3, "aap noot mies", 12), -155);
TASSERTL(rofi_scorer_fuzzy_evaluate("aap noot mies", 12, "Anm", 3),
TASSERTL(rofi_scorer_fuzzy_evaluate("Anm", 3, "aap noot mies", 12, 0),
-155);
TASSERTL(rofi_scorer_fuzzy_evaluate("aap noot mies", 12, "Anm", 3, 0),
1073741824);
}

Expand Down
Loading