branch: externals/ivy
commit 5a041b11aa68359a0d027adfba43d2c4aa59e622
Author: Basil L. Contovounesios <ba...@contovou.net>
Commit: Basil L. Contovounesios <ba...@contovou.net>

    Fix ivy-completion-common-length
    
    Thanks to Kien Nguyen <kien.n.qu...@gmail.com> and
    Madhu <enom...@meer.net> for suggestions.
    
    Fixes parts of #1361, #1755, #2879, #3051, #3056,
    and https://bugs.gnu.org/76440.
    Relates to https://bugs.gnu.org/71419.
    
    * ivy.el (ivy--face-list-p): New compatibility shim.
    (ivy-completion-common-length): Use it to handle both atom and list
    values for the face property, since it varies across Emacs versions.
    Use previous-single-property-change instead of iterating by char.
    Improve documentation.
    (ivy-completion-in-region): Translate a 'not found' result of
    ivy-completion-common-length into a first difference at index 0, not
    at the end of the string.  That means, interpret a lack of
    completions-first-difference as no commonality.  This is more
    consistent with both the definition of ivy-completion-common-length
    and completion changes in Emacs 30.
    
    * ivy-test.el (ivy-completion-common-length): Extend tests.
---
 ivy-test.el | 34 ++++++++++++++++++++++++++++++++++
 ivy.el      | 58 +++++++++++++++++++++++++++++++++++++++++-----------------
 2 files changed, 75 insertions(+), 17 deletions(-)

diff --git a/ivy-test.el b/ivy-test.el
index e33eccb7e9..e22645d293 100644
--- a/ivy-test.el
+++ b/ivy-test.el
@@ -1012,12 +1012,21 @@ Since `execute-kbd-macro' doesn't pick up a let-bound 
`default-directory'.")
             (ert-info ((format "%S" str) :prefix "String: ")
               (should (= (ivy-completion-common-length str) (car pair))))))
         '((0 ""
+             #("a"   0 1 (face completions-first-difference))
+             #("ab"  0 1 (face completions-first-difference))
+             #("abc" 0 1 (face completions-first-difference))
              #("a"   0 1 (face (completions-first-difference)))
              #("ab"  0 1 (face (completions-first-difference)))
              #("abc" 0 1 (face (completions-first-difference)))
              #("a"   0 1 (font-lock-face (completions-first-difference)))
              #("ab"  0 1 (font-lock-face (completions-first-difference)))
              #("abc" 0 1 (font-lock-face (completions-first-difference)))
+             #("abc"
+               0 1 (face completions-first-difference)
+               1 2 (face default))
+             #("abc"
+               0 1 (face completions-first-difference)
+               2 3 (face default))
              #("abc"
                0 1 (face (completions-first-difference))
                1 2 (face default))
@@ -1026,6 +1035,10 @@ Since `execute-kbd-macro' doesn't pick up a let-bound 
`default-directory'.")
                2 3 (face default)))
           (1 "a"
              #("a"   0 1 (face default))
+             #("ab"  1 2 (face completions-first-difference))
+             #("ab"  0 2 (face completions-first-difference))
+             #("abc" 1 2 (face completions-first-difference))
+             #("abc" 0 2 (face completions-first-difference))
              #("ab"  1 2 (face (completions-first-difference)))
              #("ab"  0 2 (face (completions-first-difference)))
              #("abc" 1 2 (face (completions-first-difference)))
@@ -1034,6 +1047,12 @@ Since `execute-kbd-macro' doesn't pick up a let-bound 
`default-directory'.")
              #("ab"  0 2 (font-lock-face (completions-first-difference)))
              #("abc" 1 2 (font-lock-face (completions-first-difference)))
              #("abc" 0 2 (font-lock-face (completions-first-difference)))
+             #("abc"
+               0 1 (face default)
+               1 2 (face completions-first-difference))
+             #("abc"
+               1 2 (face completions-first-difference)
+               2 3 (face default))
              #("abc"
                0 1 (face default)
                1 2 (face (completions-first-difference)))
@@ -1044,18 +1063,30 @@ Since `execute-kbd-macro' doesn't pick up a let-bound 
`default-directory'.")
              #("ab"  0 1 (face default))
              #("ab"  1 2 (face default))
              #("ab"  0 2 (face default))
+             #("abc" 2 3 (face completions-first-difference))
+             #("abc" 1 3 (face completions-first-difference))
+             #("abc" 0 3 (face completions-first-difference))
              #("abc" 2 3 (face (completions-first-difference)))
              #("abc" 1 3 (face (completions-first-difference)))
              #("abc" 0 3 (face (completions-first-difference)))
              #("abc" 2 3 (font-lock-face (completions-first-difference)))
              #("abc" 1 3 (font-lock-face (completions-first-difference)))
              #("abc" 0 3 (font-lock-face (completions-first-difference)))
+             #("abc"
+               0 1 (face default)
+               2 3 (face completions-first-difference))
+             #("abc"
+               1 2 (face default)
+               2 3 (face completions-first-difference))
              #("abc"
                0 1 (face default)
                2 3 (face (completions-first-difference)))
              #("abc"
                1 2 (face default)
                2 3 (face (completions-first-difference)))
+             #("test/"
+               0 2 (face completions-common-part)
+               2 3 (face completions-first-difference))
              #("test/"
                0 2 (face completions-common-part)
                2 3 (face (completions-first-difference))))
@@ -1067,6 +1098,9 @@ Since `execute-kbd-macro' doesn't pick up a let-bound 
`default-directory'.")
              #("abc" 1 3 (face default))
              #("abc" 0 3 (face default)))
           (5 #("Math/E"
+               0 5 (face completions-common-part)
+               5 6 (face completions-first-difference))
+             #("Math/E"
                0 5 (face completions-common-part)
                5 6 (face (completions-first-difference)))))))
 
diff --git a/ivy.el b/ivy.el
index a109025e09..fe3f58a6ca 100644
--- a/ivy.el
+++ b/ivy.el
@@ -2639,24 +2639,44 @@ The previous string is between `ivy-completion-beg' and 
`ivy-completion-end'."
           (set-marker (overlay-get cursor 'point) (point))
           (set-marker (overlay-get cursor 'mark) (point)))))))
 
+(defalias 'ivy--face-list-p
+  (if (fboundp 'face-list-p)
+      #'face-list-p
+    (lambda (face)
+      (and (listp face)
+           (listp (cdr face))
+           (not (keywordp (car face))))))
+  "Compatibility shim for Emacs 25 `face-list-p'.")
+
+;; FIXME: Should this return the smallest such index instead?
+;; Usually the two are equal, but perhaps there exist more
+;; exotic applications of `completions-first-difference'.
+;;
+;; Completing files under a directory foo/ can have a first difference at
+;; index 0 in some Emacs versions, and no such property in other versions.
+;; So perhaps this function should return 0 instead of (length str) when no
+;; property is found?  That still follows the 'largest index' definition.
 (defun ivy-completion-common-length (str)
-  "Return the amount of characters that match in  STR.
-
-`completion-all-completions' computes this and returns the result
-via text properties.
-
-The first non-matching part is propertized:
-- either with: (face (completions-first-difference))
-- or: (font-lock-face completions-first-difference)."
-  (let ((char-property-alias-alist '((face font-lock-face)))
-        (i (1- (length str))))
-    (catch 'done
-      (while (>= i 0)
-        (when (equal (get-text-property i 'face str)
-                     '(completions-first-difference))
-          (throw 'done i))
-        (cl-decf i))
-      (throw 'done (length str)))))
+  "Return the length of the completion-matching prefix of STR.
+
+That is, return the largest index into STR at which either the
+`face' or `font-lock-face' property value contains the face
+`completions-first-difference'.
+If no such index is found, return the length of STR.
+
+Typically the completion-matching parts of STR have previously been
+propertized by `completion-all-completions'."
+  (let* ((char-property-alias-alist '((face font-lock-face)))
+         (cmn (length str))
+         (i cmn))
+    (when (> i 0)
+      (while (if (let ((face (get-text-property (1- i) 'face str)))
+                   (or (eq 'completions-first-difference face)
+                       (and (ivy--face-list-p face)
+                            (memq 'completions-first-difference face))))
+                 (ignore (setq cmn (1- i)))
+               (setq i (previous-single-property-change i 'face str)))))
+    cmn))
 
 (defun ivy-completion-in-region (start end collection &optional predicate)
   "An Ivy function suitable for `completion-in-region-function'.
@@ -2685,6 +2705,10 @@ See `completion-in-region' for further information."
            (when (eq collection 'crm--collection-fn)
              (setq comps (delete-dups comps)))
            (let* ((cmn (ivy-completion-common-length (car comps)))
+                  ;; Translate a 'not found' result to 0.  Do this here 
(instead
+                  ;; of fixing `ivy-completion-common-length') for backward
+                  ;; compatibility, since it's a potentially public function.
+                  (cmn (if (= cmn (length (car comps))) 0 cmn))
                   (initial (cond ((= cmn 0)
                                   "")
                                  ((>= cmn reg)

Reply via email to