Subject: bug#42149: Substring and flex completion ignore implicit trailing ‘any’

From: Dario Gjorgjevski <[email-masked]>
Date: Wed, 09 Sep 2020 12:17:34 +0200

Hi João, hi Stefan,

Please find attached a patch that fixes this issue and optimizes the
scoring in PCM completion a bit.  As far as I can tell, this works fine
and can be merged, but please have a look and do some testing yourself.

>From 8699e72f92524da8041f63949cf29858caded4a5 Mon Sep 17 00:00:00 2001
From: Dario Gjorgjevski <[email-masked]>
Date: Wed, 9 Sep 2020 12:10:52 +0200
Subject: [PATCH] Fix (and optimize) scoring in PCM completion
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* lisp/minibuffer.el (completion-pcm--hilit-commonality): Fix scoring
to also include the last part of the query string.  This was
especially evident for single-character query strings, e.g.,
‘(completion-flex-all-completions "1" '("1" "foo1") nil 1)’ would
match both "1" and "foo1" with a completion-score of 0.  This
adjustment makes the completion-score of "1" be 1 and of "foo1" by
0.25.  See also bug#42149.  Furthermore, some optimizations are done.
---
 lisp/minibuffer.el | 78 ++++++++++++++++++++++------------------------
 1 file changed, 37 insertions(+), 41 deletions(-)

diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el
index 62a33f3e2d..7fa132f3c5 100644
--- a/lisp/minibuffer.el
+++ b/lisp/minibuffer.el
@@ -3191,17 +3191,20 @@ one-letter-long matches).")
   (when completions
     (let* ((re (completion-pcm--pattern->regex pattern 'group))
            (point-idx (completion-pcm--pattern-point-idx pattern))
-           (case-fold-search completion-ignore-case))
+           (case-fold-search completion-ignore-case)
+           (score-numerator (float (apply #'+ (mapcar (lambda (part)
+                                                        (if (stringp part)
+                                                            (length part)
+                                                          0))
+                                                      pattern)))))
       (mapcar
        (lambda (str)
-	 ;; Don't modify the string itself.
+         ;; Don't modify the string itself.
          (setq str (copy-sequence str))
          (unless (string-match re str)
            (error "Internal error: %s does not match %s" re str))
          (let* ((pos (if point-idx (match-beginning point-idx) (match-end 0)))
                 (md (match-data))
-                (start (pop md))
-                (end (pop md))
                 (len (length str))
                 ;; To understand how this works, consider these bad
                 ;; ascii(tm) diagrams showing how the pattern "foo"
@@ -3237,47 +3240,40 @@ one-letter-long matches).")
                 ;;    (SUM_across_i(hole_i_contrib) + 1) * len
                 ;;
                 ;; , where "len" is the string's length.
-                (score-numerator 0)
-                (score-denominator 0)
-                (last-b 0)
-                (update-score
-                 (lambda (a b)
-                   "Update score variables given match range (A B)."
-                   (setq
-                    score-numerator   (+ score-numerator (- b a)))
-                   (unless (or (= a last-b)
-                               (zerop last-b)
-                               (= a (length str)))
-                     (setq
-                      score-denominator (+ score-denominator
-                                           1
-                                           (expt (- a last-b 1)
-                                                 (/ 1.0
-                                                    flex-score-match-tightness)))))
-                   (setq
-                    last-b              b))))
-           (funcall update-score start start)
+                (full-match-start (pop md))
+                (full-match-end (pop md))
+                (leading-hole-start (pop md))
+                (leading-hole-end (pop md))
+                (match-start leading-hole-end)
+                (score-denominator 0))
            (while md
-             (funcall update-score start (car md))
-             (add-face-text-property
-              start (pop md)
-              'completions-common-part
-              nil str)
-             (setq start (pop md)))
-           (funcall update-score len len)
+             (let ((hole-start (pop md))
+                   (hole-end (pop md)))
+               (add-face-text-property
+                match-start hole-start
+                'completions-common-part
+                nil str)
+               (unless (= hole-start hole-end)
+                 (setq
+                  score-denominator (+ score-denominator
+                                       1
+                                       (expt
+                                        (- hole-end hole-start 1)
+                                        (/ 1.0 flex-score-match-tightness)))))
+               (setq match-start hole-end)))
            (add-face-text-property
-            start end
+            match-start full-match-end
             'completions-common-part
             nil str)
-           (if (> (length str) pos)
-               (add-face-text-property
-                pos (1+ pos)
-                'completions-first-difference
-                nil str))
-           (unless (zerop (length str))
-             (put-text-property
-              0 1 'completion-score
-              (/ score-numerator (* len (1+ score-denominator)) 1.0) str)))
+           (when (> len pos)
+             (add-face-text-property
+              pos (1+ pos)
+              'completions-first-difference
+              nil str))
+           (put-text-property
+            0 1
+            'completion-score
+            (/ score-numerator (* len (1+ score-denominator))) str))
          str)
        completions))))
 
-- 
2.17.1

Best regards,
Dario

-- 
[email-masked] :: +49 1525 8666837
%   gpg --keyserver 'hkps://hkps.pool.sks-keyservers.net' \
\`>     --recv-keys '744A4F0B4F1C9371'