(defcustom repeat-keep-prefix nil
"Whether to keep the prefix arg of the previous command when repeating."
:type 'boolean
+ :initialize #'custom-initialize-default
+ :set (lambda (sym val)
+ (set-default sym val)
+ (when repeat-mode
+ (if repeat-keep-prefix
+ (add-hook 'pre-command-hook 'repeat-pre-hook)
+ (remove-hook 'pre-command-hook 'repeat-pre-hook))))
:group 'repeat
:version "28.1")
See `describe-repeat-maps' for a list of all repeatable commands."
:global t :group 'repeat
(if (not repeat-mode)
- (remove-hook 'post-command-hook 'repeat-post-hook)
+ (progn
+ (remove-hook 'pre-command-hook 'repeat-pre-hook)
+ (remove-hook 'post-command-hook 'repeat-post-hook))
+ (when repeat-keep-prefix
+ (add-hook 'pre-command-hook 'repeat-pre-hook))
(add-hook 'post-command-hook 'repeat-post-hook)
(let* ((keymaps nil)
(commands (all-completions
(length commands)
(length (delete-dups keymaps))))))
-(defvar repeat--prev-mb '(0)
- "Previous minibuffer state.")
-
(defun repeat--command-property (property)
(or (and (symbolp this-command)
(get this-command property))
(and (symbolp real-this-command)
(get real-this-command property))))
+(defun repeat-get-map ()
+ "Return a transient map for keys repeatable after the current command."
+ (when repeat-mode
+ (let ((rep-map (or repeat-map (repeat--command-property 'repeat-map))))
+ (when rep-map
+ (when (and (symbolp rep-map) (boundp rep-map))
+ (setq rep-map (symbol-value rep-map)))
+ rep-map))))
+
(defun repeat-check-key (key map)
"Check if the last key is suitable to activate the repeating MAP."
(let* ((prop (repeat--command-property 'repeat-check-key))
;; Try without modifiers:
(lookup-key map (vector (event-basic-type key))))))
+(defvar repeat--prev-mb '(0)
+ "Previous minibuffer state.")
+
+(defun repeat-check-map (map)
+ "Decides whether MAP can be used for the next command."
+ (and map
+ ;; Detect changes in the minibuffer state to allow repetitions
+ ;; in the same minibuffer, but not when the minibuffer is activated
+ ;; in the middle of repeating sequence (bug#47566).
+ (or (< (minibuffer-depth) (car repeat--prev-mb))
+ (eq current-minibuffer-command (cdr repeat--prev-mb)))
+ (repeat-check-key last-command-event map)
+ t))
+
+(defun repeat-pre-hook ()
+ "Function run before commands to handle repeatable keys."
+ (when (and repeat-mode repeat-keep-prefix repeat-in-progress
+ (not prefix-arg) current-prefix-arg)
+ (let ((map (repeat-get-map)))
+ ;; Only when repeat-post-hook will activate the same map
+ (when (repeat-check-map map)
+ ;; Optimize to use less logic in the function `repeat-get-map'
+ ;; for the next call: when called again from `repeat-post-hook'
+ ;; it will use the variable `repeat-map'.
+ (setq repeat-map map)
+ ;; Preserve universal argument
+ (setq prefix-arg current-prefix-arg)))))
+
(defun repeat-post-hook ()
"Function run after commands to set transient keymap for repeatable keys."
(let ((was-in-progress repeat-in-progress))
(setq repeat-in-progress nil)
- (when repeat-mode
- (let ((rep-map (or repeat-map (repeat--command-property 'repeat-map))))
- (when rep-map
- (when (and (symbolp rep-map) (boundp rep-map))
- (setq rep-map (symbol-value rep-map)))
- (let ((map (copy-keymap rep-map)))
-
- (when (and
- ;; Detect changes in the minibuffer state to allow repetitions
- ;; in the same minibuffer, but not when the minibuffer is activated
- ;; in the middle of repeating sequence (bug#47566).
- (or (< (minibuffer-depth) (car repeat--prev-mb))
- (eq current-minibuffer-command (cdr repeat--prev-mb)))
- (or (not repeat-keep-prefix) prefix-arg)
- (repeat-check-key last-command-event map))
-
- ;; Messaging
- (unless prefix-arg
- (funcall repeat-echo-function map))
-
- ;; Adding an exit key
- (when repeat-exit-key
- (define-key map (if (key-valid-p repeat-exit-key)
- (kbd repeat-exit-key)
- repeat-exit-key)
- 'ignore))
-
- (when (and repeat-keep-prefix (not prefix-arg))
- (setq prefix-arg current-prefix-arg))
-
- (setq repeat-in-progress t)
- (let ((exitfun (set-transient-map map)))
- (repeat--exit)
- (setq repeat-exit-function exitfun)
-
- (let* ((prop (repeat--command-property 'repeat-exit-timeout))
- (timeout (unless (eq prop 'no) (or prop repeat-exit-timeout))))
- (when timeout
- (setq repeat-exit-timer
- (run-with-idle-timer timeout nil #'repeat-exit))))))))))
+ (let ((map (repeat-get-map)))
+ (when (repeat-check-map map)
+ ;; Messaging
+ (funcall repeat-echo-function map)
+
+ ;; Adding an exit key
+ (when repeat-exit-key
+ (setq map (copy-keymap map))
+ (define-key map (if (key-valid-p repeat-exit-key)
+ (kbd repeat-exit-key)
+ repeat-exit-key)
+ 'ignore))
+
+ (setq repeat-in-progress t)
+ (repeat--exit)
+ (let ((exitfun (set-transient-map map)))
+ (setq repeat-exit-function exitfun)
+
+ (let* ((prop (repeat--command-property 'repeat-exit-timeout))
+ (timeout (unless (eq prop 'no) (or prop repeat-exit-timeout))))
+ (when timeout
+ (setq repeat-exit-timer
+ (run-with-idle-timer timeout nil #'repeat-exit)))))))
(setq repeat-map nil)
(setq repeat--prev-mb (cons (minibuffer-depth) current-minibuffer-command))
(push s (alist-get (get s 'repeat-map) keymaps)))))
(with-help-window (help-buffer)
(with-current-buffer standard-output
+ (setq-local outline-regexp "[*]+")
(insert "A list of keymaps used by commands with the symbol property `repeat-map'.\n")
(dolist (keymap (sort keymaps (lambda (a b)
"C-x w a b a c"
'((1 a) (1 b) (1 a)) "c")
(repeat-tests--check
- "M-C-a b a c"
+ "C-M-a b a c"
'((1 a) (1 b) (1 a)) "c")
(repeat-tests--check
- "M-C-z b a c"
+ "C-M-z b a c"
'((1 a)) "bac")
(unwind-protect
(progn
(put 'repeat-tests-call-a 'repeat-check-key 'no)
(repeat-tests--check
- "M-C-z b a c"
+ "C-M-z b a c"
'((1 a) (1 b) (1 a)) "c"))
(put 'repeat-tests-call-a 'repeat-check-key nil)))
(let ((repeat-check-key nil))
(repeat-tests--check
- "M-C-z b a c"
+ "C-M-z b a c"
'((1 a) (1 b) (1 a)) "c")
(unwind-protect
(progn
(put 'repeat-tests-call-a 'repeat-check-key t)
(repeat-tests--check
- "M-C-z b a c"
+ "C-M-z b a c"
'((1 a)) "bac"))
(put 'repeat-tests-call-a 'repeat-check-key nil))))))
(repeat-tests--check
"C-2 C-x w a C-3 c"
'((2 a)) "ccc"))
- ;; TODO: fix and uncomment
- ;; (let ((repeat-keep-prefix t))
- ;; (repeat-tests--check
- ;; "C-2 C-x w a b a b c"
- ;; '((2 a) (2 b) (2 a) (2 b)) "c")
- ;; (repeat-tests--check
- ;; "C-2 C-x w a C-1 C-2 b a C-3 C-4 b c"
- ;; '((2 a) (12 b) (12 a) (34 b)) "c"))
- )))
+ ;; Fixed in bug#51281 and bug#55986
+ (let ((repeat-keep-prefix t))
+ ;; Re-enable to take effect.
+ (repeat-mode -1) (repeat-mode +1)
+ (repeat-tests--check
+ "C-2 C-x w a b a b c"
+ '((2 a) (2 b) (2 a) (2 b)) "c")
+ ;; (repeat-tests--check
+ ;; "C-2 C-x w a C-1 C-2 b a C-3 C-4 b c"
+ ;; '((2 a) (12 b) (12 a) (34 b)) "c")
+ ))))
;; TODO: :tags '(:expensive-test) for repeat-exit-timeout