aboutsummaryrefslogtreecommitdiff
path: root/src/completion.c
blob: 6f1953043600984ce55094ae25df68b5a7ab6239 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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);
}