1
0
Fork 0
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:
phanium 2025-01-28 00:52:32 +08:00 committed by GitHub
parent 3724b4dd31
commit 0c3cbd0cfb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 123 additions and 37 deletions

View file

@ -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 */

View file

@ -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'.

View file

@ -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.

View file

@ -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 */

View file

@ -144,6 +144,8 @@ struct RofiViewState {
/** Regexs used for matching */
rofi_int_matcher **tokens;
/** For case-sensitivity */
gboolean case_sensitive;
};
/** @} */
#endif

View file

@ -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++) {

View file

@ -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)) {

View file

@ -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

View file

@ -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},

View file

@ -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}",