aboutsummaryrefslogtreecommitdiff
path: root/src/completion.c
diff options
context:
space:
mode:
authorDavid Moc <personal@cdatgoose.org>2026-05-31 03:47:04 +0200
committerDavid Moc <personal@cdatgoose.org>2026-05-31 03:47:04 +0200
commit6aeaa171dc1ca43392f53cbd02097f76e1b1c5a0 (patch)
treeb16f559f5a701123ebe7b15ecebb9325263b4a3c /src/completion.c
parente930cc6bdc7f62befac063d7d9d016ffb0a64f1a (diff)
Hardened API, tetris, MD-View
Diffstat (limited to 'src/completion.c')
-rw-r--r--src/completion.c97
1 files changed, 97 insertions, 0 deletions
diff --git a/src/completion.c b/src/completion.c
new file mode 100644
index 0000000..6f19530
--- /dev/null
+++ b/src/completion.c
@@ -0,0 +1,97 @@
+#include "completion.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+static int ecex_ascii_lower(int c) {
+ if (c >= 'A' && c <= 'Z') return c - 'A' + 'a';
+ return c;
+}
+
+int ecex_ascii_strncasecmp(const char *a, const char *b, size_t n) {
+ if (!a || !b) return a == b ? 0 : (a ? 1 : -1);
+
+ for (size_t i = 0; i < n; i++) {
+ int ac = ecex_ascii_lower((unsigned char)a[i]);
+ int bc = ecex_ascii_lower((unsigned char)b[i]);
+
+ if (ac != bc || ac == '\0' || bc == '\0') return ac - bc;
+ }
+
+ return 0;
+}
+
+int ecex_ascii_contains_ci(const char *haystack, const char *needle) {
+ if (!haystack || !needle) return 0;
+ if (needle[0] == '\0') return 1;
+
+ size_t needle_len = strlen(needle);
+ for (size_t i = 0; haystack[i]; i++) {
+ if (ecex_ascii_strncasecmp(haystack + i, needle, needle_len) == 0) return 1;
+ }
+
+ return 0;
+}
+
+int ecex_fuzzy_score(const char *candidate, const char *query) {
+ if (!candidate || !query) return -1;
+ if (query[0] == '\0') return 0;
+
+ int score = 0;
+ int consecutive = 0;
+ int last_match = -1;
+ size_t ci = 0;
+ size_t qi = 0;
+
+ while (candidate[ci] && query[qi]) {
+ char c = candidate[ci];
+ char q = query[qi];
+
+ if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a');
+ if (q >= 'A' && q <= 'Z') q = (char)(q - 'A' + 'a');
+
+ if (c == q) {
+ score += 10;
+ if ((int)ci == last_match + 1) {
+ consecutive++;
+ score += 5 * consecutive;
+ } else {
+ consecutive = 0;
+ }
+ if (ci == 0) score += 20;
+ if (ci > 0 && (candidate[ci - 1] == '-' || candidate[ci - 1] == '_' || candidate[ci - 1] == ' ')) {
+ score += 15;
+ }
+ last_match = (int)ci;
+ qi++;
+ }
+ ci++;
+ }
+
+ if (query[qi] != '\0') return -1;
+
+ score -= (int)strlen(candidate);
+ if (strncmp(candidate, query, strlen(query)) == 0) score += 100;
+ if (ecex_ascii_contains_ci(candidate, query)) score += 75;
+ return score;
+}
+
+int ecex_completion_item_compare(const void *a, const void *b) {
+ const ecex_completion_item_t *pa = (const ecex_completion_item_t *)a;
+ const ecex_completion_item_t *pb = (const ecex_completion_item_t *)b;
+
+ if (pa->score != pb->score) return pb->score - pa->score;
+ if (pa->is_dir != pb->is_dir) return pb->is_dir - pa->is_dir;
+
+ int cmp = strcmp(pa->value ? pa->value : "", pb->value ? pb->value : "");
+ if (cmp != 0) return cmp;
+ if (pa->order < pb->order) return -1;
+ if (pa->order > pb->order) return 1;
+ return 0;
+}
+
+void ecex_completion_items_free(ecex_completion_item_t *items, size_t count) {
+ if (!items) return;
+ for (size_t i = 0; i < count; i++) free(items[i].value);
+ free(items);
+}