branch: externals/ivy commit 36f68985dec5cb7a90518040efb85a91219a983c Merge: 6cd8350655 6227843101 Author: Basil L. Contovounesios <ba...@contovou.net> Commit: Basil L. Contovounesios <ba...@contovou.net>
Merge branch 'master' into externals/ivy --- .mailmap | 1 + CONTRIBUTING.org | 6 +-- colir.el | 46 ++++++++-------- doc/ivy-ox.el | 2 +- doc/ivy.org | 14 ++--- doc/ivy.texi | 8 +-- doc/scripts.el | 2 +- ivy-faces.el | 2 +- ivy-overlay.el | 2 +- ivy-test.el | 112 +++++++++++++++++++++++++++++++++------ ivy.el | 158 ++++++++++++++++++++++++++++++++++--------------------- 11 files changed, 237 insertions(+), 116 deletions(-) diff --git a/.mailmap b/.mailmap index fa38b3f201..eb78d6ffb0 100644 --- a/.mailmap +++ b/.mailmap @@ -19,6 +19,7 @@ <ricouillet...@gmail.com> <eric.da...@u-cergy.fr> <syo...@gmail.com> <shohei.yosh...@dena.com> <ywwr...@gmail.com> <58066925+ywwr...@users.noreply.github.com> +Christopher Floess <chris.flo...@mailbox.org> Daanturo <daant...@gmail.com> <dantle....@gmail.com> Diego A. Mundo <diegoamu...@gmail.com> Earl Hyatt <oka...@protonmail.com> <ej...@protonmail.com> diff --git a/CONTRIBUTING.org b/CONTRIBUTING.org index 6fcc7278ea..772e9ec4ca 100644 --- a/CONTRIBUTING.org +++ b/CONTRIBUTING.org @@ -14,9 +14,9 @@ init file. This shortcut will start =emacs -Q= for you with =ivy-mode= already loaded: #+begin_src sh -git clone https://github.com/abo-abo/swiper/ -cd swiper -make plain + git clone 'https://github.com/abo-abo/swiper.git' + cd swiper + make deps plain #+end_src * Contributing code diff --git a/colir.el b/colir.el index f371ef3839..a30046f0cc 100644 --- a/colir.el +++ b/colir.el @@ -1,6 +1,6 @@ ;;; colir.el --- Color blending library -*- lexical-binding: t -*- -;; Copyright (C) 2015-2024 Free Software Foundation, Inc. +;; Copyright (C) 2015-2025 Free Software Foundation, Inc. ;; Author: Oleh Krehel <ohwoeo...@gmail.com> @@ -35,7 +35,7 @@ (require 'color) (defcustom colir-compose-method #'colir-compose-alpha - "Select a method to compose two color channels." + "The method `colir-blend' uses to compose two color channels." :group 'ivy :type '(radio (function-item colir-compose-alpha) @@ -43,22 +43,25 @@ (function-item colir-compose-soft-light))) (defun colir-compose-soft-light (a b) - "Compose A and B channels." + "Compose color channels A and B in Soft Light blend mode. +See URL `https://en.wikipedia.org/wiki/Blend_modes#Soft_Light'." (if (< b 0.5) (+ (* 2 a b) (* a a (- 1 b b))) - (+ (* 2 a (- 1 b)) (* (sqrt a) (- (* 2 b) 1))))) + (+ (* 2 a (- 1 b)) (* (sqrt a) (+ b b -1))))) (defun colir-compose-overlay (a b) - "Compose A and B channels." + "Compose color channels A and B in Overlay blend mode. +See URL `https://en.wikipedia.org/wiki/Blend_modes#Overlay'." (if (< a 0.5) (* 2 a b) (- 1 (* 2 (- 1 a) (- 1 b))))) +;; Generalizes Emacs 31 `color-blend'. (defun colir-compose-alpha (a b &optional alpha gamma) - "Compose A and B channels. -Optional argument ALPHA is a number between 0.0 and 1.0 which corresponds -to the influence of A on the result. Default value is 0.5. -Optional argument GAMMA is used for gamma correction. Default value is 2.2." + "Compose color channels A and B using alpha blending. +Optional argument ALPHA controls the influence of A on the result. +It is a number between 0.0 and 1.0, inclusive (default 0.5). +Optional argument GAMMA controls gamma correction (default 2.2)." (setq alpha (or alpha 0.5)) (setq gamma (or gamma 2.2)) (+ (* (expt a gamma) alpha) (* (expt b gamma) (- 1 alpha)))) @@ -69,8 +72,8 @@ C1 and C2 are triples of floats in [0.0 1.0] range." (apply #'color-rgb-to-hex (cl-mapcar (if (eq (frame-parameter nil 'background-mode) 'dark) - ;; this method works nicely for dark themes - 'colir-compose-soft-light + ;; This method works nicely for dark themes. + #'colir-compose-soft-light colir-compose-method) c1 c2))) @@ -85,17 +88,16 @@ C1 and C2 are triples of floats in [0.0 1.0] range." (defun colir--blend-background (start next prevn face object) (let ((background-prev (face-background prevn))) - (progn - (put-text-property - start next 'face - (if background-prev - (cons `(background-color - . ,(colir-blend - (colir-color-parse background-prev) - (colir-color-parse (face-background face nil t)))) - prevn) - (list face prevn)) - object)))) + (put-text-property + start next 'face + (if background-prev + (cons `(background-color + . ,(colir-blend + (colir-color-parse background-prev) + (colir-color-parse (face-background face nil t)))) + prevn) + (list face prevn)) + object))) (defun colir-blend-face-background (start end face &optional object) "Append to the face property of the text from START to END the face FACE. diff --git a/doc/ivy-ox.el b/doc/ivy-ox.el index fd33b453fd..863e7dce92 100644 --- a/doc/ivy-ox.el +++ b/doc/ivy-ox.el @@ -1,6 +1,6 @@ ;;; ivy-ox.el --- org-export settings for Ivy -*- lexical-binding: t -*- -;; Copyright (C) 2015-2024 Free Software Foundation, Inc. +;; Copyright (C) 2015-2025 Free Software Foundation, Inc. ;; Author: Oleh Krehel diff --git a/doc/ivy.org b/doc/ivy.org index cb1191d1d4..d54cdaf1f6 100644 --- a/doc/ivy.org +++ b/doc/ivy.org @@ -18,8 +18,8 @@ #+STARTUP: indent * Setup :noexport: #+BEGIN_SRC elisp :exports results :results silent -(add-to-list 'load-path default-directory) -(require 'ivy-ox) + (add-to-list 'load-path (directory-file-name default-directory)) + (require 'ivy-ox) #+END_SRC * Writing this manual :noexport: To highlight a section without introducing a new subheading use @@ -70,7 +70,7 @@ final candidate is either through simple keyboard character inputs or through powerful regular expressions. #+TEXINFO: @end ifnottex -Copyright (C) 2015--2024 Free Software Foundation, Inc. +Copyright (C) 2015--2025 Free Software Foundation, Inc. #+BEGIN_QUOTE Permission is granted to copy, distribute and/or modify this document @@ -189,15 +189,15 @@ For package manager details, see [[info:emacs#Packages]]. First clone the Swiper repository with: #+begin_src sh - cd ~/git && git clone https://github.com/abo-abo/swiper - cd swiper && make compile + cd ~/git && git clone 'https://github.com/abo-abo/swiper.git' + cd swiper && make deps compile #+end_src Second, add these lines to the Emacs init file: #+begin_src elisp - (add-to-list 'load-path "~/git/swiper/") - (require 'ivy) + (add-to-list 'load-path "~/git/swiper") + (require 'ivy) #+end_src Then, update the code with: diff --git a/doc/ivy.texi b/doc/ivy.texi index 90c5fa2e16..cb2a609127 100644 --- a/doc/ivy.texi +++ b/doc/ivy.texi @@ -20,7 +20,7 @@ final candidate is either through simple keyboard character inputs or through powerful regular expressions. @end ifnottex -Copyright (C) 2015--2024 Free Software Foundation, Inc. +Copyright (C) 2015--2025 Free Software Foundation, Inc. @quotation Permission is granted to copy, distribute and/or modify this document @@ -261,14 +261,14 @@ Contribute to Ivy's development; send patches; pull requests First clone the Swiper repository with: @example -cd ~/git && git clone https://github.com/abo-abo/swiper -cd swiper && make compile +cd ~/git && git clone 'https://github.com/abo-abo/swiper.git' +cd swiper && make deps compile @end example Second, add these lines to the Emacs init file: @lisp -(add-to-list 'load-path "~/git/swiper/") +(add-to-list 'load-path "~/git/swiper") (require 'ivy) @end lisp diff --git a/doc/scripts.el b/doc/scripts.el index 6e00b162ff..97b1827af6 100644 --- a/doc/scripts.el +++ b/doc/scripts.el @@ -1,4 +1,4 @@ -;; Copyright (C) 2020-2024 Free Software Foundation, Inc. +;; Copyright (C) 2020-2025 Free Software Foundation, Inc. (setq org-confirm-babel-evaluate nil) (defun org-to-texi (fname) (find-file fname) diff --git a/ivy-faces.el b/ivy-faces.el index c2195ed294..1a25cfc0a9 100644 --- a/ivy-faces.el +++ b/ivy-faces.el @@ -1,6 +1,6 @@ ;;; ivy-faces.el --- Faces for Ivy -*- lexical-binding: t -*- -;; Copyright (C) 2020-2024 Free Software Foundation, Inc. +;; Copyright (C) 2020-2025 Free Software Foundation, Inc. ;; Author: Oleh Krehel <ohwoeo...@gmail.com> ;; Keywords: convenience diff --git a/ivy-overlay.el b/ivy-overlay.el index ad359fc83d..f52b5ecbee 100644 --- a/ivy-overlay.el +++ b/ivy-overlay.el @@ -1,6 +1,6 @@ ;;; ivy-overlay.el --- Overlay display functions for Ivy -*- lexical-binding: t -*- -;; Copyright (C) 2016-2024 Free Software Foundation, Inc. +;; Copyright (C) 2016-2025 Free Software Foundation, Inc. ;; Author: Oleh Krehel <ohwoeo...@gmail.com> ;; Keywords: convenience diff --git a/ivy-test.el b/ivy-test.el index 87dda1854b..f850bbe736 100644 --- a/ivy-test.el +++ b/ivy-test.el @@ -1,6 +1,6 @@ ;;; ivy-test.el --- tests for ivy -*- lexical-binding: t -*- -;; Copyright (C) 2015-2024 Free Software Foundation, Inc. +;; Copyright (C) 2015-2025 Free Software Foundation, Inc. ;; Author: Oleh Krehel @@ -1007,20 +1007,102 @@ Since `execute-kbd-macro' doesn't pick up a let-bound `default-directory'.") (ivy-mode ivy-mode-reset-arg)))) (ert-deftest ivy-completion-common-length () - (should (= 2 - (ivy-completion-common-length - #("test/" - 0 2 (face completions-common-part) - 2 3 (face (completions-first-difference)))))) - (should (= 5 - (ivy-completion-common-length - #("Math/E" - 0 5 (face (completions-common-part)) - 5 6 (face (completions-first-difference)))))) - (should (= 3 - (ivy-completion-common-length - #("vec" - 0 3 (face (completions-common-part))))))) + (mapc (lambda (pair) + (dolist (str (cdr pair)) + (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)) + #("abc" + 0 1 (face (completions-first-difference)) + 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))) + #("abc" 0 2 (face (completions-first-difference))) + #("ab" 1 2 (font-lock-face (completions-first-difference))) + #("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))) + #("abc" + 1 2 (face (completions-first-difference)) + 2 3 (face default))) + (2 "ab" + #("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)))) + (3 "abc" + #("abc" 0 1 (face default)) + #("abc" 1 2 (face default)) + #("abc" 2 3 (face default)) + #("abc" 0 2 (face default)) + #("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))))))) (ert-deftest ivy--sort-function () "Test `ivy--sort-function' behavior." diff --git a/ivy.el b/ivy.el index 15bf9bbe55..66e71ae32c 100644 --- a/ivy.el +++ b/ivy.el @@ -1,6 +1,6 @@ ;;; ivy.el --- Incremental Vertical completYon -*- lexical-binding: t -*- -;; Copyright (C) 2015-2024 Free Software Foundation, Inc. +;; Copyright (C) 2015-2025 Free Software Foundation, Inc. ;; Author: Oleh Krehel <ohwoeo...@gmail.com> ;; Maintainer: Basil L. Contovounesios <ba...@contovou.net> @@ -2037,6 +2037,8 @@ Directories come first." (seq (cl-mapcan (lambda (f) (unless (member f '("./" "../")) + ;; FIXME: Use `substitute-in-file-name'? + ;; Re: #2012, #3060. (setq f (ivy--string-replace "$$" "$" f)) (list (if (and dirs-first (ivy--dirname-p f)) (propertize f 'ivy--dir (directory-file-name f)) @@ -2620,10 +2622,11 @@ The previous string is between `ivy-completion-beg' and `ivy-completion-end'." (ivy-state-collection ivy-last))) (minibuffer-completion-predicate (if (boundp 'ivy--minibuffer-pred) ivy--minibuffer-pred - (ivy-state-predicate ivy-last)))) - (completion--done str (cond ((eq ivy--minibuffer-try t) 'finished) - ((eq ivy-exit 'done) 'unknown) - ('exact)))) + (ivy-state-predicate ivy-last))) + (newstr (or (car-safe ivy--minibuffer-try) str))) + (completion--done newstr (cond ((eq ivy--minibuffer-try t) 'finished) + ((eq ivy-exit 'done) 'unknown) + ('exact)))) (setq ivy-completion-end (point)) (save-excursion (dolist (cursor fake-cursors) @@ -2636,24 +2639,46 @@ 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', but then the base-size +returned by that function should be preferred over +`ivy-completion-common-length'." + (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'. @@ -2661,43 +2686,57 @@ The function completes the text between START and END using COLLECTION. PREDICATE (a function called with no arguments) says when to exit. See `completion-in-region' for further information." (let* ((enable-recursive-minibuffers t) + (reg (- end start)) (str (buffer-substring-no-properties start end)) (completion-ignore-case (ivy--case-fold-p str)) (md (completion-metadata str collection predicate)) - (reg (- end start)) - (comps (completion-all-completions str collection predicate reg md)) (try (completion-try-completion str collection predicate reg md)) + (comps (completion-all-completions str collection predicate reg md)) + (last (last comps)) + (base-size (cdr last)) (ivy--minibuffer-table collection) (ivy--minibuffer-pred predicate)) - (cond ((null comps) - (message "No matches")) - ((progn - (nconc comps nil) - (and (null (cdr comps)) - (string= str (car comps)))) - (message "Sole match")) + (when last (setcdr last ())) + (cond ((not try) + (and (not completion-fail-discreetly) + completion-show-inline-help + (minibuffer-message "No matches")) + nil) + ((eq try t) + (goto-char end) + (let ((minibuffer-completion-table collection) + (minibuffer-completion-predicate predicate)) + (completion--done str 'finished "Sole match")) + t) (t (when (eq collection 'crm--collection-fn) (setq comps (delete-dups comps))) - (let* ((len (ivy-completion-common-length (car comps))) - (initial (cond ((= len 0) + (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 (base-size (substring str base-size)) + ;; The remaining clauses should hopefully never + ;; be taken, since they rely on + ;; `ivy-completion-common-length'. + ((= cmn 0) "") - ((let ((str-len (length str))) - (when (> len str-len) - (setq len str-len) - str))) + ((>= cmn reg) + (setq cmn reg) + str) (t - (substring str (- len)))))) - (delete-region (- end len) end) - (setq ivy-completion-beg (- end len)) + (substring str (- cmn))))) + (base-pos (if base-size (+ start base-size) (- end cmn)))) + (delete-region base-pos end) + (setq ivy-completion-beg base-pos) (setq ivy-completion-end ivy-completion-beg) (if (null (cdr comps)) - (progn + (let ((ivy--minibuffer-try try)) (unless (minibuffer-window-active-p (selected-window)) (setf (ivy-state-window ivy-last) (selected-window))) - (let ((ivy--minibuffer-try try)) - (ivy-completion-in-region-action - (substring-no-properties (car comps))))) + (ivy-completion-in-region-action + (substring-no-properties (car comps)))) (dolist (s comps) ;; Remove face `completions-first-difference'. (ivy--remove-props s 'face)) @@ -2711,6 +2750,9 @@ See `completion-in-region' for further information." (setq predicate nil)) (ivy-read (format "(%s): " str) collection :predicate predicate + ;; FIXME: The anchor is intrusive and not easily + ;; configurable by `ivy-initial-inputs-alist' or + ;; `ivy-hooks-alist'. :initial-input (concat (and (derived-mode-p #'emacs-lisp-mode) "^") @@ -2722,7 +2764,7 @@ See `completion-in-region' for further information." (when initial (insert initial)))) :caller 'ivy-completion-in-region))) - ;; Return value should be non-nil on valid completion; + ;; Return value should be t on valid completion; ;; see `completion-in-region'. t)))) @@ -3187,23 +3229,17 @@ parts beyond their respective faces `ivy-confirm-face' and (std-props '(front-sticky t rear-nonsticky t field t read-only t)) (n-str (concat - (if (and (bound-and-true-p minibuffer-depth-indicate-mode) - (> (minibuffer-depth) 1)) - (format "[%d] " (minibuffer-depth)) - "") - (concat - (if (string-match "%d.*%d" ivy-count-format) - (format head - (1+ ivy--index) - (or (and (ivy-state-dynamic-collection ivy-last) + (and (bound-and-true-p minibuffer-depth-indicate-mode) + (> (minibuffer-depth) 1) + (format "[%d] " (minibuffer-depth))) + (let ((count (or (and (ivy-state-dynamic-collection ivy-last) ivy--full-length) - ivy--length)) - (format head - (or (and (ivy-state-dynamic-collection ivy-last) - ivy--full-length) - ivy--length))) - ivy--prompt-extra - tail))) + ivy--length))) + (if (string-match-p "%d.*%d" ivy-count-format) + (format head (min (1+ ivy--index) count) count) + (format head count))) + ivy--prompt-extra + tail)) (d-str (if ivy--directory (abbreviate-file-name ivy--directory) "")))