From 77cd2b32c7321430565a9108162eb25588238bef Mon Sep 17 00:00:00 2001 From: "\"Michael Hauser-Raspe\"" Date: Sun, 26 Jun 2022 01:51:48 +0100 Subject: [PATCH] Extract text matching to it's own file. --- inc/match.h | 40 +++++ inc/utils.h | 7 - meson.build | 1 + src/match.c | 412 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/utils.c | 175 ---------------------- src/wofi.c | 268 ++-------------------------------- 6 files changed, 462 insertions(+), 441 deletions(-) create mode 100644 inc/match.h create mode 100644 src/match.c diff --git a/inc/match.h b/inc/match.h new file mode 100644 index 0000000..9874b36 --- /dev/null +++ b/inc/match.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2019-2022 Scoopta + * This file is part of Wofi + * Wofi is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Wofi is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Wofi. If not, see . + */ + +#ifndef MATCH_H +#define MATCH_H + +#include +#include + +typedef double score_t; +#define SCORE_MAX INFINITY +#define SCORE_MIN -INFINITY +#define MATCH_FUZZY_MAX_LEN 256 +#define MAX_MULTI_CONTAINS_FILTER_SIZE 256 + +enum matching_mode { + MATCHING_MODE_CONTAINS, + MATCHING_MODE_MULTI_CONTAINS, + MATCHING_MODE_FUZZY +}; + +int sort_for_matching_mode(const char *text1, const char *text2, int fallback, + enum matching_mode match_type, const char *filter, bool insensitive); + +bool match_for_matching_mode(const char* filter, const char* text, enum matching_mode matching, bool insensitive); +#endif diff --git a/inc/utils.h b/inc/utils.h index f995835..9b88bd9 100644 --- a/inc/utils.h +++ b/inc/utils.h @@ -22,11 +22,6 @@ #include -typedef double score_t; -#define SCORE_MAX INFINITY -#define SCORE_MIN -INFINITY -#define MATCH_FUZZY_MAX_LEN 256 - time_t utils_get_time_millis(void); void utils_sleep_millis(time_t millis); @@ -39,8 +34,6 @@ size_t utils_min3(size_t n1, size_t n2, size_t n3); size_t utils_distance(const char* haystack, const char* needle); -score_t utils_fuzzy_score(const char *haystack, const char *needle); - void utils_mkdir(char *path, mode_t mode); #endif diff --git a/meson.build b/meson.build index f37b154..e141508 100644 --- a/meson.build +++ b/meson.build @@ -23,6 +23,7 @@ add_project_link_arguments('-rdynamic', language : 'c') sources = ['src/config.c', 'src/main.c', 'src/map.c', + 'src/match.c', 'src/property_box.c', 'src/utils_g.c', 'src/utils.c', diff --git a/src/match.c b/src/match.c new file mode 100644 index 0000000..25a1604 --- /dev/null +++ b/src/match.c @@ -0,0 +1,412 @@ +/* + * Copyright (C) 2019-2022 Scoopta + * This file is part of Wofi + * Wofi is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Wofi is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Wofi. If not, see . + */ + +#include +#include +#include + +// leading gap +#define SCORE_GAP_LEADING -0.005 +// trailing gap +#define SCORE_GAP_TRAILING -0.005 +// gap in the middle +#define SCORE_GAP_INNER -0.01 +// we matched the characters consecutively +#define SCORE_MATCH_CONSECUTIVE 1.0 +// we got a consecutive match, but insensitive is on +// and we didn't match the case. +#define SCORE_MATCH_NOT_MATCH_CASE 0.9 +// we are matching after a slash +#define SCORE_MATCH_SLASH 0.9 +// we are matching after a space dash or hyphen +#define SCORE_MATCH_WORD 0.8 +// we are matching a camel case letter +#define SCORE_MATCH_CAPITAL 0.7 +// we are matching after a dot +#define SCORE_MATCH_DOT 0.6 + +#define SWAP(x, y, T) \ + do { \ + T SWAP = x; \ + x = y; \ + y = SWAP; \ + } while(0) + +#define max(a, b) (((a) > (b)) ? (a) : (b)) + +// matching +static bool contains_match(const char* filter, const char* text, bool insensitive) { + if(filter == NULL || strcmp(filter, "") == 0) { + return true; + } + if(text == NULL) { + return false; + } + if(insensitive) { + return strcasestr(text, filter) != NULL; + } else { + return strstr(text, filter) != NULL; + } +} + +static char* strcasechr(const char* s,char c, bool insensitive) { + if(insensitive) { + const char accept[3] = {c, toupper(c), 0}; + return strpbrk(s, accept); + } else { + return strchr(s, c); + } +} + +static bool fuzzy_match(const char* filter, const char* text, bool insensitive) { + if(filter == NULL || strcmp(filter, "") == 0) { + return true; + } + if(text == NULL) { + return false; + } + // we just check that all the characters (ignoring case) are in the + // search text possibly case insensitively in the correct order + while(*filter) { + char nch = *filter++; + + if(!(text = strcasechr(text, nch, insensitive))) { + return false; + } + text++; + } + return true; +} + +static bool multi_contains_match(const char* filter, const char* text, bool insensitive) { + if(filter == NULL || strcmp(filter, "") == 0) { + return true; + } + if(text == NULL) { + return false; + } + char new_filter[MAX_MULTI_CONTAINS_FILTER_SIZE]; + strncpy(new_filter, filter, sizeof(new_filter)); + new_filter[sizeof(new_filter) - 1] = '\0'; + char* token; + char* rest = new_filter; + while((token = strtok_r(rest, " ", &rest))) { + if(contains_match(token, text, insensitive) == false) { + return false; + } + } + return true; +} + +bool match_for_matching_mode(const char* filter, const char* text, + enum matching_mode matching, bool insensitive) { + bool retval; + switch(matching) { + case MATCHING_MODE_MULTI_CONTAINS: + retval = multi_contains_match(filter, text, insensitive); + break; + case MATCHING_MODE_CONTAINS: + retval = contains_match(filter, text, insensitive); + break; + case MATCHING_MODE_FUZZY: + retval = fuzzy_match(filter, text, insensitive); + break; + default: + return false; + } + return retval; +} + +// end matching + +// fuzzy matching +static void precompute_bonus(const char* haystack, score_t* match_bonus) { + /* Which positions are beginning of words */ + int m = strlen(haystack); + char last_ch = '\0'; + for(int i = 0; i < m; i++) { + char ch = haystack[i]; + + score_t score = 0; + if(isalnum(ch)) { + if(!last_ch || last_ch == '/') { + score = SCORE_MATCH_SLASH; + } else if(last_ch == '-' || last_ch == '_' || + last_ch == ' ') { + score = SCORE_MATCH_WORD; + } else if(last_ch >= 'a' && last_ch <= 'z' && + ch >= 'A' && ch <= 'Z') { + /* CamelCase */ + score = SCORE_MATCH_CAPITAL; + } else if(last_ch == '.') { + score = SCORE_MATCH_DOT; + } + } + + match_bonus[i] = score; + last_ch = ch; + } +} + +static inline bool match_with_case(char a, char b, bool insensitive) { + if(insensitive) { + return tolower(a) == tolower(b); + } else { + return a == b; + } +} + +static inline void match_row(int row, score_t* curr_D, score_t* curr_M, + const score_t* last_D, const score_t* last_M, + const char* needle, const char* haystack, int n, int m, score_t* match_bonus, bool insensitive) { + int i = row; + + score_t prev_score = SCORE_MIN; + score_t gap_score = i == n - 1 ? SCORE_GAP_TRAILING : SCORE_GAP_INNER; + + for(int j = 0; j < m; j++) { + if(match_with_case(needle[i], haystack[j], insensitive)) { + score_t score = SCORE_MIN; + if(!i) { + // first line we fill in a row for non-matching + score = (j * SCORE_GAP_LEADING) + match_bonus[j]; + } else if(j) { /* i > 0 && j > 0*/ + // we definitely match case insensitively already so if + // our character isn't the same then we have a different case + score_t consecutive_bonus = needle[i] == haystack[j] ? SCORE_MATCH_CONSECUTIVE : SCORE_MATCH_NOT_MATCH_CASE; + + score = max(last_M[j - 1] + match_bonus[j], + /* consecutive match, doesn't stack + with match_bonus */ + last_D[j - 1] + consecutive_bonus); + } + curr_D[j] = score; + curr_M[j] = prev_score = max(score, prev_score + gap_score); + } else { + curr_D[j] = SCORE_MIN; + curr_M[j] = prev_score = prev_score + gap_score; + } + } +} + +// Fuzzy matching scoring. Adapted from +// https://github.com/jhawthorn/fzy/blob/master/src/match.c and +// https://github.com/jhawthorn/fzy/blob/master/ALGORITHM.md +// For a fuzzy match string needle being searched for in haystack we provide a +// number score for how well we match. +// We create two matrices of size needle_len (n) by haystack_len (m). +// The first matrix is the score matrix. Each position (i,j) within this matrix +// consists of the score that corresponds to the score that would be generated +// by matching the first i characters of the needle with the first j +// characters of the haystack. Gaps have a fixed penalty for having a gap along +// with a linear penalty for gap size (c.f. gotoh's algorithm). +// matches give a positive score, with a slight weight given to matches after +// certain special characters (i.e. the first character after a `/` will be +// "almost" consecutive but lower than an actual consecutive match). +// Our second matrix is our diagonal matrix where we store the best match +// that ends at a match. This allows us to calculate our gap penalties alongside +// our consecutive match scores. +// In addition, since we only rely on the current, and previous row of the +// matrices and we only want to compute the score, we only store those scores +// and reuse the previous rows (rather than storing the entire (n*m) matrix). +// In addition we've simplified some of the algorithm compared to fzy to +// improve legibility. (Can reimplement lookup tables later if wanted.) +// Also, the reference algorithm does not take into account case sensitivity +// which has been implemented here. + +static score_t fuzzy_score(const char* haystack, const char* needle, bool insensitive) { + if(!*needle) + return SCORE_MIN; + + int n = strlen(needle); + int m = strlen(haystack); + score_t match_bonus[m]; + precompute_bonus(haystack, match_bonus); + + if(m > MATCH_FUZZY_MAX_LEN || n > m) { + /* + * Unreasonably large candidate: return no score + * If it is a valid match it will still be returned, it will + * just be ranked below any reasonably sized candidates + */ + return SCORE_MIN; + } else if(n == m) { + /* Since this method can only be called with a haystack which + * matches needle. If the lengths of the strings are equal the + * strings themselves must also be equal (ignoring case). + */ + return SCORE_MAX; + } + + /* + * D[][] Stores the best score for this position ending with a match. + * M[][] Stores the best possible score at this position. + */ + score_t D[2][MATCH_FUZZY_MAX_LEN], M[2][MATCH_FUZZY_MAX_LEN]; + + score_t* last_D, *last_M; + score_t* curr_D, *curr_M; + + last_D = D[0]; + last_M = M[0]; + curr_D = D[1]; + curr_M = M[1]; + + for(int i = 0; i < n; i++) { + match_row(i, curr_D, curr_M, last_D, last_M, needle, haystack, n, m, match_bonus, insensitive); + + SWAP(curr_D, last_D, score_t *); + SWAP(curr_M, last_M, score_t *); + } + + return last_M[m - 1]; +} +// end fuzzy matching + +// sorting +static int fuzzy_sort(const char* text1, const char* text2, const char* filter, bool insensitive) { + bool match1 = fuzzy_match(filter, text1, insensitive); + bool match2 = fuzzy_match(filter, text2, insensitive); + // both filters match do fuzzy scoring + if(match1 && match2) { + score_t dist1 = fuzzy_score(text1, filter, insensitive); + score_t dist2 = fuzzy_score(text2, filter, insensitive); + if(dist1 == dist2) { + // same same + return 0; + } else if(dist1 > dist2) { // highest score wins. + // text1 goes first + return -1; + } else { + // text2 goes first + return 1; + } + } else if(match1) { + // text1 goes first + return -1; + } else if(match2) { + // text2 goes first + return 1; + } else { + // same same. + return 0; + } +} + +// we sort based on how early in the string all the matches are. +// if there are matches for each. +static int multi_contains_sort(const char* text1, const char* text2, const char* filter, bool insensitive) { + // sum of string positions of each match + int t1_count = 0; + int t2_count = 0; + // does this string match with mult-contains + bool t1_match = true; + bool t2_match = true; + + char new_filter[MAX_MULTI_CONTAINS_FILTER_SIZE]; + strncpy(new_filter, filter, sizeof(new_filter)); + new_filter[sizeof(new_filter) - 1] = '\0'; + + char* token; + char* rest = new_filter; + while((token = strtok_r(rest, " ", &rest))) { + char* str1, *str2; + if(insensitive) { + str1 = strcasestr(text1, token); + str2 = strcasestr(text2, token); + } else { + str1 = strstr(text1, token); + str2 = strstr(text2, token); + } + t1_match = t1_match && str1 != NULL; + t2_match = t2_match && str2 != NULL; + if(str1 != NULL) { + int pos1 = str1 - text1; + t1_count += pos1; + } + if(str2 != NULL) { + int pos2 = str2 - text2; + t2_count += pos2; + } + } + if(t1_match && t2_match) { + // both match + // return the one with the smallest count. + return t1_count - t2_count; + } else if(t1_match) { + return -1; + } else if(t2_match) { + return 1; + } else { + return 0; + } +} +static int contains_sort(const char* text1, const char* text2, const char* filter, bool insensitive) { + char* str1, *str2; + + if(insensitive) { + str1 = strcasestr(text1, filter); + str2 = strcasestr(text2, filter); + } else { + str1 = strstr(text1, filter); + str2 = strstr(text2, filter); + } + bool tx1 = str1 == text1; + bool tx2 = str2 == text2; + bool txc1 = str1 != NULL; + bool txc2 = str2 != NULL; + + if(tx1 && tx2) { + return 0; + } else if(tx1) { + return -1; + } else if(tx2) { + return 1; + } else if(txc1 && txc2) { + return 0; + } else if(txc1) { + return -1; + } else if(txc2) { + return 1; + } else { + return 0; + } +} + +int sort_for_matching_mode(const char* text1, const char* text2, int fallback, + enum matching_mode match_type, const char* filter, bool insensitive) { + int primary = 0; + switch(match_type) { + case MATCHING_MODE_MULTI_CONTAINS: + primary = multi_contains_sort(text1, text2, filter, insensitive); + break; + case MATCHING_MODE_CONTAINS: + primary = contains_sort(text1, text2, filter, insensitive); + break; + case MATCHING_MODE_FUZZY: + primary = fuzzy_sort(text1, text2, filter, insensitive); + break; + default: + return 0; + } + if(primary == 0) { + return fallback; + } + return primary; +} +// end sorting + diff --git a/src/utils.c b/src/utils.c index edc2f50..116d460 100644 --- a/src/utils.c +++ b/src/utils.c @@ -17,11 +17,9 @@ #include -#include #include #include #include -#include #include #include #include @@ -111,179 +109,6 @@ size_t utils_distance(const char* haystack, const char* needle) { return arr[str1_len][str2_len]; } -// leading gap -#define SCORE_GAP_LEADING -0.005 -// trailing gap -#define SCORE_GAP_TRAILING -0.005 -// gap in the middle -#define SCORE_GAP_INNER -0.01 -// we matched the characters consecutively -#define SCORE_MATCH_CONSECUTIVE 1.0 -// we got a consecutive match, but insensitive is on -// and we didn't match the case. -#define SCORE_MATCH_NOT_MATCH_CASE 0.9 -// we are matching after a slash -#define SCORE_MATCH_SLASH 0.9 -// we are matching after a space dash or hyphen -#define SCORE_MATCH_WORD 0.8 -// we are matching a camel case letter -#define SCORE_MATCH_CAPITAL 0.7 -// we are matching after a dot -#define SCORE_MATCH_DOT 0.6 - -#define SWAP(x, y, T) \ - do { \ - T SWAP = x; \ - x = y; \ - y = SWAP; \ - } while (0) - -#define max(a, b) (((a) > (b)) ? (a) : (b)) - -static void precompute_bonus(const char *haystack, score_t *match_bonus) { - /* Which positions are beginning of words */ - int m = strlen(haystack); - char last_ch = '\0'; - for (int i = 0; i < m; i++) { - char ch = haystack[i]; - - score_t score = 0; - if (isalnum(ch)) { - if (!last_ch || last_ch == '/') { - score = SCORE_MATCH_SLASH; - } else if (last_ch == '-' || last_ch == '_' || - last_ch == ' ') { - score = SCORE_MATCH_WORD; - } else if (last_ch >= 'a' && last_ch <= 'z' && - ch >= 'A' && ch <= 'Z') { - /* CamelCase */ - score = SCORE_MATCH_CAPITAL; - } else if (last_ch == '.') { - score = SCORE_MATCH_DOT; - } - } - - match_bonus[i] = score; - last_ch = ch; - } -} - -static inline bool match_with_case(char a, char b, bool insensitive) { - if(insensitive) { - return tolower(a) == tolower(b); - } else { - return a == b; - } -} - -static inline void match_row(int row, score_t* curr_D, score_t* curr_M, - const score_t* last_D, const score_t * last_M, - const char* needle, const char* haystack, int n, int m, score_t* match_bonus) { - int i = row; - - score_t prev_score = SCORE_MIN; - score_t gap_score = i == n - 1 ? SCORE_GAP_TRAILING : SCORE_GAP_INNER; - - for (int j = 0; j < m; j++) { - if (match_with_case(needle[i], haystack[j], true)) { - score_t score = SCORE_MIN; - if (!i) { - // first line we fill in a row for non-matching - score = (j * SCORE_GAP_LEADING) + match_bonus[j]; - } else if (j) { /* i > 0 && j > 0*/ - // we definitely match case insensitively already so if - // our character isn't the same then we have a - // different case - score_t consecutive_bonus = needle[i] == haystack[j] ? SCORE_MATCH_CONSECUTIVE : SCORE_MATCH_NOT_MATCH_CASE; - - score = max(last_M[j - 1] + match_bonus[j], - /* consecutive match, doesn't stack - with match_bonus */ - last_D[j - 1] + consecutive_bonus); - } - curr_D[j] = score; - curr_M[j] = prev_score = max(score, prev_score + gap_score); - } else { - curr_D[j] = SCORE_MIN; - curr_M[j] = prev_score = prev_score + gap_score; - } - } -} - -// Fuzzy matching scoring. Adapted from -// https://github.com/jhawthorn/fzy/blob/master/src/match.c and -// https://github.com/jhawthorn/fzy/blob/master/ALGORITHM.md -// For a fuzzy match string needle being searched for in haystack we provide a -// number score for how well we match. -// We create two matrices of size needle_len (n) by haystack_len (m). -// The first matrix is the score matrix. Each position (i,j) within this matrix -// consists of the score that corresponds to the score that would be generated -// by matching the first i characters of the needle with the first j -// characters of the haystack. Gaps have a fixed penalty for having a gap along -// with a linear penalty for gap size (c.f. gotoh's algorithm). -// matches give a positive score, with a slight weight given to matches after -// certain special characters (i.e. the first character after a `/` will be -// "almost" consecutive but lower than an actual consecutive match). -// Our second matrix is our diagonal matrix where we store the best match -// that ends at a match. This allows us to calculate our gap penalties alongside -// our consecutive match scores. -// In addition, since we only rely on the current, and previous row of the -// matrices and we only want to compute the score, we only store those scores -// and reuse the previous rows (rather than storing the entire (n*m) matrix). -// In addition we've simplified some of the algorithm compared to fzy to -// improve legibility. (Can reimplement lookup tables later if wanted.) -// Also, the reference algorithm does not take into account case sensitivity -// which has been implemented here. - - -score_t utils_fuzzy_score(const char* haystack, const char* needle) { - if(!*needle) - return SCORE_MIN; - - int n = strlen(needle); - int m = strlen(haystack); - score_t match_bonus[m]; - precompute_bonus(haystack, match_bonus); - - if(m > MATCH_FUZZY_MAX_LEN || n > m) { - /* - * Unreasonably large candidate: return no score - * If it is a valid match it will still be returned, it will - * just be ranked below any reasonably sized candidates - */ - return SCORE_MIN; - } else if(n == m) { - /* Since this method can only be called with a haystack which - * matches needle. If the lengths of the strings are equal the - * strings themselves must also be equal (ignoring case). - */ - return SCORE_MAX; - } - - /* - * D[][] Stores the best score for this position ending with a match. - * M[][] Stores the best possible score at this position. - */ - score_t D[2][MATCH_FUZZY_MAX_LEN], M[2][MATCH_FUZZY_MAX_LEN]; - - score_t* last_D, *last_M; - score_t* curr_D, *curr_M; - - last_D = D[0]; - last_M = M[0]; - curr_D = D[1]; - curr_M = M[1]; - - for (int i = 0; i < n; i++) { - match_row(i, curr_D, curr_M, last_D, last_M, needle, haystack, n, m, match_bonus); - - SWAP(curr_D, last_D, score_t *); - SWAP(curr_M, last_M, score_t *); - } - - return last_M[m - 1]; -} - void utils_mkdir(char* path, mode_t mode) { if(access(path, F_OK) != 0) { char* tmp = strdup(path); diff --git a/src/wofi.c b/src/wofi.c index 0ec0ef3..7385086 100644 --- a/src/wofi.c +++ b/src/wofi.c @@ -29,6 +29,7 @@ #include #include +#include #include #include #include @@ -41,16 +42,9 @@ #include #define PROTO_VERSION(v1, v2) (v1 < v2 ? v1 : v2) -#define MAX_MULTI_CONTAINS_FILTER_SIZE 256 static const char* terminals[] = {"kitty", "termite", "alacritty", "foot", "gnome-terminal", "weston-terminal"}; -enum matching_mode { - MATCHING_MODE_CONTAINS, - MATCHING_MODE_MULTI_CONTAINS, - MATCHING_MODE_FUZZY -}; - enum location { LOCATION_CENTER, LOCATION_TOP_LEFT, @@ -972,66 +966,6 @@ static void activate_search(GtkEntry* entry, gpointer data) { } } -static gboolean do_strcomp(gchar* filter, const gchar* text) { - if(filter == NULL || strcmp(filter, "") == 0) { - return TRUE; - } - if(text == NULL) { - return FALSE; - } - if(insensitive) { - return strcasestr(text, filter) != NULL; - } else { - return strstr(text, filter) != NULL; - } -} - -static char* strcasechr(const char *s, char c) { - const char accept[3] = {c, toupper(c), 0}; - return strpbrk(s, accept); -} - - -static gboolean do_fuzzy_strcomp(gchar* filter, const gchar* text) { - if (filter == NULL || strcmp(filter, "") == 0) { - return TRUE; - } - if (text == NULL) { - return FALSE; - } - // we just check that all the characters (ignoring case) are in the - // search text possibly case insensitively in the correct order - while (*filter) { - char nch = *filter++; - - if (!(text = strcasechr(text, nch))) { - return FALSE; - } - text++; - } - return TRUE; -} - -static gboolean do_multi_strcomp(gchar* filter, const gchar* text) { - if(filter == NULL || strcmp(filter, "") == 0) { - return TRUE; - } - if(text == NULL) { - return FALSE; - } - gchar new_filter[MAX_MULTI_CONTAINS_FILTER_SIZE]; - strncpy(new_filter, filter, sizeof(new_filter)); - new_filter[sizeof(new_filter) - 1] = '\0'; - gchar* token; - gchar* rest = new_filter; - while((token = strtok_r(rest, " ", &rest))) { - if(do_strcomp(token, text) == FALSE) { - return FALSE; - } - } - return TRUE; -} - static gboolean filter_proxy(GtkFlowBoxChild* row) { GtkWidget* box = gtk_bin_get_child(GTK_BIN(row)); if(GTK_IS_EXPANDER(box)) { @@ -1039,40 +973,20 @@ static gboolean filter_proxy(GtkFlowBoxChild* row) { } const gchar* text = wofi_property_box_get_property(WOFI_PROPERTY_BOX(box), "filter"); - return do_strcomp(filter, text); -} - -static gboolean filter_multi_proxy(GtkFlowBoxChild* row) { - GtkWidget* box = gtk_bin_get_child(GTK_BIN(row)); - if(GTK_IS_EXPANDER(box)) { - box = gtk_expander_get_label_widget(GTK_EXPANDER(box)); - } - const gchar* text = - wofi_property_box_get_property(WOFI_PROPERTY_BOX(box), "filter"); - return do_multi_strcomp(filter, text); -} - -static gboolean filter_fuzzy_proxy(GtkFlowBoxChild *row) { - GtkWidget *box = gtk_bin_get_child(GTK_BIN(row)); - if (GTK_IS_EXPANDER(box)) { - box = gtk_expander_get_label_widget(GTK_EXPANDER(box)); - } - const gchar *text = - wofi_property_box_get_property(WOFI_PROPERTY_BOX(box), "filter"); - return do_fuzzy_strcomp(filter, text); + return match_for_matching_mode(filter, text, matching, insensitive); } static void do_resize_surface_after_filter(GtkFlowBoxChild *row, gboolean filter_return) { - if (gtk_widget_get_visible(GTK_WIDGET(row)) == !filter_return && + if(gtk_widget_get_visible(GTK_WIDGET(row)) == !filter_return && dynamic_lines) { - if (filter_return) { + if(filter_return) { ++line_count; } else { --line_count; } - if (line_count < max_lines) { + if(line_count < max_lines) { lines = line_count; update_surface_size(); } else { @@ -1093,133 +1007,6 @@ static gboolean do_filter(GtkFlowBoxChild* row, gpointer data) { return ret; } -static gboolean do_fuzzy_filter(GtkFlowBoxChild* row, gpointer data) { - (void)data; - gboolean ret = filter_fuzzy_proxy(row); - - do_resize_surface_after_filter(row, ret); - - return ret; -} - -static gboolean do_multi_filter(GtkFlowBoxChild* row, gpointer data) { - (void)data; - gboolean ret = filter_multi_proxy(row); - - do_resize_surface_after_filter(row, ret); - - return ret; -} - -static gint fuzzy_sort(const gchar *text1, const gchar *text2) { - gboolean match1 = do_fuzzy_strcomp(filter, text1); - gboolean match2 = do_fuzzy_strcomp(filter, text2); - // both filters match do fuzzy scoring - if(match1 && match2) { - score_t dist1 = utils_fuzzy_score(text1, filter); - score_t dist2 = utils_fuzzy_score(text2, filter); - if (dist1 == dist2) { - // same same - return 0; - } else if (dist1 > dist2) { // highest score wins. - // text1 goes first - return -1; - } else { - // text2 goes first - return 1; - } - } else if(match1) { - // text1 goes first - return -1; - } else if(match2) { - // text2 goes first - return 1; - } else { - // same same. - return 0; - } -} - -// we sort based on how early in the string all the matches are. -// if there are matches for each. -static gint multi_contains_sort(const gchar* text1, const gchar* text2) { - // sum of string positions of each match - int t1_count = 0; - int t2_count = 0; - // does this string match with mult-contains - bool t1_match = true; - bool t2_match = true; - - gchar new_filter[MAX_MULTI_CONTAINS_FILTER_SIZE]; - strncpy(new_filter, filter, sizeof(new_filter)); - new_filter[sizeof(new_filter) - 1] = '\0'; - - gchar* token; - gchar* rest = new_filter; - while((token = strtok_r(rest, " ", &rest))) { - char* str1, *str2; - if(insensitive) { - str1 = strcasestr(text1, token); - str2 = strcasestr(text2, token); - } else { - str1 = strstr(text1, token); - str2 = strstr(text2, token); - } - t1_match = t1_match && str1 != NULL; - t2_match = t2_match && str2 != NULL; - if(str1 != NULL) { - int pos1 = str1 - text1; - t1_count += pos1; - } - if(str2 != NULL) { - int pos2 = str2 - text2; - t2_count += pos2; - } - } - if(t1_match && t2_match) { - // both match - // return the one with the smallest count. - return t1_count - t2_count; - } else if(t1_match) { - return -1; - } else if(t2_match) { - return 1; - } else { - return 0; - } -} -static gint contains_sort(const gchar* text1, const gchar* text2) { - char* str1, *str2; - - if(insensitive) { - str1 = strcasestr(text1, filter); - str2 = strcasestr(text2, filter); - } else { - str1 = strstr(text1, filter); - str2 = strstr(text2, filter); - } - bool tx1 = str1 == text1; - bool tx2 = str2 == text2; - bool txc1 = str1 != NULL; - bool txc2 = str2 != NULL; - - if(tx1 && tx2) { - return 0; - } else if(tx1) { - return -1; - } else if(tx2) { - return 1; - } else if(txc1 && txc2) { - return 0; - } else if(txc1) { - return -1; - } else if(txc2) { - return 1; - } else { - return 0; - } -} - static gint do_sort(GtkFlowBoxChild* child1, GtkFlowBoxChild* child2, gpointer data) { (void) data; gtk_flow_box_get_child_at_index(GTK_FLOW_BOX(inner_box), 0); @@ -1242,7 +1029,7 @@ static gint do_sort(GtkFlowBoxChild* child1, GtkFlowBoxChild* child2, gpointer d return index1 - index2; } - uint64_t fallback = 0; + int fallback = 0; switch(sort_order) { case SORT_ORDER_DEFAULT: fallback = index1 - index2; @@ -1259,30 +1046,7 @@ static gint do_sort(GtkFlowBoxChild* child1, GtkFlowBoxChild* child2, gpointer d if(filter == NULL || strcmp(filter, "") == 0) { return fallback; } - - gint primary = 0; - switch(matching) { - case MATCHING_MODE_MULTI_CONTAINS: - primary = multi_contains_sort(text1, text2); - if(primary == 0) { - return fallback; - } - return primary; - case MATCHING_MODE_CONTAINS: - primary = contains_sort(text1, text2); - if(primary == 0) { - return fallback; - } - return primary; - case MATCHING_MODE_FUZZY: - primary = fuzzy_sort(text1, text2); - if(primary == 0) { - return fallback; - } - return primary; - default: - return 0; - } + return sort_for_matching_mode(text1, text2, fallback, matching, filter, insensitive); } static void select_first(void) { @@ -2035,22 +1799,8 @@ void wofi_init(struct map* _config) { gtk_container_add(GTK_CONTAINER(wrapper_box), inner_box); gtk_container_add(GTK_CONTAINER(scroll), wrapper_box); - switch(matching) { - case MATCHING_MODE_MULTI_CONTAINS: - gtk_flow_box_set_filter_func(GTK_FLOW_BOX(inner_box), do_multi_filter, NULL, NULL); - gtk_flow_box_set_sort_func(GTK_FLOW_BOX(inner_box), do_sort, NULL, NULL); - break; - case MATCHING_MODE_CONTAINS: - gtk_flow_box_set_filter_func(GTK_FLOW_BOX(inner_box), do_filter, NULL, NULL); - gtk_flow_box_set_sort_func(GTK_FLOW_BOX(inner_box), do_sort, NULL, NULL); - break; - case MATCHING_MODE_FUZZY: - gtk_flow_box_set_filter_func(GTK_FLOW_BOX(inner_box), do_fuzzy_filter, NULL, - NULL); - gtk_flow_box_set_sort_func(GTK_FLOW_BOX(inner_box), do_sort, NULL, - NULL); - break; - } + gtk_flow_box_set_filter_func(GTK_FLOW_BOX(inner_box), do_filter, NULL, NULL); + gtk_flow_box_set_sort_func(GTK_FLOW_BOX(inner_box), do_sort, NULL, NULL); g_signal_connect(inner_box, "child-activated", G_CALLBACK(activate_item), NULL); g_signal_connect(inner_box, "selected-children-changed", G_CALLBACK(select_item), NULL);