branch: externals/ivy
commit 5a041b11aa68359a0d027adfba43d2c4aa59e622
Author: Basil L. Contovounesios <[email protected]>
Commit: Basil L. Contovounesios <[email protected]>
Fix ivy-completion-common-length
Thanks to Kien Nguyen <[email protected]> and
Madhu <[email protected]> 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)