diff --git a/inc/utils.h b/inc/utils.h index e7bf0a9..429761b 100644 --- a/inc/utils.h +++ b/inc/utils.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -32,4 +33,10 @@ void utils_sleep_millis(time_t millis); char* utils_concat(size_t arg_count, ...); +size_t utils_min(size_t n1, size_t n2); + +size_t utils_min3(size_t n1, size_t n2, size_t n3); + +size_t utils_distance(const char* str1, const char* str2); + #endif diff --git a/src/main.c b/src/main.c index cb4738b..32409a4 100644 --- a/src/main.c +++ b/src/main.c @@ -68,6 +68,7 @@ static void print_usage(char** argv) { printf("--password\t-P\tRuns in password mode\n"); printf("--exec-search\t-e\tMakes enter always use the search contents not the first result\n"); printf("--hide-scroll\t-b\tHides the scroll bars\n"); + printf("--matching\t-M\tSets the matching method, default is contains\n"); exit(0); } @@ -297,6 +298,12 @@ int main(int argc, char** argv) { .flag = NULL, .val = 'b' }, + { + .name = "matching", + .has_arg = required_argument, + .flag = NULL, + .val = 'M' + }, { .name = NULL, .has_arg = 0, @@ -322,8 +329,9 @@ int main(int argc, char** argv) { char* password_char = "false"; char* exec_search = NULL; char* hide_scroll = NULL; + char* matching = NULL; int opt; - while((opt = getopt_long(argc, argv, "hfc:s:C:dS:W:H:p:x:y:nimk:t:P::eb", opts, NULL)) != -1) { + while((opt = getopt_long(argc, argv, "hfc:s:C:dS:W:H:p:x:y:nimk:t:P::ebM:", opts, NULL)) != -1) { switch(opt) { case 'h': print_usage(argv); @@ -390,6 +398,9 @@ int main(int argc, char** argv) { case 'b': hide_scroll = "true"; break; + case 'M': + matching = optarg; + break; } } @@ -526,6 +537,9 @@ int main(int argc, char** argv) { if(hide_scroll != NULL) { map_put(config, "hide_scroll", hide_scroll); } + if(matching != NULL) { + map_put(config, "matching", matching); + } gtk_init(&argc, &argv); diff --git a/src/utils.c b/src/utils.c index 7070fdd..0f6138d 100644 --- a/src/utils.c +++ b/src/utils.c @@ -47,3 +47,49 @@ char* utils_concat(size_t arg_count, ...) { va_end(args); return buffer; } + +size_t utils_min(size_t n1, size_t n2) { + if(n1 < n2) { + return n1; + } else { + return n2; + } +} + +size_t utils_min3(size_t n1, size_t n2, size_t n3) { + if(n1 < n2 && n1 < n3) { + return n1; + } else if(n2 < n1 && n2 < n3) { + return n2; + } else { + return n3; + } +} + +size_t utils_distance(const char* str1, const char* str2) { + size_t str1_len = strlen(str1); + size_t str2_len = strlen(str2); + + size_t arr[str1_len + 1][str2_len + 1]; + arr[0][0] = 0; + for(size_t count = 1; count <= str1_len; ++count) { + arr[count][0] = count; + } + for(size_t count = 1; count <= str2_len; ++count) { + arr[0][count] = count; + } + + uint8_t cost; + for(size_t c1 = 1; c1 <= str1_len; ++c1) { + for(size_t c2 = 1; c2 <= str2_len; ++c2) { + if(str1[c1 - 1] == str2[c2 - 1]) { + cost = 0; + } else { + cost = 1; + } + arr[c1][c2] = utils_min3(arr[c1 - 1][c2] + 1, arr[c1][c2 - 1] + 1, arr[c1 - 1][c2 - 1] + cost); + } + } + + return arr[str1_len][str2_len]; +} diff --git a/src/wofi.c b/src/wofi.c index 6670e59..39df8eb 100644 --- a/src/wofi.c +++ b/src/wofi.c @@ -19,6 +19,11 @@ static const char* terminals[] = {"kitty", "termite", "gnome-terminal", "weston-terminal"}; +enum matching_mode { + MATCHING_MODE_CONTAINS, + MATCHING_MODE_FUZZY +}; + static uint64_t width, height; static int64_t x, y; static struct zwlr_layer_shell_v1* shell; @@ -37,6 +42,7 @@ static char* terminal; static GtkOrientation outer_orientation; static bool exec_search; static struct map* modes; +static enum matching_mode matching; struct node { size_t action_count; @@ -75,6 +81,7 @@ static void get_input(GtkSearchEntry* entry, gpointer data) { filter = gtk_entry_get_text(GTK_ENTRY(entry)); filter_time = utils_get_time_millis(); gtk_flow_box_invalidate_filter(GTK_FLOW_BOX(inner_box)); + gtk_flow_box_invalidate_sort(GTK_FLOW_BOX(inner_box)); } } @@ -82,6 +89,7 @@ static void get_search(GtkSearchEntry* entry, gpointer data) { (void) data; filter = gtk_entry_get_text(GTK_ENTRY(entry)); gtk_flow_box_invalidate_filter(GTK_FLOW_BOX(inner_box)); + gtk_flow_box_invalidate_sort(GTK_FLOW_BOX(inner_box)); } static GtkWidget* create_label(char* mode, char* text, char* search_text, char* action) { @@ -435,10 +443,37 @@ static gboolean do_filter(GtkFlowBoxChild* row, gpointer data) { if(text == NULL) { return FALSE; } - if(strcasestr(text, filter) != NULL) { - return TRUE; + return strstr(text, filter) != NULL; +} + +static gint do_sort(GtkFlowBoxChild* child1, GtkFlowBoxChild* child2, gpointer data) { + (void) data; + GtkWidget* box1 = gtk_bin_get_child(GTK_BIN(child1)); + GtkWidget* box2 = gtk_bin_get_child(GTK_BIN(child2)); + if(GTK_IS_EXPANDER(box1)) { + box1 = gtk_expander_get_label_widget(GTK_EXPANDER(box1)); + } + if(GTK_IS_EXPANDER(box2)) { + box2 = gtk_expander_get_label_widget(GTK_EXPANDER(box2)); + } + + const gchar* text1 = wofi_property_box_get_property(WOFI_PROPERTY_BOX(box1), "filter"); + const gchar* text2 = wofi_property_box_get_property(WOFI_PROPERTY_BOX(box2), "filter"); + if(filter == NULL || strcmp(filter, "") == 0) { + return 0; + } + if(text1 == NULL || text2 == NULL) { + return 0; + } + size_t dist1 = utils_distance(text1, filter); + size_t dist2 = utils_distance(text2, filter); + if(dist1 < dist2) { + return -1; + } else if(dist1 > dist2) { + return 1; + } else { + return 0; } - return FALSE; } static gboolean key_press(GtkWidget* widget, GdkEvent* event, gpointer data) { @@ -566,6 +601,7 @@ void wofi_init(struct map* config) { char* password_char = map_get(config, "password_char"); exec_search = strcmp(config_get(config, "exec_search", "false"), "true") == 0; bool hide_scroll = strcmp(config_get(config, "hide_scroll", "false"), "true") == 0; + matching = config_get_mnemonic(config, "matching", "contains", 2, "contains", "fuzzy"); modes = map_init_void(); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); @@ -631,7 +667,14 @@ void wofi_init(struct map* config) { gtk_container_add(GTK_CONTAINER(wrapper_box), inner_box); gtk_container_add(GTK_CONTAINER(scroll), wrapper_box); - gtk_flow_box_set_filter_func(GTK_FLOW_BOX(inner_box), do_filter, NULL, NULL); + switch(matching) { + case MATCHING_MODE_CONTAINS: + gtk_flow_box_set_filter_func(GTK_FLOW_BOX(inner_box), do_filter, NULL, NULL); + break; + case MATCHING_MODE_FUZZY: + gtk_flow_box_set_sort_func(GTK_FLOW_BOX(inner_box), do_sort, NULL, NULL); + break; + } g_signal_connect(entry, "changed", G_CALLBACK(get_input), NULL); g_signal_connect(entry, "search-changed", G_CALLBACK(get_search), NULL);