branch: externals/counsel
commit 82585a61ee5d610afc86a801a83526b6584dfcd6
Merge: 4b275b4bdf 847ba97f6b
Author: Basil L. Contovounesios <ba...@contovou.net>
Commit: Basil L. Contovounesios <ba...@contovou.net>

    Merge branch 'master' into externals/counsel
---
 .dir-locals.el  |  13 +-
 counsel.el      | 863 +++++++++++++++++++++++++++++++++++---------------------
 targets/elpa.el | 125 ++++++++
 3 files changed, 678 insertions(+), 323 deletions(-)

diff --git a/.dir-locals.el b/.dir-locals.el
index f18455c3a6..9920229883 100644
--- a/.dir-locals.el
+++ b/.dir-locals.el
@@ -2,17 +2,14 @@
 ;;; For more information see (info "(emacs) Directory Variables")
 
 ((nil
-  ;; Emacs 28+ automatically sets up these `bug-reference-mode' variables
-  ;; in a more general way, so setting them here is not future-proof.  If
-  ;; you still need these settings in older Emacs versions, you can add
-  ;; them to your personal `.dir-locals-2.el' file in the meantime.
-  ;; (bug-reference-bug-regexp . "\\(#\\([[:digit:]]+\\)\\)")
-  ;; (bug-reference-url-format . "https://github.com/abo-abo/swiper/issues/%s";)
   (copyright-names-regexp . "Free Software Foundation, Inc\\.")
   (sentence-end-double-space . t))
  (emacs-lisp-mode
   (indent-tabs-mode . nil)
-  (outline-regexp . ";;\\([;*]+ [^\s\t\n]\\|###autoload\\)\\|(")
   ;; extra config here: 
https://github.com/abo-abo/oremacs/blob/github/modes/ora-elisp-style-guide.el
   ;; (lisp-indent-function . common-lisp-indent-function)
-  ))
+  )
+ (markdown-mode
+  (fill-column . 70))
+ (org-mode
+  (fill-column . 70)))
diff --git a/counsel.el b/counsel.el
index 55aee57980..5c5a8d1022 100644
--- a/counsel.el
+++ b/counsel.el
@@ -44,18 +44,26 @@
 (require 'ivy)
 (require 'swiper)
 
-(require 'compile)
-(require 'dired)
-
 (eval-when-compile
   (require 'subr-x))
 
+(eval-when-compile
+  (unless (fboundp 'static-if)
+    (defmacro static-if (condition then-form &rest else-forms)
+      "Expand to THEN-FORM or ELSE-FORMS based on compile-time CONDITION.
+Polyfill for Emacs 30 `static-if'."
+      (declare (debug (sexp sexp &rest sexp)) (indent 2))
+      (if (eval condition lexical-binding)
+          then-form
+        (macroexp-progn else-forms)))))
+
 (defgroup counsel nil
   "Completion functions using Ivy."
   :group 'matching
   :prefix "counsel-")
+
+;;; Utility
 
-;;* Utility
 (defun counsel--elisp-to-pcre (regex &optional look-around)
   "Convert REGEX from Elisp format to PCRE format, on best-effort basis.
 REGEX may be of any format returned by an Ivy regex function,
@@ -139,13 +147,16 @@ When NOERROR is non-nil, return nil instead of raising an 
error."
           (unless noerror
             (user-error "Required program \"%s\" not found in your path" 
program))))))
 
-(declare-function eshell-split-path "esh-util")
-
 (defun counsel-prompt-function-dir ()
   "Return prompt appended with the parent directory."
+  (declare (obsolete "it is no longer used." "0.16.0"))
   (require 'esh-util)
   (let* ((dir (ivy-state-directory ivy-last))
-         (parts (nthcdr 3 (eshell-split-path dir)))
+         (parts (nthcdr 3 (funcall (if (fboundp 'eshell-split-filename)
+                                       ;; New name since Emacs 30.
+                                       #'eshell-split-filename
+                                     'eshell-split-path)
+                                   dir)))
          (dir (format " [%s]: " (if parts (apply #'concat "..." parts) dir))))
     (ivy-add-prompt-count
      (replace-regexp-in-string          ; Insert dir before any trailing colon.
@@ -182,8 +193,9 @@ Return a list or string depending on input."
 (defalias 'counsel--null-device
   (if (fboundp 'null-device) #'null-device (lambda () null-device))
   "Compatibility shim for Emacs 28 function `null-device'.")
+
+;;;; Async utility
 
-;;* Async Utility
 (defvar counsel--async-time nil
   "Store the time when a new process was started.
 Or the time of the last minibuffer update.")
@@ -357,8 +369,9 @@ Update the minibuffer with the amount of lines collected 
every
   (let ((process (get-process (or name " *counsel*"))))
     (when process
       (delete-process process))))
+
+;;; Completion at point
 
-;;* Completion at point
 (define-obsolete-function-alias 'counsel-el
   #'complete-symbol "0.13.2 (2020-05-20)")
 (define-obsolete-function-alias 'counsel-cl
@@ -368,7 +381,8 @@ Update the minibuffer with the amount of lines collected 
every
 (define-obsolete-function-alias 'counsel-clj
   #'complete-symbol "0.13.2 (2020-05-20)")
 
-;;** `counsel-company'
+;;;; `counsel-company'
+
 (defvar company-candidates)
 (declare-function company-abort "ext:company")
 (declare-function company-complete "ext:company")
@@ -399,7 +413,8 @@ Update the minibuffer with the amount of lines collected 
every
               (when annot
                 (company--clean-string annot)))))
 
-;;** `counsel-irony'
+;;;; `counsel-irony'
+
 (declare-function irony-completion-candidates-async "ext:irony-completion")
 (declare-function irony-completion-symbol-bounds "ext:irony-completion")
 (declare-function irony-completion-annotation "ext:irony-completion")
@@ -432,9 +447,10 @@ Update the minibuffer with the amount of lines collected 
every
 
 (ivy-configure #'counsel-irony
   :display-fn #'ivy-display-function-overlay)
+
+;;; Elisp symbols
+;;;; `counsel-describe-variable'
 
-;;* Elisp symbols
-;;** `counsel-describe-variable'
 (defvar counsel-describe-map
   (let ((map (make-sparse-keymap)))
     (define-key map (kbd "C-.") #'counsel-find-symbol)
@@ -443,8 +459,8 @@ Update the minibuffer with the amount of lines collected 
every
 
 (ivy-set-actions
  'counsel-describe-variable
- '(("I" counsel-info-lookup-symbol "info")
-   ("d" counsel--find-symbol "definition")))
+ `(("I" ,#'counsel-info-lookup-symbol "info")
+   ("d" ,#'counsel--find-symbol "definition")))
 
 (defvar counsel-describe-symbol-history ()
   "History list for variable and function names.
@@ -455,24 +471,28 @@ Used by commands `counsel-describe-symbol',
   "Jump to the definition of the current symbol."
   (interactive)
   (ivy-exit-with-action #'counsel--find-symbol))
-(put 'counsel-find-symbol 'no-counsel-M-x t)
+(function-put #'counsel-find-symbol 'no-counsel-M-x t)
 
 (defun counsel--info-lookup-symbol ()
   "Lookup the current symbol in the info docs."
   (interactive)
   (ivy-exit-with-action #'counsel-info-lookup-symbol))
 
-(defvar find-tag-marker-ring)
-(declare-function xref-push-marker-stack "xref")
-
-(defalias 'counsel--push-xref-marker
-  ;; Added in Emacs 25.1.
-  (if (require 'xref nil t)
-      #'xref-push-marker-stack
-    (require 'etags)
-    (lambda (&optional m)
-      (ring-insert (with-no-warnings find-tag-marker-ring) (or m 
(point-marker)))))
-  "Compatibility shim for `xref-push-marker-stack'.")
+(defun counsel--push-xref-marker (&optional m)
+  "Compatibility shim for `xref-push-marker-stack'."
+  (static-if (require 'xref nil t)
+      ;; Added in Emacs 25.1.
+      (progn
+        (unless (fboundp 'xref-push-marker-stack)
+          (require 'xref))
+        (xref-push-marker-stack m))
+    (unless (boundp 'find-tag-marker-ring)
+      (require 'etags))
+    (unless (fboundp 'ring-insert)
+      (require 'ring))
+    (defvar find-tag-marker-ring)
+    (declare-function ring-insert "ring" (ring item))
+    (ring-insert find-tag-marker-ring (or m (point-marker)))))
 
 (defun counsel--find-symbol (x)
   "Find symbol definition that corresponds to string X."
@@ -537,11 +557,12 @@ Variables declared using `defcustom' are highlighted 
according to
   :parent 'counsel-describe-symbol
   :display-transformer-fn #'counsel-describe-variable-transformer)
 
-;;** `counsel-describe-function'
+;;;; `counsel-describe-function'
+
 (ivy-set-actions
  'counsel-describe-function
- '(("I" counsel-info-lookup-symbol "info")
-   ("d" counsel--find-symbol "definition")))
+ `(("I" ,#'counsel-info-lookup-symbol "info")
+   ("d" ,#'counsel--find-symbol "definition")))
 
 (defcustom counsel-describe-function-function #'describe-function
   "Function to call to describe a function passed as parameter."
@@ -563,6 +584,10 @@ Variables declared using `defcustom' are highlighted 
according to
           (function-item ivy-thing-at-point)
           (function-item ivy-function-called-at-point)))
 
+(defun counsel--describe-function (candidate)
+  "Pass string CANDIDATE to `counsel-describe-function-function'."
+  (funcall counsel-describe-function-function (intern candidate)))
+
 ;;;###autoload
 (defun counsel-describe-function ()
   "Forward to `describe-function'.
@@ -579,16 +604,16 @@ to `ivy-highlight-face'."
               :history 'counsel-describe-symbol-history
               :keymap counsel-describe-map
               :preselect (funcall counsel-describe-function-preselect)
-              :action (lambda (x)
-                        (funcall counsel-describe-function-function (intern 
x)))
+              :action #'counsel--describe-function
               :caller 'counsel-describe-function)))
 
 (ivy-configure 'counsel-describe-function
   :parent 'counsel-describe-symbol
   :display-transformer-fn #'counsel-describe-function-transformer)
 
-;;** `counsel-describe-symbol'
-(defcustom counsel-describe-symbol-function #'describe-symbol
+;;;; `counsel-describe-symbol'
+
+(defcustom counsel-describe-symbol-function 'describe-symbol
   "Function to call to describe a symbol passed as parameter."
   :type 'function)
 
@@ -599,6 +624,7 @@ to `ivy-highlight-face'."
   (unless (functionp 'describe-symbol)
     (user-error "This command requires Emacs 25.1 or later"))
   (require 'help-mode)
+  (defvar describe-symbol-backends)
   (let ((enable-recursive-minibuffers t))
     (ivy-read "Describe symbol: " obarray
               :predicate (lambda (sym)
@@ -622,7 +648,8 @@ to `ivy-highlight-face'."
  `(("I" ,#'counsel-info-lookup-symbol "info")
    ("d" ,#'counsel--find-symbol "definition")))
 
-;;** `counsel-set-variable'
+;;;; `counsel-set-variable'
+
 (defvar counsel-set-variable-history nil
   "Store history for `counsel-set-variable'.")
 
@@ -746,7 +773,8 @@ With a prefix arg, restrict list to variables defined using
       (when doc
         (lv-delete-window)))))
 
-;;** `counsel-apropos'
+;;;; `counsel-apropos'
+
 ;;;###autoload
 (defun counsel-apropos ()
   "Show all matching symbols.
@@ -778,7 +806,8 @@ a symbol and how to search for them."
 (ivy-configure 'counsel-apropos
   :sort-fn #'ivy-string<)
 
-;;** `counsel-info-lookup-symbol'
+;;;; `counsel-info-lookup-symbol'
+
 (defvar info-lookup-mode)
 (declare-function info-lookup-guess-default "info-look")
 (declare-function info-lookup->completions "info-look")
@@ -813,7 +842,8 @@ With prefix arg MODE a query for the symbol help mode is 
offered."
 (ivy-configure 'counsel-info-lookup-symbol
   :sort-fn #'ivy-string<)
 
-;;** `counsel-M-x'
+;;;; `counsel-M-x'
+
 (defface counsel-key-binding
   '((t :inherit font-lock-keyword-face))
   "Face used by `counsel-M-x' for key bindings."
@@ -968,10 +998,11 @@ when available, in that order of precedence."
 
 (ivy-set-actions
  'counsel-M-x
- `(("d" counsel--find-symbol "definition")
-   ("h" ,(lambda (x) (funcall counsel-describe-function-function (intern x))) 
"help")))
+ `(("d" ,#'counsel--find-symbol "definition")
+   ("h" ,#'counsel--describe-function "help")))
+
+;;;; `counsel-command-history'
 
-;;** `counsel-command-history'
 (defun counsel-command-history-action-eval (cmd)
   "Eval the command CMD."
   (eval (read cmd) t))
@@ -994,7 +1025,8 @@ when available, in that order of precedence."
             :action #'counsel-command-history-action-eval
             :caller 'counsel-command-history))
 
-;;** `counsel-load-library'
+;;;; `counsel-load-library'
+
 (defun counsel-library-candidates ()
   "Return a list of completion candidates for `counsel-load-library'."
   (let ((suffix (concat (regexp-opt '(".el" ".el.gz") t) "\\'"))
@@ -1051,9 +1083,10 @@ The libraries are offered from `load-path'."
 
 (ivy-set-actions
  'counsel-load-library
- '(("d" counsel--find-symbol "definition")))
+ `(("d" ,#'counsel--find-symbol "definition")))
+
+;;;; `counsel-find-library'
 
-;;** `counsel-find-library'
 (declare-function find-library-name "find-func")
 (defun counsel-find-library-other-window (library)
   (let ((buf (find-file-noselect (find-library-name library))))
@@ -1081,7 +1114,8 @@ The libraries are offered from `load-path'."
               :keymap counsel-describe-map
               :caller 'counsel-find-library)))
 
-;;** `counsel-load-theme'
+;;;; `counsel-load-theme'
+
 (declare-function powerline-reset "ext:powerline")
 
 (defun counsel-load-theme-action (x)
@@ -1106,7 +1140,8 @@ Usable with `ivy-resume', `ivy-next-line-and-call' and
             :action #'counsel-load-theme-action
             :caller 'counsel-load-theme))
 
-;;** `counsel-descbinds'
+;;;; `counsel-descbinds'
+
 (ivy-set-actions
  'counsel-descbinds
  '(("d" counsel-descbinds-action-find "definition")
@@ -1191,7 +1226,8 @@ BUFFER defaults to the current one."
             :history 'counsel-descbinds-history
             :caller 'counsel-descbinds))
 
-;;** `counsel-describe-face'
+;;;; `counsel-describe-face'
+
 (defcustom counsel-describe-face-function #'describe-face
   "Function to call to describe a face or face name argument."
   :type 'function)
@@ -1239,7 +1275,8 @@ back to the face of the character after point, and 
finally the
  '(("c" counsel-customize-face "customize")
    ("C" counsel-customize-face-other-window "customize other window")))
 
-;;** `counsel-faces'
+;;;; `counsel-faces'
+
 (defvar counsel--faces-format "%-40s %s")
 
 (defun counsel--faces-format-function (names)
@@ -1281,8 +1318,79 @@ selected face."
    ("C" counsel-customize-face-other-window "customize other window")
    ("h" counsel-highlight-with-face "highlight")))
 
-;;* Git
-;;** `counsel-git'
+;;;; Modes
+
+(defvar counsel-minor-history nil
+  "History for `counsel-minor'.")
+
+(defun counsel--minor-candidates ()
+  "Return completion alist for `counsel-minor'.
+
+The alist element is cons of minor mode string with its lighter
+and minor mode symbol."
+  (cl-mapcan
+   (let ((suffix (propertize " \"%s\"" 'face 'font-lock-string-face)))
+     (lambda (mode)
+       (when (and (boundp mode) (commandp mode))
+         (let ((lighter (cdr (assq mode minor-mode-alist))))
+           (list (cons (concat
+                        (if (symbol-value mode) "-" "+")
+                        (symbol-name mode)
+                        (and lighter
+                             (format suffix
+                                     (format-mode-line (cons t lighter)))))
+                       mode))))))
+   minor-mode-list))
+
+;;;###autoload
+(defun counsel-minor ()
+  "Enable or disable minor mode.
+
+Disabled minor modes are prefixed with \"+\", and
+selecting one of these will enable it.
+Enabled minor modes are prefixed with \"-\", and
+selecting one of these will enable it.
+
+Additional actions:\\<ivy-minibuffer-map>
+
+  \\[ivy-dispatching-done] d: Go to minor mode definition
+  \\[ivy-dispatching-done] h: Describe minor mode"
+
+  (interactive)
+  (ivy-read "Minor modes (enable +mode or disable -mode): "
+            (counsel--minor-candidates)
+            :require-match t
+            :history 'counsel-minor-history
+            :action (lambda (x)
+                      (call-interactively (cdr x)))))
+
+(ivy-configure 'counsel-minor
+  :initial-input "^+"
+  :sort-fn #'ivy-string<)
+
+(ivy-set-actions
+ 'counsel-minor
+ `(("d" ,(lambda (x) (find-function (cdr x))) "definition")
+   ("h" ,(lambda (x) (describe-function (cdr x))) "help")))
+
+;;;###autoload
+(defun counsel-major ()
+  (interactive)
+  (ivy-read "Major modes: " obarray
+            :predicate (lambda (f)
+                         (and (commandp f)
+                              (string-suffix-p "-mode" (symbol-name f))
+                              (or (and (autoloadp (symbol-function f))
+                                       (let ((doc-split (help-split-fundoc 
(documentation f) f)))
+                                         ;; major mode starters have no 
arguments
+                                         (and doc-split (null (cdr (read (car 
doc-split)))))))
+                                  (null (help-function-arglist f)))))
+            :action #'counsel-M-x-action
+            :caller 'counsel-major))
+
+;;; Git
+;;;; `counsel-git'
+
 (defvar counsel-git-cmd "git ls-files -z --full-name --"
   "Command for `counsel-git'.")
 
@@ -1353,6 +1461,10 @@ INITIAL-INPUT can be given as the initial minibuffer 
input."
   (let ((inhibit-read-only t))
     (erase-buffer)
     (dired-mode default-directory counsel-dired-listing-switches)
+    (defvar dired-sort-inhibit)
+    (defvar dired-subdir-alist)
+    (declare-function dired-insert-set-properties "dired")
+    (declare-function dired-move-to-filename "dired")
     (insert "  " default-directory ":\n")
     (let ((point (point)))
       (insert "  " full-cmd "\n")
@@ -1374,7 +1486,8 @@ INITIAL-INPUT can be given as the initial minibuffer 
input."
            (forward-line 2)
            (dired-move-to-filename)))))))
 
-;;** `counsel-git-grep'
+;;;; `counsel-git-grep'
+
 (defvar counsel-git-grep-map
   (let ((map (make-sparse-keymap)))
     (define-key map (kbd "C-l") #'ivy-call-and-recenter)
@@ -1696,7 +1809,8 @@ When CMD is non-nil, prompt for a specific \"git grep\" 
command."
                    (goto-char (point-min)))
                  (perform-replace from to t t nil))))))))))
 
-;;** `counsel-git-stash'
+;;;; `counsel-git-stash'
+
 (defun counsel-git-stash-kill-action (x)
   "Add git stash command to kill ring.
 The git command applies the stash entry where candidate X was found in."
@@ -1717,7 +1831,8 @@ done") "\n" t)))
               :action #'counsel-git-stash-kill-action
               :caller 'counsel-git-stash)))
 
-;;** `counsel-git-log'
+;;;; `counsel-git-log'
+
 (defvar counsel-git-log-cmd "GIT_PAGER=cat git log --no-color --grep '%s'"
   "Command used for \"git log\".")
 
@@ -1751,7 +1866,8 @@ done") "\n" t)))
  'counsel-git-log
  '(("v" counsel-git-log-show-commit-action "visit commit")))
 
-;;** `counsel-git-change-worktree'
+;;;; `counsel-git-change-worktree'
+
 (defun counsel-git-change-worktree-action (git-root-dir tree)
   "Find the corresponding file in the worktree located at tree.
 The current buffer is assumed to be in a subdirectory of GIT-ROOT-DIR.
@@ -1799,7 +1915,8 @@ TREE is the selected candidate."
               :require-match t
               :caller 'counsel-git-change-worktree)))
 
-;;** `counsel-git-checkout'
+;;;; `counsel-git-checkout'
+
 (defun counsel-git-checkout-action (branch)
   "Switch branch by invoking git-checkout(1).
 The command is passed a single argument comprising all characters
@@ -1852,16 +1969,17 @@ currently checked out."
 
 (add-to-list 'counsel-async-split-string-re-alist '(counsel-git-log . "^commit 
"))
 (add-to-list 'counsel-async-ignore-re-alist '(counsel-git-log . "^[ \n]*$"))
+
+;;; File
+;;;; `counsel-find-file'
 
-;;* File
-;;** `counsel-find-file'
 (defvar counsel-find-file-map
   (let ((map (make-sparse-keymap)))
     (define-key map (kbd "C-DEL") #'counsel-up-directory)
     (define-key map (kbd "C-<backspace>") #'counsel-up-directory)
     (define-key map (kbd "`") #'counsel-file-jump-from-find)
     (define-key map (kbd "C-`") (ivy-make-magic-action #'counsel-find-file 
"b"))
-    (define-key map [remap undo] #'counsel-find-file-undo)
+    (define-key map `[remap ,#'undo] #'counsel-find-file-undo)
     map))
 
 (defun counsel-file-jump-from-find ()
@@ -1922,7 +2040,8 @@ choose between `yes-or-no-p' and `y-or-n-p'; otherwise 
default to
 
 (defun counsel-find-file-copy (x)
   "Copy file X."
-  (require 'dired-aux)
+  ;; Autoloaded by `dired'.
+  (declare-function dired-copy-file "dired-aux")
   (counsel--find-file-1 "Copy file to: "
                         ivy--directory
                         (lambda (new-name)
@@ -1931,6 +2050,9 @@ choose between `yes-or-no-p' and `y-or-n-p'; otherwise 
default to
 
 (defun counsel-find-file-delete (x)
   "Delete file X."
+  (defvar dired-recursive-deletes)
+  (declare-function dired-clean-up-after-deletion "dired")
+  (declare-function dired-delete-file "dired")
   (when (or delete-by-moving-to-trash
             ;; `dired-delete-file', which see, already prompts for directories
             (eq t (car (file-attributes x)))
@@ -1943,7 +2065,8 @@ choose between `yes-or-no-p' and `y-or-n-p'; otherwise 
default to
 
 (defun counsel-find-file-move (x)
   "Move or rename file X."
-  (require 'dired-aux)
+  ;; Autoloaded by `dired'.
+  (declare-function dired-rename-file "dired-aux")
   (counsel--find-file-1 "Rename file to: "
                         ivy--directory
                         (lambda (new-name)
@@ -2063,8 +2186,9 @@ The preselect behavior can be customized via user options
         (file-name-nondirectory buffer-file-name))))
 
 (defun counsel--find-file-1 (prompt initial-input action caller)
+  (declare-function dired-current-directory "dired")
   (let ((default-directory
-         (if (eq major-mode 'dired-mode)
+         (if (derived-mode-p 'dired-mode)
              (dired-current-directory)
            default-directory)))
     (ivy-read prompt #'read-file-name-internal
@@ -2082,6 +2206,7 @@ The preselect behavior can be customized via user options
   "Forward to `find-file'.
 When INITIAL-INPUT is non-nil, use it in the minibuffer during completion."
   (interactive)
+  (require 'dired)
   (defvar tramp-archive-enabled)
   (let ((tramp-archive-enabled nil)
         (default-directory (or initial-directory default-directory)))
@@ -2311,14 +2436,14 @@ result as a URL."
                (format formatter word-at-point)))))
        counsel-url-expansions-alist))))
 
-;;** `counsel-dired'
-(declare-function dired "dired")
+;;;; `counsel-dired'
 
 ;;;###autoload
 (defun counsel-dired (&optional initial-input)
   "Forward to `dired'.
 When INITIAL-INPUT is non-nil, use it in the minibuffer during completion."
   (interactive)
+  (require 'dired)
   (let ((counsel--find-file-predicate #'file-directory-p))
     (counsel--find-file-1
      "Dired (directory): " initial-input
@@ -2328,7 +2453,8 @@ When INITIAL-INPUT is non-nil, use it in the minibuffer 
during completion."
 (ivy-configure 'counsel-dired
   :parent 'read-file-name-internal)
 
-;;** `counsel-recentf'
+;;;; `counsel-recentf'
+
 (defvar recentf-list)
 (declare-function recentf-mode "recentf")
 
@@ -2344,7 +2470,6 @@ 
https://www.freedesktop.org/wiki/Specifications/desktop-bookmark-spec";))
 (defun counsel-recentf ()
   "Find a file on `recentf-list'."
   (interactive)
-  (require 'recentf)
   (recentf-mode)
   (ivy-read "Recentf: " (counsel-recentf-candidates)
             :action (lambda (f)
@@ -2411,30 +2536,29 @@ For convenience, BEG and END default to `point-min' and
 This information is parsed from the file \"recently-used.xbel\",
 which lists both files and directories, under `xdg-data-home'.
 This function uses the `dom' library from Emacs 25.1 or later."
-  (unless (require 'dom nil t)
+  (unless (eval-and-compile (require 'dom nil t))
     (user-error "This function requires Emacs 25.1 or later"))
-  (declare-function dom-attr "dom" (node attr))
   (declare-function dom-by-tag "dom" (dom tag))
   (let ((file-of-recent-files
          (expand-file-name "recently-used.xbel" (counsel--xdg-data-home))))
     (unless (file-readable-p file-of-recent-files)
       (user-error "List of XDG recent files not found: %s"
                   file-of-recent-files))
-    (cl-mapcan (lambda (bookmark-node)
-                 (let* ((file (dom-attr bookmark-node 'href))
-                        (file (string-remove-prefix "file://" file))
-                        (file (url-unhex-string file t))
-                        (file (decode-coding-string file 'utf-8 t)))
-                   (and (file-exists-p file)
-                        (list file))))
-               (let ((dom (with-temp-buffer
-                            (insert-file-contents file-of-recent-files)
-                            (counsel--xml-parse-region))))
-                 (nreverse (dom-by-tag dom 'bookmark))))))
+    (when (fboundp 'dom-attr) ;; Pacify Emacs 24.
+      (cl-mapcan (lambda (bookmark-node)
+                   (let* ((file (dom-attr bookmark-node 'href))
+                          (file (string-remove-prefix "file://" file))
+                          (file (url-unhex-string file t))
+                          (file (decode-coding-string file 'utf-8 t)))
+                     (and (file-exists-p file)
+                          (list file))))
+                 (let ((dom (with-temp-buffer
+                              (insert-file-contents file-of-recent-files)
+                              (counsel--xml-parse-region))))
+                   (nreverse (dom-by-tag dom 'bookmark)))))))
 
 (defun counsel-buffer-or-recentf-candidates ()
   "Return candidates for `counsel-buffer-or-recentf'."
-  (require 'recentf)
   (recentf-mode)
   (let ((buffers (delq nil (mapcar #'buffer-file-name (buffer-list)))))
     (nconc
@@ -2470,7 +2594,8 @@ This function uses the `dom' library from Emacs 25.1 or 
later."
       (ivy-append-face var 'ivy-highlight-face)
     var))
 
-;;** `counsel-bookmark'
+;;;; `counsel-bookmark'
+
 (defcustom counsel-bookmark-avoid-dired nil
   "If non-nil, open directory bookmarks with `counsel-find-file'.
 By default `counsel-bookmark' opens a dired buffer for directories."
@@ -2520,7 +2645,8 @@ By default `counsel-bookmark' opens a dired buffer for 
directories."
    ("r" ,(counsel--apply-bookmark-fn #'counsel-find-file-as-root)
         "open as root")))
 
-;;** `counsel-bookmarked-directory'
+;;;; `counsel-bookmarked-directory'
+
 (defun counsel-bookmarked-directory--candidates ()
   "Get a list of bookmarked directories sorted by file path."
   (bookmark-maybe-load-default-file)
@@ -2543,15 +2669,16 @@ current value of `default-directory'."
             :action #'dired))
 
 (ivy-set-actions 'counsel-bookmarked-directory
-                 `(("j" dired-other-window "other window")
-                   ("x" counsel-find-file-extern "open externally")
-                   ("r" counsel-find-file-as-root "open as root")
+                 `(("j" ,#'dired-other-window "other window")
+                   ("x" ,#'counsel-find-file-extern "open externally")
+                   ("r" ,#'counsel-find-file-as-root "open as root")
                    ("f" ,(lambda (dir)
                            (let ((default-directory dir))
                              (call-interactively #'find-file)))
                     "find-file")))
 
-;;** `counsel-file-register'
+;;;; `counsel-file-register'
+
 ;;;###autoload
 (defun counsel-file-register ()
   "Search file in register.
@@ -2588,7 +2715,8 @@ can use `C-x r j i' to open that file."
  'counsel-file-register
  '(("j" find-file-other-window "other window")))
 
-;;** `counsel-locate'
+;;;; `counsel-locate'
+
 (defcustom counsel-locate-cmd (cond ((memq system-type '(darwin berkeley-unix))
                                      #'counsel-locate-cmd-noregex)
                                     ((and (eq system-type 'windows-nt)
@@ -2633,7 +2761,10 @@ string - the full shell command to run."
 
 (defalias 'counsel-find-file-extern #'counsel-locate-action-extern)
 
-(declare-function dired-jump "dired-x")
+(eval-and-compile
+  ;; Autoloaded by `dired' since Emacs 28.
+  (unless (fboundp 'dired-jump)
+    (autoload 'dired-jump "dired-x" nil t)))
 
 (defun counsel-locate-action-dired (x)
   "Use `dired-jump' on X."
@@ -2735,7 +2866,8 @@ INITIAL-INPUT can be given as the initial minibuffer 
input."
   :unwind-fn #'counsel-delete-process
   :exit-codes '(1 "Nothing found"))
 
-;;** `counsel-tracker'
+;;;; `counsel-tracker'
+
 (defun counsel-tracker-function (input)
   "Call the \"tracker\" shell command with INPUT."
   (or
@@ -2764,7 +2896,8 @@ INITIAL-INPUT can be given as the initial minibuffer 
input."
   :display-transformer-fn #'counsel-tracker-transformer
   :unwind-fn #'counsel-delete-process)
 
-;;** `counsel-fzf'
+;;;; `counsel-fzf'
+
 (defvar counsel-fzf-cmd "fzf -f \"%s\""
   "Command for `counsel-fzf'.")
 
@@ -2839,7 +2972,8 @@ FZF-PROMPT, if non-nil, is passed as `ivy-read' prompt 
argument."
  '(("x" counsel-locate-action-extern "xdg-open")
    ("d" counsel-locate-action-dired "dired")))
 
-;;** `counsel-dpkg'
+;;;; `counsel-dpkg'
+
 ;;;###autoload
 (defun counsel-dpkg ()
   "Call the \"dpkg\" shell command."
@@ -2860,7 +2994,8 @@ FZF-PROMPT, if non-nil, is passed as `ivy-read' prompt 
argument."
                         (message (cdr x)))
               :caller 'counsel-dpkg)))
 
-;;** `counsel-rpm'
+;;;; `counsel-rpm'
+
 ;;;###autoload
 (defun counsel-rpm ()
   "Call the \"rpm\" shell command."
@@ -2901,7 +3036,8 @@ FZF-PROMPT, if non-nil, is passed as `ivy-read' prompt 
argument."
   "Arguments for the `find-command' when using `counsel-file-jump'."
   :type '(repeat string))
 
-;;** `counsel-file-jump'
+;;;; `counsel-file-jump'
+
 (defvar counsel-file-jump-map
   (let ((map (make-sparse-keymap)))
     (define-key map (kbd "`") #'counsel-find-file-from-jump)
@@ -2943,11 +3079,12 @@ INITIAL-DIRECTORY, if non-nil, is used as the root 
directory for search."
            (dired (or (file-name-directory x) default-directory)))
     "open in dired")))
 
+;;;; `counsel-dired-jump'
+
 (defcustom counsel-dired-jump-args (split-string ". -name .git -prune -o -type 
d -print")
   "Arguments for the `find-command' when using `counsel-dired-jump'."
   :type '(repeat string))
 
-;;** `counsel-dired-jump'
 ;;;###autoload
 (defun counsel-dired-jump (&optional initial-input initial-directory)
   "Jump to a directory (see `dired-jump') below the current directory.
@@ -2969,9 +3106,10 @@ INITIAL-DIRECTORY, if non-nil, is used as the root 
directory for search."
               :history 'file-name-history
               :keymap counsel-find-file-map
               :caller 'counsel-dired-jump)))
+
+;;; Grep
+;;;; `counsel-ag'
 
-;;* Grep
-;;** `counsel-ag'
 (defvar counsel-ag-map
   (let ((map (make-sparse-keymap)))
     (define-key map (kbd "C-l") #'ivy-call-and-recenter)
@@ -3201,7 +3339,8 @@ Works for `counsel-git-grep', `counsel-ag', etc."
   (counsel-grep-like-occur
    counsel-ag-command))
 
-;;** `counsel-pt'
+;;;; `counsel-pt'
+
 (defcustom counsel-pt-base-command "pt --nocolor --nogroup -e %s"
   "Alternative to `counsel-ag-base-command' using pt."
   :type 'string)
@@ -3222,7 +3361,8 @@ This uses `counsel-ag' with `counsel-pt-base-command' 
instead of
   :display-transformer-fn #'counsel-git-grep-transformer
   :grep-p t)
 
-;;** `counsel-ack'
+;;;; `counsel-ack'
+
 (defcustom counsel-ack-base-command
   (concat
    (file-name-nondirectory
@@ -3244,7 +3384,8 @@ This uses `counsel-ag' with `counsel-ack-base-command' 
replacing
      initial-input nil nil nil
      :caller 'counsel-ack)))
 
-;;** `counsel-rg'
+;;;; `counsel-rg'
+
 (defcustom counsel-rg-base-command
   `("rg"
     "--max-columns" "240"
@@ -3264,7 +3405,9 @@ Note: don't use single quotes for the regexp."
 
 (defun counsel--rg-targets ()
   "Return a list of files to operate on, based on `dired-mode' marks."
-  (when (eq major-mode 'dired-mode)
+  (when (derived-mode-p 'dired-mode)
+    (declare-function dired-get-marked-files "dired")
+    (declare-function dired-toggle-marks "dired")
     (let ((files
            (dired-get-marked-files 'no-dir nil nil t)))
       (when (or (cdr files)
@@ -3307,7 +3450,8 @@ Example input with inclusion and exclusion file patterns:
   :grep-p t
   :exit-codes '(1 "No matches found"))
 
-;;** `counsel-grep'
+;;;; `counsel-grep'
+
 (defvar counsel-grep-map
   (let ((map (make-sparse-keymap)))
     (define-key map (kbd "C-l") #'ivy-call-and-recenter)
@@ -3451,7 +3595,8 @@ the initial search pattern."
          '((counsel-grep . ivy-recompute-index-swiper-async-backward))))
     (counsel-grep initial-input)))
 
-;;** `counsel-grep-or-swiper'
+;;;; `counsel-grep-or-swiper'
+
 (defcustom counsel-grep-swiper-limit 300000
   "Buffer size threshold for `counsel-grep-or-swiper'.
 When the number of characters in a buffer exceeds this threshold,
@@ -3487,7 +3632,8 @@ When non-nil, INITIAL-INPUT is the initial search 
pattern."
       (save-buffer))
     (counsel-grep initial-input)))
 
-;;** `counsel-grep-or-swiper-backward'
+;;;; `counsel-grep-or-swiper-backward'
+
 ;;;###autoload
 (defun counsel-grep-or-swiper-backward (&optional initial-input)
   "Call `swiper-backward' for small buffers and `counsel-grep-backward' for
@@ -3498,7 +3644,8 @@ large ones.  When non-nil, INITIAL-INPUT is the initial 
search pattern."
            (counsel-grep . ivy-recompute-index-swiper-async-backward))))
     (counsel-grep-or-swiper initial-input)))
 
-;;** `counsel-recoll'
+;;;; `counsel-recoll'
+
 (defun counsel-recoll-function (str)
   "Run recoll for STR."
   (or
@@ -3539,9 +3686,10 @@ INITIAL-INPUT can be given as the initial minibuffer 
input."
 
 (ivy-configure 'counsel-recoll
   :unwind-fn #'counsel-delete-process)
+
+;;; Org
+;;;; `counsel-org-tag'
 
-;;* Org
-;;** `counsel-org-tag'
 (defvar counsel-org-tags nil
   "Store the current list of tags.")
 
@@ -3549,13 +3697,11 @@ INITIAL-INPUT can be given as the initial minibuffer 
input."
 (defvar org-indent-mode)
 (defvar org-indent-indentation-per-level)
 (defvar org-tags-column)
-(declare-function org-get-tags-string "org")
 (declare-function org-get-tags "org")
-(declare-function org-make-tag-string "org")
 (declare-function org-move-to-column "org-compat")
 
 (defun counsel--org-make-tag-string ()
-  (if (fboundp #'org-make-tag-string)
+  (if (fboundp 'org-make-tag-string)
       ;; >= Org 9.2
       (org-make-tag-string (counsel--org-get-tags))
     (with-no-warnings
@@ -3606,7 +3752,10 @@ INITIAL-INPUT can be given as the initial minibuffer 
input."
 
 (defvar org-agenda-bulk-marked-entries)
 
-(declare-function org-get-at-bol "org")
+;; Moved from `org' to `org-macs' in Emacs 27.
+(declare-function org-get-at-bol "org-macs")
+(declare-function org-trim "org-macs")
+
 (declare-function org-agenda-error "org-agenda")
 
 (defun counsel-org-tag-action (x)
@@ -3869,7 +4018,8 @@ version.  Argument values are based on the
                   (version< org-version "9.1.1"))
                 2 0)))
 
-;;** `counsel-org-file'
+;;;; `counsel-org-file'
+
 (declare-function org-attach-dir "org-attach")
 (declare-function org-attach-file-list "org-attach")
 (defvar org-attach-directory)
@@ -3903,40 +4053,48 @@ include attachments of other Org buffers."
             :action #'counsel-locate-action-dired
             :caller 'counsel-org-file))
 
-;;** `counsel-org-entity'
-(defvar org-entities)
-(defvar org-entities-user)
+;;;; `counsel-org-entity'
 
 ;;;###autoload
 (defun counsel-org-entity ()
   "Complete Org entities using Ivy."
   (interactive)
   (require 'org)
-  (ivy-read "Entity: " (cl-loop for element in (append org-entities 
org-entities-user)
-                          unless (stringp element)
-                          collect (cons
-                                   (format "%20s | %20s | %20s | %s"
-                                           (cl-first element)    ; name
-                                           (cl-second element)   ; latex
-                                           (cl-fourth element)   ; html
-                                           (cl-seventh element)) ; utf-8
-                                   element))
+  (defvar org-entities)
+  (defvar org-entities-user)
+  (ivy-read "Entity: "
+            (cl-loop for element in (append org-entities org-entities-user)
+                     when (consp element)
+                     collect (cons
+                              (format "%20s | %20s | %20s | %s"
+                                      (nth 0 element)  ; Name.
+                                      (nth 1 element)  ; LaTeX.
+                                      (nth 3 element)  ; HTML.
+                                      (nth 6 element)) ; UTF-8.
+                              element))
             :require-match t
-            :action '(1
-                      ("u" (lambda (candidate)
-                             (insert (cl-seventh (cdr candidate)))) "utf-8")
-                      ("o" (lambda (candidate)
-                             (insert "\\" (cl-first (cdr candidate)))) 
"org-entity")
-                      ("l" (lambda (candidate)
-                             (insert (cl-second (cdr candidate)))) "latex")
-                      ("h" (lambda (candidate)
-                             (insert (cl-fourth (cdr candidate)))) "html")
-                      ("a" (lambda (candidate)
-                             (insert (cl-fifth (cdr candidate)))) "ascii")
-                      ("L" (lambda (candidate)
-                             (insert (cl-sixth (cdr candidate))) "Latin-1")))))
-
-;;** `counsel-org-capture'
+            :action `(1
+                      ("u" ,(lambda (candidate)
+                              (insert (nth 6 (cdr candidate))))
+                       "UTF-8")
+                      ("o" ,(lambda (candidate)
+                              (insert "\\" (nth 0 (cdr candidate))))
+                       "Org entity")
+                      ("l" ,(lambda (candidate)
+                              (insert (nth 1 (cdr candidate))))
+                       "LaTeX")
+                      ("h" ,(lambda (candidate)
+                              (insert (nth 3 (cdr candidate))))
+                       "HTML")
+                      ("a" ,(lambda (candidate)
+                              (insert (nth 4 (cdr candidate))))
+                       "ASCII")
+                      ("L" ,(lambda (candidate)
+                              (insert (nth 5 (cdr candidate))))
+                       "Latin-1"))))
+
+;;;; `counsel-org-capture'
+
 (defvar org-capture-templates)
 (defvar org-capture-templates-contexts)
 (declare-function org-contextualize-keys "org")
@@ -3995,19 +4153,26 @@ include attachments of other Org buffers."
            (customize-variable 'org-capture-templates))
         "customize org-capture-templates")))
 
-;;** `counsel-org-agenda-headlines'
+;;;; `counsel-org-agenda-headlines'
+
 (defvar org-odd-levels-only)
-(declare-function org-set-startup-visibility "org")
-(declare-function org-show-entry "org")
 (declare-function org-map-entries "org")
 (declare-function org-heading-components "org")
 
 (defun counsel-org-agenda-headlines-action-goto (headline)
   "Go to the `org-mode' agenda HEADLINE."
   (find-file (nth 1 headline))
-  (org-set-startup-visibility)
+  (if (fboundp 'org-cycle-set-startup-visibility)
+      (org-cycle-set-startup-visibility)
+    ;; Obsolete alias since Org 9.6 / Emacs 29.
+    (with-no-warnings
+      (org-set-startup-visibility)))
   (goto-char (nth 2 headline))
-  (org-show-entry))
+  (if (fboundp 'org-fold-show-entry)
+      (org-fold-show-entry)
+    ;; Obsolete alias since Org 9.6 / Emacs 29.
+    (with-no-warnings
+      (org-show-entry))))
 
 (ivy-set-actions
  'counsel-org-agenda-headlines
@@ -4090,7 +4255,9 @@ This variable has no effect unless
               :history 'counsel-org-agenda-headlines-history
               :caller 'counsel-org-agenda-headlines)))
 
-;;** `counsel-org-link'
+;;;; `counsel-org-link'
+
+;; Moved from `org' to `ol' in Emacs 27.
 (declare-function org-insert-link "ol")
 (declare-function org-id-get-create "org-id")
 
@@ -4110,9 +4277,10 @@ This variable has no effect unless
             :action #'counsel-org-link-action
             :history 'counsel-org-link-history
             :caller 'counsel-org-link))
+
+;;; Misc. Emacs
+;;;; `counsel-mark-ring'
 
-;; Misc. Emacs
-;;** `counsel-mark-ring'
 (defface counsel--mark-ring-highlight
   '((t :inherit highlight))
   "Face for current `counsel-mark-ring' line."
@@ -4216,7 +4384,8 @@ Position of selected mark outside accessible part of 
buffer")))
   :unwind-fn #'counsel--mark-ring-unwind
   :sort-fn #'ivy-string<)
 
-;;** `counsel-evil-marks'
+;;;; `counsel-evil-marks'
+
 (defvar counsel-evil-marks-exclude-registers nil
   "List of evil registers to not display in `counsel-evil-marks' by default.
 Each member of the list should be a character (stored as an integer).")
@@ -4281,7 +4450,8 @@ When ARG is non-nil, display all active evil registers."
           (message "No evil marks are active")))
     (user-error "Required feature `evil' not installed or loaded")))
 
-;;** `counsel-package'
+;;;; `counsel-package'
+
 (defvar package--initialized)
 (defvar package-alist)
 (defvar package-archive-contents)
@@ -4289,7 +4459,7 @@ When ARG is non-nil, display all active evil registers."
 (defvar package-user-dir)
 (declare-function package-installed-p "package")
 (declare-function package-delete "package")
-(declare-function package-desc-extras "package")
+(declare-function package-desc-extras "package" t t)
 
 (defvar counsel-package-history nil
   "History for `counsel-package'.")
@@ -4373,16 +4543,16 @@ Additional actions:\\<ivy-minibuffer-map>
  '(("d" counsel-package-action-describe "describe package")
    ("h" counsel-package-action-homepage "open package homepage")))
 
-;;** `counsel-tmm'
+;;;; `counsel-tmm'
+
 (declare-function tmm-get-keymap "tmm" (elt &optional in-x-menu))
-(declare-function tmm--completion-table "tmm" (items))
 
 (defalias 'counsel--menu-keymap
   ;; Added in Emacs 28.1.
   (if (fboundp 'menu-bar-keymap)
       #'menu-bar-keymap
-    (autoload 'tmm-get-keybind "tmm")
-    (declare-function tmm-get-keybind "tmm" (keyseq))
+    ;; Removed in Emacs 28.1.
+    (declare-function tmm-get-keybind "tmm" (keyseq) t)
     (lambda () (tmm-get-keybind [menu-bar])))
   "Compatibility shim for `menu-bar-keymap'.")
 
@@ -4394,9 +4564,14 @@ Additional actions:\\<ivy-minibuffer-map>
         chosen-string)
     (setq tmm-km-list nil)
     (map-keymap (lambda (k v) (tmm-get-keymap (cons k v))) menu)
-    (setq tmm-km-list (nreverse tmm-km-list))
-    (setq out (ivy-read "Menu bar: " (tmm--completion-table tmm-km-list)
-                        :require-match t))
+    (let ((items (setq tmm-km-list (nreverse tmm-km-list))))
+      (setq out (ivy-read "Menu bar: "
+                          ;; From `tmm--completion-table', removed in Emacs 31.
+                          (lambda (str pred action)
+                            (if (eq action 'metadata)
+                                '(metadata (display-sort-function . identity))
+                              (complete-with-action action items str pred)))
+                          :require-match t)))
     (setq choice (cdr (assoc out tmm-km-list)))
     (setq chosen-string (car choice))
     (setq choice (cdr choice))
@@ -4416,7 +4591,8 @@ Additional actions:\\<ivy-minibuffer-map>
   (setq tmm-table-undef nil)
   (counsel-tmm-prompt (counsel--menu-keymap)))
 
-;;** `counsel-yank-pop'
+;;;; `counsel-yank-pop'
+
 (defcustom counsel-yank-pop-truncate-radius 2
   "Number of context lines around `counsel-yank-pop' candidates."
   :type 'integer)
@@ -4666,7 +4842,7 @@ Note: Duplicate elements of `kill-ring' are always 
deleted."
               :action #'counsel-yank-pop-action
               :caller 'counsel-yank-pop)))
 
-(put #'counsel-yank-pop 'delete-selection 'yank)
+(function-put #'counsel-yank-pop 'delete-selection 'yank)
 
 (ivy-configure 'counsel-yank-pop
   :height 5
@@ -4677,7 +4853,8 @@ Note: Duplicate elements of `kill-ring' are always 
deleted."
  '(("d" counsel-yank-pop-action-remove "delete")
    ("r" counsel-yank-pop-action-rotate "rotate")))
 
-;;** `counsel-register'
+;;;; `counsel-register'
+
 (defvar counsel-register-actions
   '(("\\`buffer" . jump-to-register)
     ("\\`text" . insert-register)
@@ -4731,7 +4908,8 @@ matching the register's value description against a 
regexp in
 (ivy-configure 'counsel-register
   :sort-fn #'ivy-string<)
 
-;;** `counsel-evil-registers'
+;;;; `counsel-evil-registers'
+
 (defface counsel-evil-register-face
   '((t :inherit counsel-outline-1))
   "Face for highlighting `evil' registers in ivy."
@@ -4765,14 +4943,15 @@ S will be of the form \"[register]: content\"."
     (insert
      (replace-regexp-in-string "\\`\\[.*?]: " "" s t t))))
 
-;;** `counsel-imenu'
-(defvar imenu-auto-rescan)
-(defvar imenu-auto-rescan-maxout)
+;;;; `counsel-imenu'
+
 (declare-function imenu--subalist-p "imenu")
 (declare-function imenu--make-index-alist "imenu")
 
 (defun counsel--imenu-candidates ()
   (require 'imenu)
+  (defvar imenu-auto-rescan)
+  (defvar imenu-auto-rescan-maxout)
   (let* ((imenu-auto-rescan t)
          (imenu-auto-rescan-maxout (if current-prefix-arg
                                        (buffer-size)
@@ -4842,7 +5021,8 @@ PREFIX is used to create the key."
             :history 'counsel-imenu-history
             :caller 'counsel-imenu))
 
-;;** `counsel-list-processes'
+;;;; `counsel-list-processes'
+
 (defun counsel-list-processes-action-delete (x)
   "Delete process X."
   (delete-process x)
@@ -4874,7 +5054,8 @@ An extra action allows to switch to the process buffer."
               ("s" counsel-list-processes-action-switch "switch"))
             :caller 'counsel-list-processes))
 
-;;** `counsel-ace-link'
+;;;; `counsel-ace-link'
+
 (defun counsel-ace-link ()
   "Use Ivy completion for `ace-link'."
   (interactive)
@@ -4904,7 +5085,8 @@ An extra action allows to switch to the process buffer."
                 :require-match t
                 :caller 'counsel-ace-link))))
 
-;;** `counsel-minibuffer-history'
+;;;; `counsel-minibuffer-history'
+
 ;;;###autoload
 (defun counsel-minibuffer-history ()
   "Browse minibuffer history."
@@ -4917,7 +5099,8 @@ An extra action allows to switch to the process buffer."
                         (insert (substring-no-properties (car x))))
               :caller 'counsel-minibuffer-history)))
 
-;;** `counsel-esh-history'
+;;;; `counsel-esh-history'
+
 (defvar comint-input-ring-index)
 (defvar eshell-history-index)
 (defvar slime-repl-input-history-position)
@@ -5015,7 +5198,8 @@ An extra action allows to switch to the process buffer."
 ;; `counsel-slime-repl-history' within
 ;; `counsel--browse-history-action'.
 
-;;** `counsel-hydra-heads'
+;;;; `counsel-hydra-heads'
+
 (defvar hydra-curr-body-fn)
 (declare-function hydra-keyboard-quit "ext:hydra")
 
@@ -5039,7 +5223,9 @@ An extra action allows to switch to the process buffer."
     (ivy-read "head: " head-names
               :action (lambda (x) (call-interactively (cdr x))))
     (hydra-keyboard-quit)))
-;;** `counsel-semantic'
+
+;;;; `counsel-semantic'
+
 (declare-function semantic-tag-start "semantic/tag")
 (declare-function semantic-tag-class "semantic/tag")
 (declare-function semantic-tag-name "semantic/tag")
@@ -5117,8 +5303,7 @@ TREEP is used to expand internal nodes."
       (counsel-semantic)
     (counsel-imenu)))
 
-;;** `counsel-outline'
-(declare-function org-trim "org-macs")
+;;;; `counsel-outline'
 
 (defcustom counsel-outline-face-style nil
   "Determines how to style outline headings during completion.
@@ -5329,7 +5514,8 @@ the face to apply."
               :caller (or (plist-get settings :caller)
                           'counsel-outline))))
 
-;;** `counsel-ibuffer'
+;;;; `counsel-ibuffer'
+
 (defvar counsel-ibuffer--buffer-name nil
   "Name of the buffer to use for `counsel-ibuffer'.")
 
@@ -5397,7 +5583,8 @@ the values are the corresponding buffer objects."
  '(("j" counsel-ibuffer-visit-buffer-other-window "other window")
    ("v" counsel-ibuffer-visit-ibuffer "switch to Ibuffer")))
 
-;;** `counsel-switch-to-shell-buffer'
+;;;; `counsel-switch-to-shell-buffer'
+
 (defun counsel--buffers-with-mode (mode)
   "Return names of buffers with MODE as their `major-mode'."
   (let (bufs)
@@ -5427,7 +5614,8 @@ If there is no such buffer, start a new `shell' with 
NAME."
                             (reusable-frames . visible)))
     (shell name)))
 
-;;** `counsel-unicode-char'
+;;;; `counsel-unicode-char'
+
 (defvar counsel-unicode-char-history nil
   "History for `counsel-unicode-char'.")
 
@@ -5484,7 +5672,8 @@ COUNT defaults to 1."
  'counsel-unicode-char
  '(("w" counsel-unicode-copy "copy")))
 
-;;** `counsel-colors'
+;;;; Colors
+
 (defun counsel-colors-action-insert-hex (color)
   "Insert the hexadecimal RGB value of COLOR."
   (insert (get-text-property 0 'hex color)))
@@ -5493,7 +5682,8 @@ COUNT defaults to 1."
   "Kill the hexadecimal RGB value of COLOR."
   (kill-new (get-text-property 0 'hex color)))
 
-;;** `counsel-colors-emacs'
+;;;;; `counsel-colors-emacs'
+
 (defvar counsel-colors-emacs-history ()
   "History for `counsel-colors-emacs'.")
 
@@ -5586,7 +5776,8 @@ selected color."
  '(("h" counsel-colors-action-insert-hex "insert hexadecimal value")
    ("H" counsel-colors-action-kill-hex "kill hexadecimal value")))
 
-;;** `counsel-colors-web'
+;;;;; `counsel-colors-web'
+
 (defvar shr-color-html-colors-alist)
 
 (defun counsel-colors--web-alist ()
@@ -5635,7 +5826,8 @@ selected color."
  '(("h" counsel-colors-action-insert-hex "insert hexadecimal value")
    ("H" counsel-colors-action-kill-hex "kill hexadecimal value")))
 
-;;** `counsel-fonts'
+;;;; `counsel-fonts'
+
 (defvar counsel-fonts-history ()
   "History for `counsel-fonts'.")
 
@@ -5663,15 +5855,17 @@ You can insert or kill the name of the selected font."
           (propertize "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
                       'face (list :family font-name))))
 
-;;** `counsel-kmacro'
+;;;; `counsel-kmacro'
+
 (defvar counsel-kmacro-map
   (let ((map (make-sparse-keymap)))
     (define-key map (kbd "C-k") #'counsel-kmacro-kill)
     map))
 
+;; Avoid (declare (modes ...)) warnings in Emacs < 28.
+(function-put #'counsel-kmacro-kill 'command-modes '(minibuffer-mode))
 (defun counsel-kmacro-kill ()
   "Kill the line, or delete the currently selected keyboard macro."
-  (declare (modes minibuffer-mode))
   (interactive)
   (unless (window-minibuffer-p)
     (user-error "No completion session is active"))
@@ -5911,7 +6105,8 @@ The existing CANDIDATE, its counter and format, are left 
unchanged."
    ("v" ,#'counsel-kmacro-action-copy-initial-counter-value
     "copy starting counter value")))
 
-;;** `counsel-geiser-doc-look-up-manual'
+;;;; `counsel-geiser-doc-look-up-manual'
+
 (declare-function geiser-doc-manual-for-symbol "ext:geiser-doc")
 (defvar geiser-completion-symbol-list-func)
 
@@ -5928,9 +6123,10 @@ The existing CANDIDATE, its counter and format, are left 
unchanged."
             :action (lambda (cand)
                       (geiser-doc-manual-for-symbol (intern cand)))
             :caller 'counsel-geiser-doc-look-up-manual))
+
+;;; Misc. OS
+;;;; `counsel-rhythmbox'
 
-;;* Misc. OS
-;;** `counsel-rhythmbox'
 (declare-function dbus-call-method "dbus")
 (declare-function dbus-get-property "dbus")
 
@@ -6049,7 +6245,7 @@ The existing CANDIDATE, its counter and format, are left 
unchanged."
               ("s" counsel-rhythmbox-toggle-shuffle "Shuffle on/off"))
             :caller 'counsel-rhythmbox))
 
-;;** `counsel-linux-app'
+;;;; `counsel-linux-app'
 
 ;; Added in Emacs 26.1.
 (require 'xdg nil t)
@@ -6215,7 +6411,8 @@ This function always returns its elements in a stable 
order."
       (when (file-exists-p dir)
         (let ((dir (file-name-as-directory dir)))
           ;; Function `directory-files-recursively' added in Emacs 25.1.
-          (dolist (file (directory-files-recursively dir "\\.desktop\\'"))
+          (dolist (file (and (fboundp 'directory-files-recursively)
+                             (directory-files-recursively dir 
"\\.desktop\\'")))
             (let ((id (subst-char-in-string ?/ ?- (file-relative-name file 
dir))))
               (when (and (not (gethash id hash)) (file-readable-p file))
                 (push (cons id file) result)
@@ -6340,7 +6537,8 @@ When ARG is non-nil, ignore NoDisplay property in 
*.desktop files."
             :action #'counsel-linux-app-action-default
             :caller 'counsel-linux-app))
 
-;;** `counsel-wmctrl'
+;;;; `counsel-wmctrl'
+
 (defun counsel-wmctrl-action (x)
   "Select the desktop window that corresponds to X."
   (counsel--run "wmctrl" "-i" "-a" (cdr x)))
@@ -6457,7 +6655,8 @@ in the current window."
  '(("x" counsel-open-buffer-file-externally "open externally")
    ("j" ivy--switch-buffer-other-window-action "other window")))
 
-;;** `counsel-compile'
+;;;; `counsel-compile'
+
 (defvar counsel-compile-history nil
   "History for `counsel-compile'.
 
@@ -6748,6 +6947,8 @@ This is determined by `counsel-compile-local-builds', 
which see."
 ;; things like infer `default-directory' from 'cd's in the string.
 (defun counsel-compile--update-history (_proc)
   "Update `counsel-compile-history' from the compilation state."
+  (defvar compilation-arguments)
+  (defvar compilation-environment)
   (let* ((srcdir (counsel--compile-root))
          (blddir default-directory)
          (bldenv compilation-environment)
@@ -6776,6 +6977,7 @@ edited the command, thus losing our embedded state.")
 If CMD has the `recursive' property set we call `counsel-compile'
 again to further refine the compile options in the directory
 specified by the `blddir' property."
+  (defvar compilation-environment)
   (let ((blddir (get-text-property 0 'blddir cmd))
         (bldenv (get-text-property 0 'bldenv cmd)))
     (if (get-text-property 0 'recursive cmd)
@@ -6812,7 +7014,8 @@ handling for the `counsel-compile' metadata."
 ;; operation which doesn't include the metadata we want.
 (defvar counsel-compile-map
   (let ((map (make-sparse-keymap)))
-    (define-key map [remap ivy-insert-current] #'counsel-compile-edit-command)
+    (define-key map `[remap ,#'ivy-insert-current]
+                #'counsel-compile-edit-command)
     map)
   "Additional ivy keybindings during command selection.")
 
@@ -6824,6 +7027,8 @@ Additional actions:
 
 \\{counsel-compile-map}"
   (interactive)
+  (require 'compile)
+  (require 'dired) ;; For face `dired-directory'.
   (setq counsel-compile--current-build-dir (or dir
                                                (counsel--compile-root)
                                                default-directory))
@@ -6882,134 +7087,155 @@ Additional actions:
 (ivy-configure 'counsel-compile-env
   :format-fn #'counsel-compile-env--format-hint)
 
-;;** `counsel-minor'
-(defvar counsel-minor-history nil
-  "History for `counsel-minor'.")
-
-(defun counsel--minor-candidates ()
-  "Return completion alist for `counsel-minor'.
-
-The alist element is cons of minor mode string with its lighter
-and minor mode symbol."
-  (cl-mapcan
-   (let ((suffix (propertize " \"%s\"" 'face 'font-lock-string-face)))
-     (lambda (mode)
-       (when (and (boundp mode) (commandp mode))
-         (let ((lighter (cdr (assq mode minor-mode-alist))))
-           (list (cons (concat
-                        (if (symbol-value mode) "-" "+")
-                        (symbol-name mode)
-                        (and lighter
-                             (format suffix
-                                     (format-mode-line (cons t lighter)))))
-                       mode))))))
-   minor-mode-list))
-
-;;;###autoload
-(defun counsel-minor ()
-  "Enable or disable minor mode.
-
-Disabled minor modes are prefixed with \"+\", and
-selecting one of these will enable it.
-Enabled minor modes are prefixed with \"-\", and
-selecting one of these will enable it.
-
-Additional actions:\\<ivy-minibuffer-map>
-
-  \\[ivy-dispatching-done] d: Go to minor mode definition
-  \\[ivy-dispatching-done] h: Describe minor mode"
-
-  (interactive)
-  (ivy-read "Minor modes (enable +mode or disable -mode): "
-            (counsel--minor-candidates)
-            :require-match t
-            :history 'counsel-minor-history
-            :action (lambda (x)
-                      (call-interactively (cdr x)))))
-
-(ivy-configure 'counsel-minor
-  :initial-input "^+"
-  :sort-fn #'ivy-string<)
-
-(ivy-set-actions
- 'counsel-minor
- `(("d" ,(lambda (x) (find-function (cdr x))) "definition")
-   ("h" ,(lambda (x) (describe-function (cdr x))) "help")))
-
-;;;###autoload
-(defun counsel-major ()
-  (interactive)
-  (ivy-read "Major modes: " obarray
-            :predicate (lambda (f)
-                         (and (commandp f)
-                              (string-suffix-p "-mode" (symbol-name f))
-                              (or (and (autoloadp (symbol-function f))
-                                       (let ((doc-split (help-split-fundoc 
(documentation f) f)))
-                                         ;; major mode starters have no 
arguments
-                                         (and doc-split (null (cdr (read (car 
doc-split)))))))
-                                  (null (help-function-arglist f)))))
-            :action #'counsel-M-x-action
-            :caller 'counsel-major))
-
-;;** `counsel-search'
-(declare-function request "ext:request")
+;;;; `counsel-search'
 
 (defcustom counsel-search-engine 'ddg
   "The search engine choice in `counsel-search-engines-alist'."
   :type '(choice
-          (const ddg)
-          (const google)))
+          (const :tag "DuckDuckGo" ddg)
+          (const :tag "Google" google)))
 
 (defcustom counsel-search-engines-alist
-  '((google
-     "http://suggestqueries.google.com/complete/search";
-     "https://www.google.com/search?q=";
-     counsel--search-request-data-google)
-    (ddg
+  '((ddg
      "https://duckduckgo.com/ac/";
      "https://duckduckgo.com/html/?q=";
-     counsel--search-request-data-ddg))
-  "Search engine parameters for `counsel-search'."
+     counsel--search-request-data-ddg)
+    (google
+     "https://suggestqueries.google.com/complete/search";
+     "https://www.google.com/search?q=";
+     counsel--search-request-data-google))
+  "List of search engine parameters for `counsel-search'.
+Each element is of the form (SYMBOL SUGGEST BROWSE EXTRACT), where:
+SYMBOL identifies the search engine, as per `counsel-search-engine'.
+SUGGEST is the URL to query for suggestions.
+BROWSE is the URL prefix for visiting the selected result.
+EXTRACT is a function that takes the object parsed from the SUGGEST
+ endpoint and transforms it into a set of Ivy candidates."
+  :package-version '(counsel . "0.16.0")
   :type '(alist :key-type symbol :value-type (list string string function)))
 
 (defun counsel--search-request-data-google (data)
-  (mapcar #'identity (aref data 1)))
+  "Extract Google suggestions from parsed JSON DATA.
+Expects input of the form [\"a\" [\"ab\" \"ac\"] ...]."
+  (append (aref data 1) ()))
 
 (defun counsel--search-request-data-ddg (data)
+  "Extract DuckDuckGo suggestions from parsed JSON DATA.
+Expects input of the form [((phrase . \"ab\")) ...]."
   (mapcar #'cdar data))
 
+(defvar counsel--native-json)
+(put 'counsel--native-json 'variable-documentation
+     "Non-nil if Emacs supports JSON natively, or void.")
+
+(defun counsel--search-update (extract str type)
+  "Call EXTRACT on JSON STR of Content-TYPE."
+  (unless (fboundp 'mail-header-parse-content-type)
+    (require 'mail-parse))
+  (declare-function json-parse-string "json.c")
+  (declare-function json-read-from-string "json")
+  (declare-function mail-content-type-get "mail-parse")
+  (declare-function mail-header-parse-content-type "mail-parse")
+  (let* ((ct (and type (mail-header-parse-content-type type)))
+         (coding (coding-system-from-name (mail-content-type-get ct 
'charset))))
+    (when coding
+      (setq str (decode-coding-string str coding t))))
+  (let ((obj (if counsel--native-json
+                 (json-parse-string str :object-type 'alist)
+               (defvar json-array-type)
+               (defvar json-object-type)
+               (let ((json-array-type 'vector)
+                     (json-object-type 'alist))
+                 (json-read-from-string str)))))
+    (ivy-update-candidates (funcall extract obj))))
+
+(defun counsel--search-plz (url extract)
+  "Fetch URL with `plz' and EXTRACT its JSON payload."
+  (declare-function plz "ext:plz")
+  (declare-function plz-response-body "ext:plz")
+  (declare-function plz-response-headers "ext:plz")
+  ;; Doesn't handle Content-Type, so defer decoding+parsing until :then.
+  ;; (See URL `https://github.com/alphapapa/plz.el/pull/66'.)
+  ;; Ask for a `plz-response' object because it already contains the parsed
+  ;; headers (though just widening the response buffer could be quicker).
+  (plz 'get url :as 'response :decode nil :noquery t
+    :then (lambda (response)
+            (let* ((heads (plz-response-headers response))
+                   (body (plz-response-body response))
+                   (ct (cdr (assq 'content-type heads))))
+              (counsel--search-update extract body ct)))))
+
+(defun counsel--search-request (url extract)
+  "Fetch URL with `request' and EXTRACT its JSON payload."
+  (declare-function request "ext:request")
+  (declare-function request-response-header "ext:request")
+  ;; Doesn't handle Content-Type (expects coding system a priori),
+  ;; so defer decoding+parsing until :success.
+  (request url :type "GET"
+    :success (cl-function
+              (lambda (&key data response &allow-other-keys)
+                (let ((ct (request-response-header response "content-type")))
+                  (counsel--search-update extract data ct))))))
+
+(defvar counsel--search-backend)
+(put 'counsel--search-backend 'variable-documentation
+     "Feature symbol indicating available HTTP library, or void.
+Valid values are the keys of `counsel--search-backends'.")
+
+(defvar counsel--search-backends
+  `((plz ,#'counsel--search-plz)
+    (request ,#'counsel--search-request))
+  "List of (BACKEND GETTER) for `counsel-search'.
+BACKEND is a feature symbol like `counsel--search-backend'.
+GETTER is a function taking a URL and an EXTRACT function as in
+ `counsel-search-engines-alist'.")
+
 (defun counsel-search-function (input)
   "Create a request to a search engine with INPUT.
 Return 0 tells `ivy--exhibit' not to update the minibuffer.
 We update it in the callback with `ivy-update-candidates'."
   (or
    (ivy-more-chars)
-   (let ((engine (cdr (assoc counsel-search-engine 
counsel-search-engines-alist))))
-     (request
-      (nth 0 engine)
-      :type "GET"
-      :params (list
-               (cons "client" "firefox")
-               (cons "q" input))
-      :parser 'json-read
-      :success (cl-function
-                (lambda (&key data &allow-other-keys)
-                  (ivy-update-candidates
-                   (funcall (nth 2 engine) data)))))
+   (let* ((backend (assq counsel--search-backend counsel--search-backends))
+          (engine (assq counsel-search-engine counsel-search-engines-alist))
+          (suggest (nth 1 engine))
+          (extract (nth 3 engine))
+          (url (concat suggest (if (ivy--string-search "?" suggest) "&" "?")
+                       ;; FIXME: `client' needed only for `google'?
+                       (url-build-query-string `(("client" "firefox")
+                                                 ("q" ,input))))))
+     ;; Do we need to cancel requests already in flight?
+     (funcall (nth 1 backend) url extract)
      0)))
 
-(defun counsel-search-action (x)
-  "Search for X."
-  (browse-url
-   (concat
-    (nth 2 (assoc counsel-search-engine counsel-search-engines-alist))
-    (url-hexify-string x))))
+(defun counsel-search-action (candidate)
+  "Browse the search results for `counsel-search' CANDIDATE."
+  (let ((engine (assq counsel-search-engine counsel-search-engines-alist)))
+    (browse-url (concat (nth 2 engine) (url-hexify-string candidate)))))
 
 (defun counsel-search ()
-  "Ivy interface for dynamically querying a search engine."
+  "Ivy interface for querying a search engine.
+Dynamically displays search suggestions for the current input.
+The user options `counsel-search-engine' and
+`counsel-search-engines-alist' determine the engine."
   (interactive)
-  (require 'request)
-  (require 'json)
+  (unless (boundp 'counsel--search-backend)
+    (setq counsel--search-backend
+          ;; `plz' is on GNU ELPA; `request' on NonGNU ELPA.
+          (or (require 'plz nil t)
+              (require 'request nil t)
+              (user-error
+               "Required package `plz' (or `request') not installed"))))
+  ;; - Emacs 27: optional native JSON support.
+  ;; - Emacs 28: `json-available-p'.
+  ;; - Emacs 30: unconditional native JSON support.
+  ;; That means the following sets `counsel--native-json' to nil even for
+  ;; Emacs 27 with native JSON support, in the interest of simplicity.
+  (or (boundp 'counsel--native-json)
+      (setq counsel--native-json
+            (and (fboundp 'json-available-p)
+                 (json-available-p)))
+      (require 'json))
   (ivy-read "search: " #'counsel-search-function
             :action #'counsel-search-action
             :dynamic-collection t
@@ -7018,7 +7244,13 @@ We update it in the callback with 
`ivy-update-candidates'."
 (define-obsolete-function-alias 'counsel-google
     #'counsel-search "0.13.2 (2019-10-17)")
 
-;;** `counsel-compilation-errors'
+;;;; `counsel-compilation-errors'
+
+(declare-function compilation--message->loc "compile" t t)
+(declare-function compilation-buffer-p "compile")
+(declare-function compilation-next-single-property-change "compile")
+(declare-function compile-goto-error "compile")
+
 (defun counsel--compilation-errors-buffer (buf)
   (with-current-buffer buf
     (let ((res nil)
@@ -7052,12 +7284,14 @@ We update it in the callback with 
`ivy-update-candidates'."
 (defun counsel-compilation-errors ()
   "Compilation errors."
   (interactive)
+  (require 'compile)
   (ivy-read "compilation errors: " (counsel-compilation-errors-cands)
             :require-match t
             :action #'counsel-compilation-errors-action
             :history 'counsel-compilation-errors-history))
 
-;;** `counsel-flycheck'
+;;;; `counsel-flycheck'
+
 (defvar flycheck-current-errors)
 (declare-function flycheck-error-filename "ext:flycheck")
 (declare-function flycheck-error-line "ext:flycheck")
@@ -7107,34 +7341,33 @@ We update it in the callback with 
`ivy-update-candidates'."
             :require-match t
             :action #'counsel-flycheck-errors-action
             :history 'counsel-flycheck-errors-history))
+
+;;; `counsel-mode'
 
-
-;;* `counsel-mode'
 (defvar counsel-mode-map
   (let ((map (make-sparse-keymap)))
-    (dolist (binding
-              '((execute-extended-command . counsel-M-x)
-                (describe-bindings . counsel-descbinds)
-                (describe-function . counsel-describe-function)
-                (describe-variable . counsel-describe-variable)
-                (describe-symbol . counsel-describe-symbol)
-                (apropos-command . counsel-apropos)
-                (describe-face . counsel-describe-face)
-                (list-faces-display . counsel-faces)
-                (find-file . counsel-find-file)
-                (find-library . counsel-find-library)
-                (imenu . counsel-imenu)
-                (load-library . counsel-load-library)
-                (load-theme . counsel-load-theme)
-                (yank-pop . counsel-yank-pop)
-                (info-lookup-symbol . counsel-info-lookup-symbol)
-                (pop-to-mark-command . counsel-mark-ring)
-                (geiser-doc-look-up-manual . counsel-geiser-doc-look-up-manual)
-                (bookmark-jump . counsel-bookmark)))
-      (define-key map (vector 'remap (car binding)) (cdr binding)))
+    (define-key map `[remap ,#'execute-extended-command] #'counsel-M-x)
+    (define-key map `[remap ,#'describe-bindings] #'counsel-descbinds)
+    (define-key map `[remap ,#'describe-function] #'counsel-describe-function)
+    (define-key map `[remap ,#'describe-variable] #'counsel-describe-variable)
+    (define-key map [remap describe-symbol] #'counsel-describe-symbol)
+    (define-key map `[remap ,#'apropos-command] #'counsel-apropos)
+    (define-key map `[remap ,#'describe-face] #'counsel-describe-face)
+    (define-key map `[remap ,#'list-faces-display] #'counsel-faces)
+    (define-key map `[remap ,#'find-file] #'counsel-find-file)
+    (define-key map `[remap ,#'find-library] #'counsel-find-library)
+    (define-key map `[remap ,#'imenu] #'counsel-imenu)
+    (define-key map `[remap ,#'load-library] #'counsel-load-library)
+    (define-key map `[remap ,#'load-theme] #'counsel-load-theme)
+    (define-key map `[remap ,#'yank-pop] #'counsel-yank-pop)
+    (define-key map `[remap ,#'info-lookup-symbol] 
#'counsel-info-lookup-symbol)
+    (define-key map `[remap ,#'pop-to-mark-command] #'counsel-mark-ring)
+    (define-key map [remap geiser-doc-look-up-manual]
+                #'counsel-geiser-doc-look-up-manual)
+    (define-key map `[remap ,#'bookmark-jump] #'counsel-bookmark)
     map)
-  "Map for `counsel-mode'.
-Remaps built-in functions to counsel replacements.")
+  "Keymap for `counsel-mode'.
+Remaps built-in and external functions to Counsel replacements.")
 
 (defcustom counsel-mode-override-describe-bindings nil
   "Whether to override `describe-bindings' when `counsel-mode' is active."
diff --git a/targets/elpa.el b/targets/elpa.el
new file mode 100644
index 0000000000..e80dc6a981
--- /dev/null
+++ b/targets/elpa.el
@@ -0,0 +1,125 @@
+;;; targets/elpa.el --- Optional Ivy dependencies -*- lexical-binding: t -*-
+
+;; Copyright (C) 2019-2025 Free Software Foundation, Inc.
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(require 'package)
+
+(defvar ivy--elpa-stable
+  (or (getenv "ELPA_STABLE")
+      (getenv "MELPA_STABLE"))
+  "Non-nil if GNU ELPA should be used instead of GNU-devel ELPA.")
+
+(defvar ivy--elpa-dir "~/.elpa"
+  "Parent directory for installing optional dependencies.")
+
+(defvar ivy--elpa-user-dir
+  (expand-file-name
+   (format "%s%s/elpa" emacs-version (if ivy--elpa-stable "-stable" ""))
+   ivy--elpa-dir)
+  "Instance-specific value for `package-user-dir'.")
+
+;; FIXME: Switch to `gnu' once https://bugs.gnu.org/76264 is resolved.
+(defvar ivy--elpa-archive 'melpa
+  "Preferred ELPA archive; keys `ivy--elpa-archives'.")
+
+(defvar ivy--elpa-archives
+  ;; Check default value rather than `gnutls-available-p': even when
+  ;; the latter is non-nil my Emacs 24.5 fails with https://.
+  (let ((s (if (string-prefix-p "https" (cdar package-archives)) "s" "")))
+    `((gnu
+       ("gnu" . ,(format "http%s://elpa.gnu.org/%s/"
+                         s (if ivy--elpa-stable "packages" "devel")))
+       ;; For `wgrep'.
+       ("nongnu" . ,(format "http%s://elpa.nongnu.org/nongnu%s/"
+                            s (if ivy--elpa-stable "" "-devel"))))
+      (melpa
+       ("melpa" . ,(format "https://%smelpa.org/packages/";
+                           (if ivy--elpa-stable "stable." ""))))))
+  "Map ELPA archive symbols to their `package-archives'.")
+
+(defvar ivy--elpa-pkgs
+  '(avy
+    hydra
+    wgrep)
+  "List of optional (or development) package dependencies.")
+
+(defvar ivy--elpa-activated nil
+  "Non-nil if `ivy--elpa-activate' succeeded.")
+
+(defvar ivy--elpa-refreshed nil
+  "Non-nil if `ivy--elpa-refresh' succeeded.")
+
+(defun ivy--elpa-activate ()
+  "Ensure packages under `ivy--elpa-dir' are activated."
+  (unless ivy--elpa-activated
+    (setq package-user-dir ivy--elpa-user-dir)
+    (let ((msg (format "Activating packages in %s" package-user-dir)))
+      (message "%s..." msg)
+      (package-initialize)
+      (message "%s...done" msg))
+    (setq ivy--elpa-activated t)))
+
+(defun ivy--elpa-refresh ()
+  "Ensure archive contents are refreshed."
+  (defvar gnutls-algorithm-priority)
+  (unless ivy--elpa-refreshed
+    (let ((archive ivy--elpa-archive))
+      (setq package-archives (cdr (assq archive ivy--elpa-archives)))
+      (and (eq archive 'melpa)
+           (version< emacs-version "26.3")
+           ;; See https://melpa.org/#/getting-started.
+           (setq gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3")))
+    (package-refresh-contents)
+    (setq ivy--elpa-refreshed (and package-archive-contents t))))
+
+(defun ivy--elpa-install-pkg (pkg)
+  "Compatibility shim for Emacs 25 `package-install'."
+  (condition-case nil
+      (package-install pkg t)
+    (wrong-number-of-arguments
+     (package-install pkg))))
+
+(defun ivy--elpa-install ()
+  "Install any missing `ivy--elpa-pkgs' with demoted errors."
+  (ivy--elpa-activate)
+  (ivy--elpa-refresh)
+  (let ((msg-all (format "Installing in %s" package-user-dir))
+        any-ok any-err)
+    (message "%s..." msg-all)
+    (dolist (pkg ivy--elpa-pkgs)
+      (unless (package-installed-p pkg)
+        (let ((msg (format "Installing %s" pkg))
+              err)
+          (message "%s..." msg)
+          (condition-case-unless-debug e
+              (ivy--elpa-install-pkg pkg)
+            (error (message "Error: %s" (error-message-string e))
+                   (message "%s...INCOMPLETE" msg)
+                   (setq any-err t)
+                   (setq err e)))
+          (unless err
+            (message "%s...done" msg)
+            (setq any-ok t)))))
+    (message "%s...%s" msg-all
+             (cond (any-err "INCOMPLETE")
+                   (any-ok "done")
+                   (t "already present")))))
+
+;; TODO: upgrade-deps target?
+
+(provide 'targets/elpa)

Reply via email to