config

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

vertico-repeat.el (9472B)


      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.9
      9 ;; Package-Requires: ((emacs "28.1") (compat "30") (vertico "1.9"))
     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 ;;;###autoload
    184 (defun vertico-repeat-select ()
    185   "Select a Vertico session from the session history and repeat it.
    186 If called from an existing Vertico session, you can select among
    187 previous sessions for the current command."
    188   (interactive)
    189   (vertico-repeat--run
    190    (let* ((current-cmd vertico-repeat--command)
    191           (trimmed
    192            (delete-dups
    193             (or
    194              (cl-loop
    195               for session in vertico-repeat-history
    196               if (or (not current-cmd) (eq (car session) current-cmd))
    197               collect
    198               (list
    199                (symbol-name (car session))
    200                (replace-regexp-in-string
    201                 "\\s-+" " "
    202                 (string-trim (cadr session)))
    203                session))
    204              (user-error "No repeatable session"))))
    205           (max-cmd (cl-loop for (cmd . _) in trimmed
    206                             maximize (string-width cmd)))
    207           (formatted (cl-loop
    208                       for (cmd input session) in trimmed collect
    209                       (cons
    210                        (concat
    211                         (and (not current-cmd)
    212                              (propertize cmd 'face 'font-lock-function-name-face))
    213                         (and (not current-cmd)
    214                              (make-string (- max-cmd (string-width cmd) -4) ?\s))
    215                         input)
    216                        session)))
    217           (enable-recursive-minibuffers t))
    218      (cdr (assoc (completing-read
    219                   (if current-cmd
    220                       (format "History of %s: " current-cmd)
    221                     "Completion history: ")
    222                   (lambda (str pred action)
    223                     (if (eq action 'metadata)
    224                         '(metadata (display-sort-function . identity)
    225                                    (cycle-sort-function . identity))
    226                       (complete-with-action action formatted str pred)))
    227                   nil t nil t)
    228                  formatted)))))
    229 
    230 ;;;###autoload
    231 (defun vertico-repeat (&optional arg)
    232   "Repeat last Vertico session.
    233 If prefix ARG is non-nil, offer completion menu to select from session history."
    234   (interactive "P")
    235   (if arg (vertico-repeat-select) (vertico-repeat-previous 1)))
    236 
    237 (provide 'vertico-repeat)
    238 ;;; vertico-repeat.el ends here