mirror of
https://github.com/davatorium/rofi.git
synced 2025-01-27 15:25:24 -05:00
feat: add smartcase support like vim (#2060)
This commit is contained in:
parent
3724b4dd31
commit
0c3cbd0cfb
10 changed files with 123 additions and 37 deletions
|
@ -104,6 +104,8 @@ Settings config = {
|
|||
.sorting_method = "normal",
|
||||
/** Case sensitivity of the search */
|
||||
.case_sensitive = FALSE,
|
||||
/** Case smart of the search */
|
||||
.case_smart = FALSE,
|
||||
/** Cycle through in the element list */
|
||||
.cycle = TRUE,
|
||||
/** Height of an element in #chars */
|
||||
|
|
|
@ -246,6 +246,12 @@ exec command. For that case, `#` can be used as a separator.
|
|||
Start in case-sensitive mode. This option can be changed at run-time using the
|
||||
`-kb-toggle-case-sensitivity` key binding.
|
||||
|
||||
`-case-smart`
|
||||
|
||||
Start in case-smart mode behave like vim's `smartcase`, which determines
|
||||
case-sensitivity by input. When enabled, this will suppress `-case-sensitive`
|
||||
config.
|
||||
|
||||
`-cycle`
|
||||
|
||||
Cycle through the result list. Default is 'true'.
|
||||
|
|
|
@ -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,
|
||||
const int case_sensitive);
|
||||
|
||||
/**
|
||||
* @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);
|
|||
* @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
|
||||
|
@ -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, const int case_sensitive);
|
||||
/*@}*/
|
||||
|
||||
/**
|
||||
|
@ -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(const char *input);
|
||||
|
||||
/**
|
||||
* @param format The format string used. See below for possible syntax.
|
||||
* @param string The selected entry.
|
||||
|
|
|
@ -127,6 +127,8 @@ typedef struct {
|
|||
|
||||
/** Search case sensitivity */
|
||||
unsigned int case_sensitive;
|
||||
/** Smart case sensitivity like vim */
|
||||
unsigned int case_smart;
|
||||
/** Cycle through in the element list */
|
||||
unsigned int cycle;
|
||||
/** Height of an element in number of rows */
|
||||
|
|
|
@ -144,6 +144,8 @@ struct RofiViewState {
|
|||
|
||||
/** Regexs used for matching */
|
||||
rofi_int_matcher **tokens;
|
||||
/** For case-sensitivity */
|
||||
gboolean case_sensitive;
|
||||
};
|
||||
/** @} */
|
||||
#endif
|
||||
|
|
|
@ -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;
|
||||
|
@ -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];
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -1247,6 +1247,28 @@ void parse_ranges(char *input, rofi_range_pair **list, unsigned int *length) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
int parse_case_sensitivity(const char *input) {
|
||||
int case_sensitive = config.case_sensitive;
|
||||
if (config.case_smart) {
|
||||
// By default case is false, unless the search query has a
|
||||
// uppercase in it?
|
||||
case_sensitive = FALSE;
|
||||
const char *end;
|
||||
if (g_utf8_validate(input, -1, &end)) {
|
||||
for (const char *c = (input); !case_sensitive && c != NULL && *c;
|
||||
c = g_utf8_next_char(c)) {
|
||||
gunichar uc = g_utf8_get_char(c);
|
||||
if (g_unichar_isupper(uc)) {
|
||||
case_sensitive = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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++) {
|
||||
|
|
|
@ -954,7 +954,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);
|
||||
rofi_int_matcher **tokens =
|
||||
helper_tokenize(select, parse_case_sensitivity(select));
|
||||
unsigned int i = 0;
|
||||
for (i = 0; i < cmd_list_length; i++) {
|
||||
if (helper_token_match(tokens, cmd_list[i].entry)) {
|
||||
|
@ -965,8 +966,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)) {
|
||||
|
|
|
@ -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->case_sensitive) {
|
||||
if (config.sort) {
|
||||
return "±";
|
||||
}
|
||||
|
@ -775,12 +775,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->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->case_sensitive);
|
||||
break;
|
||||
}
|
||||
g_free(str);
|
||||
|
@ -1482,7 +1483,12 @@ static gboolean rofi_view_refilter_real(RofiViewState *state) {
|
|||
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->case_sensitive = parse_case_sensitivity(state->text->text);
|
||||
state->tokens = helper_tokenize(pattern, state->case_sensitive);
|
||||
|
||||
if ( config.case_smart && state->case_indicator ) {
|
||||
textbox_text(state->case_indicator, get_matching_state(state));
|
||||
}
|
||||
/**
|
||||
* On long lists it can be beneficial to parallelize.
|
||||
* If number of threads is 1, no thread is spawn.
|
||||
|
@ -1709,7 +1715,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:
|
||||
|
@ -1739,7 +1745,7 @@ static void rofi_view_trigger_global_action(KeyBindingAction action) {
|
|||
config.case_sensitive = !config.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.
|
||||
|
@ -2397,7 +2403,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
|
||||
|
|
|
@ -290,6 +290,12 @@ static XrmOption xrmOptions[] = {
|
|||
NULL,
|
||||
"Set case-sensitivity",
|
||||
CONFIG_DEFAULT},
|
||||
{xrm_Boolean,
|
||||
"case-smart",
|
||||
{.num = &config.case_smart},
|
||||
NULL,
|
||||
"Set smartcase like vim (determine case-sensitivity by input)",
|
||||
CONFIG_DEFAULT},
|
||||
{xrm_Boolean,
|
||||
"cycle",
|
||||
{.num = &config.cycle},
|
||||
|
|
|
@ -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.
|
||||
|
@ -192,20 +192,48 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Case sensitivity
|
||||
*/
|
||||
{
|
||||
int case_smart = config.case_smart;
|
||||
int case_sensitive = config.case_sensitive;
|
||||
{
|
||||
config.case_smart = FALSE;
|
||||
config.case_sensitive = FALSE;
|
||||
TASSERT(parse_case_sensitivity("all lower case 你好") == 0);
|
||||
TASSERT(parse_case_sensitivity("not All lowEr Case 你好") == 0);
|
||||
config.case_sensitive = TRUE;
|
||||
TASSERT(parse_case_sensitivity("all lower case 你好") == 1);
|
||||
TASSERT(parse_case_sensitivity("not All lowEr Case 你好") == 1);
|
||||
}
|
||||
{
|
||||
config.case_smart = TRUE;
|
||||
config.case_sensitive = TRUE;
|
||||
TASSERT(parse_case_sensitivity("all lower case") == 0);
|
||||
TASSERT(parse_case_sensitivity("AAAAAAAAAAAA") == 1);
|
||||
config.case_sensitive = FALSE;
|
||||
TASSERT(parse_case_sensitivity("all lower case 你好") == 0);
|
||||
TASSERT(parse_case_sensitivity("not All lowEr Case 你好") == 1);
|
||||
}
|
||||
config.case_smart = case_smart;
|
||||
config.case_sensitive = case_sensitive;
|
||||
}
|
||||
|
||||
char *a;
|
||||
a = helper_string_replace_if_exists(
|
||||
"{terminal} [-t {title} blub ]-e {cmd}", "{cmd}", "aap", "{title}",
|
||||
|
|
Loading…
Add table
Reference in a new issue