From 6aeaa171dc1ca43392f53cbd02097f76e1b1c5a0 Mon Sep 17 00:00:00 2001 From: David Moc Date: Sun, 31 May 2026 03:47:04 +0200 Subject: Hardened API, tetris, MD-View --- src/completion.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/completion.c (limited to 'src/completion.c') 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 +#include + +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); +} -- cgit v1.2.3