branch: externals/hotfuzz
commit a19395aca9eff0e31c51efbbe4c6e16229f3b380
Author: Axel Forsman <[email protected]>
Commit: Axel Forsman <[email protected]>

    Obey completion-ignore-case in hotfuzz--filter-c
    
    `hotfuzz--filter-c` always being case-insensitive posed a problem since
    `hotfuzz-highlight` would try to highlight the completions according to
    `completion-ignore-case` irrespective of whether a completion had
    matched only case-insensitively.
    
    Resolves #5
---
 README.md        |  3 +--
 hotfuzz-module.c | 41 ++++++++++++++++++++---------------------
 hotfuzz.el       |  3 ++-
 test/tests.el    |  6 +++---
 4 files changed, 26 insertions(+), 27 deletions(-)

diff --git a/README.md b/README.md
index 64a0c6c465..1b9b1a245d 100644
--- a/README.md
+++ b/README.md
@@ -59,8 +59,7 @@ cmake -DCMAKE_C_FLAGS='-O3 -march=native' .. \
 and place the resulting shared library somewhere in `load-path`.
 
 Unlike the Lisp implementation,
-the dynamic module uses an unstable sorting algorithm
-and is always case-insensitive.
+the dynamic module uses an unstable sorting algorithm.
 
 ## Related projects
 
diff --git a/hotfuzz-module.c b/hotfuzz-module.c
index bc576e4848..46773ab8c3 100644
--- a/hotfuzz-module.c
+++ b/hotfuzz-module.c
@@ -66,7 +66,7 @@ static void match_row(struct EmacsStr *a, struct EmacsStr *b, 
cost *bonuses, uns
        }
 }
 
-static cost get_cost(struct EmacsStr *needle, struct EmacsStr *haystack) {
+static cost get_cost(struct EmacsStr *needle, struct EmacsStr *haystack, bool 
ignore_case) {
        unsigned n = haystack->len, m = needle->len;
        if (n > MAX_HAYSTACK_LEN || m > MAX_NEEDLE_LEN) return 10000;
        cost c[MAX_NEEDLE_LEN], d[MAX_NEEDLE_LEN];
@@ -76,7 +76,8 @@ static cost get_cost(struct EmacsStr *needle, struct EmacsStr 
*haystack) {
        calc_bonus(haystack, bonuses);
 
        for (unsigned i = 0; i < n; ++i) {
-               haystack->b[i] = tolower(haystack->b[i]);
+               if (ignore_case)
+                       haystack->b[i] = tolower(haystack->b[i]);
                match_row(haystack, needle, bonuses, i, c, d, c, d);
        }
 
@@ -84,17 +85,17 @@ static cost get_cost(struct EmacsStr *needle, struct 
EmacsStr *haystack) {
 }
 
 /**
- * Returns whether haystack case-insensitively matches needle.
- *
- * This function does not take the value of completion-ignore-case
- * into account.
+ * Returns whether @p haystack matches @p needle.
  *
  * @param needle Null-terminated search string.
  * @param haystack Null-terminated completion candidate.
+ * @param ignore_case Whether to match case-insensitively.
  */
-static bool is_match(char *needle, char *haystack) {
+static bool is_match(char *needle, char *haystack, bool ignore_case) {
        while (*needle) {
-               if ((haystack = strpbrk(haystack, (char[]) { *needle, 
toupper(*needle), '\0' })))
+               if (ignore_case
+                       ? (haystack = strpbrk(haystack, (char[]) { *needle, 
toupper(*needle), '\0' }))
+                       : (haystack = strchr(haystack, *needle)))
                        ++needle, ++haystack; // Skip past matched character
                else
                        return false;
@@ -174,6 +175,7 @@ struct Batch {
 
 struct Shared {
        pthread_mutex_t mutex;
+       bool ignore_case;
        struct EmacsStr *needle;
        struct Batch *batches, *batches_end;
 };
@@ -199,10 +201,10 @@ static void *worker_routine(void *ptr) {
                unsigned num_matches = 0;
                for (unsigned i = 0; i < batch->len; ++i) {
                        struct Candidate *candidate = batch->xs + i;
-                       if (!is_match(needle->b, candidate->s->b)) continue;
+                       if (!is_match(needle->b, candidate->s->b, 
shared->ignore_case)) continue;
                        batch->xs[num_matches++] = (struct Candidate) {
                                .s = candidate->s,
-                               .key = get_cost(needle, candidate->s),
+                               .key = get_cost(needle, candidate->s, 
shared->ignore_case),
                        };
                }
                batch->len = num_matches;
@@ -222,13 +224,7 @@ struct Data {
        struct Worker *workers;
 };
 
-emacs_value hotfuzz_filter(emacs_env *env, ptrdiff_t nargs __attribute__ 
((__unused__)), emacs_value args[], void *data_ptr) {
-       // Short-circuit if needle is empty
-       ptrdiff_t needle_len;
-       env->copy_string_contents(env, args[0], NULL, &needle_len);
-       if (needle_len == /* solely null byte */ 1)
-               return args[1];
-
+emacs_value hotfuzz_filter(emacs_env *env, ptrdiff_t nargs, emacs_value 
args[], void *data_ptr) {
        struct Data *data = data_ptr;
        emacs_value fcar = env->intern(env, "car"),
                fcdr = env->intern(env, "cdr"),
@@ -262,11 +258,14 @@ emacs_value hotfuzz_filter(emacs_env *env, ptrdiff_t 
nargs __attribute__ ((__unu
        }
        if (!batches) return nil;
 
+       bool ignore_case = nargs >= 3 && env->is_not_nil(env, args[2]);
        struct EmacsStr *needle = copy_emacs_string(env, &bump, args[0]);
        if (!needle) goto error;
-       for (unsigned i = 0; i < needle->len; ++i)
-               needle->b[i] = tolower(needle->b[i]);
+       if (ignore_case)
+               for (size_t i = 0; i < needle->len; ++i)
+                       needle->b[i] = tolower(needle->b[i]);
        struct Shared shared = {
+               .ignore_case = ignore_case,
                .needle = needle,
                .batches = batches,
                .batches_end = batches + batch_idx + 1,
@@ -340,10 +339,10 @@ int emacs_module_init(struct emacs_runtime *rt) {
 
        env->funcall(env, env->intern(env, "defalias"), 2, (emacs_value[]) {
                        env->intern(env, "hotfuzz--filter-c"),
-                       env->make_function(env, 2, 2, hotfuzz_filter,
+                       env->make_function(env, 2, 3, hotfuzz_filter,
                                                           "Filter and sort 
CANDIDATES that match STRING.\n"
                                                           "\n"
-                                                          "\(fn STRING 
CANDIDATES)",
+                                                          "\(fn STRING 
CANDIDATES &optional IGNORE-CASE)",
                                                           &data),
                });
 
diff --git a/hotfuzz.el b/hotfuzz.el
index c95859987c..7445128958 100644
--- a/hotfuzz.el
+++ b/hotfuzz.el
@@ -149,7 +149,8 @@ HAYSTACK has to be a match according to `hotfuzz-filter'."
 CANDIDATES should be a list of strings."
   (cond
    ((not (<= 1 (length string) hotfuzz--max-needle-len)) candidates)
-   ((featurep 'hotfuzz-module) (hotfuzz--filter-c string candidates))
+   ((featurep 'hotfuzz-module)
+    (hotfuzz--filter-c string candidates completion-ignore-case))
    ((let ((re (concat
                "\\`"
                (mapconcat
diff --git a/test/tests.el b/test/tests.el
index b2d7da2726..e31b6c844f 100644
--- a/test/tests.el
+++ b/test/tests.el
@@ -43,10 +43,10 @@
 ;;; Filtering tests
 
 (ert-deftest case-sensitivity-test ()
-  (let ((xs '("aa" "aA" "Aa" "AA")))
+  (let ((xs '("aa" "aA " "Aa  " "AA   ")))
     (let ((completion-ignore-case nil))
-      (should (equal (hotfuzz-filter "a" xs) '("aa" "aA" "Aa")))
-      (should (equal (hotfuzz-filter "A" xs) '("Aa" "AA" "aA"))))
+      (should (equal (hotfuzz-filter "a" xs) '("aa" "aA " "Aa  ")))
+      (should (equal (hotfuzz-filter "A" xs) '("Aa  " "AA   " "aA "))))
     (let ((completion-ignore-case t))
       (should (equal (hotfuzz-filter "a" xs) xs))
       (should (equal (hotfuzz-filter "A" xs) xs)))))

Reply via email to