config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

vertico-repeat.el (9566B)


      1 ;;; vertico-repeat.el --- Repeat Vertico sessions -*- lexical-binding: t -*-
      2 
      3 ;; Copyright (C) 2021-2024 Free Software Foundation, Inc.
      4 
      5 ;; Author: Daniel Mendler <mail@daniel-mendler.de>
      6 ;; Maintainer: Daniel Mendler <mail@daniel-mendler.de>
      7 ;; Created: 2021
      8 ;; Version: 1.8
      9 ;; Package-Requires: ((emacs "27.1") (compat "29.1.4.4") (vertico "1.8"))
     10 ;; Homepage: https://github.com/minad/vertico
     11 
     12 ;; This file is part of GNU Emacs.
     13 
     14 ;; This program is free software: you can redistribute it and/or modify
     15 ;; it under the terms of the GNU General Public License as published by
     16 ;; the Free Software Foundation, either version 3 of the License, or
     17 ;; (at your option) any later version.
     18 
     19 ;; This program is distributed in the hope that it will be useful,
     20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     22 ;; GNU General Public License for more details.
     23 
     24 ;; You should have received a copy of the GNU General Public License
     25 ;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
     26 
     27 ;;; Commentary:
     28 
     29 ;; This package is a Vertico extension, which enables repetition of
     30 ;; Vertico sessions via the `vertico-repeat', `vertico-repeat-previous'
     31 ;; and `vertico-repeat-select' commands.  If the repeat commands are
     32 ;; called from an existing Vertico minibuffer session, only sessions
     33 ;; corresponding to the current minibuffer command are offered via
     34 ;; completion.
     35 ;;
     36 ;; It is necessary to register a minibuffer setup hook, which saves
     37 ;; the Vertico state for repetition.  In order to save the history
     38 ;; across Emacs sessions, enable `savehist-mode' and add
     39 ;; `vertico-repeat-history' to `savehist-additional-variables'.
     40 ;;
     41 ;; (keymap-global-set "M-R" #'vertico-repeat)
     42 ;; (keymap-set vertico-map "M-P" #'vertico-repeat-previous)
     43 ;; (keymap-set vertico-map "M-N" #'vertico-repeat-next)
     44 ;; (keymap-set vertico-map "S-<prior>" #'vertico-repeat-previous)
     45 ;; (keymap-set vertico-map "S-<next>" #'vertico-repeat-next)
     46 ;; (add-hook 'minibuffer-setup-hook #'vertico-repeat-save)
     47 ;;
     48 ;; See also the related extension `vertico-suspend', which uses a
     49 ;; different technique, relying on recursive minibuffers to suspend
     50 ;; the current completion session temporarily while preserving the
     51 ;; entire state.
     52 
     53 ;;; Code:
     54 
     55 (require 'vertico)
     56 (eval-when-compile (require 'cl-lib))
     57 
     58 (defcustom vertico-repeat-filter
     59   '(vertico-repeat
     60     vertico-repeat-select
     61     execute-extended-command
     62     execute-extended-command-for-buffer)
     63   "List of commands to filter out from the history."
     64   :type '(repeat symbol)
     65   :group 'vertico)
     66 
     67 (defcustom vertico-repeat-transformers
     68   (list #'vertico-repeat--filter-empty
     69         #'vertico-repeat--filter-commands)
     70   "List of functions to apply to history element before saving."
     71   :type '(repeat function)
     72   :group 'vertico)
     73 
     74 (declare-function vertico-multiform-vertical "ext:vertico-multiform")
     75 (defvar vertico-multiform--display-modes)
     76 (defvar vertico-repeat-history nil)
     77 (defvar-local vertico-repeat--command nil)
     78 (defvar-local vertico-repeat--input nil)
     79 (defvar-local vertico-repeat--step nil)
     80 (defvar-local vertico-repeat--pos 0)
     81 
     82 (defun vertico-repeat--filter-commands (session)
     83   "Filter SESSION if command is listed in `vertico-repeat-filter'."
     84   (and (not (memq (car session) vertico-repeat-filter)) session))
     85 
     86 (defun vertico-repeat--filter-empty (session)
     87   "Filter SESSION if input is empty."
     88   (and (cadr session) (not (equal (cadr session) "")) session))
     89 
     90 (defun vertico-repeat--save-input ()
     91   "Save current minibuffer input."
     92   (setq vertico-repeat--input (minibuffer-contents-no-properties)))
     93 
     94 (defun vertico-repeat--current ()
     95   "Return the current session datum."
     96   `(,vertico-repeat--command
     97     ,vertico-repeat--input
     98     ,@(and vertico--lock-candidate
     99            (>= vertico--index 0)
    100            (list (substring-no-properties
    101                   (nth vertico--index vertico--candidates))))
    102     ,@(and (bound-and-true-p vertico-multiform-mode)
    103            (ensure-list
    104             (seq-find (lambda (x) (and (boundp x) (symbol-value x)))
    105                       vertico-multiform--display-modes)))))
    106 
    107 (defun vertico-repeat--save-exit ()
    108   "Save command session in `vertico-repeat-history'."
    109   (let ((session (vertico-repeat--current))
    110         (transform vertico-repeat-transformers))
    111     (while (and transform (setq session (funcall (pop transform) session))))
    112     (when session
    113       (add-to-history 'vertico-repeat-history session))))
    114 
    115 (defun vertico-repeat--restore (session)
    116   "Restore Vertico SESSION for `vertico-repeat'."
    117   (delete-minibuffer-contents)
    118   (insert (cadr session))
    119   (setq vertico--lock-candidate
    120         (when-let ((cand (seq-find #'stringp (cddr session))))
    121           (vertico--update)
    122           (when-let ((idx (seq-position vertico--candidates cand)))
    123             (setq vertico--index idx)
    124             t)))
    125   ;; Restore display modes if not modifying the current session
    126   (when-let (((not (and vertico-repeat--command
    127                         (eq vertico-repeat--command (car session)))))
    128              (mode (seq-find #'symbolp (cddr session)))
    129              ((bound-and-true-p vertico-multiform-mode))
    130              ((not (and (boundp mode) (symbol-value mode)))))
    131     (vertico-multiform-vertical mode))
    132   (vertico--exhibit))
    133 
    134 (defun vertico-repeat--run (session)
    135   "Run Vertico completion SESSION."
    136   (unless session
    137     (user-error "No repeatable session"))
    138   (if (and vertico-repeat--command (eq vertico-repeat--command (car session)))
    139       (vertico-repeat--restore session)
    140     (minibuffer-with-setup-hook
    141         (apply-partially #'vertico-repeat--restore session)
    142       (command-execute (setq this-command (car session))))))
    143 
    144 ;;;###autoload
    145 (defun vertico-repeat-save ()
    146   "Save Vertico session for `vertico-repeat'.
    147 This function must be registered as `minibuffer-setup-hook'."
    148   (when (and vertico--input (symbolp this-command))
    149     (setq vertico-repeat--command this-command)
    150     (add-hook 'post-command-hook #'vertico-repeat--save-input nil 'local)
    151     (add-hook 'minibuffer-exit-hook #'vertico-repeat--save-exit nil 'local)))
    152 
    153 ;;;###autoload
    154 (defun vertico-repeat-next (n)
    155   "Repeat Nth next Vertico completion session.
    156 This command must be called from an existing Vertico session
    157 after `vertico-repeat-previous'."
    158   (interactive "p")
    159   (vertico-repeat-previous (- n)))
    160 
    161 ;;;###autoload
    162 (defun vertico-repeat-previous (n)
    163   "Repeat Nth previous Vertico completion session.
    164 If called from an existing Vertico session, restore the input and
    165 selected candidate for the current command."
    166   (interactive "p")
    167   (vertico-repeat--run
    168    (if (not vertico-repeat--command)
    169        (and (> n 0) (nth (1- n) vertico-repeat-history))
    170      (cond
    171       ((not vertico-repeat--step)
    172        (setq vertico-repeat--step
    173              (cons (vertico-repeat--current)
    174                    (cl-loop for h in vertico-repeat-history
    175                             if (eq (car h) vertico-repeat--command) collect h))))
    176       ((= vertico-repeat--pos 0)
    177        (setcar vertico-repeat--step (vertico-repeat--current))))
    178      (cl-incf n vertico-repeat--pos)
    179      (when-let (((>= n 0)) (session (nth n vertico-repeat--step)))
    180        (setq vertico-repeat--pos n)
    181        session))))
    182 
    183 (define-obsolete-function-alias
    184   'vertico-repeat-last 'vertico-repeat-previous "1.4")
    185 
    186 ;;;###autoload
    187 (defun vertico-repeat-select ()
    188   "Select a Vertico session from the session history and repeat it.
    189 If called from an existing Vertico session, you can select among
    190 previous sessions for the current command."
    191   (interactive)
    192   (vertico-repeat--run
    193    (let* ((current-cmd vertico-repeat--command)
    194           (trimmed
    195            (delete-dups
    196             (or
    197              (cl-loop
    198               for session in vertico-repeat-history
    199               if (or (not current-cmd) (eq (car session) current-cmd))
    200               collect
    201               (list
    202                (symbol-name (car session))
    203                (replace-regexp-in-string
    204                 "\\s-+" " "
    205                 (string-trim (cadr session)))
    206                session))
    207              (user-error "No repeatable session"))))
    208           (max-cmd (cl-loop for (cmd . _) in trimmed
    209                             maximize (string-width cmd)))
    210           (formatted (cl-loop
    211                       for (cmd input session) in trimmed collect
    212                       (cons
    213                        (concat
    214                         (and (not current-cmd)
    215                              (propertize cmd 'face 'font-lock-function-name-face))
    216                         (and (not current-cmd)
    217                              (make-string (- max-cmd (string-width cmd) -4) ?\s))
    218                         input)
    219                        session)))
    220           (enable-recursive-minibuffers t))
    221      (cdr (assoc (completing-read
    222                   (if current-cmd
    223                       (format "History of %s: " current-cmd)
    224                     "Completion history: ")
    225                   (lambda (str pred action)
    226                     (if (eq action 'metadata)
    227                         '(metadata (display-sort-function . identity)
    228                                    (cycle-sort-function . identity))
    229                       (complete-with-action action formatted str pred)))
    230                   nil t nil t)
    231                  formatted)))))
    232 
    233 ;;;###autoload
    234 (defun vertico-repeat (&optional arg)
    235   "Repeat last Vertico session.
    236 If prefix ARG is non-nil, offer completion menu to select from session history."
    237   (interactive "P")
    238   (if arg (vertico-repeat-select) (vertico-repeat-previous 1)))
    239 
    240 (provide 'vertico-repeat)
    241 ;;; vertico-repeat.el ends here