From: Juri Linkov Date: Wed, 17 Feb 2021 18:04:42 +0000 (+0200) Subject: New transient mode 'repeat-mode' to allow shorter key sequences (bug#46515) X-Git-Tag: emacs-28.0.90~3679 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=12409c9064c386a496dcbdca76b790108f6c1cad;p=emacs.git New transient mode 'repeat-mode' to allow shorter key sequences (bug#46515) * doc/emacs/basic.texi (Repeating): Document repeat-mode. * lisp/repeat.el (repeat-exit-key): New defcustom. (repeat-mode): New global minor mode. (repeat-post-hook): New function. * lisp/bindings.el (undo-repeat-map): New variable. (undo): Put 'repeat-map' property with 'undo-repeat-map'. (next-error-repeat-map): New variable. (next-error, previous-error): Put 'repeat-map' property with 'next-error-repeat-map'. * lisp/window.el (other-window-repeat-map): New variable. (other-window): Put 'repeat-map' property with 'other-window-repeat-map'. (resize-window-repeat-map): New variable. (enlarge-window, enlarge-window-horizontally) (shrink-window-horizontally, shrink-window): Put 'repeat-map' property with 'resize-window-repeat-map'. --- diff --git a/doc/emacs/basic.texi b/doc/emacs/basic.texi index 444b28f24be..8bf52d5dd30 100644 --- a/doc/emacs/basic.texi +++ b/doc/emacs/basic.texi @@ -880,3 +880,14 @@ characters. You can repeat that command (including its argument) three additional times, to delete a total of 80 characters, by typing @kbd{C-x z z z}. The first @kbd{C-x z} repeats the command once, and each subsequent @kbd{z} repeats it once again. + +@findex repeat-mode + Also you can activate @code{repeat-mode} that temporarily enables +a transient mode with short keys after a limited number of commands. +Currently supported shorter key sequences are @kbd{C-x u u} instead of +@kbd{C-x u C-x u} to undo many changes, @kbd{C-x o o} instead of +@kbd{C-x o C-x o} to switch several windows, @kbd{C-x @{ @{ @} @} ^ ^ +v v} to resize the selected window interactively, @kbd{M-g n n p p} to +navigate @code{next-error} matches. Any other key exits transient mode +and then is executed normally. The user option @code{repeat-exit-key} +defines an additional key to exit this transient mode. diff --git a/etc/NEWS b/etc/NEWS index 5c7acfdefaa..b96bcd9eccd 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2068,6 +2068,17 @@ the behavior introduced in Octave 3.8 of using a backslash as a line continuation marker within double-quoted strings, and an ellipsis everywhere else. +** Repeat + ++++ +*** New transient mode 'repeat-mode' to allow shorter key sequences. +You can type 'C-x u u' instead of 'C-x u C-x u' to undo many changes, +'C-x o o' instead of 'C-x o C-x o' to switch several windows, +'C-x { { } } ^ ^ v v' to resize the selected window interactively, +'M-g n n p p' to navigate next-error matches. Any other key exits +transient mode and then is executed normally. 'repeat-exit-key' +defines an additional key to exit mode like 'isearch-exit' (RET). + * New Modes and Packages in Emacs 28.1 diff --git a/lisp/bindings.el b/lisp/bindings.el index 2f4bab11cf5..7111ae6612c 100644 --- a/lisp/bindings.el +++ b/lisp/bindings.el @@ -950,6 +950,12 @@ if `inhibit-field-text-motion' is non-nil." ;; Richard said that we should not use C-x and I have ;; no idea whereas to bind it. Any suggestion welcome. -stef ;; (define-key ctl-x-map "U" 'undo-only) +(defvar undo-repeat-map + (let ((map (make-sparse-keymap))) + (define-key map "u" 'undo) + map) + "Keymap to repeat undo key sequences `C-x u u'. Used in `repeat-mode'.") +(put 'undo 'repeat-map 'undo-repeat-map) (define-key esc-map "!" 'shell-command) (define-key esc-map "|" 'shell-command-on-region) @@ -1036,6 +1042,17 @@ if `inhibit-field-text-motion' is non-nil." (define-key ctl-x-map "`" 'next-error) +(defvar next-error-repeat-map + (let ((map (make-sparse-keymap))) + (define-key map "n" 'next-error) + (define-key map "\M-n" 'next-error) + (define-key map "p" 'previous-error) + (define-key map "\M-p" 'previous-error) + map) + "Keymap to repeat next-error key sequences. Used in `repeat-mode'.") +(put 'next-error 'repeat-map 'next-error-repeat-map) +(put 'previous-error 'repeat-map 'next-error-repeat-map) + (defvar goto-map (make-sparse-keymap) "Keymap for navigation commands.") (define-key esc-map "g" goto-map) diff --git a/lisp/repeat.el b/lisp/repeat.el index 795577c93fc..84a613da0cf 100644 --- a/lisp/repeat.el +++ b/lisp/repeat.el @@ -329,6 +329,77 @@ recently executed command not bound to an input event\"." ;;;;; ************************* EMACS CONTROL ************************* ;;;;; + +;; And now for something completely different. + +;;; repeat-mode + +(defcustom repeat-exit-key nil + "Key that stops the modal repeating of keys in sequence. +For example, you can set it to like `isearch-exit'." + :type '(choice (const :tag "No special key to exit repeating sequence" nil) + (key-sequence :tag "Key that exits repeating sequence")) + :group 'convenience + :version "28.1") + +;;;###autoload +(define-minor-mode repeat-mode + "Toggle Repeat mode. +When Repeat mode is enabled, and the command symbol has the property named +`repeat-map', this map is activated temporarily for the next command." + :global t :group 'convenience + (if (not repeat-mode) + (remove-hook 'post-command-hook 'repeat-post-hook) + (add-hook 'post-command-hook 'repeat-post-hook) + (let* ((keymaps nil) + (commands (all-completions + "" obarray (lambda (s) + (and (commandp s) + (get s 'repeat-map) + (push (get s 'repeat-map) keymaps)))))) + (message "Repeat mode is enabled for %d commands and %d keymaps" + (length commands) + (length (delete-dups keymaps)))))) + +(defun repeat-post-hook () + "Function run after commands to set transient keymap for repeatable keys." + (when repeat-mode + (let ((repeat-map (and (symbolp this-command) + (get this-command 'repeat-map)))) + (when repeat-map + (when (boundp repeat-map) + (setq repeat-map (symbol-value repeat-map))) + (let ((map (copy-keymap repeat-map)) + keys mess) + (map-keymap (lambda (key _) (push key keys)) map) + + ;; Exit when the last char is not among repeatable keys, + ;; so e.g. `C-x u u' repeats undo, whereas `C-/ u' doesn't. + (when (or (memq last-command-event keys) + (memq this-original-command '(universal-argument + universal-argument-more + digit-argument + negative-argument))) + ;; Messaging + (setq mess (format-message + "Repeat with %s%s" + (mapconcat (lambda (key) + (key-description (vector key))) + keys ", ") + (if repeat-exit-key + (format ", or exit with %s" + (key-description repeat-exit-key)) + ""))) + (if (current-message) + (message "%s [%s]" (current-message) mess) + (message mess)) + + ;; Adding an exit key + (when repeat-exit-key + (define-key map repeat-exit-key 'ignore)) + + (set-transient-map map))))))) + (provide 'repeat) ;;; repeat.el ends here diff --git a/lisp/window.el b/lisp/window.el index 2d0a73b426d..cfd9876ed05 100644 --- a/lisp/window.el +++ b/lisp/window.el @@ -10252,6 +10252,28 @@ displaying that processes's buffer." (define-key ctl-x-4-map "1" 'same-window-prefix) (define-key ctl-x-4-map "4" 'other-window-prefix) +(defvar other-window-repeat-map + (let ((map (make-sparse-keymap))) + (define-key map "o" 'other-window) + map) + "Keymap to repeat other-window key sequences. Used in `repeat-mode'.") +(put 'other-window 'repeat-map 'other-window-repeat-map) + +(defvar resize-window-repeat-map + (let ((map (make-sparse-keymap))) + ;; Standard keys: + (define-key map "^" 'enlarge-window) + (define-key map "}" 'enlarge-window-horizontally) + (define-key map "{" 'shrink-window-horizontally) + ;; Additional keys: + (define-key map "v" 'shrink-window) + map) + "Keymap to repeat window resizing commands. Used in `repeat-mode'.") +(put 'enlarge-window 'repeat-map 'resize-window-repeat-map) +(put 'enlarge-window-horizontally 'repeat-map 'resize-window-repeat-map) +(put 'shrink-window-horizontally 'repeat-map 'resize-window-repeat-map) +(put 'shrink-window 'repeat-map 'resize-window-repeat-map) + (provide 'window) ;;; window.el ends here