config

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

vertico-repeat.el (9451B)


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