aboutsummaryrefslogtreecommitdiff
path: root/src/Input.c
diff options
context:
space:
mode:
authorfrosty <gabriel@bwaaa.monster>2026-02-24 01:03:30 -0500
committerfrosty <gabriel@bwaaa.monster>2026-02-24 01:03:30 -0500
commitcc42dbe9e282a5c30ce2d1baa9805927a79a19c3 (patch)
tree4cbab72827bb8e5aa008f6a28e5a6500a7de3fc0 /src/Input.c
parent8b2434d855c22435a99447aac748f87a4388b569 (diff)
downloadinsel-cc42dbe9e282a5c30ce2d1baa9805927a79a19c3.tar.gz
add fuzzy matching, bump versionHEADmaster
Diffstat (limited to 'src/Input.c')
-rw-r--r--src/Input.c84
1 files changed, 84 insertions, 0 deletions
diff --git a/src/Input.c b/src/Input.c
index 23a474d..2bb1889 100644
--- a/src/Input.c
+++ b/src/Input.c
@@ -8,8 +8,13 @@
#include "Terminal.h"
#include "Input.h"
+int fuzzy_score(const char *s);
+
int matches(const char *s) {
if (input_len == 0) return 1;
+ if (opts.fuzzy) {
+ return fuzzy_score(s) >= 0;
+ }
const char *p = s;
if (opts.insensitive) {
while (*p) {
@@ -25,6 +30,82 @@ int matches(const char *s) {
return 0;
}
+static int is_separator(char c) {
+ return c == ' ' || c == '-' || c == '_' || c == '.' || c == '/' || c == '\\';
+}
+
+int fuzzy_score(const char *s) {
+ if (input_len == 0) return 0;
+ int score = 0;
+ int prev_pos = -1;
+ const char *input_lower = NULL;
+ const char *s_lower = NULL;
+ char s_lower_buf[1024];
+ char input_lower_buf[256];
+
+ if (opts.insensitive) {
+ for (size_t i = 0; i < input_len && i < 255; i++) {
+ input_lower_buf[i] = tolower(input[i]);
+ }
+ input_lower_buf[input_len] = '\0';
+ input_lower = input_lower_buf;
+
+ size_t slen = strlen(s);
+ if (slen < 1024) {
+ for (size_t i = 0; i <= slen; i++) {
+ s_lower_buf[i] = tolower(s[i]);
+ }
+ s_lower = s_lower_buf;
+ }
+ }
+
+ const char *search = input_lower ? input_lower : input;
+ const char *search_s = s_lower ? s_lower : s;
+
+ for (size_t i = 0; i < input_len; i++) {
+ const char *found = NULL;
+ int best_pos = -1;
+ int best_score = -1000;
+ const char *p = search_s;
+ int pos = 0;
+ while (*p) {
+ if (*p == search[i] && (i == 0 || pos > prev_pos)) {
+ int char_score;
+ if (i == 0) {
+ if (pos == 0) char_score = 25;
+ else if (is_separator(s[pos - 1])) char_score = 3;
+ else char_score = 1;
+ } else {
+ if (prev_pos + 1 == pos) char_score = 25;
+ else if (is_separator(s[pos - 1])) char_score = 1;
+ else char_score = 8 - (pos - prev_pos - 1) * 2;
+ }
+ if (char_score > best_score) {
+ best_score = char_score;
+ best_pos = pos;
+ found = p;
+ }
+ }
+ p++;
+ pos++;
+ }
+ if (!found) return -1;
+ score += best_score;
+ if (score < 1) score = 1;
+ prev_pos = best_pos;
+ }
+ score -= (int)(strlen(s) / 3);
+ return score;
+}
+
+static int compare_scores(const void *a, const void *b) {
+ char *const *sa = a;
+ char *const *sb = b;
+ int score_a = fuzzy_score(*sa);
+ int score_b = fuzzy_score(*sb);
+ return score_b - score_a;
+}
+
void filter_items(void) {
filtered.count = 0;
for (size_t i = 0; i < all_items.count; i++) {
@@ -37,6 +118,9 @@ void filter_items(void) {
filtered.items[filtered.count++] = all_items.items[i];
}
}
+ if (opts.fuzzy && filtered.count > 1) {
+ qsort(filtered.items, filtered.count, sizeof(char *), compare_scores);
+ }
if ((size_t)cursor >= filtered.count) cursor = filtered.count > 0 ? (int)filtered.count - 1 : 0;
if (scroll > cursor) scroll = cursor;
if (scroll < cursor - DEFAULT_LINES + 1) scroll = cursor - DEFAULT_LINES + 1;