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)