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

    Simplify the C implementation
    
    * Use conventional fat pointer string slices instead of prepending the
      string data with the Emacs Lisp value and length.
    * Try to copy_string_contents into the remaining buffer instead of
      always calling it once initially to get the required capacity.
    * Always pad copied Emacs strings to 8 bytes boundaries to remove the
      strtolower special cases.
    * Use a signed atomic counter instead of protecting it with a mutex.
    
      This makes the worker routine infallible. Also, skip assigning an
      initial batch to each worker and let them fetch it themselves.
---
 hotfuzz-module.c | 348 ++++++++++++++++++++-----------------------------------
 hotfuzz.el       |  36 +++---
 2 files changed, 143 insertions(+), 241 deletions(-)

diff --git a/hotfuzz-module.c b/hotfuzz-module.c
index e55af5e575..145f1de161 100644
--- a/hotfuzz-module.c
+++ b/hotfuzz-module.c
@@ -12,8 +12,7 @@
 #include <pthread.h>
 #include <unistd.h>
 
-#define MIN(a, b) ({ __typeof__(a) _a = (a), _b = (b); _a < _b ? _a : _b; })
-#define MAX(a, b) ({ __typeof__(a) _a = (a), _b = (b); _a > _b ? _a : _b; })
+#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
 
 #define MAX_NEEDLE_LEN 128
 #define MAX_HAYSTACK_LEN 512
@@ -21,52 +20,25 @@
 
 int plugin_is_GPL_compatible;
 
-/** An Emacs string made accessible by copying. */
-struct EmacsStr {
-       emacs_value value; ///< The original string value.
-       size_t len; ///< The length of the string minus the null byte.
-       char b[]; ///< The null-terminated copied string.
-};
-
-static char tolower_utf8(char c) {
-       return *u8"A" <= c && c <= *u8"Z" ? c + (*u8"a" - *u8"A") : c;
-}
+struct Str { char *b; size_t len; };
 
 static char toupper_utf8(char c) {
        return *u8"a" <= c && c <= *u8"z" ? c - (*u8"a" - *u8"A") : c;
 }
-
-static uint64_t tolower8(uint64_t x) {
-       uint64_t ones = 0x0101010101010101,
-               is_gt_Z = (0x7f * ones & x) + (0x7f - *u8"Z") * ones,
-               is_ge_A = (0x7f * ones & x) + (0x80 - *u8"A") * ones,
-               is_upper = 0x80 * ones & ~x & (is_ge_A ^ is_gt_Z);
-       return x | is_upper >> 2;
-}
-
-static void strtolower(struct EmacsStr *s) {
-       // Complicated in order to optimize out the calls to tolower_utf8
-       // on AMD64 System V with GCC 11.3.0.
-
-       size_t i = 0;
-       if ((alignof(struct EmacsStr) + offsetof(struct EmacsStr, b)) % 
alignof(uint64_t))
-               for (; ((uintptr_t) s->b + i) % alignof(uint64_t) && i < 
s->len; ++i)
-                       s->b[i] = tolower_utf8(s->b[i]);
-
-       size_t pad = alignof(struct EmacsStr)
-               - offsetof(struct EmacsStr, b) % alignof(struct EmacsStr);
-       for (; i + 8 < s->len + MIN(pad, 8); i += 8) {
-               uint64_t *x = (uint64_t *) (s->b + i);
-               *x = tolower8(*x);
+static void strtolower(struct Str s) {
+       uint64_t ones = ~UINT64_C(0) / 0xff, x;
+       for (size_t i = 0; i < s.len; i += sizeof x) {
+               memcpy(&x, s.b, sizeof x);
+               uint64_t is_gt_Z = (0x7f * ones & x) + (0x7f - *u8"Z") * ones,
+                       is_ge_A = (0x7f * ones & x) + (0x80 - *u8"A") * ones,
+                       is_upper = 0x80 * ones & ~x & (is_ge_A ^ is_gt_Z);
+               x |= is_upper >> 2;
+               memcpy(s.b, &x, sizeof x);
        }
-
-       if (pad < 8 - 1) for (; i < s->len; ++i) s->b[i] = 
tolower_utf8(s->b[i]);
 }
 
-typedef int cost;
-
-static cost char_bonus(char prev, char ch) {
-       cost word_bonus = 80;
+static int char_bonus(char prev, char ch) {
+       int word_bonus = 80;
        switch (ch) {
        case 'A' ... 'Z':
                if ('a' <= prev && prev <= 'z') return word_bonus;
@@ -83,86 +55,59 @@ static cost char_bonus(char prev, char ch) {
        }
 }
 
-static void calc_bonus(struct EmacsStr *haystack, cost *out) {
-       char ch, lastch = '/';
-       for (size_t i = 0; i < haystack->len; ++i, lastch = ch)
-               out[i] = char_bonus(lastch, ch = haystack->b[i]);
-}
-
-static void match_row(struct EmacsStr *a, struct EmacsStr *b, cost *bonuses, 
unsigned i,
-       cost *nc, cost *nd, cost *pc, cost *pd) {
-       cost g = 100, h = 5;
-       size_t m = b->len;
-       cost oldc, s = i ? g + h * i : 0;
+static void match_row(struct Str a, struct Str b, int *bonuses, unsigned i, 
int *c, int *d) {
+       int g = 100, h = 5;
+       size_t m = b.len;
+       int oldc, s = i ? g + h * i : 0;
        for (size_t j = 0; j < m; ++j, s = oldc) {
-               oldc = pc[j];
-               nc[j] = MIN(nd[j] = MIN(pd[j], oldc + g) + (j == m - 1 ? h : 2 
* h),
-                       a->b[i] == b->b[j] ? s - bonuses[i] : 100000);
+               oldc = c[j];
+               d[j] = MIN(d[j], oldc + g) + (j == m - 1 ? h : 2 * h);
+               c[j] = MIN(d[j], a.b[i] == b.b[j] ? s - bonuses[i] : 100000);
        }
 }
 
-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];
-       for (unsigned j = 0; j < m; ++j) c[j] = d[j] = 10000;
+static int calc_cost(struct Str needle, struct Str haystack, bool ignore_case) 
{
+       unsigned n = haystack.len, m = needle.len;
+       if (n > MAX_HAYSTACK_LEN || m > MAX_NEEDLE_LEN) return 100000;
+       int c[MAX_NEEDLE_LEN], d[MAX_NEEDLE_LEN];
+       for (unsigned j = 0; j < m; ++j) c[j] = d[j] = 100000;
 
-       cost bonuses[MAX_HAYSTACK_LEN];
-       calc_bonus(haystack, bonuses);
+       int bonuses[MAX_HAYSTACK_LEN];
+       char ch, lastch = '/';
+       for (size_t i = 0; i < n; ++i, lastch = ch)
+               bonuses[i] = char_bonus(lastch, ch = haystack.b[i]);
 
        if (ignore_case) strtolower(haystack);
        for (unsigned i = 0; i < n; ++i)
-               match_row(haystack, needle, bonuses, i, c, d, c, d);
+               match_row(haystack, needle, bonuses, i, c, d);
 
        return c[m - 1];
 }
 
 /**
- * Returns whether @p haystack matches @p needle.
+ * Returns whether @a haystack matches @a 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, bool ignore_case) {
-       while (*needle) {
+       while (*needle)
                if (ignore_case
                        ? (haystack = strpbrk(haystack, (char[]) { *needle, 
toupper_utf8(*needle), '\0' }))
                        : (haystack = strchr(haystack, *needle)))
                        ++needle, ++haystack; // Skip past matched character
                else
                        return false;
-       }
        return true;
 }
 
 /** Intrusive linked list of bump allocation blocks. */
 struct Bump {
        struct Bump *next;
-       size_t index, capacity;
-       char b[];
+       char *cursor, *limit, b[];
 };
 
-/**
- * Allocates the specified number of bytes.
- *
- * Returns NULL on failure.
- */
-static void *bump_alloc(struct Bump **head, size_t len) {
-       if (!*head || (*head)->capacity - (*head)->index < len) {
-               size_t capacity = MAX(*head ? 2 * (*head)->capacity : 1024, 
len);
-               struct Bump *new_head;
-               if (!(new_head = malloc(sizeof *new_head + capacity)))
-                       return NULL;
-               *new_head = (struct Bump) { .next = *head, .index = 0, 
.capacity = capacity };
-               *head = new_head;
-       }
-
-       void *p = (*head)->b + (*head)->index;
-       (*head)->index += len;
-       return p;
-}
-
 static void bump_free(struct Bump *head) {
        while (head) {
                struct Bump *next = head->next;
@@ -171,31 +116,43 @@ static void bump_free(struct Bump *head) {
        }
 }
 
-/**
- * Copies the Emacs string to make its lifetime that of the allocator.
- */
-static struct EmacsStr *copy_emacs_string(emacs_env *env, struct Bump **bump, 
emacs_value value) {
-       ptrdiff_t len;
+/** Copies the Emacs string to make its contents accessible. */
+static struct Str copy_emacs_string(emacs_env *env, struct Bump **bump, 
emacs_value value) {
+       char *buf = NULL;
+       ptrdiff_t origlen, len;
+       if (*bump) {
+               // Opportunistically try to copy into remaining space
+               buf = (*bump)->cursor;
+               len = origlen = (*bump)->limit - (*bump)->cursor;
+       }
        // Determine the size of the string (including null-terminator)
-       if (!env->copy_string_contents(env, value, NULL, &len))
-               return NULL;
-
-       struct EmacsStr *result;
-       // Note: Since only EmacsStr:s are allocated with bump_alloc we
-       // may use its smaller alignment rather than the scalar maximum.
-       if (!(result = bump_alloc(bump, sizeof *result + len
-                               + alignof(struct EmacsStr) - 1 & 
~(alignof(struct EmacsStr) - 1))))
-               return NULL;
-
-       result->value = value;
-       result->len = len - 1;
-       env->copy_string_contents(env, value, result->b, &len);
-       return result;
+       if (env->copy_string_contents(env, value, buf, &len)) {
+               if (buf) goto success;
+       } else {
+               if (!buf || len == origlen) return (struct Str) { 0 };
+               env->non_local_exit_clear(env);
+       }
+
+       size_t capacity = *bump ? 2 * ((*bump)->limit - (*bump)->b) : 2048;
+       if (capacity < (size_t) len) capacity = len + alignof(uint64_t) - 1;
+       struct Bump *new;
+       if (!(new = malloc(sizeof *new + capacity))) return (struct Str) { 0 };
+       *new = (struct Bump) { .next = *bump, .cursor = new->b, .limit = new->b 
+ capacity };
+       *bump = new;
+
+       env->copy_string_contents(env, value, buf = new->cursor, &len);
+success:
+       (*bump)->cursor = (char *) (((uintptr_t) (*bump)->cursor + len
+                       + alignof(uint64_t) - 1) & ~(alignof(uint64_t) - 1));
+       return (struct Str) { buf, len - 1 };
 }
 
 struct Candidate {
-       struct EmacsStr *s;
-       cost key;
+       emacs_value value;
+       union {
+               struct Str s;
+               int key;
+       };
 };
 
 static int cmp_candidate(const void *a, const void *b) {
@@ -208,139 +165,95 @@ struct Batch {
 };
 
 struct Shared {
-       pthread_mutex_t mutex;
-       bool ignore_case;
-       struct EmacsStr *needle;
-       struct Batch *batches, *batches_end;
-};
-
-struct Worker {
-       pthread_t thread;
-       struct Shared *shared;
-       struct Batch *batch; ///< The initial batch to work on.
+       const bool ignore_case;
+       const struct Str needle;
+       struct Batch *const batches;
+       _Atomic ssize_t remaining;
 };
 
-static enum JobRetVal {
-       JOB_FINISHED,
-       JOB_FAILED
-} job_finished = JOB_FINISHED, job_failed = JOB_FAILED;
-
 static void *worker_routine(void *ptr) {
-       struct Worker *worker = ptr;
-       struct Shared *shared = worker->shared;
-       struct EmacsStr *needle = shared->needle;
-       struct Batch *batch = worker->batch;
+       struct Shared *shared = ptr;
+       struct Str needle = shared->needle;
 
-       do {
-               unsigned num_matches = 0;
+       ssize_t batch_idx;
+       while ((batch_idx = --shared->remaining) >= 0) {
+               struct Batch *batch = shared->batches + batch_idx;
+               unsigned n = 0;
                for (unsigned i = 0; i < batch->len; ++i) {
-                       struct Candidate *candidate = batch->xs + i;
-                       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, 
shared->ignore_case),
-                       };
+                       struct Candidate x = batch->xs[i];
+                       if (!is_match(needle.b, x.s.b, shared->ignore_case)) 
continue;
+                       x.key = calc_cost(needle, x.s, shared->ignore_case);
+                       batch->xs[n++] = x;
                }
-               batch->len = num_matches;
-
-               // Try to fetch a new batch
-               if (pthread_mutex_lock(&shared->mutex)) return &job_failed;
-               batch = shared->batches < shared->batches_end ? 
shared->batches++ : NULL;
-               pthread_mutex_unlock(&shared->mutex);
-       } while (batch);
+               batch->len = n;
+       }
 
-       return &job_finished;
+       return NULL;
 }
 
-/** Module userdata that gets allocated once at initialization. */
+/** Module userdata allocated at initialization. */
 struct Data {
        unsigned max_workers;
-       struct Worker *workers;
+       pthread_t threads[];
 };
 
-emacs_value hotfuzz_filter(emacs_env *env, ptrdiff_t nargs, emacs_value 
args[], void *data_ptr) {
+static 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"),
-               fcons = env->intern(env, "cons"),
-               nil = env->intern(env, "nil");
+       emacs_value fcar = env->intern(env, "car"), fcdr = env->intern(env, 
"cdr"),
+               fcons = env->intern(env, "cons"), nil = env->intern(env, "nil");
        struct Bump *bump = NULL;
        int success = false;
        emacs_value result = nil;
 
        // Collect all candidates
-       emacs_value list = args[1];
        struct Batch *batches = NULL;
-       size_t batch_idx = 0, capacity = 0;
-       while (env->is_not_nil(env, list)) {
-               if ((batches && batches[batch_idx].len >= BATCH_SIZE ? 
++batch_idx : batch_idx)
-                       >= capacity) {
-                       capacity = capacity ? 2 * capacity : 1;
+       size_t batch_idx = 0, capacity;
+       for (emacs_value list = args[1]; env->is_not_nil(env, list);
+                       list = env->funcall(env, fcdr, 1, (emacs_value[]) { 
list })) {
+               if (!batches || (batches[batch_idx].len >= BATCH_SIZE && 
++batch_idx >= capacity)) {
+                       capacity = batches ? 2 * capacity : 1;
                        struct Batch *new_batches;
-                       if (!(new_batches = realloc(batches, capacity * sizeof 
*batches)))
-                               goto error;
+                       if (!(new_batches = realloc(batches, capacity * sizeof 
*batches))) goto err;
                        batches = new_batches;
-                       for (size_t i = batch_idx; i < capacity; ++i)
-                               batches[i].len = 0;
+                       for (size_t i = batch_idx; i < capacity; ++i) 
batches[i].len = 0;
                }
 
-               emacs_value value = env->funcall(env, fcar, 1, (emacs_value[]) 
{list});
-               struct Batch *b = batches + batch_idx;
-               if (!(b->xs[b->len++].s = copy_emacs_string(env, &bump, value)))
-                       goto error;
-               list = env->funcall(env, fcdr, 1, (emacs_value[]) {list});
+               emacs_value value = env->funcall(env, fcar, 1, (emacs_value[]) 
{ list });
+               struct Batch *batch = batches + batch_idx;
+               struct Candidate *x = batch->xs + batch->len++;
+               if (!(x->s = copy_emacs_string(env, &bump, x->value = 
value)).b) goto err;
        }
        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;
-       if (ignore_case)
-               for (size_t i = 0; i < needle->len; ++i)
-                       needle->b[i] = tolower_utf8(needle->b[i]);
+       bool ignore_case = env->is_not_nil(env, args[2]);
+       struct Str needle = copy_emacs_string(env, &bump, args[0]);
+       if (!needle.b) goto err;
+       if (ignore_case) strtolower(needle);
        struct Shared shared = {
                .ignore_case = ignore_case,
                .needle = needle,
                .batches = batches,
-               .batches_end = batches + batch_idx + 1,
+               .remaining = batch_idx + 1,
        };
-       if (pthread_mutex_init(&shared.mutex, NULL)) goto error;
-       if (pthread_mutex_lock(&shared.mutex)) goto mutex_error;
-       enum JobRetVal res = job_finished;
-       unsigned worker_count;
-
-       struct Worker *workers = data->workers;
-       for (worker_count = 0; worker_count < data->max_workers
-                               && shared.batches < shared.batches_end; 
++worker_count) {
-               struct Worker *worker = workers + worker_count;
-               *worker = (struct Worker) {
-                       .shared = &shared,
-                       .batch = shared.batches++,
-               };
-
-               if (pthread_create(&worker->thread, NULL, worker_routine, 
worker)) {
-                       // Join all workers in order to at least safely destroy 
mutex
-                       res = job_failed;
-                       break;
-               }
-       }
-       pthread_mutex_unlock(&shared.mutex);
 
+       unsigned num_workers = 0;
+       for (; num_workers < MIN(data->max_workers, batch_idx + 1); 
++num_workers)
+               if (pthread_create(data->threads + num_workers, NULL, 
worker_routine, &shared))
+                       // Join all workers in order to at least safely free 
memory
+                       goto err_join_threads;
+       success = true;
+
+err_join_threads:
        // Wait for all worker threads
-       for (unsigned i = 0; i < worker_count; ++i) {
-               enum JobRetVal *retval;
-               pthread_join(workers[i].thread, (void **) &retval);
-               res |= *retval;
-       }
-       if (res != job_finished) goto mutex_error;
+       for (unsigned i = 0; i < num_workers; ++i) 
pthread_join(data->threads[i], NULL);
+       if (!success) goto err;
 
-       success = true;
-       if (env->process_input(env) == emacs_process_input_quit) goto 
mutex_error;
+       if (env->process_input(env) == emacs_process_input_quit) goto err;
 
        // Compact all batches
        size_t len = batches[0].len;
        struct Candidate *xs = batches[0].xs;
-       for (struct Batch *b = batches + 1; b < shared.batches_end; ++b) {
+       for (struct Batch *b = batches + 1; b <= batches + batch_idx; ++b) {
                unsigned n = b->len;
                memmove(xs + len, b->xs, n * sizeof *b->xs);
                len += n;
@@ -348,11 +261,9 @@ emacs_value hotfuzz_filter(emacs_env *env, ptrdiff_t 
nargs, emacs_value args[],
        qsort(xs, len, sizeof *xs, cmp_candidate); // Sort the completions
 
        for (size_t i = len; i-- > 0;)
-               result = env->funcall(env, fcons, 2, (emacs_value[]) 
{xs[i].s->value, result});
+               result = env->funcall(env, fcons, 2, (emacs_value[]) { 
xs[i].value, result });
 
-mutex_error:
-       pthread_mutex_destroy(&shared.mutex);
-error:
+err:
        free(batches);
        bump_free(bump);
 
@@ -362,27 +273,24 @@ error:
 }
 
 int emacs_module_init(struct emacs_runtime *rt) {
-       // Verify compatability with Emacs executable loading this module
-       if ((size_t) rt->size < sizeof *rt)
-               return 1;
+       // Verify compatibility with the Emacs executable loading this module
+       if ((size_t) rt->size < sizeof *rt) return 1;
        emacs_env *env = rt->get_environment(rt);
-       if ((size_t) env->size < sizeof *env)
-               return 2;
+       if ((size_t) env->size < sizeof *env) return 2;
 
-       static struct Data data;
-       data.max_workers = sysconf(_SC_NPROCESSORS_ONLN);
-       if (!(data.workers = malloc(data.max_workers * sizeof *data.workers)))
-               return 1;
+       long max_workers = sysconf(_SC_NPROCESSORS_ONLN);
+       struct Data *data;
+       if (!(data = malloc(max_workers * sizeof *data->threads))) return 1;
+       *data = (struct Data) { max_workers };
 
        env->funcall(env, env->intern(env, "defalias"), 2, (emacs_value[]) {
                        env->intern(env, "hotfuzz--filter-c"),
-                       env->make_function(env, 2, 3, hotfuzz_filter,
+                       env->make_function(env, 3, 3, hotfuzz_filter,
                                "Filter and sort CANDIDATES that match 
STRING.\n"
                                "\n"
-                               "\(fn STRING CANDIDATES &optional IGNORE-CASE)",
-                               &data),
+                               "\(fn STRING CANDIDATES IGNORE-CASE)",
+                               data),
                });
-
        env->funcall(env, env->intern(env, "provide"), 1,
                (emacs_value[]) { env->intern(env, "hotfuzz-module") });
 
diff --git a/hotfuzz.el b/hotfuzz.el
index bfa39c6de4..6a9d7f79d2 100644
--- a/hotfuzz.el
+++ b/hotfuzz.el
@@ -20,26 +20,23 @@
 
 ;;; Code:
 
-;; See: Myers, Eugene W., and Webb Miller. "Optimal alignments in
-;;      linear space." Bioinformatics 4.1 (1988): 11-17.
+;; See: GOTOH, Osamu. An improved algorithm for matching biological
+;;      sequences. Journal of molecular biology, 1982, 162.3: 705-708.
 
 (eval-when-compile (require 'cl-lib))
 (require 'hotfuzz-module nil t)
 (declare-function hotfuzz--filter-c "hotfuzz-module")
 
-(defgroup hotfuzz nil
-  "Fuzzy completion style."
-  :group 'minibuffer)
+(defgroup hotfuzz nil "Fuzzy completion style." :group 'minibuffer)
 
 (defcustom hotfuzz-max-highlighted-completions 25
   "The number of top-ranking completions that should be highlighted.
 Large values will decrease performance."
   :type 'integer)
 
-;; Since the vectors are pre-allocated the optimization where
-;; symmetricity w.r.t. to insertions/deletions means it suffices to
-;; allocate min(#needle, #haystack) for C/D when only calculating the
-;; cost does not apply.
+;; Pre-allocated vectors make the cost-only calulation optimization
+;; where symmetricity w.r.t. insertions/deletions means it suffices to
+;; allocate min(#needle, #haystack) for C/D inapplicable.
 (defconst hotfuzz--max-needle-len 128)
 (defconst hotfuzz--max-haystack-len 512)
 (defvar hotfuzz--c (make-vector hotfuzz--max-needle-len 0))
@@ -101,14 +98,12 @@ and ND/PD respectively may alias."
         (aref c (1- m)))))) ; Final cost
 
 (defun hotfuzz-highlight (needle haystack)
-  "Highlight the characters that NEEDLE matched in HAYSTACK.
+  "Highlight destructively the characters NEEDLE matched in HAYSTACK.
 HAYSTACK has to be a match according to `hotfuzz-all-completions'."
   (let ((n (length haystack)) (m (length needle))
-        (c hotfuzz--c) (d hotfuzz--d)
+        (c (fillarray hotfuzz--c 10000)) (d (fillarray hotfuzz--d 10000))
         (case-fold-search completion-ignore-case))
     (unless (or (> n hotfuzz--max-haystack-len) (> m hotfuzz--max-needle-len))
-      (fillarray c 10000)
-      (fillarray d 10000)
       (hotfuzz--calc-bonus haystack)
       (cl-loop
        with rows initially
@@ -129,12 +124,11 @@ HAYSTACK has to be a match according to 
`hotfuzz-all-completions'."
 (defun hotfuzz-all-completions (string table &optional pred point)
   "Get hotfuzz-completions of STRING in TABLE.
 See `completion-all-completions' for the semantics of PRED and POINT.
-This function prematurely sorts the completions; mutating the returned
-list before passing it to `display-sort-function' or
-`cycle-sort-function' will lead to inaccuracies."
-  (unless point (setq point (length string)))
+This function prematurely sorts the completions; mutating the result
+before passing it to `display-sort-function' or `cycle-sort-function'
+will lead to inaccuracies."
   (let* ((beforepoint (substring string 0 point))
-         (afterpoint (substring string point))
+         (afterpoint (if point (substring string point) ""))
          (bounds (completion-boundaries beforepoint table pred afterpoint))
          (prefix (substring beforepoint 0 (car bounds)))
          (needle (substring beforepoint (car bounds)))
@@ -187,11 +181,11 @@ list before passing it to `display-sort-function' or
 
 ;;;###autoload
 (progn
-  ;; Why is the Emacs completions API so cursed?
-  (put 'hotfuzz 'completion--adjust-metadata #'hotfuzz--adjust-metadata)
   (add-to-list 'completion-styles-alist
                '(hotfuzz completion-flex-try-completion hotfuzz-all-completions
-                         "Fuzzy completion.")))
+                         "Fuzzy completion."))
+  ;; Why is the Emacs completions API so cursed?
+  (put 'hotfuzz 'completion--adjust-metadata #'hotfuzz--adjust-metadata))
 
 (provide 'hotfuzz)
 ;;; hotfuzz.el ends here

Reply via email to